From 71dc004690fc5b18da966dec56ca369e4406aa4c Mon Sep 17 00:00:00 2001 From: bensku Date: Sat, 3 Aug 2024 01:07:34 +0300 Subject: [PATCH] lua4jvm: Replace hidden classes with single-use ClassLoaders Hidden classes have hidden stack frames, which is not ok when they are compiled from user code that might throw exceptions. --- .../code4jvm/lua/compiler/ClassData.java | 33 +++++++++++++++++++ .../lua/compiler/FunctionCompiler.java | 21 +++++++++--- .../code4jvm/lua/compiler/LuaContext.java | 7 ++-- .../lua/compiler/SingleClassLoader.java | 9 +++-- .../java/fi/benjami/code4jvm/Constant.java | 12 +++++++ 5 files changed, 71 insertions(+), 11 deletions(-) create mode 100644 lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/ClassData.java diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/ClassData.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/ClassData.java new file mode 100644 index 0000000..fc70f66 --- /dev/null +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/ClassData.java @@ -0,0 +1,33 @@ +package fi.benjami.code4jvm.lua.compiler; + +import java.lang.invoke.MethodHandles; + +import fi.benjami.code4jvm.Type; +import fi.benjami.code4jvm.call.CallTarget; +import fi.benjami.code4jvm.call.FixedCallTarget; + +/** + * Support code for custom class data dynamic constants. + * JDK already has {@link MethodHandles#classDataAt(java.lang.invoke.MethodHandles.Lookup, String, Class, int) a form of this}, + * but it only works with actual JDK class data. That, unfortunately, is only + * available for hidden classes, which we can't use because they're invisible + * in stack frames... + * + */ +public class ClassData { + + static final String FIELD_NAME = "$CONSTANTS"; + static final FixedCallTarget BOOTSTRAP = CallTarget.staticMethod(Type.of(ClassData.class), Type.OBJECT, "get", + Type.of(MethodHandles.Lookup.class), Type.STRING, Type.of(Class.class), Type.INT); + + public static Object get(MethodHandles.Lookup lookup, String ignoredName, Class type, int index) { + Object[] array; + try { + array = (Object[]) lookup.findStaticGetter(lookup.lookupClass(), FIELD_NAME, Object[].class) + .invokeExact(); + } catch (Throwable e) { + throw new AssertionError(e); + } + return array[index]; + } +} diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/FunctionCompiler.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/FunctionCompiler.java index 2ca8c73..9a01bd3 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/FunctionCompiler.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/FunctionCompiler.java @@ -62,7 +62,17 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function, var ctx = LuaContext.forFunction(function.owner(), function.type(), truncateReturn, argTypes); var code = generateCode(ctx, function.type(), argTypes, upvalueTypes); try { - var lookup = LOOKUP.defineHiddenClassWithClassData(code, ctx.allClassData(), true); + // Load the class with single-use class loader + // Using hidden classes would be preferable, but JVM hides them from stack frames + // ... which really screws up stack traces of Lua code + // See https://bugs.openjdk.org/browse/JDK-8212620 + var implClass = SingleClassLoader.load("unknown", code); + try { + LOOKUP.findStaticSetter(implClass, ClassData.FIELD_NAME, Object[].class) + .invokeExact(ctx.allClassData()); + } catch (Throwable e) { + throw new AssertionError(e); // We just generated the field! + } // Cache the constructor and actual function MHs // They'll hold references to the underlying class @@ -70,7 +80,7 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function, .map(LuaType::backingType) .map(Type::loadedClass) .toArray(Class[]::new); - var constructor = lookup.findConstructor(lookup.lookupClass(), + var constructor = LOOKUP.findConstructor(implClass, MethodType.methodType(void.class, jvmUpvalueTypes)); var normalArgCount = function.type().acceptedArgs().size(); @@ -90,7 +100,7 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function, var jvmReturnType = ctx.returnType().equals(LuaType.NIL) ? Type.VOID : ctx.returnType().backingType(); - var method = LOOKUP.findVirtual(lookup.lookupClass(), "call", + var method = LOOKUP.findVirtual(implClass, "call", MethodType.methodType(jvmReturnType.loadedClass(), jvmArgTypes)); return new CompiledFunction(constructor, method); @@ -118,7 +128,10 @@ private static byte[] generateCode(LuaContext ctx, LuaType.Function type, LuaType[] argTypes, LuaType[] upvalueTypes) { // Create class that wraps the method acting as function body // TODO store name in function if available? - var def = ClassDef.create("fi.benjami.code4jvm.lua.compiler.CompiledFunction", Access.PUBLIC); + var def = ClassDef.create("unknown", Access.PUBLIC); + + // Class data constants + def.addStaticField(Access.PUBLIC, Type.OBJECT.array(1), ClassData.FIELD_NAME); // Add fields for upvalues for (var i = 0; i < upvalueTypes.length; i++) { diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/LuaContext.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/LuaContext.java index 200d3d9..2663351 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/LuaContext.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/LuaContext.java @@ -1,7 +1,6 @@ package fi.benjami.code4jvm.lua.compiler; import java.util.ArrayList; -import java.util.Arrays; import java.util.IdentityHashMap; import java.util.List; import java.util.Map; @@ -199,11 +198,11 @@ public Constant addClassData(Object value) { public Constant addClassData(Object value, Type type) { var index = classData.size(); classData.add(value); - return Constant.classDataAt(type, index); + return Constant.dynamic(type, ClassData.BOOTSTRAP, Constant.of(index)); } - public Object allClassData() { - return classData; + public Object[] allClassData() { + return classData.toArray(); } public T cached(Object key, T value) { diff --git a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/SingleClassLoader.java b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/SingleClassLoader.java index 71d61a0..23204ef 100644 --- a/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/SingleClassLoader.java +++ b/lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/SingleClassLoader.java @@ -1,6 +1,10 @@ package fi.benjami.code4jvm.lua.compiler; -public class SingleClassLoader extends ClassLoader { +class SingleClassLoader extends ClassLoader { + + public static Class load(String name, byte[] code) { + return new SingleClassLoader(code).findClass(name); + } private final byte[] code; @@ -9,8 +13,7 @@ public SingleClassLoader(byte[] code) { } @Override - protected Class findClass(String name) throws ClassNotFoundException { + public Class findClass(String name) { return defineClass(name, code, 0, code.length); - } } diff --git a/src/main/java/fi/benjami/code4jvm/Constant.java b/src/main/java/fi/benjami/code4jvm/Constant.java index 690ea85..df0d349 100644 --- a/src/main/java/fi/benjami/code4jvm/Constant.java +++ b/src/main/java/fi/benjami/code4jvm/Constant.java @@ -1,11 +1,13 @@ package fi.benjami.code4jvm; import java.lang.invoke.MethodHandles; +import java.util.Arrays; import java.util.Optional; import org.objectweb.asm.ConstantDynamic; import fi.benjami.code4jvm.call.CallTarget; +import fi.benjami.code4jvm.call.FixedCallTarget; public class Constant implements Value { @@ -71,6 +73,16 @@ public static Constant classDataAt(Type type, int index) { return new Constant(dynamic, type); } + public static Constant dynamic(Type type, FixedCallTarget bootstrap, Constant... args) { + var handle = bootstrap.toMethodHandle(); + var argValues = Arrays.stream(args) + .map(Constant::value) + .toArray(); + var dynamic = new ConstantDynamic("_", type.descriptor(), handle, argValues); + return new Constant(dynamic, type); + + } + // TODO method handle, (more) constant dynamic private final Object value;