Skip to content

Commit 71dc004

Browse files
committed
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.
1 parent 6ae5186 commit 71dc004

File tree

5 files changed

+71
-11
lines changed

5 files changed

+71
-11
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package fi.benjami.code4jvm.lua.compiler;
2+
3+
import java.lang.invoke.MethodHandles;
4+
5+
import fi.benjami.code4jvm.Type;
6+
import fi.benjami.code4jvm.call.CallTarget;
7+
import fi.benjami.code4jvm.call.FixedCallTarget;
8+
9+
/**
10+
* Support code for custom class data dynamic constants.
11+
* JDK already has {@link MethodHandles#classDataAt(java.lang.invoke.MethodHandles.Lookup, String, Class, int) a form of this},
12+
* but it only works with actual JDK class data. That, unfortunately, is only
13+
* available for hidden classes, which we can't use because they're invisible
14+
* in stack frames...
15+
*
16+
*/
17+
public class ClassData {
18+
19+
static final String FIELD_NAME = "$CONSTANTS";
20+
static final FixedCallTarget BOOTSTRAP = CallTarget.staticMethod(Type.of(ClassData.class), Type.OBJECT, "get",
21+
Type.of(MethodHandles.Lookup.class), Type.STRING, Type.of(Class.class), Type.INT);
22+
23+
public static Object get(MethodHandles.Lookup lookup, String ignoredName, Class<?> type, int index) {
24+
Object[] array;
25+
try {
26+
array = (Object[]) lookup.findStaticGetter(lookup.lookupClass(), FIELD_NAME, Object[].class)
27+
.invokeExact();
28+
} catch (Throwable e) {
29+
throw new AssertionError(e);
30+
}
31+
return array[index];
32+
}
33+
}

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/FunctionCompiler.java

+17-4
Original file line numberDiff line numberDiff line change
@@ -62,15 +62,25 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function,
6262
var ctx = LuaContext.forFunction(function.owner(), function.type(), truncateReturn, argTypes);
6363
var code = generateCode(ctx, function.type(), argTypes, upvalueTypes);
6464
try {
65-
var lookup = LOOKUP.defineHiddenClassWithClassData(code, ctx.allClassData(), true);
65+
// Load the class with single-use class loader
66+
// Using hidden classes would be preferable, but JVM hides them from stack frames
67+
// ... which really screws up stack traces of Lua code
68+
// See https://bugs.openjdk.org/browse/JDK-8212620
69+
var implClass = SingleClassLoader.load("unknown", code);
70+
try {
71+
LOOKUP.findStaticSetter(implClass, ClassData.FIELD_NAME, Object[].class)
72+
.invokeExact(ctx.allClassData());
73+
} catch (Throwable e) {
74+
throw new AssertionError(e); // We just generated the field!
75+
}
6676

6777
// Cache the constructor and actual function MHs
6878
// They'll hold references to the underlying class
6979
var jvmUpvalueTypes = Arrays.stream(upvalueTypes)
7080
.map(LuaType::backingType)
7181
.map(Type::loadedClass)
7282
.toArray(Class[]::new);
73-
var constructor = lookup.findConstructor(lookup.lookupClass(),
83+
var constructor = LOOKUP.findConstructor(implClass,
7484
MethodType.methodType(void.class, jvmUpvalueTypes));
7585

7686
var normalArgCount = function.type().acceptedArgs().size();
@@ -90,7 +100,7 @@ public static MethodHandle callTarget(LuaType[] argTypes, LuaFunction function,
90100

91101
var jvmReturnType = ctx.returnType().equals(LuaType.NIL)
92102
? Type.VOID : ctx.returnType().backingType();
93-
var method = LOOKUP.findVirtual(lookup.lookupClass(), "call",
103+
var method = LOOKUP.findVirtual(implClass, "call",
94104
MethodType.methodType(jvmReturnType.loadedClass(), jvmArgTypes));
95105

96106
return new CompiledFunction(constructor, method);
@@ -118,7 +128,10 @@ private static byte[] generateCode(LuaContext ctx, LuaType.Function type,
118128
LuaType[] argTypes, LuaType[] upvalueTypes) {
119129
// Create class that wraps the method acting as function body
120130
// TODO store name in function if available?
121-
var def = ClassDef.create("fi.benjami.code4jvm.lua.compiler.CompiledFunction", Access.PUBLIC);
131+
var def = ClassDef.create("unknown", Access.PUBLIC);
132+
133+
// Class data constants
134+
def.addStaticField(Access.PUBLIC, Type.OBJECT.array(1), ClassData.FIELD_NAME);
122135

123136
// Add fields for upvalues
124137
for (var i = 0; i < upvalueTypes.length; i++) {

lua4jvm/src/main/java/fi/benjami/code4jvm/lua/compiler/LuaContext.java

+3-4
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
package fi.benjami.code4jvm.lua.compiler;
22

33
import java.util.ArrayList;
4-
import java.util.Arrays;
54
import java.util.IdentityHashMap;
65
import java.util.List;
76
import java.util.Map;
@@ -199,11 +198,11 @@ public Constant addClassData(Object value) {
199198
public Constant addClassData(Object value, Type type) {
200199
var index = classData.size();
201200
classData.add(value);
202-
return Constant.classDataAt(type, index);
201+
return Constant.dynamic(type, ClassData.BOOTSTRAP, Constant.of(index));
203202
}
204203

205-
public Object allClassData() {
206-
return classData;
204+
public Object[] allClassData() {
205+
return classData.toArray();
207206
}
208207

209208
public <T> T cached(Object key, T value) {
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,10 @@
11
package fi.benjami.code4jvm.lua.compiler;
22

3-
public class SingleClassLoader extends ClassLoader {
3+
class SingleClassLoader extends ClassLoader {
4+
5+
public static Class<?> load(String name, byte[] code) {
6+
return new SingleClassLoader(code).findClass(name);
7+
}
48

59
private final byte[] code;
610

@@ -9,8 +13,7 @@ public SingleClassLoader(byte[] code) {
913
}
1014

1115
@Override
12-
protected Class<?> findClass(String name) throws ClassNotFoundException {
16+
public Class<?> findClass(String name) {
1317
return defineClass(name, code, 0, code.length);
14-
1518
}
1619
}

src/main/java/fi/benjami/code4jvm/Constant.java

+12
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
11
package fi.benjami.code4jvm;
22

33
import java.lang.invoke.MethodHandles;
4+
import java.util.Arrays;
45
import java.util.Optional;
56

67
import org.objectweb.asm.ConstantDynamic;
78

89
import fi.benjami.code4jvm.call.CallTarget;
10+
import fi.benjami.code4jvm.call.FixedCallTarget;
911

1012
public class Constant implements Value {
1113

@@ -71,6 +73,16 @@ public static Constant classDataAt(Type type, int index) {
7173
return new Constant(dynamic, type);
7274
}
7375

76+
public static Constant dynamic(Type type, FixedCallTarget bootstrap, Constant... args) {
77+
var handle = bootstrap.toMethodHandle();
78+
var argValues = Arrays.stream(args)
79+
.map(Constant::value)
80+
.toArray();
81+
var dynamic = new ConstantDynamic("_", type.descriptor(), handle, argValues);
82+
return new Constant(dynamic, type);
83+
84+
}
85+
7486
// TODO method handle, (more) constant dynamic
7587

7688
private final Object value;

0 commit comments

Comments
 (0)