From 2a01f3230a2adb26c6d811efcf8b0ca66110b32a Mon Sep 17 00:00:00 2001 From: IzzelAliz Date: Sat, 20 Feb 2021 02:12:37 +0800 Subject: [PATCH] Remap classes for Unsafe and redirect bridge classes (#138) --- .../java/io/izzel/arclight/api/Unsafe.java | 17 +- .../remapper/ArclightRedirectAdapter.java | 187 ++++++++++++++++-- .../generated/ArclightReflectionHandler.java | 89 ++++----- 3 files changed, 231 insertions(+), 62 deletions(-) diff --git a/arclight-api/src/main/java/io/izzel/arclight/api/Unsafe.java b/arclight-api/src/main/java/io/izzel/arclight/api/Unsafe.java index 25dd5339..c81f9541 100644 --- a/arclight-api/src/main/java/io/izzel/arclight/api/Unsafe.java +++ b/arclight-api/src/main/java/io/izzel/arclight/api/Unsafe.java @@ -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; diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ArclightRedirectAdapter.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ArclightRedirectAdapter.java index a81a55ad..8711d413 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ArclightRedirectAdapter.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ArclightRedirectAdapter.java @@ -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> METHOD_MODIFY = HashMultimap.create(); private static final Multimap> METHOD_REDIRECT = HashMultimap.create(); - private static final Map[], String, Class[]>> METHOD_TO_HANDLER = new HashMap<>(); + private static final Map> 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[]> getInvokeRule(Method method) { - return METHOD_TO_HANDLER.get(method); + public static Object[] runHandle(ClassLoaderRemapper remapper, Method method, Object src, Object[] param) { + Func4 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 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 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 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[]> 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 { + + 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 { + + 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 { + + private final Func4 bridge; + private final Method targetMethod; + + private BridgeHandler(Func4 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])}; + } + } + } } diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/generated/ArclightReflectionHandler.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/generated/ArclightReflectionHandler.java index 078e7f72..496187e0 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/generated/ArclightReflectionHandler.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/generated/ArclightReflectionHandler.java @@ -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[]> 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[]> 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; + } + } }