Skip to content

Commit

Permalink
Add KnotFinder for Quilt isolating classloaders.
Browse files Browse the repository at this point in the history
  • Loading branch information
Ampflower committed Jan 23, 2023
1 parent cdde023 commit fdb7404
Show file tree
Hide file tree
Showing 4 changed files with 301 additions and 40 deletions.
264 changes: 264 additions & 0 deletions src/main/java/gay/ampflower/junkie/internal/KnotFinder.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,264 @@
package gay.ampflower.junkie.internal;// Created 2023-23-01T00:02:19

import net.gudenau.minecraft.asm.impl.ReflectionHelper;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;

import java.lang.reflect.Field;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.*;

/**
* @author Ampflower
* @since 0.3.2
**/
public class KnotFinder {
private static final Logger logger = LogManager.getLogger();

private static Set<ClassLoader> loadersSeen = new HashSet<>();
private static Queue<ClassLoader> loaders = new ArrayDeque<>();

private static ClassLoader knot;
private static Object delegate;
private static Field transformer;

private static final MethodSignature transformClassBytes = new MethodSignature("transformClassBytes", byte[].class, String.class, String.class, byte[].class);

public static ClassLoader getKnot() {
if (knot != null) {
return knot;
}

ClassLoader loader = KnotFinder.class.getClassLoader();

loadersSeen.add(loader);
loaders.add(loader);

while ((loader = loaders.poll()) != null) {
loader = seek(loader);
if (loader != null) {
// Clear these to avoid memleaking, as we never need these again.
loadersSeen = null;
loaders = null;

// Return Knot.
return loader;
}
}

throw new AssertionError("Knot was not found in loader tree.");
}

private static ClassLoader seek(ClassLoader loader) {
for (ClassLoader system = ClassLoader.getSystemClassLoader(); loader != null && loader != system; loader = loader.getParent())
try {
loadersSeen.add(loader);
logger.info("Traversing {}", loader);

final Class<? extends ClassLoader> clazz = loader.getClass();

Field field = tryDelegateImpl(clazz);

if (field == null) {
field = tryDelegate(loader, clazz);
}

if (field != null) {
transformer = field;
return knot = loader;
}

findClassLoaders(loader);
} catch (Throwable throwable) {
throw new AssertionError("This should never throw", throwable);
}

return null;
}

public static Object getTransformer() {
if (knot == null) {
getKnot();
}

try {
return get(delegate, transformer);
} catch (Throwable throwable) {
throw new AssertionError("This should never throw", throwable);
}
}

public static void setTransformer(Object value) {
if (knot == null) {
getKnot();
}

try {
set(delegate, transformer, value);
} catch (Throwable throwable) {
throw new AssertionError("This should never throw", throwable);
}
}

private static void findClassLoaders(final ClassLoader loader) {
for (final Field field : loader.getClass().getDeclaredFields()) {
if (ClassLoader.class.isAssignableFrom(field.getType())) {
ClassLoader sub = get(loader, field);
if (loadersSeen.add(sub)) {
loaders.add(sub);
}
}
}
}

private static Field tryDelegate(final Object instance, final Class<?> loader) throws Throwable {
final Set<Class<?>> witness = new HashSet<>();
{
final Field field = ReflectionHelper.findField(loader, "delegate");

if (field != null) {
witness.add(field.getType());

final Object delegateInst = get(instance, field);

if (delegateInst != null) {
witness.add(delegateInst.getClass());
Field transformer = tryDelegateImpl(field.getType());

if (transformer == null) {
transformer = tryDelegateImpl(delegateInst.getClass());
}

if (transformer != null) {
delegate = delegateInst;
return transformer;
}
}
}
}

for (final Field declaredField : loader.getDeclaredFields()) {
final Class<?> type = declaredField.getType();

if (!mayScan(loader, type)) {
continue;
}


final Object delegateInst = get(instance, declaredField);

if (delegateInst != null) {

Field transformer = null;

if (witness.add(declaredField.getType())) {
transformer = tryDelegateImpl(declaredField.getType());
}

if (witness.add(delegateInst.getClass())) {
transformer = tryDelegateImpl(delegateInst.getClass());
}

if (transformer != null) {
delegate = delegateInst;
return transformer;
}
}
}

return null;
}

private static Field tryDelegateImpl(final Class<?> clazz) {
Field field = ReflectionHelper.findField(clazz, "mixinTransformer");

if (field == null) {
field = findFieldOfType(clazz, IMixinTransformer.class);
}

if (field == null) {
field = findFieldByMethod(clazz, transformClassBytes);
}

return field;
}

private static Field findFieldOfType(final Class<?> delegate, final Class<?> type) {
for (final Field field : delegate.getDeclaredFields()) {
if (type.isAssignableFrom(field.getType())) {
return field;
}
}

return null;
}

private static Field findFieldByMethod(final Class<?> delegate, final MethodSignature signature) {
Set<Class<?>> witness = new HashSet<>();
for (final Field field : delegate.getDeclaredFields()) {
final Class<?> type = field.getType();
if (witness.add(type) && methodsMatchesSignature(type, signature)) {
return field;
}
}

return null;
}

private static boolean methodsMatchesSignature(final Class<?> transformer, final MethodSignature signature) {
for (final Method method : transformer.getDeclaredMethods()) {
if (signature.matches(method)) {
return true;
}
}

return false;
}

// Avoid recursively looking at foreign-to-Knot classes.
private static boolean mayScan(final Class<?> reference, final Class<?> potentialDelegate) {
return reference.getClassLoader() == potentialDelegate.getClassLoader();
}

private static <T> T get(Object instance, Field field) {
try {
if (Modifier.isStatic(field.getModifiers())) {
return (T) ReflectionHelper.getGetter(field).invoke();
}
return (T) ReflectionHelper.getGetter(instance, field).invoke();
} catch (Throwable throwable) {
throw new AssertionError("This should never fail", throwable);
}
}

private static void set(Object instance, Field field, Object value) {
try {
if (Modifier.isStatic(field.getModifiers())) {
ReflectionHelper.getSetter(field).invoke(value);
}
ReflectionHelper.getSetter(instance, field).invoke(value);
} catch (Throwable throwable) {
throw new AssertionError("This should never fail", throwable);
}
}

private static class MethodSignature {
private String name;
private Class<?> rtype;
private Class<?>[] param;

private MethodSignature(String name, Class<?> rtype, Class<?>... param) {
this.name = name;
this.rtype = rtype;
this.param = param;
}

private boolean matches(Method method) {
return method.getName().equals(name)
&& method.getReturnType().equals(rtype)
&& Arrays.equals(method.getParameterTypes(), param);
}
}
}
52 changes: 20 additions & 32 deletions src/main/java/net/gudenau/minecraft/asm/impl/Bootstrap.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import net.gudenau.minecraft.asm.api.v1.ClassCache;
import net.gudenau.minecraft.asm.api.v1.type.MethodType;
import net.gudenau.minecraft.asm.util.FileUtils;
import gay.ampflower.junkie.internal.KnotFinder;
import gay.ampflower.junkie.internal.UnsafeProxy;
import gay.ampflower.junkie.internal.definer.ClassDefiner;
import org.apache.logging.log4j.LogManager;
Expand All @@ -18,13 +19,12 @@
import org.objectweb.asm.tree.MethodNode;
import org.spongepowered.asm.mixin.MixinEnvironment;
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
import org.spongepowered.asm.transformers.MixinClassWriter;

