Skip to content
Draft
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
185 changes: 181 additions & 4 deletions src/compiler/code-generator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import {
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 @@ -576,6 +577,181 @@ const codeGenerators: { [type: string]: (node: Node, cg: CodeGenerator) => Compi
return { stackSize: maxStack, resultType: resType }
},

TryStatement: (node: Node, cg: CodeGenerator) => {
let maxStack = 0
const { block, catches } = node as any
const finallyNode: any = (node as any).finally

const hasCatches = catches && catches.catchClauses && catches.catchClauses.length > 0

if (!hasCatches && !finallyNode) {
return { stackSize: compile(block, cg).stackSize, resultType: EMPTY_TYPE }
}

if (hasCatches || finallyNode) {
maxStack = Math.max(maxStack, 1)
}

const localExceptionTable: Array<{
startPc: number
endPc: number
handlerLabel: Label
catchType: number
}> = []

// mark start of protected region
const tryStart = cg.generateNewLabel()
tryStart.offset = cg.code.length

// compile try block
maxStack = Math.max(maxStack, compile(block, cg).stackSize)

// end of protected region (first instruction after try block)
const tryEnd = cg.generateNewLabel()
tryEnd.offset = cg.code.length

const catchAllLabel = finallyNode ? cg.generateNewLabel() : null

// If finally exists, add catch-all entry for the try block
if (finallyNode && catchAllLabel) {
localExceptionTable.push({
startPc: tryStart.offset,
endPc: tryEnd.offset,
handlerLabel: catchAllLabel,
catchType: 0
})
}

// For normal path: run finally block if it exists
if (finallyNode) {
finallyNode.blockStatements.forEach((stmt: any) => {
const { stackSize } = compile(stmt, cg)
maxStack = Math.max(maxStack, stackSize)
})
}

// jump over handlers when try completes normally
const afterHandlers = cg.generateNewLabel()
cg.addBranchInstr(OPCODE.GOTO, afterHandlers)

// For each catch clause, emit a handler and an exception table entry
if (hasCatches) {
for (const catchClause of catches.catchClauses) {
const handlerLabel = cg.generateNewLabel()
handlerLabel.offset = cg.code.length

// determine catch type index (constant pool)
const catchTypeNode = catchClause.catchFormalParameter.catchType
const catchTypeName = unannTypeToString(catchTypeNode.unannClassType)
let catchClassName = 'java/lang/Throwable'
try {
catchClassName = cg.symbolTable.queryClass(catchTypeName).name
} catch (e) {
catchClassName = catchTypeName.includes('/') ? catchTypeName : catchTypeName.replace(/\./g, '/')
}
const catchTypeIndex = cg.constantPoolManager.indexClassInfo(catchClassName)

// add exception table entry (startPc, endPc, handlerPc, catchType)
localExceptionTable.push({
startPc: tryStart.offset,
endPc: tryEnd.offset,
handlerLabel: handlerLabel,
catchType: catchTypeIndex
})

// create scope for catch variable
cg.symbolTable.extend()
const varName = catchClause.catchFormalParameter.variableDeclaratorId
const varTypeStr = unannTypeToString(catchTypeNode.unannClassType)
const varInfo = {
name: varName,
accessFlags: 0,
index: cg.maxLocals,
typeName: varTypeStr,
typeDescriptor: cg.symbolTable.generateFieldDescriptor(varTypeStr)
}
cg.symbolTable.insertVariableInfo(varInfo)
if (['J', 'D'].includes(varInfo.typeDescriptor)) {
cg.maxLocals += 2
} else {
cg.maxLocals++
}

// at handler entry, the exception object is on the stack; store it into the local
cg.code.push(OPCODE.ASTORE, varInfo.index)

const catchStartOffset = cg.code.length

// compile catch block statements
const catchBlock = catchClause.block
catchBlock.blockStatements.forEach((stmt: any) => {
const { stackSize } = compile(stmt, cg)
maxStack = Math.max(maxStack, stackSize)
})

const catchEndOffset = cg.code.length

// teardown catch scope
cg.symbolTable.teardown()

// If finally exists, add catch-all entry for this catch block
if (finallyNode && catchAllLabel && catchStartOffset < catchEndOffset) {
localExceptionTable.push({
startPc: catchStartOffset,
endPc: catchEndOffset,
handlerLabel: catchAllLabel,
catchType: 0
})
}

// For caught path: run finally block if it exists
if (finallyNode) {
finallyNode.blockStatements.forEach((stmt: any) => {
const { stackSize } = compile(stmt, cg)
maxStack = Math.max(maxStack, stackSize)
})
}

// after handler, jump to afterHandlers
cg.addBranchInstr(OPCODE.GOTO, afterHandlers)
}
}

// If finally exists, add a catch-all handler that runs finally then rethrows
if (finallyNode && catchAllLabel) {
catchAllLabel.offset = cg.code.length

// allocate temp local to store exception
const tempIndex = cg.maxLocals
cg.maxLocals += 1
cg.code.push(OPCODE.ASTORE, tempIndex)

// compile finally block inside catch-all
finallyNode.blockStatements.forEach((stmt: any) => {
const { stackSize } = compile(stmt, cg)
maxStack = Math.max(maxStack, stackSize)
})

// reload exception and rethrow
cg.code.push(OPCODE.ALOAD, tempIndex, OPCODE.ATHROW)
}

// place after-handlers label
afterHandlers.offset = cg.code.length

// Now that all labels are resolved, push to cg.exceptionTable
localExceptionTable.forEach(entry => {
cg.exceptionTable.push({
startPc: entry.startPc,
endPc: entry.endPc,
handlerPc: entry.handlerLabel.offset,
catchType: entry.catchType
})
})

return { stackSize: maxStack, resultType: EMPTY_TYPE }
},
Comment thread
kjw142857 marked this conversation as resolved.

TernaryExpression: (node: Node, cg: CodeGenerator) => {
let maxStack = 0
const {
Expand Down Expand Up @@ -1723,6 +1899,7 @@ class CodeGenerator {
constantPoolManager: ConstantPoolManager
maxLocals: number = 0
stackSize: number = 0
exceptionTable: Array<ExceptionHandler> = []
labels: Label[] = []
loopLabels: Label[][] = []
switchLabels: Label[] = []
Expand Down Expand Up @@ -1761,6 +1938,7 @@ class CodeGenerator {
generateCode(currentClass: string, methodNode: MethodDeclaration) {
this.symbolTable.extend()
this.currentClass = currentClass
this.exceptionTable = []
if (!methodNode.methodModifier.includes('static')) {
this.maxLocals++
}
Expand Down Expand Up @@ -1799,7 +1977,6 @@ class CodeGenerator {
}
this.resolveLabels()

const exceptionTable: Array<ExceptionHandler> = []
const attributes: Array<AttributeInfo> = []
const codeBuf = new Uint8Array(this.code).buffer
const dataView = new DataView(codeBuf)
Expand All @@ -1808,7 +1985,7 @@ class CodeGenerator {
const attributeLength =
12 +
this.code.length +
8 * exceptionTable.length +
8 * this.exceptionTable.length +
attributes.map(attr => attr.attributeLength + 6).reduce((acc, val) => acc + val, 0)
this.symbolTable.teardown()

Expand All @@ -1819,8 +1996,8 @@ class CodeGenerator {
maxLocals: this.maxLocals,
codeLength: this.code.length,
code: dataView,
exceptionTableLength: exceptionTable.length,
exceptionTable: exceptionTable,
exceptionTableLength: this.exceptionTable.length,
exceptionTable: this.exceptionTable,
attributesCount: attributes.length,
attributes: attributes
}
Expand Down
40 changes: 40 additions & 0 deletions src/jvm/__tests__/thread.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@ import { ReferenceClassData } from '../types/class/ClassData'
import { JvmObject } from '../types/reference/Object'
import Thread from '../../jvm/thread'
import JVM from '../../jvm/jvm'
import { JavaStackFrame } from '../../jvm/stackframe'
import { setupTest, TestThreadPool } from './__utils__/test-utils'
import { METHOD_FLAGS } from '../../ClassFile/types/methods'

let thread: Thread
let threadClass: ReferenceClassData
Expand Down Expand Up @@ -67,4 +69,42 @@ describe('Thread', () => {
test('should manage wide (64-bit) values on the operand stack correctly', () => {
// TODO
})

test('should route an exception to a matching try-catch handler in the current method', () => {
const setup = setupTest()
const { testLoader, thread: testThread, classes } = setup
const exceptionMethodClass = testLoader.createClass({
className: 'TryCatchTest',
loader: testLoader,
methods: [
{
accessFlags: [METHOD_FLAGS.ACC_PUBLIC],
name: 'test0',
descriptor: '()V',
attributes: [],
code: new DataView(new ArrayBuffer(1)),
exceptionTable: [
{
startPc: 0,
endPc: 1,
handlerPc: 0,
catchType: 'java/lang/NullPointerException'
}
]
}
],
}) as ReferenceClassData

const method = exceptionMethodClass.getMethod('test0()V')
expect(method).not.toBeNull()

testThread.invokeStackFrame(
new JavaStackFrame(exceptionMethodClass, method as any, 0, [])
)
const exceptionObj = classes.NullPointerException.instantiate()
testThread.throwException(exceptionObj)

expect(testThread.getPC()).toBe(0)
expect(testThread.peekStackFrame().operandStack).toEqual([exceptionObj])
})
})
60 changes: 36 additions & 24 deletions src/jvm/exception-table.ts
Original file line number Diff line number Diff line change
@@ -1,33 +1,45 @@
import { ClassData } from "./types/class/ClassData"

class Entry {
from: number
to: number
target: number
type: ClassData

constructor(from: number, to: number, target: number, type: ClassData) {
this.from = from;
this.to = to;
this.target = target;
this.type = type;
}
import { ClassData } from './types/class/ClassData'

export interface ExceptionTableEntry {
startPc: number
endPc: number
handlerPc: number
catchType: any | null
}

export class ExceptionTable {
private entries: Entry[]
export class ExceptionTable implements Iterable<ExceptionTableEntry> {
private entries: ExceptionTableEntry[]

constructor(entries?: ExceptionTableEntry[]) {
this.entries = entries ? entries.slice() : []
}

retrieve(line: number): Entry | null {
this.entries.forEach(entry => {
if (line >= entry.from && line <= entry.to) {
return entry
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(from: number, to: number, target: number, type: ClassData): void {
var entry = new Entry(from, to, target, type)
this.entries.push(entry)
insert(startPc: number, endPc: number, handlerPc: number, catchType: ClassData | 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
}
}
12 changes: 5 additions & 7 deletions src/jvm/types/class/Attributes.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
SourceFileAttribute,
StackMapFrame
} from '../../../ClassFile/types/attributes'
import { ExceptionTable } from '../../exception-table'
import { ConstantPool } from '../../constant-pool'
import {
ConstantClass,
Expand Down Expand Up @@ -45,7 +46,8 @@ export const info2Attribute = (info: AttributeInfo, constantPool: ConstantPool):
case 'Code':
const code = info as CodeAttribute
const attr: { [attributeName: string]: IAttribute } = {}
const exceptionTable = code.exceptionTable.map(handler => {
const exceptionTable = new ExceptionTable(
code.exceptionTable.map(handler => {
return {
startPc: handler.startPc,
endPc: handler.endPc,
Expand All @@ -54,6 +56,7 @@ export const info2Attribute = (info: AttributeInfo, constantPool: ConstantPool):
handler.catchType === 0 ? null : (constantPool.get(handler.catchType) as ConstantClass)
}
})
)
code.attributes.forEach(element => {
attr[(constantPool.get(element.attributeNameIndex) as ConstantUtf8).get()] = info2Attribute(
element,
Expand Down Expand Up @@ -244,12 +247,7 @@ export interface Code extends IAttribute {
codeLength: number
code: DataView
exceptionTableLength: number
exceptionTable: Array<{
startPc: number
endPc: number
handlerPc: number
catchType: ConstantClass | null
}>
exceptionTable: ExceptionTable
attributes: {
[attributeName: string]: IAttribute
}
Expand Down
Loading
Loading