diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 9f419239..788314ba 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -42,6 +42,10 @@ jobs: distribution: 'oracle' java-version: '17' - uses: ArtiomTr/jest-coverage-report-action@v2 + if: github.event_name == 'pull_request' with: test-script: yarn test annotations: none + - name: Run tests + if: github.event_name != 'pull_request' + run: yarn test diff --git a/src/compiler/binary-writer.ts b/src/compiler/binary-writer.ts index 8b97dfb0..e4f09b77 100644 --- a/src/compiler/binary-writer.ts +++ b/src/compiler/binary-writer.ts @@ -49,7 +49,9 @@ export class BinaryWriter { fs.writeFileSync(filename, binary) } - private normalizeClassFile(classFile: ClassFile | Class | Array | Array): ClassFile { + private normalizeClassFile( + classFile: ClassFile | Class | Array | Array + ): ClassFile { if (Array.isArray(classFile)) { if (classFile.length === 0) { throw new Error('BinaryWriter expected a non-empty array of classes') diff --git a/src/compiler/code-generator.ts b/src/compiler/code-generator.ts index 2eea5203..9506a1f2 100644 --- a/src/compiler/code-generator.ts +++ b/src/compiler/code-generator.ts @@ -31,13 +31,13 @@ import { CaseLabel } from '../ast/types/blocks-and-statements' import { MethodDeclaration, UnannType } from '../ast/types/classes' +import { unannTypeToString } from '../types/ast/utils' import { ConstantPoolManager } from './constant-pool-manager' import { AmbiguousMethodCallError, ConstructNotSupportedError, NoMethodMatchingSignatureError } from './error' -import { unannTypeToString } from '../types/ast/utils' import { FieldInfo, MethodInfos, SymbolInfo, SymbolTable, VariableInfo } from './symbol-table' type Label = { @@ -191,19 +191,19 @@ const EMPTY_TYPE: string = '' function areClassTypesCompatible(fromType: string, toType: string, cg: CodeGenerator): boolean { const cleanFrom = fromType.replace(/^L|;$/g, '') const cleanTo = toType.replace(/^L|;$/g, '') - if (cleanFrom === cleanTo) return true; + if (cleanFrom === cleanTo) return true try { - let current = cg.symbolTable.queryClass(cleanFrom); + let current = cg.symbolTable.queryClass(cleanFrom) while (current.parentClassName) { - const parentClean = current.parentClassName; - if (parentClean === cleanTo) return true; - current = cg.symbolTable.queryClass(parentClean); + const parentClean = current.parentClassName + if (parentClean === cleanTo) return true + current = cg.symbolTable.queryClass(parentClean) } } catch (e) { - return false; + return false } - return false; + return false } function handleImplicitTypeConversion(fromType: string, toType: string, cg: CodeGenerator): number { @@ -443,7 +443,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi ReturnStatement: (node: Node, cg: CodeGenerator) => { const { exp: expr } = node as ReturnStatement - + // Emit finally blocks from innermost to outermost before returning for (let i = cg.finallyBlockStack.length - 1; i >= 0; i--) { const finallyBlock = cg.finallyBlockStack[i] as any @@ -451,7 +451,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi compile(stmt, cg) }) } - + if (expr) { const { stackSize: stackSize, resultType: resultType } = compile(expr, cg) cg.code.push(resultType in returnOp ? returnOp[resultType] : OPCODE.ARETURN) @@ -470,7 +470,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi compile(stmt, cg) }) } - + if (cg.loopLabels.length > 0) { // If inside a loop, break jumps to the end of the loop cg.addBranchInstr(OPCODE.GOTO, cg.loopLabels[cg.loopLabels.length - 1][1]) @@ -491,7 +491,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi compile(stmt, cg) }) } - + cg.addBranchInstr(OPCODE.GOTO, cg.loopLabels[cg.loopLabels.length - 1][0]) return { stackSize: 0, resultType: EMPTY_TYPE } }, @@ -668,7 +668,9 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi try { catchClassName = cg.symbolTable.queryClass(catchTypeName).name } catch (e) { - catchClassName = catchTypeName.includes('/') ? catchTypeName : catchTypeName.replace(/\./g, '/') + catchClassName = catchTypeName.includes('/') + ? catchTypeName + : catchTypeName.replace(/\./g, '/') } const catchTypeIndex = cg.constantPoolManager.indexClassInfo(catchClassName) @@ -1052,30 +1054,30 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi // --- Handle super. calls --- if (n.identifier.startsWith('super.')) { candidateMethods = cg.symbolTable.queryMethod(n.identifier.slice(6)) as MethodInfos - candidateMethods = candidateMethods.filter(method => - method.className == cg.symbolTable.queryClass(cg.currentClass).parentClassName) - cg.code.push(OPCODE.ALOAD, 0); + candidateMethods = candidateMethods.filter( + method => method.className == cg.symbolTable.queryClass(cg.currentClass).parentClassName + ) + cg.code.push(OPCODE.ALOAD, 0) } // --- Handle qualified calls (e.g. System.out.println or p.show) --- else if (n.identifier.includes('.')) { - const lastDot = n.identifier.lastIndexOf('.'); - const receiverStr = n.identifier.slice(0, lastDot); + const lastDot = n.identifier.lastIndexOf('.') + const receiverStr = n.identifier.slice(0, lastDot) if (receiverStr === 'this') { candidateMethods = cg.symbolTable.queryMethod(n.identifier.slice(5)) as MethodInfos - candidateMethods = candidateMethods.filter(method => - method.className == cg.currentClass) - cg.code.push(OPCODE.ALOAD, 0); + candidateMethods = candidateMethods.filter(method => method.className == cg.currentClass) + cg.code.push(OPCODE.ALOAD, 0) } else { - const recvRes = compile({ kind: 'ExpressionName', name: receiverStr }, cg); - maxStack = Math.max(maxStack, recvRes.stackSize); + const recvRes = compile({ kind: 'ExpressionName', name: receiverStr }, cg) + maxStack = Math.max(maxStack, recvRes.stackSize) candidateMethods = cg.symbolTable.queryMethod(n.identifier).pop() as MethodInfos } } // --- Handle unqualified calls --- else { candidateMethods = cg.symbolTable.queryMethod(n.identifier) as MethodInfos - unqualifiedCall = true; + unqualifiedCall = true } // Filter candidate methods by matching the argument list. @@ -1117,11 +1119,15 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi .slice(1, methodMatches[i].typeDescriptor.indexOf(')')) .match(/(\[+[BCDFIJSZ])|(\[+L[^;]+;)|[BCDFIJSZ]|L[^;]+;/g) || [] if ( - candParams.map((p, idx) => isSubtype(p, currParams[idx], cg)).reduce((a, b) => a && b, true) + candParams + .map((p, idx) => isSubtype(p, currParams[idx], cg)) + .reduce((a, b) => a && b, true) ) { selectedMethod = methodMatches[i] } else if ( - !currParams.map((p, idx) => isSubtype(p, candParams[idx], cg)).reduce((a, b) => a && b, true) + !currParams + .map((p, idx) => isSubtype(p, candParams[idx], cg)) + .reduce((a, b) => a && b, true) ) { throw new AmbiguousMethodCallError(n.identifier + argDescs.join(',')) } @@ -1480,7 +1486,7 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi try { info = cg.symbolTable.queryVariable(name) } catch (e) { - return { stackSize: 1, resultType: 'Ljava/lang/Class;' }; + return { stackSize: 1, resultType: 'Ljava/lang/Class;' } } if (Array.isArray(info)) { const fieldInfos = info @@ -1684,7 +1690,12 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi // Generate lookup table (pairs of case values and corresponding labels) caseValues.forEach((value, index) => { // push 4-byte key - cg.code.push((value >> 24) & 0xff, (value >> 16) & 0xff, (value >> 8) & 0xff, value & 0xff) + cg.code.push( + (value >> 24) & 0xff, + (value >> 16) & 0xff, + (value >> 8) & 0xff, + value & 0xff + ) // reserve 4 bytes for the branch target cg.code.push(0, 0, 0, 0) // label offset starts after the 4-byte key @@ -1827,7 +1838,12 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi // Populate LOOKUPSWITCH const hashLabels: Label[] = [] hashCaseMap.forEach((label, hashCode) => { - cg.code.push((hashCode >> 24) & 0xff, (hashCode >> 16) & 0xff, (hashCode >> 8) & 0xff, hashCode & 0xff) + cg.code.push( + (hashCode >> 24) & 0xff, + (hashCode >> 16) & 0xff, + (hashCode >> 8) & 0xff, + hashCode & 0xff + ) // reserve 4 bytes for the branch target cg.code.push(0, 0, 0, 0) // label offset starts after the 4-byte key diff --git a/src/compiler/compiler.ts b/src/compiler/compiler.ts index 3e116800..a186c598 100644 --- a/src/compiler/compiler.ts +++ b/src/compiler/compiler.ts @@ -58,15 +58,18 @@ export class Compiler { const className = decl.typeIdentifier const parentClassName = decl.sclass ? decl.sclass : 'java/lang/Object' const accessFlags = generateClassAccessFlags(decl.classModifier) - this.symbolTable.insertClassInfo( - { name: className, accessFlags: accessFlags, parentClassName: parentClassName }) + this.symbolTable.insertClassInfo({ + name: className, + accessFlags: accessFlags, + parentClassName: parentClassName + }) this.symbolTable.returnToRoot() }) ast.topLevelClassOrInterfaceDeclarations.forEach(decl => { this.resetClassFileState() const classFile = this.compileClass(decl) - classFiles.push({classFile: classFile, className: this.className}) + classFiles.push({ classFile: classFile, className: this.className }) }) return classFiles diff --git a/src/compiler/error.ts b/src/compiler/error.ts index 1044d4fd..e0355528 100644 --- a/src/compiler/error.ts +++ b/src/compiler/error.ts @@ -50,4 +50,4 @@ export class OverrideFinalMethodError extends CompileError { constructor(name: string) { super(`Cannot override final method ${name}`) } -} \ No newline at end of file +} diff --git a/src/compiler/symbol-table.ts b/src/compiler/symbol-table.ts index 394ffd34..314ebcc4 100644 --- a/src/compiler/symbol-table.ts +++ b/src/compiler/symbol-table.ts @@ -1,18 +1,19 @@ import { UnannType } from '../ast/types/classes' import { ImportDeclaration } from '../ast/types/packages-and-modules' +import { METHOD_FLAGS } from '../ClassFile/types/methods' import { generateClassAccessFlags, generateFieldAccessFlags, generateMethodAccessFlags } from './compiler-utils' import { - InvalidMethodCallError, OverrideFinalMethodError, + InvalidMethodCallError, + OverrideFinalMethodError, SymbolCannotBeResolvedError, SymbolNotFoundError, SymbolRedeclarationError } from './error' import { libraries } from './import/libs' -import { METHOD_FLAGS } from '../ClassFile/types/methods' export const typeMap = new Map([ ['byte', 'B'], @@ -209,14 +210,17 @@ export class SymbolTable { const key = generateSymbol(info.name, SymbolType.METHOD) for (let i = this.curClassIdx - 1; i > 0; i--) { - const parentTable = this.tables[i]; + const parentTable = this.tables[i] if (parentTable.has(key)) { - const parentMethods = parentTable.get(key)!.info; + const parentMethods = parentTable.get(key)!.info if (Array.isArray(parentMethods)) { for (const m of parentMethods) { - if (m.typeDescriptor === info.typeDescriptor && (m.accessFlags & METHOD_FLAGS.ACC_FINAL) - && m.className == info.parentClassName) { - throw new OverrideFinalMethodError(info.name); + if ( + m.typeDescriptor === info.typeDescriptor && + m.accessFlags & METHOD_FLAGS.ACC_FINAL && + m.className == info.parentClassName + ) { + throw new OverrideFinalMethodError(info.name) } } } diff --git a/src/jvm/exception-table.ts b/src/jvm/exception-table.ts index 7bcf0d99..b9974fd9 100644 --- a/src/jvm/exception-table.ts +++ b/src/jvm/exception-table.ts @@ -1,45 +1,45 @@ -import { ClassData } from './types/class/ClassData' +import { ConstantClass } from './types/class/Constants' export interface ExceptionTableEntry { - startPc: number - endPc: number - handlerPc: number - catchType: any | null + startPc: number + endPc: number + handlerPc: number + catchType: ConstantClass | null } export class ExceptionTable implements Iterable { - private entries: ExceptionTableEntry[] - - constructor(entries?: ExceptionTableEntry[]) { - this.entries = entries ? entries.slice() : [] - } - - retrieve(pc: number): ExceptionTableEntry | null { - for (let i = 0; i < this.entries.length; i++) { - const e = this.entries[i] - if (pc >= e.startPc && pc < e.endPc) { - return e - } - } - return null - } - - insert(startPc: number, endPc: number, handlerPc: number, catchType: ClassData | null): void { - this.entries.push({ startPc, endPc, handlerPc, catchType }) - } - - toArray(): ExceptionTableEntry[] { - return this.entries.slice() + private entries: ExceptionTableEntry[] + + constructor(entries?: ExceptionTableEntry[]) { + this.entries = entries ? entries.slice() : [] + } + + retrieve(pc: number): ExceptionTableEntry | null { + for (let i = 0; i < this.entries.length; i++) { + const e = this.entries[i] + if (pc >= e.startPc && pc < e.endPc) { + return e + } } - - [Symbol.iterator](): Iterator { - return this.entries[Symbol.iterator]() - } - forEach(cb: (entry: ExceptionTableEntry, idx?: number) => void) { - this.entries.forEach(cb) - } - - get length() { - return this.entries.length - } -} \ No newline at end of file + return null + } + + insert(startPc: number, endPc: number, handlerPc: number, catchType: ConstantClass | null): void { + this.entries.push({ startPc, endPc, handlerPc, catchType }) + } + + toArray(): ExceptionTableEntry[] { + return this.entries.slice() + } + + [Symbol.iterator](): Iterator { + return this.entries[Symbol.iterator]() + } + forEach(cb: (entry: ExceptionTableEntry, idx?: number) => void) { + this.entries.forEach(cb) + } + + get length() { + return this.entries.length + } +} diff --git a/src/jvm/types/class/Method.ts b/src/jvm/types/class/Method.ts index 8e803643..a6c3ec74 100644 --- a/src/jvm/types/class/Method.ts +++ b/src/jvm/types/class/Method.ts @@ -5,8 +5,8 @@ import Thread from '../../thread' import { attrInfo2Interface, parseMethodDescriptor, getArgs, logger } from '../../utils' import { ErrorResult, ImmediateResult, ResultType, SuccessResult } from '../Result' import { JavaType, JvmObject } from '../reference/Object' -import { Code, Exceptions, IAttribute, NestHost, Signature } from './Attributes' import { ExceptionTable } from '../../exception-table' +import { Code, Exceptions, IAttribute, NestHost, Signature } from './Attributes' import { ReferenceClassData, ArrayClassData, ClassData } from './ClassData' import { ConstantClass, ConstantMethodref, ConstantNameAndType, ConstantUtf8 } from './Constants' diff --git a/src/types/typeFactories/methodFactory.ts b/src/types/typeFactories/methodFactory.ts index c415fd10..14a70333 100644 --- a/src/types/typeFactories/methodFactory.ts +++ b/src/types/typeFactories/methodFactory.ts @@ -70,8 +70,8 @@ export const createMethod = ( // Add declared exceptions (throws clause) if present const throwsNode: any = node.kind === 'MethodDeclaration' ? node.methodHeader.throws : node.throws - if (throwsNode && (throwsNode as any).exceptionTypeList) { - for (const exceptionTypeNode of (throwsNode as any).exceptionTypeList) { + if (throwsNode && (throwsNode).exceptionTypeList) { + for (const exceptionTypeNode of (throwsNode).exceptionTypeList) { const exceptionType = frame.getType( unannTypeToString(exceptionTypeNode), exceptionTypeNode.location