Skip to content

Commit

Permalink
Root.MethodHandleLookup
Browse files Browse the repository at this point in the history
  • Loading branch information
Karlatemp committed Jul 9, 2022
1 parent 1ea1dc8 commit 7d65047
Show file tree
Hide file tree
Showing 5 changed files with 420 additions and 4 deletions.
69 changes: 69 additions & 0 deletions api/src/main/java/io/github/karlatemp/unsafeaccessor/MHLookup.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package io.github.karlatemp.unsafeaccessor;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;

class MHLookup {
static MethodHandle lookupDirect(String name, MethodType type, boolean bind) throws NoSuchMethodException {
Object anyx = Unsafe.getUnsafe0().getOriginalUnsafe();
MethodHandles.Lookup lookup = Root.RootLookupHolder.trustedIn(anyx.getClass());
MethodHandle rsp;
try {
rsp = lookup.findVirtual(anyx.getClass(), name, type);
} catch (IllegalAccessException e) {
throw new InternalError(e);
}
return bind ? rsp.bindTo(anyx) : rsp;
}

private static boolean checkUsingObj() {
Unsafe usf = Unsafe.getUnsafe0();
if (!usf.isJava9()) return true;
return usf.getClass().getName().endsWith("Obj");
}

private static final boolean isUsingObj = checkUsingObj();

static MethodHandle lookup(String name, MethodType type, Object[] bind) throws NoSuchMethodException {
Unsafe usf = Unsafe.getUnsafe0();
Object anyx = usf.getOriginalUnsafe();
MethodHandles.Lookup lookup = Root.RootLookupHolder.trustedIn(anyx.getClass());
String directName;
if (isUsingObj) {
directName = name.replace("Reference", "Object");
} else directName = name;

Object bindx = null;
MethodHandle rsp = null;
try {
try {
rsp = lookup.findVirtual(anyx.getClass(), directName, type);
bindx = anyx;
} catch (NoSuchMethodException ignored) {
if (!usf.isJava9()) {
// Opaque acquire Release
try {
String nwm = directName
.replace("Opaque", "Volatile")
.replace("Release", "Volatile")
.replace("Acquire", "Volatile");
rsp = lookup.findVirtual(anyx.getClass(), directName, type);
bindx = anyx;
} catch (NoSuchMethodException ignored2) {
}
}
if (rsp == null) {
rsp = lookup.findVirtual(Unsafe.class, name, type);
bindx = Unsafe.getUnsafe0();
}
}
} catch (IllegalAccessException e) {
throw new InternalError(e);
}

if (bind == null) return rsp.bindTo(bindx);
bind[0] = bindx;
return rsp;
}
}
151 changes: 149 additions & 2 deletions api/src/main/java/io/github/karlatemp/unsafeaccessor/Root.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,7 @@

import org.jetbrains.annotations.Contract;

import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.*;
import java.lang.reflect.AccessibleObject;
import java.lang.reflect.Field;
import java.util.function.Consumer;
Expand Down Expand Up @@ -205,4 +204,152 @@ public static void initializeObject(Object instance) {
Unsafe.getUnsafe0().ensureClassInitialized(instance.getClass());
ObjectInitializer.initializer().accept(instance);
}

