Remap custom classloader. Close #16.

This commit is contained in:
IzzelAliz 2020-07-20 12:39:30 +08:00
parent 18b5977853
commit e2784ffb0f
5 changed files with 319 additions and 7 deletions

View File

@ -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<String, Class<?>> 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) {

View File

@ -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<PluginTransformer> getTransformerList() {

View File

@ -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<Type, Map.Entry<Type, Integer>> defineClassTypes = ImmutableMap.<Type, Map.Entry<Type, Integer>>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<String, String> classLoaderTypes = ImmutableMap.<String, String>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("<init>") || !((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("<init>") && methodInsnNode.name.equals("<init>") && methodInsnNode.owner.equals(node.superName)) {
methodInsnNode.owner = info.superName;
}
if (methodInsnNode.name.equals("defineClass")) {
Type descType = Type.getMethodType(methodInsnNode.desc);
Map.Entry<Type, Integer> 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<String> 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;
}
}

View File

@ -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();
}
}

View File

@ -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;
}
}