From a95dcd9af498e153b630b9458426d037ba7e34c3 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Tue, 14 May 2024 20:34:52 +0100 Subject: [PATCH 01/26] 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 02/26] 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 03/26] 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 04/26] 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 05/26] 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 06/26] 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 07/26] 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 08/26] 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 09/26] 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 02bf3d22400ce3ce094f24677028462d53674676 Mon Sep 17 00:00:00 2001 From: LlamaLad7 Date: Tue, 21 May 2024 22:51:14 +0100 Subject: [PATCH 10/26] 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 11/26] 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 2d14c78a1065601e66ea0f93612f8704a3e0f0aa Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Fri, 7 Jun 2024 21:30:03 +0100 Subject: [PATCH 12/26] 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 13/26] 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 8f0bb79ae5d1757a0aafe80d32ba0625b81a2e11 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Thu, 20 Jun 2024 23:54:19 +0100 Subject: [PATCH 14/26] 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 15/26] 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 16/26] 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 96e81bb78031a2406173fbfe6720e1971de7e420 Mon Sep 17 00:00:00 2001 From: Mumfrey Date: Thu, 4 Jul 2024 22:35:11 +0100 Subject: [PATCH 17/26] 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 18/26] 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 19/26] 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 20/26] 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 21/26] 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 22/26] 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 23/26] 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 24/26] 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 25/26] 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 26/26] 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