From c3c2b0a34c03f5368852ebee4fa90f48098ef38c Mon Sep 17 00:00:00 2001 From: jglrxavpok Date: Sun, 23 Aug 2020 23:27:53 +0200 Subject: [PATCH] Loading mixins from extensions --- .../java/net/minestom/server/Bootstrap.java | 3 +- .../server/extensions/ExtensionManager.java | 7 +- .../MinestomOverwriteClassLoader.java | 65 +++++++--------- .../mixins/GlobalPropertyServiceMinestom.java | 3 + .../mixins/MinestomBytecodeProvider.java | 3 + .../mixins/MinestomClassProvider.java | 3 + .../mixins/MinestomTransformerProvider.java | 75 ------------------- .../mixins/MixinAuditTrailMinestom.java | 25 +++++++ .../mixins/MixinCodeModifier.java | 38 ++++------ .../mixins/MixinServiceMinestom.java | 11 ++- ...java => TestExtensionLauncherNoSetup.java} | 4 +- .../mixins/InstanceContainerMixin.java | 1 - src/test/resources/extension.json | 3 +- 13 files changed, 91 insertions(+), 150 deletions(-) delete mode 100644 src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomTransformerProvider.java create mode 100644 src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinAuditTrailMinestom.java rename src/test/java/testextension/{TestExtensionLauncher.java => TestExtensionLauncherNoSetup.java} (77%) diff --git a/src/main/java/net/minestom/server/Bootstrap.java b/src/main/java/net/minestom/server/Bootstrap.java index 0c0f20e43..ec1f5139c 100644 --- a/src/main/java/net/minestom/server/Bootstrap.java +++ b/src/main/java/net/minestom/server/Bootstrap.java @@ -21,15 +21,14 @@ public class Bootstrap { try { ClassLoader classLoader = MinestomOverwriteClassLoader.getInstance(); startMixin(args); - MixinEnvironment.init(MixinEnvironment.Phase.INIT); MinestomOverwriteClassLoader.getInstance().addCodeModifier(new MixinCodeModifier()); + MixinEnvironment.init(MixinEnvironment.Phase.DEFAULT); // ensure extensions are loaded when starting the server Class serverClass = classLoader.loadClass("net.minestom.server.MinecraftServer"); Method init = serverClass.getMethod("init"); init.invoke(null); - Class mainClass = classLoader.loadClass(mainClassFullName); Method main = mainClass.getDeclaredMethod("main", String[].class); main.invoke(null, new Object[] { args }); diff --git a/src/main/java/net/minestom/server/extensions/ExtensionManager.java b/src/main/java/net/minestom/server/extensions/ExtensionManager.java index 33572a0c1..80dd06717 100644 --- a/src/main/java/net/minestom/server/extensions/ExtensionManager.java +++ b/src/main/java/net/minestom/server/extensions/ExtensionManager.java @@ -6,6 +6,7 @@ import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import org.slf4j.LoggerFactory; +import org.spongepowered.asm.mixin.Mixins; import java.io.*; import java.lang.reflect.Constructor; @@ -232,7 +233,11 @@ public class ExtensionManager { modifiableClassLoader.loadModifier(extension.files, elem.getAsString()); } } - // TODO: special support for mixins + if(extension.description.has("mixinConfig")) { + String mixinConfigFile = extension.description.get("mixinConfig").getAsString(); + Mixins.addConfiguration(mixinConfigFile); + log.info("Found mixin in extension "+extension.description.get("name").getAsString()+": "+mixinConfigFile); + } } catch (Exception e) { e.printStackTrace(); log.error("Failed to load code modifier for extension in files: "+Arrays.toString(extension.files), e); diff --git a/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java b/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java index 8690db615..721f7c30c 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java @@ -9,7 +9,6 @@ import org.objectweb.asm.tree.ClassNode; import java.io.File; import java.io.IOException; import java.lang.reflect.InvocationTargetException; -import java.lang.reflect.Method; import java.net.MalformedURLException; import java.net.URL; import java.net.URLClassLoader; @@ -18,6 +17,9 @@ import java.util.LinkedList; import java.util.List; import java.util.Set; +/** + * Class Loader that can modify class bytecode when they are loaded + */ @Slf4j public class MinestomOverwriteClassLoader extends URLClassLoader { @@ -38,8 +40,16 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { add("com.google"); add("com.mojang"); add("org.objectweb.asm"); + add("org.slf4j"); + add("org.apache"); + add("org.spongepowered"); + add("net.minestom.server.extras.selfmodification"); } }; + /** + * Used to let ASM find out common super types, without actually commiting to loading them + * Otherwise ASM would accidentally load classes we might want to modify + */ private final URLClassLoader asmClassLoader; // TODO: replace by tree to optimize lookup times. We can use the fact that package names are split with '.' to allow for fast lookup @@ -48,26 +58,9 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { // TODO: priorities? private List modifiers = new LinkedList<>(); - private final Method findParentLoadedClass; - private final Class loadedCodeModifier; private MinestomOverwriteClassLoader(ClassLoader parent) { - super("Minestom ClassLoader", loadURLs(), parent); - try { - findParentLoadedClass = ClassLoader.class.getDeclaredMethod("findLoadedClass", String.class); - findParentLoadedClass.setAccessible(true); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - throw new Error("Failed to access ClassLoader#findLoadedClass", e); - } - - try { - loadedCodeModifier = loadClass("net.minestom.server.extras.selfmodification.CodeModifier"); - } catch (ClassNotFoundException e) { - e.printStackTrace(); - throw new Error("Failed to access CodeModifier class."); - } - + super("Minestom ClassLoader", extractURLsFromClasspath(), parent); asmClassLoader = newChild(new URL[0]); } @@ -82,7 +75,7 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { return INSTANCE; } - private static URL[] loadURLs() { + private static URL[] extractURLsFromClasspath() { String classpath = System.getProperty("java.class.path"); String[] parts = classpath.split(";"); URL[] urls = new URL[parts.length]; @@ -103,13 +96,6 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { return urls; } - private static URL[] fromParent(ClassLoader parent) { - if(parent instanceof URLClassLoader) { - return ((URLClassLoader) parent).getURLs(); - } - return new URL[0]; - } - @Override public Class loadClass(String name, boolean resolve) throws ClassNotFoundException { Class loadedClass = findLoadedClass(name); @@ -117,18 +103,12 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { return loadedClass; try { + // we do not load system classes by ourselves Class systemClass = ClassLoader.getPlatformClassLoader().loadClass(name); log.trace("System class: "+systemClass); return systemClass; } catch (ClassNotFoundException e) { try { - // check if parent already loaded the class - Class loadedByParent = (Class) findParentLoadedClass.invoke(getParent(), name); - if(loadedByParent != null) { - log.trace("Already found in parent: "+loadedByParent); - return super.loadClass(name, resolve); - } - if(isProtected(name)) { log.trace("Protected: "+name); return super.loadClass(name, resolve); @@ -164,7 +144,17 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { return defined; } - public byte[] loadBytes(String name, boolean transform) throws IOException { + /** + * Loads and possibly transforms class bytecode corresponding to the given binary name. + * @param name + * @param transform + * @return + * @throws IOException + * @throws ClassNotFoundException + */ + public byte[] loadBytes(String name, boolean transform) throws IOException, ClassNotFoundException { + if(name == null) + throw new ClassNotFoundException(); String path = name.replace(".", "/") + ".class"; byte[] bytes = getResourceAsStream(path).readAllBytes(); if(transform && !isProtected(name)) { @@ -195,15 +185,12 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { return bytes; } + // overriden to increase access (from protected to public) @Override public Class findClass(String name) throws ClassNotFoundException { return super.findClass(name); } - public void resolve(Class clazz) { - resolveClass(clazz); - } - @NotNull public URLClassLoader newChild(@NotNull URL[] urls) { return URLClassLoader.newInstance(urls, this); diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/GlobalPropertyServiceMinestom.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/GlobalPropertyServiceMinestom.java index a33cc14b8..e7f5df0f3 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/mixins/GlobalPropertyServiceMinestom.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/GlobalPropertyServiceMinestom.java @@ -7,6 +7,9 @@ import java.util.HashMap; import java.util.Map; import java.util.Objects; +/** + * Global properties service for Mixin + */ public class GlobalPropertyServiceMinestom implements IGlobalPropertyService { private class BasicProperty implements IPropertyKey { diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomBytecodeProvider.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomBytecodeProvider.java index e303a6bcc..c8b3f20cb 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomBytecodeProvider.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomBytecodeProvider.java @@ -7,6 +7,9 @@ import org.spongepowered.asm.service.IClassBytecodeProvider; import java.io.IOException; +/** + * Provides class bytecode for Mixin + */ public class MinestomBytecodeProvider implements IClassBytecodeProvider { private final MinestomOverwriteClassLoader classLoader; diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomClassProvider.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomClassProvider.java index 7ee9ce2c1..ae3b98b4f 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomClassProvider.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomClassProvider.java @@ -5,6 +5,9 @@ import org.spongepowered.asm.service.IClassProvider; import java.net.URL; +/** + * Provides classes for Mixin + */ public class MinestomClassProvider implements IClassProvider { private final MinestomOverwriteClassLoader classLoader; diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomTransformerProvider.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomTransformerProvider.java deleted file mode 100644 index 43bbac597..000000000 --- a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MinestomTransformerProvider.java +++ /dev/null @@ -1,75 +0,0 @@ -package net.minestom.server.extras.selfmodification.mixins; - -import net.minestom.server.extras.selfmodification.CodeModifier; -import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; -import org.objectweb.asm.tree.ClassNode; -import org.spongepowered.asm.service.ITransformer; -import org.spongepowered.asm.service.ITransformerProvider; -import org.spongepowered.asm.service.ITreeClassTransformer; - -import java.lang.reflect.InvocationTargetException; -import java.util.Collection; -import java.util.LinkedList; -import java.util.List; - -public class MinestomTransformerProvider implements ITransformerProvider { - private final MinestomOverwriteClassLoader classLoader; - private List transformers; - - public MinestomTransformerProvider(MinestomOverwriteClassLoader classLoader) { - this.classLoader = classLoader; - } - - @Override - public void addTransformerExclusion(String name) { - classLoader.protectedClasses.add(name); - } - - @Override - public Collection getTransformers() { - return getDelegatedTransformers(); - } - - @Override - public Collection getDelegatedTransformers() { - if(transformers == null) { - transformers = buildTransformerList(); - } - return transformers; - } - - private List buildTransformerList() { - List result = new LinkedList<>(); - for(CodeModifier modifier : classLoader.getModifiers()) { - result.add(toMixin(modifier)); - } - - try { - Class clazz = classLoader.loadClass("org.spongepowered.asm.mixin.transformer.MixingTransformer"); - ITransformer mixinTransformer = (ITransformer) clazz.getDeclaredConstructor().newInstance(); - result.add(mixinTransformer); - } catch (ClassNotFoundException | InstantiationException | InvocationTargetException | NoSuchMethodException | IllegalAccessException e) { - e.printStackTrace(); - } - return result; - } - - private ITransformer toMixin(CodeModifier modifier) { - return new ITreeClassTransformer() { - @Override - public boolean transformClassNode(String name, String transformedName, ClassNode classNode) { - return modifier.transform(classNode); - } - - @Override - public String getName() { - return modifier.getClass().getName(); - } - - @Override - public boolean isDelegationExcluded() { - return false; - } - }; - } -} diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinAuditTrailMinestom.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinAuditTrailMinestom.java new file mode 100644 index 000000000..d8a2784af --- /dev/null +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinAuditTrailMinestom.java @@ -0,0 +1,25 @@ +package net.minestom.server.extras.selfmodification.mixins; + +import lombok.extern.slf4j.Slf4j; +import org.spongepowered.asm.service.IMixinAuditTrail; + +/** + * Takes care of logging mixin operations + */ +@Slf4j +public class MixinAuditTrailMinestom implements IMixinAuditTrail { + @Override + public void onApply(String className, String mixinName) { + log.trace("Applied mixin "+mixinName+" to class "+className); + } + + @Override + public void onPostProcess(String className) { + log.trace("Post processing "+className); + } + + @Override + public void onGenerate(String className, String generatorName) { + log.trace("Generating class "+className+" via generator "+generatorName); + } +} diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinCodeModifier.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinCodeModifier.java index 7c5dc4123..c52e51713 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinCodeModifier.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinCodeModifier.java @@ -3,50 +3,44 @@ package net.minestom.server.extras.selfmodification.mixins; import net.minestom.server.extras.selfmodification.CodeModifier; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.MixinEnvironment; -import org.spongepowered.asm.mixin.transformer.MixinProcessor; -import org.spongepowered.asm.mixin.transformer.ext.Extensions; -import org.spongepowered.asm.mixin.transformer.ext.IHotSwap; -import org.spongepowered.asm.service.ISyntheticClassInfo; -import org.spongepowered.asm.service.ISyntheticClassRegistry; import org.spongepowered.asm.transformers.TreeTransformer; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; +/** + * CodeModifier responsible for applying Mixins during class load + */ public class MixinCodeModifier extends CodeModifier { - private Method transformClassMethod; - private TreeTransformer processor; + /** + * Call MixinTransformer's transformClass + */ + private final Method transformClassMethod; + private final TreeTransformer transformer; public MixinCodeModifier() { try { + // MixinTransformer is package-protected, so we have to force to gain access Class mixinTransformerClass = Class.forName("org.spongepowered.asm.mixin.transformer.MixinTransformer"); Constructor ctor = mixinTransformerClass.getDeclaredConstructor(); ctor.setAccessible(true); - this.processor = (TreeTransformer) ctor.newInstance(); + this.transformer = (TreeTransformer) ctor.newInstance(); + + // we can't access the MixinTransformer type here, so we use reflection to access the method transformClassMethod = mixinTransformerClass.getDeclaredMethod("transformClass", MixinEnvironment.class, String.class, ClassNode.class); transformClassMethod.setAccessible(true); - } catch (NoSuchMethodException e) { - e.printStackTrace(); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InstantiationException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { - e.printStackTrace(); - } catch (ClassNotFoundException e) { - e.printStackTrace(); + } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException | ClassNotFoundException e) { + throw new RuntimeException("Failed to initialize MixinCodeModifier", e); } } @Override public boolean transform(ClassNode source) { try { - return (boolean) transformClassMethod.invoke(processor, MixinEnvironment.getEnvironment(MixinEnvironment.Phase.DEFAULT), source.name.replace("/", "."), source); - } catch (IllegalAccessException e) { - e.printStackTrace(); - } catch (InvocationTargetException e) { + return (boolean) transformClassMethod.invoke(transformer, MixinEnvironment.getEnvironment(MixinEnvironment.Phase.DEFAULT), source.name.replace("/", "."), source); + } catch (IllegalAccessException | InvocationTargetException e) { e.printStackTrace(); } return false; diff --git a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestom.java b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestom.java index 878aac9e8..a3c9a2cc6 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestom.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/mixins/MixinServiceMinestom.java @@ -16,13 +16,13 @@ public class MixinServiceMinestom extends MixinServiceAbstract { private final MinestomOverwriteClassLoader classLoader; private final MinestomClassProvider classProvider; private final MinestomBytecodeProvider bytecodeProvider; - private final MinestomTransformerProvider transformerProvider; + private final MixinAuditTrailMinestom auditTrail; public MixinServiceMinestom() { this.classLoader = MinestomOverwriteClassLoader.getInstance(); classProvider = new MinestomClassProvider(classLoader); bytecodeProvider = new MinestomBytecodeProvider(classLoader); - transformerProvider = new MinestomTransformerProvider(classLoader); + auditTrail = new MixinAuditTrailMinestom(); } @Override @@ -47,7 +47,7 @@ public class MixinServiceMinestom extends MixinServiceAbstract { @Override public ITransformerProvider getTransformerProvider() { - return transformerProvider; + return null; } @Override @@ -65,8 +65,6 @@ public class MixinServiceMinestom extends MixinServiceAbstract { return classLoader.getResourceAsStream(name); } - // TODO: everything below - @Override public IClassTracker getClassTracker() { return null; @@ -74,12 +72,13 @@ public class MixinServiceMinestom extends MixinServiceAbstract { @Override public IMixinAuditTrail getAuditTrail() { - return null; + return auditTrail; } @Override public void wire(MixinEnvironment.Phase phase, IConsumer phaseConsumer) { super.wire(phase, phaseConsumer); + // TODO: hook into Minestom initialization process phaseConsumer.accept(MixinEnvironment.Phase.PREINIT); phaseConsumer.accept(MixinEnvironment.Phase.INIT); } diff --git a/src/test/java/testextension/TestExtensionLauncher.java b/src/test/java/testextension/TestExtensionLauncherNoSetup.java similarity index 77% rename from src/test/java/testextension/TestExtensionLauncher.java rename to src/test/java/testextension/TestExtensionLauncherNoSetup.java index 6479b71af..a3e358579 100644 --- a/src/test/java/testextension/TestExtensionLauncher.java +++ b/src/test/java/testextension/TestExtensionLauncherNoSetup.java @@ -6,11 +6,9 @@ import org.spongepowered.asm.mixin.Mixins; // To launch with VM arguments: // -Dminestom.extension.indevfolder.classes=build/classes/java/test/ -Dminestom.extension.indevfolder.resources=build/resources/test/ -public class TestExtensionLauncher { +public class TestExtensionLauncherNoSetup { public static void main(String[] args) { - MixinBootstrap.init(); - Mixins.addConfiguration("mixins.testextension.json"); Bootstrap.bootstrap("fr.themode.demo.MainDemo", args); } diff --git a/src/test/java/testextension/mixins/InstanceContainerMixin.java b/src/test/java/testextension/mixins/InstanceContainerMixin.java index 972ac6c51..7bed56a2c 100644 --- a/src/test/java/testextension/mixins/InstanceContainerMixin.java +++ b/src/test/java/testextension/mixins/InstanceContainerMixin.java @@ -14,5 +14,4 @@ public class InstanceContainerMixin { System.out.println("Hello from Mixin!!!"); } - } diff --git a/src/test/resources/extension.json b/src/test/resources/extension.json index c464737a0..c7d404173 100644 --- a/src/test/resources/extension.json +++ b/src/test/resources/extension.json @@ -3,5 +3,6 @@ "name": "Test extension", "codeModifiers": [ "testextension.TestModifier" - ] + ], + "mixinConfig": "mixins.testextension.json" } \ No newline at end of file