Remap custom classloader. Close #16.
This commit is contained in:
parent
18b5977853
commit
e2784ffb0f
|
@ -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) {
|
||||
|
|
|
@ -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() {
|
||||
|
|
|
@ -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;
|
||||
}
|
||||
}
|
|
@ -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();
|
||||
}
|
||||
}
|
|
@ -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;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue
Block a user