From e2784ffb0fb2ed3e05f505a2d5b3d56b1be87b6f Mon Sep 17 00:00:00 2001 From: IzzelAliz Date: Mon, 20 Jul 2020 12:39:30 +0800 Subject: [PATCH] Remap custom classloader. Close #16. --- .../mixin/bukkit/PluginClassLoaderMixin.java | 16 +- .../mod/util/remapper/ArclightRemapper.java | 5 +- .../mod/util/remapper/ClassLoaderAdapter.java | 204 ++++++++++++++++++ .../util/remapper/RemappingClassLoader.java | 21 ++ .../generated/RemappingURLClassLoader.java | 80 +++++++ 5 files changed, 319 insertions(+), 7 deletions(-) create mode 100644 arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ClassLoaderAdapter.java create mode 100644 arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/RemappingClassLoader.java create mode 100644 arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/generated/RemappingURLClassLoader.java diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/PluginClassLoaderMixin.java b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/PluginClassLoaderMixin.java index ad9f3880..feb7e7fb 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/PluginClassLoaderMixin.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mixin/bukkit/PluginClassLoaderMixin.java @@ -5,6 +5,7 @@ import io.izzel.arclight.common.asm.SwitchTableFixer; import io.izzel.arclight.common.bridge.bukkit.JavaPluginLoaderBridge; import io.izzel.arclight.common.mod.util.remapper.ArclightRemapper; import io.izzel.arclight.common.mod.util.remapper.ClassLoaderRemapper; +import io.izzel.arclight.common.mod.util.remapper.RemappingClassLoader; import org.bukkit.Bukkit; import org.bukkit.plugin.PluginDescriptionFile; import org.bukkit.plugin.java.JavaPluginLoader; @@ -25,7 +26,7 @@ import java.util.jar.JarFile; import java.util.jar.Manifest; @Mixin(targets = "org.bukkit.plugin.java.PluginClassLoader", remap = false) -public class PluginClassLoaderMixin extends URLClassLoader { +public class PluginClassLoaderMixin extends URLClassLoader implements RemappingClassLoader { // @formatter:off @Shadow @Final private Map> classes; @@ -38,6 +39,14 @@ public class PluginClassLoaderMixin extends URLClassLoader { private ClassLoaderRemapper remapper; + @Override + public ClassLoaderRemapper getRemapper() { + if (remapper == null) { + remapper = ArclightRemapper.createClassLoaderRemapper(this); + } + return remapper; + } + public PluginClassLoaderMixin(URL[] urls) { super(urls); } @@ -73,10 +82,7 @@ public class PluginClassLoaderMixin extends URLClassLoader { classBytes = SwitchTableFixer.INSTANCE.processClass(classBytes); classBytes = Bukkit.getUnsafe().processClass(description, path, classBytes); - if (remapper == null) { - remapper = ArclightRemapper.INSTANCE.createClassLoaderRemapper(this); - } - classBytes = remapper.remapClass(classBytes); + classBytes = this.getRemapper().remapClass(classBytes); int dot = name.lastIndexOf('.'); if (dot != -1) { diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ArclightRemapper.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ArclightRemapper.java index d6224f86..136ca8f6 100644 --- a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ArclightRemapper.java +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ArclightRemapper.java @@ -55,10 +55,11 @@ public class ArclightRemapper { this.toBukkitMapping.setFallbackInheritanceProvider(inheritanceProvider); this.transformerList.add(ArclightInterfaceInvokerGen.INSTANCE); this.transformerList.add(ArclightRedirectAdapter.INSTANCE); + this.transformerList.add(ClassLoaderAdapter.INSTANCE); } - public ClassLoaderRemapper createClassLoaderRemapper(ClassLoader classLoader) { - return new ClassLoaderRemapper(copyOf(toNmsMapping), copyOf(toBukkitMapping), classLoader); + public static ClassLoaderRemapper createClassLoaderRemapper(ClassLoader classLoader) { + return new ClassLoaderRemapper(INSTANCE.copyOf(INSTANCE.toNmsMapping), INSTANCE.copyOf(INSTANCE.toBukkitMapping), classLoader); } public List getTransformerList() { diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ClassLoaderAdapter.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ClassLoaderAdapter.java new file mode 100644 index 00000000..2a8befc1 --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/ClassLoaderAdapter.java @@ -0,0 +1,204 @@ +package io.izzel.arclight.common.mod.util.remapper; + +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.Maps; +import io.izzel.arclight.common.mod.ArclightMod; +import io.izzel.arclight.common.mod.util.remapper.generated.RemappingURLClassLoader; +import org.apache.logging.log4j.Marker; +import org.apache.logging.log4j.MarkerManager; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.FieldNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.InsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; +import org.objectweb.asm.tree.VarInsnNode; + +import java.net.URLClassLoader; +import java.nio.ByteBuffer; +import java.security.SecureClassLoader; +import java.util.Collection; +import java.util.Map; + +public class ClassLoaderAdapter implements PluginTransformer { + + public static final ClassLoaderAdapter INSTANCE = new ClassLoaderAdapter(); + private static final Marker MARKER = MarkerManager.getMarker("CLADAPTER"); + private static final String CLASSLOADER = "java/lang/ClassLoader"; + + private final Map> defineClassTypes = ImmutableMap.>builder() + .put(Type.getMethodType("(Ljava/lang/String;[BIILjava/security/CodeSource;)Ljava/lang/Class;"), Maps.immutableEntry(Type.getType(SecureClassLoader.class), 1)) + .put(Type.getMethodType("(Ljava/lang/String;Ljava/nio/ByteBuffer;Ljava/security/CodeSource;)Ljava/lang/Class;"), Maps.immutableEntry(Type.getType(SecureClassLoader.class), 1)) + .put(Type.getMethodType("([BII)Ljava/lang/Class;"), Maps.immutableEntry(Type.getType(ClassLoader.class), 0)) + .put(Type.getMethodType("(Ljava/lang/String;[BII)Ljava/lang/Class;"), Maps.immutableEntry(Type.getType(ClassLoader.class), 1)) + .put(Type.getMethodType("(Ljava/lang/String;[BIILjava/security/ProtectionDomain;)Ljava/lang/Class;"), Maps.immutableEntry(Type.getType(ClassLoader.class), 1)) + .put(Type.getMethodType("(Ljava/lang/String;Ljava/nio/ByteBuffer;Ljava/security/ProtectionDomain;)Ljava/lang/Class;"), Maps.immutableEntry(Type.getType(ClassLoader.class), 1)) + .build(); + private final Map classLoaderTypes = ImmutableMap.builder() + .put(Type.getInternalName(URLClassLoader.class), Type.getInternalName(RemappingURLClassLoader.class)) + .build(); + + @Override + public void handleClass(ClassNode node, ClassLoaderRemapper remapper) { + for (MethodNode methodNode : node.methods) { + for (AbstractInsnNode insnNode : methodNode.instructions) { + if (insnNode.getOpcode() == Opcodes.NEW) { + TypeInsnNode typeInsnNode = (TypeInsnNode) insnNode; + String replace = classLoaderTypes.get(typeInsnNode.desc); + if (replace != null) { + AbstractInsnNode next = typeInsnNode.getNext(); + while (next != null && (next.getOpcode() != Opcodes.INVOKESPECIAL || !((MethodInsnNode) next).name.equals("") || !((MethodInsnNode) next).owner.equals(typeInsnNode.desc))) { + next = next.getNext(); + } + if (next == null) continue; + ArclightMod.LOGGER.debug(MARKER, "Found new {}/{} call in {} {}", typeInsnNode.desc, ((MethodInsnNode) next).name + ((MethodInsnNode) next).desc, node.name, methodNode.name + methodNode.desc); + ((MethodInsnNode) next).owner = replace; + typeInsnNode.desc = replace; + } + } + } + } + ClassInfo info = classInfo(node); + if (info == null) return; + ArclightMod.LOGGER.debug(MARKER, "Transforming classloader class {}", node.name); + if (!info.remapping) { + implementIntf(node); + } + for (MethodNode methodNode : node.methods) { + for (AbstractInsnNode insnNode : methodNode.instructions) { + if (insnNode instanceof MethodInsnNode) { + MethodInsnNode methodInsnNode = (MethodInsnNode) insnNode; + if (methodInsnNode.getOpcode() == Opcodes.INVOKESPECIAL && methodNode.name.equals("") && methodInsnNode.name.equals("") && methodInsnNode.owner.equals(node.superName)) { + methodInsnNode.owner = info.superName; + } + if (methodInsnNode.name.equals("defineClass")) { + Type descType = Type.getMethodType(methodInsnNode.desc); + Map.Entry entry = defineClassTypes.get(descType); + if (entry != null && GlobalClassRepo.inheritanceProvider().getAll(methodInsnNode.owner).contains(entry.getKey().getInternalName())) { + ArclightMod.LOGGER.debug(MARKER, "Found defineClass {} call in {} {}", descType.getInternalName(), node.name, methodNode.name + methodNode.desc); + int index = entry.getValue(); + InsnList insnList = new InsnList(); + Type[] argumentTypes = descType.getArgumentTypes(); + int[] argsMap = argsMap(argumentTypes); + storeArgs(argumentTypes, argsMap, methodNode, insnList); + insnList.add(new InsnNode(Opcodes.DUP)); + insnList.add(new VarInsnNode(argumentTypes[index].getOpcode(Opcodes.ILOAD), methodNode.maxLocals + argsMap[index])); + insnList.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(ClassLoaderAdapter.class), "remapClassContent", "(Ljava/lang/Object;Ljava/lang/Object;)Ljava/lang/Object;", false)); + insnList.add(new TypeInsnNode(Opcodes.CHECKCAST, argumentTypes[index].getInternalName())); + insnList.add(new VarInsnNode(argumentTypes[index].getOpcode(Opcodes.ISTORE), methodNode.maxLocals + argsMap[index])); + loadArgs(argumentTypes, argsMap, methodNode, insnList); + methodNode.instructions.insertBefore(methodInsnNode, insnList); + } + } + } + } + } + node.superName = info.superName; + } + + @SuppressWarnings("unused") + public static Object remapClassContent(ClassLoader classLoader, Object classContent) { + ArclightMod.LOGGER.trace(MARKER, "Remapping class content {} from classloader {}", classContent, classLoader); + if (!(classLoader instanceof RemappingClassLoader)) { + throw new IllegalArgumentException("" + classLoader + " is not a remapping class loader!"); + } + byte[] classBytes; + if (classContent instanceof byte[]) { + classBytes = ((byte[]) classContent); + } else if (classContent instanceof ByteBuffer) { + classBytes = new byte[((ByteBuffer) classContent).remaining()]; + ((ByteBuffer) classContent).get(classBytes); + } else { + throw new IllegalArgumentException("" + classContent + " is not a recognized class content type!"); + } + byte[] bytes = ((RemappingClassLoader) classLoader).getRemapper().remapClass(classBytes); + if (classContent instanceof byte[]) { + return bytes; + } else { + return ByteBuffer.wrap(bytes); + } + } + + private static int[] argsMap(Type[] args) { + int[] ints = new int[args.length]; + int offset = 0; + for (int i = 0; i < args.length; i++) { + Type arg = args[i]; + ints[i] = offset; + offset += arg.getSize(); + } + return ints; + } + + private static void storeArgs(Type[] args, int[] argsMap, MethodNode node, InsnList list) { + int start = node.maxLocals; + for (int i = args.length - 1; i >= 0; i--) { + Type arg = args[i]; + list.add(new VarInsnNode(arg.getOpcode(Opcodes.ISTORE), start + argsMap[i])); + } + } + + private static void loadArgs(Type[] args, int[] argsMap, MethodNode node, InsnList list) { + int start = node.maxLocals; + for (int i = 0; i < args.length; i++) { + Type arg = args[i]; + list.add(new VarInsnNode(arg.getOpcode(Opcodes.ILOAD), start + argsMap[i])); + node.maxLocals += argsMap[i]; + } + } + + private void implementIntf(ClassNode node) { + ArclightMod.LOGGER.debug(MARKER, "Implementing RemappingClassLoader for class {}", node.name); + FieldNode remapper = new FieldNode(Opcodes.ACC_PRIVATE | Opcodes.ACC_SYNTHETIC, "remapper", Type.getDescriptor(ClassLoaderRemapper.class), null, null); + MethodNode methodNode = new MethodNode(Opcodes.ACC_PUBLIC | Opcodes.ACC_SYNTHETIC, "getRemapper", Type.getMethodDescriptor(Type.getType(ClassLoaderRemapper.class)), null, null); + InsnList list = new InsnList(); + LabelNode labelNode = new LabelNode(); + list.add(new VarInsnNode(Opcodes.ALOAD, 0)); + list.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, remapper.name, remapper.desc)); + list.add(new JumpInsnNode(Opcodes.IFNONNULL, labelNode)); + list.add(new VarInsnNode(Opcodes.ALOAD, 0)); + list.add(new InsnNode(Opcodes.DUP)); + list.add(new MethodInsnNode(Opcodes.INVOKESTATIC, Type.getInternalName(ArclightRemapper.class), "createClassLoaderRemapper", Type.getMethodDescriptor(Type.getType(ClassLoaderRemapper.class), Type.getType(ClassLoader.class)), false)); + list.add(new FieldInsnNode(Opcodes.PUTFIELD, node.name, remapper.name, remapper.desc)); + list.add(labelNode); + list.add(new VarInsnNode(Opcodes.ALOAD, 0)); + list.add(new FieldInsnNode(Opcodes.GETFIELD, node.name, remapper.name, remapper.desc)); + list.add(new InsnNode(Opcodes.ARETURN)); + methodNode.instructions = list; + node.fields.add(remapper); + node.methods.add(methodNode); + node.interfaces.add(Type.getInternalName(RemappingClassLoader.class)); + } + + private ClassInfo classInfo(ClassNode node) { + ClassInfo info = new ClassInfo(); + Collection parents = GlobalClassRepo.inheritanceProvider().getAll(node.superName); + if (!parents.contains(CLASSLOADER)) return null; + for (String s : classLoaderTypes.keySet()) { + if (parents.contains(s)) { + info.remapping = true; + break; + } + } + String s = classLoaderTypes.get(node.superName); + if (s != null) { + info.superName = s; + } else { + info.superName = node.superName; + } + return info; + } + + private static class ClassInfo { + + private String superName; + private boolean remapping; + } +} diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/RemappingClassLoader.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/RemappingClassLoader.java new file mode 100644 index 00000000..9e436751 --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/RemappingClassLoader.java @@ -0,0 +1,21 @@ +package io.izzel.arclight.common.mod.util.remapper; + +import cpw.mods.modlauncher.api.ITransformingClassLoader; + +public interface RemappingClassLoader { + + ClassLoaderRemapper getRemapper(); + + static ClassLoader asTransforming(ClassLoader classLoader) { + boolean found = false; + while (classLoader != null) { + if (classLoader instanceof ITransformingClassLoader || classLoader instanceof RemappingClassLoader) { + found = true; + break; + } else { + classLoader = classLoader.getParent(); + } + } + return found ? classLoader : RemappingClassLoader.class.getClassLoader(); + } +} diff --git a/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/generated/RemappingURLClassLoader.java b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/generated/RemappingURLClassLoader.java new file mode 100644 index 00000000..75320039 --- /dev/null +++ b/arclight-common/src/main/java/io/izzel/arclight/common/mod/util/remapper/generated/RemappingURLClassLoader.java @@ -0,0 +1,80 @@ +package io.izzel.arclight.common.mod.util.remapper.generated; + +import com.google.common.io.ByteStreams; +import io.izzel.arclight.common.mod.util.remapper.ArclightRemapper; +import io.izzel.arclight.common.mod.util.remapper.ClassLoaderRemapper; +import io.izzel.arclight.common.mod.util.remapper.RemappingClassLoader; + +import java.io.IOException; +import java.net.JarURLConnection; +import java.net.URL; +import java.net.URLClassLoader; +import java.net.URLConnection; +import java.net.URLStreamHandlerFactory; +import java.security.CodeSource; + +public class RemappingURLClassLoader extends URLClassLoader implements RemappingClassLoader { + + static { + ClassLoader.registerAsParallelCapable(); + } + + public RemappingURLClassLoader(URL[] urls, ClassLoader parent) { + super(urls, RemappingClassLoader.asTransforming(parent)); + } + + public RemappingURLClassLoader(URL[] urls) { + super(urls, RemappingClassLoader.asTransforming(null)); + } + + public RemappingURLClassLoader(URL[] urls, ClassLoader parent, URLStreamHandlerFactory factory) { + super(urls, RemappingClassLoader.asTransforming(parent), factory); + } + + @Override + protected Class findClass(String name) throws ClassNotFoundException { + Class result = null; + String path = name.replace('.', '/').concat(".class"); + URL resource = this.getResource(path); + if (resource != null) { + try { + URLConnection connection = resource.openConnection(); + byte[] bytes = getRemapper().remapClass(ByteStreams.toByteArray(connection.getInputStream())); + int i = name.lastIndexOf('.'); + if (i != -1) { + String pkgName = name.substring(0, i); + if (getPackage(pkgName) == null) { + if (connection instanceof JarURLConnection && ((JarURLConnection) connection).getManifest() != null) { + this.definePackage(pkgName, ((JarURLConnection) connection).getManifest(), ((JarURLConnection) connection).getJarFileURL()); + } else { + this.definePackage(pkgName, null, null, null, null, null, null, null); + } + } + } + CodeSource codeSource; + if (connection instanceof JarURLConnection) { + codeSource = new CodeSource(((JarURLConnection) connection).getJarFileURL(), ((JarURLConnection) connection).getJarEntry().getCodeSigners()); + } else { + codeSource = null; + } + result = this.defineClass(name, bytes, 0, bytes.length, codeSource); + } catch (IOException e) { + throw new ClassNotFoundException(name, e); + } + } + if (result == null) { + throw new ClassNotFoundException(name); + } + return result; + } + + private ClassLoaderRemapper remapper; + + @Override + public ClassLoaderRemapper getRemapper() { + if (remapper == null) { + remapper = ArclightRemapper.createClassLoaderRemapper(this); + } + return remapper; + } +}