Loading mixins from extensions

This commit is contained in:
jglrxavpok 2020-08-23 23:27:53 +02:00
parent 26b8ad125e
commit c3c2b0a34c
13 changed files with 91 additions and 150 deletions

View File

@ -21,15 +21,14 @@ public class Bootstrap {
try { try {
ClassLoader classLoader = MinestomOverwriteClassLoader.getInstance(); ClassLoader classLoader = MinestomOverwriteClassLoader.getInstance();
startMixin(args); startMixin(args);
MixinEnvironment.init(MixinEnvironment.Phase.INIT);
MinestomOverwriteClassLoader.getInstance().addCodeModifier(new MixinCodeModifier()); MinestomOverwriteClassLoader.getInstance().addCodeModifier(new MixinCodeModifier());
MixinEnvironment.init(MixinEnvironment.Phase.DEFAULT);
// ensure extensions are loaded when starting the server // ensure extensions are loaded when starting the server
Class<?> serverClass = classLoader.loadClass("net.minestom.server.MinecraftServer"); Class<?> serverClass = classLoader.loadClass("net.minestom.server.MinecraftServer");
Method init = serverClass.getMethod("init"); Method init = serverClass.getMethod("init");
init.invoke(null); init.invoke(null);
Class<?> mainClass = classLoader.loadClass(mainClassFullName); Class<?> mainClass = classLoader.loadClass(mainClassFullName);
Method main = mainClass.getDeclaredMethod("main", String[].class); Method main = mainClass.getDeclaredMethod("main", String[].class);
main.invoke(null, new Object[] { args }); main.invoke(null, new Object[] { args });

View File

@ -6,6 +6,7 @@ import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable; import org.jetbrains.annotations.Nullable;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.spongepowered.asm.mixin.Mixins;
import java.io.*; import java.io.*;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -232,7 +233,11 @@ public class ExtensionManager {
modifiableClassLoader.loadModifier(extension.files, elem.getAsString()); 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) { } catch (Exception e) {
e.printStackTrace(); e.printStackTrace();
log.error("Failed to load code modifier for extension in files: "+Arrays.toString(extension.files), e); log.error("Failed to load code modifier for extension in files: "+Arrays.toString(extension.files), e);

View File

@ -9,7 +9,6 @@ import org.objectweb.asm.tree.ClassNode;
import java.io.File; import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.MalformedURLException; import java.net.MalformedURLException;
import java.net.URL; import java.net.URL;
import java.net.URLClassLoader; import java.net.URLClassLoader;
@ -18,6 +17,9 @@ import java.util.LinkedList;
import java.util.List; import java.util.List;
import java.util.Set; import java.util.Set;
/**
* Class Loader that can modify class bytecode when they are loaded
*/
@Slf4j @Slf4j
public class MinestomOverwriteClassLoader extends URLClassLoader { public class MinestomOverwriteClassLoader extends URLClassLoader {
@ -38,8 +40,16 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
add("com.google"); add("com.google");
add("com.mojang"); add("com.mojang");
add("org.objectweb.asm"); 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; 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 // 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? // TODO: priorities?
private List<CodeModifier> modifiers = new LinkedList<>(); private List<CodeModifier> modifiers = new LinkedList<>();
private final Method findParentLoadedClass;
private final Class<?> loadedCodeModifier;
private MinestomOverwriteClassLoader(ClassLoader parent) { private MinestomOverwriteClassLoader(ClassLoader parent) {
super("Minestom ClassLoader", loadURLs(), parent); super("Minestom ClassLoader", extractURLsFromClasspath(), 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.");
}
asmClassLoader = newChild(new URL[0]); asmClassLoader = newChild(new URL[0]);
} }
@ -82,7 +75,7 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
return INSTANCE; return INSTANCE;
} }
private static URL[] loadURLs() { private static URL[] extractURLsFromClasspath() {
String classpath = System.getProperty("java.class.path"); String classpath = System.getProperty("java.class.path");
String[] parts = classpath.split(";"); String[] parts = classpath.split(";");
URL[] urls = new URL[parts.length]; URL[] urls = new URL[parts.length];
@ -103,13 +96,6 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
return urls; return urls;
} }
private static URL[] fromParent(ClassLoader parent) {
if(parent instanceof URLClassLoader) {
return ((URLClassLoader) parent).getURLs();
}
return new URL[0];
}
@Override @Override
public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException { public Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException {
Class<?> loadedClass = findLoadedClass(name); Class<?> loadedClass = findLoadedClass(name);
@ -117,18 +103,12 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
return loadedClass; return loadedClass;
try { try {
// we do not load system classes by ourselves
Class<?> systemClass = ClassLoader.getPlatformClassLoader().loadClass(name); Class<?> systemClass = ClassLoader.getPlatformClassLoader().loadClass(name);
log.trace("System class: "+systemClass); log.trace("System class: "+systemClass);
return systemClass; return systemClass;
} catch (ClassNotFoundException e) { } catch (ClassNotFoundException e) {
try { 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)) { if(isProtected(name)) {
log.trace("Protected: "+name); log.trace("Protected: "+name);
return super.loadClass(name, resolve); return super.loadClass(name, resolve);
@ -164,7 +144,17 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
return defined; 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"; String path = name.replace(".", "/") + ".class";
byte[] bytes = getResourceAsStream(path).readAllBytes(); byte[] bytes = getResourceAsStream(path).readAllBytes();
if(transform && !isProtected(name)) { if(transform && !isProtected(name)) {
@ -195,15 +185,12 @@ public class MinestomOverwriteClassLoader extends URLClassLoader {
return bytes; return bytes;
} }
// overriden to increase access (from protected to public)
@Override @Override
public Class<?> findClass(String name) throws ClassNotFoundException { public Class<?> findClass(String name) throws ClassNotFoundException {
return super.findClass(name); return super.findClass(name);
} }
public void resolve(Class<?> clazz) {
resolveClass(clazz);
}
@NotNull @NotNull
public URLClassLoader newChild(@NotNull URL[] urls) { public URLClassLoader newChild(@NotNull URL[] urls) {
return URLClassLoader.newInstance(urls, this); return URLClassLoader.newInstance(urls, this);

View File

@ -7,6 +7,9 @@ import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
/**
* Global properties service for Mixin
*/
public class GlobalPropertyServiceMinestom implements IGlobalPropertyService { public class GlobalPropertyServiceMinestom implements IGlobalPropertyService {
private class BasicProperty implements IPropertyKey { private class BasicProperty implements IPropertyKey {

View File

@ -7,6 +7,9 @@ import org.spongepowered.asm.service.IClassBytecodeProvider;
import java.io.IOException; import java.io.IOException;
/**
* Provides class bytecode for Mixin
*/
public class MinestomBytecodeProvider implements IClassBytecodeProvider { public class MinestomBytecodeProvider implements IClassBytecodeProvider {
private final MinestomOverwriteClassLoader classLoader; private final MinestomOverwriteClassLoader classLoader;

View File

@ -5,6 +5,9 @@ import org.spongepowered.asm.service.IClassProvider;
import java.net.URL; import java.net.URL;
/**
* Provides classes for Mixin
*/
public class MinestomClassProvider implements IClassProvider { public class MinestomClassProvider implements IClassProvider {
private final MinestomOverwriteClassLoader classLoader; private final MinestomOverwriteClassLoader classLoader;

View File

@ -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<ITransformer> transformers;
public MinestomTransformerProvider(MinestomOverwriteClassLoader classLoader) {
this.classLoader = classLoader;
}
@Override
public void addTransformerExclusion(String name) {
classLoader.protectedClasses.add(name);
}
@Override
public Collection<ITransformer> getTransformers() {
return getDelegatedTransformers();
}
@Override
public Collection<ITransformer> getDelegatedTransformers() {
if(transformers == null) {
transformers = buildTransformerList();
}
return transformers;
}
private List<ITransformer> buildTransformerList() {
List<ITransformer> 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;
}
};
}
}

View File

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

View File

@ -3,50 +3,44 @@ package net.minestom.server.extras.selfmodification.mixins;
import net.minestom.server.extras.selfmodification.CodeModifier; import net.minestom.server.extras.selfmodification.CodeModifier;
import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.MixinEnvironment; 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 org.spongepowered.asm.transformers.TreeTransformer;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException; import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method; import java.lang.reflect.Method;
/**
* CodeModifier responsible for applying Mixins during class load
*/
public class MixinCodeModifier extends CodeModifier { 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() { public MixinCodeModifier() {
try { try {
// MixinTransformer is package-protected, so we have to force to gain access
Class<?> mixinTransformerClass = Class.forName("org.spongepowered.asm.mixin.transformer.MixinTransformer"); Class<?> mixinTransformerClass = Class.forName("org.spongepowered.asm.mixin.transformer.MixinTransformer");
Constructor<?> ctor = mixinTransformerClass.getDeclaredConstructor(); Constructor<?> ctor = mixinTransformerClass.getDeclaredConstructor();
ctor.setAccessible(true); 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 = mixinTransformerClass.getDeclaredMethod("transformClass", MixinEnvironment.class, String.class, ClassNode.class);
transformClassMethod.setAccessible(true); transformClassMethod.setAccessible(true);
} catch (NoSuchMethodException e) { } catch (NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException | ClassNotFoundException e) {
e.printStackTrace(); throw new RuntimeException("Failed to initialize MixinCodeModifier", e);
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (InstantiationException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} }
} }
@Override @Override
public boolean transform(ClassNode source) { public boolean transform(ClassNode source) {
try { try {
return (boolean) transformClassMethod.invoke(processor, MixinEnvironment.getEnvironment(MixinEnvironment.Phase.DEFAULT), source.name.replace("/", "."), source); return (boolean) transformClassMethod.invoke(transformer, MixinEnvironment.getEnvironment(MixinEnvironment.Phase.DEFAULT), source.name.replace("/", "."), source);
} catch (IllegalAccessException e) { } catch (IllegalAccessException | InvocationTargetException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace(); e.printStackTrace();
} }
return false; return false;

View File

@ -16,13 +16,13 @@ public class MixinServiceMinestom extends MixinServiceAbstract {
private final MinestomOverwriteClassLoader classLoader; private final MinestomOverwriteClassLoader classLoader;
private final MinestomClassProvider classProvider; private final MinestomClassProvider classProvider;
private final MinestomBytecodeProvider bytecodeProvider; private final MinestomBytecodeProvider bytecodeProvider;
private final MinestomTransformerProvider transformerProvider; private final MixinAuditTrailMinestom auditTrail;
public MixinServiceMinestom() { public MixinServiceMinestom() {
this.classLoader = MinestomOverwriteClassLoader.getInstance(); this.classLoader = MinestomOverwriteClassLoader.getInstance();
classProvider = new MinestomClassProvider(classLoader); classProvider = new MinestomClassProvider(classLoader);
bytecodeProvider = new MinestomBytecodeProvider(classLoader); bytecodeProvider = new MinestomBytecodeProvider(classLoader);
transformerProvider = new MinestomTransformerProvider(classLoader); auditTrail = new MixinAuditTrailMinestom();
} }
@Override @Override
@ -47,7 +47,7 @@ public class MixinServiceMinestom extends MixinServiceAbstract {
@Override @Override
public ITransformerProvider getTransformerProvider() { public ITransformerProvider getTransformerProvider() {
return transformerProvider; return null;
} }
@Override @Override
@ -65,8 +65,6 @@ public class MixinServiceMinestom extends MixinServiceAbstract {
return classLoader.getResourceAsStream(name); return classLoader.getResourceAsStream(name);
} }
// TODO: everything below
@Override @Override
public IClassTracker getClassTracker() { public IClassTracker getClassTracker() {
return null; return null;
@ -74,12 +72,13 @@ public class MixinServiceMinestom extends MixinServiceAbstract {
@Override @Override
public IMixinAuditTrail getAuditTrail() { public IMixinAuditTrail getAuditTrail() {
return null; return auditTrail;
} }
@Override @Override
public void wire(MixinEnvironment.Phase phase, IConsumer<MixinEnvironment.Phase> phaseConsumer) { public void wire(MixinEnvironment.Phase phase, IConsumer<MixinEnvironment.Phase> phaseConsumer) {
super.wire(phase, phaseConsumer); super.wire(phase, phaseConsumer);
// TODO: hook into Minestom initialization process
phaseConsumer.accept(MixinEnvironment.Phase.PREINIT); phaseConsumer.accept(MixinEnvironment.Phase.PREINIT);
phaseConsumer.accept(MixinEnvironment.Phase.INIT); phaseConsumer.accept(MixinEnvironment.Phase.INIT);
} }

View File

@ -6,11 +6,9 @@ import org.spongepowered.asm.mixin.Mixins;
// To launch with VM arguments: // To launch with VM arguments:
// -Dminestom.extension.indevfolder.classes=build/classes/java/test/ -Dminestom.extension.indevfolder.resources=build/resources/test/ // -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) { public static void main(String[] args) {
MixinBootstrap.init();
Mixins.addConfiguration("mixins.testextension.json");
Bootstrap.bootstrap("fr.themode.demo.MainDemo", args); Bootstrap.bootstrap("fr.themode.demo.MainDemo", args);
} }

View File

@ -14,5 +14,4 @@ public class InstanceContainerMixin {
System.out.println("Hello from Mixin!!!"); System.out.println("Hello from Mixin!!!");
} }
} }

View File

@ -3,5 +3,6 @@
"name": "Test extension", "name": "Test extension",
"codeModifiers": [ "codeModifiers": [
"testextension.TestModifier" "testextension.TestModifier"
] ],
"mixinConfig": "mixins.testextension.json"
} }