diff options
Diffstat (limited to 'lib/compilers')
-rw-r--r-- | lib/compilers/argument-parsers.js | 8 | ||||
-rw-r--r-- | lib/compilers/java.js | 265 |
2 files changed, 273 insertions, 0 deletions
diff --git a/lib/compilers/argument-parsers.js b/lib/compilers/argument-parsers.js index adae6e81f..1a95fb617 100644 --- a/lib/compilers/argument-parsers.js +++ b/lib/compilers/argument-parsers.js @@ -166,6 +166,13 @@ class ISPCParser extends BaseParser { } } +class JavaParser extends BaseParser { + + static parse(compiler) { + return JavaParser.getOptions(compiler, "-help").then(() => compiler); + } +} + class VCParser extends BaseParser { static parse(compiler) { return Promise.all([ @@ -248,6 +255,7 @@ module.exports = { Base: BaseParser, Clang: ClangParser, GCC: GCCParser, + Java: JavaParser, VC: VCParser, Pascal: PascalParser, ISPC: ISPCParser diff --git a/lib/compilers/java.js b/lib/compilers/java.js new file mode 100644 index 000000000..b3b04e9ea --- /dev/null +++ b/lib/compilers/java.js @@ -0,0 +1,265 @@ +// Copyright (c) 2017, Rubén Rincón +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions are met: +// +// * Redistributions of source code must retain the above copyright notice, +// this list of conditions and the following disclaimer. +// * Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +// AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +// ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE +// LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +// CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +// SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +// INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +// CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +// ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +// POSSIBILITY OF SUCH DAMAGE. + +const BaseCompiler = require('../base-compiler'), + argumentParsers = require("./argument-parsers"), + fs = require('fs-extra'), + path = require('path'), + logger = require('../logger').logger; + +class JavaCompiler extends BaseCompiler { + constructor(compilerInfo, env) { + // Default is to disable all "cosmetic" filters + if (!compilerInfo.disabledFilters) { + compilerInfo.disabledFilters = ['labels', 'directives', 'commentOnly', 'trim']; + } + + super(compilerInfo, env); + } + + + objdump(outputFilename, result, maxSize) { + const dirPath = path.dirname(outputFilename); + return fs.readdir(dirPath) + .then(files => { + logger.verbose("Class files: ", files); + return files.filter(f => f.endsWith('.class')); + }) + .then(classFiles => { + return Promise.all(classFiles.map(classFile => { + const args = [ + // Prints out disassembled code, i.e., the instructions that comprise the Java bytecodes, + // for each of the methods in the class. + '-c', + // Prints out line and local variable tables. + '-l', + classFile + ]; + return this.exec(this.compiler.objdumper, args, {maxOutput: maxSize, customCwd: dirPath}) + .then(objResult => { + const oneResult = {}; + oneResult.asm = objResult.stdout; + if (objResult.code !== 0) { + oneResult.asm = "<No output: javap returned " + objResult.code + ">"; + } + return oneResult; + }); + })); + }) + .then(results => { + const merged = {asm: []}; + for (const result of results) { + const asmBackup = merged.asm; + Object.assign(merged, result); + merged.asm = asmBackup; + merged.asm.push(result.asm); + } + + result.asm = merged.asm; + return result; + }); + } + + + optionsForFilter(filters) { + // Forcibly enable javap + filters.binary = true; + + return [ + '-Xlint:all', + ]; + } + + + getArgumentParser() { + return argumentParsers.Java; + } + + getOutputFilename(dirPath) { + return path.join(dirPath, `${path.basename(this.compileFilename, this.lang.extensions[0])}.class`); + } + + + filterUserOptions(userOptions) { + const filteredOptions = []; + let toSkip = 0; + + const oneArgBlacklist = [ + // -d directory + // Sets the destination directory for class files. + "-d", + // -s directory + // Specifies the directory used to place the generated source files. + "-s", + // --source-path path or -sourcepath path + // Specifies where to find input source files. + "--source-path", "-sourcepath"]; + for (const userOption of userOptions) { + if (toSkip > 0) { + toSkip--; + continue; + } + if (oneArgBlacklist.indexOf(userOption) !== -1) { + toSkip = 1; + continue; + } + + filteredOptions.push(userOption); + } + + return filteredOptions; + } + + processAsm(result) { + // Handle "error" documents. + if (result.asm.indexOf("\n") === -1 && result.asm[0] === '<') { + return [{text: result.asm, source: null}]; + } + + // result.asm is an array of javap stdouts + const parseds = result.asm.map(asm => this.parseAsmForClass(asm)); + // Sort class file outputs according to first source line they reference + parseds.sort((o1, o2) => o1.firstSourceLine - o2.firstSourceLine); + + const segments = []; + parseds.forEach(parsed => { + for (let i = 0; i < parsed.textsBeforeMethod.length; i++) { + // Line-based highlighting doesn't work if some segments span multiple lines, + // even if they don't have a source line number + // -> split the lines and create segment for each separately + for (const line of parsed.textsBeforeMethod[i].split("\n")) { + // javap output always starts with "Compiled from" on first line, discard these lines. + if (line.startsWith("Compiled from")) { + continue; + } + segments.push({ + text: line, + source: null + }); + } + + // textsBeforeMethod[last] is actually *after* the last method. + // Check whether there is a method following the text block + if (i < parsed.methods.length) { + parsed.methods[i].instructions.forEach(({text, sourceLine}) => { + segments.push({text: text, source: {file: null, line: sourceLine}}); + }); + } + } + }); + return segments; + } + + parseAsmForClass(javapOut) { + const textsBeforeMethod = []; + const methods = []; + const [linesSkipped, ...codeAndLineNumberTables] = javapOut.split(/^\s+Code:\s*$\s/m); + textsBeforeMethod.push(linesSkipped); + + for (const codeAndLineNumberTable of codeAndLineNumberTables) { + const method = { + instructions: [] + }; + methods.push(method); + + + for (const codeLineCandidate of codeAndLineNumberTable.split("\n")) { + // Match + // 1: invokespecial #1 // Method java/lang/Object."<init>":()V + const match = codeLineCandidate.match(/\s+(\d+): (.*)/); + if (match) { + const instrOffset = Number.parseInt(match[1]); + method.instructions.push({ + instrOffset: instrOffset, + text: codeLineCandidate + }); + } else { + break; + } + } + + + let lineRegex = /line\s*(\d+):\s*(\d+)/g; + let m; + let currentInstr = 0; + let currentSourceLine = -1; + let lastIndex = -1; + do { + m = lineRegex.exec(codeAndLineNumberTable); + if (m) { + // If exec doesn't find a match anymore, lineRegex.lastIndex will be reset to 0 + // therefore, cache value here on match + lastIndex = lineRegex.lastIndex; + const [, sourceLineS, instructionS] = m; + logger.verbose("Found source mapping: ", sourceLineS, "to instruction", instructionS); + const instrOffset = Number.parseInt(instructionS); + + // Some instructions don't receive an explicit line number. + // They are all assigned to the previous explicit line number, + // because the line consists of multiple instructions. + while (currentInstr < method.instructions.length && + method.instructions[currentInstr].instrOffset !== instrOffset ) { + + if (currentSourceLine !== -1) { + // instructions without explicit line number get assigned the last explicit/same line number + method.instructions[currentInstr].sourceLine = currentSourceLine; + } else { + logger.error("Skipping over instruction even though currentSourceLine == -1"); + } + currentInstr++; + } + + const sourceLine = Number.parseInt(sourceLineS); + currentSourceLine = sourceLine; + method.instructions[currentInstr].sourceLine = currentSourceLine; + + if (typeof method.startLine === "undefined") { + method.startLine = sourceLine; + } + // method.instructions.push({sourceLine: instrOffset}); + } + } while (m); + if (lastIndex !== -1) { + // Get "interesting" text after the LineNumbers table (header of next method/tail of file) + textsBeforeMethod.push(codeAndLineNumberTable.substr(lastIndex)); + } + + if (currentSourceLine !== -1) { + // Assign remaining instructions to the same source line + while (currentInstr + 1 < method.instructions.length){ + currentInstr++; + method.instructions[currentInstr].sourceLine = currentSourceLine; + } + } + } + return { + // Used for sorting + firstSourceLine: methods.reduce((p, m) => p === -1 ? m.startLine : Math.min(p, m.startLine), -1), + methods: methods, + textsBeforeMethod + }; + } +} + +module.exports = JavaCompiler; |