mirror of
https://github.com/compiler-explorer/compiler-explorer.git
synced 2025-12-27 11:44:09 -05:00
Automatically detect Java entry points (#3614)
This commit is contained in:
@@ -26,11 +26,11 @@ import path from 'path';
|
||||
|
||||
import fs from 'fs-extra';
|
||||
|
||||
import { BaseCompiler } from '../base-compiler';
|
||||
import { logger } from '../logger';
|
||||
import {BaseCompiler} from '../base-compiler';
|
||||
import {logger} from '../logger';
|
||||
import * as utils from '../utils';
|
||||
|
||||
import { JavaParser } from './argument-parsers';
|
||||
import {JavaParser} from './argument-parsers';
|
||||
|
||||
export class JavaCompiler extends BaseCompiler {
|
||||
static get key() {
|
||||
@@ -44,6 +44,7 @@ export class JavaCompiler extends BaseCompiler {
|
||||
}
|
||||
super(compilerInfo, env);
|
||||
this.javaRuntime = this.compilerProps(`compiler.${this.compiler.id}.runtime`);
|
||||
this.mainRegex = /public static ?(.*?) void main\(java\.lang\.String\[]\)/;
|
||||
}
|
||||
|
||||
getSharedLibraryPathsAsArguments() {
|
||||
@@ -54,33 +55,40 @@ export class JavaCompiler extends BaseCompiler {
|
||||
const dirPath = path.dirname(outputFilename);
|
||||
const files = await fs.readdir(dirPath);
|
||||
logger.verbose('Class files: ', files);
|
||||
const results = await Promise.all(files.filter(f => f.endsWith('.class')).map(async 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',
|
||||
// Private things
|
||||
'-p',
|
||||
// Final constants
|
||||
'-constants',
|
||||
// Verbose - ideally we'd enable this and then we get constant pools too. Needs work to parse.
|
||||
//'-v',
|
||||
classFile,
|
||||
];
|
||||
const objResult = await this.exec(this.compiler.objdumper, args, {maxOutput: maxSize, customCwd: dirPath});
|
||||
const oneResult = {
|
||||
asm: objResult.stdout,
|
||||
};
|
||||
const results = await Promise.all(
|
||||
files
|
||||
.filter(f => f.endsWith('.class'))
|
||||
.map(async 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',
|
||||
// Private things
|
||||
'-p',
|
||||
// Final constants
|
||||
'-constants',
|
||||
// Verbose - ideally we'd enable this and then we get constant pools too. Needs work to parse.
|
||||
//'-v',
|
||||
classFile,
|
||||
];
|
||||
const objResult = await this.exec(this.compiler.objdumper, args, {
|
||||
maxOutput: maxSize,
|
||||
customCwd: dirPath,
|
||||
});
|
||||
const oneResult = {
|
||||
asm: objResult.stdout,
|
||||
};
|
||||
|
||||
if (objResult.code !== 0) {
|
||||
oneResult.asm = '<No output: javap returned ' + objResult.code + '>';
|
||||
} else {
|
||||
oneResult.objdumpTime = objResult.execTime;
|
||||
}
|
||||
return oneResult;
|
||||
}));
|
||||
if (objResult.code !== 0) {
|
||||
oneResult.asm = '<No output: javap returned ' + objResult.code + '>';
|
||||
} else {
|
||||
oneResult.objdumpTime = objResult.execTime;
|
||||
}
|
||||
return oneResult;
|
||||
}),
|
||||
);
|
||||
|
||||
const merged = {asm: []};
|
||||
for (const result of results) {
|
||||
@@ -98,11 +106,7 @@ export class JavaCompiler extends BaseCompiler {
|
||||
// Forcibly enable javap
|
||||
filters.binary = true;
|
||||
|
||||
return [
|
||||
'-Xlint:all',
|
||||
'-encoding',
|
||||
'utf8',
|
||||
];
|
||||
return ['-Xlint:all', '-encoding', 'utf8'];
|
||||
}
|
||||
|
||||
async handleInterpreting(key, executeParameters) {
|
||||
@@ -113,7 +117,7 @@ export class JavaCompiler extends BaseCompiler {
|
||||
'-XX:-UseDynamicNumberOfCompilerThreads',
|
||||
'-XX:-UseDynamicNumberOfGCThreads',
|
||||
'-XX:+UseSerialGC', // Disable parallell/concurrent garbage collector
|
||||
this.getMainClassName(),
|
||||
await this.getMainClassName(compileResult.dirPath),
|
||||
'-cp',
|
||||
compileResult.dirPath,
|
||||
...executeParameters.args,
|
||||
@@ -124,8 +128,36 @@ export class JavaCompiler extends BaseCompiler {
|
||||
return result;
|
||||
}
|
||||
|
||||
getMainClassName() {
|
||||
// TODO(supergrecko): The main class name is currently hardcoded
|
||||
async getMainClassName(dirPath) {
|
||||
const maxSize = this.env.ceProps('max-asm-size', 64 * 1024 * 1024);
|
||||
const files = await fs.readdir(dirPath);
|
||||
const results = await Promise.all(
|
||||
files
|
||||
.filter(f => f.endsWith('.class'))
|
||||
.map(async classFile => {
|
||||
const options = {
|
||||
maxOutput: maxSize,
|
||||
customCwd: dirPath,
|
||||
};
|
||||
const objResult = await this.exec(this.compiler.objdumper, [classFile], options);
|
||||
if (objResult.code !== 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (this.mainRegex.test(objResult.stdout)) {
|
||||
return classFile;
|
||||
}
|
||||
return null;
|
||||
}),
|
||||
);
|
||||
|
||||
const candidates = results.filter(file => file !== null);
|
||||
if (candidates.length > 0) {
|
||||
// In case of multiple candidates, we'll just take the first one.
|
||||
const fileName = candidates[0];
|
||||
return fileName.substring(0, fileName.lastIndexOf('.'));
|
||||
}
|
||||
// We were unable to find a main method, let's error out assuming "Main"
|
||||
return 'Main';
|
||||
}
|
||||
|
||||
@@ -167,7 +199,8 @@ export class JavaCompiler extends BaseCompiler {
|
||||
'-s',
|
||||
// --source-path path or -sourcepath path
|
||||
// Specifies where to find input source files.
|
||||
'--source-path', '-sourcepath',
|
||||
'--source-path',
|
||||
'-sourcepath',
|
||||
]);
|
||||
|
||||
return this.filterUserOptionsWithArg(userOptions, oneArgForbiddenList);
|
||||
@@ -238,7 +271,7 @@ export class JavaCompiler extends BaseCompiler {
|
||||
for (const codeLineCandidate of utils.splitLines(codeAndLineNumberTable)) {
|
||||
// Match
|
||||
// 1: invokespecial #1 // Method java/lang/Object."<init>":()V
|
||||
// Or match the "default: <code>" block inside a lookupswitch instruction
|
||||
// Or match the "default: <code>" block inside a lookupswitch instruction
|
||||
const match = codeLineCandidate.match(/\s+(\d+|default): (.*)/);
|
||||
if (match) {
|
||||
const instrOffset = Number.parseInt(match[1]);
|
||||
@@ -281,8 +314,10 @@ export class JavaCompiler extends BaseCompiler {
|
||||
// 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) {
|
||||
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;
|
||||
@@ -320,7 +355,7 @@ export class JavaCompiler extends BaseCompiler {
|
||||
}
|
||||
return {
|
||||
// Used for sorting
|
||||
firstSourceLine: methods.reduce((p, m) => p === -1 ? m.startLine : Math.min(p, m.startLine), -1),
|
||||
firstSourceLine: methods.reduce((p, m) => (p === -1 ? m.startLine : Math.min(p, m.startLine)), -1),
|
||||
methods: methods,
|
||||
textsBeforeMethod,
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user