Skip to content

Exceptions Implementation

opencode-agent[bot] edited this page May 10, 2026 · 2 revisions

Exceptions-Implementation

How JNode implements Java exception handling at the VM and JIT levels.

Overview

JNode's exception handling spans two layers: bytecode interpretation and native code compilation. The JIT compiler (NativeCodeCompiler) transforms Java's exception table structure into a compact native representation, while the VM runtime (VmSystem.findThrowableHandler) performs stack unwinding to locate handlers.

Key Components

ClassPurpose
VmCompiledExceptionHandlerNative representation of a bytecode exception entry
VmCompiledCodeHolds compiled native code + exception handler table
VmExceptionsDeclared exceptions from method's Exceptions attribute
AbstractExceptionHandlerBase class; stores the catchType (VmConstClass)
NativeCodeCompilerConverts bytecode exception table to VmCompiledExceptionHandler[]

Source path:core/src/core/org/jnode/vm/classmgr/

How It Works

Exception Table Representation

The bytecode exception table is converted at JIT compile time into an array of VmCompiledExceptionHandler objects. Each handler stores raw native addresses:

publicfinalclassVmCompiledExceptionHandlerextendsAbstractExceptionHandler {
privatefinalVmAddresshandler; // Native address of catch blockprivatefinalVmAddressstartPtr; // PC range startprivatefinalVmAddressendPtr; // PC range endpublicbooleanisInScope(Addressaddress) {
finalAddressstart = Address.fromAddress(startPtr);
finalAddressend = Address.fromAddress(endPtr);
returnaddress.GE(start) && address.LT(end);
}
}

VmCompiledCode holds the handler table and a defaultExceptionHandler:

publicfinalclassVmCompiledCodeextendsAbstractCode {
privatefinalVmCompiledExceptionHandler[] eTable;
privatefinalVmAddressdefaultExceptionHandler;
// ...
}

Stack Unwinding

When an exception is thrown, VmSystem.findThrowableHandler() walks the call stack:

publicstaticAddressfindThrowableHandler(Throwableex, Addressframe, Addressaddress) {
// 1. Get current method + compiled codefinalVmMethodmethod = reader.getMethod(frame);
finalVmCompiledCodecc = reader.getCompiledCode(frame);
// 2. Iterate exception handlersfor (inti = 0; i < cc.getNoExceptionHandlers(); i++) {
finalVmCompiledExceptionHandlerceh = cc.getExceptionHandler(i);
if (ceh.isInScope(address)) {
finalVmConstClasscatchType = eh.getCatchType();
if (catchType == null) {
// Catch-all (finally block)returnAddress.fromAddress(ceh.getHandler());
} else {
// Check assignabilityif (handlerClass.isAssignableFrom(exClass)) {
returnAddress.fromAddress(ceh.getHandler());
}
}
}
}
// 3. If PC is in compiled code but no handler matchedif (cc.contains(address)) {
returnAddress.fromAddress(cc.getDefaultExceptionHandler());
}
returnnull;
}

The process repeats for each stack frame until a handler is found or the stack is exhausted.

Default Exception Handler

The defaultExceptionHandler is invoked when:

  • The exception PC is within the method's compiled code bounds
  • No explicit handler matched (wrong exception type)

It typically transfers control to an interpreter fallback or performs a fatal error dump.

@Uninterruptible Code Restrictions

Methods annotated with @Uninterruptible cannot throw exceptions because:

  1. They run without GC safepoints — throwing creates a safepoint
  2. Stack unwinding requires potentially blocking operations
  3. Thread switching is disabled, but handler dispatch may need it

Code marked @Uninterruptible must handle all error conditions inline. If an error occurs, the typical pattern is:

@UninterruptiblepublicfinalvoidcriticalOperation() {
if (somethingWrong) {
Unsafe.die("Fatal error in criticalOperation");
}
}

Attempting to throw from uninterruptible code results in undefined behavior or a fatal VM halt.

Related Pages

Clone this wiki locally