Start of tree-based classloading

This commit is contained in:
jglrxavpok 2020-10-28 16:24:29 +01:00
parent 35f7b27135
commit 47eb0084eb
8 changed files with 145 additions and 42 deletions

View File

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

View File

@ -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<DiscoveredExtension> 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 {

View File

@ -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 {
/**

View File

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

View File

@ -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<CodeModifier> modifiers = new LinkedList<>();
private final List<MinestomExtensionClassLoader> 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<CodeModifier> getModifiers() {
return modifiers;
}
public void addChild(MinestomExtensionClassLoader loader) {
children.add(loader);
}
}

View File

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

View File

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

View File

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