Skip to content

Upcall Guide

squid233 edited this page Aug 10, 2025 · 3 revisions

Upcalls are calls from native code back to Java code. OverrunGL wraps upcalls into a functional interface which you may use lambda to define.

For example, the GLFWerrorfun in C is defined as:

typedef void (* GLFWerrorfun)(int error_code, const char* description);

In Java, this is:

@FunctionalInterface
public interface GLFWErrorFun extends Upcall {
    void invoke(int error_code, MemorySegment description);
}

The description is not converted to String.

The upcall interface typically provides an alloc method that allows you to allocate an upcall stub with a specified arena.

glfwSetErrorCallback(GLFWErrorFun.alloc(Arena.global(), (error_code, description) ->
    System.err.println("GLFW error " + error_code + ": " + MemoryUtil.nativeString(description))));

Limitations

  1. The implementation of upcall interfaces should not throw any exceptions, or the JVM will terminate abruptly.
  2. The automatic arena (Arena::ofAuto) should not be used without holding a strong reference of the upcall stub. JVM is unable to trace usages of upcall after one passed to native code, causing use-after-free issues.

A minimal reproducible example of how automatic arena causes JVM crash:

void main() {
    glfwSetErrorCallback(GLFWErrorFun.alloc(Arena.ofAuto(), (_, _) -> {}));
    // after passing to native code, the upcall stub allocated above is no longer used in Java code,
    // making garbage collector think it not used
    System.gc(); // try to close arena
    glfwInitHint(0, 0); // JVM crash!
}

A simple fix to this is to introduce a variable, so that the upcall stub will be valid until main returns.

var stub = GLFWErrorFun.alloc(Arena.ofAuto(), ...);
glfwSetErrorCallback(stub);

Clone this wiki locally