-
Notifications
You must be signed in to change notification settings - Fork 17
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Change: Don't generate classes via Mixin.
This breaks in dev on certain Forge versions, so we have to go via the stdlib instead. Thankfully we don't need to reference any non-JDK classes in our generated ones.
- Loading branch information
Showing
8 changed files
with
92 additions
and
123 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
91 changes: 31 additions & 60 deletions
91
src/main/java/com/llamalad7/mixinextras/sugar/impl/ref/LocalRefClassGenerator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,104 +1,75 @@ | ||
package com.llamalad7.mixinextras.sugar.impl.ref; | ||
|
||
import com.llamalad7.mixinextras.sugar.ref.*; | ||
import com.llamalad7.mixinextras.sugar.ref.LocalRef; | ||
import com.llamalad7.mixinextras.utils.ClassGenUtils; | ||
import com.llamalad7.mixinextras.utils.PackageUtils; | ||
import org.apache.commons.lang3.StringUtils; | ||
import org.objectweb.asm.MethodVisitor; | ||
import org.objectweb.asm.Opcodes; | ||
import org.objectweb.asm.Type; | ||
import org.objectweb.asm.tree.ClassNode; | ||
import org.spongepowered.asm.mixin.extensibility.IMixinInfo; | ||
import org.spongepowered.asm.mixin.transformer.ext.IClassGenerator; | ||
import org.spongepowered.asm.service.ISyntheticClassInfo; | ||
import org.spongepowered.asm.util.IConsumer; | ||
|
||
import java.lang.invoke.MethodHandles; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
/** | ||
* We must generate implementations of {@link LocalRef} and co. at runtime so they implement all the required interfaces. | ||
* These objects will be shared between handlers that possibly use different relocations of MixinExtras. | ||
*/ | ||
public class LocalRefClassGenerator implements IClassGenerator { | ||
private static LocalRefClassGenerator INSTANCE; | ||
public class LocalRefClassGenerator { | ||
private static final String IMPL_PACKAGE = StringUtils.substringBeforeLast(LocalRefClassGenerator.class.getName(), ".").replace('.', '/'); | ||
private static final Map<Class<?>, String> interfaceToImpl = new HashMap<>(); | ||
|
||
private final IConsumer<ISyntheticClassInfo> registry; | ||
private final Map<Class<?>, LocalRefClassInfo> interfaceToInfo = new HashMap<>(); | ||
private final Map<String, LocalRefClassInfo> nameToInfo = new HashMap<>(); | ||
|
||
public LocalRefClassGenerator(IConsumer<ISyntheticClassInfo> registry) { | ||
this.registry = registry; | ||
INSTANCE = this; | ||
} | ||
|
||
@Override | ||
public String getName() { | ||
return "MixinExtras LocalRefImpl"; | ||
} | ||
|
||
@Override | ||
public boolean generate(String name, ClassNode classNode) { | ||
LocalRefClassInfo info = nameToInfo.get(name); | ||
if (info == null) { | ||
return false; | ||
} | ||
classNode.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, info.getName(), null, | ||
Type.getInternalName(Object.class), null); | ||
this.generateClass(classNode, info); | ||
classNode.visitEnd(); | ||
info.markAsLoaded(); | ||
return true; | ||
} | ||
|
||
public String getForType(IMixinInfo mixin, Type type) { | ||
public static String getForType(Type type) { | ||
Class<?> refInterface = LocalRefUtils.getInterfaceFor(type); | ||
LocalRefClassInfo info = interfaceToInfo.get(refInterface); | ||
if (info != null) { | ||
return info.getName(); | ||
String owner = interfaceToImpl.get(refInterface); | ||
if (owner != null) { | ||
return owner; | ||
} | ||
owner = IMPL_PACKAGE + '/' + StringUtils.substringAfterLast(refInterface.getName(), ".") + "Impl"; | ||
String desc = type.getDescriptor(); | ||
info = new LocalRefClassInfo(mixin, refInterface, desc.length() == 1 ? desc : Type.getDescriptor(Object.class)); | ||
interfaceToInfo.put(refInterface, info); | ||
nameToInfo.put(info.getClassName(), info); | ||
registry.accept(info); | ||
return info.getName(); | ||
String innerDesc = desc.length() == 1 ? desc : Type.getDescriptor(Object.class); | ||
interfaceToImpl.put(refInterface, owner); | ||
ClassNode node = new ClassNode(); | ||
node.visit(Opcodes.V1_8, Opcodes.ACC_PUBLIC | Opcodes.ACC_SUPER, owner, null, Type.getInternalName(Object.class), null); | ||
generateClass(node, owner, innerDesc, refInterface.getName()); | ||
ClassGenUtils.defineClass(node, MethodHandles.lookup()); | ||
return owner; | ||
} | ||
|
||
private void generateClass(ClassNode node, LocalRefClassInfo info) { | ||
for (String name : PackageUtils.getAllClassNames(info.getInterfaceName())) { | ||
private static void generateClass(ClassNode node, String owner, String innerDesc, String interfaceName) { | ||
for (String name : PackageUtils.getAllClassNames(interfaceName)) { | ||
node.interfaces.add(name.replace('.', '/')); | ||
} | ||
node.visitField(Opcodes.ACC_PRIVATE, "value", info.getDesc(), null, null); | ||
node.visitField(Opcodes.ACC_PRIVATE, "value", innerDesc, null, null); | ||
|
||
MethodVisitor ctor = node.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(" + info.getDesc() + ")V", null, null); | ||
MethodVisitor ctor = node.visitMethod(Opcodes.ACC_PUBLIC, "<init>", "(" + innerDesc + ")V", null, null); | ||
ctor.visitCode(); | ||
ctor.visitVarInsn(Opcodes.ALOAD, 0); | ||
ctor.visitMethodInsn(Opcodes.INVOKESPECIAL, Type.getInternalName(Object.class), "<init>", "()V", false); | ||
ctor.visitVarInsn(Opcodes.ALOAD, 0); | ||
ctor.visitVarInsn(Type.getType(info.getDesc()).getOpcode(Opcodes.ILOAD), 1); | ||
ctor.visitFieldInsn(Opcodes.PUTFIELD, info.getName(), "value", info.getDesc()); | ||
ctor.visitVarInsn(Type.getType(innerDesc).getOpcode(Opcodes.ILOAD), 1); | ||
ctor.visitFieldInsn(Opcodes.PUTFIELD, owner, "value", innerDesc); | ||
ctor.visitInsn(Opcodes.RETURN); | ||
ctor.visitMaxs(3, 3); | ||
ctor.visitEnd(); | ||
|
||
MethodVisitor getter = node.visitMethod(Opcodes.ACC_PUBLIC, "get", "()" + info.getDesc(), null, null); | ||
MethodVisitor getter = node.visitMethod(Opcodes.ACC_PUBLIC, "get", "()" + innerDesc, null, null); | ||
getter.visitCode(); | ||
getter.visitVarInsn(Opcodes.ALOAD, 0); | ||
getter.visitFieldInsn(Opcodes.GETFIELD, info.getName(), "value", info.getDesc()); | ||
getter.visitInsn(Type.getType(info.getDesc()).getOpcode(Opcodes.IRETURN)); | ||
getter.visitFieldInsn(Opcodes.GETFIELD, owner, "value", innerDesc); | ||
getter.visitInsn(Type.getType(innerDesc).getOpcode(Opcodes.IRETURN)); | ||
getter.visitMaxs(2, 1); | ||
getter.visitEnd(); | ||
|
||
MethodVisitor setter = node.visitMethod(Opcodes.ACC_PUBLIC, "set", "(" + info.getDesc() + ")V", null, null); | ||
MethodVisitor setter = node.visitMethod(Opcodes.ACC_PUBLIC, "set", "(" + innerDesc + ")V", null, null); | ||
setter.visitCode(); | ||
setter.visitVarInsn(Opcodes.ALOAD, 0); | ||
setter.visitVarInsn(Type.getType(info.getDesc()).getOpcode(Opcodes.ILOAD), 1); | ||
setter.visitFieldInsn(Opcodes.PUTFIELD, info.getName(), "value", info.getDesc()); | ||
setter.visitVarInsn(Type.getType(innerDesc).getOpcode(Opcodes.ILOAD), 1); | ||
setter.visitFieldInsn(Opcodes.PUTFIELD, owner, "value", innerDesc); | ||
setter.visitInsn(Opcodes.RETURN); | ||
setter.visitMaxs(3, 3); | ||
setter.visitEnd(); | ||
} | ||
|
||
public static LocalRefClassGenerator getInstance() { | ||
return INSTANCE; | ||
} | ||
} |
34 changes: 0 additions & 34 deletions
34
src/main/java/com/llamalad7/mixinextras/sugar/impl/ref/LocalRefClassInfo.java
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
56 changes: 56 additions & 0 deletions
56
src/main/java/com/llamalad7/mixinextras/utils/ClassGenUtils.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,56 @@ | ||
package com.llamalad7.mixinextras.utils; | ||
|
||
import org.objectweb.asm.ClassWriter; | ||
import org.objectweb.asm.tree.ClassNode; | ||
import sun.misc.Unsafe; | ||
|
||
import java.lang.invoke.MethodHandles; | ||
import java.lang.reflect.Field; | ||
import java.lang.reflect.InvocationTargetException; | ||
import java.lang.reflect.Method; | ||
import java.security.ProtectionDomain; | ||
|
||
public class ClassGenUtils { | ||
private static final Definer DEFINER; | ||
|
||
static { | ||
Definer theDefiner; | ||
try { | ||
Unsafe.class.getMethod("defineClass", String.class, byte[].class, int.class, int.class, ClassLoader.class, ProtectionDomain.class); | ||
Field theUnsafe = Unsafe.class.getDeclaredField("theUnsafe"); | ||
theUnsafe.setAccessible(true); | ||
Unsafe unsafe = (Unsafe) theUnsafe.get(null); | ||
theDefiner = (name, bytes, scope) -> unsafe.defineClass(name, bytes, 0, bytes.length, scope.lookupClass().getClassLoader(), scope.lookupClass().getProtectionDomain()); | ||
} catch (IllegalAccessException | NoSuchFieldException | NoSuchMethodException e1) { | ||
try { | ||
//noinspection JavaReflectionMemberAccess | ||
Method defineClass = MethodHandles.Lookup.class.getMethod("defineClass", byte[].class); | ||
theDefiner = (name, bytes, scope) -> { | ||
try { | ||
//noinspection PrimitiveArrayArgumentToVarargsMethod | ||
defineClass.invoke(scope, bytes); | ||
} catch (IllegalAccessException | InvocationTargetException e) { | ||
throw new RuntimeException(e); | ||
} | ||
}; | ||
} catch (NoSuchMethodException e2) { | ||
RuntimeException e = new RuntimeException("Could not resolve class definer! Please report to LlamaLad7."); | ||
e.addSuppressed(e1); | ||
e.addSuppressed(e2); | ||
throw e; | ||
} | ||
} | ||
DEFINER = theDefiner; | ||
} | ||
|
||
public static void defineClass(ClassNode node, MethodHandles.Lookup scope) { | ||
ClassWriter writer = new ClassWriter(ClassWriter.COMPUTE_FRAMES); | ||
node.accept(writer); | ||
DEFINER.define(node.name.replace('/', '.'), writer.toByteArray(), scope); | ||
} | ||
|
||
@FunctionalInterface | ||
private interface Definer { | ||
void define(String name, byte[] bytes, MethodHandles.Lookup scope); | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters