I am trying to load a class as a hidden class in Java, but run into a VerifyError. The class uses a VarHandle, which has a PolymorphicSignature
. I believe the error is implying that the polymorphic call is using the non-hidden class, but it's not obvious to me what to do to fix it. What is the correct way to define hidden classes that contain calls to MethodHandles or VarHandles?
package foo;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.VarHandle;
import java.util.function.IntUnaryOperator;
import org.junit.jupiter.api.Test;
public class HiddenClassTest {
@Test
public void loadHidden() throws Exception {
MethodHandles.Lookup lookup;
try (var is = IntThing.class.getResourceAsStream("HiddenClassTest$IntThing.class")) {
lookup =
MethodHandles.lookup().defineHiddenClass(is.readAllBytes(), true);
}
var instance =
lookup.lookupClass()
.asSubclass(IntUnaryOperator.class)
.getConstructor()
.newInstance();
System.out.println(instance.applyAsInt(12));
System.out.println(instance.applyAsInt(24));
}
static final class IntThing implements IntUnaryOperator {
static final VarHandle IDX;
static {
try {
IDX = MethodHandles.lookup().findVarHandle(IntThing.class, "idx", int.class);
} catch (Exception e) {
throw new RuntimeException(e);
}
}
private int idx;
@Override
public int applyAsInt(int newIdx) {
int oldIdx = (int) IDX.getOpaque(this);
IDX.setOpaque(this, newIdx);
return oldIdx;
}
}
}
The error:
Bad type on operand stack
Exception Details:
Location:
foo/HiddenClassTest$IntThing+0x0000000800d38c00.applyAsInt(I)I @4: invokevirtual
Reason:
Type 'foo/HiddenClassTest$IntThing+0x0000000800d38c00' (current frame, stack[1]) is not assignable to 'foo/HiddenClassTest$IntThing'
Current Frame:
bci: @4
flags: { }
locals: { 'foo/HiddenClassTest$IntThing+0x0000000800d38c00', integer }
stack: { 'java/lang/invoke/VarHandle', 'foo/HiddenClassTest$IntThing+0x0000000800d38c00' }
Bytecode:
0000000: b200 072a b600 0d3d b200 072a 1bb6 0013
0000010: 1cac
java.lang.VerifyError: Bad type on operand stack
Exception Details:
Location:
foo/HiddenClassTest$IntThing+0x0000000800d38c00.applyAsInt(I)I @4: invokevirtual
Reason:
Type 'foo/HiddenClassTest$IntThing+0x0000000800d38c00' (current frame, stack[1]) is not assignable to 'foo/HiddenClassTest$IntThing'
Current Frame:
bci: @4
flags: { }
locals: { 'foo/HiddenClassTest$IntThing+0x0000000800d38c00', integer }
stack: { 'java/lang/invoke/VarHandle', 'foo/HiddenClassTest$IntThing+0x0000000800d38c00' }
Bytecode:
0000000: b200 072a b600 0d3d b200 072a 1bb6 0013
0000010: 1cac
at java.base/java.lang.ClassLoader.defineClass0(Native Method)
at java.base/java.lang.System$2.defineClass(System.java:2307)
at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClass(MethodHandles.java:2439)
at java.base/java.lang.invoke.MethodHandles$Lookup$ClassDefiner.defineClassAsLookup(MethodHandles.java:2420)
at java.base/java.lang.invoke.MethodHandles$Lookup.defineHiddenClass(MethodHandles.java:2127)
at foo.HiddenClassTest.loadHidden(HiddenClassTest.java:16)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:568)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
You are right: this is because of the polymorphic signature where this
results in a reference to a different class in the signature. The workaround is to explicitly cast this
to Object
to force Ljava/lang/Object;
in the signature instead of LHiddenClassTest$IntThing;
int oldIdx = (int) IDX.getOpaque((Object) this);
IDX.setOpaque((Object) this, newIdx);
return oldIdx;