diff --git a/src/main/java/net/minestom/server/Bootstrap.java b/src/main/java/net/minestom/server/Bootstrap.java index 5b1174acd..9e96b65df 100644 --- a/src/main/java/net/minestom/server/Bootstrap.java +++ b/src/main/java/net/minestom/server/Bootstrap.java @@ -1,6 +1,6 @@ package net.minestom.server; -import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; +import net.minestom.server.extras.selfmodification.MinestomRootClassLoader; import net.minestom.server.extras.selfmodification.mixins.MixinCodeModifier; import net.minestom.server.extras.selfmodification.mixins.MixinServiceMinestom; import org.spongepowered.asm.launch.MixinBootstrap; @@ -12,15 +12,15 @@ import java.lang.reflect.Method; import java.util.Arrays; /** - * Used to launch Minestom with the {@link MinestomOverwriteClassLoader} to allow for self-modifications + * Used to launch Minestom with the {@link MinestomRootClassLoader} to allow for self-modifications */ public final class Bootstrap { public static void bootstrap(String mainClassFullName, String[] args) { try { - ClassLoader classLoader = MinestomOverwriteClassLoader.getInstance(); + ClassLoader classLoader = MinestomRootClassLoader.getInstance(); startMixin(args); - MinestomOverwriteClassLoader.getInstance().addCodeModifier(new MixinCodeModifier()); + MinestomRootClassLoader.getInstance().addCodeModifier(new MixinCodeModifier()); MixinServiceMinestom.gotoPreinitPhase(); // ensure extensions are loaded when starting the server @@ -53,6 +53,6 @@ public final class Bootstrap { doInit.invoke(null, CommandLineOptions.ofArgs(Arrays.asList(args))); MixinBootstrap.getPlatform().inject(); - Mixins.getConfigs().forEach(c -> MinestomOverwriteClassLoader.getInstance().protectedPackages.add(c.getConfig().getMixinPackage())); + Mixins.getConfigs().forEach(c -> MinestomRootClassLoader.getInstance().protectedPackages.add(c.getConfig().getMixinPackage())); } } diff --git a/src/main/java/net/minestom/server/extensions/ExtensionManager.java b/src/main/java/net/minestom/server/extensions/ExtensionManager.java index 1f7bf28a0..a52e40a67 100644 --- a/src/main/java/net/minestom/server/extensions/ExtensionManager.java +++ b/src/main/java/net/minestom/server/extensions/ExtensionManager.java @@ -4,7 +4,8 @@ import com.google.gson.Gson; import lombok.extern.slf4j.Slf4j; import net.minestom.dependencies.DependencyGetter; import net.minestom.dependencies.maven.MavenRepository; -import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; +import net.minestom.server.extras.selfmodification.MinestomExtensionClassLoader; +import net.minestom.server.extras.selfmodification.MinestomRootClassLoader; import net.minestom.server.utils.validate.Check; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; @@ -76,7 +77,7 @@ public class ExtensionManager { // TODO: If we want modifications to be possible, we need to add these urls to the current classloader // TODO: Indeed, without adding the urls, the classloader is not able to load the bytecode of extension classes // TODO: Whether we want to allow extensions to modify one-another is our choice now. - loader = newClassLoader(urls); + loader = newClassLoader(discoveredExtension, urls); // Create ExtensionDescription (authors, version etc.) String extensionName = discoveredExtension.getName(); @@ -340,8 +341,8 @@ public class ExtensionManager { if (!(cl instanceof URLClassLoader)) { throw new IllegalStateException("Current class loader is not a URLClassLoader, but " + cl + ". This prevents adding URLs into the classpath at runtime."); } - if(cl instanceof MinestomOverwriteClassLoader) { - ((MinestomOverwriteClassLoader) cl).addURL(dependency); // no reflection warnings for us! + if(cl instanceof MinestomRootClassLoader) { + ((MinestomRootClassLoader) cl).addURL(dependency); // no reflection warnings for us! } else { try { Method addURL = URLClassLoader.class.getDeclaredMethod("addURL", URL.class); @@ -364,8 +365,12 @@ public class ExtensionManager { * @param urls {@link URL} (usually a JAR) that should be loaded. */ @NotNull - public URLClassLoader newClassLoader(@NotNull URL[] urls) { - return URLClassLoader.newInstance(urls, ExtensionManager.class.getClassLoader()); + public URLClassLoader newClassLoader(@NotNull DiscoveredExtension extension, @NotNull URL[] urls) { + MinestomRootClassLoader root = MinestomRootClassLoader.getInstance(); + MinestomExtensionClassLoader loader = new MinestomExtensionClassLoader(extension.getName(), urls, root); + // TODO: tree structure + root.addChild(loader); + return loader; } @NotNull @@ -393,11 +398,11 @@ public class ExtensionManager { */ private void setupCodeModifiers(@NotNull List extensions) { final ClassLoader cl = getClass().getClassLoader(); - if (!(cl instanceof MinestomOverwriteClassLoader)) { + if (!(cl instanceof MinestomRootClassLoader)) { log.warn("Current class loader is not a MinestomOverwriteClassLoader, but " + cl + ". This disables code modifiers (Mixin support is therefore disabled)"); return; } - MinestomOverwriteClassLoader modifiableClassLoader = (MinestomOverwriteClassLoader) cl; + MinestomRootClassLoader modifiableClassLoader = (MinestomRootClassLoader) cl; log.info("Start loading code modifiers..."); for (DiscoveredExtension extension : extensions) { try { diff --git a/src/main/java/net/minestom/server/extras/selfmodification/CodeModifier.java b/src/main/java/net/minestom/server/extras/selfmodification/CodeModifier.java index faa739c0a..b02ebd4e4 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/CodeModifier.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/CodeModifier.java @@ -3,7 +3,7 @@ package net.minestom.server.extras.selfmodification; import org.objectweb.asm.tree.ClassNode; /** - * Will be called by {@link MinestomOverwriteClassLoader} to transform classes at load-time + * Will be called by {@link MinestomRootClassLoader} to transform classes at load-time */ public abstract class CodeModifier { /** diff --git a/src/main/java/net/minestom/server/extras/selfmodification/MinestomExtensionClassLoader.java b/src/main/java/net/minestom/server/extras/selfmodification/MinestomExtensionClassLoader.java new file mode 100644 index 000000000..5bd6bd804 --- /dev/null +++ b/src/main/java/net/minestom/server/extras/selfmodification/MinestomExtensionClassLoader.java @@ -0,0 +1,70 @@ +package net.minestom.server.extras.selfmodification; + +import java.io.IOException; +import java.io.InputStream; +import java.net.URL; +import java.net.URLClassLoader; +import java.util.LinkedList; +import java.util.List; + +public class MinestomExtensionClassLoader extends URLClassLoader { + /** + * Root ClassLoader, everything goes through it before any attempt at loading is done inside this classloader + */ + private final MinestomRootClassLoader root; + private final List children = new LinkedList<>(); + + public MinestomExtensionClassLoader(String name, URL[] urls, MinestomRootClassLoader root) { + super(name, urls, root); + this.root = root; + } + + @Override + public Class loadClass(String name) throws ClassNotFoundException { + return root.loadClass(name); + } + + @Override + protected Class loadClass(String name, boolean resolve) throws ClassNotFoundException { + return root.loadClass(name, resolve); + } + + /** + * Assumes the name is not null, nor it does represent a protected class + * @param name + * @return + * @throws ClassNotFoundException if the class is not found inside this classloader + */ + public Class loadClassAsChild(String name, boolean resolve) throws ClassNotFoundException { + for(MinestomExtensionClassLoader child : children) { + try { + Class loaded = child.loadClassAsChild(name, resolve); + return loaded; + } catch (ClassNotFoundException e) { + // move on to next child + } + } + + Class loadedClass = findLoadedClass(name); + if(loadedClass != null) { + return loadedClass; + } + // not in children, attempt load in this classloader + String path = name.replace(".", "/") + ".class"; + InputStream in = getResourceAsStream(path); + if(in == null) { + throw new ClassNotFoundException("Could not load class "+name); + } + try(in) { + byte[] bytes = in.readAllBytes(); + bytes = root.transformBytes(bytes, name); + Class clazz = defineClass(name, bytes, 0, bytes.length); + if(resolve) { + resolveClass(clazz); + } + return clazz; + } catch (IOException e) { + throw new ClassNotFoundException("Could not load class "+name, e); + } + } +} diff --git a/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java b/src/main/java/net/minestom/server/extras/selfmodification/MinestomRootClassLoader.java similarity index 78% rename from src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java rename to src/main/java/net/minestom/server/extras/selfmodification/MinestomRootClassLoader.java index 0ce77b25e..f5cf3e390 100644 --- a/src/main/java/net/minestom/server/extras/selfmodification/MinestomOverwriteClassLoader.java +++ b/src/main/java/net/minestom/server/extras/selfmodification/MinestomRootClassLoader.java @@ -22,9 +22,9 @@ import java.util.Set; * Class Loader that can modify class bytecode when they are loaded */ @Slf4j -public class MinestomOverwriteClassLoader extends URLClassLoader { +public class MinestomRootClassLoader extends URLClassLoader { - private static MinestomOverwriteClassLoader INSTANCE; + private static MinestomRootClassLoader INSTANCE; /** * Classes that cannot be loaded/modified by this classloader. @@ -60,17 +60,18 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { // TODO: priorities? private final List modifiers = new LinkedList<>(); + private final List children = new LinkedList<>(); - private MinestomOverwriteClassLoader(ClassLoader parent) { - super("Minestom ClassLoader", extractURLsFromClasspath(), parent); + private MinestomRootClassLoader(ClassLoader parent) { + super("Minestom Root ClassLoader", extractURLsFromClasspath(), parent); asmClassLoader = newChild(new URL[0]); } - public static MinestomOverwriteClassLoader getInstance() { + public static MinestomRootClassLoader getInstance() { if (INSTANCE == null) { - synchronized (MinestomOverwriteClassLoader.class) { + synchronized (MinestomRootClassLoader.class) { if (INSTANCE == null) { - INSTANCE = new MinestomOverwriteClassLoader(MinestomOverwriteClassLoader.class.getClassLoader()); + INSTANCE = new MinestomRootClassLoader(MinestomRootClassLoader.class.getClassLoader()); } } } @@ -116,7 +117,7 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { return super.loadClass(name, resolve); } - return define(name, loadBytes(name, true), resolve); + return define(name, resolve); } catch (Exception ex) { log.trace("Fail to load class, resorting to parent loader: " + name, ex); // fail to load class, let parent load @@ -137,13 +138,29 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { return true; } - private Class define(String name, byte[] bytes, boolean resolve) { - Class defined = defineClass(name, bytes, 0, bytes.length); - log.trace("Loaded with code modifiers: " + name); - if (resolve) { - resolveClass(defined); + private Class define(String name, boolean resolve) throws IOException, ClassNotFoundException { + try { + byte[] bytes = loadBytes(name, true); + Class defined = defineClass(name, bytes, 0, bytes.length); + log.trace("Loaded with code modifiers: " + name); + if (resolve) { + resolveClass(defined); + } + return defined; + } catch (ClassNotFoundException e) { + // could not load inside this classloader, attempt with children + Class defined = null; + for(MinestomExtensionClassLoader subloader : children) { + try { + defined = subloader.loadClassAsChild(name, resolve); + log.trace("Loaded from child {}: {}", subloader, name); + return defined; + } catch (ClassNotFoundException e1) { + // not found inside this child, move on to next + } + } + throw e; } - return defined; } /** @@ -163,9 +180,16 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { if(input == null) { throw new ClassNotFoundException("Could not find resource "+path); } - byte[] bytes = input.readAllBytes(); - if (transform && !isProtected(name)) { - ClassReader reader = new ClassReader(bytes); + byte[] originalBytes = input.readAllBytes(); + if(transform) { + return transformBytes(originalBytes, name); + } + return originalBytes; + } + + byte[] transformBytes(byte[] classBytecode, String name) { + if (!isProtected(name)) { + ClassReader reader = new ClassReader(classBytecode); ClassNode node = new ClassNode(); reader.accept(node, 0); boolean modified = false; @@ -185,11 +209,11 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { } }; node.accept(writer); - bytes = writer.toByteArray(); + classBytecode = writer.toByteArray(); log.trace("Modified " + name); } } - return bytes; + return classBytecode; } // overriden to increase access (from protected to public) @@ -237,4 +261,8 @@ public class MinestomOverwriteClassLoader extends URLClassLoader { public List getModifiers() { return modifiers; } + + public void addChild(MinestomExtensionClassLoader loader) { + children.add(loader); + } } 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 cca2f37d0..87dd2feab 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 @@ -1,6 +1,6 @@ package net.minestom.server.extras.selfmodification.mixins; -import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; +import net.minestom.server.extras.selfmodification.MinestomRootClassLoader; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.service.IClassBytecodeProvider; @@ -11,9 +11,9 @@ import java.io.IOException; * Provides class bytecode for Mixin */ public class MinestomBytecodeProvider implements IClassBytecodeProvider { - private final MinestomOverwriteClassLoader classLoader; + private final MinestomRootClassLoader classLoader; - public MinestomBytecodeProvider(MinestomOverwriteClassLoader classLoader) { + public MinestomBytecodeProvider(MinestomRootClassLoader classLoader) { this.classLoader = 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 ae3b98b4f..b661dee4b 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 @@ -1,6 +1,6 @@ package net.minestom.server.extras.selfmodification.mixins; -import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; +import net.minestom.server.extras.selfmodification.MinestomRootClassLoader; import org.spongepowered.asm.service.IClassProvider; import java.net.URL; @@ -9,9 +9,9 @@ import java.net.URL; * Provides classes for Mixin */ public class MinestomClassProvider implements IClassProvider { - private final MinestomOverwriteClassLoader classLoader; + private final MinestomRootClassLoader classLoader; - public MinestomClassProvider(MinestomOverwriteClassLoader classLoader) { + public MinestomClassProvider(MinestomRootClassLoader classLoader) { this.classLoader = classLoader; } 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 ef4c4c760..b68ba7627 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 @@ -1,6 +1,6 @@ package net.minestom.server.extras.selfmodification.mixins; -import net.minestom.server.extras.selfmodification.MinestomOverwriteClassLoader; +import net.minestom.server.extras.selfmodification.MinestomRootClassLoader; import org.spongepowered.asm.launch.platform.container.ContainerHandleVirtual; import org.spongepowered.asm.launch.platform.container.IContainerHandle; import org.spongepowered.asm.mixin.MixinEnvironment; @@ -13,7 +13,7 @@ import java.util.Collections; public class MixinServiceMinestom extends MixinServiceAbstract { - private final MinestomOverwriteClassLoader classLoader; + private final MinestomRootClassLoader classLoader; private final MinestomClassProvider classProvider; private final MinestomBytecodeProvider bytecodeProvider; private final MixinAuditTrailMinestom auditTrail; @@ -22,7 +22,7 @@ public class MixinServiceMinestom extends MixinServiceAbstract { public MixinServiceMinestom() { INSTANCE = this; - this.classLoader = MinestomOverwriteClassLoader.getInstance(); + this.classLoader = MinestomRootClassLoader.getInstance(); classProvider = new MinestomClassProvider(classLoader); bytecodeProvider = new MinestomBytecodeProvider(classLoader); auditTrail = new MixinAuditTrailMinestom();