Skip to content

Instantly share code, notes, and snippets.

@burningtnt
Last active April 28, 2025 13:36
Show Gist options
  • Save burningtnt/65e1d9bfb2000e69c852335b178692e8 to your computer and use it in GitHub Desktop.
Save burningtnt/65e1d9bfb2000e69c852335b178692e8 to your computer and use it in GitHub Desktop.
package net.burningtnt.javatest.dmh;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
public final class Example {
private static final MethodHandleLinker<Runnable> LINKER = new MethodHandleLinker<>(Runnable.class, "run", MethodType.methodType(void.class));
public static void main(String[] args) throws Throwable {
Runnable runnable = LINKER.of(MethodHandles.lookup().findStatic(
Example.class, "run", MethodType.methodType(void.class)
));
runnable.run();
}
private static void run() {
System.out.println("run1");
}
}
/*
* MethodHandleLinker
* Copyright (c) 2024 Burning_TNT<[email protected]>
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package net.burningtnt.javatest.dmh;
import org.objectweb.asm.ClassWriter;
import org.objectweb.asm.FieldVisitor;
import org.objectweb.asm.MethodVisitor;
import org.objectweb.asm.Opcodes;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Array;
import java.lang.reflect.Field;
public final class MethodHandleLinker<T> {
private static final String DMH_CLASS = MethodHandleLinker.class.getName().replace('.', '/');
private static final String IMPL_CLASS = DMH_CLASS + "$LinkerImpl";
private static final ThreadLocal<MethodHandle> PENDING_MH = new ThreadLocal<>();
@SuppressWarnings("unused")
private static MethodHandle getMH() {
MethodHandle mh = PENDING_MH.get();
PENDING_MH.remove();
return mh;
}
private static final MethodHandle CLASS_DEFINER = locateClassDefiner();
@SuppressWarnings("all")
private static MethodHandle locateClassDefiner() {
MethodHandles.Lookup LOOKUP = MethodHandles.lookup();
Exception e1;
try {
Class<?> clazz = Class.forName("sun.misc.Unsafe");
MethodHandle mh = LOOKUP.findVirtual(clazz, "defineAnonymousClass", MethodType.methodType(Class.class, Class.class, byte[].class, Object[].class));
Field f = clazz.getDeclaredField("theUnsafe");
f.setAccessible(true);
return MethodHandles.insertArguments(MethodHandles.insertArguments(mh, 0, f.get(null), MethodHandleLinker.class), 1, (Object) null);
} catch (Exception e) {
e1 = e;
}
try {
Class<?> classOptionClass = Class.forName("java.lang.invoke.MethodHandles$Lookup$ClassOption");
Object[] options = (Object[]) Array.newInstance(classOptionClass, 1);
options[0] = classOptionClass.getField("NESTMATE").get(null);
MethodHandle mh = LOOKUP.findVirtual(MethodHandles.Lookup.class, "defineHiddenClass", MethodType.methodType(MethodHandles.Lookup.class, byte[].class, boolean.class, options.getClass()));
return MethodHandles.filterReturnValue(MethodHandles.insertArguments(mh.bindTo(LOOKUP), 1, false, options), LOOKUP.findVirtual(MethodHandles.Lookup.class, "lookupClass", MethodType.methodType(Class.class)));
} catch (Exception e2) {
IllegalStateException e = new IllegalStateException("Cannot initialize CLASS_DEFINER");
e.addSuppressed(e1);
e.addSuppressed(e2);
throw e;
}
}
private enum VMStackType {
OBJECT(Opcodes.ALOAD, Opcodes.ARETURN, 1),
NUMBER(Opcodes.ILOAD, Opcodes.IRETURN, 1),
FLOAT(Opcodes.FLOAD, Opcodes.FRETURN, 1),
DOUBLE(Opcodes.DLOAD, Opcodes.DRETURN, 2),
LONG(Opcodes.LLOAD, Opcodes.LRETURN, 2),
VOID(-1, Opcodes.RETURN, 0);
private final int loadingOpcode, returnOpcode, stackWidth;
VMStackType(int loadingOpcode, int returnOpcode, int stackWidth) {
this.loadingOpcode = loadingOpcode;
this.returnOpcode = returnOpcode;
this.stackWidth = stackWidth;
}
public static VMStackType of(Class<?> clazz) {
if (!clazz.isPrimitive()) {
return OBJECT;
} else if (clazz == int.class || clazz == short.class || clazz == char.class || clazz == byte.class || clazz == boolean.class) {
return NUMBER;
} else if (clazz == float.class) {
return FLOAT;
} else if (clazz == double.class) {
return DOUBLE;
} else if (clazz == long.class) {
return LONG;
} else {
return VOID;
}
}
}
private final byte[] classBytes;
public MethodHandleLinker(Class<T> interfaceClass, String methodName, MethodType methodType) {
ClassWriter cw = new ClassWriter(0);
cw.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_FINAL, IMPL_CLASS, null, "java/lang/Object", new String[]{
interfaceClass.getName().replace('.', '/')
});
{
FieldVisitor fv = cw.visitField(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC | Opcodes.ACC_FINAL, "MH", "Ljava/lang/invoke/MethodHandle;", null, null);
fv.visitEnd();
}
{
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC | Opcodes.ACC_STATIC, "<clinit>", "()V", null, null);
mv.visitCode();
mv.visitMethodInsn(Opcodes.INVOKESTATIC, DMH_CLASS, "getMH", "()Ljava/lang/invoke/MethodHandle;", false);
mv.visitFieldInsn(Opcodes.PUTSTATIC, IMPL_CLASS, "MH", "Ljava/lang/invoke/MethodHandle;");
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 0);
mv.visitEnd();
}
{
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "()V", null, null);
mv.visitCode();
mv.visitVarInsn(Opcodes.ALOAD, 0);
mv.visitMethodInsn(Opcodes.INVOKESPECIAL, "java/lang/Object", "<init>", "()V", false);
mv.visitInsn(Opcodes.RETURN);
mv.visitMaxs(1, 1);
mv.visitEnd();
}
{
String methodDesc = methodType.toMethodDescriptorString();
MethodVisitor mv = cw.visitMethod(Opcodes.ACC_PUBLIC, methodName, methodDesc, null, null);
mv.visitCode();
mv.visitFieldInsn(Opcodes.GETSTATIC, IMPL_CLASS, "MH", "Ljava/lang/invoke/MethodHandle;");
int stackMAX = 1, l = methodType.parameterCount();
for (int i = 0; i < l; i++) {
VMStackType type = VMStackType.of(methodType.parameterType(i));
if (type == VMStackType.VOID) {
throw new AssertionError("Parameters should NOT be void.");
}
mv.visitVarInsn(type.loadingOpcode, stackMAX);
stackMAX += type.stackWidth;
}
mv.visitMethodInsn(Opcodes.INVOKEVIRTUAL, "java/lang/invoke/MethodHandle", "invokeExact", methodDesc, false);
VMStackType type = VMStackType.of(methodType.returnType());
mv.visitInsn(type.returnOpcode);
stackMAX = Math.max(stackMAX, 1 + type.stackWidth);
mv.visitMaxs(stackMAX, stackMAX);
mv.visitEnd();
}
cw.visitEnd();
classBytes = cw.toByteArray();
}
@SuppressWarnings("unchecked")
public T buildBinding(MethodHandle mh) {
try {
PENDING_MH.set(mh);
return (T) ((Class<?>) CLASS_DEFINER.invokeExact(classBytes)).getConstructor().newInstance();
} catch (Throwable e) {
throw new IllegalArgumentException("Cannot define the linker.", e);
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment