mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2026-05-19 17:32:46 -04:00
266 lines
10 KiB
JavaScript
266 lines
10 KiB
JavaScript
// 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;
|