import java.io.IOException;
import java.io.InputStream;
import java.lang.invoke.MethodHandle;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.Objects;

// Bootstraps all the mess we make.
public class Bootstrap {
Expand Down Expand Up @@ -79,10 +79,8 @@ public static void setup() {
}

// Hack into knot.
ClassLoader classLoader = Bootstrap.class.getClassLoader();

try {
unsafe$generic(classLoader, cache);
unsafe$generic(KnotFinder.getKnot(), cache);
} catch (Throwable t) {
new RuntimeException("Failed to hook into Knot", t).printStackTrace();
System.exit(0);
Expand All @@ -92,31 +90,8 @@ public static void setup() {
}

private static void unsafe$generic(ClassLoader classLoader, ClassCache cache) throws Throwable {
Class<? extends ClassLoader> KnotClassLoader = classLoader.getClass();

Class<?> KnotClassDelegate;
Class<?> transformerClass;
Object KnotClassLoader$delegate;

{
Field tmp = ReflectionHelper.findField(KnotClassLoader, "mixinTransformer");
if (tmp != null) {
KnotClassDelegate = KnotClassLoader;
transformerClass = tmp.getType();
KnotClassLoader$delegate = classLoader;
} else {
KnotClassDelegate = Objects.requireNonNull(ReflectionHelper.findField(KnotClassLoader, "delegate"), "NoSuchField: KnotClassLoader/delegate").getType();
transformerClass = Objects.requireNonNull(ReflectionHelper.findField(KnotClassDelegate, "mixinTransformer"), "NoSuchField: KnotClassDelegate/mixinTransformer").getType();

// Get the class delegate
MethodHandle KnotClassLoader$delegate$getter = ReflectionHelper.findGetter(KnotClassLoader, classLoader, "delegate", KnotClassDelegate);
KnotClassLoader$delegate = KnotClassLoader$delegate$getter.invoke();
}
}

// Get the transformer proxy as Object
MethodHandle KnotClassDelegate$mixinTransformer$getter = ReflectionHelper.findGetter(KnotClassDelegate, KnotClassLoader$delegate, "mixinTransformer", transformerClass);
Object KnotClassDelegate$mixinTransformer = KnotClassDelegate$mixinTransformer$getter.invoke();
Object KnotClassDelegate$mixinTransformer = KnotFinder.getTransformer();

// Get the environment's transformer.
MethodHandle MixinEnvironment$transformer$getter = ReflectionHelper.findStaticGetter(MixinEnvironment.class, "transformer", IMixinTransformer.class);
Expand All @@ -141,8 +116,7 @@ public static void setup() {
MixinEnvironment$transformer$setter.invokeExact(originalTransformer);

// Set our custom transformer so it will be used in future class loads
MethodHandle KnotClassDelegate$mixinTransformer$setter = ReflectionHelper.findSetter(KnotClassDelegate, KnotClassLoader$delegate, "mixinTransformer", transformerClass);
KnotClassDelegate$mixinTransformer$setter.invoke(transformer);
KnotFinder.setTransformer(transformer);
}

/**
Expand Down Expand Up @@ -174,8 +148,13 @@ private static IMixinTransformer loadUnsafeProxy(ClassLoader inject, IMixinTrans

// Load the entire required structure into the final transformer.
unsafeReader.accept(unsafeTransformer, ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES);
unsafeTransformer.access |= Opcodes.ACC_PUBLIC;

for (MethodNode method : unsafeTransformer.methods) {
if ("<init>".equals(method.name)) {
method.access |= Opcodes.ACC_PUBLIC;
}

methods.add(new Method(method.name, method.desc));
}

Expand Down Expand Up @@ -256,7 +235,16 @@ private static void mkProxyMethod(ClassNode classVisitor, int access, String pro
methodVisitor.visitEnd();
}

static Class<?> forceDefineClass(ClassLoader loader, byte[] bytes, boolean unmangledName) throws Throwable {
public static ClassWriter createClassWriter(int flags, ClassLoader loader) {
return new MixinClassWriter(flags) {
@Override
protected ClassLoader getClassLoader() {
return loader;
}
};
}

public static Class<?> forceDefineClass(ClassLoader loader, byte[] bytes, boolean unmangledName) throws Throwable {
ClassDefiner definer = ClassDefiner.getInstance();

// Pre-conditions.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import org.objectweb.asm.tree.ClassNode;
import org.spongepowered.asm.mixin.transformer.FabricMixinTransformerProxy;
import org.spongepowered.asm.mixin.transformer.IMixinTransformer;
import org.spongepowered.asm.transformers.MixinClassWriter;

import java.io.IOException;
import java.io.OutputStream;
Expand Down Expand Up @@ -164,13 +163,7 @@ private byte[] transform(String name, String transformedName, byte[] bytecode, L
return bytecode;
}

ClassWriter writer = new MixinClassWriter(flags.getClassWriterFlags()) {
// Fixes an issue with stack calculations
@Override
protected ClassLoader getClassLoader() {
return classLoader;
}
};
ClassWriter writer = Bootstrap.createClassWriter(flags.getClassWriterFlags(), classLoader);
classNode.accept(writer);
parentModifier.set(true);
return writer.toByteArray();
Expand Down
Loading

0 comments on commit fdb7404

Please sign in to comment.