/**
* Lookup for method handles
*
* @since 1.7.0
*/
@SuppressWarnings("UnusedReturnValue")
public static class MethodHandleLookup {
private static void checkAccess(UnsafeAccess access) {
if (access == null) {
getUnsafe();
} else {
access.checkTrusted();
}
}

public static MethodHandle lookup(
UnsafeAccess access,
String methodName,
MethodType methodType
) throws NoSuchMethodException {
return lookup(access, methodName, methodType, false, true);
}

/**
* Search a method handle from unsafe instance
*
* @param access The unsafe access object. No perm checking when access provided
* @param methodName The name of target method. Eg: {@code "getInt"}
* @param methodType The method type of target method.<br/> Eg: {@code MethodType.methodType(int.class, long.class)}
* @param lookupDirect Do the direct search.
* @param doBind Bind the unsafe object to method handle. Provide `false` to run {@link MethodHandles.Lookup#revealDirect(MethodHandle)}
*/
public static MethodHandle lookup(
UnsafeAccess access,
String methodName,
MethodType methodType,
boolean lookupDirect,
boolean doBind
) throws NoSuchMethodException {
checkAccess(access);
if (lookupDirect) {
return MHLookup.lookupDirect(methodName, methodType, doBind);
} else {
return MHLookup.lookup(methodName, methodType, doBind ? null : new Object[1]);
}
}

private static UnsafeAccess detectUnsafeAccessHold(MethodHandles.Lookup lookup, MethodHandle mh) {
if (mh != null) {
if (mh.type().parameterCount() != 0) {
throw new IllegalArgumentException("parameters not empty: " + mh);
}
if (mh.type().returnType() != UnsafeAccess.class) {
throw new IllegalArgumentException("Provided method handle is not a access check handle.");
}
try {
return (UnsafeAccess) mh.invokeExact();
} catch (Error | RuntimeException e) {
throw e;
} catch (Throwable throwable) {
throw new InternalError(throwable);
}
}
if (lookup == null) return null;
if (lookup == MethodHandles.publicLookup()) return null;
try {
try {
return detectUnsafeAccessHold(null, lookup.findStaticGetter(
lookup.lookupClass(), "UA", UnsafeAccess.class
));
} catch (NoSuchFieldException ignored) {
}
try {
return detectUnsafeAccessHold(null, lookup.findStaticGetter(
lookup.lookupClass(), "UNSAFE_ACCESS", UnsafeAccess.class
));
} catch (NoSuchFieldException ignored) {
}
} catch (IllegalAccessException ignored) {
throw new IllegalArgumentException("Provided caller <" + lookup + "> have no full access for itself");
}
return null;
}

public static CallSite resolveHandle(
MethodHandles.Lookup caller,
String methodName,
MethodType methodType
) throws NoSuchMethodException {
return resolve(caller, methodName, methodType, 0, null);
}

public static CallSite resolveHandleDirect(
MethodHandles.Lookup caller,
String methodName,
MethodType methodType
) throws NoSuchMethodException {
return resolve(caller, methodName, methodType, 1, null);
}

public static CallSite resolve(
MethodHandles.Lookup caller,
String methodName,
MethodType methodType,
int direct,
MethodHandle unsafeAccess_static_getter
) throws NoSuchMethodException {
return resolve(caller, methodName, methodType, direct, 0, unsafeAccess_static_getter);
}

/**
* Resolve a method handle for `invokeDynamic`
* <p>
* Example: <pre>{@code
* mymethod.visitInvokeDynamicInsn("getInt", "(J)I", new Handle(
* Opcodes.H_INVOKESTATIC,
* "io/github/karlatemp/unsafeaccessor/Root$MethodHandleLookup",
* "resolve", "(Ljava/lang/invoke/MethodHandles$Lookup;Ljava/lang/String;Ljava/lang/invoke/MethodType;IILjava/lang/invoke/MethodHandle;)Ljava/lang/invoke/CallSite;", false
* ), 0, 0, new Handle(
* Opcodes.H_GETSTATIC,
* "org/example/generated/MyClass", "UNSAFE_ACCESS", "Lio/github/karlatemp/unsafeaccessor/UnsafeAccess;",
* false
* ));
* }</pre>
*
* @param noCallerCheck if {@code true}, will skip caller permission detect
* @param unsafeAccess_static_getter The handle to get an unsafe-access instance. <br/>
*/
public static CallSite resolve(
MethodHandles.Lookup caller,
String methodName,
MethodType methodType,
int direct,
int noCallerCheck,
MethodHandle unsafeAccess_static_getter
) throws NoSuchMethodException {
return new ConstantCallSite(
lookup(detectUnsafeAccessHold(icb(noCallerCheck) ? null : caller, unsafeAccess_static_getter),
methodName, methodType, icb(direct), true
)
);
}

private static boolean icb(int v) {
return v != 0;
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

@TestTask(name = "BinaryCompatibilityAnalysis")
Expand Down Expand Up @@ -121,7 +120,11 @@ private static void analyze(Class<?> targetClass) throws Throwable {
lookup.findConstructor(ownerClass, methodType);
break;
}
throw new AssertionError("INVOKESPECIAL with-out <init>");
MethodHandle handle = lookup.findVirtual(ownerClass, methodName, methodType);
MethodHandleInfo handleInfo = lookup.revealDirect(handle);
Assertions.assertEquals(handleInfo.getDeclaringClass(), ownerClass);
Assertions.assertEquals(handleInfo.getReferenceKind(), MethodHandleInfo.REF_invokeVirtual);
break;
}
default:
throw new AssertionError("Unknown opcode: " + methodInsnNode.getOpcode() + "(" + Integer.toHexString(methodInsnNode.getOpcode()) + ")");
Expand Down
11 changes: 11 additions & 0 deletions impl/testunit/src/main/java/runtest/RunTestUnit.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package runtest;

import io.github.karlatemp.unsafeaccessor.Unsafe;
import org.objectweb.asm.ClassWriter;

import java.util.ArrayList;
import java.util.List;

Expand All @@ -11,4 +14,12 @@ public static void main(String[] args) throws Throwable {
classes.sort(String::compareTo);
TestTasks.runTests(classes);
}

public static Class<?> define(ClassWriter cw) {
return define(cw.toByteArray());
}

public static Class<?> define(byte[] code) {
return Unsafe.getUnsafe().defineClass(null, code, 0, code.length, ClassLoader.getSystemClassLoader(), null);
}
}
Loading

0 comments on commit 7d65047

Please sign in to comment.