Remap classes for Unsafe and redirect bridge classes (#138)

This commit is contained in:
IzzelAliz 2021-02-20 02:12:37 +08:00
parent 65ed2913f6
commit 2a01f3230a
3 changed files with 231 additions and 62 deletions

View File

@ -3,7 +3,9 @@ package io.izzel.arclight.api;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.security.ProtectionDomain;
import java.util.Objects;
@SuppressWarnings("all")
public class Unsafe {
@ -22,7 +24,18 @@ public class Unsafe {
Object base = unsafe.staticFieldBase(field);
long offset = unsafe.staticFieldOffset(field);
lookup = (MethodHandles.Lookup) unsafe.getObject(base, offset);
defineClass = lookup.unreflect(ClassLoader.class.getDeclaredMethod("defineClass", String.class, byte[].class, int.class, int.class, ProtectionDomain.class));
MethodHandle mh;
try {
Method sunMisc = unsafe.getClass().getMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
mh = lookup.unreflect(sunMisc).bindTo(unsafe);
} catch (Exception e) {
Class<?> jdkInternalUnsafe = Class.forName("jdk.internal.misc.Unsafe");
Field internalUnsafeField = jdkInternalUnsafe.getDeclaredField("theUnsafe");
Object internalUnsafe = unsafe.getObject(unsafe.staticFieldBase(internalUnsafeField), unsafe.staticFieldOffset(internalUnsafeField));
Method internalDefineClass = jdkInternalUnsafe.getMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
mh = lookup.unreflect(internalDefineClass).bindTo(internalUnsafe);
}
defineClass = Objects.requireNonNull(mh);
} catch (Exception e) {
throw new RuntimeException(e);
}
@ -250,7 +263,7 @@ public class Unsafe {
public static Class<?> defineClass(String s, byte[] bytes, int i, int i1, ClassLoader classLoader, ProtectionDomain protectionDomain) {
try {
return (Class<?>) defineClass.bindTo(classLoader).invoke(s, bytes, i, i1, protectionDomain);
return (Class<?>) defineClass.invokeExact(s, bytes, i, i1, classLoader, protectionDomain);
} catch (Throwable throwable) {
throwException(throwable);
return null;

View File

@ -3,13 +3,17 @@ package io.izzel.arclight.common.mod.util.remapper;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Multimap;
import io.izzel.arclight.api.Unsafe;
import io.izzel.arclight.common.mod.ArclightMod;
import io.izzel.arclight.common.mod.util.remapper.generated.ArclightReflectionHandler;
import io.izzel.arclight.common.util.ArrayUtil;
import io.izzel.tools.func.Func4;
import io.izzel.tools.product.Product;
import io.izzel.tools.product.Product2;
import io.izzel.tools.product.Product4;
import org.apache.commons.lang3.ClassUtils;
import org.apache.logging.log4j.Marker;
import org.apache.logging.log4j.MarkerManager;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Handle;
import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
@ -34,9 +38,11 @@ import java.nio.ByteBuffer;
import java.security.CodeSource;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.ListIterator;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
public class ArclightRedirectAdapter implements PluginTransformer {
@ -45,7 +51,7 @@ public class ArclightRedirectAdapter implements PluginTransformer {
private static final String REPLACED_NAME = Type.getInternalName(ArclightReflectionHandler.class);
private static final Multimap<String, Product2<String, MethodInsnNode>> METHOD_MODIFY = HashMultimap.create();
private static final Multimap<String, Product2<String, MethodInsnNode>> METHOD_REDIRECT = HashMultimap.create();
private static final Map<Method, Product4<String, Class<?>[], String, Class<?>[]>> METHOD_TO_HANDLER = new HashMap<>();
private static final Map<String, Func4<ClassLoaderRemapper, Method, Object, Object[], Object[]>> METHOD_TO_HANDLER = new ConcurrentHashMap<>();
static {
redirect(Field.class, "getName", "fieldGetName");
@ -81,10 +87,69 @@ public class ArclightRedirectAdapter implements PluginTransformer {
modify(ClassLoader.class, "defineClass", String.class, ByteBuffer.class, ProtectionDomain.class);
modify(SecureClassLoader.class, "defineClass", String.class, byte[].class, int.class, int.class, CodeSource.class);
modify(SecureClassLoader.class, "defineClass", String.class, ByteBuffer.class, CodeSource.class);
modify(classOf("sun.misc.Unsafe"), "defineClass", "unsafeDefineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
modify(classOf("jdk.internal.misc.Unsafe"), "defineClass", "unsafeDefineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
modify(classOf("jdk.internal.misc.Unsafe"), "defineClass0", "unsafeDefineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class);
}
public static Product4<String, Class<?>[], String, Class<?>[]> getInvokeRule(Method method) {
return METHOD_TO_HANDLER.get(method);
public static Object[] runHandle(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) {
Func4<ClassLoaderRemapper, Method, Object, Object[], Object[]> handler = METHOD_TO_HANDLER.get(methodToString(method));
if (handler != null) {
return handler.apply(remapper, method, src, param);
}
return null;
}
public static Object runRedirect(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) throws Throwable {
Func4<ClassLoaderRemapper, Method, Object, Object[], Object[]> handler = METHOD_TO_HANDLER.get(methodToString(method));
if (handler != null) {
Object[] ret = handler.apply(remapper, method, src, param);
return ((Method) ret[0]).invoke(ret[1], (Object[]) ret[2]);
}
return remapper;
}
public static void scanMethod(byte[] bytes) {
ClassReader reader = new ClassReader(bytes);
ArclightMod.LOGGER.debug(MARKER, "Scanning {}", reader.getClassName());
ClassNode node = new ClassNode();
reader.accept(node, ClassReader.SKIP_FRAMES | ClassReader.SKIP_DEBUG);
for (MethodNode method : node.methods) {
for (ListIterator<AbstractInsnNode> iterator = method.instructions.iterator(); iterator.hasNext(); ) {
AbstractInsnNode instruction = iterator.next();
int opcode = instruction.getOpcode();
if (opcode >= Opcodes.INVOKEVIRTUAL && opcode <= Opcodes.INVOKEINTERFACE) {
if (iterator.nextIndex() < method.instructions.size() - 1) {
break;
}
MethodInsnNode insnNode = (MethodInsnNode) instruction;
String key = insnNode.name + insnNode.desc;
if (METHOD_MODIFY.containsKey(key) || METHOD_REDIRECT.containsKey(key)) {
try {
Class<?> cl = Class.forName(insnNode.owner.replace('/', '.'));
Type[] argumentTypes = Type.getMethodType(insnNode.desc).getArgumentTypes();
Class<?>[] paramTypes = new Class<?>[argumentTypes.length];
for (int i = 0, argumentTypesLength = argumentTypes.length; i < argumentTypesLength; i++) {
Type type = argumentTypes[i];
paramTypes[i] = ClassUtils.getClass(type.getClassName());
}
Method target = methodOf(cl, insnNode.name, paramTypes);
if (target != null) {
Func4<ClassLoaderRemapper, Method, Object, Object[], Object[]> bridge = METHOD_TO_HANDLER.get(methodToString(target));
if (bridge != null) {
ArclightMod.LOGGER.debug(MARKER, "Creating bridge handler {}/{}{} to {}", node.name, method.name, method.desc, methodToString(target));
METHOD_TO_HANDLER.put(node.name + '/' + method.name + method.desc, new BridgeHandler(bridge, target));
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
} else if (opcode < Opcodes.ILOAD || opcode > Opcodes.ALOAD) {
break;
}
}
}
}
@Override
@ -242,7 +307,9 @@ public class ArclightRedirectAdapter implements PluginTransformer {
}
private static void addRule(boolean modifyArgs, Class<?> owner, String name, String handlerName, Class<?>... args) {
if (owner == null) return;
Method original = methodOf(owner, name, args);
if (original == null) return;
Class<?>[] handlerArgs;
if (!Modifier.isStatic(original.getModifiers())) {
handlerArgs = ArrayUtil.prepend(args, owner, Class[]::new);
@ -250,28 +317,40 @@ public class ArclightRedirectAdapter implements PluginTransformer {
handlerArgs = args;
}
Method handler = methodOf(ArclightReflectionHandler.class, "redirect" + capitalize(handlerName), handlerArgs);
while (handler == null) {
handlerArgs[0] = handlerArgs[0].getSuperclass();
handler = methodOf(ArclightReflectionHandler.class, "redirect" + capitalize(handlerName), handlerArgs);
}
METHOD_REDIRECT.put(name + Type.getMethodDescriptor(original), Product.of(Type.getInternalName(owner), methodNodeOf(handler)));
Product2<String, Class<?>[]> handleProd;
String key = methodToString(original);
if (modifyArgs) {
Method modifyHandler;
try {
modifyHandler = methodOf(ArclightReflectionHandler.class, "handle" + capitalize(handlerName), handlerArgs);
} catch (RuntimeException e) {
Method modifyHandler = methodOf(ArclightReflectionHandler.class, "handle" + capitalize(handlerName), handlerArgs);
if (modifyHandler == null) {
handlerArgs[0] = original.getReturnType();
modifyHandler = methodOf(ArclightReflectionHandler.class, "handle" + capitalize(handlerName), handlerArgs);
}
if (modifyHandler == null) {
throw new RuntimeException("No handler for " + original);
}
METHOD_MODIFY.put(name + Type.getMethodDescriptor(original), Product.of(Type.getInternalName(owner), methodNodeOf(modifyHandler)));
handleProd = Product.of("handle" + capitalize(handlerName), handlerArgs);
METHOD_TO_HANDLER.put(key, new ModifyHandler("handle" + capitalize(handlerName), handlerArgs));
} else {
handleProd = Product.of(null, null);
METHOD_TO_HANDLER.put(key, new RedirectHandler("redirect" + capitalize(handlerName), handlerArgs));
}
METHOD_TO_HANDLER.put(original, Product.of("redirect" + capitalize(handlerName), handlerArgs, handleProd._1, handleProd._2));
}
private static String capitalize(String name) {
return Character.toUpperCase(name.charAt(0)) + name.substring(1);
}
private static Class<?> classOf(String cl) {
try {
return Class.forName(cl);
} catch (ClassNotFoundException e) {
return null;
}
}
private static Method methodOf(Class<?> owner, String name, Class<?>... args) {
try {
return owner.getMethod(name, args);
@ -279,7 +358,7 @@ public class ArclightRedirectAdapter implements PluginTransformer {
try {
return owner.getDeclaredMethod(name, args);
} catch (NoSuchMethodException e2) {
throw new RuntimeException(e2);
return null;
}
}
}
@ -291,6 +370,10 @@ public class ArclightRedirectAdapter implements PluginTransformer {
return new MethodInsnNode(Opcodes.INVOKESTATIC, owner, name, desc);
}
private static String methodToString(Method method) {
return Type.getInternalName(method.getDeclaringClass()) + "/" + method.getName() + Type.getMethodDescriptor(method);
}
private static int toOpcode(int handleType) {
switch (handleType) {
case Opcodes.H_INVOKEINTERFACE:
@ -328,4 +411,80 @@ public class ArclightRedirectAdapter implements PluginTransformer {
return new LdcInsnNode(i);
}
}
private static class ModifyHandler implements Func4<ClassLoaderRemapper, Method, Object, Object[], Object[]> {
private final String handlerName;
private final Class<?>[] handlerArgs;
public ModifyHandler(String handlerName, Class<?>[] handlerArgs) {
this.handlerName = handlerName;
this.handlerArgs = handlerArgs;
}
@Override
public Object[] apply(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) {
try {
Method handleMethod = remapper.getGeneratedHandlerClass().getMethod(handlerName, handlerArgs);
if (method.getParameterCount() > 0) {
if (handleMethod.getReturnType().isArray() && !Modifier.isStatic(method.getModifiers())) {
Object[] invoke = (Object[]) handleMethod.invoke(null, ArrayUtil.prepend(param, src));
return new Object[]{method, invoke[0], Arrays.copyOfRange(invoke, 1, invoke.length)};
} else {
return new Object[]{method, src, handleMethod.invoke(null, param)};
}
} else {
return new Object[]{handleMethod, null, new Object[]{method.invoke(src, param)}};
}
} catch (Exception e) {
Unsafe.throwException(e);
}
return null;
}
}
private static class RedirectHandler implements Func4<ClassLoaderRemapper, Method, Object, Object[], Object[]> {
private final String handlerName;
private final Class<?>[] handlerArgs;
public RedirectHandler(String handlerName, Class<?>[] handlerArgs) {
this.handlerName = handlerName;
this.handlerArgs = handlerArgs;
}
@Override
public Object[] apply(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) {
try {
Method redirectMethod = remapper.getGeneratedHandlerClass().getMethod(handlerName, handlerArgs);
return new Object[]{redirectMethod, null, Modifier.isStatic(method.getModifiers()) ? param : ArrayUtil.prepend(param, src)};
} catch (Exception e) {
Unsafe.throwException(e);
return null;
}
}
}
private static class BridgeHandler implements Func4<ClassLoaderRemapper, Method, Object, Object[], Object[]> {
private final Func4<ClassLoaderRemapper, Method, Object, Object[], Object[]> bridge;
private final Method targetMethod;
private BridgeHandler(Func4<ClassLoaderRemapper, Method, Object, Object[], Object[]> bridge, Method targetMethod) {
this.bridge = bridge;
this.targetMethod = targetMethod;
}
@Override
public Object[] apply(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) {
boolean bridgeStatic = Modifier.isStatic(targetMethod.getModifiers());
if (bridgeStatic) {
Object[] ret = bridge.apply(remapper, this.targetMethod, null, param);
return new Object[]{method, src, ret[2]};
} else {
Object[] ret = bridge.apply(remapper, this.targetMethod, param[0], Arrays.copyOfRange(param, 1, param.length));
return new Object[]{method, src, ArrayUtil.prepend((Object[]) ret[2], ret[1])};
}
}
}
}

View File

@ -5,8 +5,7 @@ import io.izzel.arclight.api.ArclightVersion;
import io.izzel.arclight.api.Unsafe;
import io.izzel.arclight.common.mod.util.remapper.ArclightRedirectAdapter;
import io.izzel.arclight.common.mod.util.remapper.ClassLoaderRemapper;
import io.izzel.arclight.common.util.ArrayUtil;
import io.izzel.tools.product.Product4;
import io.izzel.arclight.common.mod.util.remapper.RemappingClassLoader;
import org.apache.commons.collections.iterators.IteratorEnumeration;
import org.objectweb.asm.ClassReader;
import org.objectweb.asm.Type;
@ -17,9 +16,7 @@ import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.net.MalformedURLException;
import java.net.URL;
import java.nio.ByteBuffer;
@ -27,7 +24,6 @@ import java.security.CodeSource;
import java.security.Permissions;
import java.security.ProtectionDomain;
import java.security.SecureClassLoader;
import java.util.Arrays;
import java.util.Enumeration;
@SuppressWarnings("unused")
@ -378,52 +374,26 @@ public class ArclightReflectionHandler extends ClassLoader {
}
}
public static Object[] handleMethodInvoke(Method method, Object src, Object[] param) throws NoSuchMethodException, InvocationTargetException, IllegalAccessException {
Product4<String, Class<?>[], String, Class<?>[]> invokeRule = ArclightRedirectAdapter.getInvokeRule(method);
if (invokeRule != null) {
if (invokeRule._3 != null && method.getParameterCount() > 0) {
Method handleMethod = remapper.getGeneratedHandlerClass().getMethod(invokeRule._3, invokeRule._4);
if (handleMethod.getReturnType().isArray() && !Modifier.isStatic(method.getModifiers())) {
Object[] invoke = (Object[]) handleMethod.invoke(null, ArrayUtil.prepend(param, src));
return new Object[]{method, invoke[0], Arrays.copyOfRange(invoke, 1, invoke.length)};
} else {
return new Object[]{method, src, handleMethod.invoke(null, param)};
}
} else {
Method redirectMethod = remapper.getGeneratedHandlerClass().getMethod(invokeRule._1, invokeRule._2);
return new Object[]{redirectMethod, null, ArrayUtil.prepend(param, src)};
}
public static Object[] handleMethodInvoke(Method method, Object src, Object[] param) throws Throwable {
Object[] ret = ArclightRedirectAdapter.runHandle(remapper, method, src, param);
if (ret != null) {
return ret;
} else {
return new Object[]{method, src, param};
}
}
public static Object redirectMethodInvoke(Method method, Object src, Object[] param) throws Throwable {
Product4<String, Class<?>[], String, Class<?>[]> invokeRule = ArclightRedirectAdapter.getInvokeRule(method);
if (invokeRule != null) {
if (invokeRule._3 != null && method.getParameterCount() > 0) {
Method handleMethod = remapper.getGeneratedHandlerClass().getMethod(invokeRule._3, invokeRule._4);
if (handleMethod.getReturnType().isArray()) {
if (Modifier.isStatic(method.getModifiers())) {
return method.invoke(src, (Object[]) handleMethod.invoke(null, param));
} else {
Object[] result = (Object[]) handleMethod.invoke(null, ArrayUtil.prepend(param, src));
return method.invoke(result[0], Arrays.copyOfRange(result, 1, result.length));
}
} else {
return method.invoke(src, handleMethod.invoke(null, param));
}
} else {
Method redirectMethod = remapper.getGeneratedHandlerClass().getMethod(invokeRule._1, invokeRule._2);
return redirectMethod.invoke(null, ArrayUtil.prepend(param, src));
}
Object ret = ArclightRedirectAdapter.runRedirect(remapper, method, src, param);
if (ret != remapper) {
return ret;
} else {
return method.invoke(src, param);
}
}
public static Object[] handleDefineClass(ClassLoader loader, byte[] b, int off, int len) {
byte[] bytes = remapper.remapClass(b);
byte[] bytes = transformOrAdd(loader, b);
return new Object[]{loader, bytes, 0, bytes.length};
}
@ -432,28 +402,28 @@ public class ArclightReflectionHandler extends ClassLoader {
}
public static Object[] handleDefineClass(ClassLoader loader, String name, byte[] b, int off, int len) {
byte[] bytes = remapper.remapClass(b);
byte[] bytes = transformOrAdd(loader, b);
return new Object[]{loader, new ClassReader(bytes).getClassName().replace('/', '.'), bytes, 0, bytes.length};
}
public static Class<?> redirectDefineClass(ClassLoader loader, String name, byte[] b, int off, int len) {
return redirectDefineClass(loader, name, b, off, len, (ProtectionDomain) null);
return redirectDefineClass(loader, name, b, off, len, null);
}
public static Object[] handleDefineClass(ClassLoader loader, String name, byte[] b, int off, int len, ProtectionDomain pd) {
byte[] bytes = remapper.remapClass(b);
byte[] bytes = transformOrAdd(loader, b);
return new Object[]{loader, new ClassReader(bytes).getClassName().replace('/', '.'), bytes, 0, bytes.length, pd};
}
public static Class<?> redirectDefineClass(ClassLoader loader, String name, byte[] b, int off, int len, ProtectionDomain pd) {
byte[] bytes = remapper.remapClass(b);
byte[] bytes = transformOrAdd(loader, b);
return Unsafe.defineClass(new ClassReader(bytes).getClassName().replace('/', '.'), bytes, 0, bytes.length, loader, pd);
}
public static Object[] handleDefineClass(ClassLoader loader, String name, ByteBuffer b, ProtectionDomain pd) {
byte[] bytes = new byte[b.remaining()];
b.get(bytes);
bytes = remapper.remapClass(bytes);
bytes = transformOrAdd(loader, bytes);
return new Object[]{loader, new ClassReader(bytes).getClassName().replace('/', '.'), ByteBuffer.wrap(bytes), pd};
}
@ -464,7 +434,7 @@ public class ArclightReflectionHandler extends ClassLoader {
}
public static Object[] handleDefineClass(SecureClassLoader loader, String name, byte[] b, int off, int len, CodeSource cs) {
byte[] bytes = remapper.remapClass(b);
byte[] bytes = transformOrAdd(loader, b);
return new Object[]{loader, new ClassReader(bytes).getClassName().replace('/', '.'), bytes, 0, bytes.length, cs};
}
@ -475,11 +445,38 @@ public class ArclightReflectionHandler extends ClassLoader {
public static Object[] handleDefineClass(SecureClassLoader loader, String name, ByteBuffer b, CodeSource cs) {
byte[] bytes = new byte[b.remaining()];
b.get(bytes);
bytes = remapper.remapClass(bytes);
bytes = transformOrAdd(loader, bytes);
return new Object[]{loader, new ClassReader(bytes).getClassName().replace('/', '.'), ByteBuffer.wrap(bytes), cs};
}
public static Class<?> redirectDefineClass(SecureClassLoader loader, String name, ByteBuffer b, CodeSource cs) {
return redirectDefineClass(loader, name, b, new ProtectionDomain(cs, new Permissions()));
}
public static Object[] handleUnsafeDefineClass(Object unsafe, String name, byte[] bytes, int off, int len, ClassLoader loader, ProtectionDomain pd) {
bytes = transformOrAdd(loader, bytes);
return new Object[]{unsafe, new ClassReader(bytes).getClassName().replace('/', '.'), bytes, 0, bytes.length, loader, pd};
}
public static Class<?> redirectUnsafeDefineClass(Object unsafe, String name, byte[] bytes, int off, int len, ClassLoader loader, ProtectionDomain pd) {
return redirectDefineClass(loader, name, bytes, off, len, pd);
}
public static byte[] transformOrAdd(ClassLoader loader, byte[] bytes) {
RemappingClassLoader rcl = null;
while (loader != null) {
if (loader instanceof RemappingClassLoader) {
rcl = ((RemappingClassLoader) loader);
break;
} else {
loader = loader.getParent();
}
}
if (rcl != null) {
return rcl.getRemapper().remapClass(bytes);
} else {
ArclightRedirectAdapter.scanMethod(bytes);
return bytes;
}
}
}