Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
4 changes: 3 additions & 1 deletion src/compiler/binary-writer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,9 @@ export class BinaryWriter {
fs.writeFileSync(filename, binary)
}

private normalizeClassFile(classFile: ClassFile | Class | Array<ClassFile> | Array<Class>): ClassFile {
private normalizeClassFile(
classFile: ClassFile | Class | Array<ClassFile> | Array<Class>
): ClassFile {
if (Array.isArray(classFile)) {
if (classFile.length === 0) {
throw new Error('BinaryWriter expected a non-empty array of classes')
Expand Down
74 changes: 45 additions & 29 deletions src/compiler/code-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 = {
Expand Down Expand Up @@ -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 {
Expand Down Expand Up @@ -443,15 +443,15 @@ 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
finallyBlock.blockStatements.forEach((stmt: any) => {
compile(stmt, cg)
})
}

if (expr) {
const { stackSize: stackSize, resultType: resultType } = compile(expr, cg)
cg.code.push(resultType in returnOp ? returnOp[resultType] : OPCODE.ARETURN)
Expand All @@ -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])
Expand All @@ -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 }
},
Expand Down Expand Up @@ -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)

Expand Down Expand Up @@ -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.
Expand Down Expand Up @@ -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(','))
}
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down Expand Up @@ -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
Expand Down
9 changes: 6 additions & 3 deletions src/compiler/compiler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion src/compiler/error.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,4 +50,4 @@ export class OverrideFinalMethodError extends CompileError {
constructor(name: string) {
super(`Cannot override final method ${name}`)
}
}
}
18 changes: 11 additions & 7 deletions src/compiler/symbol-table.ts
Original file line number Diff line number Diff line change
@@ -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'],
Expand Down Expand Up @@ -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)
}
}
}
Expand Down
78 changes: 39 additions & 39 deletions src/jvm/exception-table.ts
Original file line number Diff line number Diff line change
@@ -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<ExceptionTableEntry> {
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<ExceptionTableEntry> {
return this.entries[Symbol.iterator]()
}
forEach(cb: (entry: ExceptionTableEntry, idx?: number) => void) {
this.entries.forEach(cb)
}

get length() {
return this.entries.length
}
}
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<ExceptionTableEntry> {
return this.entries[Symbol.iterator]()
}
forEach(cb: (entry: ExceptionTableEntry, idx?: number) => void) {
this.entries.forEach(cb)
}

get length() {
return this.entries.length
}
}
2 changes: 1 addition & 1 deletion src/jvm/types/class/Method.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'

Expand Down
4 changes: 2 additions & 2 deletions src/types/typeFactories/methodFactory.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
Loading