diff --git a/gradle.properties b/gradle.properties index 5978aff3e..f1e4b6e39 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,8 +4,8 @@ packaging=jar description=Mixin (Fabric fork) url=https://fabricmc.net organization=FabricMC -buildVersion=0.14.0 -upstreamMixinVersion=0.8.6 +buildVersion=0.15.0 +upstreamMixinVersion=0.8.7 buildType=RELEASE asmVersion=9.6 legacyForgeAsmVersion=5.0.3 diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java b/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java index 850b368f3..ec1ad5544 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/ReferenceManager.java @@ -27,8 +27,10 @@ import java.io.File; import java.io.IOException; import java.io.PrintWriter; +import java.net.URI; import java.util.List; +import javax.annotation.processing.Filer; import javax.tools.FileObject; import javax.tools.StandardLocation; @@ -129,7 +131,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 +158,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) 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 d263db97e..965da9f51 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/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/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 diff --git a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java index 1474e0965..3c9633971 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 @@ -460,6 +474,11 @@ private enum Inherit { */ 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, isFlag, 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; @@ -754,42 +806,42 @@ boolean isSupported() { } }, - + /** * Java 19 or above is required */ JAVA_19(19, Opcodes.V19, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { - + @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_19 && ASM.isAtLeastVersion(9, 3); } - + }, - + /** * Java 20 or above is required */ JAVA_20(20, Opcodes.V20, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { - + @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_20 && ASM.isAtLeastVersion(9, 4); } - + }, - + /** * Java 21 or above is required */ JAVA_21(21, Opcodes.V21, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { - + @Override boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_21 && ASM.isAtLeastVersion(9, 5); @@ -980,7 +1032,7 @@ public static CompatibilityLevel requiredFor(int languageFeatures) { } return null; } - + /** * Return the maximum compatibility level which is actually effective in * the current runtime, taking into account the current JRE and ASM @@ -1039,30 +1091,30 @@ static String getSupportedVersions() { * features added in version 0.8.6 and higher. */ public static enum Feature { - + /** * Supports the unsafe flag on @At annotations to * facilitate hassle-free constructor injections. */ UNSAFE_INJECTION(true), - + /** - * Support for the use of injector annotations in interface mixins + * Support for the use of injector annotations in interface mixins */ - INJECTORS_IN_INTERFACE_MIXINS(false) { - + INJECTORS_IN_INTERFACE_MIXINS { + @Override public boolean isAvailable() { return CompatibilityLevel.getMaxEffective().supports(LanguageFeatures.METHODS_IN_INTERFACES); } - + @Override public boolean isEnabled() { return MixinEnvironment.getCompatibilityLevel().supports(LanguageFeatures.METHODS_IN_INTERFACES); } }; - + /** * Existence of the enum constant does not necessarily indicate that the * feature is actually supported by this version, for example if @@ -1072,10 +1124,14 @@ public boolean isEnabled() { */ private boolean enabled; + private Feature() { + this(false); + } + private Feature(boolean enabled) { this.enabled = enabled; } - + /** * Get whether this feature is available in the current runtime * environment @@ -1083,7 +1139,7 @@ private Feature(boolean enabled) { public boolean isAvailable() { return true; } - + /** * Get whether this feature is supported in the current environment and * compatibility level @@ -1091,11 +1147,11 @@ public boolean isAvailable() { public boolean isEnabled() { return this.isAvailable() && this.enabled; } - + /** * Convenience function which returns a Feature constant based on the * feature id, but returns null instead of throwing an exception. - * + * * @param featureId Feature ID (enum constant name) to check for * @return Feature or null */ @@ -1113,17 +1169,17 @@ public static Feature get(String featureId) { /** * Check whether a particular feature exists in this mixin version, even * if it's not currently available - * + * * @param featureId Feature ID (enum constant name) to check for * @return true if the feature exists */ public static boolean exists(String featureId) { return Feature.get(featureId) != null; } - + /** * Check whether a particular feature is available and enabled - * + * * @param featureId Feature ID (enum constant name) to check for * @return true if the feature is currently available */ @@ -1131,9 +1187,9 @@ public static boolean isActive(String featureId) { Feature feature = Feature.get(featureId); return feature != null && feature.isEnabled(); } - + } - + /** * Wrapper for providing a natural sorting order for providers */ @@ -1315,6 +1371,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/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/callback/CallbackInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java index 9494ee9f5..1a0089432 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 @@ -186,7 +186,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) { @@ -635,8 +635,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"); 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 3e147524e..c718f1608 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,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.invoke.util.InvokeUtil; +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,16 +110,31 @@ 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); + ArgOffsets offsets = node.getDecoration(ArgOffsets.KEY, ArgOffsets.DEFAULT); + 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(); + 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, nested); } else { - this.injectMultiArgHandler(target, extraLocals, originalArgs, currentArgs, argIndex, insns); + if (!Arrays.equals(originalArgs, this.methodArgs)) { + throw new InvalidInjectionException(this.info, "@ModifyArg injector " + 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 +150,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,17 +161,15 @@ 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)) { - throw new InvalidInjectionException(this.info, "@ModifyArg method " + this + " targets a method with an invalid signature " - + Bytecode.getDescriptor(originalArgs) + ", 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()); + 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()); } 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..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 @@ -33,7 +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.invoke.util.InvokeUtil; +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; @@ -41,8 +41,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}. @@ -80,26 +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); + 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 (originalArgs.length == 0) { throw new InvalidInjectionException(this.info, "@ModifyArgs injector " + this + " targets a method invocation " - + targetMethod.name + targetMethod.desc + " with no arguments!"); + + ((MethodInsnNode)node.getOriginalTarget()).name + targetMethodDesc + " 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(targetMethodDesc, 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); - + + int[] afterWindowArgMap = this.storeArgs(target, args, insns, endIndex); + this.packArgs(insns, clArgs, targetMethodDesc); + if (withArgs) { extraStack.add(target.arguments); Bytecode.loadArgs(target.arguments, insns, target.isStatic ? 0 : 1); @@ -107,10 +107,10 @@ protected void injectAtInvoke(Target target, InjectionNode node) { this.invokeHandler(insns); this.unpackArgs(insns, clArgs, originalArgs); - this.pushArgs(extraArgs, insns, extraArgMap, 0, extraArgs.length); - + 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) { @@ -129,8 +129,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, 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 45d95da72..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 @@ -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; @@ -104,7 +105,7 @@ public class RedirectInjector extends InvokeInjector { /** * Meta decoration object for redirector target nodes */ - public class Meta { + class Meta { public static final String KEY = "redirector"; @@ -159,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; @@ -166,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); } @@ -267,7 +270,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) { @@ -358,7 +361,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); @@ -392,6 +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(invoke.isStatic ? 0 : 1, invoke.targetArgs.length); if (invoke.captureTargetArgs > 0) { int argSize = Bytecode.getArgsSize(target.arguments, 0, invoke.captureTargetArgs); extraLocals.add(argSize); @@ -404,6 +408,7 @@ protected void injectAtInvoke(Target target, InjectionNode node) { insns.add(new TypeInsnNode(Opcodes.CHECKCAST, invoke.returnType.getInternalName())); } 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/modify/LocalVariableDiscriminator.java b/src/main/java/org/spongepowered/asm/mixin/injection/modify/LocalVariableDiscriminator.java index 42376f7ee..676665b31 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/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/ArgOffsets.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java new file mode 100644 index 000000000..2483cb02f --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ArgOffsets.java @@ -0,0 +1,195 @@ +/* + * 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 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 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 (original) args within the new args + */ + private final int offset; + + /** + * The total number of (original) args + */ + private final int length; + + /** + * 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 offset start index + * @param length length + */ + public ArgOffsets(int offset, int length) { + 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 + * #replace( + * org.spongepowered.asm.mixin.injection.struct.IChainedDecoration) + */ + @Override + public void replace(ArgOffsets old) { + this.next = old; + } + + /** + * 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) + * + * @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.isEmpty() ? this.getStartIndex() : this.getArgIndex(this.length - 1); + } + + /** + * 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) { + 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; + } + + /** + * 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.length]; + for (int i = 0; i < this.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/CallbackInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/CallbackInjectionInfo.java index c1875ba4e..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 @@ -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.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/invoke/util/InvokeUtil.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/IChainedDecoration.java similarity index 50% rename from src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java rename to src/main/java/org/spongepowered/asm/mixin/injection/struct/IChainedDecoration.java index a57279b0e..41b779e6c 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/IChainedDecoration.java @@ -22,32 +22,22 @@ * 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; +package org.spongepowered.asm.mixin.injection.struct; -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 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. - return Arrays.copyOfRange(currentArgs, 1, currentArgs.length); - } - return currentArgs; - } +/** + * 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); - private InvokeUtil() { - } } 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..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 @@ -108,12 +108,63 @@ 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 except for redirectors all run at DEFAULT unless specified in + * the injector annotation. + * + *

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 { + + /** + * An early injector, run before most injectors + */ + public static final int EARLY = 0; + + /** + * Default order, all injectors except redirect run here unless manually + * adjusted + */ + public static final int DEFAULT = 1000; + + /** + * Late injector, runs after most injectors but before redirects + */ + public static final int LATE = 2000; + + /** + * Built-in order for Redirect injectors + */ + public static final int REDIRECT = 10000; + + /** + * Injector which should run after redirect injector + */ + public static final int AFTER_REDIRECT = 20000; + + /** + * String prefix for conforming handler methods + */ + public int value() default InjectorOrder.DEFAULT; + + } /** * An injector registration entry @@ -137,7 +188,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 +207,6 @@ InjectionInfo create(MixinTargetContext mixin, MethodNode method, AnnotationNode } } - /** - * Default conform prefix for handler methods - */ - public static final String DEFAULT_PREFIX = "handler"; - /** * Registry of subclasses */ @@ -268,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 = InjectorOrder.DEFAULT; + /** * ctor * @@ -307,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"); @@ -355,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(); @@ -387,6 +452,13 @@ public boolean isValid() { return this.targets.size() > 0 && this.injectionPoints.size() > 0; } + /** + * Get the application order for this injector type + */ + public int getOrder() { + return this.order; + } + /** * Discover injection points */ @@ -624,7 +696,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 +705,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/InjectionNodes.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionNodes.java index 86bacb2f0..44bcb0ee8 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/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/mixin/injection/struct/ModifyArgInjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/ModifyArgInjectionInfo.java index 9446e9805..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 @@ -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.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 52ff5573e..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 @@ -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.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 7a0b16e8f..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 @@ -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.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 cefb00ae2..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 @@ -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.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 91ee7cd8d..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 @@ -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.REDIRECT) public class RedirectInjectionInfo extends InjectionInfo { public RedirectInjectionInfo(MixinTargetContext mixin, MethodNode method, AnnotationNode annotation) { 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 020e800df..912b833e5 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 @@ -441,6 +441,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(); } @@ -448,7 +464,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; 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 c718286a0..66fa2a278 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; @@ -2023,7 +2025,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/mixin/transformer/MixinApplicatorInterface.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java index 638934095..99aec8db3 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java @@ -143,13 +143,26 @@ protected void prepareInjections(MixinTargetContext mixin) { /* (non-Javadoc) * @see org.spongepowered.asm.mixin.transformer.MixinApplicator - * #applyInjections( + * #applyPreInjections( * org.spongepowered.asm.mixin.transformer.MixinTargetContext) */ @Override - protected void applyInjections(MixinTargetContext mixin) { + 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( + * org.spongepowered.asm.mixin.transformer.MixinTargetContext, int) + */ + @Override + 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 2bf01716c..2e61a4c43 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorStandard.java @@ -72,6 +72,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,14 +105,29 @@ 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 } + + /** + * Order collection to use for all passes except ApplicatorPass.INJECT + */ + protected static final Set ORDERS_NONE = ImmutableSet.of(Integer.valueOf(0)); /** * Log more things @@ -213,17 +229,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_APPLY) { + 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(); @@ -263,7 +290,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: @@ -283,16 +310,24 @@ protected final void applyMixin(MixinTargetContext mixin, ApplicatorPass pass) { this.applyInitialisers(mixin); break; - case PREINJECT: + case INJECT_PREPARE: activity.next("Prepare Injections"); this.prepareInjections(mixin); break; - case INJECT: + case ACCESSOR: activity.next("Apply Accessors"); this.applyAccessors(mixin); + break; + + case INJECT_PREINJECT: + activity.next("Apply Injections"); + this.applyPreInjections(mixin); + break; + + case INJECT_APPLY: activity.next("Apply Injections"); - this.applyInjections(mixin); + this.applyInjections(mixin, injectorOrder); break; default: @@ -696,13 +731,25 @@ 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 * * @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/MixinInfo.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinInfo.java index 0a445e8c7..980bcd26a 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/mixin/transformer/MixinTargetContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java index 996621fdf..a97fc0b78 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MixinTargetContext.java @@ -1427,9 +1427,20 @@ InjectionInfo getFirstInjectionInfo() { } /** - * Apply injectors discovered in the {@link #prepareInjections()} pass + * 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()); + } + } + + /** + * Run the preinject step for all discovered injectors */ - void applyInjections() { + void applyPreInjections() { this.activities.clear(); try { @@ -1439,23 +1450,48 @@ void applyInjections() { 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); + } + } - applyActivity.next("Inject"); + /** + * Apply injectors discovered in the {@link #prepareInjections()} pass + * + * @param injectorOrder injector order for this pass + */ + 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("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; 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/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); } } 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 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); } } diff --git a/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java b/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java index 0840a565b..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, 0); + classReader.accept(classNode, readerFlags); return classNode; } 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/modlauncher/resources/shims/mixin_synthetic.zip b/src/modlauncher/resources/shims/mixin_synthetic.zip deleted file mode 100644 index 25fa34826..000000000 Binary files a/src/modlauncher/resources/shims/mixin_synthetic.zip and /dev/null differ diff --git a/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java b/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java index 0fe1c2023..4f2e13ec2 100644 --- a/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java +++ b/src/modlauncher9/java/org/spongepowered/asm/launch/MixinTransformationService.java @@ -25,18 +25,20 @@ package org.spongepowered.asm.launch; import java.net.URISyntaxException; -import java.net.URL; import java.nio.file.Path; -import java.nio.file.Paths; import java.util.List; +import java.util.Set; import org.spongepowered.asm.mixin.injection.invoke.arg.ArgsClassGenerator; import org.spongepowered.asm.service.MixinService; import org.spongepowered.asm.util.Constants; import com.google.common.collect.ImmutableList; +import com.google.common.collect.ImmutableSet; +import cpw.mods.jarhandling.JarMetadata; import cpw.mods.jarhandling.SecureJar; +import cpw.mods.jarhandling.SecureJar.Provider; import cpw.mods.jarhandling.VirtualJar; import cpw.mods.modlauncher.api.IModuleLayerManager; @@ -53,16 +55,18 @@ public class MixinTransformationService extends MixinTransformationServiceAbstra @Override public List 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 { + 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)); }