From ad1d110a63ac1089567ed04e1d7babbd65a0eb81 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Wed, 1 Dec 2021 20:50:05 +0000 Subject: [PATCH 01/84] Bump version to 0.8.6-SNAPSHOT --- gradle.properties | 4 ++-- .../java/org/spongepowered/asm/launch/MixinBootstrap.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index 8b0d502ac..0ac521a68 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ packaging=jar description=Mixin url=https://www.spongepowered.org organization=SpongePowered -buildVersion=0.8.5 -buildType=RELEASE +buildVersion=0.8.6 +buildType=SNAPSHOT asmVersion=9.2 legacyForgeAsmVersion=5.0.3 modlauncherAsmVersion=9.1 diff --git a/src/main/java/org/spongepowered/asm/launch/MixinBootstrap.java b/src/main/java/org/spongepowered/asm/launch/MixinBootstrap.java index 8dbc050af..94d1d3cfa 100644 --- a/src/main/java/org/spongepowered/asm/launch/MixinBootstrap.java +++ b/src/main/java/org/spongepowered/asm/launch/MixinBootstrap.java @@ -65,7 +65,7 @@ public abstract class MixinBootstrap { /** * Subsystem version */ - public static final String VERSION = "0.8.5"; + public static final String VERSION = "0.8.6"; /** * Transformer factory From 45856b4028cf6050a8690f18382d99d0966c028d Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 4 Dec 2021 13:02:06 +0000 Subject: [PATCH 02/84] Replace guava deleteRecursively with internal version for guava compat --- build.gradle | 3 +- .../transformer/debug/RuntimeDecompiler.java | 4 +- .../extensions/ExtensionClassExporter.java | 4 +- .../org/spongepowered/asm/util/Files.java | 39 +++++++++++++++++++ 4 files changed, 43 insertions(+), 7 deletions(-) diff --git a/build.gradle b/build.gradle index c73873f28..a0bbf8ed5 100644 --- a/build.gradle +++ b/build.gradle @@ -221,7 +221,8 @@ javadoc { classpath += sourceSets.legacy.output source sourceSets.ap.allJava exclude { - it.relativePath.file && it.relativePath.pathString =~ 'tools' && !(it.name =~ /SuppressedBy|package-info/) } + it.relativePath.file && it.relativePath.pathString =~ 'tools' && !(it.name =~ /SuppressedBy|package-info/) + } options { docTitle 'Welcome to the Mixin Javadoc' overview 'docs/javadoc/overview.html' diff --git a/src/fernflower/java/org/spongepowered/asm/mixin/transformer/debug/RuntimeDecompiler.java b/src/fernflower/java/org/spongepowered/asm/mixin/transformer/debug/RuntimeDecompiler.java index bb2620a47..5121a39df 100644 --- a/src/fernflower/java/org/spongepowered/asm/mixin/transformer/debug/RuntimeDecompiler.java +++ b/src/fernflower/java/org/spongepowered/asm/mixin/transformer/debug/RuntimeDecompiler.java @@ -45,8 +45,6 @@ import com.google.common.base.Charsets; import com.google.common.collect.ImmutableMap; import com.google.common.io.Files; -import com.google.common.io.MoreFiles; -import com.google.common.io.RecursiveDeleteOption; /** * Wrapper for FernFlower to support runtime-decompilation of post-mixin classes @@ -73,7 +71,7 @@ public RuntimeDecompiler(File outputPath) { this.outputPath = outputPath; if (this.outputPath.exists()) { try { - MoreFiles.deleteRecursively(this.outputPath.toPath(), RecursiveDeleteOption.ALLOW_INSECURE); + org.spongepowered.asm.util.Files.deleteRecursively(this.outputPath); } catch (IOException ex) { this.logger.debug("Error cleaning output directory: {}", ex.getMessage()); } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionClassExporter.java b/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionClassExporter.java index 02ed7a38c..352e83b96 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionClassExporter.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionClassExporter.java @@ -44,8 +44,6 @@ import org.spongepowered.asm.util.perf.Profiler.Section; import com.google.common.io.Files; -import com.google.common.io.MoreFiles; -import com.google.common.io.RecursiveDeleteOption; /** * Debug exporter @@ -76,7 +74,7 @@ public ExtensionClassExporter(MixinEnvironment env) { this.decompiler = this.initDecompiler(env, new File(Constants.DEBUG_OUTPUT_DIR, ExtensionClassExporter.EXPORT_JAVA_DIR)); try { - MoreFiles.deleteRecursively(this.classExportDir.toPath(), RecursiveDeleteOption.ALLOW_INSECURE); + org.spongepowered.asm.util.Files.deleteRecursively(this.classExportDir); } catch (IOException ex) { ExtensionClassExporter.logger.debug("Error cleaning class output directory: {}", ex.getMessage()); } diff --git a/src/main/java/org/spongepowered/asm/util/Files.java b/src/main/java/org/spongepowered/asm/util/Files.java index 82a26778d..a28931247 100644 --- a/src/main/java/org/spongepowered/asm/util/Files.java +++ b/src/main/java/org/spongepowered/asm/util/Files.java @@ -25,6 +25,7 @@ package org.spongepowered.asm.util; import java.io.File; +import java.io.IOException; import java.net.URI; import java.net.URISyntaxException; import java.net.URL; @@ -75,4 +76,42 @@ public static File toFile(URI uri) { return new File(uri); } + /** + * Recursively delete a directory. Does not support symlinks but this is + * mainly used by mixin internals which only write files and normal + * directories so it should be fine + * + * @param dir Root directory to delete + * @throws IOException Security errors and any other IO errors encountered + * are raised as IOExceptions + */ + public static void deleteRecursively(File dir) throws IOException { + if (dir == null || !dir.isDirectory()) { + return; + } + + try { + File[] files = dir.listFiles(); + if (files == null) { + throw new IOException("Error enumerating directory during recursive delete operation: " + dir.getAbsolutePath()); + } + + for (File child : files) { + if (child.isDirectory()) { + Files.deleteRecursively(child); + } else if (child.isFile()) { + if (!child.delete()) { + throw new IOException("Error deleting file during recursive delete operation: " + child.getAbsolutePath()); + } + } + } + + if (!dir.delete()) { + throw new IOException("Error deleting directory during recursive delete operation: " + dir.getAbsolutePath()); + } + } catch (SecurityException ex) { + throw new IOException("Security error during recursive delete operation", ex); + } + } + } From a60200dcbb084318e6eaee4d1468681fcebde8ea Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 4 Dec 2021 13:48:03 +0000 Subject: [PATCH 03/84] Restore compat with ASM 5.0.3 by dynamically creating ClassRemapper --- .../transformer/InnerClassGenerator.java | 58 +++++++++++++++++-- 1 file changed, 52 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/InnerClassGenerator.java b/src/main/java/org/spongepowered/asm/mixin/transformer/InnerClassGenerator.java index d8009cbcb..a05d45223 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/InnerClassGenerator.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/InnerClassGenerator.java @@ -34,7 +34,6 @@ import org.objectweb.asm.ClassVisitor; import org.objectweb.asm.commons.Remapper; import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.commons.ClassRemapper; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import org.spongepowered.asm.mixin.transformer.ClassInfo.Method; import org.spongepowered.asm.mixin.transformer.ext.IClassGenerator; @@ -201,18 +200,17 @@ public String toString() { * Just a basic remapping adapter, but we also decorate the transformed * class with a meta annotation describing the original class. */ - static class InnerClassAdapter extends ClassRemapper { + static class InnerClassAdapter extends ClassVisitor { private final InnerClassInfo info; InnerClassAdapter(ClassVisitor cv, InnerClassInfo info) { - super(ASM.API_VERSION, cv, info); + super(ASM.API_VERSION, cv); this.info = info; } /* (non-Javadoc) - * @see org.objectweb.asm.commons.ClassRemapper - * #visitNestHost(java.lang.String) + * @see org.objectweb.asm.ClassVisitor#visitNestHost(java.lang.String) */ @Override public void visitNestHost(String nestHost) { @@ -251,6 +249,21 @@ public void visitInnerClass(String name, String outerName, String innerName, int } + /** + * ClassRemapper from ASM 5.2 onward + */ + private static final String REMAPPER_CLASS = "org.objectweb.asm.commons.ClassRemapper"; + + /** + * RemappingClassAdapter prior to ASM 5.2 + */ + private static final String REMAPPER_CLASS_LEGACY = "org.objectweb.asm.commons.RemappingClassAdapter"; + + /** + * Resolved ClassRemapper class + */ + private static Class clRemapper; + /** * Logger */ @@ -364,7 +377,7 @@ public boolean generate(String name, ClassNode classNode) { private boolean generate(InnerClassInfo info, ClassNode classNode) { try { InnerClassGenerator.logger.debug("Generating mapped inner class {} (originally {})", info.getName(), info.getOriginalName()); - info.accept(new InnerClassAdapter(classNode, info)); + info.accept(new InnerClassAdapter(InnerClassGenerator.createRemappingAdapter(classNode, info), info)); return true; } catch (InvalidMixinException ex) { throw ex; @@ -391,4 +404,37 @@ private static String getUniqueReference(String originalName, ClassInfo targetCl return String.format("%s$%s$%s", targetClass, name, UUID.randomUUID().toString().replace("-", "")); } + /** + * Since we still technically support ASM5, and the remapping visitor class + * was refactored between ASM 5.0.3 and ASM 5.2, we can instatiate it using + * reflection in order to try both variants. Throws CNFE if the class can't + * be loaded for some reason + * + * @param cv Upstream ClassVisitor + * @param remapper Remapper to use + * @return New ClassRemapper or RemappingClassAdapter + * @throws ReflectiveOperationException if something goes wrong + */ + @SuppressWarnings("unchecked") + private static ClassVisitor createRemappingAdapter(ClassVisitor cv, Remapper remapper) throws ReflectiveOperationException { + if (InnerClassGenerator.clRemapper == null) { + try { + InnerClassGenerator.clRemapper = (Class)Class.forName(InnerClassGenerator.REMAPPER_CLASS); + } catch (ClassNotFoundException ex) { + // expected under ASM 5.0.3 since the new class doesn't exist yet + } + + if (InnerClassGenerator.clRemapper == null) { + try { + InnerClassGenerator.clRemapper = (Class)Class.forName(InnerClassGenerator.REMAPPER_CLASS_LEGACY); + } catch (ClassNotFoundException ex) { + // Not expected + throw new ClassNotFoundException(InnerClassGenerator.REMAPPER_CLASS + " or " + InnerClassGenerator.REMAPPER_CLASS_LEGACY); + } + } + } + + return InnerClassGenerator.clRemapper.getConstructor(ClassVisitor.class, Remapper.class).newInstance(cv, remapper); + } + } From 89acb8387aea527ae05908040ca4fdda231e6e16 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 24 Jul 2022 13:19:19 +0100 Subject: [PATCH 04/84] Prioritise TypeHandleASM when it is available. Fixes remapping of synthetic members in resolvable classes. (cherry picked from commit c33a48ba5ec71ec736d5ef6b782a78a1fc60295d) --- .../tools/obfuscation/AnnotatedMixins.java | 56 ++++++++++--------- 1 file changed, 31 insertions(+), 25 deletions(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixins.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixins.java index 0835ead59..654e61dd7 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixins.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixins.java @@ -73,6 +73,7 @@ import org.spongepowered.tools.obfuscation.mirror.TypeHandleASM; import org.spongepowered.tools.obfuscation.mirror.TypeHandleSimulated; import org.spongepowered.tools.obfuscation.mirror.TypeReference; +import org.spongepowered.tools.obfuscation.mirror.TypeUtils; import org.spongepowered.tools.obfuscation.struct.InjectorRemap; import org.spongepowered.tools.obfuscation.validation.ParentValidator; import org.spongepowered.tools.obfuscation.validation.TargetValidator; @@ -158,7 +159,7 @@ private AnnotatedMixins(ProcessingEnvironment processingEnv) { this.targets = this.initTargetMap(); this.obf = new ObfuscationManager(this); this.obf.init(); - + this.validators = ImmutableList.of( new ParentValidator(this), new TargetValidator(this) @@ -243,7 +244,7 @@ public ProcessingEnvironment getProcessingEnvironment() { public CompilerEnvironment getCompilerEnvironment() { return this.env; } - + @Override public Integer getToken(String token) { if (this.tokenCache.containsKey(token)) { @@ -590,7 +591,7 @@ public void printMessage(MessageType type, CharSequence msg, Element element, Su this.printMessage(type.getKind(), type.decorate(msg), element, suppressedBy); } } - + /** * Print a message to the AP messager */ @@ -648,7 +649,7 @@ public void printMessage(MessageType type, CharSequence msg, Element element, An this.printMessage(type.getKind(), type.decorate(msg), element, annotation, value); } } - + /** * Print a message to the AP messager */ @@ -688,6 +689,24 @@ public TypeHandle getTypeHandle(String name) { name = name.replace('/', '.'); Elements elements = this.processingEnv.getElementUtils(); + PackageElement pkg = null; + + int lastDotPos = name.lastIndexOf('.'); + if (lastDotPos > -1) { + String pkgName = name.substring(0, lastDotPos); + pkg = elements.getPackageElement(pkgName); + } + + if (pkg != null) { + // ASM gives the most and most accurate information. Try that first. + TypeHandle asmTypeHandle = TypeHandleASM.of(pkg, name.substring(lastDotPos + 1), this); + if (asmTypeHandle != null) { + return asmTypeHandle; + } + } + + // ASM may be unable to resolve the class, for example if it's currently being compiled. + // Mirror is our next best bet. TypeElement element = this.getTypeElement(name, elements); if (element != null) { try { @@ -697,28 +716,15 @@ public TypeHandle getTypeHandle(String name) { } } - int lastDotPos = name.lastIndexOf('.'); - if (lastDotPos > -1) { - String pkgName = name.substring(0, lastDotPos); - PackageElement pkg = elements.getPackageElement(pkgName); - if (pkg != null) { - // If we can resolve the package but not the class, it's possible - // we're dealing with a class that mirror can't access, such as - // an anonymous class. The class might be available via the - // classpath though, so let's attempt to read the class with ASM - TypeHandle asmTypeHandle = TypeHandleASM.of(pkg, name.substring(lastDotPos + 1), this); - if (asmTypeHandle != null) { - return asmTypeHandle; - } - - // Couldn't resolve the class, so just return an imaginary handle - return new TypeHandle(pkg, name); - } + if (pkg != null) { + // Couldn't resolve the class, but could resolve the package, so just return an imaginary handle. + return new TypeHandle(pkg, name); } - + + // Couldn't even resolve the package, all hope is lost. return null; } - + /** * Get a TypeHandle representing the supplied type in the current processing * environment @@ -728,11 +734,11 @@ public TypeHandle getTypeHandle(Object type) { if (type instanceof TypeHandle) { return (TypeHandle)type; } else if (type instanceof DeclaredType) { - return new TypeHandle((DeclaredType)type); + return this.getTypeHandle(TypeUtils.getInternalName((DeclaredType) type)); } else if (type instanceof Type) { return this.getTypeHandle(((Type)type).getClassName()); } else if (type instanceof TypeElement) { - return new TypeHandle((DeclaredType)((TypeElement)type).asType()); + return this.getTypeHandle(TypeUtils.getInternalName((TypeElement) type)); } else if (type instanceof String) { return this.getTypeHandle(type.toString()); } From 1cec8b137739ebe0e4e79bb66f9eb6369dfa067d Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Mon, 13 Feb 2023 19:17:32 +0000 Subject: [PATCH 05/84] AP: Fix several cases of ASM TypeHandles breaking code that relies on Mirror. (cherry picked from commit e5d9db491a4d031fe590e274f7eeb9ef4fae33f7) --- .../tools/obfuscation/AnnotatedMixin.java | 2 +- ...atedMixinElementHandlerSoftImplements.java | 2 +- .../tools/obfuscation/AnnotatedMixins.java | 22 ++++---- .../tools/obfuscation/MixinValidator.java | 12 +++-- .../obfuscation/ObfuscationEnvironment.java | 6 +-- .../tools/obfuscation/TargetMap.java | 10 ---- .../tools/obfuscation/mirror/TypeHandle.java | 49 +++++++++++++++--- .../obfuscation/mirror/TypeHandleASM.java | 29 +++++++---- .../mirror/TypeHandleSimulated.java | 22 ++++---- .../obfuscation/mirror/TypeReference.java | 9 ++-- .../validation/TargetValidator.java | 51 ++++--------------- 11 files changed, 110 insertions(+), 104 deletions(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java index f3b918a80..001bbdfae 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java @@ -176,7 +176,7 @@ public AnnotatedMixin(IMixinAnnotationProcessor ap, TypeElement type) { this.mappings = this.obf.createMappingConsumer(); this.messager = ap; this.mixin = type; - this.handle = new TypeHandle(type); + this.handle = new TypeHandle(type, ap.getTypeProvider()); this.methods = new ArrayList(this.handle.getMethods()); this.virtual = this.handle.getAnnotation(Pseudo.class).exists(); this.annotation = this.handle.getAnnotation(Mixin.class); diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerSoftImplements.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerSoftImplements.java index f9e7dfb20..82be0f1b1 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerSoftImplements.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerSoftImplements.java @@ -77,7 +77,7 @@ public void process(AnnotationHandle implementsAnnotation) { } try { - TypeHandle iface = new TypeHandle(interfaceAnnotation.getValue("iface")); + TypeHandle iface = this.ap.getTypeProvider().getTypeHandle(interfaceAnnotation.getValue("iface")); String prefix = interfaceAnnotation.getValue("prefix"); this.processSoftImplements(remap, iface, prefix); } catch (Exception ex) { diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixins.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixins.java index 654e61dd7..ec6670b70 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixins.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixins.java @@ -371,17 +371,13 @@ public AnnotatedMixin getMixin(String mixinType) { return this.mixins.get(mixinType); } - public Collection getMixinsTargeting(TypeMirror targetType) { - return this.getMixinsTargeting((TypeElement)((DeclaredType)targetType).asElement()); - } - - public Collection getMixinsTargeting(TypeElement targetType) { - List minions = new ArrayList(); + public Collection getMixinsTargeting(TypeHandle targetType) { + List minions = new ArrayList(); for (TypeReference mixin : this.targets.getMixinsTargeting(targetType)) { - TypeHandle handle = mixin.getHandle(this.processingEnv); - if (handle != null && handle.hasTypeMirror()) { - minions.add(handle.getTypeMirror()); + TypeHandle handle = mixin.getHandle(this); + if (handle != null) { + minions.add(handle); } } @@ -710,7 +706,7 @@ public TypeHandle getTypeHandle(String name) { TypeElement element = this.getTypeElement(name, elements); if (element != null) { try { - return new TypeHandle(element); + return new TypeHandle(element, this); } catch (NullPointerException ex) { // probably bad package } @@ -718,7 +714,7 @@ public TypeHandle getTypeHandle(String name) { if (pkg != null) { // Couldn't resolve the class, but could resolve the package, so just return an imaginary handle. - return new TypeHandle(pkg, name); + return new TypeHandle(pkg, name, this); } // Couldn't even resolve the package, all hope is lost. @@ -816,11 +812,11 @@ public TypeHandle getSimulatedHandle(String name, TypeMirror simulatedTarget) { String pkg = name.substring(0, lastDotPos); PackageElement packageElement = this.processingEnv.getElementUtils().getPackageElement(pkg); if (packageElement != null) { - return new TypeHandleSimulated(packageElement, name, simulatedTarget); + return new TypeHandleSimulated(packageElement, name, simulatedTarget, this); } } - return new TypeHandleSimulated(name, simulatedTarget); + return new TypeHandleSimulated(name, simulatedTarget, this); } /* (non-Javadoc) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/MixinValidator.java b/src/ap/java/org/spongepowered/tools/obfuscation/MixinValidator.java index ab10529ad..85f685124 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/MixinValidator.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/MixinValidator.java @@ -28,13 +28,13 @@ import javax.annotation.processing.ProcessingEnvironment; import javax.lang.model.element.TypeElement; -import javax.lang.model.type.TypeMirror; import org.spongepowered.asm.util.asm.IAnnotationHandle; import org.spongepowered.tools.obfuscation.interfaces.IMessagerSuppressible; import org.spongepowered.tools.obfuscation.interfaces.IMixinAnnotationProcessor; import org.spongepowered.tools.obfuscation.interfaces.IMixinValidator; import org.spongepowered.tools.obfuscation.interfaces.IOptionProvider; +import org.spongepowered.tools.obfuscation.interfaces.ITypeHandleProvider; import org.spongepowered.tools.obfuscation.mirror.TypeHandle; /** @@ -57,7 +57,12 @@ public abstract class MixinValidator implements IMixinValidator { * Option provider */ protected final IOptionProvider options; - + + /** + * Type handle provider + */ + protected final ITypeHandleProvider typeHandleProvider; + /** * Pass to run this validator in */ @@ -73,6 +78,7 @@ public MixinValidator(IMixinAnnotationProcessor ap, ValidationPass pass) { this.processingEnv = ap.getProcessingEnvironment(); this.messager = ap; this.options = ap; + this.typeHandleProvider = ap.getTypeProvider(); this.pass = pass; } @@ -94,7 +100,7 @@ public final boolean validate(ValidationPass pass, TypeElement mixin, IAnnotatio protected abstract boolean validate(TypeElement mixin, IAnnotationHandle annotation, Collection targets); - protected final Collection getMixinsTargeting(TypeMirror targetType) { + protected final Collection getMixinsTargeting(TypeHandle targetType) { return AnnotatedMixins.getMixinsForEnvironment(this.processingEnv).getMixinsTargeting(targetType); } } diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java b/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java index 2f55a8732..fa42dcb87 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java @@ -196,13 +196,13 @@ public MappingMethod getObfMethod(ITargetSelectorRemappable method) { } // See if we can get the superclass from the reference - TypeMirror superClass = type.getElement().getSuperclass(); - if (superClass.getKind() != TypeKind.DECLARED) { + TypeHandle superClass = type.getSuperclass(); + if (superClass == null) { return null; } // Well we found it, let's inflect the class name and recurse the search - String superClassName = ((TypeElement)((DeclaredType)superClass).asElement()).getQualifiedName().toString(); + String superClassName = superClass.getSimpleName(); return this.getObfMethod(method.move(superClassName.replace('.', '/'))); } diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/TargetMap.java b/src/ap/java/org/spongepowered/tools/obfuscation/TargetMap.java index 1cd2fbadb..80c8e2148 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/TargetMap.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/TargetMap.java @@ -132,16 +132,6 @@ void addMixin(TypeReference target, TypeReference mixin) { Set mixins = this.getMixinsFor(target); mixins.add(mixin); } - - /** - * Get mixin classes which target the specified class - * - * @param target Target class - * @return Collection of mixins registered as targetting the specified class - */ - Collection getMixinsTargeting(TypeElement target) { - return this.getMixinsTargeting(new TypeHandle(target)); - } /** * Get mixin classes which target the specified class diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java index 3b92cb08b..11ed760e8 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java @@ -44,6 +44,7 @@ import org.spongepowered.asm.obfuscation.mapping.common.MappingMethod; import org.spongepowered.asm.util.Bytecode; import org.spongepowered.asm.util.asm.IAnnotationHandle; +import org.spongepowered.tools.obfuscation.interfaces.ITypeHandleProvider; import org.spongepowered.tools.obfuscation.mirror.mapping.MappingMethodResolvable; import com.google.common.collect.ImmutableList; @@ -71,6 +72,12 @@ public class TypeHandle { * Actual type element, this is null for inaccessible classes */ private final TypeElement element; + + /** + * Type handle provider, we need this since we have to resolve type handles + * for related classes (eg. superclass) without using mirror + */ + protected final ITypeHandleProvider typeProvider; /** * Reference to this handle, for serialisation @@ -84,10 +91,11 @@ public class TypeHandle { * @param pkg Package * @param name FQ class name */ - public TypeHandle(PackageElement pkg, String name) { + public TypeHandle(PackageElement pkg, String name, ITypeHandleProvider typeProvider) { this.name = name.replace('.', '/'); this.pkg = pkg; this.element = null; + this.typeProvider = typeProvider; } /** @@ -95,10 +103,11 @@ public TypeHandle(PackageElement pkg, String name) { * * @param element ze element */ - public TypeHandle(TypeElement element) { + public TypeHandle(TypeElement element, ITypeHandleProvider typeProvider) { this.pkg = TypeUtils.getPackage(element); this.name = TypeUtils.getInternalName(element); this.element = element; + this.typeProvider = typeProvider; } /** @@ -106,8 +115,8 @@ public TypeHandle(TypeElement element) { * * @param type type */ - public TypeHandle(DeclaredType type) { - this((TypeElement)type.asElement()); + public TypeHandle(DeclaredType type, ITypeHandleProvider typeProvider) { + this((TypeElement)type.asElement(), typeProvider); } /* (non-Javadoc) @@ -215,7 +224,7 @@ public TypeHandle getSuperclass() { return null; } - return new TypeHandle((DeclaredType)superClass); + return typeProvider.getTypeHandle(superClass); } /** @@ -228,7 +237,7 @@ public List getInterfaces() { Builder list = ImmutableList.builder(); for (TypeMirror iface : this.getTargetElement().getInterfaces()) { - list.add(new TypeHandle((DeclaredType)iface)); + list.add(typeProvider.getTypeHandle(iface)); } return list.build(); } @@ -275,6 +284,34 @@ public boolean isImaginary() { public boolean isSimulated() { return false; } + + /** + * Gets whether this handle definitely does not represent an interface + */ + public boolean isNotInterface() { + TypeElement target = this.getTargetElement(); + return target != null && !target.getKind().isInterface(); + } + + /** + * Gets whether this handle is the same as or a supertype of the other handle + */ + public boolean isAssignableFrom(TypeHandle other) { + if (this.getName().equals(other.getName())) { + return true; + } + List superTypes = new ArrayList<>(); + if (other.getSuperclass() != null) { + superTypes.add(other.getSuperclass()); + } + superTypes.addAll(other.getInterfaces()); + for (TypeHandle superType : superTypes) { + if (this.isAssignableFrom(superType)) { + return true; + } + } + return false; + } /** * Get the TypeReference for this type, used for serialisation diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleASM.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleASM.java index aa745b405..00ae928fa 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleASM.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleASM.java @@ -76,12 +76,6 @@ public class TypeHandleASM extends TypeHandle { * ClassNode used for accessing information when not accessible via mirror */ private final ClassNode classNode; - - /** - * Type handle provider, we need this since we have to resolve type handles - * for related classes (eg. superclass) without using mirror - */ - private final ITypeHandleProvider typeProvider; /** * Ctor for imaginary elements, require the enclosing package and the FQ @@ -91,9 +85,8 @@ public class TypeHandleASM extends TypeHandle { * @param name FQ class name */ protected TypeHandleASM(PackageElement pkg, String name, ClassNode classNode, ITypeHandleProvider typeProvider) { - super(pkg, name); + super(pkg, name, typeProvider); this.classNode = classNode; - this.typeProvider = typeProvider; } /* (non-Javadoc) @@ -146,8 +139,10 @@ public TypeMirror getTypeMirror() { */ @Override public TypeHandle getSuperclass() { - TypeHandle superClass = this.typeProvider.getTypeHandle(this.classNode.superName); - return superClass; + if (this.classNode.superName == null) { + return null; + } + return this.typeProvider.getTypeHandle(this.classNode.superName); } /* (non-Javadoc) @@ -196,6 +191,14 @@ public boolean isImaginary() { return false; } + /* (non-Javadoc) + * @see org.spongepowered.tools.obfuscation.mirror.TypeHandle#isInterface() + */ + @Override + public boolean isNotInterface() { + return (this.classNode.access & Opcodes.ACC_INTERFACE) == 0; + } + /* (non-Javadoc) * @see org.spongepowered.tools.obfuscation.mirror.TypeHandle#findDescriptor * (org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorByName) @@ -272,7 +275,11 @@ public static TypeHandle of(PackageElement pkg, String name, IMixinAnnotationPro InputStream is = null; try { Filer filer = ap.getProcessingEnvironment().getFiler(); - is = filer.getResource(StandardLocation.CLASS_PATH, pkg.getQualifiedName(), name + ".class").openInputStream(); + try { + is = filer.getResource(StandardLocation.CLASS_PATH, pkg.getQualifiedName(), name + ".class").openInputStream(); + } catch (FileNotFoundException ignored) { + is = filer.getResource(StandardLocation.PLATFORM_CLASS_PATH, pkg.getQualifiedName(), name + ".class").openInputStream(); + } ClassNode classNode = new ClassNode(); new ClassReader(is).accept(classNode, 0); TypeHandleASM typeHandle = new TypeHandleASM(pkg, fqName, classNode, ap.getTypeProvider()); diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleSimulated.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleSimulated.java index e34c7298b..bdb44612e 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleSimulated.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleSimulated.java @@ -35,6 +35,7 @@ import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorByName; import org.spongepowered.asm.obfuscation.mapping.common.MappingMethod; import org.spongepowered.asm.util.SignaturePrinter; +import org.spongepowered.tools.obfuscation.interfaces.ITypeHandleProvider; /** * A simulated type handle, used with virtual (pseudo) mixins. For obfuscation @@ -48,12 +49,12 @@ public class TypeHandleSimulated extends TypeHandle { private final TypeElement simulatedType; - public TypeHandleSimulated(String name, TypeMirror type) { - this(TypeUtils.getPackage(type), name, type); + public TypeHandleSimulated(String name, TypeMirror type, ITypeHandleProvider typeProvider) { + this(TypeUtils.getPackage(type), name, type, typeProvider); } - public TypeHandleSimulated(PackageElement pkg, String name, TypeMirror type) { - super(pkg, name); + public TypeHandleSimulated(PackageElement pkg, String name, TypeMirror type, ITypeHandleProvider typeProvider) { + super(pkg, name, typeProvider); this.simulatedType = (TypeElement)((DeclaredType)type).asElement(); } @@ -152,14 +153,14 @@ public MappingMethod getMappingMethod(String name, String desc) { String rawSignature = TypeUtils.stripGenerics(signature); // Try to locate a member anywhere in the hierarchy which matches - MethodHandle method = TypeHandleSimulated.findMethodRecursive(this, name, signature, rawSignature, true); + MethodHandle method = TypeHandleSimulated.findMethodRecursive(this, name, signature, rawSignature, true, this.typeProvider); // If we find one, return it otherwise just simulate the method return method != null ? method.asMapping(true) : super.getMappingMethod(name, desc); } - private static MethodHandle findMethodRecursive(TypeHandle target, String name, String signature, String rawSignature, boolean matchCase) { + private static MethodHandle findMethodRecursive(TypeHandle target, String name, String signature, String rawSignature, boolean matchCase, ITypeHandleProvider typeProvider) { TypeElement elem = target.getTargetElement(); if (elem == null) { return null; @@ -171,7 +172,7 @@ private static MethodHandle findMethodRecursive(TypeHandle target, String name, } for (TypeMirror iface : elem.getInterfaces()) { - method = TypeHandleSimulated.findMethodRecursive(iface, name, signature, rawSignature, matchCase); + method = TypeHandleSimulated.findMethodRecursive(iface, name, signature, rawSignature, matchCase, typeProvider); if (method != null) { return method; } @@ -182,15 +183,14 @@ private static MethodHandle findMethodRecursive(TypeHandle target, String name, return null; } - return TypeHandleSimulated.findMethodRecursive(superClass, name, signature, rawSignature, matchCase); + return TypeHandleSimulated.findMethodRecursive(superClass, name, signature, rawSignature, matchCase, typeProvider); } - private static MethodHandle findMethodRecursive(TypeMirror target, String name, String signature, String rawSignature, boolean matchCase) { + private static MethodHandle findMethodRecursive(TypeMirror target, String name, String signature, String rawSignature, boolean matchCase, ITypeHandleProvider typeProvider) { if (!(target instanceof DeclaredType)) { return null; } - TypeElement element = (TypeElement)((DeclaredType)target).asElement(); - return TypeHandleSimulated.findMethodRecursive(new TypeHandle(element), name, signature, rawSignature, matchCase); + return TypeHandleSimulated.findMethodRecursive(typeProvider.getTypeHandle(target), name, signature, rawSignature, matchCase, typeProvider); } } diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeReference.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeReference.java index e2a0b9798..41ea5f452 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeReference.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeReference.java @@ -24,6 +24,8 @@ */ package org.spongepowered.tools.obfuscation.mirror; +import org.spongepowered.tools.obfuscation.interfaces.ITypeHandleProvider; + import java.io.Serializable; import javax.annotation.processing.ProcessingEnvironment; @@ -82,15 +84,14 @@ public String getClassName() { /** * Fetch or attempt to generate the type handle * - * @param processingEnv environment to create handle if it needs to be + * @param typeHandleProvider provider to create handle if it needs to be * regenerated * @return type handle */ - public TypeHandle getHandle(ProcessingEnvironment processingEnv) { + public TypeHandle getHandle(ITypeHandleProvider typeHandleProvider) { if (this.handle == null) { - TypeElement element = processingEnv.getElementUtils().getTypeElement(this.getClassName()); try { - this.handle = new TypeHandle(element); + this.handle = typeHandleProvider.getTypeHandle(this.name); } catch (Exception ex) { // Class probably doesn't exist in scope :/ ex.printStackTrace(); diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java b/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java index 3db43e90d..d0a3cafab 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java @@ -29,9 +29,6 @@ import javax.lang.model.element.Element; import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Invoker; @@ -94,8 +91,7 @@ private void validateInterfaceMixin(TypeElement mixin, Collection ta } for (TypeHandle target : targets) { - TypeElement targetType = target.getElement(); - if (targetType != null && !(targetType.getKind() == ElementKind.INTERFACE)) { + if (target != null && target.isNotInterface()) { this.messager.printMessage(MessageType.TARGET_VALIDATOR, "Targetted type '" + target + " of " + mixin + " is not an interface", mixin); } @@ -103,54 +99,27 @@ private void validateInterfaceMixin(TypeElement mixin, Collection ta } private void validateClassMixin(TypeElement mixin, Collection targets) { - TypeMirror superClass = mixin.getSuperclass(); + TypeHandle superClass = this.typeHandleProvider.getTypeHandle(TypeUtils.getInternalName(mixin)).getSuperclass(); for (TypeHandle target : targets) { - TypeMirror targetType = target.getTypeMirror(); - if (targetType != null && !this.validateSuperClass(targetType, superClass)) { + if (target != null && !this.validateSuperClass(target, superClass)) { this.messager.printMessage(MessageType.TARGET_VALIDATOR, "Superclass " + superClass + " of " + mixin - + " was not found in the hierarchy of target class " + targetType, mixin); + + " was not found in the hierarchy of target class " + target, mixin); } } } - private boolean validateSuperClass(TypeMirror targetType, TypeMirror superClass) { - if (TypeUtils.isAssignable(this.processingEnv, targetType, superClass)) { - return true; - } - - return this.validateSuperClassRecursive(targetType, superClass); - } - - private boolean validateSuperClassRecursive(TypeMirror targetType, TypeMirror superClass) { - if (!(targetType instanceof DeclaredType)) { - return false; - } - - if (TypeUtils.isAssignable(this.processingEnv, targetType, superClass)) { - return true; - } - - TypeElement targetElement = (TypeElement)((DeclaredType)targetType).asElement(); - TypeMirror targetSuper = targetElement.getSuperclass(); - if (targetSuper.getKind() == TypeKind.NONE) { - return false; - } - - if (this.checkMixinsFor(targetSuper, superClass)) { - return true; - } - - return this.validateSuperClassRecursive(targetSuper, superClass); + private boolean validateSuperClass(TypeHandle targetType, TypeHandle superClass) { + return superClass.isAssignableFrom(targetType) || this.checkMixinsFor(targetType, superClass); } - private boolean checkMixinsFor(TypeMirror targetType, TypeMirror superClass) { - for (TypeMirror mixinType : this.getMixinsTargeting(targetType)) { - if (TypeUtils.isAssignable(this.processingEnv, mixinType, superClass)) { + private boolean checkMixinsFor(TypeHandle targetType, TypeHandle superClass) { + for (TypeHandle mixinType : this.getMixinsTargeting(targetType)) { + if (mixinType.isAssignableFrom(superClass)) { return true; } } - return false; + return targetType.getSuperclass() != null && this.checkMixinsFor(targetType.getSuperclass(), superClass); } } From 2e4bf962e9eaf85adee1865b1bdad9805f1befb2 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 24 Jul 2022 13:46:04 +0100 Subject: [PATCH 06/84] Fix resolution of upper bounds of intersection types. (cherry picked from commit c90298501502d9b23634345ccaf293470a0a6465) --- .../spongepowered/tools/obfuscation/mirror/TypeUtils.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeUtils.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeUtils.java index cbc888203..47242bbd3 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeUtils.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeUtils.java @@ -37,6 +37,7 @@ import javax.lang.model.element.VariableElement; import javax.lang.model.type.ArrayType; import javax.lang.model.type.DeclaredType; +import javax.lang.model.type.IntersectionType; import javax.lang.model.type.TypeKind; import javax.lang.model.type.TypeMirror; import javax.lang.model.type.TypeVariable; @@ -448,6 +449,10 @@ private static DeclaredType getUpperBound0(TypeMirror type, int depth) { throw new IllegalStateException("Generic symbol \"" + type + "\" is too complex, exceeded " + TypeUtils.MAX_GENERIC_RECURSION_DEPTH + " iterations attempting to determine upper bound"); } + if (type instanceof IntersectionType) { + TypeMirror first = ((IntersectionType) type).getBounds().get(0); + return TypeUtils.getUpperBound0(first, --depth); + } if (type instanceof DeclaredType) { return (DeclaredType)type; } From be3b94df6adf6b60566d4332640c6624037f9621 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 16 Feb 2023 20:10:24 +0000 Subject: [PATCH 07/84] AP: Skip superclass validation for imaginary and simulated targets. (cherry picked from commit 0fb3116c4f7143a46a0a2706cc6e7a43484aaafd) --- .../tools/obfuscation/validation/TargetValidator.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java b/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java index d0a3cafab..d86661bde 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java @@ -110,7 +110,7 @@ private void validateClassMixin(TypeElement mixin, Collection target } private boolean validateSuperClass(TypeHandle targetType, TypeHandle superClass) { - return superClass.isAssignableFrom(targetType) || this.checkMixinsFor(targetType, superClass); + return targetType.isImaginary() || targetType.isSimulated() || superClass.isAssignableFrom(targetType) || this.checkMixinsFor(targetType, superClass); } private boolean checkMixinsFor(TypeHandle targetType, TypeHandle superClass) { From ebc72cf7827ff4219d417074357a2012799a0b75 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 16 Feb 2023 20:10:24 +0000 Subject: [PATCH 08/84] AP: Improve validation of mixin superclasses. Extending an already-compiled mixin no longer results in an error, and extending (a mixin that targets) your target class does result in an error. (cherry picked from commit 7f7dbc1f37c7290bb25ac4ab07f8c5fa1534f1f8) --- .../tools/obfuscation/mirror/TypeHandle.java | 9 +++------ .../validation/TargetValidator.java | 18 ++++++++++++------ 2 files changed, 15 insertions(+), 12 deletions(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java index 11ed760e8..5c2f1e6ac 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java @@ -294,19 +294,16 @@ public boolean isNotInterface() { } /** - * Gets whether this handle is the same as or a supertype of the other handle + * Gets whether this handle is a supertype of the other handle */ - public boolean isAssignableFrom(TypeHandle other) { - if (this.getName().equals(other.getName())) { - return true; - } + public boolean isSuperTypeOf(TypeHandle other) { List superTypes = new ArrayList<>(); if (other.getSuperclass() != null) { superTypes.add(other.getSuperclass()); } superTypes.addAll(other.getInterfaces()); for (TypeHandle superType : superTypes) { - if (this.isAssignableFrom(superType)) { + if (this.name.equals(superType.name) || this.isSuperTypeOf(superType)) { return true; } } diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java b/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java index d86661bde..00a0e41d7 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java @@ -30,6 +30,7 @@ import javax.lang.model.element.ElementKind; import javax.lang.model.element.TypeElement; +import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Invoker; import org.spongepowered.asm.util.asm.IAnnotationHandle; @@ -110,16 +111,21 @@ private void validateClassMixin(TypeElement mixin, Collection target } private boolean validateSuperClass(TypeHandle targetType, TypeHandle superClass) { - return targetType.isImaginary() || targetType.isSimulated() || superClass.isAssignableFrom(targetType) || this.checkMixinsFor(targetType, superClass); + return targetType.isImaginary() || targetType.isSimulated() || superClass.isSuperTypeOf(targetType) || this.checkMixinsFor(targetType, superClass); } - private boolean checkMixinsFor(TypeHandle targetType, TypeHandle superClass) { - for (TypeHandle mixinType : this.getMixinsTargeting(targetType)) { - if (mixinType.isAssignableFrom(superClass)) { + private boolean checkMixinsFor(TypeHandle targetType, TypeHandle superMixin) { + IAnnotationHandle annotation = superMixin.getAnnotation(Mixin.class); + for (Object target : annotation.getList()) { + if (this.typeHandleProvider.getTypeHandle(target).isSuperTypeOf(targetType)) { return true; } } - - return targetType.getSuperclass() != null && this.checkMixinsFor(targetType.getSuperclass(), superClass); + for (String target : annotation.getList("targets")) { + if (this.typeHandleProvider.getTypeHandle(target).isSuperTypeOf(targetType)) { + return true; + } + } + return false; } } From 74c25d25ea841e286d79369b54111264ff6466c7 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Fri, 24 Feb 2023 22:24:59 +0000 Subject: [PATCH 09/84] AP: Fix and simplify checking of factory invoker return types. We no longer use mirror. The generics are irrelevant in the first place because Mixin does not check them at runtime. (cherry picked from commit ccfb5ee089629ce9c6f4137a0ef77aa1efc101ef) --- .../AnnotatedMixinElementHandlerAccessor.java | 20 +++++-------------- 1 file changed, 5 insertions(+), 15 deletions(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java index d8dc48622..d5bfd8025 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java @@ -46,8 +46,6 @@ import org.spongepowered.tools.obfuscation.mirror.MethodHandle; import org.spongepowered.tools.obfuscation.mirror.TypeHandle; import org.spongepowered.tools.obfuscation.mirror.TypeUtils; -import org.spongepowered.tools.obfuscation.mirror.TypeUtils.Equivalency; -import org.spongepowered.tools.obfuscation.mirror.TypeUtils.EquivalencyResult; import com.google.common.base.Strings; @@ -304,19 +302,11 @@ private void registerInvokerForTarget(AnnotatedElementInvoker elem, TypeHandle t } private void registerFactoryForTarget(AnnotatedElementInvoker elem, TypeHandle target) { - EquivalencyResult equivalency = TypeUtils.isEquivalentType(this.ap.getProcessingEnvironment(), elem.getReturnType(), target.getTypeMirror()); - if (equivalency.type != Equivalency.EQUIVALENT) { - if (equivalency.type == Equivalency.EQUIVALENT_BUT_RAW && equivalency.rawType == 1) { - elem.printMessage(this.ap, MessageType.INVOKER_RAW_RETURN_TYPE, "Raw return type for Factory @Invoker", SuppressedBy.RAW_TYPES); - } else if (equivalency.type == Equivalency.BOUNDS_MISMATCH) { - elem.printMessage(this.ap, MessageType.FACTORY_INVOKER_GENERIC_ARGS, "Invalid Factory @Invoker return type, generic type args of " - + target.getTypeMirror() + " are incompatible with " + elem.getReturnType() + ". " + equivalency); - return; - } else { - elem.printMessage(this.ap, MessageType.FACTORY_INVOKER_RETURN_TYPE, "Invalid Factory @Invoker return type, expected " - + target.getTypeMirror() + " but found " + elem.getReturnType()); - return; - } + String returnType = TypeUtils.getTypeName(elem.getReturnType()); + if (!returnType.equals(target.toString())) { + elem.printMessage(this.ap, MessageType.FACTORY_INVOKER_RETURN_TYPE, "Invalid Factory @Invoker return type, expected " + + target + " but found " + returnType); + return; } if (!elem.isStatic()) { elem.printMessage(this.ap, MessageType.FACTORY_INVOKER_NONSTATIC, "Factory @Invoker must be static"); From d633516a277b7704be6f5e2babf429d75b403814 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sat, 12 Aug 2023 17:42:00 +0100 Subject: [PATCH 10/84] Bump AP to Java 8. --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index c73873f28..756b26992 100644 --- a/build.gradle +++ b/build.gradle @@ -94,7 +94,7 @@ sourceSets { ap { compileClasspath += main.output ext.languageVersion = 8 - ext.compatibility = '1.6' + ext.compatibility = '1.8' } fernflower { compileClasspath += main.output From 1edd5f10cbb624058b968509c6a88e5fd971d4f8 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sat, 12 Aug 2023 17:54:26 +0100 Subject: [PATCH 11/84] Fix NPE when resolving fields in `TypeHandleASM`. Closes https://github.com/SpongePowered/Mixin/issues/560. --- .../org/spongepowered/tools/obfuscation/mirror/TypeUtils.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeUtils.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeUtils.java index 47242bbd3..709a6eaff 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeUtils.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeUtils.java @@ -275,6 +275,9 @@ public static String getJavaSignature(Element element) { * @return java signature */ public static String getJavaSignature(String descriptor) { + if (!descriptor.contains("(")) { + return SignaturePrinter.getTypeName(org.objectweb.asm.Type.getType(descriptor), false, true); + } return new SignaturePrinter("", descriptor).setFullyQualified(true).toDescriptor(); } From a40c96a63f4d000fa518e65ae7f7093c06c4228d Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Sun, 7 Jan 2024 15:50:40 +0100 Subject: [PATCH 12/84] Rely on the ML-Specification version present in the ModLauncher environment rather than on the Package specification version, since that version is unavailable in two scenarios: 1) ML loaded as a JPMS module and 2) ML added to the classpath using a folder rather than JAR-file. --- .../modlauncher/MixinServiceModLauncher.java | 37 ++++++++++++++----- 1 file changed, 28 insertions(+), 9 deletions(-) diff --git a/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java b/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java index 4f95c01c2..ac8ed2ac8 100644 --- a/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java +++ b/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java @@ -27,7 +27,10 @@ import java.io.InputStream; import java.lang.reflect.Constructor; import java.util.Collection; +import java.util.Optional; +import cpw.mods.modlauncher.api.IEnvironment; +import cpw.mods.modlauncher.api.TypesafeMap; import org.spongepowered.asm.launch.IClassProcessor; import org.spongepowered.asm.launch.platform.container.ContainerHandleModLauncher; import org.spongepowered.asm.logging.ILogger; @@ -47,6 +50,7 @@ import cpw.mods.modlauncher.Launcher; import cpw.mods.modlauncher.api.ITransformationService; +import org.spongepowered.asm.util.VersionNumber; /** * Mixin service for ModLauncher @@ -56,7 +60,7 @@ public class MixinServiceModLauncher extends MixinServiceAbstract { /** * Specification version to check for at startup */ - private static final String MODLAUNCHER_4_SPECIFICATION_VERSION = "4.0"; + private static final VersionNumber MODLAUNCHER_4_SPECIFICATION_VERSION = VersionNumber.parse("4.0"); /** * Specification version for ModLauncher versions >= 9.0.4, yes this is @@ -65,8 +69,8 @@ public class MixinServiceModLauncher extends MixinServiceAbstract { * version 5.0 for example, and ML7 and ML8 both had specification version * 7.0). */ - private static final String MODLAUNCHER_9_SPECIFICATION_VERSION = "8.0"; - + private static final VersionNumber MODLAUNCHER_9_SPECIFICATION_VERSION = VersionNumber.parse("8.0"); + private static final String CONTAINER_PACKAGE = MixinServiceAbstract.LAUNCH_PACKAGE + "platform.container."; private static final String MODLAUNCHER_4_ROOT_CONTAINER_CLASS = MixinServiceModLauncher.CONTAINER_PACKAGE + "ContainerHandleModLauncher"; private static final String MODLAUNCHER_9_ROOT_CONTAINER_CLASS = MixinServiceModLauncher.CONTAINER_PACKAGE + "ContainerHandleModLauncherEx"; @@ -117,15 +121,15 @@ public class MixinServiceModLauncher extends MixinServiceAbstract { private CompatibilityLevel minCompatibilityLevel = CompatibilityLevel.JAVA_8; public MixinServiceModLauncher() { - final Package pkg = ITransformationService.class.getPackage(); - if (pkg.isCompatibleWith(MixinServiceModLauncher.MODLAUNCHER_9_SPECIFICATION_VERSION)) { + VersionNumber apiVersion = getModLauncherApiVersion(); + if (apiVersion.compareTo(MODLAUNCHER_9_SPECIFICATION_VERSION) >= 0) { this.createRootContainer(MixinServiceModLauncher.MODLAUNCHER_9_ROOT_CONTAINER_CLASS); this.minCompatibilityLevel = CompatibilityLevel.JAVA_16; } else { this.createRootContainer(MixinServiceModLauncher.MODLAUNCHER_4_ROOT_CONTAINER_CLASS); } } - + /** * Begin init * @@ -200,9 +204,8 @@ protected ILogger createLogger(String name) { @Override public boolean isValid() { try { - Launcher.INSTANCE.hashCode(); - final Package pkg = ITransformationService.class.getPackage(); - if (!pkg.isCompatibleWith(MixinServiceModLauncher.MODLAUNCHER_4_SPECIFICATION_VERSION)) { + VersionNumber apiVersion = getModLauncherApiVersion(); + if (apiVersion.compareTo(MixinServiceModLauncher.MODLAUNCHER_4_SPECIFICATION_VERSION) < 0) { return false; } } catch (Throwable th) { @@ -311,4 +314,20 @@ public Collection getProcessors() { ); } + private static VersionNumber getModLauncherApiVersion() { + Optional version = Optional.empty(); + try { + TypesafeMap.Key versionProperty = IEnvironment.Keys.MLSPEC_VERSION.get(); + version = Launcher.INSTANCE.environment().getProperty(versionProperty); + } catch (Exception ignored) { + } + + // Fall back to the package information (this is not present when loaded as a module) + if (!version.isPresent()) { + version = Optional.ofNullable(ITransformationService.class.getPackage().getSpecificationVersion()); + } + + return version.map(VersionNumber::parse).orElse(VersionNumber.NONE); + } + } From 9e8af15ea250182a4f68f615f3ecd94ce6d77dde Mon Sep 17 00:00:00 2001 From: Sebastian Hartte Date: Mon, 8 Jan 2024 22:34:53 +0100 Subject: [PATCH 13/84] Style fixes --- .../modlauncher/MixinServiceModLauncher.java | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java b/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java index ac8ed2ac8..fa54c5b1d 100644 --- a/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java +++ b/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java @@ -29,8 +29,6 @@ import java.util.Collection; import java.util.Optional; -import cpw.mods.modlauncher.api.IEnvironment; -import cpw.mods.modlauncher.api.TypesafeMap; import org.spongepowered.asm.launch.IClassProcessor; import org.spongepowered.asm.launch.platform.container.ContainerHandleModLauncher; import org.spongepowered.asm.logging.ILogger; @@ -49,7 +47,9 @@ import com.google.common.collect.ImmutableList; import cpw.mods.modlauncher.Launcher; +import cpw.mods.modlauncher.api.IEnvironment; import cpw.mods.modlauncher.api.ITransformationService; +import cpw.mods.modlauncher.api.TypesafeMap; import org.spongepowered.asm.util.VersionNumber; /** @@ -121,7 +121,7 @@ public class MixinServiceModLauncher extends MixinServiceAbstract { private CompatibilityLevel minCompatibilityLevel = CompatibilityLevel.JAVA_8; public MixinServiceModLauncher() { - VersionNumber apiVersion = getModLauncherApiVersion(); + VersionNumber apiVersion = MixinServiceModLauncher.getModLauncherApiVersion(); if (apiVersion.compareTo(MODLAUNCHER_9_SPECIFICATION_VERSION) >= 0) { this.createRootContainer(MixinServiceModLauncher.MODLAUNCHER_9_ROOT_CONTAINER_CLASS); this.minCompatibilityLevel = CompatibilityLevel.JAVA_16; @@ -204,7 +204,7 @@ protected ILogger createLogger(String name) { @Override public boolean isValid() { try { - VersionNumber apiVersion = getModLauncherApiVersion(); + VersionNumber apiVersion = MixinServiceModLauncher.getModLauncherApiVersion(); if (apiVersion.compareTo(MixinServiceModLauncher.MODLAUNCHER_4_SPECIFICATION_VERSION) < 0) { return false; } @@ -315,12 +315,8 @@ public Collection getProcessors() { } private static VersionNumber getModLauncherApiVersion() { - Optional version = Optional.empty(); - try { - TypesafeMap.Key versionProperty = IEnvironment.Keys.MLSPEC_VERSION.get(); - version = Launcher.INSTANCE.environment().getProperty(versionProperty); - } catch (Exception ignored) { - } + TypesafeMap.Key versionProperty = IEnvironment.Keys.MLSPEC_VERSION.get(); + Optional version = Launcher.INSTANCE.environment().getProperty(versionProperty); // Fall back to the package information (this is not present when loaded as a module) if (!version.isPresent()) { From e02f4cae3fe51d6bd83de5e020956587ead8087c Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sun, 21 Jan 2024 18:25:47 +0000 Subject: [PATCH 14/84] Refactoring cherry-pick from 0.8.6-dev for target selectors --- .../tools/obfuscation/AnnotatedMixin.java | 5 + .../AnnotatedMixinElementHandler.java | 5 + .../AnnotatedMixinElementHandlerInjector.java | 10 +- .../asm/mixin/gen/AccessorInfo.java | 3 +- .../asm/mixin/gen/InvokerInfo.java | 2 +- .../asm/mixin/injection/Desc.java | 2 +- .../asm/mixin/injection/InjectionPoint.java | 42 +- .../asm/mixin/injection/Next.java | 86 ++++ .../asm/mixin/injection/Slice.java | 14 +- .../injection/callback/CallbackInjector.java | 9 +- .../asm/mixin/injection/code/Injector.java | 40 +- .../mixin/injection/code/InjectorTarget.java | 38 +- .../asm/mixin/injection/code/MethodSlice.java | 50 ++- .../mixin/injection/code/MethodSlices.java | 10 + .../injection/invoke/RedirectInjector.java | 7 +- .../invoke/arg/ArgsClassGenerator.java | 4 +- .../injection/points/BeforeConstant.java | 2 +- .../injection/selectors/ISelectorContext.java | 6 + .../injection/selectors/TargetSelectors.java | 375 ++++++++++++++++++ .../selectors/dynamic/DescriptorResolver.java | 25 +- .../dynamic/DynamicSelectorDesc.java | 26 +- .../dynamic/IResolvedDescriptor.java | 6 + .../mixin/injection/struct/InjectionInfo.java | 327 +++++---------- .../injection/struct/InjectionPointData.java | 18 +- .../struct/ModifyConstantInjectionInfo.java | 16 +- .../struct/SelectorAnnotationContext.java | 9 + .../asm/mixin/injection/struct/Target.java | 2 +- .../asm/mixin/refmap/IMixinContext.java | 7 + .../asm/mixin/struct/AnnotatedMethodInfo.java | 80 ++++ .../asm/mixin/struct/SpecialMethodInfo.java | 16 +- .../mixin/transformer/MixinCoprocessor.java | 4 +- .../mixin/transformer/MixinTargetContext.java | 8 + 32 files changed, 898 insertions(+), 356 deletions(-) create mode 100644 src/main/java/org/spongepowered/asm/mixin/injection/Next.java create mode 100644 src/main/java/org/spongepowered/asm/mixin/injection/selectors/TargetSelectors.java diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java index f3b918a80..5e85e4212 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixin.java @@ -467,6 +467,11 @@ public ReferenceMapper getReferenceMapper() { public String getClassName() { return this.getClassRef().replace('/', '.'); } + + @Override + public String getTargetClassName() { + return this.primaryTarget.toString(); + } @Override public String getTargetClassRef() { diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandler.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandler.java index 2e7c7415f..375af9e00 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandler.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandler.java @@ -165,6 +165,11 @@ public String remap(String reference) { return reference; } + @Override + public String getElementDescription() { + return String.format("%s annotation on %s", this.getAnnotation(), this); + } + @Override public String toString() { return TypeUtils.getName(this.element); diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerInjector.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerInjector.java index fb6b96239..ebea76218 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerInjector.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerInjector.java @@ -257,13 +257,13 @@ private boolean registerInjector(AnnotatedElementInjector elem, String reference } } - IReferenceManager refMap = this.obf.getReferenceManager(); + IReferenceManager refMaps = this.obf.getReferenceManager(); try { // If the original owner is unspecified, and the mixin is multi-target, we strip the owner from the obf mappings if ((targetMember.getOwner() == null && this.mixin.isMultiTarget()) || target.isSimulated()) { obfData = AnnotatedMixinElementHandler.stripOwnerData(obfData); } - refMap.addMethodMapping(this.classRef, reference, obfData); + refMaps.addMethodMapping(this.classRef, reference, obfData); } catch (ReferenceConflictException ex) { String conflictType = this.mixin.isMultiTarget() ? "Multi-target" : "Target"; @@ -274,9 +274,9 @@ private boolean registerInjector(AnnotatedElementInjector elem, String reference String newName = newMember instanceof ITargetSelectorByName ? ((ITargetSelectorByName)newMember).getName() : newMember.toString(); if (oldName != null && oldName.equals(newName)) { obfData = AnnotatedMixinElementHandler.stripDescriptors(obfData); - refMap.setAllowConflicts(true); - refMap.addMethodMapping(this.classRef, reference, obfData); - refMap.setAllowConflicts(false); + refMaps.setAllowConflicts(true); + refMaps.addMethodMapping(this.classRef, reference, obfData); + refMaps.setAllowConflicts(false); // This is bad because in notch mappings, using the bare target name might cause everything to explode elem.printMessage(this.ap, MessageType.BARE_REFERENCE, "Coerced " + conflictType + " reference has conflicting descriptors for " diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorInfo.java b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorInfo.java index 14cd8fb2e..68bc81be5 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorInfo.java @@ -515,7 +515,8 @@ protected TNode findTarget(List> nodes) { try { return result.getSingleResult(true); } catch (IllegalStateException ex) { - throw new InvalidAccessorException(this, ex.getMessage() + " matching " + this.target + " in " + this.classNode.name + " for " + this); + throw new InvalidAccessorException(this, String.format("%s matching %s in %s for %s", + ex.getMessage(), this.target, this.classNode.name, this)); } } diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/InvokerInfo.java b/src/main/java/org/spongepowered/asm/mixin/gen/InvokerInfo.java index b65d0c15c..ef6ccabe9 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/InvokerInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/InvokerInfo.java @@ -109,7 +109,7 @@ private MethodNode findTargetMethod() { try { return result.getSingleResult(true); } catch (IllegalStateException ex) { - String message = ex.getMessage() + " matching " + this.target + " in " + this.classNode.name + " for " + this; + String message = String.format("%s matching %s in %s for %s", ex.getMessage(), this.target, this.classNode.name, this); if (this.type == AccessorType.METHOD_PROXY && this.specifiedName != null && this.target instanceof ITargetSelectorByName) { String name = ((ITargetSelectorByName)this.target).getName(); if (name != null && (name.contains(".") || name.contains("/"))) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Desc.java b/src/main/java/org/spongepowered/asm/mixin/injection/Desc.java index 97ec046b9..b30f45bd7 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/Desc.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Desc.java @@ -115,7 +115,7 @@ * The next elements of this descriptor path, evaluated in order for each * recurse point. */ - // public Next[] next() default { }; + public Next[] next() default { }; /** * The minimum number of times this selector should match. By default the diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java index 141740a37..725ba6c7b 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java @@ -162,15 +162,19 @@ public abstract class InjectionPoint { } /** - * Selector type for slice delmiters, ignored for normal injection points. - * Selectors can be supplied in {@link At} annotations by including - * a colon (:) character followed by the selector type - * (case-sensitive), eg: + * Additional specifier for injection points. Specifiers can be + * supplied in {@link At} annotations by including a colon (:) + * character followed by the specifier type (case-sensitive), eg: * *
@At(value = "INVOKE:LAST", ... )
*/ - public enum Selector { - + public enum Specifier { + + /** + * Use all instructions from the query result. + */ + ALL, + /** * Use the first instruction from the query result. */ @@ -186,13 +190,13 @@ public enum Selector { * more than one instruction this should be considered a fail-fast error * state and a runtime exception will be thrown. */ - ONE; + ONE, /** - * Default selector type used if no selector is explicitly specified. - * For internal use only. Currently {@link #FIRST} + * Use the default setting as defined by the consumer. For slices this + * is {@link #FIRST}, for all other consumers this is {@link #ALL} */ - public static final Selector DEFAULT = Selector.FIRST; + DEFAULT; } @@ -276,26 +280,26 @@ enum ShiftByViolationBehaviour { } private final String slice; - private final Selector selector; + private final Specifier specifier; private final String id; private final IMessageSink messageSink; protected InjectionPoint() { - this("", Selector.DEFAULT, null); + this("", Specifier.DEFAULT, null); } protected InjectionPoint(InjectionPointData data) { - this(data.getSlice(), data.getSelector(), data.getId(), data.getMessageSink()); + this(data.getSlice(), data.getSpecifier(), data.getId(), data.getMessageSink()); } - public InjectionPoint(String slice, Selector selector, String id) { - this(slice, selector, id, null); + public InjectionPoint(String slice, Specifier specifier, String id) { + this(slice, specifier, id, null); } - public InjectionPoint(String slice, Selector selector, String id, IMessageSink messageSink) { + public InjectionPoint(String slice, Specifier specifier, String id, IMessageSink messageSink) { this.slice = slice; - this.selector = selector; + this.specifier = specifier; this.id = id; this.messageSink = messageSink; } @@ -304,8 +308,8 @@ public String getSlice() { return this.slice; } - public Selector getSelector() { - return this.selector; + public Specifier getSpecifier(Specifier defaultSpecifier) { + return this.specifier == Specifier.DEFAULT ? defaultSpecifier : this.specifier; } public String getId() { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Next.java b/src/main/java/org/spongepowered/asm/mixin/injection/Next.java new file mode 100644 index 000000000..c16b96af0 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Next.java @@ -0,0 +1,86 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.injection; + +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +/** + * See {@link Desc#next @Desc.next} + */ +@Target({ }) +@Retention(RetentionPolicy.RUNTIME) +public @interface Next { + + /** + * The name of the member to match. Optional. If present is matched + * case-sensitively against targets. + */ + public String name() default ""; + + /** + * The return type of a method to match, or the declared type of a field. + * Defaults to void. + */ + public Class ret() default void.class; + + /** + * The argument types of a method to match, ignored for fields. Note that + * failing to specify this value matches a target with no arguments. + */ + public Class[] args() default { }; + + /** + * The minimum number of times this selector should match. By default the + * selector is allowed to match no targets. When selecting fields or methods + * setting this value to anything other than 0 or 1 is pointless since the + * member can either be present or absent. However when matching method + * calls or field accesses (eg. when using this value inside + * {@link At#desc @At.desc}) this allows a minimum number of matches to + * be specified in order to provide early validation that the selector + * matched the correct number of candidates. To specify an exact number of + * matches, set {@link #max max} to the same value as min. Values + * less than zero are ignored since selectors cannot match a negative number + * of times. + */ + public int min() default 0; + + /** + * The maximum number of times this selector can match. By default the + * selector is allowed to match an unlimited number of targets. When + * selecting fields or methods, setting this value to anything other than 1 + * is pointless since the member can either be present or absent. However + * when matching method calls or field accesses (eg. when + * using this value inside {@link At#desc @At.desc}) this allows a + * maximum number of matches to be specified in order to limit the number of + * times that the selector can match candidates. To specify an exact number + * of matches, set max to the same value as {@link #min min}. + * Values less than 1 are treated as Integer.MAX_VALUE (the + * default) since setting a value of 0 or less has no meaning. + */ + public int max() default Integer.MAX_VALUE; + +} diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java b/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java index 857432dbf..68689107d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java @@ -28,7 +28,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; /** * A Slice identifies a section of a method to search for injection @@ -162,10 +162,10 @@ /** * Injection point which specifies the start of the slice region. - * {@link At}s supplied here should generally specify a {@link Selector} + * {@link At}s supplied here should generally use a {@link Specifier} * in order to identify which instruction should be used for queries which - * return multiple results. The selector is specified by appending the - * selector type to the injection point type as follows: + * return multiple results. The specifier is supplied by appending the + * specifier type to the injection point type as follows: * *
@At(value = "INVOKE:LAST", ... )
* @@ -182,9 +182,9 @@ /** * Injection point which specifies the end of the slice region. * Like {@link #from}, {@link At}s supplied here should generally specify a - * {@link Selector} in order to identify which instruction should be used - * for queries which return multiple results. The selector is specified by - * appending the selector type to the injection point type as follows: + * {@link Specifier} in order to identify which instruction should be used + * for queries which return multiple results. The specifier is supplied by + * appending the specifier type to the injection point type as follows: * *
@At(value = "INVOKE:LAST", ... )
* diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java index 55e3021fc..3c6b5dd47 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java @@ -38,6 +38,7 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.Surrogate; import org.spongepowered.asm.mixin.injection.code.Injector; +import org.spongepowered.asm.mixin.injection.code.InjectorTarget; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.Target; @@ -393,13 +394,13 @@ protected void sanityCheck(Target target, List injectionPoints) * org.objectweb.asm.tree.AbstractInsnNode, java.util.Set) */ @Override - protected void addTargetNode(Target target, List myNodes, AbstractInsnNode node, Set nominators) { - InjectionNode injectionNode = target.addInjectionNode(node); + protected void addTargetNode(InjectorTarget injectorTarget, List myNodes, AbstractInsnNode node, Set nominators) { + InjectionNode injectionNode = injectorTarget.addInjectionNode(node); for (InjectionPoint ip : nominators) { try { - this.checkTargetForNode(target, injectionNode, ip.getTargetRestriction(this.info)); + this.checkTargetForNode(injectorTarget.getTarget(), injectionNode, ip.getTargetRestriction(this.info)); } catch (InvalidInjectionException ex) { throw new InvalidInjectionException(this.info, String.format("%s selector %s", ip, ex.getMessage())); } @@ -412,7 +413,7 @@ protected void addTargetNode(Target target, List myNodes, Abstrac String existingId = this.ids.get(Integer.valueOf(injectionNode.getId())); if (existingId != null && !existingId.equals(id)) { Injector.logger.warn("Conflicting id for {} insn in {}, found id {} on {}, previously defined as {}", Bytecode.getOpcodeName(node), - target.toString(), id, this.info, existingId); + injectorTarget.toString(), id, this.info, existingId); break; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java index e0f645c93..a53aa7c48 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java @@ -42,6 +42,7 @@ import org.spongepowered.asm.mixin.injection.Coerce; import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; import org.spongepowered.asm.mixin.injection.invoke.RedirectInjector; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; @@ -238,13 +239,13 @@ public final List find(InjectorTarget injectorTarget, List myNodes = new ArrayList(); for (TargetNode node : this.findTargetNodes(injectorTarget, injectionPoints)) { - this.addTargetNode(injectorTarget.getTarget(), myNodes, node.insn, node.nominators); + this.addTargetNode(injectorTarget, myNodes, node.insn, node.nominators); } return myNodes; } - protected void addTargetNode(Target target, List myNodes, AbstractInsnNode node, Set nominators) { - myNodes.add(target.addInjectionNode(node)); + protected void addTargetNode(InjectorTarget injectorTarget, List myNodes, AbstractInsnNode node, Set nominators) { + myNodes.add(injectorTarget.addInjectionNode(node)); } /** @@ -294,7 +295,7 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li IMixinContext mixin = this.info.getMixin(); MethodNode method = injectorTarget.getMethod(); Map targetNodes = new TreeMap(); - Collection nodes = new ArrayList(32); + List nodes = new ArrayList(32); for (InjectionPoint injectionPoint : injectionPoints) { nodes.clear(); @@ -307,15 +308,20 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li injectorTarget, injectorTarget.getMergedBy(), injectorTarget.getMergedPriority())); } - if (this.findTargetNodes(method, injectionPoint, injectorTarget, nodes)) { + if (!this.findTargetNodes(method, injectionPoint, injectorTarget, nodes)) { + continue; + } + + Specifier specifier = injectionPoint.getSpecifier(Specifier.ALL); + if (specifier == Specifier.ONE && nodes.size() != 1) { + throw new InvalidInjectionException(this.info, String.format("%s on %s has specifier :ONE but matched %d instructions", + injectionPoint, this, nodes.size())); + } else if (specifier != Specifier.ALL && nodes.size() > 1) { + AbstractInsnNode specified = nodes.get(specifier == Specifier.FIRST ? 0 : nodes.size() - 1); + this.addTargetNode(method, targetNodes, injectionPoint, specified); + } else { for (AbstractInsnNode insn : nodes) { - Integer key = method.instructions.indexOf(insn); - TargetNode targetNode = targetNodes.get(key); - if (targetNode == null) { - targetNode = new TargetNode(insn); - targetNodes.put(key, targetNode); - } - targetNode.nominators.add(injectionPoint); + this.addTargetNode(method, targetNodes, injectionPoint, insn); } } } @@ -323,6 +329,16 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li return targetNodes.values(); } + protected void addTargetNode(MethodNode method, Map targetNodes, InjectionPoint injectionPoint, AbstractInsnNode insn) { + Integer key = method.instructions.indexOf(insn); + TargetNode targetNode = targetNodes.get(key); + if (targetNode == null) { + targetNode = new TargetNode(insn); + targetNodes.put(key, targetNode); + } + targetNode.nominators.add(injectionPoint); + } + protected boolean findTargetNodes(MethodNode into, InjectionPoint injectionPoint, InjectorTarget injectorTarget, Collection nodes) { return injectionPoint.find(into.desc, injectorTarget.getSlice(injectionPoint), nodes); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/InjectorTarget.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/InjectorTarget.java index 9347f3a7d..f0e551661 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/InjectorTarget.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/InjectorTarget.java @@ -27,13 +27,15 @@ import java.util.HashMap; import java.util.Map; +import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.InsnList; import org.objectweb.asm.tree.MethodNode; import org.spongepowered.asm.mixin.extensibility.IMixinConfig; import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; +import org.spongepowered.asm.mixin.injection.selectors.TargetSelectors.SelectedMethod; import org.spongepowered.asm.mixin.injection.struct.Target; +import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; import org.spongepowered.asm.util.Annotations; @@ -59,9 +61,9 @@ public class InjectorTarget { private final Target target; /** - * Selector which selected this method + * Selected method identified by the target selector */ - private final ITargetSelector selector; + private final SelectedMethod selectedMethod; /** * Name of the mixin which merged the target, if any @@ -79,10 +81,10 @@ public class InjectorTarget { * @param context owner * @param target target */ - public InjectorTarget(ISliceContext context, Target target, ITargetSelector selector) { + public InjectorTarget(ISliceContext context, Target target, SelectedMethod selectedMethod) { this.context = context; this.target = target; - this.selector = selector; + this.selectedMethod = selectedMethod; AnnotationNode merged = Annotations.getVisible(target.method, MixinMerged.class); this.mergedBy = Annotations.getValue(merged, "mixin"); @@ -94,6 +96,28 @@ public String toString() { return this.target.toString(); } + /** + * Add an injection node to this target if it does not already exist, + * returns the existing node if it exists + * + * @param node Instruction node to add + * @return wrapper for the specified node + */ + public InjectionNode addInjectionNode(AbstractInsnNode node) { + return this.target.addInjectionNode(node); + } + + /** + * Get an injection node from this collection if it already exists, returns + * null if the node is not tracked + * + * @param node instruction node + * @return wrapper node or null if not tracked + */ + public InjectionNode getInjectionNode(AbstractInsnNode node) { + return this.target.getInjectionNode(node); + } + /** * Get the target reference */ @@ -111,8 +135,8 @@ public MethodNode getMethod() { /** * Get the selector which selected this target */ - public ITargetSelector getSelector() { - return this.selector; + public SelectedMethod getSelectedMethod() { + return this.selectedMethod; } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java index 79891e95f..58201c05b 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java @@ -37,7 +37,7 @@ import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.struct.InjectionPointAnnotationContext; import org.spongepowered.asm.mixin.injection.throwables.InjectionError; @@ -325,6 +325,11 @@ public int realIndexOf(AbstractInsnNode insn) { * Descriptive name of the slice, used in exceptions */ private final String name; + + /** + * Success counts for from and to injection points + */ + private int successCountFrom, successCountTo; /** * ctor @@ -361,8 +366,8 @@ public String getId() { */ public InsnListReadOnly getSlice(MethodNode method) { int max = method.instructions.size() - 1; - int start = this.find(method, this.from, 0, 0, this.name + "(from)"); - int end = this.find(method, this.to, max, start, this.name + "(to)"); + int start = this.find(method, this.from, 0, 0, "from"); + int end = this.find(method, this.to, max, start, "to"); if (start > end) { throw new InvalidSliceException(this.owner, String.format("%s is negative size. Range(%d -> %d)", this.describe(), start, end)); @@ -389,32 +394,53 @@ public InsnListReadOnly getSlice(MethodNode method) { * @param defaultValue Value to return if injection point is null (open * ended) * @param failValue Value to use if query fails - * @param description Description for error message + * @param argument The name of the argument ("from" or "to") for debug msgs * @return matching insn index */ - private int find(MethodNode method, InjectionPoint injectionPoint, int defaultValue, int failValue, String description) { + private int find(MethodNode method, InjectionPoint injectionPoint, int defaultValue, int failValue, String argument) { if (injectionPoint == null) { return defaultValue; } + String description = String.format("%s(%s)", this.name, argument); Deque nodes = new LinkedList(); InsnListReadOnly insns = new InsnListReadOnly(method.instructions); boolean result = injectionPoint.find(method.desc, insns, nodes); - Selector select = injectionPoint.getSelector(); - if (nodes.size() != 1 && select == Selector.ONE) { + Specifier specifier = injectionPoint.getSpecifier(Specifier.FIRST); + if (specifier == Specifier.ALL) { + throw new InvalidSliceException(this.owner, String.format("ALL is not a valid specifier for slice %s", this.describe(description))); + } + if (nodes.size() != 1 && specifier == Specifier.ONE) { throw new InvalidSliceException(this.owner, String.format("%s requires 1 result but found %d", this.describe(description), nodes.size())); } if (!result) { - if (this.owner.getMixin().getOption(Option.DEBUG_VERBOSE)) { - MethodSlice.logger.warn("{} did not match any instructions", this.describe(description)); - } return failValue; } - return method.instructions.indexOf(select == Selector.FIRST ? nodes.getFirst() : nodes.getLast()); + if ("from".equals(argument)) { + this.successCountFrom++; + } else { + this.successCountTo++; + } + + return method.instructions.indexOf(specifier == Specifier.FIRST ? nodes.getFirst() : nodes.getLast()); } - + + /** + * Perform post-injection debugging and validation tasks + */ + public void postInject() { + if (this.owner.getMixin().getOption(Option.DEBUG_VERBOSE)) { + if (this.from != null && this.successCountFrom == 0) { + MethodSlice.logger.warn("{} did not match any instructions", this.describe(this.name + "(from)")); + } + if (this.to != null && this.successCountTo == 0) { + MethodSlice.logger.warn("{} did not match any instructions", this.describe(this.name + "(to)")); + } + } + } + /* (non-Javadoc) * @see java.lang.Object#toString() */ diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java index fb9979aa1..0789b7620 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java @@ -82,6 +82,16 @@ public MethodSlice get(String id) { return this.slices.get(id); } + /** + * Called to do post-injection validation/debug logging for slices + */ + public void postInject() { + for (MethodSlice slice : this.slices.values()) { + slice.postInject(); + } + } + + /* (non-Javadoc) * @see java.lang.Object#toString() */ diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java index 3a5b5fc7f..bf996cba0 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java @@ -37,6 +37,7 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.Redirect; import org.spongepowered.asm.mixin.injection.code.Injector; +import org.spongepowered.asm.mixin.injection.code.InjectorTarget; import org.spongepowered.asm.mixin.injection.points.BeforeFieldAccess; import org.spongepowered.asm.mixin.injection.points.BeforeNew; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; @@ -252,8 +253,8 @@ protected void checkTarget(Target target) { } @Override - protected void addTargetNode(Target target, List myNodes, AbstractInsnNode insn, Set nominators) { - InjectionNode node = target.getInjectionNode(insn); + protected void addTargetNode(InjectorTarget injectorTarget, List myNodes, AbstractInsnNode insn, Set nominators) { + InjectionNode node = injectorTarget.getInjectionNode(insn); ConstructorRedirectData ctorData = null; int fuzz = BeforeFieldAccess.ARRAY_SEARCH_FUZZ_DEFAULT; int opcode = 0; @@ -289,7 +290,7 @@ protected void addTargetNode(Target target, List myNodes, Abstrac } } - InjectionNode targetNode = target.addInjectionNode(insn); + InjectionNode targetNode = injectorTarget.addInjectionNode(insn); targetNode.decorate(Meta.KEY, this.meta); targetNode.decorate(RedirectInjector.KEY_NOMINATORS, nominators); if (insn instanceof TypeInsnNode && insn.getOpcode() == Opcodes.NEW) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/arg/ArgsClassGenerator.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/arg/ArgsClassGenerator.java index 05d47943a..8772344c1 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/arg/ArgsClassGenerator.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/arg/ArgsClassGenerator.java @@ -61,8 +61,10 @@ public final class ArgsClassGenerator implements IClassGenerator { public static final String ARGS_REF = ArgsClassGenerator.ARGS_NAME.replace('.', '/'); public static final String GETTER_PREFIX = "$"; + + public static final String SYNTHETIC_PACKAGE = Constants.SYNTHETIC_PACKAGE + ".args"; - private static final String CLASS_NAME_BASE = Constants.SYNTHETIC_PACKAGE + ".args.Args$"; + private static final String CLASS_NAME_BASE = ArgsClassGenerator.SYNTHETIC_PACKAGE + ".Args$"; private static final String OBJECT = "java/lang/Object"; private static final String OBJECT_ARRAY = "[L" + ArgsClassGenerator.OBJECT + ";"; diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java index e8030562e..bbad13edc 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java @@ -147,7 +147,7 @@ public class BeforeConstant extends InjectionPoint { private final boolean log; public BeforeConstant(IMixinContext context, AnnotationNode node, String returnType) { - super(Annotations.getValue(node, "slice", ""), Selector.DEFAULT, null); + super(Annotations.getValue(node, "slice", ""), Specifier.DEFAULT, null); Boolean empty = Annotations.getValue(node, "nullValue", (Boolean)null); this.ordinal = Annotations.getValue(node, "ordinal", Integer.valueOf(-1)); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ISelectorContext.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ISelectorContext.java index 763bfee43..78583e406 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ISelectorContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/ISelectorContext.java @@ -83,5 +83,11 @@ public interface ISelectorContext { * not return null! */ public abstract String remap(String reference); + + /** + * Get a human-readable description of the annotation on the method for use + * in error messages + */ + public abstract String getElementDescription(); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/TargetSelectors.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/TargetSelectors.java new file mode 100644 index 000000000..b46ee143e --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/TargetSelectors.java @@ -0,0 +1,375 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.injection.selectors; + +import java.util.ArrayList; +import java.util.Collection; +import java.util.Iterator; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.ListIterator; +import java.util.Set; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.AnnotationNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.MethodNode; +import org.spongepowered.asm.mixin.Final; +import org.spongepowered.asm.mixin.MixinEnvironment.Option; +import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector.Configure; +import org.spongepowered.asm.mixin.injection.selectors.TargetSelector.Result; +import org.spongepowered.asm.mixin.injection.selectors.throwables.SelectorConstraintException; +import org.spongepowered.asm.mixin.injection.struct.InvalidMemberDescriptorException; +import org.spongepowered.asm.mixin.injection.struct.TargetNotSupportedException; +import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException; +import org.spongepowered.asm.mixin.refmap.IMixinContext; +import org.spongepowered.asm.mixin.struct.AnnotatedMethodInfo; +import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; +import org.spongepowered.asm.util.Annotations; +import org.spongepowered.asm.util.Bytecode; + +public class TargetSelectors implements Iterable { + + /** + * Selected target method, paired with the selector which identified it + */ + public static class SelectedMethod { + + /** + * The parent target of this target. If this target is a lambda then + * this will be the selector for the enclosing method. Parent is null + * for the outermost method. + */ + private final SelectedMethod parent; + + /** + * The target selector which selected this target + */ + private final ITargetSelector selector; + + /** + * The selected target method + */ + private final MethodNode method; + + SelectedMethod(SelectedMethod parent, ITargetSelector selector, MethodNode method) { + this.parent = parent; + this.selector = selector; + this.method = method; + } + + SelectedMethod(ITargetSelector selector, MethodNode method) { + this(null, selector, method); + } + + @Override + public String toString() { + return this.method.name + this.method.desc; + } + + public SelectedMethod getParent() { + return this.parent; + } + + public ITargetSelector next() { + return this.selector.next(); + } + + public MethodNode getMethod() { + return this.method; + } + + } + + /** + * The selector context for these selectors, for example the injector which + * is running the selectors + */ + private final ISelectorContext context; + + /** + * The target class node within which targets can be resolved + */ + private final ClassNode targetClassNode; + + /** + * The mixin + */ + private final IMixinContext mixin; + + /** + * Annotated method, as MethodNode at runtime, or IAnnotatedElement during + * compile + */ + private final Object method; + + /** + * Whether the annotated method is static + */ + private final boolean isStatic; + + /** + * Root selectors + */ + private final Set selectors = new LinkedHashSet(); + + /** + * Selected targets + */ + private final List targets = new ArrayList(); + + private boolean doPermissivePass; + + public TargetSelectors(ISelectorContext context, ClassNode classNode) { + this.context = context; + this.targetClassNode = classNode; + this.mixin = context.getMixin(); + this.method = context.getMethod(); + this.isStatic = this.method instanceof MethodNode && Bytecode.isStatic((MethodNode)this.method); + } + + public void parse(Set selectors) { + // Validate and attach the parsed selectors + for (ITargetSelector selector : selectors) { + try { + this.addSelector(selector.validate().attach(this.context)); + } catch (InvalidMemberDescriptorException ex) { + throw new InvalidInjectionException(this.context, String.format("%s, has invalid target descriptor: %s. %s", + this.context.getElementDescription(), ex.getMessage(), this.mixin.getReferenceMapper().getStatus())); + } catch (TargetNotSupportedException ex) { + throw new InvalidInjectionException(this.context, String.format("%s specifies a target class '%s', which is not supported", + this.context.getElementDescription(), ex.getMessage())); + } catch (InvalidSelectorException ex) { + throw new InvalidInjectionException(this.context, String.format("%s is decorated with an invalid selector: %s", + this.context.getElementDescription(), ex.getMessage())); + } + } + } + + public TargetSelectors addSelector(ITargetSelector selector) { + this.selectors.add(selector); + return this; + } + + public int size() { + return this.targets.size(); + } + + public void clear() { + this.targets.clear(); + } + + @Override + public Iterator iterator() { + return this.targets.iterator(); + } + + public void remove(SelectedMethod target) { + this.targets.remove(target); + } + + public boolean isPermissivePassEnabled() { + return this.doPermissivePass; + } + + public TargetSelectors setPermissivePass(boolean enabled) { + this.doPermissivePass = enabled; + return this; + } + + /** + * Find methods in the target class which match the parsed selectors + */ + public void find() { + this.findRootTargets(); + // this.findNestedTargets(); + } + + /** + * Evaluate the root selectors parsed from this injector, find the root + * targets and store them in the {@link #targets} collection. + */ + private void findRootTargets() { + int passes = this.doPermissivePass ? 2 : 1; + + for (ITargetSelector selector : this.selectors) { + selector = selector.configure(Configure.SELECT_MEMBER); + + int matchCount = 0; + int maxCount = selector.getMaxMatchCount(); + + // Second pass ignores descriptor + ITargetSelector permissiveSelector = selector.configure(Configure.PERMISSIVE); + int selectorPasses = (permissiveSelector == selector) ? 1 : passes; + + scan: for (int pass = 0; pass < selectorPasses && matchCount < 1; pass++) { + ITargetSelector passSelector = pass == 0 ? selector : permissiveSelector; + for (MethodNode target : this.targetClassNode.methods) { + if (passSelector.match(ElementNode.of(this.targetClassNode, target)).isExactMatch()) { + matchCount++; + + boolean isMixinMethod = Annotations.getVisible(target, MixinMerged.class) != null; + if (maxCount <= 1 || ((this.isStatic || !Bytecode.isStatic(target)) && target != this.method && !isMixinMethod)) { + this.checkTarget(target); + this.targets.add(new SelectedMethod(passSelector, target)); + } + + if (matchCount >= maxCount) { + break scan; + } + } + } + } + + if (matchCount < selector.getMinMatchCount()) { + throw new InvalidInjectionException(this.context, new SelectorConstraintException(selector, String.format( + "Injection validation failed: %s for %s did not match the required number of targets (required=%d, matched=%d). %s%s", + selector, this.context.getElementDescription(), selector.getMinMatchCount(), matchCount, + this.mixin.getReferenceMapper().getStatus(), AnnotatedMethodInfo.getDynamicInfo(this.method)))); + } + } + } + + /** + * For each root target, resolve the nested targets from the target + * descriptor + */ + protected void findNestedTargets() { + boolean recursed = false; + do { + recursed = false; + for (ListIterator iter = this.targets.listIterator(); iter.hasNext();) { + SelectedMethod target = iter.next(); + ITargetSelector next = target.next(); + if (next == null) { + continue; + } + + recursed = true; + Result result = TargetSelector.run(next, ElementNode.dynamicInsnList(target.getMethod().instructions)); + iter.remove(); + for (ElementNode candidate : result.candidates) { + if (candidate.getInsn().getOpcode() != Opcodes.INVOKEDYNAMIC) { + continue; + } + + if (!candidate.getOwner().equals(this.mixin.getTargetClassRef())) { + throw new InvalidInjectionException(this.context, String.format( + "%s, failed to select into child. Cannot select foreign method: %s. %s", + this.context.getElementDescription(), candidate, this.mixin.getReferenceMapper().getStatus())); + } + + MethodNode method = this.findMethod(candidate); + if (method == null) { + throw new InvalidInjectionException(this.context, String.format( + "%s, failed to select into child. %s%s was not found in the target class.", + this.context.getElementDescription(), candidate.getName(), candidate.getDesc())); + } + + iter.add(new SelectedMethod(target, next, method)); + } + } + } + while (recursed); + } + + private void checkTarget(MethodNode target) { + AnnotationNode merged = Annotations.getVisible(target, MixinMerged.class); + if (merged == null) { + return; + } + + if (Annotations.getVisible(target, Final.class) != null) { + throw new InvalidInjectionException(this.context, String.format("%s cannot inject into @Final method %s::%s%s merged by %s", this, + this.mixin.getTargetClassName(), target.name, target.desc, Annotations.getValue(merged, "mixin"))); + } + } + + /** + * Finds a method in the target class + * + * @return Target method matching searchFor, or null if not found + */ + private MethodNode findMethod(ElementNode searchFor) { + for (MethodNode target : this.targetClassNode.methods) { + if (target.name.equals(searchFor.getSyntheticName()) && target.desc.equals(searchFor.getDesc())) { + return target; + } + } + return null; + } + + /** + * Post-search validation that some targets were found, we can fail-fast if + * no targets were actually identified or if the specified limits are + * exceeded. + * + * @param expectedCallbackCount Number of callbacks specified by expect + * @param requiredCallbackCount Number of callbacks specified by require + */ + public void validate(int expectedCallbackCount, int requiredCallbackCount) { + int targetCount = this.targets.size(); + if (targetCount > 0) { + return; + } + + if ((this.mixin.getOption(Option.DEBUG_INJECTORS) && expectedCallbackCount > 0)) { + throw new InvalidInjectionException(this.context, + String.format("Injection validation failed: %s could not find any targets matching %s in %s. %s%s", + this.context.getElementDescription(), TargetSelectors.namesOf(this.selectors), this.mixin.getTargetClassRef(), + this.mixin.getReferenceMapper().getStatus(), AnnotatedMethodInfo.getDynamicInfo(this.method))); + } else if (requiredCallbackCount > 0) { + throw new InvalidInjectionException(this.context, + String.format("Critical injection failure: %s could not find any targets matching %s in %s. %s%s", + this.context.getElementDescription(), TargetSelectors.namesOf(this.selectors), this.mixin.getTargetClassRef(), + this.mixin.getReferenceMapper().getStatus(), AnnotatedMethodInfo.getDynamicInfo(this.method))); + } + } + + /** + * Print the names of the specified members as a human-readable list + * + * @param selectors members to print + * @return human-readable list of member names + */ + private static String namesOf(Collection selectors) { + int index = 0, count = selectors.size(); + StringBuilder sb = new StringBuilder(); + for (ITargetSelector selector : selectors) { + if (index > 0) { + if (index == (count - 1)) { + sb.append(" or "); + } else { + sb.append(", "); + } + } + sb.append('\'').append(selector.toString()).append('\''); + index++; + } + return sb.toString(); + } + +} diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DescriptorResolver.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DescriptorResolver.java index 1a6f6f60a..49e4ea4e6 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DescriptorResolver.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DescriptorResolver.java @@ -73,11 +73,21 @@ static final class Descriptor implements IResolvedDescriptor { * Selector context */ private final ISelectorContext context; - + + /** + * True if this is a debug descriptor + */ + private final boolean debug; + Descriptor(Set searched, IAnnotationHandle desc, ISelectorContext context) { + this(searched, desc, context, false); + } + + Descriptor(Set searched, IAnnotationHandle desc, ISelectorContext context, boolean debug) { this.searched = searched; this.desc = desc; this.context = context; + this.debug = debug; } /** @@ -88,6 +98,15 @@ public boolean isResolved() { return this.desc != null; } + /** + * True if this is a debugging descriptor and shouldn't be treated as + * fully resolved + */ + @Override + public boolean isDebug() { + return this.debug; + } + /** * Get information about the resolution of this descriptor, for * inclusion in error messages when isResolved is false @@ -276,11 +295,13 @@ public static IResolvedDescriptor resolve(IAnnotationHandle desc, ISelectorConte * @return Resolution result */ public static IResolvedDescriptor resolve(String id, ISelectorContext context) { + boolean debug = false; IResolverObserver observer = new ResolverObserverBasic(); if (!Strings.isNullOrEmpty(id)) { if (DescriptorResolver.PRINT_ID.equals(id)) { observer = new ResolverObserverDebug(context); id = ""; + debug = true; } else { observer.visit(id, "", ""); } @@ -288,7 +309,7 @@ public static IResolvedDescriptor resolve(String id, ISelectorContext context) { IAnnotationHandle desc = DescriptorResolver.resolve(id, context, observer, context.getSelectorCoordinate(true)); observer.postResolve(); - return new Descriptor(observer.getSearched(), desc, context); + return new Descriptor(observer.getSearched(), desc, context, debug); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java index a13158505..f3df61903 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/DynamicSelectorDesc.java @@ -177,7 +177,7 @@ final class Next extends DynamicSelectorDesc { private final int index; Next(int index, IResolvedDescriptor next) { - super(null, null, next.getOwner(), next.getName(), next.getArgs(), next.getReturnType(), next.getMatches(), null); + super(null, null, next.getOwner(), next.getName(), next.getArgs(), next.getReturnType(), next.getMatches(), null, next.isDebug()); this.index = index; } @@ -239,25 +239,30 @@ public ITargetSelector next() { */ private final List next; + /** + * True if matching is disabled for this selector + */ + private final boolean disabled; + private DynamicSelectorDesc(IResolvedDescriptor desc) { this(null, desc.getId(), desc.getOwner(), desc.getName(), desc.getArgs(), desc.getReturnType(), desc.getMatches(), - desc.getNext()); + desc.getNext(), desc.isDebug()); } private DynamicSelectorDesc(DynamicSelectorDesc desc, Quantifier quantifier) { - this(desc.parseException, desc.id, desc.owner, desc.name, desc.args, desc.returnType, quantifier, desc.next); + this(desc.parseException, desc.id, desc.owner, desc.name, desc.args, desc.returnType, quantifier, desc.next, desc.disabled); } private DynamicSelectorDesc(DynamicSelectorDesc desc, Type owner) { - this(desc.parseException, desc.id, owner, desc.name, desc.args, desc.returnType, desc.matches, desc.next); + this(desc.parseException, desc.id, owner, desc.name, desc.args, desc.returnType, desc.matches, desc.next, desc.disabled); } private DynamicSelectorDesc(InvalidSelectorException ex) { - this(ex, null, null, null, null, null, Quantifier.NONE, null); + this(ex, null, null, null, null, null, Quantifier.NONE, null, true); } protected DynamicSelectorDesc(InvalidSelectorException ex, String id, Type owner, String name, Type[] args, Type returnType, Quantifier matches, - List then) { + List next, boolean disabled) { this.parseException = ex; this.id = id; @@ -267,7 +272,8 @@ protected DynamicSelectorDesc(InvalidSelectorException ex, String id, Type owner this.returnType = returnType; this.methodDesc = returnType != null ? Bytecode.getDescriptor(returnType, args) : null; this.matches = matches; - this.next = then; + this.next = next; + this.disabled = disabled; } /** @@ -281,7 +287,7 @@ protected DynamicSelectorDesc(InvalidSelectorException ex, String id, Type owner */ public static DynamicSelectorDesc parse(String input, ISelectorContext context) { IResolvedDescriptor descriptor = DescriptorResolver.resolve(input, context); - if (!descriptor.isResolved()) { + if (!descriptor.isResolved() && !descriptor.isDebug()) { String extra = input.length() == 0 ? ". " + descriptor.getResolutionInfo() : ""; return new DynamicSelectorDesc(new InvalidSelectorException("Could not resolve @Desc(" + input + ") for " + context + extra)); } @@ -297,7 +303,7 @@ public static DynamicSelectorDesc parse(String input, ISelectorContext context) */ public static DynamicSelectorDesc parse(IAnnotationHandle desc, ISelectorContext context) { IResolvedDescriptor descriptor = DescriptorResolver.resolve(desc, context); - if (!descriptor.isResolved()) { + if (!descriptor.isResolved() && !descriptor.isDebug()) { return new DynamicSelectorDesc(new InvalidSelectorException("Invalid descriptor")); } return DynamicSelectorDesc.of(descriptor); @@ -494,7 +500,7 @@ public MatchResult matches(String owner, String name, String desc) { */ @Override public MatchResult match(ElementNode node) { - if (node == null) { + if (node == null || this.disabled) { return MatchResult.NONE; } else if (node.isField()) { return this.matches(node.getOwner(), node.getName(), node.getDesc(), this.returnType.getInternalName()); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/IResolvedDescriptor.java b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/IResolvedDescriptor.java index 39a0860fe..d4dbc0bdf 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/IResolvedDescriptor.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/selectors/dynamic/IResolvedDescriptor.java @@ -40,6 +40,12 @@ public interface IResolvedDescriptor { * Get whether the descriptor was successfully resolved */ public abstract boolean isResolved(); + + /** + * Get whether the descriptor is for debugging and shouldn't be used as a + * live descriptor + */ + public abstract boolean isDebug(); /** * Get the resolved descriptor, or null if the descriptor was not resolved diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java index bb686fb55..a4ee28be1 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java @@ -31,7 +31,6 @@ import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.util.ArrayList; -import java.util.Collection; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; @@ -45,10 +44,9 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.MethodNode; -import org.spongepowered.asm.mixin.Dynamic; -import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; +import org.spongepowered.asm.mixin.extensibility.IActivityContext.IActivity; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.code.ISliceContext; @@ -56,22 +54,21 @@ import org.spongepowered.asm.mixin.injection.code.InjectorTarget; import org.spongepowered.asm.mixin.injection.code.MethodSlice; import org.spongepowered.asm.mixin.injection.code.MethodSlices; -import org.spongepowered.asm.mixin.injection.selectors.ElementNode; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; -import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector.Configure; -import org.spongepowered.asm.mixin.injection.selectors.InvalidSelectorException; import org.spongepowered.asm.mixin.injection.selectors.TargetSelector; -import org.spongepowered.asm.mixin.injection.selectors.throwables.SelectorConstraintException; +import org.spongepowered.asm.mixin.injection.selectors.TargetSelectors; +import org.spongepowered.asm.mixin.injection.selectors.TargetSelectors.SelectedMethod; import org.spongepowered.asm.mixin.injection.selectors.throwables.SelectorException; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.throwables.InjectionError; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException; import org.spongepowered.asm.mixin.refmap.IMixinContext; +import org.spongepowered.asm.mixin.struct.AnnotatedMethodInfo; import org.spongepowered.asm.mixin.struct.SpecialMethodInfo; import org.spongepowered.asm.mixin.throwables.MixinError; import org.spongepowered.asm.mixin.throwables.MixinException; +import org.spongepowered.asm.mixin.transformer.ActivityStack; import org.spongepowered.asm.mixin.transformer.MixinTargetContext; -import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; import org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException; import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.Bytecode; @@ -80,7 +77,6 @@ import org.spongepowered.asm.util.logging.MessageRouter; import com.google.common.base.Joiner; -import com.google.common.base.Strings; import com.google.common.collect.ImmutableSet; /** @@ -160,33 +156,6 @@ InjectionInfo create(MixinTargetContext mixin, MethodNode method, AnnotationNode } } - /** - * Selected target, paired with the selector which identified it - */ - static class SelectedTarget { - - private final ITargetSelector root; - - final ITargetSelector selector; - - final MethodNode method; - - SelectedTarget(ITargetSelector root, ITargetSelector selector, MethodNode method) { - this.root = root; - this.selector = selector; - this.method = method; - } - - SelectedTarget(ITargetSelector selector, MethodNode method) { - this(null, selector, method); - } - - ITargetSelector getRoot() { - return this.root != null ? this.root : this.selector; - } - - } - /** * Default conform prefix for handler methods */ @@ -213,6 +182,11 @@ ITargetSelector getRoot() { InjectionInfo.register(ModifyVariableInjectionInfo.class); // @ModifyVariable InjectionInfo.register(ModifyConstantInjectionInfo.class); // @ModifyConstant } + + /** + * Activity tracker + */ + protected final ActivityStack activities = new ActivityStack(null); /** * Annotated method is static @@ -220,14 +194,9 @@ ITargetSelector getRoot() { protected final boolean isStatic; /** - * Target selector(s) + * Targets */ - protected final Set selectors = new LinkedHashSet(); - - /** - * Target method(s) - */ - protected final List targets = new ArrayList(); + protected final TargetSelectors targets; /** * Method slice descriptors parsed from the annotation @@ -238,6 +207,8 @@ ITargetSelector getRoot() { * The key into the annotation which contains the injection points */ protected final String atKey; + + protected final List injectionPointAnnotations = new ArrayList(); /** * Injection points parsed from @@ -311,6 +282,7 @@ protected InjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationN protected InjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation, String atKey) { super(mixin, method, annotation); this.isStatic = Bytecode.isStatic(method); + this.targets = new TargetSelectors(this, mixin.getTargetClassNode()); this.slices = MethodSlices.parse(this); this.atKey = atKey; this.readAnnotation(); @@ -323,56 +295,44 @@ protected void readAnnotation() { if (this.annotation == null) { return; } - - List injectionPoints = this.readInjectionPoints(); - this.parseRequirements(); - this.parseSelectors(); - this.findTargets(); - this.parseInjectionPoints(injectionPoints); - this.injector = this.parseInjector(this.annotation); - } - protected void parseSelectors() { - Set selectors = new LinkedHashSet(); - TargetSelector.parse(Annotations.getValue(this.annotation, "method", false), this, selectors); - TargetSelector.parse(Annotations.getValue(this.annotation, "target", false), this, selectors); - - // Raise an error if we have no selectors - if (selectors.size() == 0) { - throw new InvalidInjectionException(this, String.format("%s annotation on %s is missing 'method' or 'target' to specify targets", - this.annotationType, this.methodName)); - } - - // Validate and attach the parsed selectors - for (ITargetSelector selector : selectors) { - try { - this.selectors.add(selector.validate().attach(this)); - } catch (InvalidMemberDescriptorException ex) { - throw new InvalidInjectionException(this, String.format("%s annotation on %s, has invalid target descriptor: %s. %s", - this.annotationType, this.methodName, ex.getMessage(), this.mixin.getReferenceMapper().getStatus())); - } catch (TargetNotSupportedException ex) { - throw new InvalidInjectionException(this, - String.format("%s annotation on %s specifies a target class '%s', which is not supported", - this.annotationType, this.methodName, ex.getMessage())); - } catch (InvalidSelectorException ex) { - throw new InvalidInjectionException(this, - String.format("%s annotation on %s is decorated with an invalid selector: %s", this.annotationType, this.methodName, - ex.getMessage())); - } - } - } - - protected List readInjectionPoints() { + this.activities.clear(); + try { + // When remapping refmap is enabled this implies we are in a development environment. In + // certain circumstances including the descriptor for the method may actually fail, so we + // will do a second "permissive" pass without the descriptor if this happens. + this.targets.setPermissivePass(this.mixin.getOption(Option.REFMAP_REMAP)); + + IActivity activity = this.activities.begin("Read Injection Points"); + this.readInjectionPoints(); + activity.next("Parse Requirements"); + this.parseRequirements(); + activity.next("Parse Selectors"); + this.parseSelectors(); + activity.next("Find Targets"); + this.targets.find(); + activity.next("Validate Targets"); + this.targets.validate(this.expectedCallbackCount, this.requiredCallbackCount); + activity.next("Parse Injection Points"); + this.parseInjectionPoints(); + activity.next("Parse Injector"); + this.injector = this.parseInjector(this.annotation); + activity.end(); + } catch (InvalidMixinException ex) { + ex.prepend(this.activities); + throw ex; + } catch (Exception ex) { + throw new InvalidMixinException(this.mixin, "Unexpected " + ex.getClass().getSimpleName() + " parsing " + + this.getElementDescription(), ex, this.activities); + } + } + + protected void readInjectionPoints() { List ats = Annotations.getValue(this.annotation, this.atKey, false); if (ats == null) { - throw new InvalidInjectionException(this, String.format("%s annotation on %s is missing '%s' value(s)", - this.annotationType, this.methodName, this.atKey)); + throw new InvalidInjectionException(this, String.format("%s is missing '%s' value(s)", this.getElementDescription(), this.atKey)); } - return ats; - } - - protected void parseInjectionPoints(List ats) { - this.injectionPoints.addAll(InjectionPoint.parse(this, ats)); + this.injectionPointAnnotations.addAll(ats); } protected void parseRequirements() { @@ -396,6 +356,24 @@ protected void parseRequirements() { } } + protected void parseSelectors() { + Set selectors = new LinkedHashSet(); + TargetSelector.parse(Annotations.getValue(this.annotation, "method", false), this, selectors); + TargetSelector.parse(Annotations.getValue(this.annotation, "target", false), this, selectors); + + // Raise an error if we have no selectors + if (selectors.size() == 0) { + throw new InvalidInjectionException(this, String.format("%s is missing 'method' or 'target' to specify targets", + this.getElementDescription())); + } + + this.targets.parse(selectors); + } + + protected void parseInjectionPoints() { + this.injectionPoints.addAll(InjectionPoint.parse(this, this.injectionPointAnnotations)); + } + // stub protected abstract Injector parseInjector(AnnotationNode injectAnnotation); @@ -413,18 +391,31 @@ public boolean isValid() { * Discover injection points */ public void prepare() { - this.targetNodes.clear(); - for (SelectedTarget targetMethod : this.targets) { - Target target = this.mixin.getTargetMethod(targetMethod.method); - InjectorTarget injectorTarget = new InjectorTarget(this, target, targetMethod.selector); - try { - this.targetNodes.put(target, this.injector.find(injectorTarget, this.injectionPoints)); - } catch (SelectorException ex) { - throw new InvalidInjectionException(this, String.format("Injection validation failed: %s on %s: %s. %s%s", - this.annotationType, this.methodName, ex.getMessage(), this.mixin.getReferenceMapper().getStatus(), this.getDynamicInfo())); - } finally { - injectorTarget.dispose(); + this.activities.clear(); + try { + this.targetNodes.clear(); + IActivity activity = this.activities.begin("?"); + for (SelectedMethod targetMethod : this.targets) { + activity.next("{ target: %s }", targetMethod); + Target target = this.mixin.getTargetMethod(targetMethod.getMethod()); + InjectorTarget injectorTarget = new InjectorTarget(this, target, targetMethod); + try { + this.targetNodes.put(target, this.injector.find(injectorTarget, this.injectionPoints)); + } catch (SelectorException ex) { + throw new InvalidInjectionException(this, String.format("Injection validation failed: %s: %s. %s%s", + this.getElementDescription(), ex.getMessage(), this.mixin.getReferenceMapper().getStatus(), + AnnotatedMethodInfo.getDynamicInfo(this.method))); + } finally { + injectorTarget.dispose(); + } } + activity.end(); + } catch (InvalidMixinException ex) { + ex.prepend(this.activities); + throw ex; + } catch (Exception ex) { + throw new InvalidMixinException(this.mixin, "Unexpecteded " + ex.getClass().getSimpleName() + " preparing " + + this.getElementDescription(), ex, this.activities); } } @@ -457,7 +448,7 @@ public void postInject() { String description = this.getDescription(); String refMapStatus = this.mixin.getReferenceMapper().getStatus(); - String extraInfo = this.getDynamicInfo() + this.getMessages(); + String extraInfo = AnnotatedMethodInfo.getDynamicInfo(this.method) + this.getMessages(); if ((this.mixin.getOption(Option.DEBUG_INJECTORS) && this.injectedCallbackCount < this.expectedCallbackCount)) { throw new InvalidInjectionException(this, String.format("Injection validation failed: %s %s%s in %s expected %d invocation(s) but %d succeeded. Scanned %d target(s). %s%s", @@ -473,6 +464,8 @@ public void postInject() { String.format("Critical injection failure: %s %s%s in %s failed injection check, %d succeeded of %d allowed.%s", description, this.methodName, this.method.desc, this.mixin, this.injectedCallbackCount, this.maxCallbackCount, extraInfo)); } + + this.slices.postInject(); } /** @@ -577,115 +570,6 @@ public void addMessage(String format, Object... args) { protected String getMessages() { return this.messages != null ? " Messages: { " + Joiner.on(" ").join(this.messages) + "}" : ""; } - - /** - * Find methods in the target class which match the parsed selectors - */ - protected void findTargets() { - this.targets.clear(); - this.findRootTargets(); - this.validateTargets(); - } - - /** - * Evaluate the root selectors parsed from this injector, find the root - * targets and store them in the {@link #targets} collection. - */ - private void findRootTargets() { - // When remapping refmap is enabled this implies we are in a development - // environment. In certain circumstances including the descriptor for - // the method may actually fail, so we will do a second pass without the - // descriptor if this happens. - int passes = this.mixin.getOption(Option.REFMAP_REMAP) ? 2 : 1; - - for (ITargetSelector selector : this.selectors) { - selector = selector.configure(Configure.SELECT_MEMBER); - - int matchCount = 0; - int maxCount = selector.getMaxMatchCount(); - - // Second pass ignores descriptor - ITargetSelector permissiveSelector = selector.configure(Configure.PERMISSIVE); - int selectorPasses = (permissiveSelector == selector) ? 1 : passes; - - scan: for (int pass = 0; pass < selectorPasses && matchCount < 1; pass++) { - ITargetSelector passSelector = pass == 0 ? selector : permissiveSelector; - for (MethodNode target : this.classNode.methods) { - if (passSelector.match(ElementNode.of(this.classNode, target)).isExactMatch()) { - matchCount++; - - boolean isMixinMethod = Annotations.getVisible(target, MixinMerged.class) != null; - if (maxCount <= 1 || ((this.isStatic || !Bytecode.isStatic(target)) && target != this.method && !isMixinMethod)) { - this.checkTarget(target); - this.targets.add(new SelectedTarget(passSelector, target)); - } - - if (matchCount >= maxCount) { - break scan; - } - } - } - } - - if (matchCount < selector.getMinMatchCount()) { - throw new InvalidInjectionException(this, new SelectorConstraintException(selector, String.format( - "Injection validation failed: %s for %s on %s did not match the required number of targets (required=%d, matched=%d). %s%s", - selector, this.annotationType, this.methodName, selector.getMinMatchCount(), matchCount, - this.mixin.getReferenceMapper().getStatus(), this.getDynamicInfo()))); - } - } - } - - /** - * Post-search validation that some targets were found, we can fail-fast if - * no targets were actually identified - */ - protected void validateTargets() { - this.targetCount = this.targets.size(); - if (this.targetCount > 0) { - return; - } - - if ((this.mixin.getOption(Option.DEBUG_INJECTORS) && this.expectedCallbackCount > 0)) { - throw new InvalidInjectionException(this, - String.format("Injection validation failed: %s annotation on %s could not find any targets matching %s in %s. %s%s", - this.annotationType, this.methodName, InjectionInfo.namesOf(this.selectors), this.mixin.getTarget(), - this.mixin.getReferenceMapper().getStatus(), this.getDynamicInfo())); - } else if (this.requiredCallbackCount > 0) { - throw new InvalidInjectionException(this, - String.format("Critical injection failure: %s annotation on %s could not find any targets matching %s in %s. %s%s", - this.annotationType, this.methodName, InjectionInfo.namesOf(this.selectors), this.mixin.getTarget(), - this.mixin.getReferenceMapper().getStatus(), this.getDynamicInfo())); - } - } - - protected void checkTarget(MethodNode target) { - AnnotationNode merged = Annotations.getVisible(target, MixinMerged.class); - if (merged == null) { - return; - } - - if (Annotations.getVisible(target, Final.class) != null) { - throw new InvalidInjectionException(this, String.format("%s cannot inject into @Final method %s::%s%s merged by %s", this, - this.classNode.name, target.name, target.desc, Annotations.getValue(merged, "mixin"))); - } - } - - /** - * Get info from a decorating {@link Dynamic} annotation. If the annotation - * is present, a descriptive string suitable for inclusion in an error - * message is returned. If the annotation is not present then an empty - * string is returned. - */ - protected String getDynamicInfo() { - AnnotationNode annotation = Annotations.getInvisible(this.method, Dynamic.class); - String description = Strings.nullToEmpty(Annotations.getValue(annotation)); - Type upstream = Annotations.getValue(annotation, "mixin"); - if (upstream != null) { - description = String.format("{%s} %s", upstream.getClassName(), description).trim(); - } - return description.length() > 0 ? String.format(" Method is @Dynamic(%s).", description) : ""; - } /** * Parse an injector from the specified method (if an injector annotation is @@ -756,29 +640,6 @@ static String describeInjector(IMixinContext mixin, AnnotationNode annotation, M return String.format("%s->@%s::%s%s", mixin.toString(), Annotations.getSimpleName(annotation), MethodNodeEx.getName(method), method.desc); } - /** - * Print the names of the specified members as a human-readable list - * - * @param selectors members to print - * @return human-readable list of member names - */ - private static String namesOf(Collection selectors) { - int index = 0, count = selectors.size(); - StringBuilder sb = new StringBuilder(); - for (ITargetSelector selector : selectors) { - if (index > 0) { - if (index == (count - 1)) { - sb.append(" or "); - } else { - sb.append(", "); - } - } - sb.append('\'').append(selector.toString()).append('\''); - index++; - } - return sb.toString(); - } - /** * Register an injector info class. The supplied class must be decorated * with an {@link AnnotationType} annotation for registration purposes. diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java index cb4290a94..b41cdd7ce 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java @@ -36,7 +36,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.IInjectionPointContext; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; import org.spongepowered.asm.mixin.injection.selectors.InvalidSelectorException; @@ -86,7 +86,7 @@ public class InjectionPointData { /** * Selector parsed from the at argument, only used by slices */ - private final Selector selector; + private final Specifier specifier; /** * Target @@ -131,7 +131,7 @@ public InjectionPointData(IInjectionPointContext context, String at, List args) { @@ -172,10 +172,10 @@ public String getType() { } /** - * Get the selector value parsed from the injector + * Get the specifier value parsed from the injector */ - public Selector getSelector() { - return this.selector; + public Specifier getSpecifier() { + return this.specifier; } /** @@ -362,7 +362,7 @@ public String toString() { } private static Pattern createPattern() { - return Pattern.compile(String.format("^(.+?)(:(%s))?$", Joiner.on('|').join(Selector.values()))); + return Pattern.compile(String.format("^(.+?)(:(%s))?$", Joiner.on('|').join(Specifier.values()))); } /** @@ -380,8 +380,8 @@ private static String parseType(Matcher matcher, String at) { return matcher.matches() ? matcher.group(1) : at; } - private static Selector parseSelector(Matcher matcher) { - return matcher.matches() && matcher.group(3) != null ? Selector.valueOf(matcher.group(3)) : Selector.DEFAULT; + private static Specifier parseSpecifier(Matcher matcher) { + return matcher.matches() && matcher.group(3) != null ? Specifier.valueOf(matcher.group(3)) : Specifier.DEFAULT; } private static int parseInt(String string, int defaultValue) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java index ad184f183..08f88124f 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java @@ -24,8 +24,6 @@ */ package org.spongepowered.asm.mixin.injection.struct; -import java.util.List; - import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.MethodNode; @@ -39,7 +37,6 @@ import org.spongepowered.asm.mixin.transformer.MixinTargetContext; import com.google.common.base.Strings; -import com.google.common.collect.ImmutableList; /** * Information about a constant modifier injector @@ -55,21 +52,20 @@ public ModifyConstantInjectionInfo(MixinTargetContext mixin, MethodNode method, } @Override - protected List readInjectionPoints() { - List ats = super.readInjectionPoints(); - if (ats.isEmpty()) { + protected void readInjectionPoints() { + super.readInjectionPoints(); + if (this.injectionPointAnnotations.isEmpty()) { AnnotationNode c = new AnnotationNode(ModifyConstantInjectionInfo.CONSTANT_ANNOTATION_CLASS); c.visit("log", Boolean.TRUE); - ats = ImmutableList.of(c); + this.injectionPointAnnotations.add(c); } - return ats; } @Override - protected void parseInjectionPoints(List ats) { + protected void parseInjectionPoints() { Type returnType = Type.getReturnType(this.method.desc); - for (AnnotationNode at : ats) { + for (AnnotationNode at : this.injectionPointAnnotations) { this.injectionPoints.add(new BeforeConstant(this.getMixin(), at, returnType.getDescriptor())); } } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/SelectorAnnotationContext.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/SelectorAnnotationContext.java index 605d0b087..76756c8d3 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/SelectorAnnotationContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/SelectorAnnotationContext.java @@ -127,5 +127,14 @@ public String getSelectorCoordinate(boolean leaf) { public String remap(String reference) { return this.parent.remap(reference); } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.injection.selectors.ISelectorContext + * #getElementDescription() + */ + @Override + public String getElementDescription() { + return String.format("%s in %s", this.selectorAnnotation, this.parent.getElementDescription()); + } } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index 971a8d595..0e042a92f 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -647,7 +647,7 @@ public MethodInsnNode findInitNodeFor(TypeInsnNode newNode) { */ public DelegateInitialiser findDelegateInitNode() { if (!this.isCtor) { - return null; + return DelegateInitialiser.NONE; } if (this.delegateInitialiser == null) { diff --git a/src/main/java/org/spongepowered/asm/mixin/refmap/IMixinContext.java b/src/main/java/org/spongepowered/asm/mixin/refmap/IMixinContext.java index 22ed77d16..0ab2e95e5 100644 --- a/src/main/java/org/spongepowered/asm/mixin/refmap/IMixinContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/refmap/IMixinContext.java @@ -56,6 +56,13 @@ public interface IMixinContext { * @return internal class name */ public abstract String getClassRef(); + + /** + * Get the name of the target class for this context + * + * @return target class name + */ + public abstract String getTargetClassName(); /** * Get the internal name of the target class for this context diff --git a/src/main/java/org/spongepowered/asm/mixin/struct/AnnotatedMethodInfo.java b/src/main/java/org/spongepowered/asm/mixin/struct/AnnotatedMethodInfo.java index 4fb3c7fdb..5ac54bb87 100644 --- a/src/main/java/org/spongepowered/asm/mixin/struct/AnnotatedMethodInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/struct/AnnotatedMethodInfo.java @@ -28,17 +28,23 @@ import javax.tools.Diagnostic.Kind; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.MethodNode; +import org.spongepowered.asm.mixin.Dynamic; import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.injection.IInjectionPointContext; import org.spongepowered.asm.mixin.injection.selectors.ISelectorContext; import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.mixin.refmap.IReferenceMapper; import org.spongepowered.asm.util.Annotations; +import org.spongepowered.asm.util.asm.IAnnotatedElement; import org.spongepowered.asm.util.asm.IAnnotationHandle; +import org.spongepowered.asm.util.asm.MethodNodeEx; import org.spongepowered.asm.util.logging.MessageRouter; +import com.google.common.base.Strings; + /** * Data bundle for an annotated method in a mixin */ @@ -59,12 +65,33 @@ public class AnnotatedMethodInfo implements IInjectionPointContext { */ protected final AnnotationNode annotation; + /** + * Human-readable annotation type + */ + protected final String annotationType; + + /** + * Original name of the method, if available + */ + protected final String methodName; + public AnnotatedMethodInfo(IMixinContext mixin, MethodNode method, AnnotationNode annotation) { this.context = mixin; this.method = method; this.annotation = annotation; + this.annotationType = this.annotation != null ? "@" + Annotations.getSimpleName(this.annotation) : "Undecorated method"; + this.methodName = MethodNodeEx.getName(method); } + /** + * Get a human-readable description of the annotation on the method for use + * in error messages + */ + @Override + public final String getElementDescription() { + return String.format("%s annotation on %s", this.annotationType, this.methodName); + } + @Override public String remap(String reference) { if (this.context != null) { @@ -160,4 +187,57 @@ public void addMessage(String format, Object... args) { } } + /** + * Get info from a decorating {@link Dynamic} annotation. If the annotation + * is present, a descriptive string suitable for inclusion in an error + * message is returned. If the annotation is not present then an empty + * string is returned. + * + * @param method method to inspect + */ + public static final String getDynamicInfo(Object method) { + if (method instanceof MethodNode) { + return AnnotatedMethodInfo.getDynamicInfo((MethodNode)method); + } else if (method instanceof IAnnotatedElement) { + return AnnotatedMethodInfo.getDynamicInfo((IAnnotatedElement)method); + } + return ""; + } + + /** + * Get info from a decorating {@link Dynamic} annotation. If the annotation + * is present, a descriptive string suitable for inclusion in an error + * message is returned. If the annotation is not present then an empty + * string is returned. + * + * @param method method to inspect + */ + public static final String getDynamicInfo(MethodNode method) { + return AnnotatedMethodInfo.getDynamicInfo(Annotations.handleOf(Annotations.getInvisible(method, Dynamic.class))); + } + + /** + * Get info from a decorating {@link Dynamic} annotation. If the annotation + * is present, a descriptive string suitable for inclusion in an error + * message is returned. If the annotation is not present then an empty + * string is returned. + * + * @param method method to inspect + */ + public static final String getDynamicInfo(IAnnotatedElement method) { + return AnnotatedMethodInfo.getDynamicInfo(method.getAnnotation(Dynamic.class)); + } + + private static String getDynamicInfo(IAnnotationHandle annotation) { + if (annotation == null) { + return ""; + } + String description = Strings.nullToEmpty(annotation.getValue()); + Type upstream = annotation.getTypeValue("mixin"); + if (upstream != null) { + description = String.format("{%s} %s", upstream.getClassName(), description).trim(); + } + return description.length() > 0 ? String.format(" Method is @Dynamic(%s).", description) : ""; + } + } diff --git a/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java b/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java index d509a8982..6dcac649e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java @@ -29,8 +29,6 @@ import org.objectweb.asm.tree.MethodNode; import org.spongepowered.asm.mixin.transformer.ClassInfo; import org.spongepowered.asm.mixin.transformer.MixinTargetContext; -import org.spongepowered.asm.util.Annotations; -import org.spongepowered.asm.util.asm.MethodNodeEx; /** * Information about a special mixin method such as an injector or accessor @@ -38,19 +36,9 @@ public class SpecialMethodInfo extends AnnotatedMethodInfo { /** - * Human-readable annotation type - */ - protected final String annotationType; - - /** - * Class + * Target class node */ protected final ClassNode classNode; - - /** - * Original name of the method, if available - */ - protected final String methodName; /** * Mixin data @@ -60,9 +48,7 @@ public class SpecialMethodInfo extends AnnotatedMethodInfo { public SpecialMethodInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { super(mixin, method, annotation); this.mixin = mixin; - this.annotationType = this.annotation != null ? "@" + Annotations.getSimpleName(this.annotation) : "Undecorated injector"; this.classNode = mixin.getTargetClassNode(); - this.methodName = MethodNodeEx.getName(method); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessor.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessor.java index 989264644..6f3116190 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessor.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessor.java @@ -156,8 +156,8 @@ ProcessResult process(String className, ClassNode classNode) { /** * Perform postprocessing actions on the supplied class. This is called for * all classes. For passthrough classes and classes which are not mixin - * targets this is called immediately after {@link process} is completed for - * all coprocessors. For mixin targets this is called after mixins are + * targets this is called immediately after {@link #process} is completed + * for all coprocessors. For mixin targets this is called after mixins are * applied. * * @param className Name of the target class diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java index 1f4a91add..c08ec0d1d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java @@ -319,6 +319,14 @@ public String getClassRef() { public TargetClassContext getTarget() { return this.targetClass; } + + /** + * Get the target class name + */ + @Override + public String getTargetClassName() { + return this.getTarget().getClassName(); + } /** * Get the target class reference From 5aa23b532ae340710e211b5a72cf8aed0cbce01a Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sun, 21 Jan 2024 18:27:34 +0000 Subject: [PATCH 15/84] Update to register VirtualJar for synthetic classes --- build.gradle | 6 +-- gradle.properties | 4 +- .../launch/MixinTransformationService.java | 41 +++++++++++++++++-- 3 files changed, 43 insertions(+), 8 deletions(-) diff --git a/build.gradle b/build.gradle index a0bbf8ed5..d3a9f17a3 100644 --- a/build.gradle +++ b/build.gradle @@ -63,8 +63,8 @@ repositories { } maven { // For modlauncher - name = 'forge' - url = 'https://files.minecraftforge.net/maven' + name = 'neoforged' + url = 'https://maven.neoforged.net/releases' } } @@ -205,7 +205,7 @@ dependencies { modlauncher9Implementation ("cpw.mods:modlauncher:$modlauncherVersion") { exclude module: 'jopt-simple' } - modlauncher9Implementation 'cpw.mods:securejarhandler:0.9.+' + modlauncher9Implementation 'cpw.mods:securejarhandler:2.1.24' // asm bridge bridgeImplementation 'org.apache.logging.log4j:log4j-core:2.0-beta9' diff --git a/gradle.properties b/gradle.properties index 0ac521a68..b31d93a8a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -8,6 +8,6 @@ buildVersion=0.8.6 buildType=SNAPSHOT asmVersion=9.2 legacyForgeAsmVersion=5.0.3 -modlauncherAsmVersion=9.1 -modlauncherVersion=9.0.7 +modlauncherAsmVersion=9.5 +modlauncherVersion=10.0.9 legacyModlauncherVersion=7.0.0 \ No newline at end of file diff --git a/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java b/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java index caa2cd8b5..76f7322c9 100644 --- a/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java +++ b/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java @@ -24,11 +24,46 @@ */ package org.spongepowered.asm.launch; +import java.net.URISyntaxException; +import java.nio.file.Path; +import java.util.List; + +import org.spongepowered.asm.mixin.injection.invoke.arg.ArgsClassGenerator; +import org.spongepowered.asm.service.MixinService; +import org.spongepowered.asm.util.Constants; + +import com.google.common.collect.ImmutableList; + +import cpw.mods.jarhandling.SecureJar; +import cpw.mods.jarhandling.VirtualJar; +import cpw.mods.modlauncher.api.IModuleLayerManager; + /** - * Service for handling transforms mixin under ModLauncher, now just a concrete - * class which extends the abstract base class used for pre-9 versions of - * ModLauncher + * Service for handling transforms mixin under ModLauncher, most of the + * functionality is provided by the abstract base class used for pre-9 versions + * of ModLauncher, though we also handle SecureJarHandler requirements here, for + * modlauncher 10+ */ public class MixinTransformationService extends MixinTransformationServiceAbstract { + + private static final String VIRTUAL_JAR_CLASS = "cpw.mods.jarhandling.VirtualJar"; + + @Override + public List completeScan(final IModuleLayerManager layerManager) { + try { + MixinService.getService().getClassProvider().findClass(MixinTransformationService.VIRTUAL_JAR_CLASS, false); + } catch (ClassNotFoundException ex) { + // VirtualJar not supported + return super.completeScan(layerManager); + } + + try { + Path codeSource = Path.of(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); + VirtualJar jar = new VirtualJar("mixin_synthetic", codeSource, Constants.SYNTHETIC_PACKAGE, ArgsClassGenerator.SYNTHETIC_PACKAGE); + return ImmutableList.of(new Resource(IModuleLayerManager.Layer.GAME, ImmutableList.of(jar))); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } } From 7a3cd42a6755e932965bcb041a502a0886a28342 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sun, 21 Jan 2024 18:28:03 +0000 Subject: [PATCH 16/84] Update ASM and add support for inject into constructor --- gradle.properties | 2 +- .../asm/mixin/MixinEnvironment.java | 169 ++++++++ .../spongepowered/asm/mixin/injection/At.java | 32 ++ .../asm/mixin/injection/InjectionPoint.java | 85 +++- .../injection/callback/CallbackInjector.java | 9 +- .../asm/mixin/injection/code/IInsnListEx.java | 98 +++++ .../asm/mixin/injection/code/Injector.java | 29 +- .../mixin/injection/code/InjectorTarget.java | 26 +- .../asm/mixin/injection/code/InsnListEx.java | 150 +++++++ .../injection/code/InsnListReadOnly.java | 5 +- .../asm/mixin/injection/code/MethodSlice.java | 35 +- .../injection/invoke/ModifyArgsInjector.java | 2 +- .../modify/ModifyVariableInjector.java | 10 +- .../injection/points/BeforeConstant.java | 6 +- .../injection/points/BeforeStringInvoke.java | 2 +- .../injection/points/ConstructorHead.java | 188 +++++++++ .../mixin/injection/points/MethodHead.java | 6 +- .../mixin/injection/struct/Constructor.java | 139 +++++++ .../injection/struct/InjectionPointData.java | 54 ++- .../asm/mixin/injection/struct/Target.java | 129 ++++-- .../transformer/MixinApplicatorStandard.java | 369 +----------------- .../asm/mixin/transformer/MixinConfig.java | 40 +- .../mixin/transformer/MixinTargetContext.java | 106 ++++- .../mixin/transformer/TargetClassContext.java | 19 +- .../mixin/transformer/struct/Initialiser.java | 241 ++++++++++++ .../asm/mixin/transformer/struct/Range.java | 97 +++++ .../org/spongepowered/asm/util/Bytecode.java | 6 + .../spongepowered/asm/util/JavaVersion.java | 15 + .../asm/util/asm/MarkerNode.java | 56 +++ 29 files changed, 1659 insertions(+), 466 deletions(-) create mode 100644 src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java create mode 100644 src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java create mode 100644 src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java create mode 100644 src/main/java/org/spongepowered/asm/mixin/injection/struct/Constructor.java create mode 100644 src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java create mode 100644 src/main/java/org/spongepowered/asm/mixin/transformer/struct/Range.java create mode 100644 src/main/java/org/spongepowered/asm/util/asm/MarkerNode.java diff --git a/gradle.properties b/gradle.properties index b31d93a8a..6c0886e3f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -6,7 +6,7 @@ url=https://www.spongepowered.org organization=SpongePowered buildVersion=0.8.6 buildType=SNAPSHOT -asmVersion=9.2 +asmVersion=9.5 legacyForgeAsmVersion=5.0.3 modlauncherAsmVersion=9.5 modlauncherVersion=10.0.9 diff --git a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java index 0eb13c9c5..6daa57c43 100644 --- a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java +++ b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java @@ -753,6 +753,48 @@ boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_18 && ASM.isAtLeastVersion(9, 2); } + }, + + /** + * Java 19 or above is required + */ + JAVA_19(19, Opcodes.V19, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES + | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS + | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { + + @Override + boolean isSupported() { + return JavaVersion.current() >= JavaVersion.JAVA_19 && ASM.isAtLeastVersion(9, 3); + } + + }, + + /** + * Java 20 or above is required + */ + JAVA_20(20, Opcodes.V20, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES + | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS + | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { + + @Override + boolean isSupported() { + return JavaVersion.current() >= JavaVersion.JAVA_20 && ASM.isAtLeastVersion(9, 4); + } + + }, + + /** + * Java 21 or above is required + */ + JAVA_21(21, Opcodes.V21, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES + | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS + | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { + + @Override + boolean isSupported() { + return JavaVersion.current() >= JavaVersion.JAVA_21 && ASM.isAtLeastVersion(9, 5); + } + }; /** @@ -923,6 +965,24 @@ public static CompatibilityLevel requiredFor(int languageFeatures) { } return null; } + + /** + * Return the maximum compatibility level which is actually effective in + * the current runtime, taking into account the current JRE and ASM + * versions + */ + public static CompatibilityLevel getMaxEffective() { + CompatibilityLevel max = CompatibilityLevel.JAVA_6; + for (CompatibilityLevel level : CompatibilityLevel.values()) { + if (level.isSupported()) { + max = level; + } + if (level == CompatibilityLevel.MAX_SUPPORTED) { + break; + } + } + return max; + } static String getSupportedVersions() { StringBuilder sb = new StringBuilder(); @@ -956,6 +1016,110 @@ static String getSupportedVersions() { } + /** + * Mixin features which can be specified in mixin configs as required for + * the config to be valid. No support for backward compatibility but should + * help in the future to allow mixin configs to specify the features they + * require rather than relying purely on minVersion. Only applies to + * features added in version 0.8.6 and higher. + */ + public static enum Feature { + + /** + * Supports the unsafe flag on @At annotations to + * facilitate hassle-free constructor injections. + */ + UNSAFE_INJECTION(true), + + /** + * Support for the use of injector annotations in interface mixins + */ + INJECTORS_IN_INTERFACE_MIXINS(false) { + + @Override + public boolean isAvailable() { + return false; +// return CompatibilityLevel.getMaxEffective().supports(LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES); + } + +// @Override +// public boolean isEnabled() { +// return MixinEnvironment.getCompatibilityLevel().supports(LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES); +// } + + }; + + /** + * Existence of the enum constant does not necessarily indicate that the + * feature is actually supported by this version, for example if + * features can not be supported by future or previous JVM versions and + * need to be selectively enabled. This also allows us to add flags in + * the future to disable certain features globally for testing reasons. + */ + private boolean enabled; + + private Feature(boolean enabled) { + this.enabled = enabled; + } + + /** + * Get whether this feature is available in the current runtime + * environment + */ + public boolean isAvailable() { + return true; + } + + /** + * Get whether this feature is supported in the current environment and + * compatibility level + */ + public boolean isEnabled() { + return this.isAvailable() && this.enabled; + } + + /** + * Convenience function which returns a Feature constant based on the + * feature id, but returns null instead of throwing an exception. + * + * @param featureId Feature ID (enum constant name) to check for + * @return Feature or null + */ + public static Feature get(String featureId) { + if (featureId == null) { + return null; + } + try { + return Feature.valueOf(featureId); + } catch (IllegalArgumentException ex) { + return null; + } + } + + /** + * Check whether a particular feature exists in this mixin version, even + * if it's not currently available + * + * @param featureId Feature ID (enum constant name) to check for + * @return true if the feature exists + */ + public static boolean exists(String featureId) { + return Feature.get(featureId) != null; + } + + /** + * Check whether a particular feature is available and enabled + * + * @param featureId Feature ID (enum constant name) to check for + * @return true if the feature is currently available + */ + public static boolean isActive(String featureId) { + Feature feature = Feature.get(featureId); + return feature != null && feature.isEnabled(); + } + + } + /** * Wrapper for providing a natural sorting order for providers */ @@ -1129,6 +1293,7 @@ private void printHeader(Object version) { printer.kv("Internal Version", version); printer.kv("Java Version", "%s (supports compatibility %s)", JavaVersion.current(), CompatibilityLevel.getSupportedVersions()); printer.kv("Default Compatibility Level", MixinEnvironment.getCompatibilityLevel()); + printer.kv("Max Effective Compatibility Level", CompatibilityLevel.getMaxEffective()); printer.kv("Detected ASM Version", ASM.getVersionString()); printer.kv("Detected ASM Supports Java", ASM.getClassVersionString()).hr(); printer.kv("Service Name", serviceName); @@ -1142,6 +1307,10 @@ private void printHeader(Object version) { } printer.kv(option.property, "%s<%s>", indent, option); } + printer.hr(); + for (Feature feature : Feature.values()) { + printer.kv(feature.name(), "available=<%s> enabled=<%s>", feature.isAvailable(), feature.isEnabled()); + } printer.hr().kv("Detected Side", side); printer.print(System.err); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/At.java b/src/main/java/org/spongepowered/asm/mixin/injection/At.java index 6136d5800..b850a2b32 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/At.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/At.java @@ -209,4 +209,36 @@ public enum Shift { */ public boolean remap() default true; + /** + * In general, injecting into constructors should be treated with care, + * since compiled constructors - unlike regular methods - contain other + * structural elements of the class, including implied (when not explicit) + * superconstructor calls, explicit superconstructor or other delegated + * constructor calls, field initialisers and code from initialiser blocks, + * and of course the code from the original "constructor". + * + *

This means that unlike targetting a regular method, where it's often + * possible to derive a reasonable injection point from the Java source, in + * a constructor such assumptions can be dangerous. For example the HEAD + * of a regular method will always mean "before the first + * instruction", however in a constructor it's not possible to reasonably + * ascertain what the first instruction will actually be without inspecting + * the bytecode. This will certainly be prior to the delegate constructor + * call, which might come as a surprise if the delegate constructor is not + * explicit. Ultimately this means that for constructor code which appears + * before the regular constructor body, the class can be in a + * partially-initialised state (during initialisers) or in a + * fully-uninitialised state (prior to the delegate constructor call).

+ * + *

Because of this, by default certain injectors restrict usage to only + * RETURN opcodes when targetting a constructor, in order to ensure + * that the consumers are properly aware of the potential pitfalls. Whilst + * it was previously necessary to create a custom injection point in order + * to bypass this restriction, setting this option to true will + * also allow other injectors to act upon constructors, though care should + * be taken to ensure that the target is properly specified and attention is + * paid to the structure of the target bytecode.

+ */ + public boolean unsafe() default false; + } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java index 725ba6c7b..d5f4c79c1 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java @@ -247,6 +247,35 @@ enum ShiftByViolationBehaviour { } + /** + * Boolean extensions for the At parser, mainly to avoid having to add many + * booleans in the future + */ + public static final class Flags { + + /** + * Change the default target restriction from METHODS_ONLY to ALLOW_ALL + */ + public static final int UNSAFE = 1; + + public static int parse(At at) { + int flags = 0; + if (at.unsafe()) { + flags |= Flags.UNSAFE; + } + return flags; + } + + public static int parse(AnnotationNode at) { + int flags = 0; + if (Annotations.getValue(at, "unsafe", Boolean.FALSE)) { + flags |= InjectionPoint.Flags.UNSAFE; + } + return flags; + } + + } + /** * Initial limit on the value of {@link At#by} which triggers warning/error * (based on environment) @@ -277,6 +306,7 @@ enum ShiftByViolationBehaviour { InjectionPoint.registerBuiltIn(AfterStoreLocal.class); InjectionPoint.registerBuiltIn(BeforeFinalReturn.class); InjectionPoint.registerBuiltIn(BeforeConstant.class); + InjectionPoint.registerBuiltIn(ConstructorHead.class); } private final String slice; @@ -284,13 +314,14 @@ enum ShiftByViolationBehaviour { private final String id; private final IMessageSink messageSink; + private RestrictTargetLevel targetRestriction; protected InjectionPoint() { this("", Specifier.DEFAULT, null); } protected InjectionPoint(InjectionPointData data) { - this(data.getSlice(), data.getSpecifier(), data.getId(), data.getMessageSink()); + this(data.getSlice(), data.getSpecifier(), data.getId(), data.getMessageSink(), data.getTargetRestriction()); } public InjectionPoint(String slice, Specifier specifier, String id) { @@ -298,10 +329,15 @@ public InjectionPoint(String slice, Specifier specifier, String id) { } public InjectionPoint(String slice, Specifier specifier, String id, IMessageSink messageSink) { + this(slice, specifier, id, messageSink, RestrictTargetLevel.METHODS_ONLY); + } + + public InjectionPoint(String slice, Specifier specifier, String id, IMessageSink messageSink, RestrictTargetLevel targetRestriction) { this.slice = slice; this.specifier = specifier; this.id = id; this.messageSink = messageSink; + this.targetRestriction = targetRestriction; } public String getSlice() { @@ -346,6 +382,13 @@ public boolean checkPriority(int targetPriority, int mixinPriority) { return targetPriority < mixinPriority; } + /** + * Set a new target restriction level for this injection point + */ + protected void setTargetRestriction(RestrictTargetLevel targetRestriction) { + this.targetRestriction = targetRestriction; + } + /** * Returns the target restriction level for this injection point. This level * defines whether an injection point is valid in its current state when @@ -355,7 +398,7 @@ public boolean checkPriority(int targetPriority, int mixinPriority) { * @return restriction level */ public RestrictTargetLevel getTargetRestriction(IInjectionPointContext context) { - return RestrictTargetLevel.METHODS_ONLY; + return this.targetRestriction; } /** @@ -410,6 +453,18 @@ protected CompositeInjectionPoint(InjectionPoint... components) { this.components = components; } + + @Override + public RestrictTargetLevel getTargetRestriction(IInjectionPointContext context) { + RestrictTargetLevel level = RestrictTargetLevel.METHODS_ONLY; + for (InjectionPoint component : this.components) { + RestrictTargetLevel componentLevel = component.getTargetRestriction(context); + if (componentLevel.ordinal() > level.ordinal()) { + level = componentLevel; + } + } + return level; + } /* (non-Javadoc) * @see org.spongepowered.asm.mixin.injection.InjectionPoint#toString() @@ -514,6 +569,11 @@ public Shift(InjectionPoint input, int shift) { public String toString() { return "InjectionPoint(" + this.getClass().getSimpleName() + ")[" + this.input + "]"; } + + @Override + public RestrictTargetLevel getTargetRestriction(IInjectionPointContext context) { + return this.input.getTargetRestriction(context); + } @Override public boolean find(String desc, InsnList insns, Collection nodes) { @@ -652,7 +712,7 @@ public static List parse(IInjectionPointContext context, ListgetValue(at, "ordinal", Integer.valueOf(-1)); int opcode = Annotations.getValue(at, "opcode", Integer.valueOf(0)); String id = Annotations.getValue(at, "id"); - + int flags = InjectionPoint.Flags.parse(at); + if (args == null) { args = ImmutableList.of(); } - return InjectionPoint.parse(context, value, shift, by, args, target, slice, ordinal, opcode, id); + return InjectionPoint.parse(context, value, shift, by, args, target, slice, ordinal, opcode, id, flags); } /** @@ -732,12 +793,13 @@ public static InjectionPoint parse(IInjectionPointContext context, AnnotationNod * @param ordinal Ordinal offset for supported injection points * @param opcode Bytecode opcode for supported injection points * @param id Injection point id from annotation + * @param flags Additional flags * @return InjectionPoint parsed from the supplied data or null if parsing * failed */ public static InjectionPoint parse(IMixinContext context, MethodNode method, AnnotationNode parent, String at, At.Shift shift, int by, - List args, String target, String slice, int ordinal, int opcode, String id) { - return InjectionPoint.parse(new AnnotatedMethodInfo(context, method, parent), at, shift, by, args, target, slice, ordinal, opcode, id); + List args, String target, String slice, int ordinal, int opcode, String id, int flags) { + return InjectionPoint.parse(new AnnotatedMethodInfo(context, method, parent), at, shift, by, args, target, slice, ordinal, opcode, id, flags); } /** @@ -755,17 +817,18 @@ public static InjectionPoint parse(IMixinContext context, MethodNode method, Ann * @param ordinal Ordinal offset for supported injection points * @param opcode Bytecode opcode for supported injection points * @param id Injection point id from annotation + * @param flags Additional flags * @return InjectionPoint parsed from the supplied data or null if parsing * failed */ public static InjectionPoint parse(IInjectionPointContext context, String at, At.Shift shift, int by, - List args, String target, String slice, int ordinal, int opcode, String id) { - InjectionPointData data = new InjectionPointData(context, at, args, target, slice, ordinal, opcode, id); + List args, String target, String slice, int ordinal, int opcode, String id, int flags) { + InjectionPointData data = new InjectionPointData(context, at, args, target, slice, ordinal, opcode, id, flags); Class ipClass = InjectionPoint.findClass(context.getMixin(), data); InjectionPoint point = InjectionPoint.create(context.getMixin(), data, ipClass); return InjectionPoint.shift(context, point, shift, by); } - + @SuppressWarnings("unchecked") private static Class findClass(IMixinContext context, InjectionPointData data) { String type = data.getType(); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java index 3c6b5dd47..52e9ba52e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java @@ -39,6 +39,7 @@ import org.spongepowered.asm.mixin.injection.Surrogate; import org.spongepowered.asm.mixin.injection.code.Injector; import org.spongepowered.asm.mixin.injection.code.InjectorTarget; +import org.spongepowered.asm.mixin.injection.struct.Constructor; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.Target; @@ -385,7 +386,7 @@ public CallbackInjector(InjectionInfo info, boolean cancellable, LocalCapture lo @Override protected void sanityCheck(Target target, List injectionPoints) { super.sanityCheck(target, injectionPoints); - this.checkTargetModifiers(target, true); + this.checkTargetModifiers(target, false); } /* (non-Javadoc) @@ -397,6 +398,10 @@ protected void sanityCheck(Target target, List injectionPoints) protected void addTargetNode(InjectorTarget injectorTarget, List myNodes, AbstractInsnNode node, Set nominators) { InjectionNode injectionNode = injectorTarget.addInjectionNode(node); + if (this.cancellable && injectorTarget.getTarget() instanceof Constructor) { + throw new InvalidInjectionException(this.info, String.format("Found cancellable @Inject targetting a constructor in injector %s", this)); + } + for (InjectionPoint ip : nominators) { try { @@ -711,7 +716,7 @@ private void invokeCallback(final Callback callback, final MethodNode callbackMe // Push the target method's parameters onto the stack if (callback.captureArgs()) { - Bytecode.loadArgs(callback.target.arguments, callback, this.isStatic ? 0 : 1, -1); //, callback.typeCasts); + Bytecode.loadArgs(callback.target.arguments, callback, callback.target.isStatic ? 0 : 1, -1); //, callback.typeCasts); } // Push the callback info onto the stack diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java new file mode 100644 index 000000000..584269201 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java @@ -0,0 +1,98 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.injection.code; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.spongepowered.asm.mixin.injection.InjectionPoint; + +/** + * Interface for extensions for InsnList which provide additional context for + * the InsnList. This is mainly to allow passing additional information to + * InjectionPoint::{@link InjectionPoint#find} without breaking + * backward compatibility. + */ +public interface IInsnListEx { + + /** + * Type of special nodes supported by {@link #getSpecialNode} + */ + public enum SpecialNodeType { + + /** + * The delegate constructor call in a constructor + */ + DELEGATE_CTOR, + + /** + * The location for injected initialisers in a constructor + */ + INITIALISER_INJECTION_POINT + + } + + /** + * Get the name of the target method + */ + public abstract String getTargetName(); + + /** + * Get the descriptor of the target method + */ + public abstract String getTargetDesc(); + + /** + * Get the signature of the target method + */ + public abstract String getTargetSignature(); + + /** + * Get the access flags from the target method + */ + public abstract int getTargetAccess(); + + /** + * Get whether the target method is static + */ + public abstract boolean isTargetStatic(); + + /** + * Get whether the target method is a constructor + */ + public abstract boolean isTargetConstructor(); + + /** + * Get whether the target method is a static initialiser + */ + public abstract boolean isTargetStaticInitialiser(); + + /** + * Get - if available - the specified special node from the target. The + * returned node is not guaranteed to be non-null. + * + * @param type type of special node to fetch + * @return the special node or null if not available + */ + public abstract AbstractInsnNode getSpecialNode(SpecialNodeType type); +} diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java index a53aa7c48..c1cfc3d22 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java @@ -44,6 +44,7 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; import org.spongepowered.asm.mixin.injection.invoke.RedirectInjector; +import org.spongepowered.asm.mixin.injection.struct.Constructor; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.Target.Extension; @@ -293,7 +294,6 @@ public final void inject(Target target, List nodes) { */ private Collection findTargetNodes(InjectorTarget injectorTarget, List injectionPoints) { IMixinContext mixin = this.info.getMixin(); - MethodNode method = injectorTarget.getMethod(); Map targetNodes = new TreeMap(); List nodes = new ArrayList(32); @@ -308,7 +308,7 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li injectorTarget, injectorTarget.getMergedBy(), injectorTarget.getMergedPriority())); } - if (!this.findTargetNodes(method, injectionPoint, injectorTarget, nodes)) { + if (!this.findTargetNodes(injectorTarget, injectionPoint, nodes)) { continue; } @@ -318,10 +318,10 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li injectionPoint, this, nodes.size())); } else if (specifier != Specifier.ALL && nodes.size() > 1) { AbstractInsnNode specified = nodes.get(specifier == Specifier.FIRST ? 0 : nodes.size() - 1); - this.addTargetNode(method, targetNodes, injectionPoint, specified); + this.addTargetNode(injectorTarget, targetNodes, injectionPoint, specified); } else { for (AbstractInsnNode insn : nodes) { - this.addTargetNode(method, targetNodes, injectionPoint, insn); + this.addTargetNode(injectorTarget, targetNodes, injectionPoint, insn); } } } @@ -329,8 +329,8 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li return targetNodes.values(); } - protected void addTargetNode(MethodNode method, Map targetNodes, InjectionPoint injectionPoint, AbstractInsnNode insn) { - Integer key = method.instructions.indexOf(insn); + protected void addTargetNode(InjectorTarget target, Map targetNodes, InjectionPoint injectionPoint, AbstractInsnNode insn) { + Integer key = target.getTarget().indexOf(insn); TargetNode targetNode = targetNodes.get(key); if (targetNode == null) { targetNode = new TargetNode(insn); @@ -339,9 +339,8 @@ protected void addTargetNode(MethodNode method, Map targetN targetNode.nominators.add(injectionPoint); } - protected boolean findTargetNodes(MethodNode into, InjectionPoint injectionPoint, InjectorTarget injectorTarget, - Collection nodes) { - return injectionPoint.find(into.desc, injectorTarget.getSlice(injectionPoint), nodes); + protected boolean findTargetNodes(InjectorTarget target, InjectionPoint injectionPoint, Collection nodes) { + return injectionPoint.find(target.getDesc(), target.getSlice(injectionPoint), nodes); } protected void sanityCheck(Target target, List injectionPoints) { @@ -379,19 +378,21 @@ protected final void checkTargetModifiers(Target target, boolean exactMatch) { * @param node Injection location */ protected void checkTargetForNode(Target target, InjectionNode node, RestrictTargetLevel targetLevel) { - if (target.isCtor) { + if (target instanceof Constructor) { + Constructor ctor = (Constructor)target; + if (targetLevel == RestrictTargetLevel.METHODS_ONLY) { throw new InvalidInjectionException(this.info, String.format("Found %s targetting a constructor in injector %s", this.annotationType, this)); } - DelegateInitialiser superCall = target.findDelegateInitNode(); + DelegateInitialiser superCall = ctor.findDelegateInitNode(); if (!superCall.isPresent) { throw new InjectionError(String.format("Delegate constructor lookup failed for %s target on %s", this.annotationType, this.info)); } - int superCallIndex = target.indexOf(superCall.insn); - int targetIndex = target.indexOf(node.getCurrentTarget()); + int superCallIndex = ctor.indexOf(superCall.insn); + int targetIndex = ctor.indexOf(node.getCurrentTarget()); if (targetIndex <= superCallIndex) { if (targetLevel == RestrictTargetLevel.CONSTRUCTORS_AFTER_DELEGATE) { throw new InvalidInjectionException(this.info, String.format("Found %s targetting a constructor before %s() in injector %s", @@ -406,7 +407,7 @@ protected void checkTargetForNode(Target target, InjectionNode node, RestrictTar } } - this.checkTargetModifiers(target, true); + this.checkTargetModifiers(target, false); } protected void preInject(Target target, InjectionNode node) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/InjectorTarget.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/InjectorTarget.java index f0e551661..8fa8f5911 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/InjectorTarget.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/InjectorTarget.java @@ -117,6 +117,27 @@ public InjectionNode addInjectionNode(AbstractInsnNode node) { public InjectionNode getInjectionNode(AbstractInsnNode node) { return this.target.getInjectionNode(node); } + + /** + * Get the target method name + */ + public String getName() { + return this.target.getName(); + } + + /** + * Get the target method descriptor + */ + public String getDesc() { + return this.target.getDesc(); + } + + /** + * Get the target method signature + */ + public String getSignature() { + return this.target.getSignature(); + } /** * Get the target reference @@ -173,10 +194,10 @@ public InsnList getSlice(String id) { if (slice == null) { MethodSlice sliceInfo = this.context.getSlice(id); if (sliceInfo != null) { - slice = sliceInfo.getSlice(this.target.method); + slice = sliceInfo.getSlice(this.target); } else { // No slice exists so just wrap the method insns - slice = new InsnListReadOnly(this.target.method.instructions); + slice = new InsnListEx(this.target); } this.cache.put(id, slice); } @@ -204,5 +225,6 @@ public void dispose() { this.cache.clear(); } + } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java new file mode 100644 index 000000000..97802871e --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java @@ -0,0 +1,150 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.injection.code; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.spongepowered.asm.mixin.injection.struct.Constructor; +import org.spongepowered.asm.mixin.injection.struct.Target; +import org.spongepowered.asm.mixin.transformer.struct.Initialiser; +import org.spongepowered.asm.mixin.transformer.struct.Initialiser.InjectionMode; +import org.spongepowered.asm.util.Bytecode.DelegateInitialiser; +import org.spongepowered.asm.util.Constants; + +/** + * InsnList with extensions, see {@link IInsnListEx} + */ +public class InsnListEx extends InsnListReadOnly implements IInsnListEx { + + /** + * The target method + */ + private final Target target; + + public InsnListEx(Target target) { + super(target.insns); + this.target = target; + } + + @Override + public String toString() { + return this.target.toString(); + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.injection.code.IInsnListEx + * #getTargetName() + */ + @Override + public String getTargetName() { + return this.target.getName(); + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.injection.code.IInsnListEx + * #getTargetDesc() + */ + @Override + public String getTargetDesc() { + return this.target.getDesc(); + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.injection.code.IInsnListEx + * #getTargetSignature() + */ + @Override + public String getTargetSignature() { + return this.target.getSignature(); + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.injection.code.IInsnListEx + * #getTargetAccess() + */ + @Override + public int getTargetAccess() { + return this.target.method.access; + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.injection.code.IInsnListEx + * #isTargetStatic() + */ + @Override + public boolean isTargetStatic() { + return this.target.isStatic; + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.injection.code.IInsnListEx + * #isTargetConstructor() + */ + @Override + public boolean isTargetConstructor() { + return this.target instanceof Constructor; + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.injection.code.IInsnListEx + * #isTargetStaticInitialiser() + */ + @Override + public boolean isTargetStaticInitialiser() { + return Constants.CLINIT.equals(this.target.getName()); + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.injection.code.IInsnListEx + * #getSpecialNode + */ + @Override + public AbstractInsnNode getSpecialNode(SpecialNodeType type) { + switch (type) { + case DELEGATE_CTOR: + if (this.target instanceof Constructor) { + DelegateInitialiser superCall = ((Constructor)this.target).findDelegateInitNode(); + if (superCall.isPresent && this.contains(superCall.insn)) { + return superCall.insn; + } + } + return null; + + case INITIALISER_INJECTION_POINT: + if (this.target instanceof Constructor) { + // mode is always DEFAULT because we want to locate initialisers if possible + InjectionMode mode = Initialiser.InjectionMode.DEFAULT; + AbstractInsnNode initialiserInjectionPoint = ((Constructor)this.target).findInitialiserInjectionPoint(mode); + if (this.contains(initialiserInjectionPoint)) { + return initialiserInjectionPoint; + } + } + return null; + + default: + return null; + } + } + +} diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListReadOnly.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListReadOnly.java index f1e647ef7..2ea71ea0e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListReadOnly.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListReadOnly.java @@ -34,7 +34,7 @@ * instances so that custom InjectionPoint implementations cannot modify the * insn list whilst inspecting it. */ -public class InsnListReadOnly extends InsnList { +public abstract class InsnListReadOnly extends InsnList { private InsnList insnList; @@ -218,6 +218,9 @@ public AbstractInsnNode get(int index) { */ @Override public boolean contains(AbstractInsnNode insn) { + if (insn == null) { + return false; + } return this.insnList.contains(insn); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java index 58201c05b..7e43f8bdd 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java @@ -40,8 +40,10 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.struct.InjectionPointAnnotationContext; +import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.mixin.injection.throwables.InjectionError; import org.spongepowered.asm.mixin.injection.throwables.InvalidSliceException; +import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.service.MixinService; import org.spongepowered.asm.util.Annotations; @@ -57,7 +59,7 @@ public final class MethodSlice { * identified by start and end to be accessed. In essence * this class provides a view of the underlying InsnList. */ - static final class InsnListSlice extends InsnListReadOnly { + static final class InsnListSlice extends InsnListEx { /** * ListIterator for the slice view, wraps an iterator returned by the @@ -178,8 +180,8 @@ public void add(AbstractInsnNode e) { */ private final int start, end; - protected InsnListSlice(InsnList inner, int start, int end) { - super(inner); + protected InsnListSlice(Target target, int start, int end) { + super(target); // Start and end are validated prior to construction this.start = start; @@ -259,6 +261,9 @@ public AbstractInsnNode get(int index) { */ @Override public boolean contains(AbstractInsnNode insn) { + if (insn == null) { + return false; + } for (AbstractInsnNode node : this.toArray()) { if (node == insn) { return true; @@ -361,13 +366,13 @@ public String getId() { /** * Get a sliced insn list based on the parameters specified in this slice * - * @param method method to slice + * @param target method to slice * @return read only slice */ - public InsnListReadOnly getSlice(MethodNode method) { - int max = method.instructions.size() - 1; - int start = this.find(method, this.from, 0, 0, "from"); - int end = this.find(method, this.to, max, start, "to"); + public InsnListReadOnly getSlice(Target target) { + int max = target.insns.size() - 1; + int start = this.find(target, this.from, 0, 0, "from"); + int end = this.find(target, this.to, max, start, "to"); if (start > end) { throw new InvalidSliceException(this.owner, String.format("%s is negative size. Range(%d -> %d)", this.describe(), start, end)); @@ -378,10 +383,10 @@ public InsnListReadOnly getSlice(MethodNode method) { } if (start == 0 && end == max) { - return new InsnListReadOnly(method.instructions); + return new InsnListEx(target); } - return new InsnListSlice(method.instructions, start, end); + return new InsnListSlice(target, start, end); } /** @@ -389,7 +394,7 @@ public InsnListReadOnly getSlice(MethodNode method) { * the index of the instruction matching the query. Returns the default * value if the query returns zero results. * - * @param method Method to query + * @param target Method to query * @param injectionPoint Query to run * @param defaultValue Value to return if injection point is null (open * ended) @@ -397,15 +402,15 @@ public InsnListReadOnly getSlice(MethodNode method) { * @param argument The name of the argument ("from" or "to") for debug msgs * @return matching insn index */ - private int find(MethodNode method, InjectionPoint injectionPoint, int defaultValue, int failValue, String argument) { + private int find(Target target, InjectionPoint injectionPoint, int defaultValue, int failValue, String argument) { if (injectionPoint == null) { return defaultValue; } String description = String.format("%s(%s)", this.name, argument); Deque nodes = new LinkedList(); - InsnListReadOnly insns = new InsnListReadOnly(method.instructions); - boolean result = injectionPoint.find(method.desc, insns, nodes); + InsnList insns = new InsnListEx(target); + boolean result = injectionPoint.find(target.getDesc(), insns, nodes); Specifier specifier = injectionPoint.getSpecifier(Specifier.FIRST); if (specifier == Specifier.ALL) { throw new InvalidSliceException(this.owner, String.format("ALL is not a valid specifier for slice %s", this.describe(description))); @@ -424,7 +429,7 @@ private int find(MethodNode method, InjectionPoint injectionPoint, int defaultVa this.successCountTo++; } - return method.instructions.indexOf(specifier == Specifier.FIRST ? nodes.getFirst() : nodes.getLast()); + return target.indexOf(specifier == Specifier.FIRST ? nodes.getFirst() : nodes.getLast()); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java index 051831a0d..8aaee8a34 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java @@ -108,7 +108,7 @@ protected void injectAtInvoke(Target target, InjectionNode node) { private boolean verifyTarget(Target target) { String shortDesc = String.format("(L%s;)V", ArgsClassGenerator.ARGS_REF); if (!this.methodNode.desc.equals(shortDesc)) { - String targetDesc = Bytecode.changeDescriptorReturnType(target.method.desc, "V"); + String targetDesc = Bytecode.changeDescriptorReturnType(target.getDesc(), "V"); String longDesc = String.format("(L%s;%s", ArgsClassGenerator.ARGS_REF, targetDesc.substring(1)); if (this.methodNode.desc.equals(longDesc)) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java index 8253ed152..9fd4fbb44 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java @@ -31,7 +31,6 @@ import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.VarInsnNode; import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; @@ -109,13 +108,12 @@ public ModifyVariableInjector(InjectionInfo info, LocalVariableDiscriminator dis } @Override - protected boolean findTargetNodes(MethodNode into, InjectionPoint injectionPoint, InjectorTarget injectorTarget, - Collection nodes) { + protected boolean findTargetNodes(InjectorTarget target, InjectionPoint injectionPoint, Collection nodes) { if (injectionPoint instanceof LocalVariableInjectionPoint) { - return ((LocalVariableInjectionPoint)injectionPoint).find(this.info, injectorTarget.getSlice(injectionPoint), nodes, - injectorTarget.getTarget()); + return ((LocalVariableInjectionPoint)injectionPoint).find(this.info, target.getSlice(injectionPoint), nodes, + target.getTarget()); } - return injectionPoint.find(into.desc, injectorTarget.getSlice(injectionPoint), nodes); + return injectionPoint.find(target.getDesc(), target.getSlice(injectionPoint), nodes); } /* (non-Javadoc) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java index bbad13edc..25ee0c513 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java @@ -169,7 +169,7 @@ public BeforeConstant(IMixinContext context, AnnotationNode node, String returnT public BeforeConstant(InjectionPointData data) { super(data); - String strNullValue = data.get("nullValue", null); + String strNullValue = data.get("nullValue", (String)null); Boolean empty = strNullValue != null ? Boolean.parseBoolean(strNullValue) : null; this.ordinal = data.getOrdinal(); @@ -178,8 +178,8 @@ public BeforeConstant(InjectionPointData data) { this.floatValue = Floats.tryParse(data.get("floatValue", "")); this.longValue = Longs.tryParse(data.get("longValue", "")); this.doubleValue = Doubles.tryParse(data.get("doubleValue", "")); - this.stringValue = data.get("stringValue", null); - String strClassValue = data.get("classValue", null); + this.stringValue = data.get("stringValue", (String)null); + String strClassValue = data.get("classValue", (String)null); this.typeValue = strClassValue != null ? Type.getObjectType(strClassValue.replace('.', '/')) : null; this.matchByType = this.validateDiscriminator(data.getMixin(), "V", empty, "in @At(\"CONSTANT\") args"); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeStringInvoke.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeStringInvoke.java index 3f6373d85..82c53ff50 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeStringInvoke.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeStringInvoke.java @@ -99,7 +99,7 @@ public class BeforeStringInvoke extends BeforeInvoke { public BeforeStringInvoke(InjectionPointData data) { super(data); - this.ldcValue = data.get("ldc", null); + this.ldcValue = data.get("ldc", (String)null); if (this.ldcValue == null) { throw new IllegalArgumentException(this.getClass().getSimpleName() + " requires named argument \"ldc\" to specify the desired target"); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java new file mode 100644 index 000000000..87f6ef6e4 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java @@ -0,0 +1,188 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.injection.points; + +import java.util.Collection; + +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.MethodNode; +import org.spongepowered.asm.logging.ILogger; +import org.spongepowered.asm.mixin.MixinEnvironment.Option; +import org.spongepowered.asm.mixin.injection.InjectionPoint.AtCode; +import org.spongepowered.asm.mixin.injection.code.IInsnListEx; +import org.spongepowered.asm.mixin.injection.code.IInsnListEx.SpecialNodeType; +import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; +import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionPointException; +import org.spongepowered.asm.service.MixinService; +import org.spongepowered.asm.util.Bytecode; +import org.spongepowered.asm.util.asm.MarkerNode; + +/** + *

Like {@link MethodHead HEAD}, this injection point can be used to specify + * the first instruction in a method, but provides special handling for + * constructors. For regular method, the behaviour is identical to HEAD + * .

+ * + *

By default, this injection point attempts to select the first instruction + * after any initialisers (including initialisers merged by mixins) but will + * fall back to selecting the first instruction after the delegate call (eg. a + * call to super() or this()) in the case where heuristic + * detection of the initialisers fails. This behaviour can be overridden by + * providing the enforce parameter to enforce selection of a specific + * location.

+ * + *
+ *
enforce=POST_DELEGATE
+ *
Select the instruction immediately after the delegate constructor
+ *
enforce=POST_INIT
+ *
Select the instruction immediately after field initialisers, this + * condition can fail if no initialisers are found.
+ *
+ * + *

Example default behaviour:

+ *
+ *   @At(value = "CTOR_HEAD", unsafe = true)
+ *
+ * + *

Example behaviour enforcing post-delegate injection point:

+ *
+ *   @At(value = "CTOR_HEAD", unsafe = true, args="enforce=POST_DELEGATE")
+ *   
+ *
+ */ +@AtCode("CTOR_HEAD") +public class ConstructorHead extends MethodHead { + + /** + * Location enforcement + */ + static enum Enforce { + + /** + * Use default behaviour + */ + DEFAULT, + + /** + * Enforce selection of post-delegate insn + */ + POST_DELEGATE, + + /** + * Enforce selection of post-initialiser insn + */ + POST_INIT; + + } + + /** + * Logger + */ + protected final ILogger logger = MixinService.getService().getLogger("mixin"); + + /** + * Enforce behaviour parsed from At args + */ + private final Enforce enforce; + + /** + * True to warn when enfored selection fails + */ + private final boolean verbose; + + private final MethodNode method; + + public ConstructorHead(InjectionPointData data) { + super(data); + if (!data.isUnsafe()) { + throw new InvalidInjectionPointException(data.getMixin(), "@At(\"CTOR_HEAD\") requires unsafe=true"); + } + this.enforce = data.get("enforce", Enforce.DEFAULT); + this.verbose = data.getMixin().getOption(Option.DEBUG_VERBOSE); + this.method = data.getMethod(); + } + + @Override + public boolean find(String desc, InsnList insns, Collection nodes) { + if (!(insns instanceof IInsnListEx)) { + return false; + } + + IInsnListEx xinsns = (IInsnListEx)insns; + if (!xinsns.isTargetConstructor()) { + return super.find(desc, insns, nodes); + } + + AbstractInsnNode delegateCtor = xinsns.getSpecialNode(SpecialNodeType.DELEGATE_CTOR); + AbstractInsnNode postDelegate = delegateCtor != null ? delegateCtor.getNext() : null; + if (this.enforce == Enforce.POST_DELEGATE) { + if (postDelegate == null) { + if (this.verbose) { + this.logger.warn("@At(\"{}\") on {}{} targetting {} failed for enforce=POST_DELEGATE because no delegate was found", + this.getAtCode(), this.method.name, this.method.desc, xinsns); + } + return false; + } + nodes.add(postDelegate); + return true; + } + + AbstractInsnNode postInit = xinsns.getSpecialNode(SpecialNodeType.INITIALISER_INJECTION_POINT); + + if (postInit instanceof MarkerNode) { + AbstractInsnNode postPostInit = postInit.getNext(); + if (postDelegate == postInit || postDelegate == postPostInit) { + // If the marker is immedately after the delegate call, then we haven't actually + // found any initialiser insns. This is fine as long as we're not Enforce.POST_INIT + // which we assume will only be set by the user when they want to be sure that the + // initialisers are properly detected. Set this null to trigger the enforcement or + // fallback behaviour below. + postInit = null; + } else { + postInit = postPostInit; + } + } + + if (this.enforce == Enforce.POST_INIT || postInit != null) { + if (postInit == null) { + if (this.verbose) { + this.logger.warn("@At(\"{}\") on {}{} targetting {} failed for enforce=POST_INIT because no initialiser was found", + this.getAtCode(), this.method.name, this.method.desc, xinsns); + } + return false; + } + nodes.add(postInit); + return true; + } + + if (postDelegate != null) { + nodes.add(postDelegate); + return true; + } + + return super.find(desc, insns, nodes); + } +} diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/MethodHead.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/MethodHead.java index 93b3642d6..0d4bf2171 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/MethodHead.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/MethodHead.java @@ -41,7 +41,10 @@ *

Example:

*
  *   @At("HEAD")
- *
+ * + * + *

Note that for constructors, use of HEAD is discouraged, see + * instead {@link ConstructorHead CTOR_HEAD}. */ @AtCode("HEAD") public class MethodHead extends InjectionPoint { @@ -60,4 +63,5 @@ public boolean find(String desc, InsnList insns, Collection no nodes.add(insns.getFirst()); return true; } + } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Constructor.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Constructor.java new file mode 100644 index 000000000..52fdab74a --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Constructor.java @@ -0,0 +1,139 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.injection.struct; + +import java.util.HashSet; +import java.util.Iterator; +import java.util.Set; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.MethodInsnNode; +import org.objectweb.asm.tree.MethodNode; +import org.spongepowered.asm.mixin.transformer.ClassInfo; +import org.spongepowered.asm.mixin.transformer.struct.Initialiser; +import org.spongepowered.asm.util.Bytecode; +import org.spongepowered.asm.util.Constants; +import org.spongepowered.asm.util.asm.MarkerNode; +import org.spongepowered.asm.util.Bytecode.DelegateInitialiser; + +/** + * A {@link Target} which is a constructor + */ +public class Constructor extends Target { + + /** + * Cached delegate initialiser call + */ + private DelegateInitialiser delegateInitialiser; + + private MarkerNode initialiserInjectionPoint; + + public Constructor(ClassInfo classInfo, ClassNode classNode, MethodNode method) { + super(classInfo, classNode, method); + } + + /** + * Find the call to super() or this() in a constructor. + * This attempts to locate the first call to <init> which + * isn't an inline call to another object ctor being passed into the super + * invocation. + * + * @return Call to super(), this() or + * DelegateInitialiser.NONE if not found + */ + public DelegateInitialiser findDelegateInitNode() { + if (this.delegateInitialiser == null) { + this.delegateInitialiser = Bytecode.findDelegateInit(this.method, this.classInfo.getSuperName(), this.classNode.name); + } + + return this.delegateInitialiser; + } + + /** + * Find the injection point for injected initialiser insns in the target + * ctor. This starts by assuming that initialiser instructions should be + * placed immediately after the delegate initialiser call, but then searches + * for field assignments and selects the last unique field + * assignment in the ctor body which represents a reasonable heuristic for + * the end of the existing initialisers. + * + * @param mode Injection mode for this specific environment + * @return target node + */ + public AbstractInsnNode findInitialiserInjectionPoint(Initialiser.InjectionMode mode) { + if (this.initialiserInjectionPoint != null) { + return this.initialiserInjectionPoint; + } + + String targetName = this.classInfo.getName(); + String targetSuperName = this.classInfo.getSuperName(); + + Set initialisedFields = new HashSet(); + for (AbstractInsnNode initialiserInsn : this.insns) { + if (initialiserInsn.getOpcode() == Opcodes.PUTFIELD) { + FieldInsnNode fieldInsn = (FieldInsnNode)initialiserInsn; + if (!fieldInsn.owner.equals(targetName)) { + continue; + } + initialisedFields.add(Constructor.fieldKey((FieldInsnNode)initialiserInsn)); + } + } + + AbstractInsnNode lastInsn = null; + for (Iterator iter = this.insns.iterator(); iter.hasNext();) { + AbstractInsnNode insn = iter.next(); + if (insn.getOpcode() == Opcodes.INVOKESPECIAL && Constants.CTOR.equals(((MethodInsnNode)insn).name)) { + String owner = ((MethodInsnNode)insn).owner; + if (owner.equals(targetName) || owner.equals(targetSuperName)) { + lastInsn = insn; + if (mode == Initialiser.InjectionMode.SAFE) { + break; + } + } + } else if (insn.getOpcode() == Opcodes.PUTFIELD && mode == Initialiser.InjectionMode.DEFAULT) { + String key = Constructor.fieldKey((FieldInsnNode)insn); + if (initialisedFields.contains(key)) { + lastInsn = insn; + } + } + } + + if (lastInsn == null) { + return null; + } + + this.initialiserInjectionPoint = new MarkerNode(MarkerNode.INITIALISER_TAIL); + this.insert(lastInsn, this.initialiserInjectionPoint); + return this.initialiserInjectionPoint; + } + + private static String fieldKey(FieldInsnNode fieldNode) { + return String.format("%s:%s", fieldNode.desc, fieldNode.name); + } + +} diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java index b41cdd7ce..e896fd486 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java @@ -36,6 +36,8 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.IInjectionPointContext; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.InjectionPoint; +import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; @@ -88,6 +90,11 @@ public class InjectionPointData { */ private final Specifier specifier; + /** + * Target restriction from the at annotation, if present + */ + private final RestrictTargetLevel targetRestriction; + /** * Target */ @@ -113,8 +120,13 @@ public class InjectionPointData { */ private final String id; + /** + * Flags from annotation parser + */ + private final int flags; + public InjectionPointData(IInjectionPointContext context, String at, List args, String target, - String slice, int ordinal, int opcode, String id) { + String slice, int ordinal, int opcode, String id, int flags) { this.context = context; this.at = at; this.target = target; @@ -122,6 +134,7 @@ public InjectionPointData(IInjectionPointContext context, String at, List args) { @@ -177,7 +192,14 @@ public String getType() { public Specifier getSpecifier() { return this.specifier; } - + + /** + * Get the target restriction specified in the annotation + */ + public RestrictTargetLevel getTargetRestriction() { + return this.targetRestriction; + } + /** * Get the injection point context */ @@ -260,6 +282,19 @@ public int get(String key, int defaultValue) { public boolean get(String key, boolean defaultValue) { return InjectionPointData.parseBoolean(this.get(key, String.valueOf(defaultValue)), defaultValue); } + + /** + * Get the supplied value from the named args, return defaultValue if the + * arg is not set + * + * @param enum type + * @param key argument name + * @param defaultValue value to return if the arg is not set + * @return argument value or default if not set + */ + public > T get(String key, T defaultValue) { + return InjectionPointData.parseEnum(this.get(key, defaultValue.name()), defaultValue); + } /** * Get the supplied value from the named args as a target selector, @@ -356,6 +391,13 @@ public String getId() { return this.id; } + /** + * Get whether the unsafe option is set on the injection point + */ + public boolean isUnsafe() { + return (this.flags & InjectionPoint.Flags.UNSAFE) != 0; + } + @Override public String toString() { return this.type; @@ -400,4 +442,12 @@ private static boolean parseBoolean(String string, boolean defaultValue) { } } + @SuppressWarnings("unchecked") + private static > T parseEnum(String string, T defaultValue) { + try { + return (T)Enum.valueOf(defaultValue.getClass(), string); + } catch (Exception ex) { + return defaultValue; + } + } } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index 0e042a92f..59a83ef90 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -42,13 +42,13 @@ import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.transformer.ClassInfo; import org.spongepowered.asm.util.Bytecode; -import org.spongepowered.asm.util.Bytecode.DelegateInitialiser; import org.spongepowered.asm.util.Constants; import org.spongepowered.asm.util.Locals.SyntheticLocalVariableNode; /** - * Information about the current injection target, mainly just convenience - * rather than passing a bunch of values around. + * Information about the current injection target (method) which bundles common + * injection context with the target method in order to allow injectors to + * interoperate. */ public class Target implements Comparable, Iterable { @@ -156,6 +156,11 @@ public void apply() { } } + + /** + * Target class info + */ + public final ClassInfo classInfo; /** * Target class node @@ -177,11 +182,6 @@ public void apply() { */ public final boolean isStatic; - /** - * True if the method is a constructor - */ - public final boolean isCtor; - /** * Method arguments */ @@ -231,23 +231,18 @@ public void apply() { * Labels for LVT ranges, generated as needed */ private LabelNode start, end; - - /** - * Cached delegate initialiser call - */ - private DelegateInitialiser delegateInitialiser; /** * Make a new Target for the supplied method * * @param method target method */ - public Target(ClassNode classNode, MethodNode method) { + Target(ClassInfo classInfo, ClassNode classNode, MethodNode method) { + this.classInfo = classInfo; this.classNode = classNode; this.method = method; this.insns = method.instructions; this.isStatic = Bytecode.isStatic(method); - this.isCtor = method.name.equals(Constants.CTOR); this.arguments = Type.getArgumentTypes(method.desc); this.returnType = Type.getReturnType(method.desc); @@ -277,6 +272,27 @@ public InjectionNode getInjectionNode(AbstractInsnNode node) { return this.injectionNodes.get(node); } + /** + * Get the target method name + */ + public String getName() { + return this.method.name; + } + + /** + * Get the target method descriptor + */ + public String getDesc() { + return this.method.desc; + } + + /** + * Get the target method signature + */ + public String getSignature() { + return this.method.signature; + } + /** * Get the original max locals of the method * @@ -545,7 +561,7 @@ public String getCallbackDescriptor(final Type[] locals, Type[] argumentTypes) { */ public String getCallbackDescriptor(final boolean captureLocals, final Type[] locals, Type[] argumentTypes, int startIndex, int extra) { if (this.callbackDescriptor == null) { - this.callbackDescriptor = String.format("(%sL%s;)V", this.method.desc.substring(1, this.method.desc.indexOf(')')), + this.callbackDescriptor = String.format("(%sL%s;)V", this.getDesc().substring(1, this.getDesc().indexOf(')')), this.getCallbackInfoClass()); } @@ -566,7 +582,7 @@ public String getCallbackDescriptor(final boolean captureLocals, final Type[] lo @Override public String toString() { - return String.format("%s::%s%s", this.classNode.name, this.method.name, this.method.desc); + return String.format("%s::%s%s", this.classNode.name, this.getName(), this.getDesc()); } @Override @@ -637,27 +653,45 @@ public MethodInsnNode findInitNodeFor(TypeInsnNode newNode) { } /** - * Find the call to super() or this() in a constructor. - * This attempts to locate the first call to <init> which - * isn't an inline call to another object ctor being passed into the super - * invocation. + * Insert the supplied instructions after the specified instruction * - * @return Call to super(), this() or - * DelegateInitialiser.NONE if not found + * @param location Instruction to insert before + * @param insns Instructions to insert */ - public DelegateInitialiser findDelegateInitNode() { - if (!this.isCtor) { - return DelegateInitialiser.NONE; - } - - if (this.delegateInitialiser == null) { - String superName = ClassInfo.forName(this.classNode.name).getSuperName(); - this.delegateInitialiser = Bytecode.findDelegateInit(this.method, superName, this.classNode.name); - } - - return this.delegateInitialiser; + public void insert(InjectionNode location, final InsnList insns) { + this.insns.insert(location.getCurrentTarget(), insns); + } + + /** + * Insert the supplied instruction after the specified instruction + * + * @param location Instruction to insert before + * @param insn Instruction to insert + */ + public void insert(InjectionNode location, final AbstractInsnNode insn) { + this.insns.insert(location.getCurrentTarget(), insn); } + /** + * Insert the supplied instructions after the specified instruction + * + * @param location Instruction to insert before + * @param insns Instructions to insert + */ + public void insert(AbstractInsnNode location, final InsnList insns) { + this.insns.insert(location, insns); + } + + /** + * Insert the supplied instruction after the specified instruction + * + * @param location Instruction to insert before + * @param insn Instruction to insert + */ + public void insert(AbstractInsnNode location, final AbstractInsnNode insn) { + this.insns.insert(location, insn); + } + /** * Insert the supplied instructions before the specified instruction * @@ -667,6 +701,16 @@ public DelegateInitialiser findDelegateInitNode() { public void insertBefore(InjectionNode location, final InsnList insns) { this.insns.insertBefore(location.getCurrentTarget(), insns); } + + /** + * Insert the supplied instruction before the specified instruction + * + * @param location Instruction to insert before + * @param insn Instruction to insert + */ + public void insertBefore(InjectionNode location, final AbstractInsnNode insn) { + this.insns.insertBefore(location.getCurrentTarget(), insn); + } /** * Insert the supplied instructions before the specified instruction @@ -678,6 +722,16 @@ public void insertBefore(AbstractInsnNode location, final InsnList insns) { this.insns.insertBefore(location, insns); } + /** + * Insert the supplied instruction before the specified instruction + * + * @param location Instruction to insert before + * @param insn Instruction to insert + */ + public void insertBefore(AbstractInsnNode location, final AbstractInsnNode insn) { + this.insns.insertBefore(location, insn); + } + /** * Replace an instruction in this target with the specified instruction and * mark the node as replaced for other injectors @@ -806,4 +860,11 @@ private LabelNode getEndLabel() { return this.end; } + public static Target of(ClassInfo classInfo, ClassNode classNode, MethodNode method) { + if (method.name.equals(Constants.CTOR)) { + return new Constructor(classInfo, classNode, method); + } + return new Target(classInfo, classNode, method); + } + } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java index 8251b25b3..2b9eb3e88 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java @@ -29,7 +29,6 @@ import java.util.Map.Entry; import org.spongepowered.asm.logging.ILogger; -import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.signature.SignatureReader; @@ -49,11 +48,13 @@ import org.spongepowered.asm.mixin.injection.ModifyConstant; import org.spongepowered.asm.mixin.injection.ModifyVariable; import org.spongepowered.asm.mixin.injection.Redirect; +import org.spongepowered.asm.mixin.injection.struct.Constructor; import org.spongepowered.asm.mixin.throwables.MixinError; import org.spongepowered.asm.mixin.transformer.ClassInfo.Field; import org.spongepowered.asm.mixin.transformer.ext.extensions.ExtensionClassExporter; import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; import org.spongepowered.asm.mixin.transformer.meta.MixinRenamed; +import org.spongepowered.asm.mixin.transformer.struct.Initialiser; import org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException; import org.spongepowered.asm.mixin.transformer.throwables.MixinApplicatorException; import org.spongepowered.asm.service.IMixinAuditTrail; @@ -110,113 +111,6 @@ enum ApplicatorPass { INJECT } - - /** - * Strategy for injecting initialiser insns - */ - enum InitialiserInjectionMode { - - /** - * Default mode, attempts to place initialisers after all other - * competing initialisers in the target ctor - */ - DEFAULT, - - /** - * Safe mode, only injects initialiser directly after the super-ctor - * invocation - */ - SAFE - - } - - /** - * Internal struct for representing a range - */ - class Range { - - /** - * Start of the range - */ - final int start; - - /** - * End of the range - */ - final int end; - - /** - * Range marker - */ - final int marker; - - /** - * Create a range with the specified values. - * - * @param start Start of the range - * @param end End of the range - * @param marker Arbitrary marker value - */ - Range(int start, int end, int marker) { - this.start = start; - this.end = end; - this.marker = marker; - } - - /** - * Range is valid if both start and end are nonzero and end is after or - * at start - * - * @return true if valid - */ - boolean isValid() { - return (this.start != 0 && this.end != 0 && this.end >= this.start); - } - - /** - * Returns true if the supplied value is between or equal to start and - * end - * - * @param value true if the range contains value - */ - boolean contains(int value) { - return value >= this.start && value <= this.end; - } - - /** - * Returns true if the supplied value is outside the range - * - * @param value true if the range does not contain value - */ - boolean excludes(int value) { - return value < this.start || value > this.end; - } - - /* (non-Javadoc) - * @see java.lang.Object#toString() - */ - @Override - public String toString() { - return String.format("Range[%d-%d,%d,valid=%s)", this.start, this.end, this.marker, this.isValid()); - } - - } - - /** - * List of opcodes which must not appear in a class initialiser, mainly a - * sanity check so that if any of the specified opcodes are found, we can - * log it as an error condition and then people can bitch at me to fix it. - * Essentially if it turns out that field initialisers can somehow make use - * of local variables, then I need to write some code to ensure that said - * locals are shifted so that they don't interfere with locals in the - * receiving constructor. - */ - protected static final int[] INITIALISER_OPCODE_BLACKLIST = { - Opcodes.RETURN, Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.IALOAD, Opcodes.LALOAD, Opcodes.FALOAD, Opcodes.DALOAD, - Opcodes.AALOAD, Opcodes.BALOAD, Opcodes.CALOAD, Opcodes.SALOAD, Opcodes.ISTORE, Opcodes.LSTORE, Opcodes.FSTORE, Opcodes.DSTORE, - Opcodes.ASTORE, Opcodes.IASTORE, Opcodes.LASTORE, Opcodes.FASTORE, Opcodes.DASTORE, Opcodes.AASTORE, Opcodes.BASTORE, Opcodes.CASTORE, - Opcodes.SASTORE - }; /** * Log more things @@ -773,265 +667,24 @@ protected final void appendInsns(MixinTargetContext mixin, MethodNode method) { * @param mixin mixin target context */ protected void applyInitialisers(MixinTargetContext mixin) { - // Try to find a suitable constructor, we need a constructor with line numbers in order to extract the initialiser - MethodNode ctor = this.getConstructor(mixin); - if (ctor == null) { - return; - } - - // Find the initialiser instructions in the candidate ctor - Deque initialiser = this.getInitialiser(mixin, ctor); + // Find the initialiser in the candidate ctor + Initialiser initialiser = mixin.getInitialiser(); if (initialiser == null || initialiser.size() == 0) { return; } - String superName = this.context.getClassInfo().getSuperName(); - // Patch the initialiser into the target class ctors - for (MethodNode method : this.targetClass.methods) { - if (Constants.CTOR.equals(method.name)) { - DelegateInitialiser superCall = Bytecode.findDelegateInit(method, superName, this.targetClass.name); - if (!superCall.isPresent || superCall.isSuper) { - method.maxStack = Math.max(method.maxStack, ctor.maxStack); - this.injectInitialiser(mixin, method, initialiser); + for (Constructor ctor : this.context.getConstructors()) { + DelegateInitialiser superCall = ctor.findDelegateInitNode(); + if (!superCall.isPresent || superCall.isSuper) { + int extraStack = initialiser.getMaxStack() - ctor.getMaxStack(); + if (extraStack > 0) { + ctor.extendStack().add(extraStack); } + initialiser.injectInto(ctor); } } } - - /** - * Finds a suitable ctor for reading the instance initialiser bytecode - * - * @param mixin mixin to search - * @return appropriate ctor or null if none found - */ - protected MethodNode getConstructor(MixinTargetContext mixin) { - MethodNode ctor = null; - - for (MethodNode mixinMethod : mixin.getMethods()) { - if (Constants.CTOR.equals(mixinMethod.name) && Bytecode.methodHasLineNumbers(mixinMethod)) { - if (ctor == null) { - ctor = mixinMethod; - } else { - // Not an error condition, just weird - this.logger.warn("Mixin {} has multiple constructors, {} was selected\n", mixin, ctor.desc); - } - } - } - - return ctor; - } - - /** - * Identifies line numbers in the supplied ctor which correspond to the - * start and end of the method body. - * - * @param ctor constructor to scan - * @return range indicating the line numbers of the specified constructor - * and the position of the superclass ctor invocation - */ - private Range getConstructorRange(MethodNode ctor) { - boolean lineNumberIsValid = false; - AbstractInsnNode endReturn = null; - - int line = 0, start = 0, end = 0, superIndex = -1; - for (Iterator iter = ctor.instructions.iterator(); iter.hasNext();) { - AbstractInsnNode insn = iter.next(); - if (insn instanceof LineNumberNode) { - line = ((LineNumberNode)insn).line; - lineNumberIsValid = true; - } else if (insn instanceof MethodInsnNode) { - if (insn.getOpcode() == Opcodes.INVOKESPECIAL && Constants.CTOR.equals(((MethodInsnNode)insn).name) && superIndex == -1) { - superIndex = ctor.instructions.indexOf(insn); - start = line; - } - } else if (insn.getOpcode() == Opcodes.PUTFIELD) { - lineNumberIsValid = false; - } else if (insn.getOpcode() == Opcodes.RETURN) { - if (lineNumberIsValid) { - end = line; - } else { - end = start; - endReturn = insn; - } - } - } - - if (endReturn != null) { - LabelNode label = new LabelNode(new Label()); - ctor.instructions.insertBefore(endReturn, label); - ctor.instructions.insertBefore(endReturn, new LineNumberNode(start, label)); - } - - return new Range(start, end, superIndex); - } - - /** - * Get insns corresponding to the instance initialiser (hopefully) from the - * supplied constructor. - * - * @param mixin mixin target context - * @param ctor constructor to inspect - * @return initialiser bytecode extracted from the supplied constructor, or - * null if the constructor range could not be parsed - */ - protected final Deque getInitialiser(MixinTargetContext mixin, MethodNode ctor) { - // - // TODO Potentially rewrite this to be less horrible. - // - - // Find the range of line numbers which corresponds to the constructor body - Range init = this.getConstructorRange(ctor); - if (!init.isValid()) { - return null; - } - - // Now we know where the constructor is, look for insns which lie OUTSIDE the method body - int line = 0; - Deque initialiser = new ArrayDeque(); - boolean gatherNodes = false; - int trimAtOpcode = -1; - LabelNode optionalInsn = null; - for (Iterator iter = ctor.instructions.iterator(init.marker); iter.hasNext();) { - AbstractInsnNode insn = iter.next(); - if (insn instanceof LineNumberNode) { - line = ((LineNumberNode)insn).line; - AbstractInsnNode next = ctor.instructions.get(ctor.instructions.indexOf(insn) + 1); - if (line == init.end && next.getOpcode() != Opcodes.RETURN) { - gatherNodes = true; - trimAtOpcode = Opcodes.RETURN; - } else { - gatherNodes = init.excludes(line); - trimAtOpcode = -1; - } - } else if (gatherNodes) { - if (optionalInsn != null) { - initialiser.add(optionalInsn); - optionalInsn = null; - } - - if (insn instanceof LabelNode) { - optionalInsn = (LabelNode)insn; - } else { - int opcode = insn.getOpcode(); - if (opcode == trimAtOpcode) { - trimAtOpcode = -1; - continue; - } - for (int ivalidOp : MixinApplicatorStandard.INITIALISER_OPCODE_BLACKLIST) { - if (opcode == ivalidOp) { - // At the moment I don't handle any transient locals because I haven't seen any in the wild, but let's avoid writing - // code which will likely break things and fix it if a real test case ever appears - throw new InvalidMixinException(mixin, "Cannot handle " + Bytecode.getOpcodeName(opcode) + " opcode (0x" - + Integer.toHexString(opcode).toUpperCase(Locale.ROOT) + ") in class initialiser"); - } - } - - initialiser.add(insn); - } - } - } - - // Check that the last insn is a PUTFIELD, if it's not then - AbstractInsnNode last = initialiser.peekLast(); - if (last != null) { - if (last.getOpcode() != Opcodes.PUTFIELD) { - throw new InvalidMixinException(mixin, "Could not parse initialiser, expected 0xB5, found 0x" - + Integer.toHexString(last.getOpcode()) + " in " + mixin); - } - } - - return initialiser; - } - - /** - * Inject initialiser code into the target constructor - * - * @param mixin mixin target context - * @param ctor target constructor - * @param initialiser initialiser instructions - */ - protected final void injectInitialiser(MixinTargetContext mixin, MethodNode ctor, Deque initialiser) { - Map labels = Bytecode.cloneLabels(ctor.instructions); - - AbstractInsnNode insn = this.findInitialiserInjectionPoint(mixin, ctor, initialiser); - if (insn == null) { - this.logger.warn("Failed to locate initialiser injection point in {}, initialiser was not mixed in.", ctor.desc); - return; - } - - for (AbstractInsnNode node : initialiser) { - if (node instanceof LabelNode) { - continue; - } - if (node instanceof JumpInsnNode) { - throw new InvalidMixinException(mixin, "Unsupported JUMP opcode in initialiser in " + mixin); - } - AbstractInsnNode imACloneNow = node.clone(labels); - ctor.instructions.insert(insn, imACloneNow); - insn = imACloneNow; - } - } - - /** - * Find the injection point for injected initialiser insns in the target - * ctor - * - * @param mixin target context for mixin being applied - * @param ctor target ctor - * @param initialiser source initialiser insns - * @return target node - */ - protected AbstractInsnNode findInitialiserInjectionPoint(MixinTargetContext mixin, MethodNode ctor, Deque initialiser) { - Set initialisedFields = new HashSet(); - for (AbstractInsnNode initialiserInsn : initialiser) { - if (initialiserInsn.getOpcode() == Opcodes.PUTFIELD) { - initialisedFields.add(MixinApplicatorStandard.fieldKey((FieldInsnNode)initialiserInsn)); - } - } - - InitialiserInjectionMode mode = this.getInitialiserInjectionMode(mixin.getEnvironment()); - String targetName = this.targetClassInfo.getName(); - String targetSuperName = this.targetClassInfo.getSuperName(); - AbstractInsnNode targetInsn = null; - - for (Iterator iter = ctor.instructions.iterator(); iter.hasNext();) { - AbstractInsnNode insn = iter.next(); - if (insn.getOpcode() == Opcodes.INVOKESPECIAL && Constants.CTOR.equals(((MethodInsnNode)insn).name)) { - String owner = ((MethodInsnNode)insn).owner; - if (owner.equals(targetName) || owner.equals(targetSuperName)) { - targetInsn = insn; - if (mode == InitialiserInjectionMode.SAFE) { - break; - } - } - } else if (insn.getOpcode() == Opcodes.PUTFIELD && mode == InitialiserInjectionMode.DEFAULT) { - String key = MixinApplicatorStandard.fieldKey((FieldInsnNode)insn); - if (initialisedFields.contains(key)) { - targetInsn = insn; - } - } - } - - return targetInsn; - } - - private InitialiserInjectionMode getInitialiserInjectionMode(MixinEnvironment environment) { - String strMode = environment.getOptionValue(Option.INITIALISER_INJECTION_MODE); - if (strMode == null) { - return InitialiserInjectionMode.DEFAULT; - } - try { - return InitialiserInjectionMode.valueOf(strMode.toUpperCase(Locale.ROOT)); - } catch (Exception ex) { - this.logger.warn("Could not parse unexpected value \"{}\" for mixin.initialiserInjectionMode, reverting to DEFAULT", strMode); - return InitialiserInjectionMode.DEFAULT; - } - } - - private static String fieldKey(FieldInsnNode fieldNode) { - return String.format("%s:%s", fieldNode.desc, fieldNode.name); - } /** * Scan for injector methods and injection points diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java index 40e6b8cd1..cfe6c5311 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java @@ -38,6 +38,7 @@ import org.objectweb.asm.tree.InsnList; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.MixinEnvironment.CompatibilityLevel; +import org.spongepowered.asm.mixin.MixinEnvironment.Feature; import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.MixinEnvironment.Phase; import org.spongepowered.asm.mixin.Overwrite; @@ -57,6 +58,7 @@ import org.spongepowered.asm.service.MixinService; import org.spongepowered.asm.util.VersionNumber; +import com.google.common.base.Joiner; import com.google.common.base.Strings; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList.Builder; @@ -210,6 +212,13 @@ interface IListener { @SerializedName("minVersion") private String version; + /** + * List of required {@link Feature} flags, can be used with or in place + * of {@link #minVersion} to provide sanity checking when a config is loaded + */ + @SerializedName("requiredFeatures") + private List requiredFeatures; + /** * Minimum compatibility level required for mixins in this set */ @@ -499,7 +508,7 @@ private boolean postInit() throws MixinInitialisationError { this.initialised = true; this.initCompatibilityLevel(); this.initExtensions(); - return this.checkVersion(); + return this.checkVersion() && this.checkFeatures(); } @SuppressWarnings("deprecation") @@ -690,6 +699,35 @@ private boolean checkVersion() throws MixinInitialisationError { return true; } + private boolean checkFeatures() throws MixinInitialisationError { + if (this.requiredFeatures == null || this.requiredFeatures.isEmpty()) { + return true; + } + + Set missingFeatures = new LinkedHashSet(); + for (String featureId : this.requiredFeatures) { + featureId = featureId.trim().toUpperCase(Locale.ROOT); + if (!Feature.isActive(featureId)) { + missingFeatures.add(featureId); + } + } + + if (missingFeatures.isEmpty()) { + return true; + } + + String strMissingFeatures = Joiner.on(", ").join(missingFeatures); + this.logger.warn("Mixin config {} requires features [{}] which are not available. The mixin config will not be applied.", + this.name, strMissingFeatures); + + if (this.required) { + throw new MixinInitialisationError("Required mixin config " + this.name + " requires features [" + strMissingFeatures + + " which are not available"); + } + + return false; + } + /** * Add a new listener * diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java index c08ec0d1d..4efafd5da 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java @@ -25,21 +25,14 @@ package org.spongepowered.asm.mixin.transformer; import java.lang.annotation.Annotation; -import java.util.ArrayList; -import java.util.Collection; -import java.util.Deque; -import java.util.Iterator; -import java.util.LinkedHashMap; -import java.util.LinkedList; -import java.util.List; -import java.util.Map; +import java.util.*; import java.util.Map.Entry; -import java.util.Set; import org.spongepowered.asm.logging.Level; import org.spongepowered.asm.logging.ILogger; import org.objectweb.asm.ConstantDynamic; import org.objectweb.asm.Handle; +import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; @@ -68,6 +61,8 @@ import org.spongepowered.asm.mixin.transformer.ClassInfo.Traversal; import org.spongepowered.asm.mixin.transformer.ext.Extensions; import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; +import org.spongepowered.asm.mixin.transformer.struct.Initialiser; +import org.spongepowered.asm.mixin.transformer.struct.Range; import org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException; import org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError; import org.spongepowered.asm.obfuscation.RemapperChain; @@ -92,7 +87,7 @@ * in the mixin to the appropriate members in the target class hierarchy. */ public class MixinTargetContext extends ClassContext implements IMixinContext { - + /** * Logger */ @@ -437,7 +432,7 @@ public InjectorGroupInfo.Map getInjectorGroups() { public boolean requireOverwriteAnnotations() { return this.mixin.getParent().requireOverwriteAnnotations(); } - + /** * Handles "re-parenting" the method supplied, changes all references to the * mixin class to refer to the target class (for field accesses and method @@ -1008,6 +1003,95 @@ private String transformMethodDescriptor(String desc) { return newDesc.append(')').append(this.transformSingleDescriptor(Type.getReturnType(desc))).toString(); } + /** + * Get insns corresponding to the instance initialiser (hopefully) from the + * supplied constructor. + * + * @return initialiser bytecode extracted from the supplied constructor, or + * null if the constructor range could not be parsed + */ + final Initialiser getInitialiser() { + // Try to find a suitable constructor, we need a constructor with line numbers in order to extract the initialiser + MethodNode ctor = this.getConstructor(); + if (ctor == null) { + return null; + } + + // Find the range of line numbers which corresponds to the constructor body + Range init = this.getConstructorRange(ctor); + if (!init.isValid()) { + return null; + } + + return new Initialiser(this, ctor, init); + } + + /** + * Finds a suitable ctor for reading the instance initialiser bytecode + * + * @return appropriate ctor or null if none found + */ + private MethodNode getConstructor() { + MethodNode ctor = null; + + for (MethodNode method : this.getMethods()) { + if (Constants.CTOR.equals(method.name) && Bytecode.methodHasLineNumbers(method)) { + if (ctor == null) { + ctor = method; + } else { + // Not an error condition, just weird + MixinTargetContext.logger.warn("Mixin {} has multiple constructors, {} was selected\n", this, ctor.desc); + } + } + } + + return ctor; + } + + /** + * Identifies line numbers in the supplied ctor which correspond to the + * start and end of the method body. + * + * @param ctor constructor to scan + * @return range indicating the line numbers of the specified constructor + * and the position of the superclass ctor invocation + */ + private Range getConstructorRange(MethodNode ctor) { + boolean lineNumberIsValid = false; + AbstractInsnNode endReturn = null; + + int line = 0, start = 0, end = 0, superIndex = -1; + for (Iterator iter = ctor.instructions.iterator(); iter.hasNext();) { + AbstractInsnNode insn = iter.next(); + if (insn instanceof LineNumberNode) { + line = ((LineNumberNode)insn).line; + lineNumberIsValid = true; + } else if (insn instanceof MethodInsnNode) { + if (insn.getOpcode() == Opcodes.INVOKESPECIAL && Constants.CTOR.equals(((MethodInsnNode)insn).name) && superIndex == -1) { + superIndex = ctor.instructions.indexOf(insn); + start = line; + } + } else if (insn.getOpcode() == Opcodes.PUTFIELD) { + lineNumberIsValid = false; + } else if (insn.getOpcode() == Opcodes.RETURN) { + if (lineNumberIsValid) { + end = line; + } else { + end = start; + endReturn = insn; + } + } + } + + if (endReturn != null) { + LabelNode label = new LabelNode(new Label()); + ctor.instructions.insertBefore(endReturn, label); + ctor.instructions.insertBefore(endReturn, new LineNumberNode(start, label)); + } + + return new Range(start, end, superIndex); + } + /** * Get a target method handle from the target class * diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java index 29e572446..3f6607a7a 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java @@ -41,6 +41,7 @@ import org.spongepowered.asm.mixin.Debug; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.MixinEnvironment.Option; +import org.spongepowered.asm.mixin.injection.struct.Constructor; import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.mixin.struct.SourceMap; import org.spongepowered.asm.mixin.transformer.ext.Extensions; @@ -51,6 +52,7 @@ import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.Bytecode; import org.spongepowered.asm.util.ClassSignature; +import org.spongepowered.asm.util.Constants; import org.spongepowered.asm.util.perf.Profiler; import org.spongepowered.asm.util.perf.Profiler.Section; @@ -224,7 +226,20 @@ public ClassNode getClassNode() { List getMethods() { return this.classNode.methods; } - + + /** + * Get the class constructors + */ + List getConstructors() { + List ctors = new ArrayList(); + for (MethodNode method : this.classNode.methods) { + if (Constants.CTOR.equals(method.name)) { + ctors.add((Constructor)this.getTargetMethod(method)); + } + } + return ctors; + } + /** * Get the class fields (from the tree) */ @@ -345,7 +360,7 @@ Target getTargetMethod(MethodNode method) { String targetName = method.name + method.desc; Target target = this.targetMethods.get(targetName); if (target == null) { - target = new Target(this.classNode, method); + target = Target.of(this.classInfo, this.classNode, method); this.targetMethods.put(targetName, target); } return target; diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java b/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java new file mode 100644 index 000000000..ddd7650d4 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java @@ -0,0 +1,241 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.transformer.struct; + +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; +import java.util.Locale; +import java.util.Map; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.JumpInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LineNumberNode; +import org.objectweb.asm.tree.MethodNode; +import org.spongepowered.asm.logging.ILogger; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.MixinEnvironment.Option; +import org.spongepowered.asm.mixin.injection.struct.Constructor; +import org.spongepowered.asm.mixin.transformer.MixinTargetContext; +import org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException; +import org.spongepowered.asm.service.MixinService; +import org.spongepowered.asm.util.Bytecode; + +public class Initialiser { + + /** + * Strategy for injecting initialiser insns + */ + public enum InjectionMode { + + /** + * Default mode, attempts to place initialisers after all other + * competing initialisers in the target ctor + */ + DEFAULT, + + /** + * Safe mode, only injects initialiser directly after the super-ctor + * invocation + */ + SAFE; + + /** + * Get the injection mode based on the the environment + * + * @param env Environment to query for the injection mode option + */ + public static InjectionMode ofEnvironment(MixinEnvironment env) { + String strMode = env.getOptionValue(Option.INITIALISER_INJECTION_MODE); + if (strMode == null) { + return Initialiser.InjectionMode.DEFAULT; + } + try { + return Initialiser.InjectionMode.valueOf(strMode.toUpperCase(Locale.ROOT)); + } catch (Exception ex) { + Initialiser.logger.warn("Could not parse unexpected value \"{}\" for mixin.initialiserInjectionMode, reverting to DEFAULT", + strMode); + return Initialiser.InjectionMode.DEFAULT; + } + } + + } + + /** + * Logger + */ + static final ILogger logger = MixinService.getService().getLogger("mixin"); + + /** + * List of opcodes which must not appear in a class initialiser, mainly a + * sanity check so that if any of the specified opcodes are found, we can + * log it as an error condition and then people can bitch at me to fix it. + * Essentially if it turns out that field initialisers can somehow make use + * of local variables, then I need to write some code to ensure that said + * locals are shifted so that they don't interfere with locals in the + * receiving constructor. + */ + protected static final int[] OPCODE_BLACKLIST = { + Opcodes.RETURN, Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.IALOAD, Opcodes.LALOAD, Opcodes.FALOAD, Opcodes.DALOAD, + Opcodes.AALOAD, Opcodes.BALOAD, Opcodes.CALOAD, Opcodes.SALOAD, Opcodes.ISTORE, Opcodes.LSTORE, Opcodes.FSTORE, Opcodes.DSTORE, + Opcodes.ASTORE, Opcodes.IASTORE, Opcodes.LASTORE, Opcodes.FASTORE, Opcodes.DASTORE, Opcodes.AASTORE, Opcodes.BASTORE, Opcodes.CASTORE, + Opcodes.SASTORE + }; + + + /** + * Mixin context which contains the source constructor + */ + private final MixinTargetContext mixin; + + /** + * Source constructor + */ + private final MethodNode ctor; + + /** + * Extracted instructions + */ + private final Deque insns = new ArrayDeque(); + + public Initialiser(MixinTargetContext mixin, MethodNode ctor, Range range) { + this.mixin = mixin; + this.ctor = ctor; + this.initInstructions(range); + } + + private void initInstructions(Range range) { + // Now we know where the constructor is, look for insns which lie OUTSIDE the method body + int line = 0; + + boolean gatherNodes = false; + int trimAtOpcode = -1; + LabelNode optionalInsn = null; + for (Iterator iter = ctor.instructions.iterator(range.marker); iter.hasNext();) { + AbstractInsnNode insn = iter.next(); + if (insn instanceof LineNumberNode) { + line = ((LineNumberNode)insn).line; + AbstractInsnNode next = ctor.instructions.get(ctor.instructions.indexOf(insn) + 1); + if (line == range.end && next.getOpcode() != Opcodes.RETURN) { + gatherNodes = true; + trimAtOpcode = Opcodes.RETURN; + } else { + gatherNodes = range.excludes(line); + trimAtOpcode = -1; + } + } else if (gatherNodes) { + if (optionalInsn != null) { + this.insns.add(optionalInsn); + optionalInsn = null; + } + + if (insn instanceof LabelNode) { + optionalInsn = (LabelNode)insn; + } else { + int opcode = insn.getOpcode(); + if (opcode == trimAtOpcode) { + trimAtOpcode = -1; + continue; + } + for (int ivalidOp : Initialiser.OPCODE_BLACKLIST) { + if (opcode == ivalidOp) { + // At the moment I don't handle any transient locals because I haven't seen any in the wild, but let's avoid writing + // code which will likely break things and fix it if a real test case ever appears + throw new InvalidMixinException(this.mixin, "Cannot handle " + Bytecode.getOpcodeName(opcode) + " opcode (0x" + + Integer.toHexString(opcode).toUpperCase(Locale.ROOT) + ") in class initialiser"); + } + } + + this.insns.add(insn); + } + } + } + + // Check that the last insn is a PUTFIELD, if it's not then + AbstractInsnNode last = this.insns.peekLast(); + if (last != null) { + if (last.getOpcode() != Opcodes.PUTFIELD) { + throw new InvalidMixinException(this.mixin, "Could not parse initialiser, expected 0xB5, found 0x" + + Integer.toHexString(last.getOpcode()) + " in " + this); + } + } + } + + /** + * Get the number of instructions in the extracted initialiser + */ + public int size() { + return this.insns.size(); + } + + /** + * Get the MAXS for the original (source) constructor + */ + public int getMaxStack() { + return this.ctor.maxStack; + } + + /** + * Get the source constructor + */ + public MethodNode getCtor() { + return this.ctor; + } + + /** + * Get the extracted instructions + */ + public Deque getInsns() { + return this.insns; + } + + /** + * Inject initialiser code into the target constructor + * + * @param ctor Constructor to inject into + */ + public void injectInto(Constructor ctor) { + AbstractInsnNode marker = ctor.findInitialiserInjectionPoint(Initialiser.InjectionMode.ofEnvironment(this.mixin.getEnvironment())); + if (marker == null) { + Initialiser.logger.warn("Failed to locate initialiser injection point in {}, initialiser was not mixed in.", ctor.getDesc()); + return; + } + + Map labels = Bytecode.cloneLabels(ctor.insns); + for (AbstractInsnNode node : this.insns) { + if (node instanceof LabelNode) { + continue; + } + if (node instanceof JumpInsnNode) { + throw new InvalidMixinException(this.mixin, "Unsupported JUMP opcode in initialiser in " + this.mixin); + } + + ctor.insertBefore(marker, node.clone(labels)); + } + } + +} diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Range.java b/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Range.java new file mode 100644 index 000000000..eb00121d9 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Range.java @@ -0,0 +1,97 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.transformer.struct; + +/** + * Struct for representing a range + */ +public class Range { + + /** + * Start of the range + */ + public final int start; + + /** + * End of the range + */ + public final int end; + + /** + * Range marker + */ + public final int marker; + + /** + * Create a range with the specified values. + * + * @param start Start of the range + * @param end End of the range + * @param marker Arbitrary marker value + */ + public Range(int start, int end, int marker) { + this.start = start; + this.end = end; + this.marker = marker; + } + + /** + * Range is valid if both start and end are nonzero and end is after or + * at start + * + * @return true if valid + */ + public boolean isValid() { + return (this.start != 0 && this.end != 0 && this.end >= this.start); + } + + /** + * Returns true if the supplied value is between or equal to start and + * end + * + * @param value true if the range contains value + */ + public boolean contains(int value) { + return value >= this.start && value <= this.end; + } + + /** + * Returns true if the supplied value is outside the range + * + * @param value true if the range does not contain value + */ + public boolean excludes(int value) { + return value < this.start || value > this.end; + } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("Range[%d-%d,%d,valid=%s)", this.start, this.end, this.marker, this.isValid()); + } + +} diff --git a/src/main/java/org/spongepowered/asm/util/Bytecode.java b/src/main/java/org/spongepowered/asm/util/Bytecode.java index d2544c416..b9588b374 100644 --- a/src/main/java/org/spongepowered/asm/util/Bytecode.java +++ b/src/main/java/org/spongepowered/asm/util/Bytecode.java @@ -43,6 +43,7 @@ import org.objectweb.asm.util.CheckClassAdapter; import org.objectweb.asm.util.TraceClassVisitor; import org.spongepowered.asm.util.asm.ASM; +import org.spongepowered.asm.util.asm.MarkerNode; import org.spongepowered.asm.util.throwables.SyntheticBridgeException; import org.spongepowered.asm.util.throwables.SyntheticBridgeException.Problem; @@ -440,6 +441,11 @@ public static String describeNode(AbstractInsnNode node, boolean listFormat) { return listFormat ? String.format(" %-14s ", "null") : "null"; } + if (node instanceof MarkerNode) { + MarkerNode marker = (MarkerNode)node; + return String.format("[%s] Marker type=%d", marker.getLabel(), marker.type); + } + if (node instanceof LabelNode) { return String.format("[%s]", ((LabelNode)node).getLabel()); } diff --git a/src/main/java/org/spongepowered/asm/util/JavaVersion.java b/src/main/java/org/spongepowered/asm/util/JavaVersion.java index 1197d121d..beb03fd46 100644 --- a/src/main/java/org/spongepowered/asm/util/JavaVersion.java +++ b/src/main/java/org/spongepowered/asm/util/JavaVersion.java @@ -97,6 +97,21 @@ public abstract class JavaVersion { */ public static final double JAVA_18 = 18.0; + /** + * Version number for Java 19 + */ + public static final double JAVA_19 = 19.0; + + /** + * Version number for Java 20 + */ + public static final double JAVA_20 = 20.0; + + /** + * Version number for Java 21 + */ + public static final double JAVA_21 = 21.0; + private static double current = 0.0; private JavaVersion() {} diff --git a/src/main/java/org/spongepowered/asm/util/asm/MarkerNode.java b/src/main/java/org/spongepowered/asm/util/asm/MarkerNode.java new file mode 100644 index 000000000..228e3e987 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/util/asm/MarkerNode.java @@ -0,0 +1,56 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.util.asm; + +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.tree.LabelNode; + +/** + * A label node used as a marker in the bytecode. Does not actually visit the + * label when visited. + */ +public class MarkerNode extends LabelNode { + + /** + * Marks the end of the initialiser in a constructor + */ + public static final int INITIALISER_TAIL = 1; + + /** + * The type for this marker + */ + public final int type; + + public MarkerNode(int type) { + super(null); + this.type = type; + } + + @Override + public void accept(MethodVisitor methodVisitor) { + // Noop + } + +} From f6951762a7ab505c0e02f95c0e26a691140685a9 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sun, 21 Jan 2024 18:28:27 +0000 Subject: [PATCH 17/84] Track source of mixin configs and use source id in conformed names --- .../platform/MixinPlatformAgentDefault.java | 2 +- .../launch/platform/MixinPlatformManager.java | 15 ++++--- .../container/ContainerHandleURI.java | 11 +++++ .../container/ContainerHandleVirtual.java | 10 +++++ .../platform/container/IContainerHandle.java | 4 +- .../org/spongepowered/asm/mixin/Mixins.java | 33 +++++++++++--- .../asm/mixin/extensibility/IMixinConfig.java | 17 ++++++- .../extensibility/IMixinConfigSource.java | 44 +++++++++++++++++++ .../asm/mixin/injection/code/MethodSlice.java | 1 - .../injection/points/ConstructorHead.java | 1 - .../asm/mixin/transformer/Config.java | 36 ++++++++++++--- .../asm/mixin/transformer/MethodMapper.java | 33 ++++++++++---- .../asm/mixin/transformer/MixinConfig.java | 36 +++++++++++++-- .../transformer/MixinCoprocessorAccessor.java | 2 +- .../mixin/transformer/MixinTargetContext.java | 4 +- .../container/ContainerHandleModLauncher.java | 15 +++++++ .../ContainerHandleModLauncherEx.java | 15 +++++++ 17 files changed, 242 insertions(+), 37 deletions(-) create mode 100644 src/main/java/org/spongepowered/asm/mixin/extensibility/IMixinConfigSource.java diff --git a/src/main/java/org/spongepowered/asm/launch/platform/MixinPlatformAgentDefault.java b/src/main/java/org/spongepowered/asm/launch/platform/MixinPlatformAgentDefault.java index 926626cc7..e55143094 100644 --- a/src/main/java/org/spongepowered/asm/launch/platform/MixinPlatformAgentDefault.java +++ b/src/main/java/org/spongepowered/asm/launch/platform/MixinPlatformAgentDefault.java @@ -43,7 +43,7 @@ public void prepare() { String mixinConfigs = this.handle.getAttribute(ManifestAttributes.MIXINCONFIGS); if (mixinConfigs != null) { for (String config : mixinConfigs.split(",")) { - this.manager.addConfig(config.trim()); + this.manager.addConfig(config.trim(), this.handle); } } diff --git a/src/main/java/org/spongepowered/asm/launch/platform/MixinPlatformManager.java b/src/main/java/org/spongepowered/asm/launch/platform/MixinPlatformManager.java index 457210e95..4897363ad 100644 --- a/src/main/java/org/spongepowered/asm/launch/platform/MixinPlatformManager.java +++ b/src/main/java/org/spongepowered/asm/launch/platform/MixinPlatformManager.java @@ -32,15 +32,16 @@ import java.util.Locale; import java.util.Map; -import org.spongepowered.asm.logging.ILogger; import org.spongepowered.asm.launch.platform.container.IContainerHandle; +import org.spongepowered.asm.logging.ILogger; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.MixinEnvironment.CompatibilityLevel; import org.spongepowered.asm.mixin.MixinEnvironment.Phase; -import org.spongepowered.asm.service.MixinService; -import org.spongepowered.asm.service.ServiceVersionError; import org.spongepowered.asm.mixin.Mixins; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigSource; import org.spongepowered.asm.mixin.throwables.MixinError; +import org.spongepowered.asm.service.MixinService; +import org.spongepowered.asm.service.ServiceVersionError; //import com.google.common.collect.ImmutableList; @@ -166,7 +167,7 @@ public final void prepare(CommandLineOptions args) { } for (String config : args.getConfigs()) { - this.addConfig(config); + this.addConfig(config, null); } } @@ -256,10 +257,10 @@ final void setCompatibilityLevel(String level) { * * @param config config resource name, does not require a leading / */ - final void addConfig(String config) { + final void addConfig(String config, IMixinConfigSource source) { if (config.endsWith(".json")) { - MixinPlatformManager.logger.debug("Registering mixin config: {}", config); - Mixins.addConfiguration(config); + MixinPlatformManager.logger.debug("Registering mixin config: {} source={}", config, source); + Mixins.addConfiguration(config, source); } else if (config.contains(".json@")) { throw new MixinError("Setting config phase via manifest is no longer supported: " + config + ". Specify target in config instead"); } diff --git a/src/main/java/org/spongepowered/asm/launch/platform/container/ContainerHandleURI.java b/src/main/java/org/spongepowered/asm/launch/platform/container/ContainerHandleURI.java index 95108fd70..c65328de6 100644 --- a/src/main/java/org/spongepowered/asm/launch/platform/container/ContainerHandleURI.java +++ b/src/main/java/org/spongepowered/asm/launch/platform/container/ContainerHandleURI.java @@ -53,6 +53,17 @@ public ContainerHandleURI(URI uri) { this.attributes = MainAttributes.of(uri); } + @Override + public String getId() { + // Not enough information for this + return null; + } + + @Override + public String getDescription() { + return this.uri.toString(); + } + /** * Get the URI for this handle */ diff --git a/src/main/java/org/spongepowered/asm/launch/platform/container/ContainerHandleVirtual.java b/src/main/java/org/spongepowered/asm/launch/platform/container/ContainerHandleVirtual.java index 0e7129bcc..7db24cb02 100644 --- a/src/main/java/org/spongepowered/asm/launch/platform/container/ContainerHandleVirtual.java +++ b/src/main/java/org/spongepowered/asm/launch/platform/container/ContainerHandleVirtual.java @@ -62,6 +62,16 @@ public ContainerHandleVirtual(String name) { this.name = name; } + @Override + public String getId() { + return this.name; + } + + @Override + public String getDescription() { + return this.toString(); + } + /** * Get the name of this container */ diff --git a/src/main/java/org/spongepowered/asm/launch/platform/container/IContainerHandle.java b/src/main/java/org/spongepowered/asm/launch/platform/container/IContainerHandle.java index 0d05af9d8..cc18d7303 100644 --- a/src/main/java/org/spongepowered/asm/launch/platform/container/IContainerHandle.java +++ b/src/main/java/org/spongepowered/asm/launch/platform/container/IContainerHandle.java @@ -26,13 +26,15 @@ import java.util.Collection; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigSource; + /** * Interface for container handles. Previously resources considered by Mixin * were indexed by URI but in order to provide more flexibility the container * handle system now wraps URIs in a more expressive object, and provides * support for both virtual containers and nested container resolution. */ -public interface IContainerHandle { +public interface IContainerHandle extends IMixinConfigSource { /** * Retrieve the value of attribute with the specified name, or null if not diff --git a/src/main/java/org/spongepowered/asm/mixin/Mixins.java b/src/main/java/org/spongepowered/asm/mixin/Mixins.java index e3f8164b2..1d9f3ef69 100644 --- a/src/main/java/org/spongepowered/asm/mixin/Mixins.java +++ b/src/main/java/org/spongepowered/asm/mixin/Mixins.java @@ -32,6 +32,7 @@ import org.spongepowered.asm.logging.ILogger; import org.spongepowered.asm.launch.GlobalProperties; import org.spongepowered.asm.launch.GlobalProperties.Keys; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigSource; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import org.spongepowered.asm.mixin.transformer.ClassInfo; import org.spongepowered.asm.mixin.transformer.Config; @@ -66,15 +67,25 @@ public final class Mixins { private Mixins() {} +// /** +// * Add multiple configurations +// * +// * @param configFiles config resources to add +// */ +// public static void addConfigurations(String... configFiles) { +// Mixins.addConfigurations(configFiles, null); +// } + /** * Add multiple configurations * * @param configFiles config resources to add + * @param source source of the configuration resource */ - public static void addConfigurations(String... configFiles) { + public static void addConfigurations(String[] configFiles, IMixinConfigSource source) { MixinEnvironment fallback = MixinEnvironment.getDefaultEnvironment(); for (String configFile : configFiles) { - Mixins.createConfiguration(configFile, fallback); + Mixins.createConfiguration(configFile, fallback, source); } } @@ -84,20 +95,30 @@ public static void addConfigurations(String... configFiles) { * @param configFile path to configuration resource */ public static void addConfiguration(String configFile) { - Mixins.createConfiguration(configFile, MixinEnvironment.getDefaultEnvironment()); + Mixins.createConfiguration(configFile, null, null); + } + + /** + * Add a mixin configuration resource + * + * @param configFile path to configuration resource + * @param source source of the configuration resource + */ + public static void addConfiguration(String configFile, IMixinConfigSource source) { + Mixins.createConfiguration(configFile, MixinEnvironment.getDefaultEnvironment(), source); } @Deprecated static void addConfiguration(String configFile, MixinEnvironment fallback) { - Mixins.createConfiguration(configFile, fallback); + Mixins.createConfiguration(configFile, fallback, null); } @SuppressWarnings("deprecation") - private static void createConfiguration(String configFile, MixinEnvironment fallback) { + private static void createConfiguration(String configFile, MixinEnvironment fallback, IMixinConfigSource source) { Config config = null; try { - config = Config.create(configFile, fallback); + config = Config.create(configFile, fallback, source); } catch (Exception ex) { Mixins.logger.error("Error encountered reading mixin config " + configFile + ": " + ex.getClass().getName() + " " + ex.getMessage(), ex); } diff --git a/src/main/java/org/spongepowered/asm/mixin/extensibility/IMixinConfig.java b/src/main/java/org/spongepowered/asm/mixin/extensibility/IMixinConfig.java index 611d6371a..c74a05bc3 100644 --- a/src/main/java/org/spongepowered/asm/mixin/extensibility/IMixinConfig.java +++ b/src/main/java/org/spongepowered/asm/mixin/extensibility/IMixinConfig.java @@ -52,7 +52,22 @@ public interface IMixinConfig { * @return the config filename (resource name) */ public abstract String getName(); - + + /** + * Get the source which initially provide this configuration object, for + * example the jar which specified it. + * + * @return the config source, or null if not provided by the service + */ + public abstract IMixinConfigSource getSource(); + + /** + * Get the id of the source id with all non-alpha characters removed. If the + * source is null or the source's id is null then this method returns null + * also. + */ + public abstract String getCleanSourceId(); + /** * Get the package containing all mixin classes * diff --git a/src/main/java/org/spongepowered/asm/mixin/extensibility/IMixinConfigSource.java b/src/main/java/org/spongepowered/asm/mixin/extensibility/IMixinConfigSource.java new file mode 100644 index 000000000..96d5f6a3e --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/extensibility/IMixinConfigSource.java @@ -0,0 +1,44 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.extensibility; + +/** + * Interface for loaded mixin configurations + */ +public interface IMixinConfigSource { + + /** + * Get the identifier for this source + */ + public abstract String getId(); + + /** + * Plain text description of the config source, can be as descriptive as + * necessary to help the user identify the source, for example could be the + * full path to the source item, or something more descriptive. + */ + public abstract String getDescription(); + +} diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java index 7e43f8bdd..aa2d3d4c8 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java @@ -43,7 +43,6 @@ import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.mixin.injection.throwables.InjectionError; import org.spongepowered.asm.mixin.injection.throwables.InvalidSliceException; -import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.service.MixinService; import org.spongepowered.asm.util.Annotations; diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java index 87f6ef6e4..21edce2b6 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java @@ -37,7 +37,6 @@ import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionPointException; import org.spongepowered.asm.service.MixinService; -import org.spongepowered.asm.util.Bytecode; import org.spongepowered.asm.util.asm.MarkerNode; /** diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/Config.java b/src/main/java/org/spongepowered/asm/mixin/transformer/Config.java index f0a1d177f..759b9bdbc 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/Config.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/Config.java @@ -31,6 +31,7 @@ import org.spongepowered.asm.launch.MixinInitialisationError; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.extensibility.IMixinConfig; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigSource; import org.spongepowered.asm.service.MixinService; import com.google.common.base.Strings; @@ -129,23 +130,37 @@ public int hashCode() { return this.name.hashCode(); } +// /** +// * Factory method, create a config from the specified config file and fail +// * over to the specified environment if no selector is present in the config +// * +// * @param configFile config resource +// * @param outer failover environment +// * @return new config or null if invalid config version +// */ +// @Deprecated +// public static Config create(String configFile, MixinEnvironment outer) { +// return Config.create(configFile, outer, null); +// } + /** * Factory method, create a config from the specified config file and fail * over to the specified environment if no selector is present in the config * * @param configFile config resource * @param outer failover environment + * @param source config source * @return new config or null if invalid config version */ @Deprecated - public static Config create(String configFile, MixinEnvironment outer) { + public static Config create(String configFile, MixinEnvironment outer, IMixinConfigSource source) { Config config = Config.allConfigs.get(configFile); if (config != null) { return config; } try { - config = MixinConfig.create(configFile, outer); + config = MixinConfig.create(configFile, outer, source); if (config != null) { Config.allConfigs.put(config.getName(), config); } @@ -161,7 +176,7 @@ public static Config create(String configFile, MixinEnvironment outer) { if (!Strings.isNullOrEmpty(parent)) { Config parentConfig; try { - parentConfig = Config.create(parent, outer); + parentConfig = Config.create(parent, outer, source); if (parentConfig != null) { if (!config.get().assignParent(parentConfig)) { config = null; @@ -178,14 +193,25 @@ public static Config create(String configFile, MixinEnvironment outer) { return config; } +// /** +// * Factory method, create a config from the specified config resource +// * +// * @param configFile config resource +// * @return new config or null if invalid config version +// */ +// public static Config create(String configFile) { +// return Config.create(configFile, (IMixinConfigSource)null); +// } + /** * Factory method, create a config from the specified config resource * * @param configFile config resource + * @param source config source * @return new config or null if invalid config version */ - public static Config create(String configFile) { - return MixinConfig.create(configFile, MixinEnvironment.getDefaultEnvironment()); + public static Config create(String configFile, IMixinConfigSource source) { + return MixinConfig.create(configFile, MixinEnvironment.getDefaultEnvironment(), source); } } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java index e8720c658..ba36a5258 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java @@ -95,7 +95,7 @@ public void remapHandlerMethod(MixinInfo mixin, MethodNode handler, Method metho return; } - String handlerName = this.getHandlerName((MixinMethodNode)handler); + String handlerName = this.getHandlerName(mixin, (MixinMethodNode)handler); handler.name = method.conform(handlerName); } @@ -105,11 +105,11 @@ public void remapHandlerMethod(MixinInfo mixin, MethodNode handler, Method metho * @param method mixin method * @return conformed handler name */ - public String getHandlerName(MixinMethodNode method) { + public String getHandlerName(MixinInfo mixin, MixinMethodNode method) { String prefix = InjectionInfo.getInjectorPrefix(method.getInjectorAnnotation()); String classUID = MethodMapper.getClassUID(method.getOwner().getClassRef()); String methodUID = MethodMapper.getMethodUID(method.name, method.desc, !method.isSurrogate()); - return String.format("%s$%s%s$%s", prefix, classUID, methodUID, method.name); + return String.format("%s$%s%s%s$%s", prefix, MethodMapper.getMixinSourceId(mixin, "$"), classUID, methodUID, method.name); } /** @@ -121,10 +121,10 @@ public String getHandlerName(MixinMethodNode method) { * method name prefix * @return Unique method name */ - public String getUniqueName(MethodNode method, String sessionId, boolean preservePrefix) { + public String getUniqueName(MixinInfo mixin, MethodNode method, String sessionId, boolean preservePrefix) { String uniqueIndex = Integer.toHexString(this.nextUniqueMethodIndex++); - String pattern = preservePrefix ? "%2$s_$md$%1$s$%3$s" : "md%s$%s$%s"; - return String.format(pattern, sessionId.substring(30), method.name, uniqueIndex); + String pattern = preservePrefix ? "%3$s_$md$%2$s%1$s$%4$s" : "md%s$%s%s$%s"; + return String.format(pattern, sessionId.substring(30), MethodMapper.getMixinSourceId(mixin, "$"), method.name, uniqueIndex); } /** @@ -134,9 +134,26 @@ public String getUniqueName(MethodNode method, String sessionId, boolean preserv * @param sessionId Session ID, for uniqueness * @return Unique field name */ - public String getUniqueName(FieldNode field, String sessionId) { + public String getUniqueName(MixinInfo mixin, FieldNode field, String sessionId) { String uniqueIndex = Integer.toHexString(this.nextUniqueFieldIndex++); - return String.format("fd%s$%s$%s", sessionId.substring(30), field.name, uniqueIndex); + return String.format("fd%s$%s%s$%s", sessionId.substring(30), MethodMapper.getMixinSourceId(mixin, "$"), field.name, uniqueIndex); + } + + /** + * Get clean sourceId from mixin + * + * @param mixin mixin info + * @return clean source id with dollar suffix or empty string + */ + private static String getMixinSourceId(MixinInfo mixin, String separator) { + String sourceId = mixin.getConfig().getCleanSourceId(); + if (sourceId == null) { + return ""; + } + if (sourceId.length() > 12) { + sourceId = sourceId.substring(0, 12); + } + return String.format("%s%s", sourceId, separator); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java index cfe6c5311..b2c188d58 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java @@ -44,6 +44,7 @@ import org.spongepowered.asm.mixin.Overwrite; import org.spongepowered.asm.mixin.extensibility.IMixinConfig; import org.spongepowered.asm.mixin.extensibility.IMixinConfigPlugin; +import org.spongepowered.asm.mixin.extensibility.IMixinConfigSource; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.Inject; import org.spongepowered.asm.mixin.injection.InjectionPoint; @@ -337,6 +338,11 @@ interface IListener { */ private transient String name; + /** + * The source of this mixin config + */ + private transient IMixinConfigSource source; + /** * Name of the {@link IMixinConfigPlugin} to hook onto this MixinConfig */ @@ -411,7 +417,7 @@ private MixinConfig() {} * returned, or false if initialisation failed and the config should * be discarded */ - private boolean onLoad(IMixinService service, String name, MixinEnvironment fallbackEnvironment) { + private boolean onLoad(IMixinService service, String name, MixinEnvironment fallbackEnvironment, IMixinConfigSource source) { this.service = service; this.name = name; @@ -972,6 +978,30 @@ MixinConfig getParent() { public String getName() { return this.name; } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.extensibility.IMixinConfig#getSource() + */ + @Override + public IMixinConfigSource getSource() { + return this.source; + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.extensibility.IMixinConfig + * #getCleanSourceId() + */ + @Override + public String getCleanSourceId() { + if (this.source == null) { + return null; + } + String sourceId = this.source.getId(); + if (sourceId == null) { + return null; + } + return sourceId.replaceAll("[^A-Za-z]", ""); + } /** * Get the package containing all mixin classes @@ -1312,7 +1342,7 @@ public int compareTo(MixinConfig other) { * @param outer fallback environment * @return new Config */ - static Config create(String configFile, MixinEnvironment outer) { + static Config create(String configFile, MixinEnvironment outer, IMixinConfigSource source) { try { IMixinService service = MixinService.getService(); InputStream resource = service.getResourceAsStream(configFile); @@ -1320,7 +1350,7 @@ static Config create(String configFile, MixinEnvironment outer) { throw new IllegalArgumentException(String.format("The specified resource '%s' was invalid or could not be read", configFile)); } MixinConfig config = new Gson().fromJson(new InputStreamReader(resource), MixinConfig.class); - if (config.onLoad(service, configFile, outer)) { + if (config.onLoad(service, configFile, outer, source)) { return config.getHandle(); } return null; diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java index 57d2d3dc8..4fb5ef6b7 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java @@ -132,7 +132,7 @@ private Method getAccessorMethod(MixinInfo mixin, MethodNode methodNode, ClassIn // Normally the target will be renamed when the mixin is conformed to the target, if we get here // without this happening then we will end up invoking an undecorated method, which is bad! if (!method.isConformed()) { - String uniqueName = targetClass.getMethodMapper().getUniqueName(methodNode, this.sessionId, true); + String uniqueName = targetClass.getMethodMapper().getUniqueName(mixin, methodNode, this.sessionId, true); method.conform(uniqueName); } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java index 4efafd5da..52607eba9 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java @@ -1372,7 +1372,7 @@ void postApply(String transformedName, ClassNode targetClass) { * @return unique method name */ String getUniqueName(MethodNode method, boolean preservePrefix) { - return this.targetClassInfo.getMethodMapper().getUniqueName(method, this.sessionId, preservePrefix); + return this.targetClassInfo.getMethodMapper().getUniqueName(this.mixin, method, this.sessionId, preservePrefix); } /** @@ -1383,7 +1383,7 @@ String getUniqueName(MethodNode method, boolean preservePrefix) { * @return unique field name */ String getUniqueName(FieldNode field) { - return this.targetClassInfo.getMethodMapper().getUniqueName(field, this.sessionId); + return this.targetClassInfo.getMethodMapper().getUniqueName(this.mixin, field, this.sessionId); } /** diff --git a/src/modlauncher/java/org/spongepowered/asm/launch/platform/container/ContainerHandleModLauncher.java b/src/modlauncher/java/org/spongepowered/asm/launch/platform/container/ContainerHandleModLauncher.java index 56774262b..6742587e4 100644 --- a/src/modlauncher/java/org/spongepowered/asm/launch/platform/container/ContainerHandleModLauncher.java +++ b/src/modlauncher/java/org/spongepowered/asm/launch/platform/container/ContainerHandleModLauncher.java @@ -49,6 +49,21 @@ public Resource(String name, Path path) { this.path = path; } + @Override + public String getId() { + String name = this.name; + int lastDotPos = name.lastIndexOf('.'); + if (lastDotPos > 0) { + name = name.substring(0, lastDotPos); + } + return name; + } + + @Override + public String getDescription() { + return this.path.toAbsolutePath().toString(); + } + public String getName() { return this.name; } diff --git a/src/modlauncher9/java/org/spongepowered/asm/launch/platform/container/ContainerHandleModLauncherEx.java b/src/modlauncher9/java/org/spongepowered/asm/launch/platform/container/ContainerHandleModLauncherEx.java index a6553ef5d..5d4a9b45a 100644 --- a/src/modlauncher9/java/org/spongepowered/asm/launch/platform/container/ContainerHandleModLauncherEx.java +++ b/src/modlauncher9/java/org/spongepowered/asm/launch/platform/container/ContainerHandleModLauncherEx.java @@ -45,6 +45,21 @@ public SecureJarResource(SecureJar resource) { this.jar = resource; } + @Override + public String getId() { + String name = this.jar.name(); + int lastDotPos = name.lastIndexOf('.'); + if (lastDotPos > 0) { + name = name.substring(0, lastDotPos); + } + return name; + } + + @Override + public String getDescription() { + return this.jar.getRootPath().toAbsolutePath().toString(); + } + public String getName() { return this.jar.name(); } From d5fc6623c0ca3b3af45363b63a3cf99785a53aaf Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sun, 21 Jan 2024 18:28:40 +0000 Subject: [PATCH 18/84] Add support for injectors in interface mixins --- .../tools/obfuscation/SupportedOptions.java | 4 +- .../obfuscation/interfaces/IMessagerEx.java | 3 ++ .../asm/mixin/MixinEnvironment.java | 11 ++-- .../asm/mixin/gen/AccessorGenerator.java | 8 ++- .../gen/AccessorGeneratorMethodProxy.java | 8 ++- .../asm/mixin/injection/code/Injector.java | 15 ++++-- .../transformer/MixinApplicatorInterface.java | 12 ++++- .../asm/mixin/transformer/MixinConfig.java | 5 +- .../transformer/MixinCoprocessorAccessor.java | 3 +- .../MixinPreProcessorInterface.java | 51 +++++++++++++++---- .../mixin/transformer/MixinTargetContext.java | 1 + 11 files changed, 94 insertions(+), 27 deletions(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/SupportedOptions.java b/src/ap/java/org/spongepowered/tools/obfuscation/SupportedOptions.java index ac0a03a9e..1383eba36 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/SupportedOptions.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/SupportedOptions.java @@ -50,6 +50,7 @@ public final class SupportedOptions { public static final String PLUGIN_VERSION = "pluginVersion"; public static final String QUIET = "quiet"; public static final String SHOW_MESSAGE_TYPES = "showMessageTypes"; + public static final String DISABLE_INTERFACE_MIXINS = "disableInterfaceMixins"; private SupportedOptions() {} @@ -70,7 +71,8 @@ public static Set getAllOptions() { SupportedOptions.MAPPING_TYPES, SupportedOptions.PLUGIN_VERSION, SupportedOptions.QUIET, - SupportedOptions.SHOW_MESSAGE_TYPES + SupportedOptions.SHOW_MESSAGE_TYPES, + SupportedOptions.DISABLE_INTERFACE_MIXINS ); options.addAll( ObfuscationServices.getInstance().getSupportedOptions() diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/interfaces/IMessagerEx.java b/src/ap/java/org/spongepowered/tools/obfuscation/interfaces/IMessagerEx.java index 85df92ec9..282a1f5b4 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/interfaces/IMessagerEx.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/interfaces/IMessagerEx.java @@ -425,6 +425,9 @@ public static void applyOptions(CompilerEnvironment env, IOptionProvider options // Apply the "quiet" option if specified, or if in dev env MessageType.INFO.setEnabled(!(env.isDevelopmentEnvironment() || "true".equalsIgnoreCase(options.getOption(SupportedOptions.QUIET)))); + // Selectively enable injector-in-interface as an error based on user option + MessageType.INJECTOR_IN_INTERFACE.setEnabled(options.getOption(SupportedOptions.DISABLE_INTERFACE_MIXINS, false)); + // Legacy option if ("error".equalsIgnoreCase(options.getOption(SupportedOptions.OVERWRITE_ERROR_LEVEL))) { MessageType.OVERWRITE_DOCS.setKind(Kind.ERROR); diff --git a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java index 6daa57c43..16e7d816d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java +++ b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java @@ -1038,14 +1038,13 @@ public static enum Feature { @Override public boolean isAvailable() { - return false; -// return CompatibilityLevel.getMaxEffective().supports(LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES); + return CompatibilityLevel.getMaxEffective().supports(LanguageFeatures.METHODS_IN_INTERFACES); } -// @Override -// public boolean isEnabled() { -// return MixinEnvironment.getCompatibilityLevel().supports(LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES); -// } + @Override + public boolean isEnabled() { + return MixinEnvironment.getCompatibilityLevel().supports(LanguageFeatures.METHODS_IN_INTERFACES); + } }; diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGenerator.java b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGenerator.java index 98d52d644..977761a73 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGenerator.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGenerator.java @@ -44,13 +44,19 @@ public abstract class AccessorGenerator { protected final AccessorInfo info; /** - * True for static field, false for instance field + * True for static target, false for instance target */ protected final boolean targetIsStatic; + + /** + * True if the target is in an interface + */ + protected final boolean targetIsInterface; public AccessorGenerator(AccessorInfo info, boolean isStatic) { this.info = info; this.targetIsStatic = isStatic; + this.targetIsInterface = info.getClassInfo().isInterface(); } protected void checkModifiers() { diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorMethodProxy.java b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorMethodProxy.java index c91baece1..3239cd780 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorMethodProxy.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorMethodProxy.java @@ -76,8 +76,12 @@ public MethodNode generate() { } Bytecode.loadArgs(this.argTypes, method.instructions, this.info.isStatic ? 0 : 1); boolean isPrivate = Bytecode.hasFlag(this.targetMethod, Opcodes.ACC_PRIVATE); - int opcode = this.targetIsStatic ? Opcodes.INVOKESTATIC : (isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL); - method.instructions.add(new MethodInsnNode(opcode, this.info.getClassNode().name, this.targetMethod.name, this.targetMethod.desc, false)); + boolean isSynthetic = Bytecode.hasFlag(this.targetMethod, Opcodes.ACC_SYNTHETIC); + int opcode = this.targetIsStatic ? Opcodes.INVOKESTATIC : + this.targetIsInterface ? (isSynthetic && isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEINTERFACE) : + isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL; + method.instructions.add(new MethodInsnNode(opcode, this.info.getClassNode().name, this.targetMethod.name, this.targetMethod.desc, + this.targetIsInterface)); method.instructions.add(new InsnNode(this.returnType.getOpcode(Opcodes.IRETURN))); return method; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java index c1cfc3d22..7a1bf6118 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java @@ -204,6 +204,11 @@ public String toString() { * True if the callback method is static */ protected final boolean isStatic; + + /** + * True if the callback method is in an interface + */ + protected final boolean isInterface; /** * Make a new CallbackInjector for the supplied InjectionInfo @@ -220,6 +225,7 @@ public Injector(InjectionInfo info, String annotationType) { this.methodArgs = Type.getArgumentTypes(this.methodNode.desc); this.returnType = Type.getReturnType(this.methodNode.desc); this.isStatic = Bytecode.isStatic(this.methodNode); + this.isInterface = Bytecode.hasFlag(this.classNode, Opcodes.ACC_INTERFACE); } @Override @@ -439,9 +445,12 @@ protected AbstractInsnNode invokeHandler(InsnList insns) { * @return injected insn node */ protected AbstractInsnNode invokeHandler(InsnList insns, MethodNode handler) { - boolean isPrivate = (handler.access & Opcodes.ACC_PRIVATE) != 0; - int invokeOpcode = this.isStatic ? Opcodes.INVOKESTATIC : isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL; - MethodInsnNode insn = new MethodInsnNode(invokeOpcode, this.classNode.name, handler.name, handler.desc, false); + boolean isPrivate = Bytecode.hasFlag(handler, Opcodes.ACC_PRIVATE); + boolean isSynthetic = Bytecode.hasFlag(handler, Opcodes.ACC_SYNTHETIC); + int invokeOpcode = this.isStatic ? Opcodes.INVOKESTATIC : + this.isInterface ? (isSynthetic && isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEINTERFACE) : + isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL; + MethodInsnNode insn = new MethodInsnNode(invokeOpcode, this.classNode.name, handler.name, handler.desc, this.isInterface); insns.add(insn); this.info.addCallbackInvocation(handler); return insn; diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java index d8e90e3e6..3f47ceb04 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java @@ -28,6 +28,7 @@ import org.objectweb.asm.tree.FieldNode; import org.objectweb.asm.tree.MethodNode; +import org.spongepowered.asm.mixin.MixinEnvironment.Feature; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException; import org.spongepowered.asm.mixin.transformer.ClassInfo.Field; @@ -96,7 +97,11 @@ protected void applyInitialisers(MixinTargetContext mixin) { */ @Override protected void prepareInjections(MixinTargetContext mixin) { - // disabled for interface mixins + if (Feature.INJECTORS_IN_INTERFACE_MIXINS.isEnabled()) { + super.prepareInjections(mixin); + return; + } + for (MethodNode method : this.targetClass.methods) { try { InjectionInfo injectInfo = InjectionInfo.parse(mixin, method); @@ -117,7 +122,10 @@ protected void prepareInjections(MixinTargetContext mixin) { */ @Override protected void applyInjections(MixinTargetContext mixin) { - // Do nothing + if (Feature.INJECTORS_IN_INTERFACE_MIXINS.isEnabled()) { + super.applyInjections(mixin); + return; + } } } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java index b2c188d58..f8cd2b3a0 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java @@ -686,7 +686,10 @@ private boolean checkVersion() throws MixinInitialisationError { if (this.parent != null && this.parent.version != null) { return true; } - this.logger.error("Mixin config {} does not specify \"minVersion\" property", this.name); + // requiredFeatures can be used instead of minVersion going forward + if (this.requiredFeatures == null || this.requiredFeatures.isEmpty()) { + this.logger.error("Mixin config {} does not specify \"minVersion\" or \"requiredFeatures\" property", this.name); + } } VersionNumber minVersion = VersionNumber.parse(this.version); diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java index 4fb5ef6b7..423c16394 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinCoprocessorAccessor.java @@ -145,7 +145,8 @@ private static void createProxy(MethodNode methodNode, ClassInfo targetClass, Me Type[] args = Type.getArgumentTypes(methodNode.desc); Type returnType = Type.getReturnType(methodNode.desc); Bytecode.loadArgs(args, methodNode.instructions, 0); - methodNode.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, targetClass.getName(), method.getName(), methodNode.desc, false)); + methodNode.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, targetClass.getName(), method.getName(), methodNode.desc, + targetClass.isInterface())); methodNode.instructions.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN))); methodNode.maxStack = Bytecode.getFirstNonArgLocalIndex(args, false); methodNode.maxLocals = 0; diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorInterface.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorInterface.java index 1638355c4..9b048687b 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorInterface.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorInterface.java @@ -29,11 +29,14 @@ import org.objectweb.asm.tree.FieldNode; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.MixinEnvironment.CompatibilityLevel; +import org.spongepowered.asm.mixin.MixinEnvironment.Feature; +import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.transformer.ClassInfo.Method; import org.spongepowered.asm.mixin.transformer.MixinInfo.MixinClassNode; import org.spongepowered.asm.mixin.transformer.MixinInfo.MixinMethodNode; import org.spongepowered.asm.mixin.transformer.throwables.InvalidInterfaceMixinException; import org.spongepowered.asm.util.Bytecode; +import org.spongepowered.asm.util.Bytecode.Visibility; import org.spongepowered.asm.util.LanguageFeatures; /** @@ -59,21 +62,49 @@ class MixinPreProcessorInterface extends MixinPreProcessorStandard { */ @Override protected void prepareMethod(MixinMethodNode mixinMethod, Method method) { - // Userland interfaces should not have non-public methods except for lambda bodies - if (!Bytecode.hasFlag(mixinMethod, Opcodes.ACC_PUBLIC)) { - if (!Bytecode.hasFlag(mixinMethod, Opcodes.ACC_SYNTHETIC)) { - throw new InvalidInterfaceMixinException(this.mixin, String.format("Interface mixin contains a non-public method! Found %s in %s", - method, this.mixin)); - } - CompatibilityLevel requiredLevel = CompatibilityLevel.requiredFor(LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES); - if (MixinEnvironment.getCompatibilityLevel().isLessThan(requiredLevel)) { + boolean isPublic = Bytecode.hasFlag(mixinMethod, Opcodes.ACC_PUBLIC); + Feature injectorsInInterfaceMixins = Feature.INJECTORS_IN_INTERFACE_MIXINS; + CompatibilityLevel currentLevel = MixinEnvironment.getCompatibilityLevel(); + CompatibilityLevel requiredLevelSynthetic = CompatibilityLevel.requiredFor(LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES); + + if (!isPublic && mixinMethod.isSynthetic()) { + if (currentLevel.isLessThan(requiredLevelSynthetic)) { throw new InvalidInterfaceMixinException(this.mixin, String.format( "Interface mixin contains a synthetic private method but compatibility level %s is required! Found %s in %s", - requiredLevel, method, this.mixin)); + requiredLevelSynthetic, method, this.mixin)); } + // Private synthetic is ok, do not process further + return; } - super.prepareMethod(mixinMethod, method); + if (!isPublic) { + CompatibilityLevel requiredLevelPrivate = CompatibilityLevel.requiredFor(LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES); + if (currentLevel.isLessThan(requiredLevelPrivate)) { + throw new InvalidInterfaceMixinException(this.mixin, String.format( + "Interface mixin contains a private method but compatibility level %s is required! Found %s in %s", + requiredLevelPrivate, method, this.mixin)); + } + } + + AnnotationNode injectorAnnotation = InjectionInfo.getInjectorAnnotation(this.mixin, mixinMethod); + if (injectorAnnotation == null) { + super.prepareMethod(mixinMethod, method); + return; + } + + if (injectorsInInterfaceMixins.isAvailable() && !injectorsInInterfaceMixins.isEnabled()) { + throw new InvalidInterfaceMixinException(this.mixin, String.format( + "Interface mixin contains an injector but Feature.INJECTORS_IN_INTERFACE_MIXINS is disabled! Found %s in %s", + method, this.mixin)); + } + + // Make injectors private synthetic if the current runtime supports it + if (isPublic + && !currentLevel.supports(LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES) + && currentLevel.supports(LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES)) { + Bytecode.setVisibility(mixinMethod, Visibility.PRIVATE); + mixinMethod.access |= Opcodes.ACC_SYNTHETIC; + } } /* (non-Javadoc) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java index 52607eba9..f5879cbac 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java @@ -545,6 +545,7 @@ private void transformLVT(MethodNode method) { localVarActivity.next("var=%s", local.name); local.desc = this.transformSingleDescriptor(Type.getType(local.desc)); + local.signature = null; } localVarActivity.end(); } From 3104a5b0b5119caf096d3ed3175ccfcb6db4ea98 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sun, 21 Jan 2024 18:29:00 +0000 Subject: [PATCH 19/84] Revert method change which breaks MixinExtras --- .../asm/mixin/injection/struct/InjectionInfo.java | 6 +++--- .../mixin/injection/struct/ModifyConstantInjectionInfo.java | 6 ++++-- 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java index a4ee28be1..f52034330 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java @@ -314,7 +314,7 @@ protected void readAnnotation() { activity.next("Validate Targets"); this.targets.validate(this.expectedCallbackCount, this.requiredCallbackCount); activity.next("Parse Injection Points"); - this.parseInjectionPoints(); + this.parseInjectionPoints(this.injectionPointAnnotations); activity.next("Parse Injector"); this.injector = this.parseInjector(this.annotation); activity.end(); @@ -370,8 +370,8 @@ protected void parseSelectors() { this.targets.parse(selectors); } - protected void parseInjectionPoints() { - this.injectionPoints.addAll(InjectionPoint.parse(this, this.injectionPointAnnotations)); + protected void parseInjectionPoints(List ats) { + this.injectionPoints.addAll(InjectionPoint.parse(this, ats)); } // stub diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java index 08f88124f..7a0b16e8f 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java @@ -24,6 +24,8 @@ */ package org.spongepowered.asm.mixin.injection.struct; +import java.util.List; + import org.objectweb.asm.Type; import org.objectweb.asm.tree.AnnotationNode; import org.objectweb.asm.tree.MethodNode; @@ -62,10 +64,10 @@ protected void readInjectionPoints() { } @Override - protected void parseInjectionPoints() { + protected void parseInjectionPoints(List ats) { Type returnType = Type.getReturnType(this.method.desc); - for (AnnotationNode at : this.injectionPointAnnotations) { + for (AnnotationNode at : ats) { this.injectionPoints.add(new BeforeConstant(this.getMixin(), at, returnType.getDescriptor())); } } From 3c8c9132c951e80390ef0564de0b18e79c9942a8 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Wed, 24 Jan 2024 21:42:32 +0000 Subject: [PATCH 20/84] Fix checkstyle and stale imports --- .../tools/obfuscation/ObfuscationEnvironment.java | 4 ---- .../java/org/spongepowered/tools/obfuscation/TargetMap.java | 2 -- .../spongepowered/tools/obfuscation/mirror/TypeHandle.java | 2 ++ .../tools/obfuscation/mirror/TypeHandleSimulated.java | 6 ++++-- .../tools/obfuscation/mirror/TypeReference.java | 3 --- .../tools/obfuscation/validation/TargetValidator.java | 3 ++- 6 files changed, 8 insertions(+), 12 deletions(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java b/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java index fa42dcb87..206eed830 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/ObfuscationEnvironment.java @@ -30,10 +30,6 @@ import javax.annotation.processing.Filer; import javax.annotation.processing.Messager; -import javax.lang.model.element.TypeElement; -import javax.lang.model.type.DeclaredType; -import javax.lang.model.type.TypeKind; -import javax.lang.model.type.TypeMirror; import javax.tools.Diagnostic.Kind; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorRemappable; diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/TargetMap.java b/src/ap/java/org/spongepowered/tools/obfuscation/TargetMap.java index 80c8e2148..f3d37e68b 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/TargetMap.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/TargetMap.java @@ -38,8 +38,6 @@ import java.util.List; import java.util.Set; -import javax.lang.model.element.TypeElement; - import org.spongepowered.tools.obfuscation.mirror.TypeHandle; import org.spongepowered.tools.obfuscation.mirror.TypeReference; diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java index 5c2f1e6ac..95b9a02a8 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java @@ -295,6 +295,8 @@ public boolean isNotInterface() { /** * Gets whether this handle is a supertype of the other handle + * + * @param other the TypeHandle to compare with */ public boolean isSuperTypeOf(TypeHandle other) { List superTypes = new ArrayList<>(); diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleSimulated.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleSimulated.java index bdb44612e..f4ef0e116 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleSimulated.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandleSimulated.java @@ -160,7 +160,8 @@ public MappingMethod getMappingMethod(String name, String desc) { } - private static MethodHandle findMethodRecursive(TypeHandle target, String name, String signature, String rawSignature, boolean matchCase, ITypeHandleProvider typeProvider) { + private static MethodHandle findMethodRecursive(TypeHandle target, String name, String signature, String rawSignature, boolean matchCase, + ITypeHandleProvider typeProvider) { TypeElement elem = target.getTargetElement(); if (elem == null) { return null; @@ -186,7 +187,8 @@ private static MethodHandle findMethodRecursive(TypeHandle target, String name, return TypeHandleSimulated.findMethodRecursive(superClass, name, signature, rawSignature, matchCase, typeProvider); } - private static MethodHandle findMethodRecursive(TypeMirror target, String name, String signature, String rawSignature, boolean matchCase, ITypeHandleProvider typeProvider) { + private static MethodHandle findMethodRecursive(TypeMirror target, String name, String signature, String rawSignature, boolean matchCase, + ITypeHandleProvider typeProvider) { if (!(target instanceof DeclaredType)) { return null; } diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeReference.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeReference.java index 41ea5f452..91504e91f 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeReference.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeReference.java @@ -28,9 +28,6 @@ import java.io.Serializable; -import javax.annotation.processing.ProcessingEnvironment; -import javax.lang.model.element.TypeElement; - /** * Soft wrapper for a {@link TypeHandle} so that we can serialise it */ diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java b/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java index 00a0e41d7..9e9c424af 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/validation/TargetValidator.java @@ -111,7 +111,8 @@ private void validateClassMixin(TypeElement mixin, Collection target } private boolean validateSuperClass(TypeHandle targetType, TypeHandle superClass) { - return targetType.isImaginary() || targetType.isSimulated() || superClass.isSuperTypeOf(targetType) || this.checkMixinsFor(targetType, superClass); + return targetType.isImaginary() || targetType.isSimulated() || superClass.isSuperTypeOf(targetType) + || this.checkMixinsFor(targetType, superClass); } private boolean checkMixinsFor(TypeHandle targetType, TypeHandle superMixin) { From 738638f09258fe66cc54251be69a26be015c1edb Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Wed, 3 Apr 2024 16:18:33 +0100 Subject: [PATCH 21/84] Fix: Strip synthetic LVT entries after exporting. (#135) They are useful for the dumped classes, but they have incorrect labels and as such they confuse IDEA's debugger. --- .../mixin/transformer/DefaultExtensions.java | 2 + .../ext/extensions/ExtensionLVTCleaner.java | 85 +++++++++++++++++++ 2 files changed, 87 insertions(+) create mode 100644 src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionLVTCleaner.java diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/DefaultExtensions.java b/src/main/java/org/spongepowered/asm/mixin/transformer/DefaultExtensions.java index cc4031d11..85a68882d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/DefaultExtensions.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/DefaultExtensions.java @@ -30,6 +30,7 @@ import org.spongepowered.asm.mixin.transformer.ext.extensions.ExtensionCheckClass; import org.spongepowered.asm.mixin.transformer.ext.extensions.ExtensionCheckInterfaces; import org.spongepowered.asm.mixin.transformer.ext.extensions.ExtensionClassExporter; +import org.spongepowered.asm.mixin.transformer.ext.extensions.ExtensionLVTCleaner; import org.spongepowered.asm.service.ISyntheticClassInfo; import org.spongepowered.asm.util.IConsumer; @@ -54,6 +55,7 @@ public void accept(ISyntheticClassInfo item) { extensions.add(new InnerClassGenerator(registryDelegate, nestHostCoprocessor)); extensions.add(new ExtensionClassExporter(environment)); + extensions.add(new ExtensionLVTCleaner()); extensions.add(new ExtensionCheckClass()); extensions.add(new ExtensionCheckInterfaces()); } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionLVTCleaner.java b/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionLVTCleaner.java new file mode 100644 index 000000000..6729b5360 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionLVTCleaner.java @@ -0,0 +1,85 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.transformer.ext.extensions; + +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodNode; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.transformer.ext.IExtension; +import org.spongepowered.asm.mixin.transformer.ext.ITargetClassContext; +import org.spongepowered.asm.util.Locals; + +import java.util.Iterator; + +/** + * Strips synthetic local variables from the LVT after exporting to avoid debuggers becoming confused by them. + */ +public class ExtensionLVTCleaner implements IExtension { + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.transformer.ext.IExtension#checkActive( + * org.spongepowered.asm.mixin.MixinEnvironment) + */ + @Override + public boolean checkActive(MixinEnvironment environment) { + return true; + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.transformer.IMixinTransformerModule + * #preApply(org.spongepowered.asm.mixin.transformer.TargetClassContext) + */ + @Override + public void preApply(ITargetClassContext context) { + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.transformer.IMixinTransformerModule + * #postApply(org.spongepowered.asm.mixin.transformer.TargetClassContext) + */ + @Override + public void postApply(ITargetClassContext context) { + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.transformer.ext.IExtension + * #export(org.spongepowered.asm.mixin.MixinEnvironment, + * java.lang.String, boolean, org.objectweb.asm.tree.ClassNode) + */ + @Override + public void export(MixinEnvironment env, String name, boolean force, ClassNode classNode) { + for (MethodNode methodNode : classNode.methods) { + if (methodNode.localVariables != null) { + for (Iterator it = methodNode.localVariables.iterator(); it.hasNext(); ) { + if (it.next() instanceof Locals.SyntheticLocalVariableNode) { + it.remove(); + } + } + } + } + } + +} From eb44a338812bf80557e434935f289977a50b6b3c Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 3 Apr 2024 16:25:51 +0100 Subject: [PATCH 22/84] Set maven pom name --- build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/build.gradle b/build.gradle index a023b524e..128c6a55f 100644 --- a/build.gradle +++ b/build.gradle @@ -498,6 +498,7 @@ publishing { } pom { + name = "Fabric Mixin" description = 'Fabric Mixin is a trait/mixin and bytecode weaving framework for Java using ASM.' url = 'https://github.com/FabricMC/Mixin' From 989efeb1a454e38711d95ec36139fb445be14e53 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 3 Apr 2024 16:26:05 +0100 Subject: [PATCH 23/84] Bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index a44a2e815..38c85c356 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ packaging=jar description=Mixin (Fabric fork) url=https://fabricmc.net organization=FabricMC -buildVersion=0.13.1 +buildVersion=0.13.2 upstreamMixinVersion=0.8.5 buildType=RELEASE asmVersion=9.6 From a2091792e8bb9b0951cef82528da9127f2c3b451 Mon Sep 17 00:00:00 2001 From: modmuss50 Date: Wed, 3 Apr 2024 16:36:02 +0100 Subject: [PATCH 24/84] Fix indent --- build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index 128c6a55f..5ad8caa5a 100644 --- a/build.gradle +++ b/build.gradle @@ -498,7 +498,7 @@ publishing { } pom { - name = "Fabric Mixin" + name = "Fabric Mixin" description = 'Fabric Mixin is a trait/mixin and bytecode weaving framework for Java using ASM.' url = 'https://github.com/FabricMC/Mixin' From 320b74c2d4bd513f7e58c8e36a718660cd746a80 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Thu, 11 Apr 2024 13:52:14 +0100 Subject: [PATCH 25/84] Fix: Reset Unique field counters on Mixin application. (#136) They previously caused hotswaps to fail due to the number increasing on each application. --- .../spongepowered/asm/mixin/transformer/MethodMapper.java | 8 ++++++++ .../asm/mixin/transformer/TargetClassContext.java | 3 ++- 2 files changed, 10 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java index 369dcff8b..88454531c 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java @@ -76,6 +76,14 @@ public MethodMapper(MixinEnvironment env, ClassInfo info) { public ClassInfo getClassInfo() { return this.info; } + + /** + * Resets the counters to prepare for application, which can happen multiple times due to hotswap. + */ + public void reset() { + this.nextUniqueMethodIndex = 0; + this.nextUniqueFieldIndex = 0; + } /** * Conforms an injector handler method diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java index 1e3b32ff8..8f4a33f68 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java @@ -407,9 +407,10 @@ void applyMixins() { } /** - * Run extensions before apply + * Run extensions before apply and clean up any global state in case this is a hotswap */ private void preApply() { + this.getClassInfo().getMethodMapper().reset(); this.extensions.preApply(this); } From ba8066133ee6db395ab3d776e15a75aad077f923 Mon Sep 17 00:00:00 2001 From: modmuss Date: Fri, 12 Apr 2024 09:21:26 +0100 Subject: [PATCH 26/84] Bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 38c85c356..077526256 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ packaging=jar description=Mixin (Fabric fork) url=https://fabricmc.net organization=FabricMC -buildVersion=0.13.2 +buildVersion=0.13.3 upstreamMixinVersion=0.8.5 buildType=RELEASE asmVersion=9.6 From e779303628af31233ce848687036a954a57110c7 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Tue, 16 Apr 2024 22:38:47 +0200 Subject: [PATCH 27/84] Use 0 as class reader flags in Modlauncher bytecode provider (#137) Relates to neoforged/NeoForge#768 --- .../org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java b/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java index 9cbbce1c4..0840a565b 100644 --- a/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java +++ b/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java @@ -235,7 +235,7 @@ public ClassNode getClassNode(String name, boolean runTransformers) throws Class if (classBytes != null && classBytes.length != 0) { ClassNode classNode = new ClassNode(); ClassReader classReader = new MixinClassReader(classBytes, canonicalName); - classReader.accept(classNode, ClassReader.EXPAND_FRAMES); + classReader.accept(classNode, 0); return classNode; } From 2f35debf1028da808b771546c9834f35d9e518df Mon Sep 17 00:00:00 2001 From: modmuss Date: Tue, 16 Apr 2024 21:39:27 +0100 Subject: [PATCH 28/84] Bump version --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 077526256..30cc75f30 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ packaging=jar description=Mixin (Fabric fork) url=https://fabricmc.net organization=FabricMC -buildVersion=0.13.3 +buildVersion=0.13.4 upstreamMixinVersion=0.8.5 buildType=RELEASE asmVersion=9.6 From 73def3578b9bf49103303beb9f211c74d066ea52 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 3 Feb 2024 17:45:39 +0000 Subject: [PATCH 29/84] Restore ctor init merging to previous logic, introduce PRE_BODY enforcer Closes #658 --- .../asm/mixin/injection/code/IInsnListEx.java | 9 +- .../asm/mixin/injection/code/InsnListEx.java | 8 ++ .../injection/points/ConstructorHead.java | 48 +++---- .../mixin/injection/struct/Constructor.java | 128 +++++++++++++++--- .../transformer/MixinApplicatorStandard.java | 4 +- .../mixin/transformer/MixinTargetContext.java | 69 +++------- .../mixin/transformer/struct/Initialiser.java | 62 ++------- .../struct/{Range.java => InsnRange.java} | 71 +++++++++- .../asm/util/asm/MarkerNode.java | 5 + 9 files changed, 253 insertions(+), 151 deletions(-) rename src/main/java/org/spongepowered/asm/mixin/transformer/struct/{Range.java => InsnRange.java} (53%) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java index 584269201..73979e59c 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java @@ -48,7 +48,14 @@ public enum SpecialNodeType { /** * The location for injected initialisers in a constructor */ - INITIALISER_INJECTION_POINT + INITIALISER_INJECTION_POINT, + + /** + * The location after field initialisers but before the first + * constructor body instruction, requires line numbers to be present in + * the target class + */ + CTOR_BODY } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java index 97802871e..0a6a26a39 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java @@ -142,6 +142,14 @@ public AbstractInsnNode getSpecialNode(SpecialNodeType type) { } return null; + case CTOR_BODY: + if (this.target instanceof Constructor) { + AbstractInsnNode beforeBody = ((Constructor)this.target).findFirstBodyInsn(); + if (this.contains(beforeBody)) { + return beforeBody; + } + } + return null; default: return null; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java index 21edce2b6..804f7369d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/ConstructorHead.java @@ -37,7 +37,6 @@ import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionPointException; import org.spongepowered.asm.service.MixinService; -import org.spongepowered.asm.util.asm.MarkerNode; /** *

Like {@link MethodHead HEAD}, this injection point can be used to specify @@ -56,9 +55,15 @@ *

*
enforce=POST_DELEGATE
*
Select the instruction immediately after the delegate constructor
- *
enforce=POST_INIT
- *
Select the instruction immediately after field initialisers, this - * condition can fail if no initialisers are found.
+ *
enforce=POST_MIXIN
+ *
Select the instruction immediately after all mixin-initialised field + * initialisers, this is similar to POST_DELEGATE if no applied mixins have + * initialisers for target class fields, except that the injection point + * will be after any mixin-supplied initialisers.
+ *
enforce=PRE_BODY
+ *
Selects the first instruction in the target method body, as determined + * by the line numbers. If the target method does not have line numbers + * available, the result is equivalent to POST_DELEGATE.
*
* *

Example default behaviour:

@@ -81,7 +86,7 @@ public class ConstructorHead extends MethodHead { static enum Enforce { /** - * Use default behaviour + * Use default behaviour (POST_INIT) */ DEFAULT, @@ -93,7 +98,12 @@ static enum Enforce { /** * Enforce selection of post-initialiser insn */ - POST_INIT; + POST_INIT, + + /** + * Enforce selection of the first body insn + */ + PRE_BODY; } @@ -149,30 +159,10 @@ public boolean find(String desc, InsnList insns, Collection no return true; } - AbstractInsnNode postInit = xinsns.getSpecialNode(SpecialNodeType.INITIALISER_INJECTION_POINT); + SpecialNodeType type = this.enforce == Enforce.PRE_BODY ? SpecialNodeType.CTOR_BODY : SpecialNodeType.INITIALISER_INJECTION_POINT; + AbstractInsnNode postInit = xinsns.getSpecialNode(type); - if (postInit instanceof MarkerNode) { - AbstractInsnNode postPostInit = postInit.getNext(); - if (postDelegate == postInit || postDelegate == postPostInit) { - // If the marker is immedately after the delegate call, then we haven't actually - // found any initialiser insns. This is fine as long as we're not Enforce.POST_INIT - // which we assume will only be set by the user when they want to be sure that the - // initialisers are properly detected. Set this null to trigger the enforcement or - // fallback behaviour below. - postInit = null; - } else { - postInit = postPostInit; - } - } - - if (this.enforce == Enforce.POST_INIT || postInit != null) { - if (postInit == null) { - if (this.verbose) { - this.logger.warn("@At(\"{}\") on {}{} targetting {} failed for enforce=POST_INIT because no initialiser was found", - this.getAtCode(), this.method.name, this.method.desc, xinsns); - } - return false; - } + if (postInit != null) { nodes.add(postInit); return true; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Constructor.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Constructor.java index 52fdab74a..943edadf2 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Constructor.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Constructor.java @@ -24,18 +24,23 @@ */ package org.spongepowered.asm.mixin.injection.struct; +import java.util.Deque; import java.util.HashSet; import java.util.Iterator; import java.util.Set; +import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; import org.objectweb.asm.tree.FieldInsnNode; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LineNumberNode; import org.objectweb.asm.tree.MethodInsnNode; import org.objectweb.asm.tree.MethodNode; import org.spongepowered.asm.mixin.transformer.ClassInfo; import org.spongepowered.asm.mixin.transformer.struct.Initialiser; +import org.spongepowered.asm.mixin.transformer.struct.InsnRange; import org.spongepowered.asm.util.Bytecode; import org.spongepowered.asm.util.Constants; import org.spongepowered.asm.util.asm.MarkerNode; @@ -52,9 +57,18 @@ public class Constructor extends Target { private DelegateInitialiser delegateInitialiser; private MarkerNode initialiserInjectionPoint; + private MarkerNode bodyStart; + + private final String targetName; + private final String targetSuperName; + + private Set mixinInitialisedFields = new HashSet(); public Constructor(ClassInfo classInfo, ClassNode classNode, MethodNode method) { super(classInfo, classNode, method); + + this.targetName = this.classInfo.getName(); + this.targetSuperName = this.classInfo.getSuperName(); } /** @@ -73,6 +87,40 @@ public DelegateInitialiser findDelegateInitNode() { return this.delegateInitialiser; } + + /** + * Get whether this constructor should have mixin initialisers injected into + * it based on whether a delegate initialiser call is absent or is a call to + * super() + */ + public boolean isInjectable() { + DelegateInitialiser delegateInit = this.findDelegateInitNode(); + return !delegateInit.isPresent || delegateInit.isSuper; + } + + /** + * Inspect an incoming initialiser to determine which fields are touched by + * the mixin. This is then used in {@link #findInitialiserInjectionPoint} to + * determine the instruction after which the mixin initialisers will be + * placed + * + * @param initialiser the initialiser to inspect + */ + public void inspect(Initialiser initialiser) { + if (this.initialiserInjectionPoint != null) { + throw new IllegalStateException("Attempted to inspect an incoming initialiser after the injection point was already determined"); + } + + for (AbstractInsnNode initialiserInsn : initialiser.getInsns()) { + if (initialiserInsn.getOpcode() == Opcodes.PUTFIELD) { + FieldInsnNode fieldInsn = (FieldInsnNode)initialiserInsn; + if (!fieldInsn.owner.equals(this.targetName)) { + continue; + } + this.mixinInitialisedFields.add(Constructor.fieldKey((FieldInsnNode)initialiserInsn)); + } + } + } /** * Find the injection point for injected initialiser insns in the target @@ -90,26 +138,12 @@ public AbstractInsnNode findInitialiserInjectionPoint(Initialiser.InjectionMode return this.initialiserInjectionPoint; } - String targetName = this.classInfo.getName(); - String targetSuperName = this.classInfo.getSuperName(); - - Set initialisedFields = new HashSet(); - for (AbstractInsnNode initialiserInsn : this.insns) { - if (initialiserInsn.getOpcode() == Opcodes.PUTFIELD) { - FieldInsnNode fieldInsn = (FieldInsnNode)initialiserInsn; - if (!fieldInsn.owner.equals(targetName)) { - continue; - } - initialisedFields.add(Constructor.fieldKey((FieldInsnNode)initialiserInsn)); - } - } - AbstractInsnNode lastInsn = null; for (Iterator iter = this.insns.iterator(); iter.hasNext();) { AbstractInsnNode insn = iter.next(); if (insn.getOpcode() == Opcodes.INVOKESPECIAL && Constants.CTOR.equals(((MethodInsnNode)insn).name)) { String owner = ((MethodInsnNode)insn).owner; - if (owner.equals(targetName) || owner.equals(targetSuperName)) { + if (owner.equals(this.targetName) || owner.equals(targetSuperName)) { lastInsn = insn; if (mode == Initialiser.InjectionMode.SAFE) { break; @@ -117,7 +151,7 @@ public AbstractInsnNode findInitialiserInjectionPoint(Initialiser.InjectionMode } } else if (insn.getOpcode() == Opcodes.PUTFIELD && mode == Initialiser.InjectionMode.DEFAULT) { String key = Constructor.fieldKey((FieldInsnNode)insn); - if (initialisedFields.contains(key)) { + if (this.mixinInitialisedFields.contains(key)) { lastInsn = insn; } } @@ -132,8 +166,70 @@ public AbstractInsnNode findInitialiserInjectionPoint(Initialiser.InjectionMode return this.initialiserInjectionPoint; } + /** + * Find the injection point + */ + public AbstractInsnNode findFirstBodyInsn() { + if (this.bodyStart == null) { + this.bodyStart = new MarkerNode(MarkerNode.BODY_START); + InsnRange range = Constructor.getRange(this.method); + if (range.isValid()) { + Deque body = range.apply(this.insns, true); + this.insertBefore(body.pop(), this.bodyStart); + } else if (range.marker > -1) { + this.insert(this.insns.get(range.marker), this.bodyStart); + } else { + this.bodyStart = null; + } + } + return this.bodyStart; + } + private static String fieldKey(FieldInsnNode fieldNode) { return String.format("%s:%s", fieldNode.desc, fieldNode.name); } + /** + * Identifies line numbers in the supplied ctor which correspond to the + * start and end of the method body. + * + * @param ctor constructor to scan + * @return range indicating the line numbers of the specified constructor + * and the position of the superclass ctor invocation + */ + public static InsnRange getRange(MethodNode ctor) { + boolean lineNumberIsValid = false; + AbstractInsnNode endReturn = null; + + int line = 0, start = 0, end = 0, delegateIndex = -1; + for (Iterator iter = ctor.instructions.iterator(); iter.hasNext();) { + AbstractInsnNode insn = iter.next(); + if (insn instanceof LineNumberNode) { + line = ((LineNumberNode)insn).line; + lineNumberIsValid = true; + } else if (insn instanceof MethodInsnNode) { + if (insn.getOpcode() == Opcodes.INVOKESPECIAL && Constants.CTOR.equals(((MethodInsnNode)insn).name) && delegateIndex == -1) { + delegateIndex = ctor.instructions.indexOf(insn); + start = line; + } + } else if (insn.getOpcode() == Opcodes.PUTFIELD) { + lineNumberIsValid = false; + } else if (insn.getOpcode() == Opcodes.RETURN) { + if (lineNumberIsValid) { + end = line; + } else { + end = start; + endReturn = insn; + } + } + } + + if (endReturn != null) { + LabelNode label = new LabelNode(new Label()); + ctor.instructions.insertBefore(endReturn, label); + ctor.instructions.insertBefore(endReturn, new LineNumberNode(start, label)); + } + + return new InsnRange(start, end, delegateIndex); + } } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java index 2b9eb3e88..d5350c291 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java @@ -61,7 +61,6 @@ import org.spongepowered.asm.service.MixinService; import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.Bytecode; -import org.spongepowered.asm.util.Bytecode.DelegateInitialiser; import org.spongepowered.asm.util.Constants; import org.spongepowered.asm.util.ConstraintParser; import org.spongepowered.asm.util.ConstraintParser.Constraint; @@ -675,8 +674,7 @@ protected void applyInitialisers(MixinTargetContext mixin) { // Patch the initialiser into the target class ctors for (Constructor ctor : this.context.getConstructors()) { - DelegateInitialiser superCall = ctor.findDelegateInitNode(); - if (!superCall.isPresent || superCall.isSuper) { + if (ctor.isInjectable()) { int extraStack = initialiser.getMaxStack() - ctor.getMaxStack(); if (extraStack > 0) { ctor.extendStack().add(extraStack); diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java index f5879cbac..83a1b6226 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java @@ -32,7 +32,6 @@ import org.spongepowered.asm.logging.ILogger; import org.objectweb.asm.ConstantDynamic; import org.objectweb.asm.Handle; -import org.objectweb.asm.Label; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; @@ -45,6 +44,7 @@ import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import org.spongepowered.asm.mixin.gen.AccessorInfo; import org.spongepowered.asm.mixin.injection.Inject; +import org.spongepowered.asm.mixin.injection.struct.Constructor; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectorGroupInfo; import org.spongepowered.asm.mixin.injection.struct.Target; @@ -62,7 +62,7 @@ import org.spongepowered.asm.mixin.transformer.ext.Extensions; import org.spongepowered.asm.mixin.transformer.meta.MixinMerged; import org.spongepowered.asm.mixin.transformer.struct.Initialiser; -import org.spongepowered.asm.mixin.transformer.struct.Range; +import org.spongepowered.asm.mixin.transformer.struct.InsnRange; import org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException; import org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError; import org.spongepowered.asm.obfuscation.RemapperChain; @@ -178,6 +178,11 @@ public class MixinTargetContext extends ClassContext implements IMixinContext { * upgraded if the version is below this value */ private int minRequiredClassVersion = CompatibilityLevel.JAVA_6.getClassVersion(); + + /** + * The mixin's initialiser + */ + private Initialiser initialiser; /** * ctor @@ -199,6 +204,8 @@ public class MixinTargetContext extends ClassContext implements IMixinContext { InnerClassGenerator icg = context.getExtensions().getGenerator(InnerClassGenerator.class); this.innerClasses = icg.getInnerClasses(this.mixin, this.getTargetClassRef()); + + this.initialiser = this.findInitialiser(); } /** @@ -1012,6 +1019,10 @@ private String transformMethodDescriptor(String desc) { * null if the constructor range could not be parsed */ final Initialiser getInitialiser() { + return this.initialiser; + } + + private Initialiser findInitialiser() { // Try to find a suitable constructor, we need a constructor with line numbers in order to extract the initialiser MethodNode ctor = this.getConstructor(); if (ctor == null) { @@ -1019,12 +1030,18 @@ final Initialiser getInitialiser() { } // Find the range of line numbers which corresponds to the constructor body - Range init = this.getConstructorRange(ctor); + InsnRange init = Constructor.getRange(ctor); if (!init.isValid()) { return null; } - return new Initialiser(this, ctor, init); + Initialiser initialiser = new Initialiser(this, ctor, init); + for (Constructor targetCtor : this.getTarget().getConstructors()) { + if (targetCtor.isInjectable()) { + targetCtor.inspect(initialiser); + } + } + return initialiser; } /** @@ -1049,50 +1066,6 @@ private MethodNode getConstructor() { return ctor; } - /** - * Identifies line numbers in the supplied ctor which correspond to the - * start and end of the method body. - * - * @param ctor constructor to scan - * @return range indicating the line numbers of the specified constructor - * and the position of the superclass ctor invocation - */ - private Range getConstructorRange(MethodNode ctor) { - boolean lineNumberIsValid = false; - AbstractInsnNode endReturn = null; - - int line = 0, start = 0, end = 0, superIndex = -1; - for (Iterator iter = ctor.instructions.iterator(); iter.hasNext();) { - AbstractInsnNode insn = iter.next(); - if (insn instanceof LineNumberNode) { - line = ((LineNumberNode)insn).line; - lineNumberIsValid = true; - } else if (insn instanceof MethodInsnNode) { - if (insn.getOpcode() == Opcodes.INVOKESPECIAL && Constants.CTOR.equals(((MethodInsnNode)insn).name) && superIndex == -1) { - superIndex = ctor.instructions.indexOf(insn); - start = line; - } - } else if (insn.getOpcode() == Opcodes.PUTFIELD) { - lineNumberIsValid = false; - } else if (insn.getOpcode() == Opcodes.RETURN) { - if (lineNumberIsValid) { - end = line; - } else { - end = start; - endReturn = insn; - } - } - } - - if (endReturn != null) { - LabelNode label = new LabelNode(new Label()); - ctor.instructions.insertBefore(endReturn, label); - ctor.instructions.insertBefore(endReturn, new LineNumberNode(start, label)); - } - - return new Range(start, end, superIndex); - } - /** * Get a target method handle from the target class * diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java b/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java index ddd7650d4..78ea3540b 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java @@ -24,9 +24,7 @@ */ package org.spongepowered.asm.mixin.transformer.struct; -import java.util.ArrayDeque; import java.util.Deque; -import java.util.Iterator; import java.util.Locale; import java.util.Map; @@ -34,7 +32,6 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.JumpInsnNode; import org.objectweb.asm.tree.LabelNode; -import org.objectweb.asm.tree.LineNumberNode; import org.objectweb.asm.tree.MethodNode; import org.spongepowered.asm.logging.ILogger; import org.spongepowered.asm.mixin.MixinEnvironment; @@ -118,59 +115,28 @@ public static InjectionMode ofEnvironment(MixinEnvironment env) { private final MethodNode ctor; /** - * Extracted instructions + * Filtered instructions */ - private final Deque insns = new ArrayDeque(); + private Deque insns; - public Initialiser(MixinTargetContext mixin, MethodNode ctor, Range range) { + public Initialiser(MixinTargetContext mixin, MethodNode ctor, InsnRange range) { this.mixin = mixin; this.ctor = ctor; this.initInstructions(range); } - private void initInstructions(Range range) { + private void initInstructions(InsnRange range) { // Now we know where the constructor is, look for insns which lie OUTSIDE the method body - int line = 0; - - boolean gatherNodes = false; - int trimAtOpcode = -1; - LabelNode optionalInsn = null; - for (Iterator iter = ctor.instructions.iterator(range.marker); iter.hasNext();) { - AbstractInsnNode insn = iter.next(); - if (insn instanceof LineNumberNode) { - line = ((LineNumberNode)insn).line; - AbstractInsnNode next = ctor.instructions.get(ctor.instructions.indexOf(insn) + 1); - if (line == range.end && next.getOpcode() != Opcodes.RETURN) { - gatherNodes = true; - trimAtOpcode = Opcodes.RETURN; - } else { - gatherNodes = range.excludes(line); - trimAtOpcode = -1; - } - } else if (gatherNodes) { - if (optionalInsn != null) { - this.insns.add(optionalInsn); - optionalInsn = null; - } - - if (insn instanceof LabelNode) { - optionalInsn = (LabelNode)insn; - } else { - int opcode = insn.getOpcode(); - if (opcode == trimAtOpcode) { - trimAtOpcode = -1; - continue; - } - for (int ivalidOp : Initialiser.OPCODE_BLACKLIST) { - if (opcode == ivalidOp) { - // At the moment I don't handle any transient locals because I haven't seen any in the wild, but let's avoid writing - // code which will likely break things and fix it if a real test case ever appears - throw new InvalidMixinException(this.mixin, "Cannot handle " + Bytecode.getOpcodeName(opcode) + " opcode (0x" - + Integer.toHexString(opcode).toUpperCase(Locale.ROOT) + ") in class initialiser"); - } - } - - this.insns.add(insn); + this.insns = range.apply(this.ctor.instructions, false); + + for (AbstractInsnNode insn : this.insns) { + int opcode = insn.getOpcode(); + for (int ivalidOp : Initialiser.OPCODE_BLACKLIST) { + if (opcode == ivalidOp) { + // At the moment I don't handle any transient locals because I haven't seen any in the wild, but let's avoid writing + // code which will likely break things and fix it if a real test case ever appears + throw new InvalidMixinException(this.mixin, "Cannot handle " + Bytecode.getOpcodeName(opcode) + " opcode (0x" + + Integer.toHexString(opcode).toUpperCase(Locale.ROOT) + ") in class initialiser"); } } } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Range.java b/src/main/java/org/spongepowered/asm/mixin/transformer/struct/InsnRange.java similarity index 53% rename from src/main/java/org/spongepowered/asm/mixin/transformer/struct/Range.java rename to src/main/java/org/spongepowered/asm/mixin/transformer/struct/InsnRange.java index eb00121d9..86ed33f38 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Range.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/struct/InsnRange.java @@ -24,23 +24,33 @@ */ package org.spongepowered.asm.mixin.transformer.struct; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.Iterator; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.tree.AbstractInsnNode; +import org.objectweb.asm.tree.InsnList; +import org.objectweb.asm.tree.LabelNode; +import org.objectweb.asm.tree.LineNumberNode; + /** - * Struct for representing a range + * Struct for representing a range of instructions */ -public class Range { +public class InsnRange { /** - * Start of the range + * Start of the range (line number) */ public final int start; /** - * End of the range + * End of the range (line number) */ public final int end; /** - * Range marker + * Range marker (index of insn) */ public final int marker; @@ -51,7 +61,7 @@ public class Range { * @param end End of the range * @param marker Arbitrary marker value */ - public Range(int start, int end, int marker) { + public InsnRange(int start, int end, int marker) { this.start = start; this.end = end; this.marker = marker; @@ -94,4 +104,53 @@ public String toString() { return String.format("Range[%d-%d,%d,valid=%s)", this.start, this.end, this.marker, this.isValid()); } + /** + * Apply this range to the specified insn list + * + * @param insns insn list to filter + * @param inclusive whether to include or exclude instructions + * @return filtered list + */ + public Deque apply(InsnList insns, boolean inclusive) { + Deque filtered = new ArrayDeque(); + int line = 0; + + boolean gatherNodes = false; + int trimAtOpcode = -1; + LabelNode optionalInsn = null; + for (Iterator iter = insns.iterator(this.marker); iter.hasNext();) { + AbstractInsnNode insn = iter.next(); + if (insn instanceof LineNumberNode) { + line = ((LineNumberNode)insn).line; + AbstractInsnNode next = insns.get(insns.indexOf(insn) + 1); + if (line == this.end && next.getOpcode() != Opcodes.RETURN) { + gatherNodes = !inclusive; + trimAtOpcode = Opcodes.RETURN; + } else { + gatherNodes = inclusive ? this.contains(line) : this.excludes(line); + trimAtOpcode = -1; + } + } else if (gatherNodes) { + if (optionalInsn != null) { + filtered.add(optionalInsn); + optionalInsn = null; + } + + if (insn instanceof LabelNode) { + optionalInsn = (LabelNode)insn; + } else { + int opcode = insn.getOpcode(); + if (opcode == trimAtOpcode) { + trimAtOpcode = -1; + continue; + } + + filtered.add(insn); + } + } + } + + return filtered; + } + } diff --git a/src/main/java/org/spongepowered/asm/util/asm/MarkerNode.java b/src/main/java/org/spongepowered/asm/util/asm/MarkerNode.java index 228e3e987..64321baf7 100644 --- a/src/main/java/org/spongepowered/asm/util/asm/MarkerNode.java +++ b/src/main/java/org/spongepowered/asm/util/asm/MarkerNode.java @@ -38,6 +38,11 @@ public class MarkerNode extends LabelNode { */ public static final int INITIALISER_TAIL = 1; + /** + * Marks the start of the body in a constructor + */ + public static final int BODY_START = 2; + /** * The type for this marker */ From 9c1abe34e5402817259e0e5bdc15e0948ac15402 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 3 Feb 2024 18:06:03 +0000 Subject: [PATCH 30/84] Fix broken invokers when target is an interface --- .../asm/mixin/gen/AccessorGenerator.java | 2 +- .../gen/AccessorGeneratorFieldGetter.java | 2 +- .../gen/AccessorGeneratorFieldSetter.java | 2 +- .../gen/AccessorGeneratorMethodProxy.java | 2 +- .../gen/AccessorGeneratorObjectFactory.java | 2 +- .../asm/mixin/injection/code/Injector.java | 2 +- .../asm/mixin/struct/SpecialMethodInfo.java | 18 ++++++++++++++++++ 7 files changed, 24 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGenerator.java b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGenerator.java index 977761a73..3fc6c4a28 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGenerator.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGenerator.java @@ -56,7 +56,7 @@ public abstract class AccessorGenerator { public AccessorGenerator(AccessorInfo info, boolean isStatic) { this.info = info; this.targetIsStatic = isStatic; - this.targetIsInterface = info.getClassInfo().isInterface(); + this.targetIsInterface = info.getTargetClassInfo().isInterface(); } protected void checkModifiers() { diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldGetter.java b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldGetter.java index b34a592e3..1ca0886fb 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldGetter.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldGetter.java @@ -49,7 +49,7 @@ public MethodNode generate() { method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); } int opcode = this.targetIsStatic ? Opcodes.GETSTATIC : Opcodes.GETFIELD; - method.instructions.add(new FieldInsnNode(opcode, this.info.getClassNode().name, this.targetField.name, this.targetField.desc)); + method.instructions.add(new FieldInsnNode(opcode, this.info.getTargetClassNode().name, this.targetField.name, this.targetField.desc)); method.instructions.add(new InsnNode(this.targetType.getOpcode(Opcodes.IRETURN))); return method; } diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldSetter.java b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldSetter.java index 03ef532be..84e898550 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldSetter.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldSetter.java @@ -84,7 +84,7 @@ public MethodNode generate() { } method.instructions.add(new VarInsnNode(this.targetType.getOpcode(Opcodes.ILOAD), stackSpace)); int opcode = this.targetIsStatic ? Opcodes.PUTSTATIC : Opcodes.PUTFIELD; - method.instructions.add(new FieldInsnNode(opcode, this.info.getClassNode().name, this.targetField.name, this.targetField.desc)); + method.instructions.add(new FieldInsnNode(opcode, this.info.getTargetClassNode().name, this.targetField.name, this.targetField.desc)); method.instructions.add(new InsnNode(Opcodes.RETURN)); return method; } diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorMethodProxy.java b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorMethodProxy.java index 3239cd780..03b731c01 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorMethodProxy.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorMethodProxy.java @@ -80,7 +80,7 @@ public MethodNode generate() { int opcode = this.targetIsStatic ? Opcodes.INVOKESTATIC : this.targetIsInterface ? (isSynthetic && isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEINTERFACE) : isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL; - method.instructions.add(new MethodInsnNode(opcode, this.info.getClassNode().name, this.targetMethod.name, this.targetMethod.desc, + method.instructions.add(new MethodInsnNode(opcode, this.info.getTargetClassNode().name, this.targetMethod.name, this.targetMethod.desc, this.targetIsInterface)); method.instructions.add(new InsnNode(this.returnType.getOpcode(Opcodes.IRETURN))); return method; diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorObjectFactory.java b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorObjectFactory.java index bcbc16ff6..fbcfcc2f9 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorObjectFactory.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorObjectFactory.java @@ -52,7 +52,7 @@ public MethodNode generate() { int size = Bytecode.getArgsSize(this.argTypes) + (returnSize * 2); MethodNode method = this.createMethod(size, size); - String className = this.info.getClassNode().name; + String className = this.info.getTargetClassNode().name; method.instructions.add(new TypeInsnNode(Opcodes.NEW, className)); method.instructions.add(new InsnNode(returnSize == 1 ? Opcodes.DUP : Opcodes.DUP2)); Bytecode.loadArgs(this.argTypes, method.instructions, 0); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java index 7a1bf6118..232cd9878 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java @@ -219,7 +219,7 @@ public Injector(InjectionInfo info, String annotationType) { this.info = info; this.annotationType = annotationType; - this.classNode = info.getClassNode(); + this.classNode = info.getTargetClassNode(); this.methodNode = info.getMethod(); this.methodArgs = Type.getArgumentTypes(this.methodNode.desc); diff --git a/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java b/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java index 6dcac649e..8201aa00c 100644 --- a/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/struct/SpecialMethodInfo.java @@ -55,11 +55,29 @@ public SpecialMethodInfo(MixinTargetContext mixin, MethodNode method, Annotation * Get the class node for this injection * * @return the class containing the injector and the target + * @deprecated use getTargetClassNode instead */ + @Deprecated public final ClassNode getClassNode() { return this.classNode; } + + /** + * Get the target class node for this injection + * + * @return the class containing the injector and the target + */ + public final ClassNode getTargetClassNode() { + return this.classNode; + } + /** + * Get the class metadata for the target class + */ + public final ClassInfo getTargetClassInfo() { + return this.mixin.getTargetClassInfo(); + } + /** * Get the class metadata for the mixin */ From a87babe37c4db52fe0698cee83018e260708bbb0 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 3 Feb 2024 18:09:33 +0000 Subject: [PATCH 31/84] Fix missing field assignment for source in MixinConfig --- .../org/spongepowered/asm/mixin/transformer/MixinConfig.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java index f8cd2b3a0..948a5289e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java @@ -420,6 +420,7 @@ private MixinConfig() {} private boolean onLoad(IMixinService service, String name, MixinEnvironment fallbackEnvironment, IMixinConfigSource source) { this.service = service; this.name = name; + this.source = source; // If parent is specified, don't perform postinit until parent is assigned if (!Strings.isNullOrEmpty(this.parentName)) { From c970d84ec33d9b42682cdd2c09d8c35d17634520 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 10 Feb 2024 14:47:31 +0000 Subject: [PATCH 32/84] Wrap logger abstraction in agent classes to prevent crash at startup --- .../spongepowered/tools/agent/MixinAgent.java | 37 +++++++++++++------ .../tools/agent/MixinAgentClassLoader.java | 27 +++++++++++--- 2 files changed, 47 insertions(+), 17 deletions(-) diff --git a/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java b/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java index 91d8c6218..a44f54836 100644 --- a/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java +++ b/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java @@ -33,6 +33,7 @@ import java.util.List; import org.spongepowered.asm.logging.ILogger; +import org.spongepowered.asm.logging.Level; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.transformer.IMixinTransformer; @@ -40,6 +41,7 @@ import org.spongepowered.asm.mixin.transformer.throwables.MixinReloadException; import org.spongepowered.asm.service.IMixinService; import org.spongepowered.asm.service.MixinService; +import org.spongepowered.asm.service.ServiceNotAvailableError; import org.spongepowered.asm.transformers.MixinClassReader; import org.spongepowered.asm.util.asm.ASM; @@ -76,23 +78,23 @@ public byte[] transform(ClassLoader loader, String className, Class classBein } try { - MixinAgent.logger.info("Redefining class {}", className); + MixinAgent.log(Level.INFO, "Redefining class {}", className); return MixinAgent.this.classTransformer.transformClassBytes(null, className, classfileBuffer); } catch (Throwable th) { - MixinAgent.logger.error("Error while re-transforming class {}", className, th); + MixinAgent.log(Level.ERROR, "Error while re-transforming class {}", className, th); return MixinAgent.ERROR_BYTECODE; } } private List reloadMixin(String className, ClassNode classNode) { - MixinAgent.logger.info("Redefining mixin {}", className); + MixinAgent.log(Level.INFO, "Redefining mixin {}", className); try { return MixinAgent.this.classTransformer.reload(className.replace('/', '.'), classNode); } catch (MixinReloadException e) { - MixinAgent.logger.error("Mixin {} cannot be reloaded, needs a restart to be applied: {} ", e.getMixinInfo(), e.getMessage()); + MixinAgent.log(Level.ERROR, "Mixin {} cannot be reloaded, needs a restart to be applied: {} ", e.getMixinInfo(), e.getMessage()); } catch (Throwable th) { // catch everything as otherwise it is ignored - MixinAgent.logger.error("Error while finding targets for mixin {}", className, th); + MixinAgent.log(Level.ERROR, "Error while finding targets for mixin {}", className, th); } return null; } @@ -109,18 +111,18 @@ private boolean reApplyMixins(List targets) { for (String target : targets) { String targetName = target.replace('/', '.'); - MixinAgent.logger.debug("Re-transforming target class {}", target); + MixinAgent.log(Level.DEBUG, "Re-transforming target class {}", target); try { Class targetClass = service.getClassProvider().findClass(targetName); byte[] targetBytecode = MixinAgent.classLoader.getOriginalTargetBytecode(targetName); if (targetBytecode == null) { - MixinAgent.logger.error("Target class {} bytecode is not registered", targetName); + MixinAgent.log(Level.ERROR, "Target class {} bytecode is not registered", targetName); return false; } targetBytecode = MixinAgent.this.classTransformer.transformClassBytes(null, targetName, targetBytecode); MixinAgent.instrumentation.redefineClasses(new ClassDefinition(targetClass, targetBytecode)); } catch (Throwable th) { - MixinAgent.logger.error("Error while re-transforming target class {}", target, th); + MixinAgent.log(Level.ERROR, "Error while re-transforming target class {}", target, th); return false; } } @@ -140,8 +142,6 @@ private boolean reApplyMixins(List targets) { */ static final MixinAgentClassLoader classLoader = new MixinAgentClassLoader(); - static final ILogger logger = MixinService.getService().getLogger("mixin.agent"); - /** * Instance used to register the transformer */ @@ -195,7 +195,7 @@ public void registerTargetClass(String name, ClassNode classNode) { public static void init(Instrumentation instrumentation) { MixinAgent.instrumentation = instrumentation; if (!MixinAgent.instrumentation.isRedefineClassesSupported()) { - MixinAgent.logger.error("The instrumentation doesn't support re-definition of classes"); + MixinAgent.log(Level.ERROR, "The instrumentation doesn't support re-definition of classes"); } for (MixinAgent agent : MixinAgent.agents) { agent.initTransformer(); @@ -229,4 +229,19 @@ public static void agentmain(String arg, Instrumentation instrumentation) { MixinAgent.init(instrumentation); } + /** + * Wrapper for logger since we can't access the log abstraction in premain + * + * @param level the logging level + * @param message the message to log + * @param params parameters to the message + */ + public static void log(Level level, String message, Object... params) { + try { + MixinService.getService().getLogger("mixin.agent").log(level, message, params); + } catch (ServiceNotAvailableError err) { + System.err.printf("MixinAgent: %s: %s", level.name(), String.format(message, params)); + } + } + } diff --git a/src/agent/java/org/spongepowered/tools/agent/MixinAgentClassLoader.java b/src/agent/java/org/spongepowered/tools/agent/MixinAgentClassLoader.java index 2b4af2709..08969c2c3 100644 --- a/src/agent/java/org/spongepowered/tools/agent/MixinAgentClassLoader.java +++ b/src/agent/java/org/spongepowered/tools/agent/MixinAgentClassLoader.java @@ -28,6 +28,7 @@ import java.util.Map; import org.spongepowered.asm.logging.ILogger; +import org.spongepowered.asm.logging.Level; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; @@ -35,6 +36,7 @@ import org.objectweb.asm.tree.ClassNode; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.service.MixinService; +import org.spongepowered.asm.service.ServiceNotAvailableError; import org.spongepowered.asm.util.Constants; /** @@ -43,8 +45,6 @@ */ class MixinAgentClassLoader extends ClassLoader { - private static final ILogger logger = MixinService.getService().getLogger("mixin.agent"); - /** * Mapping of mixin mixin classes to their fake classes */ @@ -62,7 +62,7 @@ class MixinAgentClassLoader extends ClassLoader { * @param name Name of the fake class */ void addMixinClass(String name) { - MixinAgentClassLoader.logger.debug("Mixin class {} added to class loader", name); + MixinAgentClassLoader.log(Level.DEBUG, "Mixin class {} added to class loader", name); try { byte[] bytes = this.materialise(name); Class clazz = this.defineClass(name, bytes, 0, bytes.length); @@ -71,7 +71,7 @@ void addMixinClass(String name) { clazz.getDeclaredConstructor().newInstance(); this.mixins.put(clazz, bytes); } catch (Throwable e) { - MixinAgentClassLoader.logger.catching(e); + MixinAgentClassLoader.log(Level.ERROR, "Catching {}", e); } } @@ -91,9 +91,9 @@ void addTargetClass(String name, ClassNode classNode) { classNode.accept(cw); this.targets.put(name, cw.toByteArray()); } catch (Exception ex) { - MixinAgentClassLoader.logger.error("Error storing original class bytecode for {} in mixin hotswap agent. {}: {}", + MixinAgentClassLoader.log(Level.ERROR, "Error storing original class bytecode for {} in mixin hotswap agent. {}: {}", name, ex.getClass().getName(), ex.getMessage()); - MixinAgentClassLoader.logger.debug(ex.toString()); + MixinAgentClassLoader.log(Level.DEBUG, ex.toString()); } } } @@ -144,4 +144,19 @@ private byte[] materialise(String name) { return cw.toByteArray(); } + /** + * Wrapper for logger since we can't access the log abstraction in premain + * + * @param level the logging level + * @param message the message to log + * @param params parameters to the message + */ + public static void log(Level level, String message, Object... params) { + try { + MixinService.getService().getLogger("mixin.agent").log(level, message, params); + } catch (ServiceNotAvailableError err) { + System.err.printf("MixinAgent: %s: %s", level.name(), String.format(message, params)); + } + } + } From 5f57d5006bcf7b1adb95bcd57b713bd7daf01e60 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 2 Mar 2024 21:41:13 +0000 Subject: [PATCH 33/84] Add fallback shim for synthetic package for older ModLauncher versions --- .../resources/shims/mixin_synthetic.zip | Bin 0 -> 1672 bytes .../launch/MixinTransformationService.java | 48 ++++++++++++++---- 2 files changed, 39 insertions(+), 9 deletions(-) create mode 100644 src/modlauncher/resources/shims/mixin_synthetic.zip diff --git a/src/modlauncher/resources/shims/mixin_synthetic.zip b/src/modlauncher/resources/shims/mixin_synthetic.zip new file mode 100644 index 0000000000000000000000000000000000000000..25fa34826b78fd343e117e6dcaa78c23ecea2a00 GIT binary patch literal 1672 zcmWIWW@h1H0D-rwgCf8TD8T}x^NZ5;18}Mkf~qJk$j?hpEyyoVElN$nqh1WF`o!W~ zJbDyx>M5?wE6GSL$xOzhTa^giiACwfU?W6;iaEf+uyUSw**zf7jgf)DgjgejVeZjO z&PgmTp6b8R@34W$p6kmvTP}pH@yR(5pd2Q^9kjfI&-L!yFPf!OZ%uvM^g~BYh0U&^ z-{}V9uFq|kcUPCI?>)c%J3qs9iRUjKTO2kx+%e&v*4jU^3+wAPDyV!BioI=*80aW~Fc2BXTLXrTSvs(YUKlw~lrG zl;5lPq^ccD#XJv`Tta*V}?3WE2RxrUyG@pvrxRQ*LlGWDFa@g z5*dk@eT{L4FFA`Dygd>u!=h8iW8re|XunnXM_y2(K!n%!SIDWBlcUHS_T#ei)CHootS=t%%`r7o3lY~e_)>lR^u{MQRI9s>onNH1@dNW8jmPg+ zUfGvqWc>cD%KOi+|1uxYb<^Bs;cehypmOB9m-csu7jb@KEiW$W%rrPN_1W^f%MyO; zbl completeScan(final IModuleLayerManager layerManager) { try { - MixinService.getService().getClassProvider().findClass(MixinTransformationService.VIRTUAL_JAR_CLASS, false); - } catch (ClassNotFoundException ex) { - // VirtualJar not supported + if (this.detectVirtualJar(layerManager)) + { + try { + return ImmutableList.of(this.createVirtualJar(layerManager)); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } + + try { + return ImmutableList.of(this.createShim(layerManager)); + } catch (URISyntaxException e) { + throw new RuntimeException(e); + } + } catch (Throwable th) { + th.printStackTrace(); return super.completeScan(layerManager); } - + } + + private boolean detectVirtualJar(final IModuleLayerManager layerManager) { try { - Path codeSource = Path.of(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); - VirtualJar jar = new VirtualJar("mixin_synthetic", codeSource, Constants.SYNTHETIC_PACKAGE, ArgsClassGenerator.SYNTHETIC_PACKAGE); - return ImmutableList.of(new Resource(IModuleLayerManager.Layer.GAME, ImmutableList.of(jar))); - } catch (URISyntaxException e) { - throw new RuntimeException(e); + MixinService.getService().getClassProvider().findClass(MixinTransformationService.VIRTUAL_JAR_CLASS, false); + return true; + } catch (ClassNotFoundException ex) { + // VirtualJar not supported + return false; } } + + private Resource createVirtualJar(final IModuleLayerManager layerManager) throws URISyntaxException { + Path codeSource = Path.of(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); + VirtualJar jar = new VirtualJar("mixin_synthetic", codeSource, Constants.SYNTHETIC_PACKAGE, ArgsClassGenerator.SYNTHETIC_PACKAGE); + return new Resource(IModuleLayerManager.Layer.GAME, ImmutableList.of(jar)); + } + + private Resource createShim(IModuleLayerManager layerManager) throws URISyntaxException { + URL resource = this.getClass().getResource("/shims/mixin_synthetic.zip"); + Path path = Paths.get(resource.toURI()); + SecureJar jar = SecureJar.from(path); + return new Resource(IModuleLayerManager.Layer.GAME, ImmutableList.of(jar)); + } } From f0d2cd21dff1a4aa8bf913d6522b0e0252390740 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Fri, 10 May 2024 19:38:02 +0100 Subject: [PATCH 34/84] Add constructor invokers to refmap when type is implied, closes #454 --- .../AnnotatedMixinElementHandlerAccessor.java | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java index d5bfd8025..f91add4af 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java @@ -138,7 +138,7 @@ public String toString() { static class AnnotatedElementInvoker extends AnnotatedElementAccessor { private AccessorType type = AccessorType.METHOD_PROXY; - + public AnnotatedElementInvoker(ExecutableElement element, AnnotationHandle annotation, IMixinContext context, boolean shouldRemap) { super(element, annotation, context, shouldRemap); } @@ -165,16 +165,24 @@ public void attach(TypeHandle target) { for (String prefix : AccessorType.OBJECT_FACTORY.getExpectedPrefixes()) { if (prefix.equals(accessorName.prefix) - && (Constants.CTOR.equals(accessorName.name) || target.getSimpleName().equals(accessorName.name))) { + && (Constants.CTOR.equals(accessorName.name) || target.getSimpleName().equalsIgnoreCase(accessorName.name))) { this.type = AccessorType.OBJECT_FACTORY; return; } } } + @Override + public String getAnnotationValue() { + String value = super.getAnnotationValue(); + return (this.type == AccessorType.OBJECT_FACTORY && value == null) ? this.returnType.toString() : value; + } + @Override public boolean shouldRemap() { - return (this.type == AccessorType.METHOD_PROXY || this.getAnnotationValue() != null) && super.shouldRemap(); + return (this.type == AccessorType.OBJECT_FACTORY + || this.type == AccessorType.METHOD_PROXY + || this.getAnnotationValue() != null) && super.shouldRemap(); } @Override From 2e5a4b051241ccaee630125f92d47fa5cb0fb817 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Fri, 10 May 2024 22:40:05 +0100 Subject: [PATCH 35/84] Fix descriptor filtering for NEW injection point, closes #515 --- .../spongepowered/tools/agent/MixinAgent.java | 1 - .../tools/agent/MixinAgentClassLoader.java | 1 - .../injection/invoke/RedirectInjector.java | 10 ++++-- .../asm/mixin/injection/points/BeforeNew.java | 20 ++++++++--- .../asm/mixin/injection/struct/Target.java | 16 +++------ .../asm/mixin/transformer/MixinConfig.java | 36 ++++++++++++++++++- .../launch/MixinTransformationService.java | 3 +- 7 files changed, 62 insertions(+), 25 deletions(-) diff --git a/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java b/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java index a44f54836..7821ded06 100644 --- a/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java +++ b/src/agent/java/org/spongepowered/tools/agent/MixinAgent.java @@ -32,7 +32,6 @@ import java.util.ArrayList; import java.util.List; -import org.spongepowered.asm.logging.ILogger; import org.spongepowered.asm.logging.Level; import org.objectweb.asm.ClassReader; import org.objectweb.asm.tree.ClassNode; diff --git a/src/agent/java/org/spongepowered/tools/agent/MixinAgentClassLoader.java b/src/agent/java/org/spongepowered/tools/agent/MixinAgentClassLoader.java index 08969c2c3..b3578f4f4 100644 --- a/src/agent/java/org/spongepowered/tools/agent/MixinAgentClassLoader.java +++ b/src/agent/java/org/spongepowered/tools/agent/MixinAgentClassLoader.java @@ -27,7 +27,6 @@ import java.util.HashMap; import java.util.Map; -import org.spongepowered.asm.logging.ILogger; import org.spongepowered.asm.logging.Level; import org.objectweb.asm.ClassWriter; import org.objectweb.asm.MethodVisitor; diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java index bf996cba0..0851de307 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java @@ -136,6 +136,8 @@ static class ConstructorRedirectData { public static final String KEY = "ctor"; + String desc = null; + boolean wildcard = false; int injected = 0; @@ -281,8 +283,10 @@ protected void addTargetNode(InjectorTarget injectorTarget, List for (InjectionPoint ip : nominators) { if (ip instanceof BeforeNew) { - ctorData = this.getCtorRedirect((BeforeNew)ip); - ctorData.wildcard = !((BeforeNew)ip).hasDescriptor(); + BeforeNew beforeNew = (BeforeNew)ip; + ctorData = this.getCtorRedirect(beforeNew); + ctorData.wildcard = !beforeNew.hasDescriptor(); + ctorData.desc = beforeNew.getDescriptor(); } else if (ip instanceof BeforeFieldAccess) { BeforeFieldAccess bfa = (BeforeFieldAccess)ip; fuzz = bfa.getFuzzFactor(); @@ -614,7 +618,7 @@ protected void injectAtConstructor(Target target, InjectionNode node) { final TypeInsnNode newNode = (TypeInsnNode)node.getCurrentTarget(); final AbstractInsnNode dupNode = target.get(target.indexOf(newNode) + 1); - final MethodInsnNode initNode = target.findInitNodeFor(newNode); + final MethodInsnNode initNode = target.findInitNodeFor(newNode, meta.desc); if (initNode == null) { meta.throwOrCollect(new InvalidInjectionException(this.info, String.format("%s ctor invocation was not found in %s", diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeNew.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeNew.java index 722493252..845d6e781 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeNew.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeNew.java @@ -127,6 +127,13 @@ public BeforeNew(InjectionPointData data) { public boolean hasDescriptor() { return this.desc != null; } + + /** + * Gets the descriptor from the injection point, can return null + */ + public String getDescriptor() { + return this.desc; + } @SuppressWarnings("unchecked") @Override @@ -152,7 +159,7 @@ public boolean find(String desc, InsnList insns, Collection no if (this.desc != null) { for (TypeInsnNode newNode : newNodes) { - if (this.findCtor(insns, newNode)) { + if (BeforeNew.findInitNodeFor(insns, newNode, this.desc) != null) { nodes.add(newNode); found = true; } @@ -162,18 +169,21 @@ public boolean find(String desc, InsnList insns, Collection no return found; } - protected boolean findCtor(InsnList insns, TypeInsnNode newNode) { + public static MethodInsnNode findInitNodeFor(InsnList insns, TypeInsnNode newNode, String desc) { int indexOf = insns.indexOf(newNode); + int depth = 0; for (Iterator iter = insns.iterator(indexOf); iter.hasNext();) { AbstractInsnNode insn = iter.next(); if (insn instanceof MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESPECIAL) { MethodInsnNode methodNode = (MethodInsnNode)insn; - if (Constants.CTOR.equals(methodNode.name) && methodNode.owner.equals(newNode.desc) && methodNode.desc.equals(this.desc)) { - return true; + if (Constants.CTOR.equals(methodNode.name) && --depth == 0) { + return methodNode.owner.equals(newNode.desc) && (desc == null || methodNode.desc.equals(desc)) ? methodNode : null; } + } else if (insn instanceof TypeInsnNode && insn.getOpcode() == Opcodes.NEW) { + depth++; } } - return false; + return null; } private boolean matchesOwner(TypeInsnNode insn) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index 59a83ef90..d3c097d15 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -39,6 +39,7 @@ import org.objectweb.asm.tree.MethodNode; import org.objectweb.asm.tree.TypeInsnNode; import org.spongepowered.asm.mixin.injection.callback.CallbackInfo; +import org.spongepowered.asm.mixin.injection.points.BeforeNew; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.transformer.ClassInfo; import org.spongepowered.asm.util.Bytecode; @@ -636,20 +637,11 @@ public Iterator iterator() { * NEW insn * * @param newNode NEW insn + * @param desc Descriptor to match * @return INVOKESPECIAL opcode of ctor, or null if not found */ - public MethodInsnNode findInitNodeFor(TypeInsnNode newNode) { - int start = this.indexOf(newNode); - for (Iterator iter = this.insns.iterator(start); iter.hasNext();) { - AbstractInsnNode insn = iter.next(); - if (insn instanceof MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESPECIAL) { - MethodInsnNode methodNode = (MethodInsnNode)insn; - if (Constants.CTOR.equals(methodNode.name) && methodNode.owner.equals(newNode.desc)) { - return methodNode; - } - } - } - return null; + public MethodInsnNode findInitNodeFor(TypeInsnNode newNode, String desc) { + return BeforeNew.findInitNodeFor(this.insns, newNode, desc); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java index 948a5289e..285fc8b7f 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java @@ -76,21 +76,46 @@ final class MixinConfig implements Comparable, IMixinConfig { */ static class InjectorOptions { + /** + * Specifies the default value for require to be used when no + * explicit value is defined on the injector. Setting this value to 1 + * essentially makes all injectors in the config automatically required. + * Individual injectors can still be marked optional by explicitly + * setting their require value to 0. + */ @SerializedName("defaultRequire") int defaultRequireValue = 0; + /** + * Specifies the name for injector groups which have no explicit group + * name defined. It is recommended to set this value when grouping + * injectors to support global injector groupings in the future. + */ @SerializedName("defaultGroup") String defaultGroup = "default"; + /** + * The namespace for custom injection points and dynamic selectors + */ @SerializedName("namespace") String namespace; + /** + * List of fully-qualified custom injection point classes to register + */ @SerializedName("injectionPoints") List injectionPoints; + /** + * List of fully-qualified dynamic selector classes to register + */ @SerializedName("dynamicSelectors") List dynamicSelectors; + /** + * Allows the max Shift.By value to adjusted from the environment + * default, max value is 5 + */ @SerializedName("maxShiftBy") int maxShiftBy = InjectionPoint.DEFAULT_ALLOWED_SHIFT_BY; @@ -113,9 +138,18 @@ void mergeFrom(InjectorOptions parent) { */ static class OverwriteOptions { + /** + * Flag which specifies whether an overwrite with lower visibility than + * its target is allowed to be applied, the visibility will be upgraded + * if the target method is nonprivate but the merged method is private. + */ @SerializedName("conformVisibility") boolean conformAccessModifiers; + /** + * Changes the default always-overwrite behaviour of mixins to + * explicitly require {@link Overwrite} annotations on overwrite methods + */ @SerializedName("requireAnnotations") boolean requireOverwriteAnnotations; @@ -1052,7 +1086,7 @@ public String getDefaultInjectorGroup() { } /** - * Get whether visibility levelfor overwritten methods should be conformed + * Get whether visibility level for overwritten methods should be conformed * to the target class * * @return true if conform is enabled diff --git a/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java b/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java index 10d41e080..0fe1c2023 100644 --- a/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java +++ b/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java @@ -53,8 +53,7 @@ public class MixinTransformationService extends MixinTransformationServiceAbstra @Override public List completeScan(final IModuleLayerManager layerManager) { try { - if (this.detectVirtualJar(layerManager)) - { + if (this.detectVirtualJar(layerManager)) { try { return ImmutableList.of(this.createVirtualJar(layerManager)); } catch (URISyntaxException e) { From 1accf3fd6bebc3f8f2e6597a2b58bef93d826704 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Fri, 10 May 2024 22:51:40 +0100 Subject: [PATCH 36/84] Check for null when inspecting Mixin superclass, closes #546 --- .../java/org/spongepowered/asm/mixin/transformer/MixinInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java index fb5b365ac..d41354971 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java @@ -578,7 +578,7 @@ void validate(State state, List targetClasses) { if (!targetClass.hasSuperClass(classNode.superName, ClassInfo.Traversal.SUPER)) { ClassInfo superClass = ClassInfo.forName(classNode.superName); - if (superClass.isMixin()) { + if (superClass != null && superClass.isMixin()) { // If superclass is a mixin, check for hierarchy derp for (ClassInfo superTarget : superClass.getTargets()) { if (targetClasses.contains(superTarget)) { From 480bd3d9e5b16440482e6ad36e444a95df4a0cc6 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sun, 12 May 2024 18:30:27 +0100 Subject: [PATCH 37/84] Add missing toString override, closes #562 --- .../java/org/spongepowered/asm/launch/GlobalProperties.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/org/spongepowered/asm/launch/GlobalProperties.java b/src/main/java/org/spongepowered/asm/launch/GlobalProperties.java index 85725d52d..1b62ca327 100644 --- a/src/main/java/org/spongepowered/asm/launch/GlobalProperties.java +++ b/src/main/java/org/spongepowered/asm/launch/GlobalProperties.java @@ -72,6 +72,11 @@ IPropertyKey resolve(IGlobalPropertyService service) { return this.key = service.resolveKey(this.name); } + + @Override + public String toString() { + return this.name; + } /** * Get or create a new global property key From d2d1fbc6f4e7193a778887bb6a95aba4bad07dd3 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sun, 12 May 2024 18:55:59 +0100 Subject: [PATCH 38/84] Move staticness check for mixin field after unique check, closes #576 --- .../mixin/transformer/MixinPreProcessorStandard.java | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java index 0a34097d1..5478643c5 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java @@ -633,11 +633,6 @@ protected void attachFields(MixinTargetContext context) { mixinField.name = field.renameTo(target.name); } - if (!Bytecode.compareFlags(mixinField, target, Opcodes.ACC_STATIC)) { - throw new InvalidMixinException(this.mixin, String.format("STATIC modifier of @Shadow field %s in %s does not match the target", - mixinField.name, this.mixin)); - } - if (field.isUnique()) { if ((mixinField.access & (Opcodes.ACC_PRIVATE | Opcodes.ACC_PROTECTED)) != 0) { String uniqueName = context.getUniqueName(mixinField); @@ -659,6 +654,11 @@ protected void attachFields(MixinTargetContext context) { continue; } + if (!Bytecode.compareFlags(mixinField, target, Opcodes.ACC_STATIC)) { + throw new InvalidMixinException(this.mixin, String.format("STATIC modifier of @Shadow field %s in %s does not match the target", + mixinField.name, this.mixin)); + } + // Check that the shadow field has a matching descriptor if (!target.desc.equals(mixinField.desc)) { throw new InvalidMixinException(this.mixin, String.format("The field %s in the target class has a conflicting signature", From 53aa500f8d072972008c1fa81ee9f14a40bbb207 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sun, 12 May 2024 21:30:21 +0100 Subject: [PATCH 39/84] Update build.gradle for hardware signing token --- build.gradle | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/build.gradle b/build.gradle index ef5b07904..cffa18f45 100644 --- a/build.gradle +++ b/build.gradle @@ -402,7 +402,10 @@ if (project.doSignJar) { keypass: project.keyStoreSecret, tsaurl: project.timestampAuthority, preservelastmodified: 'true', - verbose: true + verbose: true, + storetype: project.keyStoreType, + providerclass: project.antSignProviderClass, + providerarg: project.antSignProviderArg ) } } From 2c9a96700c879eb33dba214e945d84ba2488b0ad Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Mon, 13 May 2024 00:14:35 +0100 Subject: [PATCH 40/84] Mixin 0.8.6 RELEASE --- gradle.properties | 2 +- .../org/spongepowered/asm/mixin/injection/struct/Target.java | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/gradle.properties b/gradle.properties index 6c0886e3f..ab1ff9e39 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ description=Mixin url=https://www.spongepowered.org organization=SpongePowered buildVersion=0.8.6 -buildType=SNAPSHOT +buildType=RELEASE asmVersion=9.5 legacyForgeAsmVersion=5.0.3 modlauncherAsmVersion=9.5 diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index d3c097d15..5ac36e6cb 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -28,7 +28,6 @@ import java.util.Iterator; import java.util.List; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; From a95dcd9af498e153b630b9458426d037ba7e34c3 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Tue, 14 May 2024 20:34:52 +0100 Subject: [PATCH 41/84] Bump version to 0.8.7-SNAPSHOT --- gradle.properties | 4 ++-- .../java/org/spongepowered/asm/launch/MixinBootstrap.java | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/gradle.properties b/gradle.properties index ab1ff9e39..9f6eb237e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ packaging=jar description=Mixin url=https://www.spongepowered.org organization=SpongePowered -buildVersion=0.8.6 -buildType=RELEASE +buildVersion=0.8.7 +buildType=SNAPSHOT asmVersion=9.5 legacyForgeAsmVersion=5.0.3 modlauncherAsmVersion=9.5 diff --git a/src/main/java/org/spongepowered/asm/launch/MixinBootstrap.java b/src/main/java/org/spongepowered/asm/launch/MixinBootstrap.java index 94d1d3cfa..a338a4510 100644 --- a/src/main/java/org/spongepowered/asm/launch/MixinBootstrap.java +++ b/src/main/java/org/spongepowered/asm/launch/MixinBootstrap.java @@ -65,7 +65,7 @@ public abstract class MixinBootstrap { /** * Subsystem version */ - public static final String VERSION = "0.8.6"; + public static final String VERSION = "0.8.7"; /** * Transformer factory From 902c3439e8611fa8086e3999e62f3cd64a7b2665 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Tue, 14 May 2024 20:40:36 +0100 Subject: [PATCH 42/84] Restore old findInitNodeFor used by MixinExtras, mark as deprecated --- .../asm/mixin/injection/struct/Target.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index 5ac36e6cb..1c13b32a6 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -28,6 +28,7 @@ import java.util.Iterator; import java.util.List; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; @@ -633,7 +634,37 @@ public Iterator iterator() { /** * Find the first <init> invocation after the specified - * NEW insn + * NEW insn + * + * @param newNode NEW insn + * @return INVOKESPECIAL opcode of ctor, or null if not found + * + * @deprecated Use {@link #findInitNodeFor(TypeInsnNode, String)} instead: + * this method only matches the first matching <init> + * after the specified NEW, it also does not filter based on + * the descriptor passed into BeforeNew. + */ + @Deprecated + public MethodInsnNode findInitNodeFor(TypeInsnNode newNode) { + int start = this.indexOf(newNode); + for (Iterator iter = this.insns.iterator(start); iter.hasNext();) { + AbstractInsnNode insn = iter.next(); + if (insn instanceof MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESPECIAL) { + MethodInsnNode methodNode = (MethodInsnNode)insn; + if (Constants.CTOR.equals(methodNode.name) && methodNode.owner.equals(newNode.desc)) { + return methodNode; + } + } + } + return null; + } + + /** + * Find the matching <init> invocation after the specified + * NEW insn, ensuring that the supplied descriptor matches. If the + * supplied descriptor is null then any invocation matches. If + * additional NEW insns are encountered then corresponding + * <init> calls are skipped. * * @param newNode NEW insn * @param desc Descriptor to match From a13f0c0db0be4f6ff691b78b498e928a6d7504bf Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Tue, 14 May 2024 21:50:50 +0100 Subject: [PATCH 43/84] Convert EXPAND_FRAMES ClassReader flag to tunable, default to 0 --- .../mojang/MixinServiceLaunchWrapper.java | 11 +++- .../asm/mixin/MixinEnvironment.java | 59 ++++++++++++++++++- .../asm/mixin/transformer/MixinInfo.java | 3 +- .../asm/service/IClassBytecodeProvider.java | 13 ++++ .../asm/launch/MixinLaunchPluginLegacy.java | 9 ++- 5 files changed, 89 insertions(+), 6 deletions(-) diff --git a/src/launchwrapper/java/org/spongepowered/asm/service/mojang/MixinServiceLaunchWrapper.java b/src/launchwrapper/java/org/spongepowered/asm/service/mojang/MixinServiceLaunchWrapper.java index 630fcc82c..29ee7a966 100644 --- a/src/launchwrapper/java/org/spongepowered/asm/service/mojang/MixinServiceLaunchWrapper.java +++ b/src/launchwrapper/java/org/spongepowered/asm/service/mojang/MixinServiceLaunchWrapper.java @@ -598,7 +598,16 @@ public ClassNode getClassNode(String className) throws ClassNotFoundException, I */ @Override public ClassNode getClassNode(String className, boolean runTransformers) throws ClassNotFoundException, IOException { - return this.getClassNode(className, this.getClassBytes(className, true), ClassReader.EXPAND_FRAMES); + return this.getClassNode(className, this.getClassBytes(className, runTransformers), ClassReader.EXPAND_FRAMES); + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.service.IClassBytecodeProvider#getClassNode( + * java.lang.String, boolean, int) + */ + @Override + public ClassNode getClassNode(String className, boolean runTransformers, int flags) throws ClassNotFoundException, IOException { + return this.getClassNode(className, this.getClassBytes(className, runTransformers), flags); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java index 16e7d816d..ada4d0090 100644 --- a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java +++ b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java @@ -38,6 +38,7 @@ import org.spongepowered.asm.launch.GlobalProperties; import org.spongepowered.asm.launch.GlobalProperties.Keys; import org.spongepowered.asm.launch.MixinBootstrap; +import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.spongepowered.asm.mixin.extensibility.IEnvironmentTokenProvider; import org.spongepowered.asm.mixin.injection.At; @@ -330,7 +331,7 @@ public static enum Option { /** * Parent for environment settings */ - ENVIRONMENT(Inherit.ALWAYS_FALSE, "env"), + ENVIRONMENT(Inherit.ALWAYS_FALSE, true, "env"), /** * Force refmap obf type when required @@ -412,7 +413,20 @@ public static enum Option { * Behaviour for initialiser injections, current supported options are * "default" and "safe" */ - INITIALISER_INJECTION_MODE("initialiserInjectionMode", "default"); + INITIALISER_INJECTION_MODE("initialiserInjectionMode", "default"), + + /** + * Parent for tunable settings + */ + TUNABLE(Inherit.ALWAYS_FALSE, true, "tunable"), + + /** + * Tunable for the Mixin ClassReader behaviour, setting this option to + * true will cause the Mixin ClassReader to read mixin bytecode + * with {@link ClassReader#EXPAND_FRAMES} flag which restores the + * behaviour from versions 0.8.6 and below, newer versions default to 0. + */ + CLASSREADER_EXPAND_FRAMES(Option.TUNABLE, Inherit.INDEPENDENT, "classReaderExpandFrames", true, "false"); /** * Type of inheritance for options @@ -459,6 +473,11 @@ private enum Inherit { * Inheritance behaviour for this option */ final Inherit inheritance; + + /** + * Do not print this option in the masthead output + */ + final boolean isHidden; /** * Java property name @@ -488,6 +507,10 @@ private Option(Inherit inheritance, String property) { this(null, inheritance, property, true); } + private Option(Inherit inheritance, boolean hidden, String property) { + this(null, inheritance, hidden, property, true); + } + private Option(String property, boolean flag) { this(null, property, flag); } @@ -500,29 +523,58 @@ private Option(Option parent, String property) { this(parent, Inherit.INHERIT, property, true); } + private Option(Option parent, boolean hidden, String property) { + this(parent, Inherit.INHERIT, hidden, property, true); + } + private Option(Option parent, Inherit inheritance, String property) { this(parent, inheritance, property, true); } + private Option(Option parent, Inherit inheritance, boolean hidden, String property) { + this(parent, inheritance, hidden, property, true); + } + private Option(Option parent, String property, boolean isFlag) { this(parent, Inherit.INHERIT, property, isFlag, null); } + private Option(Option parent, boolean hidden, String property, boolean isFlag) { + this(parent, Inherit.INHERIT, hidden, property, isFlag, null); + } + private Option(Option parent, Inherit inheritance, String property, boolean isFlag) { this(parent, inheritance, property, isFlag, null); } + private Option(Option parent, Inherit inheritance, boolean hidden, String property, boolean isFlag) { + this(parent, inheritance, hidden, property, isFlag, null); + } + private Option(Option parent, String property, String defaultStringValue) { this(parent, Inherit.INHERIT, property, false, defaultStringValue); } + private Option(Option parent, boolean hidden, String property, String defaultStringValue) { + this(parent, Inherit.INHERIT, hidden, property, false, defaultStringValue); + } + private Option(Option parent, Inherit inheritance, String property, String defaultStringValue) { this(parent, inheritance, property, false, defaultStringValue); } + private Option(Option parent, Inherit inheritance, boolean hidden, String property, String defaultStringValue) { + this(parent, inheritance, hidden, property, false, defaultStringValue); + } + private Option(Option parent, Inherit inheritance, String property, boolean isFlag, String defaultStringValue) { + this(parent, inheritance, false, property, false, defaultStringValue); + } + + private Option(Option parent, Inherit inheritance, boolean hidden, String property, boolean isFlag, String defaultStringValue) { this.parent = parent; this.inheritance = inheritance; + this.isHidden = hidden; this.property = (parent != null ? parent.property : Option.PREFIX) + "." + property; this.defaultValue = defaultStringValue; this.isFlag = isFlag; @@ -1300,6 +1352,9 @@ private void printHeader(Object version) { printer.kv("Global Property Service Class", MixinService.getGlobalPropertyService().getClass().getName()); printer.kv("Logger Adapter Type", MixinService.getService().getLogger("mixin").getType()).hr(); for (Option option : Option.values()) { + if (option.isHidden) { + continue; + } StringBuilder indent = new StringBuilder(); for (int i = 0; i < option.depth; i++) { indent.append("- "); diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java index d41354971..9aa374ec6 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java @@ -1308,7 +1308,8 @@ private ClassNode loadMixinClass(String mixinClassName) throws ClassNotFoundExce this.logger.error("Classloader restrictions [{}] encountered loading {}, name: {}", restrictions, this, mixinClassName); } } - classNode = this.service.getBytecodeProvider().getClassNode(mixinClassName, true); + int readerFlags = this.parent.getEnvironment().getOption(Option.CLASSREADER_EXPAND_FRAMES) ? ClassReader.EXPAND_FRAMES : 0; + classNode = this.service.getBytecodeProvider().getClassNode(mixinClassName, true, readerFlags); } catch (ClassNotFoundException ex) { throw new ClassNotFoundException(String.format("The specified mixin '%s' was not found", mixinClassName)); } catch (IOException ex) { diff --git a/src/main/java/org/spongepowered/asm/service/IClassBytecodeProvider.java b/src/main/java/org/spongepowered/asm/service/IClassBytecodeProvider.java index bc8ac4cf2..0525d9723 100644 --- a/src/main/java/org/spongepowered/asm/service/IClassBytecodeProvider.java +++ b/src/main/java/org/spongepowered/asm/service/IClassBytecodeProvider.java @@ -54,4 +54,17 @@ public interface IClassBytecodeProvider { */ public abstract ClassNode getClassNode(String name, boolean runTransformers) throws ClassNotFoundException, IOException; + + /** + * Retrieve transformed class as an ASM tree + * + * @param name full class name + * @param runTransformers true to run transformers when loading the class + * @param readerFlags Flags to pass in to ClassReader.accept + * @return tree + * @throws ClassNotFoundException if class not found + * @throws IOException propagated + */ + public abstract ClassNode getClassNode(String name, boolean runTransformers, int readerFlags) throws ClassNotFoundException, IOException; + } diff --git a/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java b/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java index 9cbbce1c4..c186846b9 100644 --- a/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java +++ b/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java @@ -204,11 +204,16 @@ protected void initializeLaunch(ITransformerLoader transformerLoader) { @Override public ClassNode getClassNode(String name) throws ClassNotFoundException, IOException { - return this.getClassNode(name, true); + return this.getClassNode(name, true, 0); } @Override public ClassNode getClassNode(String name, boolean runTransformers) throws ClassNotFoundException, IOException { + return this.getClassNode(name, runTransformers, ClassReader.EXPAND_FRAMES); + } + + @Override + public ClassNode getClassNode(String name, boolean runTransformers, int readerFlags) throws ClassNotFoundException, IOException { if (!runTransformers) { throw new IllegalArgumentException("ModLauncher service does not currently support retrieval of untransformed bytecode"); } @@ -235,7 +240,7 @@ public ClassNode getClassNode(String name, boolean runTransformers) throws Class if (classBytes != null && classBytes.length != 0) { ClassNode classNode = new ClassNode(); ClassReader classReader = new MixinClassReader(classBytes, canonicalName); - classReader.accept(classNode, ClassReader.EXPAND_FRAMES); + classReader.accept(classNode, readerFlags); return classNode; } From e21251116ad556377858ef9f47c56d846f47f157 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Wed, 15 May 2024 19:22:01 +0100 Subject: [PATCH 44/84] Replace zip-based ML legacy VirtualJar shim with inline SecureJar shim --- .../resources/shims/mixin_synthetic.zip | Bin 1672 -> 0 bytes .../launch/MixinTransformationService.java | 24 ++++++++++-------- 2 files changed, 14 insertions(+), 10 deletions(-) delete mode 100644 src/modlauncher/resources/shims/mixin_synthetic.zip diff --git a/src/modlauncher/resources/shims/mixin_synthetic.zip b/src/modlauncher/resources/shims/mixin_synthetic.zip deleted file mode 100644 index 25fa34826b78fd343e117e6dcaa78c23ecea2a00..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1672 zcmWIWW@h1H0D-rwgCf8TD8T}x^NZ5;18}Mkf~qJk$j?hpEyyoVElN$nqh1WF`o!W~ zJbDyx>M5?wE6GSL$xOzhTa^giiACwfU?W6;iaEf+uyUSw**zf7jgf)DgjgejVeZjO z&PgmTp6b8R@34W$p6kmvTP}pH@yR(5pd2Q^9kjfI&-L!yFPf!OZ%uvM^g~BYh0U&^ z-{}V9uFq|kcUPCI?>)c%J3qs9iRUjKTO2kx+%e&v*4jU^3+wAPDyV!BioI=*80aW~Fc2BXTLXrTSvs(YUKlw~lrG zl;5lPq^ccD#XJv`Tta*V}?3WE2RxrUyG@pvrxRQ*LlGWDFa@g z5*dk@eT{L4FFA`Dygd>u!=h8iW8re|XunnXM_y2(K!n%!SIDWBlcUHS_T#ei)CHootS=t%%`r7o3lY~e_)>lR^u{MQRI9s>onNH1@dNW8jmPg+ zUfGvqWc>cD%KOi+|1uxYb<^Bs;cehypmOB9m-csu7jb@KEiW$W%rrPN_1W^f%MyO; zbl completeScan(final IModuleLayerManager layerManager) { try { + Path codeSource = Path.of(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); + if (this.detectVirtualJar(layerManager)) { try { - return ImmutableList.of(this.createVirtualJar(layerManager)); + return ImmutableList.of(this.createVirtualJar(codeSource)); } catch (URISyntaxException e) { throw new RuntimeException(e); } } try { - return ImmutableList.of(this.createShim(layerManager)); + return ImmutableList.of(this.createShim(codeSource)); } catch (URISyntaxException e) { throw new RuntimeException(e); } @@ -82,16 +86,16 @@ private boolean detectVirtualJar(final IModuleLayerManager layerManager) { } } - private Resource createVirtualJar(final IModuleLayerManager layerManager) throws URISyntaxException { - Path codeSource = Path.of(this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI()); + private Resource createVirtualJar(final Path codeSource) throws URISyntaxException { VirtualJar jar = new VirtualJar("mixin_synthetic", codeSource, Constants.SYNTHETIC_PACKAGE, ArgsClassGenerator.SYNTHETIC_PACKAGE); return new Resource(IModuleLayerManager.Layer.GAME, ImmutableList.of(jar)); } - private Resource createShim(IModuleLayerManager layerManager) throws URISyntaxException { - URL resource = this.getClass().getResource("/shims/mixin_synthetic.zip"); - Path path = Paths.get(resource.toURI()); - SecureJar jar = SecureJar.from(path); + @SuppressWarnings("removal") + private Resource createShim(final Path codeSource) throws URISyntaxException { + Path path = codeSource.resolve("mixin_synthetic"); + Set packages = ImmutableSet.of(Constants.SYNTHETIC_PACKAGE, ArgsClassGenerator.SYNTHETIC_PACKAGE); + SecureJar jar = SecureJar.from(sj -> JarMetadata.fromFileName(path, packages, ImmutableList.of()), codeSource); return new Resource(IModuleLayerManager.Layer.GAME, ImmutableList.of(jar)); } From 54f36446405a693057619740996e43f5b2cb8bf4 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Wed, 15 May 2024 19:31:55 +0100 Subject: [PATCH 45/84] Don't create logger in MixinServiceAbstract ctor, updates #569 --- .../asm/service/MixinServiceAbstract.java | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/service/MixinServiceAbstract.java b/src/main/java/org/spongepowered/asm/service/MixinServiceAbstract.java index c01c814d7..6c4fb5fca 100644 --- a/src/main/java/org/spongepowered/asm/service/MixinServiceAbstract.java +++ b/src/main/java/org/spongepowered/asm/service/MixinServiceAbstract.java @@ -54,12 +54,6 @@ public abstract class MixinServiceAbstract implements IMixinService { protected static final String MIXIN_PACKAGE = "org.spongepowered.asm.mixin."; protected static final String SERVICE_PACKAGE = "org.spongepowered.asm.service."; - /** - * Logger adapter, replacement for log4j2 logger as services should use - * their own loggers now in order to avoid contamination - */ - private static ILogger logger; - /** * Cached logger adapters */ @@ -86,12 +80,6 @@ public abstract class MixinServiceAbstract implements IMixinService { */ private String sideName; - protected MixinServiceAbstract() { - if (MixinServiceAbstract.logger == null) { - MixinServiceAbstract.logger = this.getLogger("mixin"); - } - } - /* (non-Javadoc) * @see org.spongepowered.asm.service.IMixinService#prepare() */ @@ -226,7 +214,7 @@ public final String getSideName() { return this.sideName = side; } } catch (Exception ex) { - MixinServiceAbstract.logger.catching(ex); + this.getLogger("mixin").catching(ex); } } From b04e6d64d3487b11b595f77596c7ff7a5a876425 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Thu, 16 May 2024 23:34:06 +0100 Subject: [PATCH 46/84] Propagate argument offsets when applying Redirect, closes #544 --- .../tools/obfuscation/mirror/TypeHandle.java | 2 +- .../injection/invoke/ModifyArgInjector.java | 7 + .../injection/invoke/ModifyArgsInjector.java | 18 ++- .../injection/invoke/RedirectInjector.java | 4 +- .../mixin/injection/struct/ArgOffsets.java | 123 ++++++++++++++++++ .../injection/struct/IChainedDecoration.java | 43 ++++++ .../injection/struct/InjectionNodes.java | 29 ++++- .../asm/mixin/injection/struct/Target.java | 20 +-- .../asm/service/modlauncher/Blackboard.java | 2 +- .../modlauncher/MixinServiceModLauncher.java | 2 +- .../launch/MixinTransformationService.java | 4 +- 11 files changed, 229 insertions(+), 25 deletions(-) create mode 100644 src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java create mode 100644 src/main/java/org/spongepowered/asm/mixin/injection/struct/IChainedDecoration.java diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java index 95b9a02a8..e02ea20c8 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/mirror/TypeHandle.java @@ -299,7 +299,7 @@ public boolean isNotInterface() { * @param other the TypeHandle to compare with */ public boolean isSuperTypeOf(TypeHandle other) { - List superTypes = new ArrayList<>(); + List superTypes = new ArrayList(); if (other.getSuperclass() != null) { superTypes.add(other.getSuperclass()); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java index 30a29ceef..839bad817 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java @@ -33,6 +33,7 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.struct.ArgOffsets; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.Target; @@ -110,7 +111,13 @@ protected void inject(Target target, InjectionNode node) { protected void injectAtInvoke(Target target, InjectionNode node) { MethodInsnNode methodNode = (MethodInsnNode)node.getCurrentTarget(); Type[] args = Type.getArgumentTypes(methodNode.desc); + ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY); + if (offsets != null) { + args = offsets.apply(args); + } + int argIndex = this.findArgIndex(target, args); + InsnList insns = new InsnList(); Extension extraLocals = target.extendLocals(); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java index 8aaee8a34..82d1a0d34 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java @@ -33,6 +33,7 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.ModifyArgs; import org.spongepowered.asm.mixin.injection.invoke.arg.ArgsClassGenerator; +import org.spongepowered.asm.mixin.injection.struct.ArgOffsets; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.Target; @@ -78,20 +79,25 @@ protected void inject(Target target, InjectionNode node) { @Override protected void injectAtInvoke(Target target, InjectionNode node) { MethodInsnNode targetMethod = (MethodInsnNode)node.getCurrentTarget(); - Type[] args = Type.getArgumentTypes(targetMethod.desc); + ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY); + if (offsets != null) { + args = offsets.apply(args); + } + String targetMethodDesc = Type.getMethodDescriptor(Type.getReturnType(targetMethod.desc), args); + if (args.length == 0) { throw new InvalidInjectionException(this.info, "@ModifyArgs injector " + this + " targets a method invocation " - + targetMethod.name + targetMethod.desc + " with no arguments!"); + + targetMethod.name + targetMethodDesc + " with no arguments!"); } - String clArgs = this.argsClassGenerator.getArgsClass(targetMethod.desc, this.info.getMixin().getMixin()).getName(); + String clArgs = this.argsClassGenerator.getArgsClass(targetMethodDesc, this.info.getMixin().getMixin()).getName(); boolean withArgs = this.verifyTarget(target); InsnList insns = new InsnList(); Extension extraStack = target.extendStack().add(1); - this.packArgs(insns, clArgs, targetMethod); + this.packArgs(insns, clArgs, targetMethodDesc); if (withArgs) { extraStack.add(target.arguments); @@ -121,8 +127,8 @@ private boolean verifyTarget(Target target) { return false; } - private void packArgs(InsnList insns, String clArgs, MethodInsnNode targetMethod) { - String factoryDesc = Bytecode.changeDescriptorReturnType(targetMethod.desc, "L" + clArgs + ";"); + private void packArgs(InsnList insns, String clArgs, String targetMethodDesc) { + String factoryDesc = Bytecode.changeDescriptorReturnType(targetMethodDesc, "L" + clArgs + ";"); insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC, clArgs, "of", factoryDesc, false)); insns.add(new InsnNode(Opcodes.DUP)); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java index 0851de307..a0ea19e2c 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java @@ -40,6 +40,7 @@ import org.spongepowered.asm.mixin.injection.code.InjectorTarget; import org.spongepowered.asm.mixin.injection.points.BeforeFieldAccess; import org.spongepowered.asm.mixin.injection.points.BeforeNew; +import org.spongepowered.asm.mixin.injection.struct.ArgOffsets; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.Target; @@ -392,6 +393,7 @@ protected void injectAtInvoke(Target target, InjectionNode node) { Extension extraLocals = target.extendLocals().add(invoke.handlerArgs).add(1); Extension extraStack = target.extendStack().add(1); // Normally only need 1 extra stack pos to store target ref int[] argMap = this.storeArgs(target, invoke.handlerArgs, insns, 0); + ArgOffsets offsets = new ArgOffsets(this.isStatic ? 0 : 1, invoke.targetArgs.length); if (invoke.captureTargetArgs > 0) { int argSize = Bytecode.getArgsSize(target.arguments, 0, invoke.captureTargetArgs); extraLocals.add(argSize); @@ -403,7 +405,7 @@ protected void injectAtInvoke(Target target, InjectionNode node) { if (invoke.coerceReturnType && invoke.returnType.getSort() >= Type.ARRAY) { insns.add(new TypeInsnNode(Opcodes.CHECKCAST, invoke.returnType.getInternalName())); } - target.replaceNode(invoke.node, champion, insns); + target.replaceNode(invoke.node, champion, insns).decorate(ArgOffsets.KEY, offsets); extraLocals.apply(); extraStack.apply(); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java new file mode 100644 index 000000000..1d23a1e51 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java @@ -0,0 +1,123 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.injection.struct; + +import org.objectweb.asm.Type; + +/** + * Decoration which stores a mapping of new argument offsets to original + * argument offsets. + */ +public class ArgOffsets implements IChainedDecoration { + + /** + * Decoration key for this decoration type + */ + public static final String KEY = "argOffsets"; + + /** + * Default offsets + */ + public static final ArgOffsets UNITY = new ArgOffsets(0, 1024); + + /** + * Mapping of original offsets to new offsets + */ + private final int[] mapping; + + /** + * If this offset collection replaces a previous mapping, chain to the next + * mapping in order to apply these offsets atop the old ones + */ + private ArgOffsets next; + + /** + * Create contiguous offsets starting from start and continuing for length + * + * @param start start index + * @param length length + */ + public ArgOffsets(int start, int length) { + this.mapping = new int[length]; + for (int i = 0; i < length; i++) { + this.mapping[i] = start++; + } + } + + /** + * Create an offset collection from an explicit array + * + * @param offsets offsets to store + */ + public ArgOffsets(int... offsets) { + this.mapping = offsets; + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.injection.struct.IChainedDecoration + * #replace( + * org.spongepowered.asm.mixin.injection.struct.IChainedDecoration) + */ + @Override + public void replace(ArgOffsets old) { + this.next = old; + } + + /** + * Get the size of this mapping collection + */ + public int getLength() { + return this.mapping.length; + } + + /** + * Compute the argument index for the specified new index + * + * @param index The new index to compute + * @return The original index based on this mapping + */ + public int getArgIndex(int index) { + int offsetIndex = this.mapping[index]; + return this.next != null ? this.next.getArgIndex(offsetIndex) : offsetIndex; + } + + /** + * Apply this offset collection to the supplied argument array + * + * @param args New arguments + * @return Unmapped arguments + */ + public Type[] apply(Type[] args) { + Type[] transformed = new Type[this.mapping.length]; + for (int i = 0; i < this.mapping.length; i++) { + int offset = this.getArgIndex(i); + if (offset < args.length) { + transformed[i] = args[offset]; + } + } + return transformed; + } + +} diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/IChainedDecoration.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/IChainedDecoration.java new file mode 100644 index 000000000..41b779e6c --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/IChainedDecoration.java @@ -0,0 +1,43 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.injection.struct; + +/** + * An InjectionNode decoration which can chain to a previously registered + * decoration with the same type and key. + * + * @param the decoration type + */ +public interface IChainedDecoration { + + /** + * Called when this decoration replaces a previous decoration with the same + * key + * + * @param old The previous decoration + */ + public abstract void replace(T old); + +} diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionNodes.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionNodes.java index 53461fabd..f8d4df36a 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionNodes.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionNodes.java @@ -162,10 +162,17 @@ public boolean isRemoved() { * @param value meta value * @param value type */ + @SuppressWarnings("unchecked") public InjectionNode decorate(String key, V value) { if (this.decorations == null) { this.decorations = new HashMap(); } + if (value instanceof IChainedDecoration && this.decorations.containsKey(key)) { + Object previous = this.decorations.get(key); + if (previous.getClass().equals(value.getClass())) { + ((IChainedDecoration)value).replace(previous); + } + } this.decorations.put(key, value); return this; } @@ -191,6 +198,20 @@ public boolean hasDecoration(String key) { public V getDecoration(String key) { return (V) (this.decorations == null ? null : this.decorations.get(key)); } + + /** + * Get the specified decoration or default value + * + * @param key meta key + * @param defaultValue default value to return + * @param value type + * @return decoration value or null if absent + */ + @SuppressWarnings("unchecked") + public V getDecoration(String key, V defaultValue) { + V existing = (V) (this.decorations == null ? null : this.decorations.get(key)); + return existing != null ? existing : defaultValue; + } /* (non-Javadoc) * @see java.lang.Comparable#compareTo(java.lang.Object) @@ -258,11 +279,12 @@ public boolean contains(AbstractInsnNode node) { * @param oldNode node being replaced * @param newNode node to replace with */ - public void replace(AbstractInsnNode oldNode, AbstractInsnNode newNode) { + public InjectionNode replace(AbstractInsnNode oldNode, AbstractInsnNode newNode) { InjectionNode injectionNode = this.get(oldNode); if (injectionNode != null) { injectionNode.replace(newNode); - } + } + return injectionNode; } /** @@ -271,11 +293,12 @@ public void replace(AbstractInsnNode oldNode, AbstractInsnNode newNode) { * * @param node node being removed */ - public void remove(AbstractInsnNode node) { + public InjectionNode remove(AbstractInsnNode node) { InjectionNode injectionNode = this.get(node); if (injectionNode != null) { injectionNode.remove(); } + return injectionNode; } } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index 1c13b32a6..9e2203c77 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -761,10 +761,10 @@ public void insertBefore(AbstractInsnNode location, final AbstractInsnNode insn) * @param location Instruction to replace * @param insn Instruction to replace with */ - public void replaceNode(AbstractInsnNode location, AbstractInsnNode insn) { + public InjectionNode replaceNode(AbstractInsnNode location, AbstractInsnNode insn) { this.insns.insertBefore(location, insn); this.insns.remove(location); - this.injectionNodes.replace(location, insn); + return this.injectionNodes.replace(location, insn); } /** @@ -775,10 +775,10 @@ public void replaceNode(AbstractInsnNode location, AbstractInsnNode insn) { * @param champion Instruction which notionally replaces the original insn * @param insns Instructions to actually insert (must contain champion) */ - public void replaceNode(AbstractInsnNode location, AbstractInsnNode champion, InsnList insns) { + public InjectionNode replaceNode(AbstractInsnNode location, AbstractInsnNode champion, InsnList insns) { this.insns.insertBefore(location, insns); this.insns.remove(location); - this.injectionNodes.replace(location, champion); + return this.injectionNodes.replace(location, champion); } /** @@ -790,10 +790,10 @@ public void replaceNode(AbstractInsnNode location, AbstractInsnNode champion, In * @param before Instructions to actually insert (must contain champion) * @param after Instructions to insert after the specified location */ - public void wrapNode(AbstractInsnNode location, AbstractInsnNode champion, InsnList before, InsnList after) { + public InjectionNode wrapNode(AbstractInsnNode location, AbstractInsnNode champion, InsnList before, InsnList after) { this.insns.insertBefore(location, before); this.insns.insert(location, after); - this.injectionNodes.replace(location, champion); + return this.injectionNodes.replace(location, champion); } /** @@ -803,9 +803,9 @@ public void wrapNode(AbstractInsnNode location, AbstractInsnNode champion, InsnL * @param location Instruction to replace * @param insns Instructions to replace with */ - public void replaceNode(AbstractInsnNode location, InsnList insns) { + public InjectionNode replaceNode(AbstractInsnNode location, InsnList insns) { this.insns.insertBefore(location, insns); - this.removeNode(location); + return this.removeNode(location); } /** @@ -814,9 +814,9 @@ public void replaceNode(AbstractInsnNode location, InsnList insns) { * * @param insn instruction to remove */ - public void removeNode(AbstractInsnNode insn) { + public InjectionNode removeNode(AbstractInsnNode insn) { this.insns.remove(insn); - this.injectionNodes.remove(insn); + return this.injectionNodes.remove(insn); } /** diff --git a/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/Blackboard.java b/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/Blackboard.java index 042c593c2..0fe7212d5 100644 --- a/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/Blackboard.java +++ b/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/Blackboard.java @@ -85,7 +85,7 @@ public T getProperty(IPropertyKey key) { */ @SuppressWarnings("unchecked") @Override - public void setProperty(IPropertyKey key, Object value) { + public void setProperty(IPropertyKey key, final Object value) { this.blackboard.computeIfAbsent(((Key)key).key, k -> value); } diff --git a/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java b/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java index fa54c5b1d..859421fba 100644 --- a/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java +++ b/src/modlauncher/java/org/spongepowered/asm/service/modlauncher/MixinServiceModLauncher.java @@ -323,7 +323,7 @@ private static VersionNumber getModLauncherApiVersion() { version = Optional.ofNullable(ITransformationService.class.getPackage().getSpecificationVersion()); } - return version.map(VersionNumber::parse).orElse(VersionNumber.NONE); + return version.map(VersionNumber::parse).orElse(VersionNumber.NONE); } } diff --git a/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java b/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java index ffe580be6..4f2e13ec2 100644 --- a/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java +++ b/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java @@ -93,8 +93,8 @@ private Resource createVirtualJar(final Path codeSource) throws URISyntaxExcepti @SuppressWarnings("removal") private Resource createShim(final Path codeSource) throws URISyntaxException { - Path path = codeSource.resolve("mixin_synthetic"); - Set packages = ImmutableSet.of(Constants.SYNTHETIC_PACKAGE, ArgsClassGenerator.SYNTHETIC_PACKAGE); + final Path path = codeSource.resolve("mixin_synthetic"); + final Set packages = ImmutableSet.of(Constants.SYNTHETIC_PACKAGE, ArgsClassGenerator.SYNTHETIC_PACKAGE); SecureJar jar = SecureJar.from(sj -> JarMetadata.fromFileName(path, packages, ImmutableList.of()), codeSource); return new Resource(IModuleLayerManager.Layer.GAME, ImmutableList.of(jar)); } From 08168676c754dbba969c94fdd456683b9ac79d61 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Fri, 17 May 2024 20:20:27 +0100 Subject: [PATCH 47/84] Really only linear offsets are supported with base assumption --- .../mixin/injection/struct/ArgOffsets.java | 43 ++++++++----------- 1 file changed, 18 insertions(+), 25 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java index 1d23a1e51..c4d88e028 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java @@ -27,8 +27,12 @@ import org.objectweb.asm.Type; /** - * Decoration which stores a mapping of new argument offsets to original - * argument offsets. + * Decoration which stores a linear offset of arguments when a node replacement + * results in a call to a method with the same arguments as the original + * (replaced) call but offset by some fixed amount. Since ModifyArg and + * ModifyArgs always assume the method args are on the top of the stack (which + * they must be), this essentially results in just chopping off a fixed number + * of arguments from the start of the method. */ public class ArgOffsets implements IChainedDecoration { @@ -38,14 +42,14 @@ public class ArgOffsets implements IChainedDecoration { public static final String KEY = "argOffsets"; /** - * Default offsets + * The offset for the start of the args */ - public static final ArgOffsets UNITY = new ArgOffsets(0, 1024); + private final int offset; /** - * Mapping of original offsets to new offsets + * The total number of (original) args */ - private final int[] mapping; + private final int length; /** * If this offset collection replaces a previous mapping, chain to the next @@ -56,23 +60,12 @@ public class ArgOffsets implements IChainedDecoration { /** * Create contiguous offsets starting from start and continuing for length * - * @param start start index + * @param offset start index * @param length length */ - public ArgOffsets(int start, int length) { - this.mapping = new int[length]; - for (int i = 0; i < length; i++) { - this.mapping[i] = start++; - } - } - - /** - * Create an offset collection from an explicit array - * - * @param offsets offsets to store - */ - public ArgOffsets(int... offsets) { - this.mapping = offsets; + public ArgOffsets(int offset, int length) { + this.offset = offset; + this.length = length; } /* (non-Javadoc) @@ -89,7 +82,7 @@ public void replace(ArgOffsets old) { * Get the size of this mapping collection */ public int getLength() { - return this.mapping.length; + return this.length; } /** @@ -99,7 +92,7 @@ public int getLength() { * @return The original index based on this mapping */ public int getArgIndex(int index) { - int offsetIndex = this.mapping[index]; + int offsetIndex = index + this.offset; return this.next != null ? this.next.getArgIndex(offsetIndex) : offsetIndex; } @@ -110,8 +103,8 @@ public int getArgIndex(int index) { * @return Unmapped arguments */ public Type[] apply(Type[] args) { - Type[] transformed = new Type[this.mapping.length]; - for (int i = 0; i < this.mapping.length; i++) { + Type[] transformed = new Type[this.length]; + for (int i = 0; i < this.length; i++) { int offset = this.getArgIndex(i); if (offset < args.length) { transformed[i] = args[offset]; From a2751aa134b043d99014abb1015cbeb9b6f07720 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 18 May 2024 11:17:03 +0100 Subject: [PATCH 48/84] Apply injectors in discrete phases, ensure Redirects always go last(ish) --- .../asm/mixin/MixinEnvironment.java | 6 +- .../struct/CallbackInjectionInfo.java | 2 + .../mixin/injection/struct/InjectionInfo.java | 117 ++++++++++++++++-- .../struct/ModifyArgInjectionInfo.java | 2 + .../struct/ModifyArgsInjectionInfo.java | 2 + .../struct/ModifyConstantInjectionInfo.java | 2 + .../struct/ModifyVariableInjectionInfo.java | 2 + .../struct/RedirectInjectionInfo.java | 2 + .../transformer/MixinApplicatorInterface.java | 6 +- .../transformer/MixinApplicatorStandard.java | 56 ++++++--- .../mixin/transformer/MixinTargetContext.java | 30 ++++- 11 files changed, 195 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java index ada4d0090..1fe135eb9 100644 --- a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java +++ b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java @@ -1086,7 +1086,7 @@ public static enum Feature { /** * Support for the use of injector annotations in interface mixins */ - INJECTORS_IN_INTERFACE_MIXINS(false) { + INJECTORS_IN_INTERFACE_MIXINS { @Override public boolean isAvailable() { @@ -1109,6 +1109,10 @@ public boolean isEnabled() { */ private boolean enabled; + private Feature() { + this(false); + } + private Feature(boolean enabled) { this.enabled = enabled; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/CallbackInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/CallbackInjectionInfo.java index c1875ba4e..b33a80132 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/CallbackInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/CallbackInjectionInfo.java @@ -31,6 +31,7 @@ import org.spongepowered.asm.mixin.injection.callback.LocalCapture; import org.spongepowered.asm.mixin.injection.code.Injector; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.AnnotationType; +import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.InjectorOrder; import org.spongepowered.asm.mixin.transformer.MixinTargetContext; import org.spongepowered.asm.util.Annotations; @@ -40,6 +41,7 @@ * Information about a callback to inject, usually specified by {@link Inject} */ @AnnotationType(Inject.class) +@InjectorOrder(InjectorOrder.BUILTIN_CALLBACKS) public class CallbackInjectionInfo extends InjectionInfo { protected CallbackInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java index f52034330..57e7c0a88 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java @@ -108,12 +108,110 @@ public abstract class InjectionInfo extends SpecialMethodInfo implements ISliceC @java.lang.annotation.Target(ElementType.TYPE) public @interface HandlerPrefix { + /** + * Default conform prefix for handler methods + */ + public static final String DEFAULT = "handler"; + /** * String prefix for conforming handler methods */ public String value(); } + + /** + * Decoration for subclasses which specifies the order (phase) in which the + * injector should be applied relative to other injectors. Built-in + * injectors all have predefined orders which allows custom injectors to + * specify their own order either as an explicit priority (eg. LATE) or + * relative to a known injector (eg. InjectorOrder.BUILTIN_REDIRECT - 100). + * + *

Built-in injectors are grouped into three separate phases rather than + * split into individual phases, in order to retain the existing behaviour + * of mixin priority. Injectors in the same order are sorted by mixin + * priority and declaration order within the mixin as always.

+ */ + @Retention(RetentionPolicy.RUNTIME) + @java.lang.annotation.Target(ElementType.TYPE) + public @interface InjectorOrder { + + /** + * The lowest possible order, avoid using this unlesss you are insane + */ + public static final int FIRST = Integer.MIN_VALUE; + + /** + * A very early injector, will run before injectors such as ModifyArgs + * and ModifyVariable, as well as before EARLY injectors + */ + public static final int VERY_EARLY = 100; + + /** + * An early injector, will run before injectors suchs as ModifyArgs + * and ModifyVariable + */ + public static final int EARLY = 250; + + /** + * Built-in order for ModifyArg injectors + */ + public static final int BUILTIN_MODIFYARG = 500; + + /** + * Built-in order for ModifyArgs injectors + */ + public static final int BUILTIN_MODIFYARGS = 500; + + /** + * Built-in order for ModifyVariable injectors + */ + public static final int BUILTIN_MODIFYVARIABLE = 500; + + /** + * Default order + */ + public static final int DEFAULT = 1000; + + /** + * Built-in order for Inject injectors + */ + public static final int BUILTIN_CALLBACKS = 1000; + + /** + * Late injector, runs after most injectors except VERY_LATE and + * Redirect injectors + */ + public static final int LATE = 2000; + + /** + * Built-in order for ModifyConstant injectors + */ + public static final int BUILTIN_MODIFYCONSTANT = 5000; + + /** + * Built-in order for Redirect injectors + */ + public static final int BUILTIN_REDIRECT = 5000; + + /** + * Very late injector, runs after nearly all injectors including + * Redirect injectors + */ + public static final int VERY_LATE = 10000; + + /** + * The highest possible order, using this causes the universe to + * implode, bringing about the end of days. + */ + public static final int LAST = Integer.MAX_VALUE; + + /** + * String prefix for conforming handler methods + */ + public int value() default InjectorOrder.DEFAULT; + + } /** * An injector registration entry @@ -137,7 +235,7 @@ static class InjectorEntry { this.annotationDesc = Type.getDescriptor(annotationType); HandlerPrefix handlerPrefix = type.getAnnotation(HandlerPrefix.class); - this.prefix = handlerPrefix != null ? handlerPrefix.value() : InjectionInfo.DEFAULT_PREFIX; + this.prefix = handlerPrefix != null ? handlerPrefix.value() : HandlerPrefix.DEFAULT; } InjectionInfo create(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { @@ -156,11 +254,6 @@ InjectionInfo create(MixinTargetContext mixin, MethodNode method, AnnotationNode } } - /** - * Default conform prefix for handler methods - */ - public static final String DEFAULT_PREFIX = "handler"; - /** * Registry of subclasses */ @@ -387,6 +480,14 @@ public boolean isValid() { return this.targets.size() > 0 && this.injectionPoints.size() > 0; } + /** + * Get the application order for this injector type + */ + public int getOrder() { + InjectorOrder injectorOrder = this.getClass().getAnnotation(InjectorOrder.class); + return injectorOrder != null ? injectorOrder.value() : InjectorOrder.DEFAULT; + } + /** * Discover injection points */ @@ -624,7 +725,7 @@ public static AnnotationNode getInjectorAnnotation(IMixinInfo mixin, MethodNode */ public static String getInjectorPrefix(AnnotationNode annotation) { if (annotation == null) { - return InjectionInfo.DEFAULT_PREFIX; + return HandlerPrefix.DEFAULT; } for (InjectorEntry injector : InjectionInfo.registry.values()) { @@ -633,7 +734,7 @@ public static String getInjectorPrefix(AnnotationNode annotation) { } } - return InjectionInfo.DEFAULT_PREFIX; + return HandlerPrefix.DEFAULT; } static String describeInjector(IMixinContext mixin, AnnotationNode annotation, MethodNode method) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgInjectionInfo.java index 9446e9805..8ca2b6e8d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgInjectionInfo.java @@ -31,6 +31,7 @@ import org.spongepowered.asm.mixin.injection.invoke.ModifyArgInjector; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.AnnotationType; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.HandlerPrefix; +import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.InjectorOrder; import org.spongepowered.asm.mixin.transformer.MixinTargetContext; import org.spongepowered.asm.util.Annotations; @@ -39,6 +40,7 @@ */ @AnnotationType(ModifyArg.class) @HandlerPrefix("modify") +@InjectorOrder(InjectorOrder.BUILTIN_MODIFYARG) public class ModifyArgInjectionInfo extends InjectionInfo { public ModifyArgInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgsInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgsInjectionInfo.java index 52ff5573e..720a79a4d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgsInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgsInjectionInfo.java @@ -31,6 +31,7 @@ import org.spongepowered.asm.mixin.injection.invoke.ModifyArgsInjector; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.AnnotationType; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.HandlerPrefix; +import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.InjectorOrder; import org.spongepowered.asm.mixin.transformer.MixinTargetContext; /** @@ -38,6 +39,7 @@ */ @AnnotationType(ModifyArgs.class) @HandlerPrefix("args") +@InjectorOrder(InjectorOrder.BUILTIN_MODIFYARG) public class ModifyArgsInjectionInfo extends InjectionInfo { public ModifyArgsInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java index 7a0b16e8f..b05146da2 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java @@ -36,6 +36,7 @@ import org.spongepowered.asm.mixin.injection.points.BeforeConstant; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.AnnotationType; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.HandlerPrefix; +import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.InjectorOrder; import org.spongepowered.asm.mixin.transformer.MixinTargetContext; import com.google.common.base.Strings; @@ -45,6 +46,7 @@ */ @AnnotationType(ModifyConstant.class) @HandlerPrefix("constant") +@InjectorOrder(InjectorOrder.BUILTIN_MODIFYCONSTANT) public class ModifyConstantInjectionInfo extends InjectionInfo { private static final String CONSTANT_ANNOTATION_CLASS = Constant.class.getName().replace('.', '/'); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyVariableInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyVariableInjectionInfo.java index cefb00ae2..4cf7e7fb6 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyVariableInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyVariableInjectionInfo.java @@ -32,6 +32,7 @@ import org.spongepowered.asm.mixin.injection.modify.ModifyVariableInjector; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.AnnotationType; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.HandlerPrefix; +import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.InjectorOrder; import org.spongepowered.asm.mixin.transformer.MixinTargetContext; /** @@ -39,6 +40,7 @@ */ @AnnotationType(ModifyVariable.class) @HandlerPrefix("localvar") +@InjectorOrder(InjectorOrder.BUILTIN_MODIFYVARIABLE) public class ModifyVariableInjectionInfo extends InjectionInfo { public ModifyVariableInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/RedirectInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/RedirectInjectionInfo.java index 91ee7cd8d..d2db9b93e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/RedirectInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/RedirectInjectionInfo.java @@ -31,6 +31,7 @@ import org.spongepowered.asm.mixin.injection.invoke.RedirectInjector; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.AnnotationType; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.HandlerPrefix; +import org.spongepowered.asm.mixin.injection.struct.InjectionInfo.InjectorOrder; import org.spongepowered.asm.mixin.transformer.MixinTargetContext; /** @@ -38,6 +39,7 @@ */ @AnnotationType(Redirect.class) @HandlerPrefix("redirect") +@InjectorOrder(InjectorOrder.BUILTIN_REDIRECT) public class RedirectInjectionInfo extends InjectionInfo { public RedirectInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java index 3f47ceb04..7d820dffe 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java @@ -118,12 +118,12 @@ protected void prepareInjections(MixinTargetContext mixin) { /* (non-Javadoc) * @see org.spongepowered.asm.mixin.transformer.MixinApplicator * #applyInjections( - * org.spongepowered.asm.mixin.transformer.MixinTargetContext) + * org.spongepowered.asm.mixin.transformer.MixinTargetContext, int) */ @Override - protected void applyInjections(MixinTargetContext mixin) { + protected void applyInjections(MixinTargetContext mixin, int injectorOrder) { if (Feature.INJECTORS_IN_INTERFACE_MIXINS.isEnabled()) { - super.applyInjections(mixin); + super.applyInjections(mixin, injectorOrder); return; } } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java index d5350c291..e80f577e0 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java @@ -70,6 +70,7 @@ import org.spongepowered.asm.util.throwables.InvalidConstraintException; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; /** * Applies mixins to a target class @@ -104,12 +105,22 @@ enum ApplicatorPass { */ PREINJECT, + /** + * Apply accessors and invokers + */ + ACCESSOR, + /** * Apply injectors from previous pass */ INJECT } + + /** + * Order collection to use for all passes except ApplicatorPass.INJECT + */ + protected static final Set ORDERS_NONE = ImmutableSet.of(Integer.valueOf(0)); /** * Log more things @@ -211,17 +222,28 @@ final void apply(SortedSet mixins) { activity.next("%s Applicator Phase", pass); Section timer = this.profiler.begin("pass", pass.name().toLowerCase(Locale.ROOT)); IActivity applyActivity = this.activities.begin("Mixin"); - for (Iterator iter = mixinContexts.iterator(); iter.hasNext();) { - current = iter.next(); - applyActivity.next(current.toString()); - try { - this.applyMixin(current, pass); - } catch (InvalidMixinException ex) { - if (current.isRequired()) { - throw ex; + + Set orders = MixinApplicatorStandard.ORDERS_NONE; + if (pass == ApplicatorPass.INJECT) { + orders = new TreeSet(); + for (MixinTargetContext context : mixinContexts) { + context.getInjectorOrders(orders); + } + } + + for (Integer injectorOrder : orders) { + for (Iterator iter = mixinContexts.iterator(); iter.hasNext();) { + current = iter.next(); + applyActivity.next(current.toString()); + try { + this.applyMixin(current, pass, injectorOrder.intValue()); + } catch (InvalidMixinException ex) { + if (current.isRequired()) { + throw ex; + } + this.context.addSuppressed(ex); + iter.remove(); // Do not process this mixin further } - this.context.addSuppressed(ex); - iter.remove(); // Do not process this mixin further } } applyActivity.end(); @@ -261,7 +283,7 @@ final void apply(SortedSet mixins) { * * @param mixin Mixin to apply */ - protected final void applyMixin(MixinTargetContext mixin, ApplicatorPass pass) { + protected final void applyMixin(MixinTargetContext mixin, ApplicatorPass pass, int injectorOrder) { IActivity activity = this.activities.begin("Apply"); switch (pass) { case MAIN: @@ -286,11 +308,14 @@ protected final void applyMixin(MixinTargetContext mixin, ApplicatorPass pass) { this.prepareInjections(mixin); break; - case INJECT: + case ACCESSOR: activity.next("Apply Accessors"); this.applyAccessors(mixin); + break; + + case INJECT: activity.next("Apply Injections"); - this.applyInjections(mixin); + this.applyInjections(mixin, injectorOrder); break; default: @@ -697,9 +722,10 @@ protected void prepareInjections(MixinTargetContext mixin) { * Apply all injectors discovered in the previous pass * * @param mixin Mixin being applied + * @param injectorOrder injector order for this pass */ - protected void applyInjections(MixinTargetContext mixin) { - mixin.applyInjections(); + protected void applyInjections(MixinTargetContext mixin, int injectorOrder) { + mixin.applyInjections(injectorOrder); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java index 83a1b6226..7565a06f9 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java @@ -1400,36 +1400,56 @@ void prepareInjections() { } } + /** + * Get all injector order values for injectors in this mixin + * + * @param orders Orders Set to add this mixin's orders to + */ + void getInjectorOrders(Set orders) { + for (InjectionInfo injectInfo : this.injectors) { + orders.add(injectInfo.getOrder()); + } + } + /** * Apply injectors discovered in the {@link #prepareInjections()} pass + * + * @param injectorOrder injector order for this pass */ - void applyInjections() { + void applyInjections(int injectorOrder) { this.activities.clear(); + List injectors = new ArrayList(); + for (InjectionInfo injectInfo : this.injectors) { + if (injectInfo.getOrder() == injectorOrder) { + injectors.add(injectInfo); + } + } + try { IActivity applyActivity = this.activities.begin("PreInject"); IActivity preInjectActivity = this.activities.begin("?"); - for (InjectionInfo injectInfo : this.injectors) { + for (InjectionInfo injectInfo : injectors) { preInjectActivity.next(injectInfo.toString()); injectInfo.preInject(); } applyActivity.next("Inject"); IActivity injectActivity = this.activities.begin("?"); - for (InjectionInfo injectInfo : this.injectors) { + for (InjectionInfo injectInfo : injectors) { injectActivity.next(injectInfo.toString()); injectInfo.inject(); } applyActivity.next("PostInject"); IActivity postInjectActivity = this.activities.begin("?"); - for (InjectionInfo injectInfo : this.injectors) { + for (InjectionInfo injectInfo : injectors) { postInjectActivity.next(injectInfo.toString()); injectInfo.postInject(); } applyActivity.end(); - this.injectors.clear(); + this.injectors.removeAll(injectors); } catch (InvalidMixinException ex) { ex.prepend(this.activities); throw ex; From 1f34ee908abd27ba18b4a5a0de608ca2de316060 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 18 May 2024 15:46:06 +0100 Subject: [PATCH 49/84] Run all preinject operations before starting any actual injections --- .../transformer/MixinApplicatorInterface.java | 13 ++++++++ .../transformer/MixinApplicatorStandard.java | 31 +++++++++++++++--- .../mixin/transformer/MixinTargetContext.java | 32 ++++++++++++++----- 3 files changed, 63 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java index 7d820dffe..9071150c0 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java @@ -115,6 +115,19 @@ protected void prepareInjections(MixinTargetContext mixin) { } } + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.transformer.MixinApplicator + * #applyPreInjections( + * org.spongepowered.asm.mixin.transformer.MixinTargetContext) + */ + @Override + protected void applyPreInjections(MixinTargetContext mixin) { + if (Feature.INJECTORS_IN_INTERFACE_MIXINS.isEnabled()) { + super.applyPreInjections(mixin); + return; + } + } + /* (non-Javadoc) * @see org.spongepowered.asm.mixin.transformer.MixinApplicator * #applyInjections( diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java index e80f577e0..31b00a3da 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java @@ -103,17 +103,22 @@ enum ApplicatorPass { /** * Enumerate injectors and scan for injection points */ - PREINJECT, + INJECT_PREPARE, /** * Apply accessors and invokers */ ACCESSOR, + /** + * Apply preinjection steps on injectors from previous pass + */ + INJECT_PREINJECT, + /** * Apply injectors from previous pass */ - INJECT + INJECT_APPLY } @@ -224,7 +229,7 @@ final void apply(SortedSet mixins) { IActivity applyActivity = this.activities.begin("Mixin"); Set orders = MixinApplicatorStandard.ORDERS_NONE; - if (pass == ApplicatorPass.INJECT) { + if (pass == ApplicatorPass.INJECT_APPLY) { orders = new TreeSet(); for (MixinTargetContext context : mixinContexts) { context.getInjectorOrders(orders); @@ -303,7 +308,7 @@ protected final void applyMixin(MixinTargetContext mixin, ApplicatorPass pass, i this.applyInitialisers(mixin); break; - case PREINJECT: + case INJECT_PREPARE: activity.next("Prepare Injections"); this.prepareInjections(mixin); break; @@ -313,7 +318,12 @@ protected final void applyMixin(MixinTargetContext mixin, ApplicatorPass pass, i this.applyAccessors(mixin); break; - case INJECT: + case INJECT_PREINJECT: + activity.next("Apply Injections"); + this.applyPreInjections(mixin); + break; + + case INJECT_APPLY: activity.next("Apply Injections"); this.applyInjections(mixin, injectorOrder); break; @@ -718,6 +728,17 @@ protected void prepareInjections(MixinTargetContext mixin) { mixin.prepareInjections(); } + /** + * Run preinject application on all injectors discovered in the previous + * pass + * + * @param mixin Mixin being applied + * @param injectorOrder injector order for this pass + */ + protected void applyPreInjections(MixinTargetContext mixin) { + mixin.applyPreInjections(); + } + /** * Apply all injectors discovered in the previous pass * diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java index 7565a06f9..3872c49f5 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java @@ -1410,6 +1410,29 @@ void getInjectorOrders(Set orders) { orders.add(injectInfo.getOrder()); } } + + /** + * Run the preinject step for all discovered injectors + */ + void applyPreInjections() { + this.activities.clear(); + + try { + IActivity applyActivity = this.activities.begin("PreInject"); + IActivity preInjectActivity = this.activities.begin("?"); + for (InjectionInfo injectInfo : this.injectors) { + preInjectActivity.next(injectInfo.toString()); + injectInfo.preInject(); + } + applyActivity.end(); + } catch (InvalidMixinException ex) { + ex.prepend(this.activities); + throw ex; + } catch (Exception ex) { + throw new InvalidMixinException(this, "Unexpecteded " + ex.getClass().getSimpleName() + " whilst transforming the mixin class:", ex, + this.activities); + } + } /** * Apply injectors discovered in the {@link #prepareInjections()} pass @@ -1427,14 +1450,7 @@ void applyInjections(int injectorOrder) { } try { - IActivity applyActivity = this.activities.begin("PreInject"); - IActivity preInjectActivity = this.activities.begin("?"); - for (InjectionInfo injectInfo : injectors) { - preInjectActivity.next(injectInfo.toString()); - injectInfo.preInject(); - } - - applyActivity.next("Inject"); + IActivity applyActivity = this.activities.begin("Inject"); IActivity injectActivity = this.activities.begin("?"); for (InjectionInfo injectInfo : injectors) { injectActivity.next(injectInfo.toString()); From 3a090bd0ca4e1caf895102ebe25427d22031b29a Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 19 May 2024 19:04:03 +0100 Subject: [PATCH 50/84] Revert "New: Allow injection point specifiers anywhere. (#129)" This reverts commit e70693a79a68a524a88b9286c87615f345c851e4. --- .../asm/mixin/injection/InjectionPoint.java | 42 +++++++--------- .../asm/mixin/injection/Slice.java | 14 +++--- .../asm/mixin/injection/code/Injector.java | 34 ++++--------- .../asm/mixin/injection/code/MethodSlice.java | 50 +++++-------------- .../mixin/injection/code/MethodSlices.java | 10 ---- .../injection/points/BeforeConstant.java | 2 +- .../mixin/injection/struct/InjectionInfo.java | 2 - .../injection/struct/InjectionPointData.java | 18 +++---- 8 files changed, 57 insertions(+), 115 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java index fb3bd5439..00d5abb23 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java @@ -162,19 +162,15 @@ public abstract class InjectionPoint { } /** - * Additional specifier for injection points. Specifiers can be - * supplied in {@link At} annotations by including a colon (:) - * character followed by the specifier type (case-sensitive), eg: + * Selector type for slice delmiters, ignored for normal injection points. + * Selectors can be supplied in {@link At} annotations by including + * a colon (:) character followed by the selector type + * (case-sensitive), eg: * *
@At(value = "INVOKE:LAST", ... )
*/ - public enum Specifier { - - /** - * Use all instructions from the query result. - */ - ALL, - + public enum Selector { + /** * Use the first instruction from the query result. */ @@ -190,13 +186,13 @@ public enum Specifier { * more than one instruction this should be considered a fail-fast error * state and a runtime exception will be thrown. */ - ONE, + ONE; /** - * Use the default setting as defined by the consumer. For slices this - * is {@link #FIRST}, for all other consumers this is {@link #ALL} + * Default selector type used if no selector is explicitly specified. + * For internal use only. Currently {@link #FIRST} */ - DEFAULT; + public static final Selector DEFAULT = Selector.FIRST; } @@ -280,26 +276,26 @@ enum ShiftByViolationBehaviour { } private final String slice; - private final Specifier specifier; + private final Selector selector; private final String id; private final IMessageSink messageSink; protected InjectionPoint() { - this("", Specifier.DEFAULT, null); + this("", Selector.DEFAULT, null); } protected InjectionPoint(InjectionPointData data) { - this(data.getSlice(), data.getSpecifier(), data.getId(), data.getMessageSink()); + this(data.getSlice(), data.getSelector(), data.getId(), data.getMessageSink()); } - public InjectionPoint(String slice, Specifier specifier, String id) { - this(slice, specifier, id, null); + public InjectionPoint(String slice, Selector selector, String id) { + this(slice, selector, id, null); } - public InjectionPoint(String slice, Specifier specifier, String id, IMessageSink messageSink) { + public InjectionPoint(String slice, Selector selector, String id, IMessageSink messageSink) { this.slice = slice; - this.specifier = specifier; + this.selector = selector; this.id = id; this.messageSink = messageSink; } @@ -308,8 +304,8 @@ public String getSlice() { return this.slice; } - public Specifier getSpecifier(Specifier defaultSpecifier) { - return this.specifier == Specifier.DEFAULT ? defaultSpecifier : this.specifier; + public Selector getSelector() { + return this.selector; } public String getId() { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java b/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java index 68689107d..857432dbf 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java @@ -28,7 +28,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; /** * A Slice identifies a section of a method to search for injection @@ -162,10 +162,10 @@ /** * Injection point which specifies the start of the slice region. - * {@link At}s supplied here should generally use a {@link Specifier} + * {@link At}s supplied here should generally specify a {@link Selector} * in order to identify which instruction should be used for queries which - * return multiple results. The specifier is supplied by appending the - * specifier type to the injection point type as follows: + * return multiple results. The selector is specified by appending the + * selector type to the injection point type as follows: * *
@At(value = "INVOKE:LAST", ... )
* @@ -182,9 +182,9 @@ /** * Injection point which specifies the end of the slice region. * Like {@link #from}, {@link At}s supplied here should generally specify a - * {@link Specifier} in order to identify which instruction should be used - * for queries which return multiple results. The specifier is supplied by - * appending the specifier type to the injection point type as follows: + * {@link Selector} in order to identify which instruction should be used + * for queries which return multiple results. The selector is specified by + * appending the selector type to the injection point type as follows: * *
@At(value = "INVOKE:LAST", ... )
* diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java index b7b55ff3e..c37a3d563 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java @@ -42,7 +42,6 @@ import org.spongepowered.asm.mixin.injection.Coerce; import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; -import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; import org.spongepowered.asm.mixin.injection.invoke.RedirectInjector; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; @@ -295,7 +294,7 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li IMixinContext mixin = this.info.getMixin(); MethodNode method = injectorTarget.getMethod(); Map targetNodes = new TreeMap(); - List nodes = new ArrayList(32); + Collection nodes = new ArrayList(32); for (InjectionPoint injectionPoint : injectionPoints) { nodes.clear(); @@ -308,20 +307,15 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li injectorTarget, injectorTarget.getMergedBy(), injectorTarget.getMergedPriority())); } - if (!this.findTargetNodes(method, injectionPoint, injectorTarget, nodes)) { - continue; - } - - Specifier specifier = injectionPoint.getSpecifier(Specifier.ALL); - if (specifier == Specifier.ONE && nodes.size() != 1) { - throw new InvalidInjectionException(this.info, String.format("%s on %s has specifier :ONE but matched %d instructions", - injectionPoint, this, nodes.size())); - } else if (specifier != Specifier.ALL && nodes.size() > 1) { - AbstractInsnNode specified = nodes.get(specifier == Specifier.FIRST ? 0 : nodes.size() - 1); - this.addTargetNode(method, targetNodes, injectionPoint, specified); - } else { + if (this.findTargetNodes(method, injectionPoint, injectorTarget, nodes)) { for (AbstractInsnNode insn : nodes) { - this.addTargetNode(method, targetNodes, injectionPoint, insn); + Integer key = method.instructions.indexOf(insn); + TargetNode targetNode = targetNodes.get(key); + if (targetNode == null) { + targetNode = new TargetNode(insn); + targetNodes.put(key, targetNode); + } + targetNode.nominators.add(injectionPoint); } } } @@ -329,16 +323,6 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li return targetNodes.values(); } - protected void addTargetNode(MethodNode method, Map targetNodes, InjectionPoint injectionPoint, AbstractInsnNode insn) { - Integer key = method.instructions.indexOf(insn); - TargetNode targetNode = targetNodes.get(key); - if (targetNode == null) { - targetNode = new TargetNode(insn); - targetNodes.put(key, targetNode); - } - targetNode.nominators.add(injectionPoint); - } - protected boolean findTargetNodes(MethodNode into, InjectionPoint injectionPoint, InjectorTarget injectorTarget, Collection nodes) { return injectionPoint.find(into.desc, injectorTarget.getSlice(injectionPoint), nodes); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java index 58201c05b..79891e95f 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java @@ -37,7 +37,7 @@ import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.struct.InjectionPointAnnotationContext; import org.spongepowered.asm.mixin.injection.throwables.InjectionError; @@ -325,11 +325,6 @@ public int realIndexOf(AbstractInsnNode insn) { * Descriptive name of the slice, used in exceptions */ private final String name; - - /** - * Success counts for from and to injection points - */ - private int successCountFrom, successCountTo; /** * ctor @@ -366,8 +361,8 @@ public String getId() { */ public InsnListReadOnly getSlice(MethodNode method) { int max = method.instructions.size() - 1; - int start = this.find(method, this.from, 0, 0, "from"); - int end = this.find(method, this.to, max, start, "to"); + int start = this.find(method, this.from, 0, 0, this.name + "(from)"); + int end = this.find(method, this.to, max, start, this.name + "(to)"); if (start > end) { throw new InvalidSliceException(this.owner, String.format("%s is negative size. Range(%d -> %d)", this.describe(), start, end)); @@ -394,53 +389,32 @@ public InsnListReadOnly getSlice(MethodNode method) { * @param defaultValue Value to return if injection point is null (open * ended) * @param failValue Value to use if query fails - * @param argument The name of the argument ("from" or "to") for debug msgs + * @param description Description for error message * @return matching insn index */ - private int find(MethodNode method, InjectionPoint injectionPoint, int defaultValue, int failValue, String argument) { + private int find(MethodNode method, InjectionPoint injectionPoint, int defaultValue, int failValue, String description) { if (injectionPoint == null) { return defaultValue; } - String description = String.format("%s(%s)", this.name, argument); Deque nodes = new LinkedList(); InsnListReadOnly insns = new InsnListReadOnly(method.instructions); boolean result = injectionPoint.find(method.desc, insns, nodes); - Specifier specifier = injectionPoint.getSpecifier(Specifier.FIRST); - if (specifier == Specifier.ALL) { - throw new InvalidSliceException(this.owner, String.format("ALL is not a valid specifier for slice %s", this.describe(description))); - } - if (nodes.size() != 1 && specifier == Specifier.ONE) { + Selector select = injectionPoint.getSelector(); + if (nodes.size() != 1 && select == Selector.ONE) { throw new InvalidSliceException(this.owner, String.format("%s requires 1 result but found %d", this.describe(description), nodes.size())); } if (!result) { + if (this.owner.getMixin().getOption(Option.DEBUG_VERBOSE)) { + MethodSlice.logger.warn("{} did not match any instructions", this.describe(description)); + } return failValue; } - if ("from".equals(argument)) { - this.successCountFrom++; - } else { - this.successCountTo++; - } - - return method.instructions.indexOf(specifier == Specifier.FIRST ? nodes.getFirst() : nodes.getLast()); - } - - /** - * Perform post-injection debugging and validation tasks - */ - public void postInject() { - if (this.owner.getMixin().getOption(Option.DEBUG_VERBOSE)) { - if (this.from != null && this.successCountFrom == 0) { - MethodSlice.logger.warn("{} did not match any instructions", this.describe(this.name + "(from)")); - } - if (this.to != null && this.successCountTo == 0) { - MethodSlice.logger.warn("{} did not match any instructions", this.describe(this.name + "(to)")); - } - } + return method.instructions.indexOf(select == Selector.FIRST ? nodes.getFirst() : nodes.getLast()); } - + /* (non-Javadoc) * @see java.lang.Object#toString() */ diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java index 0789b7620..fb9979aa1 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java @@ -82,16 +82,6 @@ public MethodSlice get(String id) { return this.slices.get(id); } - /** - * Called to do post-injection validation/debug logging for slices - */ - public void postInject() { - for (MethodSlice slice : this.slices.values()) { - slice.postInject(); - } - } - - /* (non-Javadoc) * @see java.lang.Object#toString() */ diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java index bbad13edc..e8030562e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java @@ -147,7 +147,7 @@ public class BeforeConstant extends InjectionPoint { private final boolean log; public BeforeConstant(IMixinContext context, AnnotationNode node, String returnType) { - super(Annotations.getValue(node, "slice", ""), Specifier.DEFAULT, null); + super(Annotations.getValue(node, "slice", ""), Selector.DEFAULT, null); Boolean empty = Annotations.getValue(node, "nullValue", (Boolean)null); this.ordinal = Annotations.getValue(node, "ordinal", Integer.valueOf(-1)); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java index bf6be2c55..bb686fb55 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java @@ -473,8 +473,6 @@ public void postInject() { String.format("Critical injection failure: %s %s%s in %s failed injection check, %d succeeded of %d allowed.%s", description, this.methodName, this.method.desc, this.mixin, this.injectedCallbackCount, this.maxCallbackCount, extraInfo)); } - - this.slices.postInject(); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java index b41cdd7ce..cb4290a94 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java @@ -36,7 +36,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.IInjectionPointContext; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; import org.spongepowered.asm.mixin.injection.selectors.InvalidSelectorException; @@ -86,7 +86,7 @@ public class InjectionPointData { /** * Selector parsed from the at argument, only used by slices */ - private final Specifier specifier; + private final Selector selector; /** * Target @@ -131,7 +131,7 @@ public InjectionPointData(IInjectionPointContext context, String at, List args) { @@ -172,10 +172,10 @@ public String getType() { } /** - * Get the specifier value parsed from the injector + * Get the selector value parsed from the injector */ - public Specifier getSpecifier() { - return this.specifier; + public Selector getSelector() { + return this.selector; } /** @@ -362,7 +362,7 @@ public String toString() { } private static Pattern createPattern() { - return Pattern.compile(String.format("^(.+?)(:(%s))?$", Joiner.on('|').join(Specifier.values()))); + return Pattern.compile(String.format("^(.+?)(:(%s))?$", Joiner.on('|').join(Selector.values()))); } /** @@ -380,8 +380,8 @@ private static String parseType(Matcher matcher, String at) { return matcher.matches() ? matcher.group(1) : at; } - private static Specifier parseSpecifier(Matcher matcher) { - return matcher.matches() && matcher.group(3) != null ? Specifier.valueOf(matcher.group(3)) : Specifier.DEFAULT; + private static Selector parseSelector(Matcher matcher) { + return matcher.matches() && matcher.group(3) != null ? Selector.valueOf(matcher.group(3)) : Selector.DEFAULT; } private static int parseInt(String string, int defaultValue) { From 9cf325d31ca2c8a155a651c0cc9337e224da9f23 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 19 May 2024 19:10:19 +0100 Subject: [PATCH 51/84] Revert "Allowing injecting in constructor's (#30 #40)" This reverts commit 536170a94675b07e462cc46cdb6493586bdd6dcc. --- .../asm/mixin/injection/InjectionPoint.java | 13 ------------- .../mixin/injection/callback/CallbackInjector.java | 10 ---------- 2 files changed, 23 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java index 00d5abb23..141740a37 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java @@ -351,19 +351,6 @@ public boolean checkPriority(int targetPriority, int mixinPriority) { * @return restriction level */ public RestrictTargetLevel getTargetRestriction(IInjectionPointContext context) { - return RestrictTargetLevel.CONSTRUCTORS_AFTER_DELEGATE; // Fabric change: allow inject in constructors - } - - /** - * Returns the target restriction level for this injection point's cancellation for - * {@literal @Inject} annotations. This level defines whether an injection point - * can declare {@code cancellable = true}. - * - * @param context injection-specific context - * @return restriction level - */ - // Fabric addition: prevent cancellation of inject in constructors - public RestrictTargetLevel getCancellationRestriction(IInjectionPointContext context) { return RestrictTargetLevel.METHODS_ONLY; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java index 6542992ef..8b1f157b0 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java @@ -439,16 +439,6 @@ protected void addTargetNode(Target target, List myNodes, Abstrac throw new InvalidInjectionException(this.info, String.format("%s selector %s", ip, ex.getMessage())); } - // Fabric start: prevent cancellation in constructor injections - if (this.cancellable) { - try { - this.checkTargetForNode(target, injectionNode, ip.getCancellationRestriction(this.info)); - } catch (InvalidInjectionException ex) { - throw new InvalidInjectionException(this.info, String.format("%s selector (cancellable = true) %s", ip, ex.getMessage())); - } - } - // Fabric end - String id = ip.getId(); if (Strings.isNullOrEmpty(id)) { continue; From a1d5becea7dfecae164c7968aca42154996778e2 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 19 May 2024 22:12:41 +0100 Subject: [PATCH 52/84] Fabric: Fall back to modid in MethodMapper#getMixinSourceId. --- .../spongepowered/asm/mixin/transformer/MethodMapper.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java index 1d5e4ac94..5c83b5be0 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java @@ -183,7 +183,11 @@ public String getUniqueName(MixinInfo mixin, FieldNode field, String sessionId) private static String getMixinSourceId(MixinInfo mixin, String separator) { String sourceId = mixin.getConfig().getCleanSourceId(); if (sourceId == null) { - return ""; + String modId = FabricUtil.getModId(mixin.getConfig(), null); + if (modId == null) { + return ""; + } + return modId + separator; } if (sourceId.length() > 12) { sourceId = sourceId.substring(0, 12); From 8fda508119b7dc7d85368cc0e5b8788456d76569 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 19 May 2024 22:19:31 +0100 Subject: [PATCH 53/84] Fabric: Re-allow jumps and array accesses inside initialisers. --- .../mixin/transformer/struct/Initialiser.java | 22 ++++++++++++++----- 1 file changed, 16 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java b/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java index 78ea3540b..8dfac1be2 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/struct/Initialiser.java @@ -97,10 +97,12 @@ public static InjectionMode ofEnvironment(MixinEnvironment env) { * receiving constructor. */ protected static final int[] OPCODE_BLACKLIST = { - Opcodes.RETURN, Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, Opcodes.IALOAD, Opcodes.LALOAD, Opcodes.FALOAD, Opcodes.DALOAD, - Opcodes.AALOAD, Opcodes.BALOAD, Opcodes.CALOAD, Opcodes.SALOAD, Opcodes.ISTORE, Opcodes.LSTORE, Opcodes.FSTORE, Opcodes.DSTORE, - Opcodes.ASTORE, Opcodes.IASTORE, Opcodes.LASTORE, Opcodes.FASTORE, Opcodes.DASTORE, Opcodes.AASTORE, Opcodes.BASTORE, Opcodes.CASTORE, - Opcodes.SASTORE + Opcodes.RETURN, Opcodes.ILOAD, Opcodes.LLOAD, Opcodes.FLOAD, Opcodes.DLOAD, + Opcodes.ISTORE, Opcodes.LSTORE, Opcodes.FSTORE, Opcodes.DSTORE, Opcodes.ASTORE, + // Fabric: Array opcodes cause no problems in initialisers and should not be needlessly restricted. + // Opcodes.IALOAD, Opcodes.LALOAD, Opcodes.FALOAD, Opcodes.DALOAD, Opcodes.AALOAD, + // Opcodes.BALOAD, Opcodes.CALOAD, Opcodes.SALOAD, Opcodes.IASTORE, Opcodes.LASTORE, + // Opcodes.FASTORE, Opcodes.DASTORE, Opcodes.AASTORE, Opcodes.BASTORE, Opcodes.CASTORE, Opcodes.SASTORE }; @@ -192,12 +194,20 @@ public void injectInto(Constructor ctor) { } Map labels = Bytecode.cloneLabels(ctor.insns); + // Fabric: also clone labels from the initialiser as they will be merged. for (AbstractInsnNode node : this.insns) { if (node instanceof LabelNode) { - continue; + labels.put((LabelNode) node, new LabelNode()); + } + } + for (AbstractInsnNode node : this.insns) { + if (node instanceof LabelNode) { + // Fabric: Merge cloned labels instead of skipping them. + // continue; } if (node instanceof JumpInsnNode) { - throw new InvalidMixinException(this.mixin, "Unsupported JUMP opcode in initialiser in " + this.mixin); + // Fabric: Jumps cause no issues if labels are cloned properly and should not be needlessly restricted. + // throw new InvalidMixinException(this.mixin, "Unsupported JUMP opcode in initialiser in " + this.mixin); } ctor.insertBefore(marker, node.clone(labels)); From fda52dce6b5f2d8d1c39c67b7b8d34a33fdb0046 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 19 May 2024 22:39:38 +0100 Subject: [PATCH 54/84] Fix: Resolve inappropriate `null` default in Mixins#addConfiguration. --- src/main/java/org/spongepowered/asm/mixin/Mixins.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/Mixins.java b/src/main/java/org/spongepowered/asm/mixin/Mixins.java index 1d9f3ef69..8a42f5585 100644 --- a/src/main/java/org/spongepowered/asm/mixin/Mixins.java +++ b/src/main/java/org/spongepowered/asm/mixin/Mixins.java @@ -95,7 +95,7 @@ public static void addConfigurations(String[] configFiles, IMixinConfigSource so * @param configFile path to configuration resource */ public static void addConfiguration(String configFile) { - Mixins.createConfiguration(configFile, null, null); + Mixins.addConfiguration(configFile, (IMixinConfigSource) null); } /** From 92a64e36348767ff03bc05c397d5b33ab1ab6af6 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 19 May 2024 22:45:33 +0100 Subject: [PATCH 55/84] Fabric: Default `At#unsafe` to true. --- .../java/org/spongepowered/asm/mixin/injection/At.java | 7 +++++-- .../spongepowered/asm/mixin/injection/InjectionPoint.java | 2 +- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/At.java b/src/main/java/org/spongepowered/asm/mixin/injection/At.java index b850a2b32..64ce23cd1 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/At.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/At.java @@ -237,8 +237,11 @@ public enum Shift { * to bypass this restriction, setting this option to true will * also allow other injectors to act upon constructors, though care should * be taken to ensure that the target is properly specified and attention is - * paid to the structure of the target bytecode.

+ * paid to the structure of the target bytecode.

+ * + * FABRIC CHANGE: true by default. + * */ - public boolean unsafe() default false; + public boolean unsafe() default true; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java index d5f4c79c1..e87f3e871 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java @@ -268,7 +268,7 @@ public static int parse(At at) { public static int parse(AnnotationNode at) { int flags = 0; - if (Annotations.getValue(at, "unsafe", Boolean.FALSE)) { + if (Annotations.getValue(at, "unsafe", Boolean.TRUE)) { flags |= InjectionPoint.Flags.UNSAFE; } return flags; From 0165cf9fcd001e4076bd35cad814d184d6875a89 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Tue, 14 May 2024 20:40:36 +0100 Subject: [PATCH 56/84] Restore old findInitNodeFor used by MixinExtras, mark as deprecated (cherry picked from commit 902c3439e8611fa8086e3999e62f3cd64a7b2665) --- .../asm/mixin/injection/struct/Target.java | 33 ++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index 5ac36e6cb..1c13b32a6 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -28,6 +28,7 @@ import java.util.Iterator; import java.util.List; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; @@ -633,7 +634,37 @@ public Iterator iterator() { /** * Find the first <init> invocation after the specified - * NEW insn + * NEW insn + * + * @param newNode NEW insn + * @return INVOKESPECIAL opcode of ctor, or null if not found + * + * @deprecated Use {@link #findInitNodeFor(TypeInsnNode, String)} instead: + * this method only matches the first matching <init> + * after the specified NEW, it also does not filter based on + * the descriptor passed into BeforeNew. + */ + @Deprecated + public MethodInsnNode findInitNodeFor(TypeInsnNode newNode) { + int start = this.indexOf(newNode); + for (Iterator iter = this.insns.iterator(start); iter.hasNext();) { + AbstractInsnNode insn = iter.next(); + if (insn instanceof MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESPECIAL) { + MethodInsnNode methodNode = (MethodInsnNode)insn; + if (Constants.CTOR.equals(methodNode.name) && methodNode.owner.equals(newNode.desc)) { + return methodNode; + } + } + } + return null; + } + + /** + * Find the matching <init> invocation after the specified + * NEW insn, ensuring that the supplied descriptor matches. If the + * supplied descriptor is null then any invocation matches. If + * additional NEW insns are encountered then corresponding + * <init> calls are skipped. * * @param newNode NEW insn * @param desc Descriptor to match From ab4dab0d22727892143973a1bd0ebbc47180d881 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 19 May 2024 23:07:01 +0100 Subject: [PATCH 57/84] buildscript: Relevant changes from 0.8.6 Accidentally kept these locally. --- build.gradle | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/build.gradle b/build.gradle index 5ad8caa5a..de123bf5a 100644 --- a/build.gradle +++ b/build.gradle @@ -71,8 +71,8 @@ repositories { } maven { // For modlauncher - name = 'forge' - url = 'https://files.minecraftforge.net/maven' + name = 'neoforged' + url = 'https://maven.neoforged.net/releases' } } @@ -226,7 +226,7 @@ dependencies { modlauncher9Implementation ("cpw.mods:modlauncher:$modlauncherVersion") { exclude module: 'jopt-simple' } - modlauncher9Implementation 'cpw.mods:securejarhandler:0.9.+' + modlauncher9Implementation 'cpw.mods:securejarhandler:2.1.24' // asm bridge bridgeImplementation 'org.apache.logging.log4j:log4j-core:2.0-beta9' @@ -243,7 +243,8 @@ javadoc { source sourceSets.ap.allJava options.encoding = 'UTF-8' exclude { - it.relativePath.file && it.relativePath.pathString =~ 'tools' && !(it.name =~ /SuppressedBy|package-info/) } + it.relativePath.file && it.relativePath.pathString =~ 'tools' && !(it.name =~ /SuppressedBy|package-info/) + } options { docTitle 'Welcome to the Mixin Javadoc' overview 'docs/javadoc/overview.html' From afb9f13921ea7e0e43dfb092135c1d40142e1a7e Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Mon, 20 May 2024 01:14:32 +0100 Subject: [PATCH 58/84] Fix: Restore removed methods relating to configs. --- .../org/spongepowered/asm/mixin/Mixins.java | 16 ++++++------- .../asm/mixin/transformer/Config.java | 24 +++++++++---------- 2 files changed, 20 insertions(+), 20 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/Mixins.java b/src/main/java/org/spongepowered/asm/mixin/Mixins.java index 8a42f5585..675f621f6 100644 --- a/src/main/java/org/spongepowered/asm/mixin/Mixins.java +++ b/src/main/java/org/spongepowered/asm/mixin/Mixins.java @@ -67,14 +67,14 @@ public final class Mixins { private Mixins() {} -// /** -// * Add multiple configurations -// * -// * @param configFiles config resources to add -// */ -// public static void addConfigurations(String... configFiles) { -// Mixins.addConfigurations(configFiles, null); -// } + /** + * Add multiple configurations + * + * @param configFiles config resources to add + */ + public static void addConfigurations(String... configFiles) { + Mixins.addConfigurations(configFiles, null); + } /** * Add multiple configurations diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/Config.java b/src/main/java/org/spongepowered/asm/mixin/transformer/Config.java index 759b9bdbc..805cb302b 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/Config.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/Config.java @@ -130,18 +130,18 @@ public int hashCode() { return this.name.hashCode(); } -// /** -// * Factory method, create a config from the specified config file and fail -// * over to the specified environment if no selector is present in the config -// * -// * @param configFile config resource -// * @param outer failover environment -// * @return new config or null if invalid config version -// */ -// @Deprecated -// public static Config create(String configFile, MixinEnvironment outer) { -// return Config.create(configFile, outer, null); -// } + /** + * Factory method, create a config from the specified config file and fail + * over to the specified environment if no selector is present in the config + * + * @param configFile config resource + * @param outer failover environment + * @return new config or null if invalid config version + */ + @Deprecated + public static Config create(String configFile, MixinEnvironment outer) { + return Config.create(configFile, outer, null); + } /** * Factory method, create a config from the specified config file and fail From 02bf3d22400ce3ce094f24677028462d53674676 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 21 May 2024 22:51:14 +0100 Subject: [PATCH 59/84] Change: Add stack checking to old `Target#findInitNodeFor` overload and un-deprecate it. The only reason MixinExtras uses this method is to match Redirect's behaviour, so I would like it to be kept in line with that. Additionally, there is nothing wrong with not checking the desc as long as you've definitely found the right `NEW` insn, which the injection point handles itself. --- .../asm/mixin/injection/struct/Target.java | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index 9e2203c77..eab6bd3b7 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -638,25 +638,9 @@ public Iterator iterator() { * * @param newNode NEW insn * @return INVOKESPECIAL opcode of ctor, or null if not found - * - * @deprecated Use {@link #findInitNodeFor(TypeInsnNode, String)} instead: - * this method only matches the first matching <init> - * after the specified NEW, it also does not filter based on - * the descriptor passed into BeforeNew. */ - @Deprecated public MethodInsnNode findInitNodeFor(TypeInsnNode newNode) { - int start = this.indexOf(newNode); - for (Iterator iter = this.insns.iterator(start); iter.hasNext();) { - AbstractInsnNode insn = iter.next(); - if (insn instanceof MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESPECIAL) { - MethodInsnNode methodNode = (MethodInsnNode)insn; - if (Constants.CTOR.equals(methodNode.name) && methodNode.owner.equals(newNode.desc)) { - return methodNode; - } - } - } - return null; + return this.findInitNodeFor(newNode, null); } /** From 02d7470981c0904ee20d255f44d7abc276a44ccb Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Fri, 24 May 2024 19:03:04 +0100 Subject: [PATCH 60/84] Allow authors to specify order on injector annotations --- .../asm/mixin/injection/Inject.java | 23 +++++ .../asm/mixin/injection/ModifyArg.java | 23 +++++ .../asm/mixin/injection/ModifyArgs.java | 23 +++++ .../asm/mixin/injection/ModifyConstant.java | 22 +++++ .../asm/mixin/injection/ModifyVariable.java | 23 +++++ .../asm/mixin/injection/Redirect.java | 22 +++++ .../struct/CallbackInjectionInfo.java | 2 +- .../mixin/injection/struct/InjectionInfo.java | 93 +++++++------------ .../struct/ModifyArgInjectionInfo.java | 2 +- .../struct/ModifyArgsInjectionInfo.java | 2 +- .../struct/ModifyConstantInjectionInfo.java | 2 +- .../struct/ModifyVariableInjectionInfo.java | 2 +- .../struct/RedirectInjectionInfo.java | 2 +- 13 files changed, 174 insertions(+), 67 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Inject.java b/src/main/java/org/spongepowered/asm/mixin/injection/Inject.java index d3122829b..930160ca3 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/Inject.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Inject.java @@ -293,4 +293,27 @@ */ public String constraints() default ""; + /** + * By default almost all injectors for a target class apply their injections + * at the same time. In other words, if multiple mixins target the same + * class then injectors are applied in priority order (since the mixins + * themselves are merged in priority order, and injectors run in the order + * they were merged). The exception being redirect injectors, which apply in + * a later pass. + * + *

The default order for injectors is 1000, and redirect + * injectors use 10000.

+ * + *

Specifying a value for order alters this default behaviour + * and causes the injector to inject either earlier or later than it + * normally would. For example specifying 900 will cause the + * injector to apply before others, while 1100 will apply later. + * Injectors with the same order will still apply in order of their + * mixin's priority. + * + * @return the application order for this injector, uses DEFAULT (1000) if + * not specified + */ + public int order() default 1000; + } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/ModifyArg.java b/src/main/java/org/spongepowered/asm/mixin/injection/ModifyArg.java index 8d5fcc76b..a45a15c01 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/ModifyArg.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/ModifyArg.java @@ -221,5 +221,28 @@ * @return Constraints for this annotation */ public String constraints() default ""; + + /** + * By default almost all injectors for a target class apply their injections + * at the same time. In other words, if multiple mixins target the same + * class then injectors are applied in priority order (since the mixins + * themselves are merged in priority order, and injectors run in the order + * they were merged). The exception being redirect injectors, which apply in + * a later pass. + * + *

The default order for injectors is 1000, and redirect + * injectors use 10000.

+ * + *

Specifying a value for order alters this default behaviour + * and causes the injector to inject either earlier or later than it + * normally would. For example specifying 900 will cause the + * injector to apply before others, while 1100 will apply later. + * Injectors with the same order will still apply in order of their + * mixin's priority. + * + * @return the application order for this injector, uses DEFAULT (1000) if + * not specified + */ + public int order() default 1000; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/ModifyArgs.java b/src/main/java/org/spongepowered/asm/mixin/injection/ModifyArgs.java index 2430d8359..d50a625bd 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/ModifyArgs.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/ModifyArgs.java @@ -198,5 +198,28 @@ * @return Constraints for this annotation */ public String constraints() default ""; + + /** + * By default almost all injectors for a target class apply their injections + * at the same time. In other words, if multiple mixins target the same + * class then injectors are applied in priority order (since the mixins + * themselves are merged in priority order, and injectors run in the order + * they were merged). The exception being redirect injectors, which apply in + * a later pass. + * + *

The default order for injectors is 1000, and redirect + * injectors use 10000.

+ * + *

Specifying a value for order alters this default behaviour + * and causes the injector to inject either earlier or later than it + * normally would. For example specifying 900 will cause the + * injector to apply before others, while 1100 will apply later. + * Injectors with the same order will still apply in order of their + * mixin's priority. + * + * @return the application order for this injector, uses DEFAULT (1000) if + * not specified + */ + public int order() default 1000; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/ModifyConstant.java b/src/main/java/org/spongepowered/asm/mixin/injection/ModifyConstant.java index 9870a40e7..e7a42f5fa 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/ModifyConstant.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/ModifyConstant.java @@ -178,5 +178,27 @@ * @return Constraints for this annotation */ public String constraints() default ""; + + /** + * By default almost all injectors for a target class apply their injections + * at the same time. In other words, if multiple mixins target the same + * class then injectors are applied in priority order (since the mixins + * themselves are merged in priority order, and injectors run in the order + * they were merged). Redirect injectors apply in a later pass. + * + *

The default order for redirect injectors is 10000, and all + * other injectors use 1000.

+ * + *

Specifying a value for order alters this default behaviour + * and causes the injector to inject either earlier or later than it + * normally would. For example specifying 9900 will cause the + * injector to apply before others, while 11000 will apply later. + * Injectors with the same order will still apply in order of their + * mixin's priority. + * + * @return the application order for this injector, uses default REDIRECT + * order (10000) if not specified + */ + public int order() default 10000; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/ModifyVariable.java b/src/main/java/org/spongepowered/asm/mixin/injection/ModifyVariable.java index a525f02a9..7bd2168e6 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/ModifyVariable.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/ModifyVariable.java @@ -271,5 +271,28 @@ * @return Constraints for this annotation */ public String constraints() default ""; + + /** + * By default almost all injectors for a target class apply their injections + * at the same time. In other words, if multiple mixins target the same + * class then injectors are applied in priority order (since the mixins + * themselves are merged in priority order, and injectors run in the order + * they were merged). The exception being redirect injectors, which apply in + * a later pass. + * + *

The default order for injectors is 1000, and redirect + * injectors use 10000.

+ * + *

Specifying a value for order alters this default behaviour + * and causes the injector to inject either earlier or later than it + * normally would. For example specifying 900 will cause the + * injector to apply before others, while 1100 will apply later. + * Injectors with the same order will still apply in order of their + * mixin's priority. + * + * @return the application order for this injector, uses DEFAULT (1000) if + * not specified + */ + public int order() default 1000; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Redirect.java b/src/main/java/org/spongepowered/asm/mixin/injection/Redirect.java index b72f5fdc3..c53ff6bea 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/Redirect.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Redirect.java @@ -438,5 +438,27 @@ * @return Constraints for this annotation */ public String constraints() default ""; + + /** + * By default almost all injectors for a target class apply their injections + * at the same time. In other words, if multiple mixins target the same + * class then injectors are applied in priority order (since the mixins + * themselves are merged in priority order, and injectors run in the order + * they were merged). Redirect injectors apply in a later pass. + * + *

The default order for redirect injectors is 10000, and all + * other injectors use 1000.

+ * + *

Specifying a value for order alters this default behaviour + * and causes the injector to inject either earlier or later than it + * normally would. For example specifying 9900 will cause the + * injector to apply before others, while 11000 will apply later. + * Injectors with the same order will still apply in order of their + * mixin's priority. + * + * @return the application order for this injector, uses default REDIRECT + * order (10000) if not specified + */ + public int order() default 10000; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/CallbackInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/CallbackInjectionInfo.java index b33a80132..67be0e66d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/CallbackInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/CallbackInjectionInfo.java @@ -41,7 +41,7 @@ * Information about a callback to inject, usually specified by {@link Inject} */ @AnnotationType(Inject.class) -@InjectorOrder(InjectorOrder.BUILTIN_CALLBACKS) +@InjectorOrder(InjectorOrder.DEFAULT) public class CallbackInjectionInfo extends InjectionInfo { protected CallbackInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java index 57e7c0a88..c0cfec509 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java @@ -123,88 +123,41 @@ public abstract class InjectionInfo extends SpecialMethodInfo implements ISliceC /** * Decoration for subclasses which specifies the order (phase) in which the * injector should be applied relative to other injectors. Built-in - * injectors all have predefined orders which allows custom injectors to - * specify their own order either as an explicit priority (eg. LATE) or - * relative to a known injector (eg. InjectorOrder.BUILTIN_REDIRECT - 100). + * injectors except for redirectors all run at DEFAULT unless specified in + * the injector annotation. * - *

Built-in injectors are grouped into three separate phases rather than - * split into individual phases, in order to retain the existing behaviour - * of mixin priority. Injectors in the same order are sorted by mixin - * priority and declaration order within the mixin as always.

+ *

Injectors in the same order are sorted by mixin priority and + * declaration order within the mixin as always.

*/ @Retention(RetentionPolicy.RUNTIME) @java.lang.annotation.Target(ElementType.TYPE) public @interface InjectorOrder { - - /** - * The lowest possible order, avoid using this unlesss you are insane - */ - public static final int FIRST = Integer.MIN_VALUE; - - /** - * A very early injector, will run before injectors such as ModifyArgs - * and ModifyVariable, as well as before EARLY injectors - */ - public static final int VERY_EARLY = 100; - - /** - * An early injector, will run before injectors suchs as ModifyArgs - * and ModifyVariable - */ - public static final int EARLY = 250; - - /** - * Built-in order for ModifyArg injectors - */ - public static final int BUILTIN_MODIFYARG = 500; /** - * Built-in order for ModifyArgs injectors + * An early injector, run before most injectors */ - public static final int BUILTIN_MODIFYARGS = 500; - - /** - * Built-in order for ModifyVariable injectors - */ - public static final int BUILTIN_MODIFYVARIABLE = 500; + public static final int EARLY = 0; /** - * Default order + * Default order, all injectors except redirect run here unless manually + * adjusted */ public static final int DEFAULT = 1000; - - /** - * Built-in order for Inject injectors - */ - public static final int BUILTIN_CALLBACKS = 1000; /** - * Late injector, runs after most injectors except VERY_LATE and - * Redirect injectors + * Late injector, runs after most injectors but before redirects */ public static final int LATE = 2000; - /** - * Built-in order for ModifyConstant injectors - */ - public static final int BUILTIN_MODIFYCONSTANT = 5000; - /** * Built-in order for Redirect injectors */ - public static final int BUILTIN_REDIRECT = 5000; - - /** - * Very late injector, runs after nearly all injectors including - * Redirect injectors - */ - public static final int VERY_LATE = 10000; + public static final int REDIRECT = 10000; /** - * The highest possible order, using this causes the universe to - * implode, bringing about the end of days. + * Injector which should run after redirect injector */ - public static final int LAST = Integer.MAX_VALUE; + public static final int AFTER_REDIRECT = 20000; /** * String prefix for conforming handler methods @@ -361,6 +314,12 @@ InjectionInfo create(MixinTargetContext mixin, MethodNode method, AnnotationNode */ private List messages; + /** + * Injector order, parsed from either the injector annotation or uses the + * default for this injection type + */ + private int order; + /** * ctor * @@ -400,6 +359,8 @@ protected void readAnnotation() { this.readInjectionPoints(); activity.next("Parse Requirements"); this.parseRequirements(); + activity.next("Parse Order"); + this.parseOrder(); activity.next("Parse Selectors"); this.parseSelectors(); activity.next("Find Targets"); @@ -448,6 +409,17 @@ protected void parseRequirements() { this.maxCallbackCount = Math.max(Math.max(this.requiredCallbackCount, 1), allow); } } + + protected void parseOrder() { + Integer userOrder = Annotations.getValue(this.annotation, "order"); + if (userOrder != null) { + this.order = userOrder.intValue(); + return; + } + + InjectorOrder injectorDefault = this.getClass().getAnnotation(InjectorOrder.class); + this.order = injectorDefault != null ? injectorDefault.value() : InjectorOrder.DEFAULT; + } protected void parseSelectors() { Set selectors = new LinkedHashSet(); @@ -484,8 +456,7 @@ public boolean isValid() { * Get the application order for this injector type */ public int getOrder() { - InjectorOrder injectorOrder = this.getClass().getAnnotation(InjectorOrder.class); - return injectorOrder != null ? injectorOrder.value() : InjectorOrder.DEFAULT; + return this.order; } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgInjectionInfo.java index 8ca2b6e8d..aa8f615bd 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgInjectionInfo.java @@ -40,7 +40,7 @@ */ @AnnotationType(ModifyArg.class) @HandlerPrefix("modify") -@InjectorOrder(InjectorOrder.BUILTIN_MODIFYARG) +@InjectorOrder(InjectorOrder.DEFAULT) public class ModifyArgInjectionInfo extends InjectionInfo { public ModifyArgInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgsInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgsInjectionInfo.java index 720a79a4d..a97b8fedb 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgsInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgsInjectionInfo.java @@ -39,7 +39,7 @@ */ @AnnotationType(ModifyArgs.class) @HandlerPrefix("args") -@InjectorOrder(InjectorOrder.BUILTIN_MODIFYARG) +@InjectorOrder(InjectorOrder.DEFAULT) public class ModifyArgsInjectionInfo extends InjectionInfo { public ModifyArgsInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java index b05146da2..4573bacd1 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyConstantInjectionInfo.java @@ -46,7 +46,7 @@ */ @AnnotationType(ModifyConstant.class) @HandlerPrefix("constant") -@InjectorOrder(InjectorOrder.BUILTIN_MODIFYCONSTANT) +@InjectorOrder(InjectorOrder.REDIRECT) public class ModifyConstantInjectionInfo extends InjectionInfo { private static final String CONSTANT_ANNOTATION_CLASS = Constant.class.getName().replace('.', '/'); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyVariableInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyVariableInjectionInfo.java index 4cf7e7fb6..2fa7c753a 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyVariableInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyVariableInjectionInfo.java @@ -40,7 +40,7 @@ */ @AnnotationType(ModifyVariable.class) @HandlerPrefix("localvar") -@InjectorOrder(InjectorOrder.BUILTIN_MODIFYVARIABLE) +@InjectorOrder(InjectorOrder.DEFAULT) public class ModifyVariableInjectionInfo extends InjectionInfo { public ModifyVariableInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/RedirectInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/RedirectInjectionInfo.java index d2db9b93e..0f048ff33 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/RedirectInjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/RedirectInjectionInfo.java @@ -39,7 +39,7 @@ */ @AnnotationType(Redirect.class) @HandlerPrefix("redirect") -@InjectorOrder(InjectorOrder.BUILTIN_REDIRECT) +@InjectorOrder(InjectorOrder.REDIRECT) public class RedirectInjectionInfo extends InjectionInfo { public RedirectInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { From ea733f96036f6960f0683486bc838ee89a545171 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 21 May 2024 22:51:14 +0100 Subject: [PATCH 61/84] Change: Add stack checking to old `Target#findInitNodeFor` overload and un-deprecate it. The only reason MixinExtras uses this method is to match Redirect's behaviour, so I would like it to be kept in line with that. Additionally, there is nothing wrong with not checking the desc as long as you've definitely found the right `NEW` insn, which the injection point handles itself. --- .../asm/mixin/injection/struct/Target.java | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index 1c13b32a6..020e800df 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -638,25 +638,9 @@ public Iterator iterator() { * * @param newNode NEW insn * @return INVOKESPECIAL opcode of ctor, or null if not found - * - * @deprecated Use {@link #findInitNodeFor(TypeInsnNode, String)} instead: - * this method only matches the first matching <init> - * after the specified NEW, it also does not filter based on - * the descriptor passed into BeforeNew. */ - @Deprecated public MethodInsnNode findInitNodeFor(TypeInsnNode newNode) { - int start = this.indexOf(newNode); - for (Iterator iter = this.insns.iterator(start); iter.hasNext();) { - AbstractInsnNode insn = iter.next(); - if (insn instanceof MethodInsnNode && insn.getOpcode() == Opcodes.INVOKESPECIAL) { - MethodInsnNode methodNode = (MethodInsnNode)insn; - if (Constants.CTOR.equals(methodNode.name) && methodNode.owner.equals(newNode.desc)) { - return methodNode; - } - } - } - return null; + return this.findInitNodeFor(newNode, null); } /** From 18f632ca352bdc2c04df502a40ec21dacdc1a7ce Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sat, 1 Jun 2024 18:41:38 +0100 Subject: [PATCH 62/84] Build: Bump version. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9fa9aa929..5978aff3e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ packaging=jar description=Mixin (Fabric fork) url=https://fabricmc.net organization=FabricMC -buildVersion=0.13.4 +buildVersion=0.14.0 upstreamMixinVersion=0.8.6 buildType=RELEASE asmVersion=9.6 From 8d413b8e43f47fff9ca866084e688199249920f8 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sat, 1 Jun 2024 18:44:47 +0100 Subject: [PATCH 63/84] Compat: Gate `NEW` descriptor filtering. --- src/main/java/org/spongepowered/asm/mixin/FabricUtil.java | 3 ++- .../spongepowered/asm/mixin/injection/points/BeforeNew.java | 2 +- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java b/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java index b11673bf5..d4ff75303 100644 --- a/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java +++ b/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java @@ -35,7 +35,8 @@ public final class FabricUtil { // fabric mixin version compatibility boundaries, (major * 1000 + minor) * 1000 + patch public static final int COMPATIBILITY_0_9_2 = 9002; // 0.9.2+mixin.0.8.2 incompatible local variable handling public static final int COMPATIBILITY_0_10_0 = 10000; // 0.10.0+mixin.0.8.4 - public static final int COMPATIBILITY_LATEST = COMPATIBILITY_0_10_0; + public static final int COMPATIBILITY_0_14_0 = 14000; // 0.14.0+mixin.0.8.6 + public static final int COMPATIBILITY_LATEST = COMPATIBILITY_0_14_0; public static String getModId(IMixinConfig config) { return getModId(config, "(unknown)"); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeNew.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeNew.java index 845d6e781..14850b2d3 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeNew.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeNew.java @@ -118,7 +118,7 @@ public BeforeNew(InjectionPointData data) { } ITargetSelectorConstructor targetSelector = (ITargetSelectorConstructor)member; this.target = targetSelector.toCtorType(); - this.desc = targetSelector.toCtorDesc(); + this.desc = org.spongepowered.asm.mixin.FabricUtil.getCompatibility(data.getContext()) >= org.spongepowered.asm.mixin.FabricUtil.COMPATIBILITY_0_14_0 ? targetSelector.toCtorDesc() : null; } /** From 35c079a988134f2fe8152b9008dcbfbadef11a31 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 2 Jun 2024 12:56:32 +0100 Subject: [PATCH 64/84] Fix: Resolve occasional NPE in Locals calculations. --- src/main/java/org/spongepowered/asm/util/Locals.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/util/Locals.java b/src/main/java/org/spongepowered/asm/util/Locals.java index a7f01b209..4f62ce352 100644 --- a/src/main/java/org/spongepowered/asm/util/Locals.java +++ b/src/main/java/org/spongepowered/asm/util/Locals.java @@ -542,8 +542,9 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me VarInsnNode varInsn = (VarInsnNode)insn; boolean isLoad = insn.getOpcode() >= Opcodes.ILOAD && insn.getOpcode() <= Opcodes.SALOAD; if (isLoad) { - frame[varInsn.var] = Locals.getLocalVariableAt(classNode, method, insn, varInsn.var); - int varSize = frame[varInsn.var].desc != null ? Type.getType(frame[varInsn.var].desc).getSize() : 1; + LocalVariableNode toLoad = Locals.getLocalVariableAt(classNode, method, insn, varInsn.var); + frame[varInsn.var] = toLoad; + int varSize = toLoad != null && toLoad.desc != null ? Type.getType(frame[varInsn.var].desc).getSize() : 1; knownFrameSize = Math.max(knownFrameSize, varInsn.var + varSize); if (settings.hasFlags(Settings.RESURRECT_EXPOSED_ON_LOAD)) { Locals.resurrect(frame, knownFrameSize, settings); From c0933111843364e97b8ffbc35935518d287fe1e9 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sun, 2 Jun 2024 12:58:25 +0100 Subject: [PATCH 65/84] Fix: Overhaul MixinVerifier supertype logic. It previously didn't handle array types correctly among some other issues. --- .../asm/util/asm/MixinVerifier.java | 176 +++++++++++++----- 1 file changed, 127 insertions(+), 49 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/util/asm/MixinVerifier.java b/src/main/java/org/spongepowered/asm/util/asm/MixinVerifier.java index 8519887b7..cdbd02ef4 100644 --- a/src/main/java/org/spongepowered/asm/util/asm/MixinVerifier.java +++ b/src/main/java/org/spongepowered/asm/util/asm/MixinVerifier.java @@ -24,83 +24,161 @@ */ package org.spongepowered.asm.util.asm; -import java.util.List; - import org.objectweb.asm.Type; +import org.objectweb.asm.tree.analysis.BasicValue; import org.objectweb.asm.tree.analysis.SimpleVerifier; import org.spongepowered.asm.mixin.transformer.ClassInfo; -import org.spongepowered.asm.mixin.transformer.ClassInfo.TypeLookup; + +import java.util.List; /** * Verifier which handles class info lookups via {@link ClassInfo} */ public class MixinVerifier extends SimpleVerifier { - - private Type currentClass; - private Type currentSuperClass; - private List currentClassInterfaces; - private boolean isInterface; + private static final Type OBJECT_TYPE = Type.getType(Object.class); public MixinVerifier(int api, Type currentClass, Type currentSuperClass, List currentClassInterfaces, boolean isInterface) { super(api, currentClass, currentSuperClass, currentClassInterfaces, isInterface); - this.currentClass = currentClass; - this.currentSuperClass = currentSuperClass; - this.currentClassInterfaces = currentClassInterfaces; - this.isInterface = isInterface; } @Override - protected boolean isInterface(final Type type) { - if (this.currentClass != null && type.equals(this.currentClass)) { - return this.isInterface; + protected boolean isInterface(Type type) { + if (type.getSort() != Type.OBJECT) { + return false; } - return ClassInfo.forType(type, TypeLookup.ELEMENT_TYPE).isInterface(); + return ClassInfo.forType(type, ClassInfo.TypeLookup.DECLARED_TYPE).isInterface(); } @Override - protected Type getSuperClass(final Type type) { - if (this.currentClass != null && type.equals(this.currentClass)) { - return this.currentSuperClass; + protected boolean isSubTypeOf(BasicValue value, BasicValue expected) { + Type expectedType = expected.getType(); + Type type = value.getType(); + switch (expectedType.getSort()) { + case Type.INT: + case Type.FLOAT: + case Type.LONG: + case Type.DOUBLE: + return type.equals(expectedType); + case Type.ARRAY: + case Type.OBJECT: + if (type.equals(NULL_TYPE)) { + return true; + } else if (type.getSort() == Type.OBJECT || type.getSort() == Type.ARRAY) { + if (isAssignableFrom(expectedType, type)) { + return true; + } + if (expectedType.getSort() == Type.ARRAY) { + if (type.getSort() != Type.ARRAY) { + return false; + } + int dim = expectedType.getDimensions(); + expectedType = expectedType.getElementType(); + if (dim > type.getDimensions() || expectedType.getSort() != Type.OBJECT) { + return false; + } + type = Type.getType(type.getDescriptor().substring(dim)); + } + if (isInterface(expectedType)) { + // The merge of class or interface types can only yield class types (because it is not + // possible in general to find an unambiguous common super interface, due to multiple + // inheritance). Because of this limitation, we need to relax the subtyping check here + // if 'value' is an interface. + return type.getSort() >= Type.ARRAY; + } else { + return false; + } + } else { + return false; + } + default: + throw new AssertionError(); } - ClassInfo c = ClassInfo.forType(type, TypeLookup.ELEMENT_TYPE).getSuperClass(); - return c == null ? null : Type.getType("L" + c.getName() + ";"); } @Override - protected boolean isAssignableFrom(final Type type, final Type other) { - if (type.equals(other)) { - return true; + protected boolean isAssignableFrom(Type type1, Type type2) { + return type1.equals(getCommonSupertype(type1, type2)); + } + + @Override + public BasicValue merge(BasicValue value1, BasicValue value2) { + if (value1.equals(value2)) { + return value1; } - if (this.currentClass != null && type.equals(this.currentClass)) { - if (this.getSuperClass(other) == null) { - return false; - } - if (this.isInterface) { - return other.getSort() == Type.OBJECT || other.getSort() == Type.ARRAY; - } - return this.isAssignableFrom(type, this.getSuperClass(other)); + if (value1.equals(BasicValue.UNINITIALIZED_VALUE) || value2.equals(BasicValue.UNINITIALIZED_VALUE)) { + return BasicValue.UNINITIALIZED_VALUE; } - if (this.currentClass != null && other.equals(this.currentClass)) { - if (this.isAssignableFrom(type, this.currentSuperClass)) { - return true; - } - if (this.currentClassInterfaces != null) { - for (int i = 0; i < this.currentClassInterfaces.size(); ++i) { - Type v = this.currentClassInterfaces.get(i); - if (this.isAssignableFrom(type, v)) { - return true; - } + Type supertype = getCommonSupertype(value1.getType(), value2.getType()); + return newValue(supertype); + } + + private static Type getCommonSupertype(Type type1, Type type2) { + if (type1.equals(type2) || type2.equals(NULL_TYPE)) { + return type1; + } + if (type1.equals(NULL_TYPE)) { + return type2; + } + if (type1.getSort() < Type.ARRAY || type2.getSort() < Type.ARRAY) { + // We know they're not the same, so they must be incompatible. + return null; + } + if (type1.getSort() == Type.ARRAY && type2.getSort() == Type.ARRAY) { + int dim1 = type1.getDimensions(); + Type elem1 = type1.getElementType(); + int dim2 = type2.getDimensions(); + Type elem2 = type2.getElementType(); + if (dim1 == dim2) { + Type commonSupertype; + if (elem1.equals(elem2)) { + commonSupertype = elem1; + } else if (elem1.getSort() == Type.OBJECT && elem2.getSort() == Type.OBJECT) { + commonSupertype = getCommonSupertype(elem1, elem2); + } else { + return arrayType(OBJECT_TYPE, dim1 - 1); } + return arrayType(commonSupertype, dim1); } - return false; + Type smaller; + int shared; + if (dim1 < dim2) { + smaller = elem1; + shared = dim1 - 1; + } else { + smaller = elem2; + shared = dim2 - 1; + } + if (smaller.getSort() == Type.OBJECT) { + shared++; + } + return arrayType(OBJECT_TYPE, shared); } - ClassInfo typeInfo = ClassInfo.forType(type, TypeLookup.ELEMENT_TYPE); - if (typeInfo == null) { - return false; + if (type1.getSort() == Type.ARRAY && type2.getSort() == Type.OBJECT || type2.getSort() == Type.ARRAY && type1.getSort() == Type.OBJECT) { + return OBJECT_TYPE; } - if (typeInfo.isInterface()) { - typeInfo = ClassInfo.forName("java/lang/Object"); + return ClassInfo.getCommonSuperClass(type1, type2).getType(); + } + + private static Type arrayType(final Type type, final int dimensions) { + if (dimensions == 0) { + return type; + } else { + StringBuilder descriptor = new StringBuilder(); + for (int i = 0; i < dimensions; ++i) { + descriptor.append('['); + } + descriptor.append(type.getDescriptor()); + return Type.getType(descriptor.toString()); } - return ClassInfo.forType(other, TypeLookup.ELEMENT_TYPE).hasSuperClass(typeInfo); + } + + @Override + protected Class getClass(Type type) { + throw new UnsupportedOperationException( + String.format( + "Live-loading of %s attempted by MixinVerifier! This should never happen!", + type.getClassName() + ) + ); } } From 2d14c78a1065601e66ea0f93612f8704a3e0f0aa Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Fri, 7 Jun 2024 21:30:03 +0100 Subject: [PATCH 66/84] Fix bug with option flags from previous commit --- src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java index 1fe135eb9..7974d551b 100644 --- a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java +++ b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java @@ -568,7 +568,7 @@ private Option(Option parent, Inherit inheritance, boolean hidden, String proper } private Option(Option parent, Inherit inheritance, String property, boolean isFlag, String defaultStringValue) { - this(parent, inheritance, false, property, false, defaultStringValue); + this(parent, inheritance, false, property, isFlag, defaultStringValue); } private Option(Option parent, Inherit inheritance, boolean hidden, String property, boolean isFlag, String defaultStringValue) { From a362aad99be308fd541012d0917e1704013aa96a Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Fri, 7 Jun 2024 22:58:07 +0100 Subject: [PATCH 67/84] Fix AP file writer issue with non-file outputs, fixed #622 --- .../tools/obfuscation/ReferenceManager.java | 32 ++++++++++++++++--- 1 file changed, 28 insertions(+), 4 deletions(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java b/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java index 850b368f3..f6a589cb7 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java @@ -27,9 +27,14 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.io.Writer; +import java.net.URI; import java.util.List; +import javax.annotation.processing.Filer; +import javax.annotation.processing.FilerException; import javax.tools.FileObject; +import javax.tools.JavaFileManager.Location; import javax.tools.StandardLocation; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorRemappable; @@ -129,7 +134,9 @@ public void write() { try { writer = this.newWriter(this.outRefMapFileName, "refmap"); - this.refMapper.write(writer); + if (writer != null) { + this.refMapper.write(writer); + } } catch (IOException ex) { ex.printStackTrace(); } finally { @@ -154,9 +161,26 @@ private PrintWriter newWriter(String fileName, String description) throws IOExce return new PrintWriter(outFile); } - FileObject outResource = this.ap.getProcessingEnvironment().getFiler().createResource(StandardLocation.CLASS_OUTPUT, "", fileName); - this.ap.printMessage(MessageType.INFO, "Writing " + description + " to " + new File(outResource.toUri()).getAbsolutePath()); - return new PrintWriter(outResource.openWriter()); + try { + Filer filer = this.ap.getProcessingEnvironment().getFiler(); + FileObject outResource = null; + try { + outResource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", fileName); + } catch (Exception ex) { + // fileName is not a valid relative path? + outResource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", new File(fileName).getName()); + } + + URI resourceUri = outResource.toUri(); + String absolutePath = "file".equals(resourceUri.getScheme()) ? new File(resourceUri).getAbsolutePath() : resourceUri.toString(); + PrintWriter writer = new PrintWriter(outResource.openWriter()); + this.ap.printMessage(MessageType.INFO, "Writing " + description + " to (" + resourceUri.getScheme() + ") " + absolutePath); + return writer; + } catch (Exception ex) { + this.ap.printMessage(MessageType.ERROR, "Cannot write " + description + " to (" + fileName + "): " + ex.getClass().getName() + + ": " + ex.getMessage()); + return null; + } } /* (non-Javadoc) From a8254c74830ac82c9ad945b762ad2b36911e3a18 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Sat, 8 Jun 2024 15:17:38 +0100 Subject: [PATCH 68/84] Fix: Properly extend the stack in `ModifyArgInjector`. (#144) The old logic doesn't really make any sense and the calculated extension usually ended up being negative. Instead, we just make room for our possible receiver and all of our args. --- .../asm/mixin/injection/invoke/ModifyArgInjector.java | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java index dd1639e1f..3e147524e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java @@ -123,7 +123,12 @@ protected void injectAtInvoke(Target target, InjectionNode node) { } target.insns.insertBefore(methodNode, insns); - target.extendStack().set(2 - (extraLocals.get() - 1)).apply(); + Extension extraStack = target.extendStack(); + if (!isStatic) { + extraStack.add(); + } + extraStack.add(methodArgs); + extraStack.apply(); extraLocals.apply(); } From d0a6ebdf9eb24c7ef7a0359eaa7dc7b8bc90548f Mon Sep 17 00:00:00 2001 From: modmuss Date: Wed, 19 Jun 2024 23:18:56 +0100 Subject: [PATCH 69/84] Update Gradle (#148) --- .github/workflows/build.yml | 12 +- .github/workflows/release.yml | 8 +- build.gradle | 177 +++++++------- gradle/wrapper/gradle-wrapper.jar | Bin 59536 -> 43453 bytes gradle/wrapper/gradle-wrapper.properties | 4 +- gradlew | 282 ++++++++++++++--------- gradlew.bat | 35 +-- 7 files changed, 293 insertions(+), 225 deletions(-) diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 0d36769d9..cbb6cfd46 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -5,12 +5,12 @@ jobs: strategy: fail-fast: false matrix: - java: [8-jdk, 11-jdk, 16-jdk, 17-jdk] - runs-on: ubuntu-20.04 + java: [17-jdk, 21-jdk] + runs-on: ubuntu-24.04 container: - image: openjdk:${{ matrix.java }} + image: eclipse-temurin:${{ matrix.java }} options: --user root steps: - - uses: actions/checkout@v1 - - uses: gradle/wrapper-validation-action@v1 - - run: ./gradlew build publishToMavenLocal --stacktrace \ No newline at end of file + - uses: actions/checkout@v4 + - uses: gradle/wrapper-validation-action@v2 + - run: ./gradlew build publishToMavenLocal --stacktrace --warning-mode fail \ No newline at end of file diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 319e54748..2dcf2bd66 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -2,13 +2,13 @@ name: Release on: [workflow_dispatch] # Manual trigger jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-24.04 container: - image: openjdk:17-jdk + image: eclipse-temurin:21-jdk options: --user root steps: - - uses: actions/checkout@v1 - - uses: gradle/wrapper-validation-action@v1 + - uses: actions/checkout@v4 + - uses: gradle/wrapper-validation-action@v2 - run: ./gradlew checkVersion build publish --stacktrace env: MAVEN_URL: ${{ secrets.MAVEN_URL }} diff --git a/build.gradle b/build.gradle index de123bf5a..ba391cc02 100644 --- a/build.gradle +++ b/build.gradle @@ -6,13 +6,13 @@ buildscript { } dependencies { classpath 'gradle.plugin.com.hierynomus.gradle.plugins:license-gradle-plugin:0.16.1' - classpath 'gradle.plugin.com.github.jengelman.gradle.plugins:shadow:7.0.0' - classpath 'com.guardsquare:proguard-gradle:' + (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11) ? '7.2.0' : '7.1.0') + classpath 'com.guardsquare:proguard-gradle:' + (JavaVersion.current().isCompatibleWith(JavaVersion.VERSION_11) ? '7.5.0' : '7.1.0') } } plugins { - id "me.modmuss50.remotesign" version "0.4.0" + id "me.modmuss50.remotesign" version "0.4.0" + id "io.github.goooler.shadow" version "8.1.7" } import com.github.jengelman.gradle.plugins.shadow.tasks.ShadowJar @@ -24,7 +24,6 @@ apply plugin: 'checkstyle' apply plugin: 'maven-publish' apply plugin: 'eclipse' apply plugin: 'idea' -apply plugin: 'com.github.johnrengelman.shadow' apply plugin: 'potemkin-modules' // Default tasks @@ -32,9 +31,11 @@ defaultTasks 'licenseFormat', 'check', 'build' // Basic project information group = 'net.fabricmc' -archivesBaseName = 'sponge-mixin' version = buildVersion + "+mixin." + upstreamMixinVersion +base { + archivesName = 'sponge-mixin' +} def ENV = System.getenv() if (!ENV.CI) { @@ -50,12 +51,13 @@ ext.packaging = 'jar' ext.asmVersion = project.hasProperty("asmVersion") ? asmVersion : '9.0' ext.legacyForgeAsmVersion = project.hasProperty("legacyForgeAsmVersion") ? asmVersion : '5.0.3' -// Minimum version of Java required -sourceCompatibility = '1.8' -targetCompatibility = '1.8' - java { modularity.inferModulePath = false + disableAutoTargetJvm() + + // Minimum version of Java required + sourceCompatibility = '1.8' + targetCompatibility = '1.8' } // Project repositories @@ -76,26 +78,6 @@ repositories { } } -configurations { - stagingJar - - exampleImplementation .extendsFrom implementation - fernflowerImplementation .extendsFrom implementation - launchwrapperImplementation .extendsFrom implementation - agentImplementation .extendsFrom implementation - modlauncherImplementation .extendsFrom implementation - modlauncher9Implementation .extendsFrom modlauncherImplementation - modularityImplementation .extendsFrom modlauncher9Implementation - modularityCompileOnly .extendsFrom compileOnly - - proguard { - extendsFrom fernflowerImplementation - extendsFrom launchwrapperImplementation - extendsFrom modlauncherImplementation - extendsFrom compileClasspath - } -} - sourceSets { legacy { ext.languageVersion = 8 @@ -164,6 +146,26 @@ sourceSets { modularityDummy {} } +configurations { + stagingJar + + exampleImplementation .extendsFrom implementation + fernflowerImplementation .extendsFrom implementation + launchwrapperImplementation .extendsFrom implementation + agentImplementation .extendsFrom implementation + modlauncherImplementation .extendsFrom implementation + modlauncher9Implementation .extendsFrom modlauncherImplementation + modularityImplementation .extendsFrom modlauncher9Implementation + modularityCompileOnly .extendsFrom compileOnly + + proguard { + extendsFrom fernflowerImplementation + extendsFrom launchwrapperImplementation + extendsFrom modlauncherImplementation + extendsFrom compileClasspath + } +} + // Because Mixin aims to support a variety of environments, we have to be able to run with older versions of GSON and Guava that lack official module // names. This means the same library may appear with multiple module names. We want to be able to link our module with either of these two at // runtime, without having to have two versions of the library on our compile-time module path. To do this, we generate empty "potemkin" jars with @@ -248,7 +250,6 @@ javadoc { options { docTitle 'Welcome to the Mixin Javadoc' overview 'docs/javadoc/overview.html' - stylesheetFile file('docs/javadoc/mixin.css') addBooleanOption '-allow-script-in-comments', true } doLast { @@ -334,10 +335,8 @@ tasks.withType(JavaCompile) { def modularityInputs = objects.fileCollection() project.sourceSets.each { set -> { - if (set.ext.has("languageVersion")) { - project.tasks[set.compileJavaTaskName].javaCompiler = javaToolchains.compilerFor { - languageVersion = JavaLanguageVersion.of(set.ext.languageVersion) - } + if (set.ext.has("languageVersion") && JavaVersion.current().isJava9Compatible()) { + project.tasks[set.compileJavaTaskName].options.release = set.ext.languageVersion } if (set.ext.has("compatibility")) { project.tasks[set.compileJavaTaskName].sourceCompatibility = set.ext.compatibility @@ -440,7 +439,7 @@ task sourceJar(type: Jar) { } task javadocJar(type: Jar, dependsOn: javadoc) { - from javadoc.destinationDir + from javadoc.destinationDir archiveClassifier = "javadoc" } @@ -465,7 +464,7 @@ publishing { publications { developer(MavenPublication) { publication -> groupId project.group - artifactId project.archivesBaseName + artifactId project.base.archivesName.get() version project.version artifact sourceJar @@ -498,45 +497,45 @@ publishing { } } - pom { - name = "Fabric Mixin" - description = 'Fabric Mixin is a trait/mixin and bytecode weaving framework for Java using ASM.' - url = 'https://github.com/FabricMC/Mixin' - - scm { - connection = "scm:git:https://github.com/FabricMC/Mixin.git" - developerConnection = "scm:git:git@github.com:FabricMC/Mixin.git" - url = "https://github.com/FabricMC/Mixin" - } - - issueManagement { - system = "GitHub" - url = "https://github.com/FabricMC/Mixin/issues" - } - - licenses { - license { - name = 'The MIT License' - url = 'https://raw.githubusercontent.com/FabricMC/Mixin/main/LICENSE.txt' - } - } - - developers { - // Et. al. that arent in the fabric org on maven central - - developer { - id = "modmuss50" - name = "modmuss50" - email = "modmuss50@fabricmc.net" - } - - developer { - id = "sfPlayer" - name = "Player" - email = "player@fabricmc.net" - } - } - } + pom { + name = "Fabric Mixin" + description = 'Fabric Mixin is a trait/mixin and bytecode weaving framework for Java using ASM.' + url = 'https://github.com/FabricMC/Mixin' + + scm { + connection = "scm:git:https://github.com/FabricMC/Mixin.git" + developerConnection = "scm:git:git@github.com:FabricMC/Mixin.git" + url = "https://github.com/FabricMC/Mixin" + } + + issueManagement { + system = "GitHub" + url = "https://github.com/FabricMC/Mixin/issues" + } + + licenses { + license { + name = 'The MIT License' + url = 'https://raw.githubusercontent.com/FabricMC/Mixin/main/LICENSE.txt' + } + } + + developers { + // Et. al. that arent in the fabric org on maven central + + developer { + id = "modmuss50" + name = "modmuss50" + email = "modmuss50@fabricmc.net" + } + + developer { + id = "sfPlayer" + name = "Player" + email = "player@fabricmc.net" + } + } + } } } @@ -551,24 +550,24 @@ publishing { } } - if (ENV.MAVEN_CENTRAL_URL) { - repositories.maven { - name "central" - url ENV.MAVEN_CENTRAL_URL - credentials { - username ENV.MAVEN_CENTRAL_USERNAME - password ENV.MAVEN_CENTRAL_PASSWORD - } - } - } + if (ENV.MAVEN_CENTRAL_URL) { + repositories.maven { + name "central" + url ENV.MAVEN_CENTRAL_URL + credentials { + username ENV.MAVEN_CENTRAL_USERNAME + password ENV.MAVEN_CENTRAL_PASSWORD + } + } + } } } remoteSign { - requestUrl = ENV.SIGNING_SERVER - pgpAuthKey = ENV.SIGNING_PGP_KEY - useDummyForTesting = ENV.SIGNING_SERVER == null - sign publishing.publications.developer + requestUrl = ENV.SIGNING_SERVER + pgpAuthKey = ENV.SIGNING_PGP_KEY + useDummyForTesting = ENV.SIGNING_SERVER == null + sign publishing.publications.developer } // A task to ensure that the version being released has not already been released. diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar index 7454180f2ae8848c63b8b4dea2cb829da983f2fa..e6441136f3d4ba8a0da8d277868979cfbc8ad796 100644 GIT binary patch literal 43453 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 yu?e%!_ptzGB|8GrCfu%p?(_ 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
NnX)EH+Nua)3Y(c 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 QuEJmfE|#`Dqx|px0L@K;Y5)KL diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 2e6e5897b..a4413138c 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,7 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.8-bin.zip +networkTimeout=10000 +validateDistributionUrl=true zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew index 744e882ed..b740cf133 100755 --- a/gradlew +++ b/gradlew @@ -1,7 +1,7 @@ -#!/usr/bin/env sh +#!/bin/sh # -# Copyright 2015 the original author or authors. +# Copyright © 2015-2021 the original authors. # # Licensed under the Apache License, Version 2.0 (the "License"); # you may not use this file except in compliance with the License. @@ -17,67 +17,99 @@ # ############################################################################## -## -## Gradle start up script for UN*X -## +# +# Gradle start up script for POSIX generated by Gradle. +# +# Important for running: +# +# (1) You need a POSIX-compliant shell to run this script. If your /bin/sh is +# noncompliant, but you have some other compliant shell such as ksh or +# bash, then to run this script, type that shell name before the whole +# command line, like: +# +# ksh Gradle +# +# Busybox and similar reduced shells will NOT work, because this script +# requires all of these POSIX shell features: +# * functions; +# * expansions «$var», «${var}», «${var:-default}», «${var+SET}», +# «${var#prefix}», «${var%suffix}», and «$( cmd )»; +# * compound commands having a testable exit status, especially «case»; +# * various built-in commands including «command», «set», and «ulimit». +# +# Important for patching: +# +# (2) This script targets any POSIX shell, so it avoids extensions provided +# by Bash, Ksh, etc; in particular arrays are avoided. +# +# The "traditional" practice of packing multiple parameters into a +# space-separated string is a well documented source of bugs and security +# problems, so this is (mostly) avoided, by progressively accumulating +# options in "$@", and eventually passing that to Java. +# +# Where the inherited environment variables (DEFAULT_JVM_OPTS, JAVA_OPTS, +# and GRADLE_OPTS) rely on word-splitting, this is performed explicitly; +# see the in-line comments for details. +# +# There are tweaks for specific operating systems such as AIX, CygWin, +# Darwin, MinGW, and NonStop. +# +# (3) This script is generated from the Groovy template +# https://github.com/gradle/gradle/blob/HEAD/platforms/jvm/plugins-application/src/main/resources/org/gradle/api/internal/plugins/unixStartScript.txt +# within the Gradle project. +# +# You can find Gradle at https://github.com/gradle/gradle/. +# ############################################################################## # Attempt to set APP_HOME + # Resolve links: $0 may be a link -PRG="$0" -# Need this for relative symlinks. -while [ -h "$PRG" ] ; do - ls=`ls -ld "$PRG"` - link=`expr "$ls" : '.*-> \(.*\)$'` - if expr "$link" : '/.*' > /dev/null; then - PRG="$link" - else - PRG=`dirname "$PRG"`"/$link" - fi +app_path=$0 + +# Need this for daisy-chained symlinks. +while + APP_HOME=${app_path%"${app_path##*/}"} # leaves a trailing /; empty if no leading path + [ -h "$app_path" ] +do + ls=$( ls -ld "$app_path" ) + link=${ls#*' -> '} + case $link in #( + /*) app_path=$link ;; #( + *) app_path=$APP_HOME$link ;; + esac done -SAVED="`pwd`" -cd "`dirname \"$PRG\"`/" >/dev/null -APP_HOME="`pwd -P`" -cd "$SAVED" >/dev/null -APP_NAME="Gradle" -APP_BASE_NAME=`basename "$0"` - -# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' +# This is normally unused +# shellcheck disable=SC2034 +APP_BASE_NAME=${0##*/} +# Discard cd standard output in case $CDPATH is set (https://github.com/gradle/gradle/issues/25036) +APP_HOME=$( cd "${APP_HOME:-./}" > /dev/null && pwd -P ) || exit # Use the maximum available, or set MAX_FD != -1 to use that value. -MAX_FD="maximum" +MAX_FD=maximum warn () { echo "$*" -} +} >&2 die () { echo echo "$*" echo exit 1 -} +} >&2 # OS specific support (must be 'true' or 'false'). cygwin=false msys=false darwin=false nonstop=false -case "`uname`" in - CYGWIN* ) - cygwin=true - ;; - Darwin* ) - darwin=true - ;; - MSYS* | MINGW* ) - msys=true - ;; - NONSTOP* ) - nonstop=true - ;; +case "$( uname )" in #( + CYGWIN* ) cygwin=true ;; #( + Darwin* ) darwin=true ;; #( + MSYS* | MINGW* ) msys=true ;; #( + NONSTOP* ) nonstop=true ;; esac CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar @@ -87,9 +119,9 @@ CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar if [ -n "$JAVA_HOME" ] ; then if [ -x "$JAVA_HOME/jre/sh/java" ] ; then # IBM's JDK on AIX uses strange locations for the executables - JAVACMD="$JAVA_HOME/jre/sh/java" + JAVACMD=$JAVA_HOME/jre/sh/java else - JAVACMD="$JAVA_HOME/bin/java" + JAVACMD=$JAVA_HOME/bin/java fi if [ ! -x "$JAVACMD" ] ; then die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME @@ -98,88 +130,120 @@ Please set the JAVA_HOME variable in your environment to match the location of your Java installation." fi else - JAVACMD="java" - which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + JAVACMD=java + if ! command -v java >/dev/null 2>&1 + then + die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. Please set the JAVA_HOME variable in your environment to match the location of your Java installation." + fi fi # Increase the maximum file descriptors if we can. -if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then - MAX_FD_LIMIT=`ulimit -H -n` - if [ $? -eq 0 ] ; then - if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then - MAX_FD="$MAX_FD_LIMIT" - fi - ulimit -n $MAX_FD - if [ $? -ne 0 ] ; then - warn "Could not set maximum file descriptor limit: $MAX_FD" - fi - else - warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" - fi +if ! "$cygwin" && ! "$darwin" && ! "$nonstop" ; then + case $MAX_FD in #( + max*) + # In POSIX sh, ulimit -H is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + MAX_FD=$( ulimit -H -n ) || + warn "Could not query maximum file descriptor limit" + esac + case $MAX_FD in #( + '' | soft) :;; #( + *) + # In POSIX sh, ulimit -n is undefined. That's why the result is checked to see if it worked. + # shellcheck disable=SC2039,SC3045 + ulimit -n "$MAX_FD" || + warn "Could not set maximum file descriptor limit to $MAX_FD" + esac fi -# For Darwin, add options to specify how the application appears in the dock -if $darwin; then - GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" -fi +# Collect all arguments for the java command, stacking in reverse order: +# * args from the command line +# * the main class name +# * -classpath +# * -D...appname settings +# * --module-path (only if needed) +# * DEFAULT_JVM_OPTS, JAVA_OPTS, and GRADLE_OPTS environment variables. # For Cygwin or MSYS, switch paths to Windows format before running java -if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then - APP_HOME=`cygpath --path --mixed "$APP_HOME"` - CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` - - JAVACMD=`cygpath --unix "$JAVACMD"` - - # We build the pattern for arguments to be converted via cygpath - ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` - SEP="" - for dir in $ROOTDIRSRAW ; do - ROOTDIRS="$ROOTDIRS$SEP$dir" - SEP="|" - done - OURCYGPATTERN="(^($ROOTDIRS))" - # Add a user-defined pattern to the cygpath arguments - if [ "$GRADLE_CYGPATTERN" != "" ] ; then - OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" - fi +if "$cygwin" || "$msys" ; then + APP_HOME=$( cygpath --path --mixed "$APP_HOME" ) + CLASSPATH=$( cygpath --path --mixed "$CLASSPATH" ) + + JAVACMD=$( cygpath --unix "$JAVACMD" ) + # Now convert the arguments - kludge to limit ourselves to /bin/sh - i=0 - for arg in "$@" ; do - CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` - CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option - - if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition - eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` - else - eval `echo args$i`="\"$arg\"" + for arg do + if + case $arg in #( + -*) false ;; # don't mess with options #( + /?*) t=${arg#/} t=/${t%%/*} # looks like a POSIX filepath + [ -e "$t" ] ;; #( + *) false ;; + esac + then + arg=$( cygpath --path --ignore --mixed "$arg" ) fi - i=`expr $i + 1` + # Roll the args list around exactly as many times as the number of + # args, so each arg winds up back in the position where it started, but + # possibly modified. + # + # NB: a `for` loop captures its iteration list before it begins, so + # changing the positional parameters here affects neither the number of + # iterations, nor the values presented in `arg`. + shift # remove old arg + set -- "$@" "$arg" # push replacement arg done - case $i in - 0) set -- ;; - 1) set -- "$args0" ;; - 2) set -- "$args0" "$args1" ;; - 3) set -- "$args0" "$args1" "$args2" ;; - 4) set -- "$args0" "$args1" "$args2" "$args3" ;; - 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; - 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; - 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; - 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; - 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; - esac fi -# Escape application args -save () { - for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done - echo " " -} -APP_ARGS=`save "$@"` -# Collect all arguments for the java command, following the shell quoting and substitution rules -eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Collect all arguments for the java command: +# * DEFAULT_JVM_OPTS, JAVA_OPTS, JAVA_OPTS, and optsEnvironmentVar are not allowed to contain shell fragments, +# and any embedded shellness will be escaped. +# * For example: A user cannot expect ${Hostname} to be expanded, as it is an environment variable and will be +# treated as '${Hostname}' itself on the command line. + +set -- \ + "-Dorg.gradle.appname=$APP_BASE_NAME" \ + -classpath "$CLASSPATH" \ + org.gradle.wrapper.GradleWrapperMain \ + "$@" + +# Stop when "xargs" is not available. +if ! command -v xargs >/dev/null 2>&1 +then + die "xargs is not available" +fi + +# Use "xargs" to parse quoted args. +# +# With -n1 it outputs one arg per line, with the quotes and backslashes removed. +# +# In Bash we could simply go: +# +# readarray ARGS < <( xargs -n1 <<<"$var" ) && +# set -- "${ARGS[@]}" "$@" +# +# but POSIX shell has neither arrays nor command substitution, so instead we +# post-process each arg (as a line of input to sed) to backslash-escape any +# character that might be a shell metacharacter, then use eval to reverse +# that process (while maintaining the separation between arguments), and wrap +# the whole thing up as a single "set" statement. +# +# This will of course break if any of these variables contains a newline or +# an unmatched quote. +# + +eval "set -- $( + printf '%s\n' "$DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS" | + xargs -n1 | + sed ' s~[^-[:alnum:]+,./:=@_]~\\&~g; ' | + tr '\n' ' ' + )" '"$@"' exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat index 107acd32c..25da30dbd 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -14,7 +14,7 @@ @rem limitations under the License. @rem -@if "%DEBUG%" == "" @echo off +@if "%DEBUG%"=="" @echo off @rem ########################################################################## @rem @rem Gradle startup script for Windows @@ -25,7 +25,8 @@ if "%OS%"=="Windows_NT" setlocal set DIRNAME=%~dp0 -if "%DIRNAME%" == "" set DIRNAME=. +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused set APP_BASE_NAME=%~n0 set APP_HOME=%DIRNAME% @@ -40,13 +41,13 @@ if defined JAVA_HOME goto findJavaFromJavaHome set JAVA_EXE=java.exe %JAVA_EXE% -version >NUL 2>&1 -if "%ERRORLEVEL%" == "0" goto execute +if %ERRORLEVEL% equ 0 goto execute -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -56,11 +57,11 @@ set JAVA_EXE=%JAVA_HOME%/bin/java.exe if exist "%JAVA_EXE%" goto execute -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. +echo. 1>&2 +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% 1>&2 +echo. 1>&2 +echo Please set the JAVA_HOME variable in your environment to match the 1>&2 +echo location of your Java installation. 1>&2 goto fail @@ -75,13 +76,15 @@ set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar :end @rem End local scope for the variables with windows NT shell -if "%ERRORLEVEL%"=="0" goto mainEnd +if %ERRORLEVEL% equ 0 goto mainEnd :fail rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of rem the _cmd.exe /c_ return code! -if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 -exit /b 1 +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% :mainEnd if "%OS%"=="Windows_NT" endlocal From 8f0bb79ae5d1757a0aafe80d32ba0625b81a2e11 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Thu, 20 Jun 2024 23:54:19 +0100 Subject: [PATCH 70/84] Add fuzz and skip options to AfterInvoke, closes #653 --- .../mixin/injection/points/AfterInvoke.java | 87 +++++++++++++++++-- .../injection/struct/InjectionPointData.java | 38 +++++++- .../org/spongepowered/asm/util/Bytecode.java | 51 +++++++++++ 3 files changed, 169 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/AfterInvoke.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/AfterInvoke.java index 2bf054850..8ec25593e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/AfterInvoke.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/AfterInvoke.java @@ -24,6 +24,7 @@ */ package org.spongepowered.asm.mixin.injection.points; +import java.util.Arrays; import java.util.Collection; import org.objectweb.asm.Opcodes; @@ -55,6 +56,18 @@ * the method is invoked 3 times and you want to match the 3rd then you can * specify an ordinal of 2 (ordinals are zero-indexed). The * default value is -1 which supresses ordinal matching
+ *
named argument: fuzz
+ *
By default, the injection point inspects only the instruction + * immediately following the matched invocation and will match store + * instructions. Specifying a higher fuzz increases the search range, + * skipping instructions as necessary to find a matching store opcode.
+ *
named argument: skip
+ *
When fuzz is specified, a default list of skippable opcodes is + * used. The list of skippable opcodes can be overridden by specifying a list + * of opcodes (numeric values or constant names from {@link Opcodes} can be + * used). This can be used to restrict the fuzz behaviour to consume + * only expected opcodes (eg. CHECKCAST). Note that store opcodes + * cannot be skipped and specifying them has no effect.
* * *

Example:

@@ -69,9 +82,63 @@ */ @AtCode("INVOKE_ASSIGN") public class AfterInvoke extends BeforeInvoke { - + + /** + * Default opcodes which are eligible to be skipped (see named argument + * skip above) if named argument fuzz is + * increased beyond the default value of 1. + * + *

Skipped opcodes: DUP, IADD, LADD, + * FADD, DADD, ISUB, LSUB, + * FSUB, DSUB, IMUL, LMUL, + * FMUL, DMUL, IDIV, LDIV, + * FDIV, DDIV, IREM, LREM, + * FREM, DREM, INEG, LNEG, + * FNEG, DNEG, ISHL, LSHL, + * ISHR, LSHR, IUSHR, LUSHR, + * IAND, LAND, IOR, LOR, IXOR, + * LXOR, IINC, I2L, I2F, I2D, + * L2I, L2F, L2D, F2I, F2L, + * F2D, D2I, D2L, D2F, I2B, + * I2C, I2S, CHECKCAST, INSTANCEOF

+ */ + public static final int[] DEFAULT_SKIP = new int[] { + // Opcodes which may appear if the targetted method is part of an + // expression eg. int foo = 2 + this.bar(); + Opcodes.DUP, Opcodes.IADD, Opcodes.LADD, Opcodes.FADD, Opcodes.DADD, + Opcodes.ISUB, Opcodes.LSUB, Opcodes.FSUB, Opcodes.DSUB, Opcodes.IMUL, + Opcodes.LMUL, Opcodes.FMUL, Opcodes.DMUL, Opcodes.IDIV, Opcodes.LDIV, + Opcodes.FDIV, Opcodes.DDIV, Opcodes.IREM, Opcodes.LREM, Opcodes.FREM, + Opcodes.DREM, Opcodes.INEG, Opcodes.LNEG, Opcodes.FNEG, Opcodes.DNEG, + Opcodes.ISHL, Opcodes.LSHL, Opcodes.ISHR, Opcodes.LSHR, Opcodes.IUSHR, + Opcodes.LUSHR, Opcodes.IAND, Opcodes.LAND, Opcodes.IOR, Opcodes.LOR, + Opcodes.IXOR, Opcodes.LXOR, Opcodes.IINC, + + // Opcodes which may appear if the targetted method is cast before + // assignment eg. int foo = (int)this.getFloat(); + Opcodes.I2L, Opcodes.I2F, Opcodes.I2D, Opcodes.L2I, Opcodes.L2F, + Opcodes.L2D, Opcodes.F2I, Opcodes.F2L, Opcodes.F2D, Opcodes.D2I, + Opcodes.D2L, Opcodes.D2F, Opcodes.I2B, Opcodes.I2C, Opcodes.I2S, + Opcodes.CHECKCAST, Opcodes.INSTANCEOF + }; + + /** + * Lookahead fuzz factor for finding the corresponding assignment, by + * default only the opcode immediately following the invocation is inspected + */ + private int fuzz = 1; + + /** + * If fuzz is increased beyond the default, the author can specify an allow- + * list of opcodes which can be skipped, by default the lookahead is + * unconstrained. + */ + private int[] skip = null; + public AfterInvoke(InjectionPointData data) { super(data); + this.fuzz = Math.max(data.get("fuzz", this.fuzz), 1); + this.skip = data.getOpcodeList("skip", AfterInvoke.DEFAULT_SKIP); } @Override @@ -80,12 +147,22 @@ protected boolean addInsn(InsnList insns, Collection nodes, Ab if (Type.getReturnType(methodNode.desc) == Type.VOID_TYPE) { return false; } - - insn = InjectionPoint.nextNode(insns, insn); - if (insn instanceof VarInsnNode && insn.getOpcode() >= Opcodes.ISTORE) { - insn = InjectionPoint.nextNode(insns, insn); + + if (this.fuzz > 0) { + int offset = insns.indexOf(insn); + int maxOffset = Math.min(insns.size(), offset + this.fuzz + 1); + for (int index = offset + 1; index < maxOffset; index++) { + AbstractInsnNode candidate = insns.get(index); + if (candidate instanceof VarInsnNode && insn.getOpcode() >= Opcodes.ISTORE) { + insn = candidate; + break; + } else if (this.skip != null && this.skip.length > 0 && Arrays.binarySearch(this.skip, candidate.getOpcode()) < 0) { + break; + } + } } + insn = InjectionPoint.nextNode(insns, insn); nodes.add(insn); return true; } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java index e896fd486..2a36721c7 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java @@ -27,6 +27,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; +import java.util.TreeSet; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -48,11 +50,13 @@ import org.spongepowered.asm.mixin.refmap.IMixinContext; import org.spongepowered.asm.util.Annotations; import org.spongepowered.asm.util.Annotations.Handle; +import org.spongepowered.asm.util.Bytecode; import org.spongepowered.asm.util.IMessageSink; import org.spongepowered.asm.util.asm.IAnnotationHandle; import com.google.common.base.Joiner; import com.google.common.base.Strings; +import com.google.common.primitives.Ints; /** * Data read from an {@link org.spongepowered.asm.mixin.injection.At} annotation @@ -64,7 +68,7 @@ public class InjectionPointData { * Regex for recognising at declarations */ private static final Pattern AT_PATTERN = InjectionPointData.createPattern(); - + /** * K/V arguments parsed from the "args" node in the {@link At} annotation */ @@ -383,7 +387,37 @@ public int getOpcode(int defaultOpcode, int... validOpcodes) { } return defaultOpcode; } - + + /** + * Get a list of opcodes specified in the injection point arguments. The + * opcodes can be specified as raw integer values or as their corresponding + * constant name from the {@link Opcodes} interface. All the values should + * be separated by spaces or commas. The returned array is sorted in order + * to make it suitable for use with the {@link Arrays#binarySearch} method. + * + * @param key argument name + * @param defaultValue value to return if the key is not specified + * @return parsed opcodes as array or default value if the key is not + * specified in the args + */ + public int[] getOpcodeList(String key, int[] defaultValue) { + String value = this.args.get(key); + if (value == null) { + return defaultValue; + } + + Set parsed = new TreeSet(); + String[] values = value.split("[ ,;]"); + for (String strOpcode : values) { + int opcode = Bytecode.parseOpcodeName(strOpcode.trim()); + if (opcode > 0) { + parsed.add(opcode); + } + } + + return Ints.toArray(parsed); + } + /** * Get the id specified on the injection point (or null if not specified) */ diff --git a/src/main/java/org/spongepowered/asm/util/Bytecode.java b/src/main/java/org/spongepowered/asm/util/Bytecode.java index b9588b374..eae400113 100644 --- a/src/main/java/org/spongepowered/asm/util/Bytecode.java +++ b/src/main/java/org/spongepowered/asm/util/Bytecode.java @@ -527,6 +527,57 @@ private static String getOpcodeName(int opcode, String start, int min) { return opcode >= 0 ? String.valueOf(opcode) : "UNKNOWN"; } + + /** + * Uses reflection to find a matching constant in the {@link Opcodes} + * interface for the specified opcode name. Supported formats are raw + * numeric values, bare constant names (eg. ACONST_NULL) or + * qualified names (eg. Opcodes.ACONST_NULL). Returns the value if + * found or -1 if not matched. Note that no validation is performed on + * numeric opcode values. + * + * @param opcodeName Opcode string to match + * @return matched opcode value or -1 if not matched. + */ + public static int parseOpcodeName(String opcodeName) { + if (opcodeName == null) { + return -1; + } + + if (opcodeName.matches("^1[0-9]{0,2}|[1-9][0-9]?$")) { + return Integer.parseInt(opcodeName); + } + + if (opcodeName.startsWith("Opcodes.")) { + opcodeName = opcodeName.substring(8); + } + + if (!opcodeName.matches("^[A-Z][A-Z0-9_]+$")) { + return -1; + } + + return Bytecode.parseOpcodeName(opcodeName, "UNINITIALIZED_THIS", 1); + } + + private static int parseOpcodeName(String opcodeName, String start, int min) { + boolean found = false; + + try { + for (java.lang.reflect.Field f : Opcodes.class.getDeclaredFields()) { + if (!found && !f.getName().equals(start)) { + continue; + } + found = true; + if (f.getType() == Integer.TYPE && f.getName().equalsIgnoreCase(opcodeName)) { + return f.getInt(null); + } + } + } catch (Exception ex) { + // well this is embarrassing + } + + return -1; + } /** * Returns true if the supplied method contains any line number information From d2105c63592b7ea08da0dde9d4d00039c5d3dd9d Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Fri, 21 Jun 2024 00:01:58 +0100 Subject: [PATCH 71/84] Display correct node type in printLocals --- .../asm/mixin/injection/callback/CallbackInjector.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java index 52e9ba52e..37e2b01ff 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java @@ -594,8 +594,8 @@ private void printLocals(final Callback callback) { printer.kv("Target Max LOCALS", callback.target.getMaxLocals()); printer.kv("Initial Frame Size", callback.frameSize); printer.kv("Callback Name", this.info.getMethodName()); - printer.kv("Instruction", "%s %s", callback.node.getClass().getSimpleName(), - Bytecode.getOpcodeName(callback.node.getCurrentTarget().getOpcode())); + printer.kv("Instruction", "%s %s", callback.node.getCurrentTarget().getClass().getSimpleName(), + Bytecode.describeNode(callback.node.getCurrentTarget())); printer.hr(); if (callback.locals.length > callback.frameSize) { printer.add(" %s %20s %s", "LOCAL", "TYPE", "NAME"); From 017eecf17d1cbb6dc2dab6f23a3b6db0656a004e Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Tue, 25 Jun 2024 23:45:48 +0100 Subject: [PATCH 72/84] Propagate local context through compounds via decoration, closes #532 --- .../tools/obfuscation/ReferenceManager.java | 3 - .../asm/mixin/injection/code/IInsnListEx.java | 29 ++++++ .../asm/mixin/injection/code/InsnListEx.java | 94 ++++++++++++++++++- .../injection/invoke/ModifyArgInjector.java | 2 +- .../injection/invoke/ModifyArgsInjector.java | 2 +- .../injection/invoke/RedirectInjector.java | 4 +- .../modify/LocalVariableDiscriminator.java | 2 +- .../modify/ModifyVariableInjector.java | 33 ++++++- .../asm/mixin/injection/struct/Target.java | 1 - 9 files changed, 155 insertions(+), 15 deletions(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java b/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java index f6a589cb7..ec1ad5544 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java @@ -27,14 +27,11 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; -import java.io.Writer; import java.net.URI; import java.util.List; import javax.annotation.processing.Filer; -import javax.annotation.processing.FilerException; import javax.tools.FileObject; -import javax.tools.JavaFileManager.Location; import javax.tools.StandardLocation; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorRemappable; diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java index 73979e59c..928090ff1 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/IInsnListEx.java @@ -102,4 +102,33 @@ public enum SpecialNodeType { * @return the special node or null if not available */ public abstract AbstractInsnNode getSpecialNode(SpecialNodeType type); + + + /** + * Get whether this list is decorated with the specified key + * + * @param key meta key + * @return true if the specified decoration exists + */ + public abstract boolean hasDecoration(String key); + + /** + * Get the specified decoration + * + * @param key meta key + * @param value type + * @return decoration value or null if absent + */ + public abstract V getDecoration(String key); + + /** + * Get the specified decoration or default value + * + * @param key meta key + * @param defaultValue default value to return + * @param value type + * @return decoration value or default value if absent + */ + public abstract V getDecoration(String key, V defaultValue); + } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java index 0a6a26a39..77055eeba 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/InsnListEx.java @@ -24,8 +24,12 @@ */ package org.spongepowered.asm.mixin.injection.code; +import java.util.HashMap; +import java.util.Map; + import org.objectweb.asm.tree.AbstractInsnNode; import org.spongepowered.asm.mixin.injection.struct.Constructor; +import org.spongepowered.asm.mixin.injection.struct.IChainedDecoration; import org.spongepowered.asm.mixin.injection.struct.Target; import org.spongepowered.asm.mixin.transformer.struct.Initialiser; import org.spongepowered.asm.mixin.transformer.struct.Initialiser.InjectionMode; @@ -40,7 +44,12 @@ public class InsnListEx extends InsnListReadOnly implements IInsnListEx { /** * The target method */ - private final Target target; + protected final Target target; + + /** + * Decorations on this insn list + */ + private Map decorations; public InsnListEx(Target target) { super(target.insns); @@ -154,5 +163,88 @@ public AbstractInsnNode getSpecialNode(SpecialNodeType type) { return null; } } + + /** + * Decorate this insn list with arbitrary metadata for use by + * context-specific injection points + * + * @param key meta key + * @param value meta value + * @param value type + * @return fluent + */ + @SuppressWarnings("unchecked") + public InsnListEx decorate(String key, V value) { + if (this.decorations == null) { + this.decorations = new HashMap(); + } + if (value instanceof IChainedDecoration && this.decorations.containsKey(key)) { + Object previous = this.decorations.get(key); + if (previous.getClass().equals(value.getClass())) { + ((IChainedDecoration)value).replace(previous); + } + } + this.decorations.put(key, value); + return this; + } + + /** + * Strip a specific decoration from this list if the decoration exists + * + * @param key meta key + * @return fluent + */ + public InsnListEx undecorate(String key) { + if (this.decorations != null) { + this.decorations.remove(key); + } + return this; + } + + /** + * Strip all decorations from this list + * + * @return fluent + */ + public InsnListEx undecorate() { + this.decorations = null; + return this; + } + + /** + * Get whether this list is decorated with the specified key + * + * @param key meta key + * @return true if the specified decoration exists + */ + public boolean hasDecoration(String key) { + return this.decorations != null && this.decorations.get(key) != null; + } + + /** + * Get the specified decoration + * + * @param key meta key + * @param value type + * @return decoration value or null if absent + */ + @SuppressWarnings("unchecked") + public V getDecoration(String key) { + return (V) (this.decorations == null ? null : this.decorations.get(key)); + } + + /** + * Get the specified decoration or default value + * + * @param key meta key + * @param defaultValue default value to return + * @param value type + * @return decoration value or null if absent + */ + @SuppressWarnings("unchecked") + public V getDecoration(String key, V defaultValue) { + V existing = (V) (this.decorations == null ? null : this.decorations.get(key)); + return existing != null ? existing : defaultValue; + } } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java index 839bad817..e9a0cb6f9 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java @@ -111,7 +111,7 @@ protected void inject(Target target, InjectionNode node) { protected void injectAtInvoke(Target target, InjectionNode node) { MethodInsnNode methodNode = (MethodInsnNode)node.getCurrentTarget(); Type[] args = Type.getArgumentTypes(methodNode.desc); - ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY); + ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY); if (offsets != null) { args = offsets.apply(args); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java index 82d1a0d34..c46d6b1ea 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java @@ -80,7 +80,7 @@ protected void inject(Target target, InjectionNode node) { protected void injectAtInvoke(Target target, InjectionNode node) { MethodInsnNode targetMethod = (MethodInsnNode)node.getCurrentTarget(); Type[] args = Type.getArgumentTypes(targetMethod.desc); - ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY); + ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY); if (offsets != null) { args = offsets.apply(args); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java index a0ea19e2c..eecfa5e5e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java @@ -268,7 +268,7 @@ protected void addTargetNode(InjectorTarget injectorTarget, List } if (node != null ) { - Meta other = node.getDecoration(Meta.KEY); + Meta other = node.getDecoration(Meta.KEY); if (other != null && other.getOwner() != this) { if (other.priority >= this.meta.priority) { @@ -359,7 +359,7 @@ protected void inject(Target target, InjectionNode node) { } protected boolean preInject(InjectionNode node) { - Meta other = node.getDecoration(Meta.KEY); + Meta other = node.getDecoration(Meta.KEY); if (other.getOwner() != this) { Injector.logger.warn("{} conflict. Skipping {} with priority {}, already redirected by {} with priority {}", this.annotationType, this.info, this.meta.priority, other.name, other.priority); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/modify/LocalVariableDiscriminator.java b/src/main/java/org/spongepowered/asm/mixin/injection/modify/LocalVariableDiscriminator.java index a084e3028..80b7be3d8 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/modify/LocalVariableDiscriminator.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/modify/LocalVariableDiscriminator.java @@ -102,7 +102,7 @@ int getOrdinal() { } /** - * Injection info + * Injection info */ final InjectionInfo info; diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java index 9fd4fbb44..421e79401 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/modify/ModifyVariableInjector.java @@ -35,8 +35,10 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.ModifyVariable; +import org.spongepowered.asm.mixin.injection.code.IInsnListEx; import org.spongepowered.asm.mixin.injection.code.Injector; import org.spongepowered.asm.mixin.injection.code.InjectorTarget; +import org.spongepowered.asm.mixin.injection.code.InsnListEx; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.InjectionPointData; @@ -56,6 +58,9 @@ */ public class ModifyVariableInjector extends Injector { + private static final String KEY_INFO = "mv.info"; + private static final String KEY_TARGET = "mv.target"; + /** * Target context information */ @@ -86,13 +91,22 @@ abstract static class LocalVariableInjectionPoint extends InjectionPoint { @Override public boolean find(String desc, InsnList insns, Collection nodes) { + if (insns instanceof IInsnListEx) { + IInsnListEx xinsns = (IInsnListEx)insns; + Target target = xinsns.getDecoration(ModifyVariableInjector.KEY_TARGET); + if (target != null) { + // Info is not actually used internally so we don't especially care if it's null + return this.find(xinsns.getDecoration(ModifyVariableInjector.KEY_INFO), insns, nodes, target); + } + } + throw new InvalidInjectionException(this.mixin, this.getAtCode() + " injection point must be used in conjunction with @ModifyVariable"); } abstract boolean find(InjectionInfo info, InsnList insns, Collection nodes, Target target); } - + /** * True to consider only method args */ @@ -109,11 +123,20 @@ public ModifyVariableInjector(InjectionInfo info, LocalVariableDiscriminator dis @Override protected boolean findTargetNodes(InjectorTarget target, InjectionPoint injectionPoint, Collection nodes) { - if (injectionPoint instanceof LocalVariableInjectionPoint) { - return ((LocalVariableInjectionPoint)injectionPoint).find(this.info, target.getSlice(injectionPoint), nodes, - target.getTarget()); + InsnListEx slice = (InsnListEx)target.getSlice(injectionPoint); + slice.decorate(ModifyVariableInjector.KEY_TARGET, target.getTarget()); + slice.decorate(ModifyVariableInjector.KEY_INFO, this.info); + + boolean found = injectionPoint instanceof LocalVariableInjectionPoint + ? ((LocalVariableInjectionPoint)injectionPoint).find(this.info, slice, nodes, target.getTarget()) + : injectionPoint.find(target.getDesc(), slice, nodes); + + if (slice instanceof InsnListEx) { + slice.undecorate(ModifyVariableInjector.KEY_TARGET); + slice.undecorate(ModifyVariableInjector.KEY_INFO); } - return injectionPoint.find(target.getDesc(), target.getSlice(injectionPoint), nodes); + + return found; } /* (non-Javadoc) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index eab6bd3b7..9836a652c 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -28,7 +28,6 @@ import java.util.Iterator; import java.util.List; -import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; From 2edfbc448e0ca43c8ba223381395adaa90b90d01 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Mon, 1 Jul 2024 08:58:23 +0100 Subject: [PATCH 73/84] Fix: Do not re-parent calls to interface super methods. (#150) The existing logic is wrong, and they will always be valid as-is because any superinterfaces will be merged onto the target class. --- .../asm/mixin/transformer/MixinTargetContext.java | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java index f1006b1f4..996621fdf 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java @@ -871,6 +871,10 @@ private void processImaginarySuper(MethodNode method, FieldInsnNode fieldInsn) { * @param methodRef Unbound reference to the method */ private void updateStaticBinding(MethodNode method, MemberRef methodRef) { + if (!methodRef.getOwner().equals(this.classNode.superName)) { + // Must be to an interface, no need to re-parent. + return; + } this.updateBinding(method, methodRef, Traversal.SUPER); } From 2cd49e6314aa559c8025bd6e6bb44a5e081572cd Mon Sep 17 00:00:00 2001 From: modmuss Date: Mon, 1 Jul 2024 09:09:16 +0100 Subject: [PATCH 74/84] Enable checkstyle (#149) --- build.gradle | 10 +- checkstyle.xml | 9 +- .../spongepowered/asm/mixin/FabricUtil.java | 19 +++ .../injection/callback/CallbackInjector.java | 4 +- .../injection/invoke/util/InvokeUtil.java | 5 +- .../injection/struct/InjectionNodes.java | 3 +- .../asm/mixin/transformer/ClassInfo.java | 6 +- .../asm/mixin/transformer/MethodMapper.java | 34 ++--- .../transformer/MixinApplicatorStandard.java | 4 +- .../asm/mixin/transformer/MixinConfig.java | 3 +- .../asm/mixin/transformer/MixinInfo.java | 3 +- .../transformer/MixinInheritanceTracker.java | 124 ++++++++++-------- .../MixinPreProcessorInterface.java | 2 +- .../MixinPreProcessorStandard.java | 14 +- .../asm/mixin/transformer/MixinProcessor.java | 4 +- .../asm/service/MixinService.java | 4 +- .../spongepowered/asm/util/CompareUtil.java | 45 ------- .../spongepowered/asm/util/JavaVersion.java | 32 ++--- .../org/spongepowered/asm/util/Locals.java | 5 +- .../spongepowered/asm/util/VersionNumber.java | 2 +- 20 files changed, 158 insertions(+), 174 deletions(-) delete mode 100644 src/main/java/org/spongepowered/asm/util/CompareUtil.java diff --git a/build.gradle b/build.gradle index ba391cc02..141b8076e 100644 --- a/build.gradle +++ b/build.gradle @@ -81,12 +81,12 @@ repositories { sourceSets { legacy { ext.languageVersion = 8 - ext.compatibility = '1.6' + ext.compatibility = '1.8' } main { compileClasspath += legacy.output ext.languageVersion = 8 - ext.compatibility = '1.6' + ext.compatibility = '1.8' } ap { compileClasspath += main.output @@ -96,13 +96,13 @@ sourceSets { fernflower { compileClasspath += main.output ext.languageVersion = 8 - ext.compatibility = '1.6' + ext.compatibility = '1.8' ext.modularityExcluded = true } agent { compileClasspath += main.output ext.languageVersion = 8 - ext.compatibility = '1.6' + ext.compatibility = '1.8' } bridge { compileClasspath += main.output @@ -322,7 +322,7 @@ checkstyle { "year" : project.inceptionYear ] configFile = file("checkstyle.xml") - toolVersion = '8.44' + toolVersion = '10.17.0' } // Source compiler configuration diff --git a/checkstyle.xml b/checkstyle.xml index 1109c4ac6..48e5470cc 100644 --- a/checkstyle.xml +++ b/checkstyle.xml @@ -9,7 +9,7 @@ Description: none --> - + @@ -131,13 +131,6 @@ - - - - - - - diff --git a/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java b/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java index d4ff75303..f68c3ed3f 100644 --- a/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java +++ b/src/main/java/org/spongepowered/asm/mixin/FabricUtil.java @@ -33,9 +33,25 @@ public final class FabricUtil { public static final String KEY_COMPATIBILITY = "fabric-compat"; // fabric mixin version compatibility boundaries, (major * 1000 + minor) * 1000 + patch + + /** + * Fabric compatibility version 0.9.2 + */ public static final int COMPATIBILITY_0_9_2 = 9002; // 0.9.2+mixin.0.8.2 incompatible local variable handling + + /** + * Fabric compatibility version 0.10.0 + */ public static final int COMPATIBILITY_0_10_0 = 10000; // 0.10.0+mixin.0.8.4 + + /** + * Fabric compatibility version 0.14.0 + */ public static final int COMPATIBILITY_0_14_0 = 14000; // 0.14.0+mixin.0.8.6 + + /** + * Latest compatibility version + */ public static final int COMPATIBILITY_LATEST = COMPATIBILITY_0_14_0; public static String getModId(IMixinConfig config) { @@ -65,4 +81,7 @@ private static T getDecoration(IMixinConfig config, String key, T defaultVal return defaultValue; } } + + private FabricUtil() { + } } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java index 0687a66b1..9494ee9f5 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java @@ -570,7 +570,9 @@ private void inject(final Callback callback) { } } this.invokeCallback(callback, callbackMethod); - if (callback.usesCallbackInfo) this.injectCancellationCode(callback); + if (callback.usesCallbackInfo) { + this.injectCancellationCode(callback); + } callback.inject(); this.info.notifyInjected(callback.target); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java index 8e874ed60..a57279b0e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java @@ -32,7 +32,7 @@ import java.util.Arrays; -public class InvokeUtil { +public final class InvokeUtil { public static Type[] getOriginalArgs(InjectionNode node) { return Type.getArgumentTypes(((MethodInsnNode) node.getOriginalTarget()).desc); } @@ -47,4 +47,7 @@ public static Type[] getCurrentArgs(InjectionNode node) { } return currentArgs; } + + private InvokeUtil() { + } } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionNodes.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionNodes.java index e21756004..86bacb2f0 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionNodes.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionNodes.java @@ -30,7 +30,6 @@ import org.objectweb.asm.tree.AbstractInsnNode; import org.spongepowered.asm.util.Bytecode; -import org.spongepowered.asm.util.CompareUtil; /** * Used to keep track of instruction nodes in a {@link Target} method which are @@ -198,7 +197,7 @@ public V getDecoration(String key) { */ @Override public int compareTo(InjectionNode other) { - return other == null ? Integer.MAX_VALUE : CompareUtil.compare(this.hashCode(), other.hashCode()); + return other == null ? Integer.MAX_VALUE : Integer.compare(this.hashCode(), other.hashCode()); } /* (non-Javadoc) diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java b/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java index 538198cda..c718286a0 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java @@ -232,7 +232,11 @@ public static class FrameData { * Frame local size */ public final int size; - public final int rawSize; // Fabric non-adjusted frame size for legacy support + + /** + * Fabric: non-adjusted frame size for legacy support + */ + public final int rawSize; FrameData(int index, int type, int locals, int size) { this.index = index; diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java index 5c83b5be0..8cbb9d08b 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java @@ -122,11 +122,11 @@ public String getHandlerName(MixinInfo mixin, MixinMethodNode method) { String mod = MethodMapper.getMixinSourceId(mixin, ""); String methodName = method.name; if (!mod.isEmpty()) { - //It's common for mods to prefix their own handlers, let's account for that happening - if (methodName.startsWith(mod) && methodName.length() > mod.length() + 1 && Chars.contains(new char[] {'_', '$'}, methodName.charAt(mod.length()))) { - methodName = methodName.substring(mod.length() + 1); - } - mod += '$'; + //It's common for mods to prefix their own handlers, let's account for that happening + if (methodName.startsWith(mod) && methodName.length() > mod.length() + 1 && Chars.contains(new char[] {'_', '$'}, methodName.charAt(mod.length()))) { + methodName = methodName.substring(mod.length() + 1); + } + mod += '$'; } String methodUID = MethodMapper.getMethodUID(methodName, method.desc, !method.isSurrogate()); return String.format("%s$%s%s$%s%s", prefix, classUID, methodUID, mod, methodName); @@ -145,18 +145,18 @@ public String getUniqueName(MixinInfo mixin, MethodNode method, String sessionId String uniqueIndex = Integer.toHexString(this.nextUniqueMethodIndex++); String methodName = method.name; if (method instanceof MethodNodeEx) { - String mod = MethodMapper.getMixinSourceId(mixin, ""); - if (!mod.isEmpty()) { - //It's rarer for mods to prefix their @Unique methods, but let's account for it anyway - if (methodName.startsWith(mod) && methodName.length() > mod.length() + 1 && Chars.contains(new char[] {'_', '$'}, methodName.charAt(mod.length()))) { - methodName = methodName.substring(mod.length() + 1); - } - if (preservePrefix) { - methodName += '$' + mod; - } else { - methodName = mod + '$' + methodName; - } - } + String mod = MethodMapper.getMixinSourceId(mixin, ""); + if (!mod.isEmpty()) { + //It's rarer for mods to prefix their @Unique methods, but let's account for it anyway + if (methodName.startsWith(mod) && methodName.length() > mod.length() + 1 && Chars.contains(new char[] {'_', '$'}, methodName.charAt(mod.length()))) { + methodName = methodName.substring(mod.length() + 1); + } + if (preservePrefix) { + methodName += '$' + mod; + } else { + methodName = mod + '$' + methodName; + } + } } String pattern = preservePrefix ? "%2$s_$md$%1$s$%3$s" : "md%s$%s$%s"; return String.format(pattern, sessionId.substring(30), methodName, uniqueIndex); diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java index 679899269..2bf01716c 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java @@ -781,7 +781,7 @@ protected final void checkConstraints(MixinTargetContext mixin, MethodNode metho /** * Finds a method in the target class - * @param searchFor + * @param searchFor The method node to search for * * @return Target method matching searchFor, or null if not found */ @@ -797,7 +797,7 @@ protected final MethodNode findTargetMethod(MethodNode searchFor) { /** * Finds a field in the target class - * @param searchFor + * @param searchFor The field node to search for * * @return Target field matching searchFor, or null if not found */ diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java index ee91220b4..d3a550f5e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinConfig.java @@ -59,7 +59,6 @@ import org.spongepowered.asm.mixin.transformer.throwables.InvalidMixinException; import org.spongepowered.asm.service.IMixinService; import org.spongepowered.asm.service.MixinService; -import org.spongepowered.asm.util.CompareUtil; import org.spongepowered.asm.util.VersionNumber; import com.google.common.base.Strings; @@ -1374,7 +1373,7 @@ public int compareTo(MixinConfig other) { return 0; } if (other.priority == this.priority) { - return CompareUtil.compare(this.order, other.order); + return Integer.compare(this.order, other.order); } else { return (this.priority < other.priority) ? -1 : 1; } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java index ce87922d2..0a445e8c7 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java @@ -73,7 +73,6 @@ import org.spongepowered.asm.util.LanguageFeatures; import org.spongepowered.asm.util.asm.ASM; import org.spongepowered.asm.util.asm.MethodNodeEx; -import org.spongepowered.asm.util.CompareUtil; import org.spongepowered.asm.util.perf.Profiler; import org.spongepowered.asm.util.perf.Profiler.Section; @@ -1342,7 +1341,7 @@ public int compareTo(MixinInfo other) { return 0; } if (other.priority == this.priority) { - return CompareUtil.compare(this.order, other.order); + return Integer.compare(this.order, other.order); } else { return (this.priority < other.priority) ? -1 : 1; } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInheritanceTracker.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInheritanceTracker.java index e76e20125..e6e26ca66 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInheritanceTracker.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInheritanceTracker.java @@ -38,62 +38,70 @@ import org.spongepowered.asm.util.Bytecode; public enum MixinInheritanceTracker implements IListener { - INSTANCE; - - @Override - public void onPrepare(MixinInfo mixin) { - } - - @Override - public void onInit(MixinInfo mixin) { - ClassInfo mixinInfo = mixin.getClassInfo(); - assert mixinInfo.isMixin(); //The mixin should certainly be a mixin - - for (ClassInfo superType = mixinInfo.getSuperClass(); superType != null && superType.isMixin(); superType = superType.getSuperClass()) { - List children = parentMixins.get(superType.getName()); - - if (children == null) { - parentMixins.put(superType.getName(), children = new ArrayList()); - } - - children.add(mixin); - } - } - - public List findOverrides(ClassInfo owner, String name, String desc) { - return findOverrides(owner.getName(), name, desc); - } - - public List findOverrides(String owner, String name, String desc) { - List children = parentMixins.get(owner); - if (children == null) return Collections.emptyList(); - - List out = new ArrayList(children.size()); - - for (MixinInfo child : children) { - ClassNode node = child.getClassNode(ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); - - MethodNode method = Bytecode.findMethod(node, name, desc); - if (method == null || Bytecode.isStatic(method)) continue; - - switch (Bytecode.getVisibility(method)) { - case PRIVATE: - break; - - case PACKAGE: - int ownerSplit = owner.lastIndexOf('/'); - int childSplit = node.name.lastIndexOf('/'); - //There is a reasonable chance mixins are in the same package, so it is viable that a package private method is overridden - if (ownerSplit != childSplit || (ownerSplit > 0 && !owner.regionMatches(0, node.name, 0, ownerSplit + 1))) break; - - default: - out.add(method); - break; - } - } - - return out.isEmpty() ? Collections.emptyList() : out; - } - - private final Map> parentMixins = new HashMap>(); + INSTANCE; + + @Override + public void onPrepare(MixinInfo mixin) { + } + + @Override + public void onInit(MixinInfo mixin) { + ClassInfo mixinInfo = mixin.getClassInfo(); + assert mixinInfo.isMixin(); //The mixin should certainly be a mixin + + for (ClassInfo superType = mixinInfo.getSuperClass(); superType != null && superType.isMixin(); superType = superType.getSuperClass()) { + List children = parentMixins.get(superType.getName()); + + if (children == null) { + parentMixins.put(superType.getName(), children = new ArrayList()); + } + + children.add(mixin); + } + } + + public List findOverrides(ClassInfo owner, String name, String desc) { + return findOverrides(owner.getName(), name, desc); + } + + public List findOverrides(String owner, String name, String desc) { + List children = parentMixins.get(owner); + if (children == null) { + return Collections.emptyList(); + } + + List out = new ArrayList(children.size()); + + for (MixinInfo child : children) { + ClassNode node = child.getClassNode(ClassReader.SKIP_DEBUG | ClassReader.SKIP_FRAMES); + + MethodNode method = Bytecode.findMethod(node, name, desc); + if (method == null || Bytecode.isStatic(method)) { + continue; + } + + switch (Bytecode.getVisibility(method)) { + case PRIVATE: + break; + + case PACKAGE: + int ownerSplit = owner.lastIndexOf('/'); + int childSplit = node.name.lastIndexOf('/'); + //There is a reasonable chance mixins are in the same package, so it is viable that a package private method is overridden + if (ownerSplit != childSplit || (ownerSplit > 0 && !owner.regionMatches(0, node.name, 0, ownerSplit + 1))) { + break; + } + + out.add(method); + break; + default: + out.add(method); + break; + } + } + + return out.isEmpty() ? Collections.emptyList() : out; + } + + private final Map> parentMixins = new HashMap>(); } \ No newline at end of file diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorInterface.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorInterface.java index 55c493d95..a01aa57fd 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorInterface.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorInterface.java @@ -140,7 +140,7 @@ protected boolean validateField(MixinTargetContext context, FieldNode field, Ann //Making a field non-final will result in verification crashes, so @Mutable is always a mistake if (Annotations.getVisible(field, Mutable.class) != null) { - throw new InvalidInterfaceMixinException(this.mixin, String.format("@Shadow field %s.%s is marked as mutable. This is not allowed.", + throw new InvalidInterfaceMixinException(this.mixin, String.format("@Shadow field %s.%s is marked as mutable. This is not allowed.", this.mixin, field.name)); } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java index 8b5b53132..5be4f8f22 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorStandard.java @@ -649,13 +649,13 @@ protected void attachFields(MixinTargetContext context) { iter.remove(); continue; } else if (!Bytecode.compareFlags(mixinField, target, Opcodes.ACC_STATIC)) { - if (isShadow) { - throw new InvalidMixinException(this.mixin, String.format("STATIC modifier of @Shadow field %s in %s does not match the target", - mixinField.name, this.mixin)); - } else { - throw new InvalidMixinException(this.mixin, String.format("Field %s in %s conflicts with %sstatic field in the target (%s)", - mixinField.name, mixin, Bytecode.isStatic(target) ? "" : "non-", context.getTarget())); - } + if (isShadow) { + throw new InvalidMixinException(this.mixin, String.format("STATIC modifier of @Shadow field %s in %s does not match the target", + mixinField.name, this.mixin)); + } else { + throw new InvalidMixinException(this.mixin, String.format("Field %s in %s conflicts with %sstatic field in the target (%s)", + mixinField.name, mixin, Bytecode.isStatic(target) ? "" : "non-", context.getTarget())); + } } if (!Bytecode.compareFlags(mixinField, target, Opcodes.ACC_STATIC)) { diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinProcessor.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinProcessor.java index d67cc10be..2cfa27c33 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinProcessor.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinProcessor.java @@ -543,7 +543,7 @@ public void onInit(MixinInfo mixin) { this.handleMixinPrepareError(config, ex, environment); } catch (Exception ex) { String message = ex.getMessage(); - MixinProcessor.logger.error("Error encountered whilst initialising mixin config '" + config.getName() + "' from mod '"+org.spongepowered.asm.mixin.FabricUtil.getModId(config)+"': " + message, ex); + MixinProcessor.logger.error("Error encountered whilst initialising mixin config '" + config.getName() + "' from mod '" + org.spongepowered.asm.mixin.FabricUtil.getModId(config) + "': " + message, ex); } } @@ -570,7 +570,7 @@ public void onInit(MixinInfo mixin) { this.handleMixinPrepareError(config, ex, environment); } catch (Exception ex) { String message = ex.getMessage(); - MixinProcessor.logger.error("Error encountered during mixin config postInit step '" + config.getName() + "' from mod '"+org.spongepowered.asm.mixin.FabricUtil.getModId(config)+ "': " + message, ex); + MixinProcessor.logger.error("Error encountered during mixin config postInit step '" + config.getName() + "' from mod '" + org.spongepowered.asm.mixin.FabricUtil.getModId(config) + "': " + message, ex); } } diff --git a/src/main/java/org/spongepowered/asm/service/MixinService.java b/src/main/java/org/spongepowered/asm/service/MixinService.java index d4a752e2d..6c54333ae 100644 --- a/src/main/java/org/spongepowered/asm/service/MixinService.java +++ b/src/main/java/org/spongepowered/asm/service/MixinService.java @@ -221,7 +221,9 @@ private IMixinService initService() { if (serviceCls != null) { try { IMixinService service = (IMixinService) Class.forName(serviceCls).getConstructor().newInstance(); - if (!service.isValid()) throw new RuntimeException("invalid service "+serviceCls+" configured via system property"); + if (!service.isValid()) { + throw new RuntimeException("invalid service " + serviceCls + " configured via system property"); + } return service; } catch (ReflectiveOperationException e) { diff --git a/src/main/java/org/spongepowered/asm/util/CompareUtil.java b/src/main/java/org/spongepowered/asm/util/CompareUtil.java deleted file mode 100644 index 7b1be4f95..000000000 --- a/src/main/java/org/spongepowered/asm/util/CompareUtil.java +++ /dev/null @@ -1,45 +0,0 @@ -/* - * This file is part of Mixin, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.asm.util; - -/** - * Comparison helpers. - */ -public final class CompareUtil { - private CompareUtil() { - - } - - /** - * Officially added in Java 7. - */ - public static int compare(int a, int b) { - return ((a < b) ? -1 : ((a == b) ? 0 : 1)); - } - - public static int compare(long a, long b) { - return ((a < b) ? -1 : ((a == b) ? 0 : 1)); - } -} diff --git a/src/main/java/org/spongepowered/asm/util/JavaVersion.java b/src/main/java/org/spongepowered/asm/util/JavaVersion.java index c95c93302..9382f27b3 100644 --- a/src/main/java/org/spongepowered/asm/util/JavaVersion.java +++ b/src/main/java/org/spongepowered/asm/util/JavaVersion.java @@ -97,25 +97,25 @@ public abstract class JavaVersion { */ public static final double JAVA_18 = 18.0; - /** - * Version number for Java 19 - */ - public static final double JAVA_19 = 19.0; + /** + * Version number for Java 19 + */ + public static final double JAVA_19 = 19.0; - /** - * Version number for Java 20 - */ - public static final double JAVA_20 = 20.0; + /** + * Version number for Java 20 + */ + public static final double JAVA_20 = 20.0; - /** - * Version number for Java 21 - */ - public static final double JAVA_21 = 21.0; + /** + * Version number for Java 21 + */ + public static final double JAVA_21 = 21.0; - /** - * Version number for Java 22 - */ - public static final double JAVA_22 = 22.0; + /** + * Version number for Java 22 + */ + public static final double JAVA_22 = 22.0; private static double current = 0.0; diff --git a/src/main/java/org/spongepowered/asm/util/Locals.java b/src/main/java/org/spongepowered/asm/util/Locals.java index 4f62ce352..0fcc960c1 100644 --- a/src/main/java/org/spongepowered/asm/util/Locals.java +++ b/src/main/java/org/spongepowered/asm/util/Locals.java @@ -328,6 +328,7 @@ public static void loadLocals(Type[] locals, InsnList insns, int pos, int limit) * bear in mind that if the specified node is itself a STORE opcode, * then we will be looking at the state of the locals PRIOR to its * invocation + * @param fabricCompatibility Fabric compatibility level * @return A sparse array containing a view (hopefully) of the locals at the * specified location */ @@ -335,7 +336,7 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me if (fabricCompatibility >= org.spongepowered.asm.mixin.FabricUtil.COMPATIBILITY_0_10_0) { return Locals.getLocalsAt(classNode, method, node, Settings.DEFAULT); } else { - return getLocalsAt_0_9_2(classNode, method, node); + return getLocalsAt092(classNode, method, node); } } @@ -580,7 +581,7 @@ public static LocalVariableNode[] getLocalsAt(ClassNode classNode, MethodNode me return frame; } - private static LocalVariableNode[] getLocalsAt_0_9_2(ClassNode classNode, MethodNode method, AbstractInsnNode node) { + private static LocalVariableNode[] getLocalsAt092(ClassNode classNode, MethodNode method, AbstractInsnNode node) { for (int i = 0; i < 3 && (node instanceof LabelNode || node instanceof LineNumberNode); i++) { node = Locals.nextNode(method.instructions, node); } diff --git a/src/main/java/org/spongepowered/asm/util/VersionNumber.java b/src/main/java/org/spongepowered/asm/util/VersionNumber.java index d764dd36b..ff9e8b06e 100644 --- a/src/main/java/org/spongepowered/asm/util/VersionNumber.java +++ b/src/main/java/org/spongepowered/asm/util/VersionNumber.java @@ -141,7 +141,7 @@ public int compareTo(VersionNumber other) { if (other == null) { return 1; } - return CompareUtil.compare(this.value, other.value); + return Long.compare(this.value, other.value); } /* (non-Javadoc) From 96e81bb78031a2406173fbfe6720e1971de7e420 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Thu, 4 Jul 2024 22:35:11 +0100 Subject: [PATCH 75/84] Use ClassReader flags tunable for metadata ClassNodes too, updates #671 --- .../asm/mixin/injection/callback/CallbackInjector.java | 2 +- .../spongepowered/asm/mixin/transformer/ClassInfo.java | 5 ++++- .../org/spongepowered/asm/util/SignaturePrinter.java | 9 +++++---- 3 files changed, 10 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java index 37e2b01ff..3ee3e8f0d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java @@ -178,7 +178,7 @@ private class Callback extends InsnList { List argNames = null; if (locals != null) { - int baseArgIndex = CallbackInjector.this.isStatic() ? 0 : 1; + int baseArgIndex = target.isStatic ? 0 : 1; argNames = new ArrayList(); for (int l = 0; l <= locals.length; l++) { if (l == this.frameSize) { diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java b/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java index 9e45c5355..35ebf244b 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/ClassInfo.java @@ -36,6 +36,7 @@ import org.spongepowered.asm.logging.Level; import org.spongepowered.asm.logging.ILogger; +import org.objectweb.asm.ClassReader; import org.objectweb.asm.Opcodes; import org.objectweb.asm.tree.AbstractInsnNode; import org.objectweb.asm.tree.ClassNode; @@ -50,6 +51,7 @@ import org.spongepowered.asm.mixin.Mutable; import org.spongepowered.asm.mixin.Shadow; import org.spongepowered.asm.mixin.Unique; +import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.extensibility.IMixinInfo; import org.spongepowered.asm.mixin.gen.Accessor; import org.spongepowered.asm.mixin.gen.Invoker; @@ -2002,7 +2004,8 @@ public static ClassInfo forName(String className) { ClassInfo info = ClassInfo.cache.get(className); if (info == null) { try { - ClassNode classNode = MixinService.getService().getBytecodeProvider().getClassNode(className); + int flags = MixinEnvironment.getCurrentEnvironment().getOption(Option.CLASSREADER_EXPAND_FRAMES) ? ClassReader.EXPAND_FRAMES : 0; + ClassNode classNode = MixinService.getService().getBytecodeProvider().getClassNode(className, true, flags); info = new ClassInfo(classNode); } catch (Exception ex) { ClassInfo.logger.catching(Level.TRACE, ex); diff --git a/src/main/java/org/spongepowered/asm/util/SignaturePrinter.java b/src/main/java/org/spongepowered/asm/util/SignaturePrinter.java index 8e64ae984..220f7d5d6 100644 --- a/src/main/java/org/spongepowered/asm/util/SignaturePrinter.java +++ b/src/main/java/org/spongepowered/asm/util/SignaturePrinter.java @@ -148,14 +148,15 @@ public String getReturnType() { */ public void setModifiers(MethodNode method) { String returnType = SignaturePrinter.getTypeName(Type.getReturnType(method.desc), false, this.fullyQualified); + String staticType = (method.access & Opcodes.ACC_STATIC) != 0 ? "static " : ""; if ((method.access & Opcodes.ACC_PUBLIC) != 0) { - this.setModifiers("public " + returnType); + this.setModifiers("public " + staticType + returnType); } else if ((method.access & Opcodes.ACC_PROTECTED) != 0) { - this.setModifiers("protected " + returnType); + this.setModifiers("protected " + staticType + returnType); } else if ((method.access & Opcodes.ACC_PRIVATE) != 0) { - this.setModifiers("private " + returnType); + this.setModifiers("private " + staticType + returnType); } else { - this.setModifiers(returnType); + this.setModifiers(staticType + returnType); } } From 0239904a1bf9853c811b80c7de260dc11a5abdf0 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Fri, 5 Jul 2024 21:46:09 +0100 Subject: [PATCH 76/84] Handle offset arg window correctly for ModifyArg injector, updates #544 --- .../injection/invoke/ModifyArgInjector.java | 40 ++++++++++--------- .../injection/invoke/RedirectInjector.java | 9 +++-- .../mixin/injection/struct/ArgOffsets.java | 37 +++++++++++++++-- .../asm/mixin/injection/struct/Target.java | 38 +++++++++++++----- 4 files changed, 89 insertions(+), 35 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java index e9a0cb6f9..d02f191c0 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java @@ -111,20 +111,25 @@ protected void inject(Target target, InjectionNode node) { protected void injectAtInvoke(Target target, InjectionNode node) { MethodInsnNode methodNode = (MethodInsnNode)node.getCurrentTarget(); Type[] args = Type.getArgumentTypes(methodNode.desc); - ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY); - if (offsets != null) { - args = offsets.apply(args); - } + ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY, ArgOffsets.DEFAULT); + boolean nested = node.hasDecoration(ArgOffsets.KEY); + Type[] originalArgs = offsets.apply(args); - int argIndex = this.findArgIndex(target, args); + int argIndex = offsets.getArgIndex(this.findArgIndex(target, originalArgs)); + int baseIndex = offsets.getArgIndex(0); InsnList insns = new InsnList(); Extension extraLocals = target.extendLocals(); if (this.singleArgMode) { - this.injectSingleArgHandler(target, extraLocals, args, argIndex, insns); + this.injectSingleArgHandler(target, extraLocals, args, argIndex, insns, nested); } else { - this.injectMultiArgHandler(target, extraLocals, args, argIndex, insns); + if (!Arrays.equals(originalArgs, this.methodArgs)) { + throw new InvalidInjectionException(this.info, "@ModifyArg method " + this + " targets a method with an invalid signature " + + Bytecode.getDescriptor(originalArgs) + ", expected " + Bytecode.getDescriptor(this.methodArgs)); + } + + this.injectMultiArgHandler(target, extraLocals, args, baseIndex, argIndex, insns, nested); } target.insns.insertBefore(methodNode, insns); @@ -135,8 +140,9 @@ protected void injectAtInvoke(Target target, InjectionNode node) { /** * Inject handler opcodes for a single arg handler */ - private void injectSingleArgHandler(Target target, Extension extraLocals, Type[] args, int argIndex, InsnList insns) { - int[] argMap = this.storeArgs(target, args, insns, argIndex); + private void injectSingleArgHandler(Target target, Extension extraLocals, Type[] args, int argIndex, InsnList insns, boolean nested) { + int[] argMap = target.generateArgMap(args, argIndex, nested); + this.storeArgs(target, args, insns, argMap, argIndex, args.length, null, null); this.invokeHandlerWithArgs(args, insns, argMap, argIndex, argIndex + 1); this.pushArgs(args, insns, argMap, argIndex + 1, args.length); extraLocals.add((argMap[argMap.length - 1] - target.getMaxLocals()) + args[args.length - 1].getSize()); @@ -145,15 +151,13 @@ private void injectSingleArgHandler(Target target, Extension extraLocals, Type[] /** * Inject handler opcodes for a multi arg handler */ - private void injectMultiArgHandler(Target target, Extension extraLocals, Type[] args, int argIndex, InsnList insns) { - if (!Arrays.equals(args, this.methodArgs)) { - throw new InvalidInjectionException(this.info, "@ModifyArg method " + this + " targets a method with an invalid signature " - + Bytecode.getDescriptor(args) + ", expected " + Bytecode.getDescriptor(this.methodArgs)); - } - - int[] argMap = this.storeArgs(target, args, insns, 0); - this.pushArgs(args, insns, argMap, 0, argIndex); - this.invokeHandlerWithArgs(args, insns, argMap, 0, args.length); + private void injectMultiArgHandler(Target target, Extension extraLocals, Type[] args, int baseIndex, int argIndex, InsnList insns, + boolean nested) { + int[] argMap = target.generateArgMap(args, baseIndex, nested); + int[] handlerArgMap = baseIndex == 0 ? argMap : Arrays.copyOfRange(argMap, baseIndex, baseIndex + this.methodArgs.length); + this.storeArgs(target, args, insns, argMap, baseIndex, args.length, null, null); + this.pushArgs(args, insns, argMap, baseIndex, argIndex); + this.invokeHandlerWithArgs(this.methodArgs, insns, handlerArgMap, 0, this.methodArgs.length); this.pushArgs(args, insns, argMap, argIndex + 1, args.length); extraLocals.add((argMap[argMap.length - 1] - target.getMaxLocals()) + args[args.length - 1].getSize()); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java index eecfa5e5e..dbf703dda 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java @@ -160,6 +160,7 @@ public void throwOrCollect(InvalidInjectionException ex) { static class RedirectedInvokeData extends InjectorData { final MethodInsnNode node; + final boolean isStatic; final Type returnType; final Type[] targetArgs; final Type[] handlerArgs; @@ -167,9 +168,10 @@ static class RedirectedInvokeData extends InjectorData { RedirectedInvokeData(Target target, MethodInsnNode node) { super(target); this.node = node; + this.isStatic = node.getOpcode() == Opcodes.INVOKESTATIC; this.returnType = Type.getReturnType(node.desc); this.targetArgs = Type.getArgumentTypes(node.desc); - this.handlerArgs = node.getOpcode() == Opcodes.INVOKESTATIC + this.handlerArgs = this.isStatic ? this.targetArgs : ObjectArrays.concat(Type.getObjectType(node.owner), this.targetArgs); } @@ -393,7 +395,7 @@ protected void injectAtInvoke(Target target, InjectionNode node) { Extension extraLocals = target.extendLocals().add(invoke.handlerArgs).add(1); Extension extraStack = target.extendStack().add(1); // Normally only need 1 extra stack pos to store target ref int[] argMap = this.storeArgs(target, invoke.handlerArgs, insns, 0); - ArgOffsets offsets = new ArgOffsets(this.isStatic ? 0 : 1, invoke.targetArgs.length); + ArgOffsets offsets = new ArgOffsets(invoke.isStatic ? 0 : 1, invoke.targetArgs.length); if (invoke.captureTargetArgs > 0) { int argSize = Bytecode.getArgsSize(target.arguments, 0, invoke.captureTargetArgs); extraLocals.add(argSize); @@ -405,7 +407,8 @@ protected void injectAtInvoke(Target target, InjectionNode node) { if (invoke.coerceReturnType && invoke.returnType.getSort() >= Type.ARRAY) { insns.add(new TypeInsnNode(Opcodes.CHECKCAST, invoke.returnType.getInternalName())); } - target.replaceNode(invoke.node, champion, insns).decorate(ArgOffsets.KEY, offsets); + target.replaceNode(invoke.node, champion, insns); + node.decorate(ArgOffsets.KEY, offsets); extraLocals.apply(); extraStack.apply(); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java index c4d88e028..b60cca36a 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java @@ -31,18 +31,49 @@ * results in a call to a method with the same arguments as the original * (replaced) call but offset by some fixed amount. Since ModifyArg and * ModifyArgs always assume the method args are on the top of the stack (which - * they must be), this essentially results in just chopping off a fixed number - * of arguments from the start of the method. + * they must be), this results in locating the original method args as as a + * contiguous "window" of arguments somewhere in the middle of the args as they + * exist at application time. + * + *

Injectors which mutate the arguments of an invocation should apply this + * decoration to indicate the starting offset and size of the window which + * contains the original args.

*/ public class ArgOffsets implements IChainedDecoration { + /** + * No-op arg offsets to be used when we just want unaltered arg offsets + */ + private static class Default extends ArgOffsets { + + public Default() { + super(0, 255); + } + + @Override + public int getArgIndex(int index) { + return index; + } + + @Override + public Type[] apply(Type[] args) { + return args; + } + + } + + /** + * Null offsets + */ + public static ArgOffsets DEFAULT = new ArgOffsets.Default(); + /** * Decoration key for this decoration type */ public static final String KEY = "argOffsets"; /** - * The offset for the start of the args + * The offset for the start of the (original) args within the new args */ private final int offset; diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java index 9836a652c..2fa050e79 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/Target.java @@ -440,6 +440,22 @@ private void setMaxStack(int maxStack) { * of the supplied args array */ public int[] generateArgMap(Type[] args, int start) { + return this.generateArgMap(args, start, false); + } + + /** + * Generate an array containing local indexes for the specified args, + * returns an array of identical size to the supplied array with an + * allocated local index in each corresponding position + * + * @param args Argument types + * @param start starting index + * @param fresh allocate fresh locals only, do not reuse existing argmap + * slots + * @return array containing a corresponding local arg index for each member + * of the supplied args array + */ + public int[] generateArgMap(Type[] args, int start, boolean fresh) { if (this.argMapVars == null) { this.argMapVars = new ArrayList(); } @@ -447,7 +463,7 @@ public int[] generateArgMap(Type[] args, int start) { int[] argMap = new int[args.length]; for (int arg = start, index = 0; arg < args.length; arg++) { int size = args[arg].getSize(); - argMap[arg] = this.allocateArgMapLocal(index, size); + argMap[arg] = fresh ? this.allocateLocals(size) : this.allocateArgMapLocal(index, size); index += size; } return argMap; @@ -744,10 +760,10 @@ public void insertBefore(AbstractInsnNode location, final AbstractInsnNode insn) * @param location Instruction to replace * @param insn Instruction to replace with */ - public InjectionNode replaceNode(AbstractInsnNode location, AbstractInsnNode insn) { + public void replaceNode(AbstractInsnNode location, AbstractInsnNode insn) { this.insns.insertBefore(location, insn); this.insns.remove(location); - return this.injectionNodes.replace(location, insn); + this.injectionNodes.replace(location, insn); } /** @@ -758,10 +774,10 @@ public InjectionNode replaceNode(AbstractInsnNode location, AbstractInsnNode ins * @param champion Instruction which notionally replaces the original insn * @param insns Instructions to actually insert (must contain champion) */ - public InjectionNode replaceNode(AbstractInsnNode location, AbstractInsnNode champion, InsnList insns) { + public void replaceNode(AbstractInsnNode location, AbstractInsnNode champion, InsnList insns) { this.insns.insertBefore(location, insns); this.insns.remove(location); - return this.injectionNodes.replace(location, champion); + this.injectionNodes.replace(location, champion); } /** @@ -773,10 +789,10 @@ public InjectionNode replaceNode(AbstractInsnNode location, AbstractInsnNode cha * @param before Instructions to actually insert (must contain champion) * @param after Instructions to insert after the specified location */ - public InjectionNode wrapNode(AbstractInsnNode location, AbstractInsnNode champion, InsnList before, InsnList after) { + public void wrapNode(AbstractInsnNode location, AbstractInsnNode champion, InsnList before, InsnList after) { this.insns.insertBefore(location, before); this.insns.insert(location, after); - return this.injectionNodes.replace(location, champion); + this.injectionNodes.replace(location, champion); } /** @@ -786,9 +802,9 @@ public InjectionNode wrapNode(AbstractInsnNode location, AbstractInsnNode champi * @param location Instruction to replace * @param insns Instructions to replace with */ - public InjectionNode replaceNode(AbstractInsnNode location, InsnList insns) { + public void replaceNode(AbstractInsnNode location, InsnList insns) { this.insns.insertBefore(location, insns); - return this.removeNode(location); + this.removeNode(location); } /** @@ -797,9 +813,9 @@ public InjectionNode replaceNode(AbstractInsnNode location, InsnList insns) { * * @param insn instruction to remove */ - public InjectionNode removeNode(AbstractInsnNode insn) { + public void removeNode(AbstractInsnNode insn) { this.insns.remove(insn); - return this.injectionNodes.remove(insn); + this.injectionNodes.remove(insn); } /** From 0c79de49579a5e2863140f475b68adf3b9a89f68 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 6 Jul 2024 12:41:11 +0100 Subject: [PATCH 77/84] Handle offset arg window correctly for ModifyArgs injector, updates #544 --- .../injection/invoke/ModifyArgInjector.java | 2 +- .../injection/invoke/ModifyArgsInjector.java | 26 ++++++----- .../mixin/injection/struct/ArgOffsets.java | 44 +++++++++++++++++++ 3 files changed, 59 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java index d02f191c0..a6064f7a6 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java @@ -116,7 +116,7 @@ protected void injectAtInvoke(Target target, InjectionNode node) { Type[] originalArgs = offsets.apply(args); int argIndex = offsets.getArgIndex(this.findArgIndex(target, originalArgs)); - int baseIndex = offsets.getArgIndex(0); + int baseIndex = offsets.getStartIndex(); InsnList insns = new InsnList(); Extension extraLocals = target.extendLocals(); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java index c46d6b1ea..e5712dc0a 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java @@ -78,17 +78,17 @@ protected void inject(Target target, InjectionNode node) { */ @Override protected void injectAtInvoke(Target target, InjectionNode node) { - MethodInsnNode targetMethod = (MethodInsnNode)node.getCurrentTarget(); - Type[] args = Type.getArgumentTypes(targetMethod.desc); - ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY); - if (offsets != null) { - args = offsets.apply(args); - } - String targetMethodDesc = Type.getMethodDescriptor(Type.getReturnType(targetMethod.desc), args); + MethodInsnNode methodNode = (MethodInsnNode)node.getCurrentTarget(); + Type[] args = Type.getArgumentTypes(methodNode.desc); + ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY, ArgOffsets.DEFAULT); + Type[] originalArgs = offsets.apply(args); + int endIndex = offsets.getArgIndex(originalArgs.length); + + String targetMethodDesc = Type.getMethodDescriptor(Type.getReturnType(methodNode.desc), originalArgs); - if (args.length == 0) { + if (originalArgs.length == 0) { throw new InvalidInjectionException(this.info, "@ModifyArgs injector " + this + " targets a method invocation " - + targetMethod.name + targetMethodDesc + " with no arguments!"); + + methodNode.name + targetMethodDesc + " with no arguments!"); } String clArgs = this.argsClassGenerator.getArgsClass(targetMethodDesc, this.info.getMixin().getMixin()).getName(); @@ -97,6 +97,7 @@ protected void injectAtInvoke(Target target, InjectionNode node) { InsnList insns = new InsnList(); Extension extraStack = target.extendStack().add(1); + int[] afterWindowArgMap = this.storeArgs(target, args, insns, endIndex); this.packArgs(insns, clArgs, targetMethodDesc); if (withArgs) { @@ -105,10 +106,11 @@ protected void injectAtInvoke(Target target, InjectionNode node) { } this.invokeHandler(insns); - this.unpackArgs(insns, clArgs, args); - + this.unpackArgs(insns, clArgs, originalArgs); + this.pushArgs(args, insns, afterWindowArgMap, endIndex, args.length); + extraStack.apply(); - target.insns.insertBefore(targetMethod, insns); + target.insns.insertBefore(methodNode, insns); } private boolean verifyTarget(Target target) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java index b60cca36a..4fcbcb498 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java @@ -95,9 +95,20 @@ public Type[] apply(Type[] args) { * @param length length */ public ArgOffsets(int offset, int length) { + if (length < 1) { + throw new IllegalArgumentException("Invalid length " + length + " for ArgOffsets window"); + } this.offset = offset; this.length = length; } + + /* (non-Javadoc) + * @see java.lang.Object#toString() + */ + @Override + public String toString() { + return String.format("ArgOffsets[start=%d(%d),length=%d]", this.offset, this.getStartIndex(), this.length); + } /* (non-Javadoc) * @see org.spongepowered.asm.mixin.injection.struct.IChainedDecoration @@ -116,6 +127,24 @@ public int getLength() { return this.length; } + /** + * Compute the argument index for the start of the window (offet 0) + * + * @return the offset index for the start of the window (inclusive) + */ + public int getStartIndex() { + return this.getArgIndex(0); + } + + /** + * Compute the argument index for the end of the window (offset length) + * + * @return the offset index for the end of the window (inclusive) + */ + public int getEndIndex() { + return this.getArgIndex(this.length - 1); + } + /** * Compute the argument index for the specified new index * @@ -123,6 +152,21 @@ public int getLength() { * @return The original index based on this mapping */ public int getArgIndex(int index) { + return this.getArgIndex(index, false); + } + + /** + * Compute the argument index for the specified new index + * + * @param index The new index to compute + * @param mustBeInWindow Throw an exception if the requested index exceeds + * the length of the defined window + * @return The original index based on this mapping + */ + public int getArgIndex(int index, boolean mustBeInWindow) { + if (mustBeInWindow && index > this.length) { + throw new IndexOutOfBoundsException("The specified arg index " + index + " is greater than the window size " + this.length); + } int offsetIndex = index + this.offset; return this.next != null ? this.next.getArgIndex(offsetIndex) : offsetIndex; } From c8be3caa404a7544012cabf0bbc4744b9b3131b4 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 6 Jul 2024 12:43:28 +0100 Subject: [PATCH 78/84] Use InjectorOrder.DEFAULT for field, fixes third-party injector order --- .../spongepowered/asm/mixin/injection/struct/InjectionInfo.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java index c0cfec509..743ca0dff 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java @@ -318,7 +318,7 @@ InjectionInfo create(MixinTargetContext mixin, MethodNode method, AnnotationNode * Injector order, parsed from either the injector annotation or uses the * default for this injection type */ - private int order; + private int order = InjectorOrder.DEFAULT; /** * ctor From 7d7d4dc46c2063f60e36f6297eda47e0ec1b0cd6 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 6 Jul 2024 13:54:43 +0100 Subject: [PATCH 79/84] Properly handle empty args window and report name of the original method --- .../mixin/injection/invoke/ModifyArgInjector.java | 7 ++++++- .../mixin/injection/invoke/ModifyArgsInjector.java | 2 +- .../asm/mixin/injection/struct/ArgOffsets.java | 14 +++++++++----- 3 files changed, 16 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java index a6064f7a6..9f50fa4ae 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java @@ -115,6 +115,11 @@ protected void injectAtInvoke(Target target, InjectionNode node) { boolean nested = node.hasDecoration(ArgOffsets.KEY); Type[] originalArgs = offsets.apply(args); + if (originalArgs.length == 0) { + throw new InvalidInjectionException(this.info, "@ModifyArg injector " + this + " targets a method invocation " + + ((MethodInsnNode)node.getOriginalTarget()).name + "()" + Type.getReturnType(methodNode.desc) + " with no arguments!"); + } + int argIndex = offsets.getArgIndex(this.findArgIndex(target, originalArgs)); int baseIndex = offsets.getStartIndex(); @@ -125,7 +130,7 @@ protected void injectAtInvoke(Target target, InjectionNode node) { this.injectSingleArgHandler(target, extraLocals, args, argIndex, insns, nested); } else { if (!Arrays.equals(originalArgs, this.methodArgs)) { - throw new InvalidInjectionException(this.info, "@ModifyArg method " + this + " targets a method with an invalid signature " + throw new InvalidInjectionException(this.info, "@ModifyArg injector " + this + " targets a method with an invalid signature " + Bytecode.getDescriptor(originalArgs) + ", expected " + Bytecode.getDescriptor(this.methodArgs)); } diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java index e5712dc0a..53157f1ea 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java @@ -88,7 +88,7 @@ protected void injectAtInvoke(Target target, InjectionNode node) { if (originalArgs.length == 0) { throw new InvalidInjectionException(this.info, "@ModifyArgs injector " + this + " targets a method invocation " - + methodNode.name + targetMethodDesc + " with no arguments!"); + + ((MethodInsnNode)node.getOriginalTarget()).name + targetMethodDesc + " with no arguments!"); } String clArgs = this.argsClassGenerator.getArgsClass(targetMethodDesc, this.info.getMixin().getMixin()).getName(); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java index 4fcbcb498..2483cb02f 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java @@ -95,9 +95,6 @@ public Type[] apply(Type[] args) { * @param length length */ public ArgOffsets(int offset, int length) { - if (length < 1) { - throw new IllegalArgumentException("Invalid length " + length + " for ArgOffsets window"); - } this.offset = offset; this.length = length; } @@ -121,12 +118,19 @@ public void replace(ArgOffsets old) { } /** - * Get the size of this mapping collection + * Get the size of the offset window */ public int getLength() { return this.length; } + /** + * Get whether this argument offset window is empty + */ + public boolean isEmpty() { + return this.length == 0; + } + /** * Compute the argument index for the start of the window (offet 0) * @@ -142,7 +146,7 @@ public int getStartIndex() { * @return the offset index for the end of the window (inclusive) */ public int getEndIndex() { - return this.getArgIndex(this.length - 1); + return this.isEmpty() ? this.getStartIndex() : this.getArgIndex(this.length - 1); } /** From 4053421aa10aaac6127d969028a29c94fe3054f6 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Sat, 6 Jul 2024 22:41:39 +0100 Subject: [PATCH 80/84] Mixin 0.8.7 RELEASE --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index 9f6eb237e..ea3a3c3a2 100644 --- a/gradle.properties +++ b/gradle.properties @@ -5,7 +5,7 @@ description=Mixin url=https://www.spongepowered.org organization=SpongePowered buildVersion=0.8.7 -buildType=SNAPSHOT +buildType=RELEASE asmVersion=9.5 legacyForgeAsmVersion=5.0.3 modlauncherAsmVersion=9.5 From 60aa12f26ca8c436873f0f3a0ea3515d54ffe3db Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 9 Jul 2024 15:35:12 +0100 Subject: [PATCH 81/84] Revert "Use 0 as class reader flags in Modlauncher bytecode provider (#137)" This reverts commit e779303628af31233ce848687036a954a57110c7. --- .../org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java b/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java index 0840a565b..9cbbce1c4 100644 --- a/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java +++ b/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java @@ -235,7 +235,7 @@ public ClassNode getClassNode(String name, boolean runTransformers) throws Class if (classBytes != null && classBytes.length != 0) { ClassNode classNode = new ClassNode(); ClassReader classReader = new MixinClassReader(classBytes, canonicalName); - classReader.accept(classNode, 0); + classReader.accept(classNode, ClassReader.EXPAND_FRAMES); return classNode; } From b20b9b346520f4809ae7c6e44901730f4f942a55 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 9 Jul 2024 15:35:16 +0100 Subject: [PATCH 82/84] Revert "Fix: Check the staticness of the original call not the current one. (#132)" This reverts commit 8c8ece2737a83e281542717f5205d2ca7ad0db64. --- .../asm/mixin/injection/invoke/util/InvokeUtil.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java index a57279b0e..07ecc48be 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java @@ -38,11 +38,10 @@ public static Type[] getOriginalArgs(InjectionNode node) { } public static Type[] getCurrentArgs(InjectionNode node) { - MethodInsnNode original = (MethodInsnNode) node.getOriginalTarget(); - MethodInsnNode current = (MethodInsnNode) node.getCurrentTarget(); - Type[] currentArgs = Type.getArgumentTypes(current.desc); - if (node.isReplaced() && node.hasDecoration(RedirectInjector.Meta.KEY) && original.getOpcode() != Opcodes.INVOKESTATIC) { - // A redirect on a non-static target method will have an extra arg at the start that we don't care about. + MethodInsnNode methodNode = (MethodInsnNode) node.getCurrentTarget(); + Type[] currentArgs = Type.getArgumentTypes(methodNode.desc); + if (node.isReplaced() && node.hasDecoration(RedirectInjector.Meta.KEY) && methodNode.getOpcode() != Opcodes.INVOKESTATIC) { + // A non-static redirect handler method will have an extra arg at the start that we don't care about. return Arrays.copyOfRange(currentArgs, 1, currentArgs.length); } return currentArgs; From 96b758dedf9c167241cc4557b846b4a047b2c34b Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 9 Jul 2024 15:35:43 +0100 Subject: [PATCH 83/84] Revert "Fix/modifyarg(s) after redirect (#128)" This reverts commit 3eb5281d --- .../injection/invoke/ModifyArgInjector.java | 26 +++++----- .../injection/invoke/ModifyArgsInjector.java | 30 ++++------- .../injection/invoke/RedirectInjector.java | 2 +- .../injection/invoke/util/InvokeUtil.java | 52 ------------------- 4 files changed, 24 insertions(+), 86 deletions(-) delete mode 100644 src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java index 3e147524e..54dc57868 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java @@ -33,7 +33,6 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.ModifyArg; -import org.spongepowered.asm.mixin.injection.invoke.util.InvokeUtil; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.Target; @@ -110,16 +109,15 @@ protected void inject(Target target, InjectionNode node) { @Override protected void injectAtInvoke(Target target, InjectionNode node) { MethodInsnNode methodNode = (MethodInsnNode)node.getCurrentTarget(); - Type[] originalArgs = InvokeUtil.getOriginalArgs(node); - Type[] currentArgs = InvokeUtil.getCurrentArgs(node); - int argIndex = this.findArgIndex(target, originalArgs); + Type[] args = Type.getArgumentTypes(methodNode.desc); + int argIndex = this.findArgIndex(target, args); InsnList insns = new InsnList(); Extension extraLocals = target.extendLocals(); if (this.singleArgMode) { - this.injectSingleArgHandler(target, extraLocals, currentArgs, argIndex, insns); + this.injectSingleArgHandler(target, extraLocals, args, argIndex, insns); } else { - this.injectMultiArgHandler(target, extraLocals, originalArgs, currentArgs, argIndex, insns); + this.injectMultiArgHandler(target, extraLocals, args, argIndex, insns); } target.insns.insertBefore(methodNode, insns); @@ -145,17 +143,17 @@ private void injectSingleArgHandler(Target target, Extension extraLocals, Type[] /** * Inject handler opcodes for a multi arg handler */ - private void injectMultiArgHandler(Target target, Extension extraLocals, Type[] originalArgs, Type[] currentArgs, int argIndex, InsnList insns) { - if (!Arrays.equals(originalArgs, this.methodArgs)) { + private void injectMultiArgHandler(Target target, Extension extraLocals, Type[] args, int argIndex, InsnList insns) { + if (!Arrays.equals(args, this.methodArgs)) { throw new InvalidInjectionException(this.info, "@ModifyArg method " + this + " targets a method with an invalid signature " - + Bytecode.getDescriptor(originalArgs) + ", expected " + Bytecode.getDescriptor(this.methodArgs)); + + Bytecode.getDescriptor(args) + ", expected " + Bytecode.getDescriptor(this.methodArgs)); } - int[] argMap = this.storeArgs(target, currentArgs, insns, 0); - this.pushArgs(currentArgs, insns, argMap, 0, argIndex); - this.invokeHandlerWithArgs(originalArgs, insns, argMap, 0, originalArgs.length); - this.pushArgs(currentArgs, insns, argMap, argIndex + 1, currentArgs.length); - extraLocals.add((argMap[argMap.length - 1] - target.getMaxLocals()) + currentArgs[currentArgs.length - 1].getSize()); + int[] argMap = this.storeArgs(target, args, insns, 0); + this.pushArgs(args, insns, argMap, 0, argIndex); + this.invokeHandlerWithArgs(args, insns, argMap, 0, args.length); + this.pushArgs(args, insns, argMap, argIndex + 1, args.length); + extraLocals.add((argMap[argMap.length - 1] - target.getMaxLocals()) + args[args.length - 1].getSize()); } protected int findArgIndex(Target target, Type[] args) { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java index aad5e56d9..8aaee8a34 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java @@ -33,7 +33,6 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.ModifyArgs; import org.spongepowered.asm.mixin.injection.invoke.arg.ArgsClassGenerator; -import org.spongepowered.asm.mixin.injection.invoke.util.InvokeUtil; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.Target; @@ -41,8 +40,6 @@ import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException; import org.spongepowered.asm.util.Bytecode; -import java.util.Arrays; - /** * A bytecode injector which allows a single argument of a chosen method call to * be altered. For details see javadoc for {@link ModifyArgs @ModifyArgs}. @@ -81,33 +78,28 @@ protected void inject(Target target, InjectionNode node) { @Override protected void injectAtInvoke(Target target, InjectionNode node) { MethodInsnNode targetMethod = (MethodInsnNode)node.getCurrentTarget(); - - Type[] originalArgs = InvokeUtil.getOriginalArgs(node); - Type[] currentArgs = InvokeUtil.getCurrentArgs(node); - if (originalArgs.length == 0) { + + Type[] args = Type.getArgumentTypes(targetMethod.desc); + if (args.length == 0) { throw new InvalidInjectionException(this.info, "@ModifyArgs injector " + this + " targets a method invocation " + targetMethod.name + targetMethod.desc + " with no arguments!"); } - - String originalDesc = Type.getMethodDescriptor(Type.VOID_TYPE, originalArgs); - String clArgs = this.argsClassGenerator.getArgsClass(originalDesc, this.info.getMixin().getMixin()).getName(); + + String clArgs = this.argsClassGenerator.getArgsClass(targetMethod.desc, this.info.getMixin().getMixin()).getName(); boolean withArgs = this.verifyTarget(target); InsnList insns = new InsnList(); Extension extraStack = target.extendStack().add(1); - - Type[] extraArgs = Arrays.copyOfRange(currentArgs, originalArgs.length, currentArgs.length); - int[] extraArgMap = this.storeArgs(target, extraArgs, insns, 0); - this.packArgs(insns, clArgs, originalDesc); - + + this.packArgs(insns, clArgs, targetMethod); + if (withArgs) { extraStack.add(target.arguments); Bytecode.loadArgs(target.arguments, insns, target.isStatic ? 0 : 1); } this.invokeHandler(insns); - this.unpackArgs(insns, clArgs, originalArgs); - this.pushArgs(extraArgs, insns, extraArgMap, 0, extraArgs.length); + this.unpackArgs(insns, clArgs, args); extraStack.apply(); target.insns.insertBefore(targetMethod, insns); @@ -129,8 +121,8 @@ private boolean verifyTarget(Target target) { return false; } - private void packArgs(InsnList insns, String clArgs, String targetDesc) { - String factoryDesc = Bytecode.changeDescriptorReturnType(targetDesc, "L" + clArgs + ";"); + private void packArgs(InsnList insns, String clArgs, MethodInsnNode targetMethod) { + String factoryDesc = Bytecode.changeDescriptorReturnType(targetMethod.desc, "L" + clArgs + ";"); insns.add(new MethodInsnNode(Opcodes.INVOKESTATIC, clArgs, "of", factoryDesc, false)); insns.add(new InsnNode(Opcodes.DUP)); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java index 45d95da72..0851de307 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/RedirectInjector.java @@ -104,7 +104,7 @@ public class RedirectInjector extends InvokeInjector { /** * Meta decoration object for redirector target nodes */ - public class Meta { + class Meta { public static final String KEY = "redirector"; diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java deleted file mode 100644 index 07ecc48be..000000000 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java +++ /dev/null @@ -1,52 +0,0 @@ -/* - * This file is part of Mixin, licensed under the MIT License (MIT). - * - * Copyright (c) SpongePowered - * Copyright (c) contributors - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included in - * all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN - * THE SOFTWARE. - */ -package org.spongepowered.asm.mixin.injection.invoke.util; - -import org.objectweb.asm.Opcodes; -import org.objectweb.asm.Type; -import org.objectweb.asm.tree.MethodInsnNode; -import org.spongepowered.asm.mixin.injection.invoke.RedirectInjector; -import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; - -import java.util.Arrays; - -public final class InvokeUtil { - public static Type[] getOriginalArgs(InjectionNode node) { - return Type.getArgumentTypes(((MethodInsnNode) node.getOriginalTarget()).desc); - } - - public static Type[] getCurrentArgs(InjectionNode node) { - MethodInsnNode methodNode = (MethodInsnNode) node.getCurrentTarget(); - Type[] currentArgs = Type.getArgumentTypes(methodNode.desc); - if (node.isReplaced() && node.hasDecoration(RedirectInjector.Meta.KEY) && methodNode.getOpcode() != Opcodes.INVOKESTATIC) { - // A non-static redirect handler method will have an extra arg at the start that we don't care about. - return Arrays.copyOfRange(currentArgs, 1, currentArgs.length); - } - return currentArgs; - } - - private InvokeUtil() { - } -} From 7f00ee348507648963727b8d8e70bcdd3aee8514 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 9 Jul 2024 15:40:16 +0100 Subject: [PATCH 84/84] Build: Bump version. --- gradle.properties | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle.properties b/gradle.properties index ed1249ec9..f1e4b6e39 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ packaging=jar description=Mixin (Fabric fork) url=https://fabricmc.net organization=FabricMC -buildVersion=0.14.0 +buildVersion=0.15.0 upstreamMixinVersion=0.8.7 buildType=RELEASE asmVersion=9.6