Skip to content

Commit

Permalink
Automatically detect Java entry points (#3614)
Browse files Browse the repository at this point in the history
  • Loading branch information
Headline committed May 2, 2022
1 parent a5e314a commit d68cc80
Showing 1 changed file with 78 additions and 43 deletions.
121 changes: 78 additions & 43 deletions lib/compilers/java.js
Expand Up @@ -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() {
Expand All @@ -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() {
Expand All @@ -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,
};

if (objResult.code !== 0) {
oneResult.asm = '<No output: javap returned ' + objResult.code + '>';
} else {
oneResult.objdumpTime = objResult.execTime;
}
return oneResult;
}));
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;
}),
);

const merged = {asm: []};
for (const result of results) {
Expand All @@ -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) {
Expand All @@ -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,
Expand All @@ -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';
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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]);
Expand Down Expand Up @@ -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;
Expand Down Expand Up @@ -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,
};
Expand Down

0 comments on commit d68cc80

Please sign in to comment.