diff --git a/lib/compilers/java.js b/lib/compilers/java.js index 69f5171ab..37e84ef9a 100644 --- a/lib/compilers/java.js +++ b/lib/compilers/java.js @@ -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 = ''; - } else { - oneResult.objdumpTime = objResult.execTime; - } - return oneResult; - })); + if (objResult.code !== 0) { + oneResult.asm = ''; + } 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."":()V - // Or match the "default: " block inside a lookupswitch instruction + // Or match the "default: " 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, };