diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 48aacde37..319e54748 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -13,4 +13,9 @@ jobs: env: MAVEN_URL: ${{ secrets.MAVEN_URL }} MAVEN_USERNAME: ${{ secrets.MAVEN_USERNAME }} - MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} \ No newline at end of file + MAVEN_PASSWORD: ${{ secrets.MAVEN_PASSWORD }} + MAVEN_CENTRAL_URL: ${{ secrets.MAVEN_CENTRAL_URL }} + MAVEN_CENTRAL_USERNAME: ${{ secrets.MAVEN_CENTRAL_USERNAME }} + MAVEN_CENTRAL_PASSWORD: ${{ secrets.MAVEN_CENTRAL_PASSWORD }} + SIGNING_SERVER: ${{ secrets.SIGNING_SERVER }} + SIGNING_PGP_KEY: ${{ secrets.SIGNING_PGP_KEY }} \ No newline at end of file diff --git a/build.gradle b/build.gradle index a7de95530..cd7995fdb 100644 --- a/build.gradle +++ b/build.gradle @@ -143,6 +143,7 @@ sourceSets { ext.languageVersion = langVer ext.modularityExcluded = true // don't add ourselves } + modularityDummy {} } // Because Mixin aims to support a variety of environments, we have to be able to run with older versions of GSON and Guava that lack official module @@ -160,6 +161,9 @@ dependencies { def log4j2 = 'org.apache.logging.log4j:log4j-core:2.11.2' def gson = 'com.google.code.gson:gson:2.2.4' +// stagingJar guava +// stagingJar gson + implementation guava implementation log4j2 implementation gson @@ -348,7 +352,7 @@ if (JavaVersion.current().isJava8Compatible()) { } task stagingJar(type: ShadowJar) { - sourceSets.findAll { !(it.name =~ /example|test/) }.each { + sourceSets.findAll { !(it.name =~ /example|test|modularityDummy/) }.each { from it.output } configurations = [project.configurations.stagingJar] @@ -370,7 +374,7 @@ task stagingJar(type: ShadowJar) { if (project.doSignJar) { archiveClassifier.set('unsigned') } - + mergeServiceFiles() } @@ -385,7 +389,7 @@ shadowJar { build.dependsOn(shadowJar) if (project.doSignJar) { - // Define signjar task + // Define signjar task task signJar() { inputs.files(stagingJar.outputs) outputs.files stagingJar.outputs.files.collect { @@ -434,7 +438,7 @@ task javadocJar(type: Jar, dependsOn: javadoc) { artifacts { if (project.doSignJar) { archives signJar.outputs.files[0] - } else { + } else { archives stagingJar } archives sourceJar diff --git a/gradle.properties b/gradle.properties index bf1534d3f..fd6d3741e 100644 --- a/gradle.properties +++ b/gradle.properties @@ -4,7 +4,7 @@ packaging=jar description=Mixin url=https://www.spongepowered.org organization=SpongePowered -buildVersion=0.8.5+lunar.3 +buildVersion=0.8.5+lunar.4 buildType=RELEASE asmVersion=9.6 legacyForgeAsmVersion=5.0.3 diff --git a/settings.gradle b/settings.gradle index 89f411069..5ba47c529 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1,11 @@ +pluginManagement { + repositories { + gradlePluginPortal() + maven { + name = 'Fabric' + url = 'https://maven.fabricmc.net/' + } + } +} + rootProject.name = name diff --git a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java index 90c52ecfe..e22696973 100644 --- a/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java +++ b/src/main/java/org/spongepowered/asm/mixin/MixinEnvironment.java @@ -753,7 +753,64 @@ boolean isSupported() { return JavaVersion.current() >= JavaVersion.JAVA_18 && ASM.isAtLeastVersion(9, 2); } - }; + }, + + /** + * Java 19 or above is required + */ + JAVA_19(19, Opcodes.V19, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES + | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS + | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { + + @Override + boolean isSupported() { + return JavaVersion.current() >= JavaVersion.JAVA_19 && ASM.isAtLeastVersion(9, 3); + } + + }, + + /** + * Java 20 or above is required + */ + JAVA_20(20, Opcodes.V20, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES + | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS + | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { + + @Override + boolean isSupported() { + return JavaVersion.current() >= JavaVersion.JAVA_20 && ASM.isAtLeastVersion(9, 4); + } + + }, + + /** + * Java 21 or above is required + */ + JAVA_21(21, Opcodes.V21, LanguageFeatures.METHODS_IN_INTERFACES | LanguageFeatures.PRIVATE_SYNTHETIC_METHODS_IN_INTERFACES + | LanguageFeatures.PRIVATE_METHODS_IN_INTERFACES | LanguageFeatures.NESTING | LanguageFeatures.DYNAMIC_CONSTANTS + | LanguageFeatures.RECORDS | LanguageFeatures.SEALED_CLASSES) { + + @Override + boolean isSupported() { + return JavaVersion.current() >= JavaVersion.JAVA_21 && ASM.isAtLeastVersion(9, 5); + } + + }, + + /** + * Java 22 or above is required + */ + JAVA_22(22, Opcodes.V22, 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_22 && ASM.isAtLeastVersion(9, 6); + } + + }, + ; /** * Default compatibility level to use if not specified by the service diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java index 00d5abb23..fb3bd5439 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/InjectionPoint.java @@ -162,15 +162,19 @@ public abstract class InjectionPoint { } /** - * Selector type for slice delmiters, ignored for normal injection points. - * Selectors can be supplied in {@link At} annotations by including - * a colon (:) character followed by the selector type - * (case-sensitive), eg: + * Additional specifier for injection points. Specifiers can be + * supplied in {@link At} annotations by including a colon (:) + * character followed by the specifier type (case-sensitive), eg: * *
@At(value = "INVOKE:LAST", ... )
*/ - public enum Selector { - + public enum Specifier { + + /** + * Use all instructions from the query result. + */ + ALL, + /** * Use the first instruction from the query result. */ @@ -186,13 +190,13 @@ public enum Selector { * more than one instruction this should be considered a fail-fast error * state and a runtime exception will be thrown. */ - ONE; + ONE, /** - * Default selector type used if no selector is explicitly specified. - * For internal use only. Currently {@link #FIRST} + * Use the default setting as defined by the consumer. For slices this + * is {@link #FIRST}, for all other consumers this is {@link #ALL} */ - public static final Selector DEFAULT = Selector.FIRST; + DEFAULT; } @@ -276,26 +280,26 @@ enum ShiftByViolationBehaviour { } private final String slice; - private final Selector selector; + private final Specifier specifier; private final String id; private final IMessageSink messageSink; protected InjectionPoint() { - this("", Selector.DEFAULT, null); + this("", Specifier.DEFAULT, null); } protected InjectionPoint(InjectionPointData data) { - this(data.getSlice(), data.getSelector(), data.getId(), data.getMessageSink()); + this(data.getSlice(), data.getSpecifier(), data.getId(), data.getMessageSink()); } - public InjectionPoint(String slice, Selector selector, String id) { - this(slice, selector, id, null); + public InjectionPoint(String slice, Specifier specifier, String id) { + this(slice, specifier, id, null); } - public InjectionPoint(String slice, Selector selector, String id, IMessageSink messageSink) { + public InjectionPoint(String slice, Specifier specifier, String id, IMessageSink messageSink) { this.slice = slice; - this.selector = selector; + this.specifier = specifier; this.id = id; this.messageSink = messageSink; } @@ -304,8 +308,8 @@ public String getSlice() { return this.slice; } - public Selector getSelector() { - return this.selector; + public Specifier getSpecifier(Specifier defaultSpecifier) { + return this.specifier == Specifier.DEFAULT ? defaultSpecifier : this.specifier; } public String getId() { diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java b/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java index 857432dbf..68689107d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/Slice.java @@ -28,7 +28,7 @@ import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; -import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; /** * A Slice identifies a section of a method to search for injection @@ -162,10 +162,10 @@ /** * Injection point which specifies the start of the slice region. - * {@link At}s supplied here should generally specify a {@link Selector} + * {@link At}s supplied here should generally use a {@link Specifier} * in order to identify which instruction should be used for queries which - * return multiple results. The selector is specified by appending the - * selector type to the injection point type as follows: + * return multiple results. The specifier is supplied by appending the + * specifier type to the injection point type as follows: * *
@At(value = "INVOKE:LAST", ... )
* @@ -182,9 +182,9 @@ /** * Injection point which specifies the end of the slice region. * Like {@link #from}, {@link At}s supplied here should generally specify a - * {@link Selector} in order to identify which instruction should be used - * for queries which return multiple results. The selector is specified by - * appending the selector type to the injection point type as follows: + * {@link Specifier} in order to identify which instruction should be used + * for queries which return multiple results. The specifier is supplied by + * appending the specifier type to the injection point type as follows: * *
@At(value = "INVOKE:LAST", ... )
* diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/callback/CallbackInjector.java index 75279ab14..6542992ef 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 @@ -212,10 +212,7 @@ private class Callback extends InsnList { this.invoke = target.extendStack(); this.ctor = target.extendStack(); - this.invoke.add(target.arguments.length); - if (this.canCaptureLocals) { - this.invoke.add(this.localTypes.length - this.frameSize); - } + this.invoke.add().add(handlerArgs); //If the handler doesn't captureArgs, the CallbackInfo(Returnable) will be the first LVT slot, otherwise it will be at the target's frameSize int callbackInfoSlot = handlerArgs.length == 1 ? Bytecode.isStatic(handler) ? 0 : 1 : frameSize; diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java index c37a3d563..b7b55ff3e 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java @@ -42,6 +42,7 @@ import org.spongepowered.asm.mixin.injection.Coerce; import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; import org.spongepowered.asm.mixin.injection.invoke.RedirectInjector; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; @@ -294,7 +295,7 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li IMixinContext mixin = this.info.getMixin(); MethodNode method = injectorTarget.getMethod(); Map targetNodes = new TreeMap(); - Collection nodes = new ArrayList(32); + List nodes = new ArrayList(32); for (InjectionPoint injectionPoint : injectionPoints) { nodes.clear(); @@ -307,15 +308,20 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li injectorTarget, injectorTarget.getMergedBy(), injectorTarget.getMergedPriority())); } - if (this.findTargetNodes(method, injectionPoint, injectorTarget, nodes)) { + if (!this.findTargetNodes(method, injectionPoint, injectorTarget, nodes)) { + continue; + } + + Specifier specifier = injectionPoint.getSpecifier(Specifier.ALL); + if (specifier == Specifier.ONE && nodes.size() != 1) { + throw new InvalidInjectionException(this.info, String.format("%s on %s has specifier :ONE but matched %d instructions", + injectionPoint, this, nodes.size())); + } else if (specifier != Specifier.ALL && nodes.size() > 1) { + AbstractInsnNode specified = nodes.get(specifier == Specifier.FIRST ? 0 : nodes.size() - 1); + this.addTargetNode(method, targetNodes, injectionPoint, specified); + } else { for (AbstractInsnNode insn : nodes) { - Integer key = method.instructions.indexOf(insn); - TargetNode targetNode = targetNodes.get(key); - if (targetNode == null) { - targetNode = new TargetNode(insn); - targetNodes.put(key, targetNode); - } - targetNode.nominators.add(injectionPoint); + this.addTargetNode(method, targetNodes, injectionPoint, insn); } } } @@ -323,6 +329,16 @@ private Collection findTargetNodes(InjectorTarget injectorTarget, Li return targetNodes.values(); } + protected void addTargetNode(MethodNode method, Map targetNodes, InjectionPoint injectionPoint, AbstractInsnNode insn) { + Integer key = method.instructions.indexOf(insn); + TargetNode targetNode = targetNodes.get(key); + if (targetNode == null) { + targetNode = new TargetNode(insn); + targetNodes.put(key, targetNode); + } + targetNode.nominators.add(injectionPoint); + } + protected boolean findTargetNodes(MethodNode into, InjectionPoint injectionPoint, InjectorTarget injectorTarget, Collection nodes) { return injectionPoint.find(into.desc, injectorTarget.getSlice(injectionPoint), nodes); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java index 79891e95f..58201c05b 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlice.java @@ -37,7 +37,7 @@ import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.InjectionPoint; -import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; import org.spongepowered.asm.mixin.injection.Slice; import org.spongepowered.asm.mixin.injection.struct.InjectionPointAnnotationContext; import org.spongepowered.asm.mixin.injection.throwables.InjectionError; @@ -325,6 +325,11 @@ public int realIndexOf(AbstractInsnNode insn) { * Descriptive name of the slice, used in exceptions */ private final String name; + + /** + * Success counts for from and to injection points + */ + private int successCountFrom, successCountTo; /** * ctor @@ -361,8 +366,8 @@ public String getId() { */ public InsnListReadOnly getSlice(MethodNode method) { int max = method.instructions.size() - 1; - int start = this.find(method, this.from, 0, 0, this.name + "(from)"); - int end = this.find(method, this.to, max, start, this.name + "(to)"); + int start = this.find(method, this.from, 0, 0, "from"); + int end = this.find(method, this.to, max, start, "to"); if (start > end) { throw new InvalidSliceException(this.owner, String.format("%s is negative size. Range(%d -> %d)", this.describe(), start, end)); @@ -389,32 +394,53 @@ public InsnListReadOnly getSlice(MethodNode method) { * @param defaultValue Value to return if injection point is null (open * ended) * @param failValue Value to use if query fails - * @param description Description for error message + * @param argument The name of the argument ("from" or "to") for debug msgs * @return matching insn index */ - private int find(MethodNode method, InjectionPoint injectionPoint, int defaultValue, int failValue, String description) { + private int find(MethodNode method, InjectionPoint injectionPoint, int defaultValue, int failValue, String argument) { if (injectionPoint == null) { return defaultValue; } + String description = String.format("%s(%s)", this.name, argument); Deque nodes = new LinkedList(); InsnListReadOnly insns = new InsnListReadOnly(method.instructions); boolean result = injectionPoint.find(method.desc, insns, nodes); - Selector select = injectionPoint.getSelector(); - if (nodes.size() != 1 && select == Selector.ONE) { + Specifier specifier = injectionPoint.getSpecifier(Specifier.FIRST); + if (specifier == Specifier.ALL) { + throw new InvalidSliceException(this.owner, String.format("ALL is not a valid specifier for slice %s", this.describe(description))); + } + if (nodes.size() != 1 && specifier == Specifier.ONE) { throw new InvalidSliceException(this.owner, String.format("%s requires 1 result but found %d", this.describe(description), nodes.size())); } if (!result) { - if (this.owner.getMixin().getOption(Option.DEBUG_VERBOSE)) { - MethodSlice.logger.warn("{} did not match any instructions", this.describe(description)); - } return failValue; } - return method.instructions.indexOf(select == Selector.FIRST ? nodes.getFirst() : nodes.getLast()); + if ("from".equals(argument)) { + this.successCountFrom++; + } else { + this.successCountTo++; + } + + return method.instructions.indexOf(specifier == Specifier.FIRST ? nodes.getFirst() : nodes.getLast()); } - + + /** + * Perform post-injection debugging and validation tasks + */ + public void postInject() { + if (this.owner.getMixin().getOption(Option.DEBUG_VERBOSE)) { + if (this.from != null && this.successCountFrom == 0) { + MethodSlice.logger.warn("{} did not match any instructions", this.describe(this.name + "(from)")); + } + if (this.to != null && this.successCountTo == 0) { + MethodSlice.logger.warn("{} did not match any instructions", this.describe(this.name + "(to)")); + } + } + } + /* (non-Javadoc) * @see java.lang.Object#toString() */ diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java index fb9979aa1..0789b7620 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/code/MethodSlices.java @@ -82,6 +82,16 @@ public MethodSlice get(String id) { return this.slices.get(id); } + /** + * Called to do post-injection validation/debug logging for slices + */ + public void postInject() { + for (MethodSlice slice : this.slices.values()) { + slice.postInject(); + } + } + + /* (non-Javadoc) * @see java.lang.Object#toString() */ diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java index 30a29ceef..dd1639e1f 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgInjector.java @@ -33,6 +33,7 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint; import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.ModifyArg; +import org.spongepowered.asm.mixin.injection.invoke.util.InvokeUtil; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.Target; @@ -109,15 +110,16 @@ protected void inject(Target target, InjectionNode node) { @Override protected void injectAtInvoke(Target target, InjectionNode node) { MethodInsnNode methodNode = (MethodInsnNode)node.getCurrentTarget(); - Type[] args = Type.getArgumentTypes(methodNode.desc); - int argIndex = this.findArgIndex(target, args); + Type[] originalArgs = InvokeUtil.getOriginalArgs(node); + Type[] currentArgs = InvokeUtil.getCurrentArgs(node); + int argIndex = this.findArgIndex(target, originalArgs); InsnList insns = new InsnList(); Extension extraLocals = target.extendLocals(); if (this.singleArgMode) { - this.injectSingleArgHandler(target, extraLocals, args, argIndex, insns); + this.injectSingleArgHandler(target, extraLocals, currentArgs, argIndex, insns); } else { - this.injectMultiArgHandler(target, extraLocals, args, argIndex, insns); + this.injectMultiArgHandler(target, extraLocals, originalArgs, currentArgs, argIndex, insns); } target.insns.insertBefore(methodNode, insns); @@ -138,17 +140,17 @@ private void injectSingleArgHandler(Target target, Extension extraLocals, Type[] /** * Inject handler opcodes for a multi arg handler */ - private void injectMultiArgHandler(Target target, Extension extraLocals, Type[] args, int argIndex, InsnList insns) { - if (!Arrays.equals(args, this.methodArgs)) { + 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(args) + ", expected " + Bytecode.getDescriptor(this.methodArgs)); + + Bytecode.getDescriptor(originalArgs) + ", expected " + Bytecode.getDescriptor(this.methodArgs)); } - int[] argMap = this.storeArgs(target, args, insns, 0); - this.pushArgs(args, insns, argMap, 0, argIndex); - this.invokeHandlerWithArgs(args, insns, argMap, 0, args.length); - this.pushArgs(args, insns, argMap, argIndex + 1, args.length); - extraLocals.add((argMap[argMap.length - 1] - target.getMaxLocals()) + args[args.length - 1].getSize()); + 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()); } 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 051831a0d..f67aad0f1 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/ModifyArgsInjector.java @@ -33,6 +33,7 @@ import org.spongepowered.asm.mixin.injection.InjectionPoint.RestrictTargetLevel; import org.spongepowered.asm.mixin.injection.ModifyArgs; import org.spongepowered.asm.mixin.injection.invoke.arg.ArgsClassGenerator; +import org.spongepowered.asm.mixin.injection.invoke.util.InvokeUtil; import org.spongepowered.asm.mixin.injection.struct.InjectionInfo; import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; import org.spongepowered.asm.mixin.injection.struct.Target; @@ -40,6 +41,8 @@ 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}. @@ -78,28 +81,33 @@ protected void inject(Target target, InjectionNode node) { @Override protected void injectAtInvoke(Target target, InjectionNode node) { MethodInsnNode targetMethod = (MethodInsnNode)node.getCurrentTarget(); - - Type[] args = Type.getArgumentTypes(targetMethod.desc); - if (args.length == 0) { + + Type[] originalArgs = InvokeUtil.getOriginalArgs(node); + Type[] currentArgs = InvokeUtil.getCurrentArgs(node); + if (originalArgs.length == 0) { throw new InvalidInjectionException(this.info, "@ModifyArgs injector " + this + " targets a method invocation " + targetMethod.name + targetMethod.desc + " with no arguments!"); } - - String clArgs = this.argsClassGenerator.getArgsClass(targetMethod.desc, this.info.getMixin().getMixin()).getName(); + + String originalDesc = Type.getMethodDescriptor(Type.VOID_TYPE, originalArgs); + String clArgs = this.argsClassGenerator.getArgsClass(originalDesc, this.info.getMixin().getMixin()).getName(); boolean withArgs = this.verifyTarget(target); InsnList insns = new InsnList(); Extension extraStack = target.extendStack().add(1); - - this.packArgs(insns, clArgs, targetMethod); - + + Type[] extraArgs = Arrays.copyOfRange(currentArgs, originalArgs.length, currentArgs.length); + int[] extraArgMap = this.storeArgs(target, extraArgs, insns, 0); + this.packArgs(insns, clArgs, originalDesc); + if (withArgs) { extraStack.add(target.arguments); Bytecode.loadArgs(target.arguments, insns, target.isStatic ? 0 : 1); } this.invokeHandler(insns); - this.unpackArgs(insns, clArgs, args); + this.unpackArgs(insns, clArgs, originalArgs); + this.pushArgs(extraArgs, insns, extraArgMap, 0, extraArgs.length); extraStack.apply(); target.insns.insertBefore(targetMethod, insns); @@ -121,8 +129,8 @@ private boolean verifyTarget(Target target) { return false; } - private void packArgs(InsnList insns, String clArgs, MethodInsnNode targetMethod) { - String factoryDesc = Bytecode.changeDescriptorReturnType(targetMethod.desc, "L" + clArgs + ";"); + private void packArgs(InsnList insns, String clArgs, String targetDesc) { + String factoryDesc = Bytecode.changeDescriptorReturnType(targetDesc, "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 3a5b5fc7f..ee57296ba 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 @@ -103,7 +103,7 @@ public class RedirectInjector extends InvokeInjector { /** * Meta decoration object for redirector target nodes */ - class Meta { + public class Meta { public static final String KEY = "redirector"; diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java new file mode 100644 index 000000000..8e874ed60 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/injection/invoke/util/InvokeUtil.java @@ -0,0 +1,50 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.injection.invoke.util; + +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.MethodInsnNode; +import org.spongepowered.asm.mixin.injection.invoke.RedirectInjector; +import org.spongepowered.asm.mixin.injection.struct.InjectionNodes.InjectionNode; + +import java.util.Arrays; + +public 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; + } +} diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java index e8030562e..bbad13edc 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/points/BeforeConstant.java @@ -147,7 +147,7 @@ public class BeforeConstant extends InjectionPoint { private final boolean log; public BeforeConstant(IMixinContext context, AnnotationNode node, String returnType) { - super(Annotations.getValue(node, "slice", ""), Selector.DEFAULT, null); + super(Annotations.getValue(node, "slice", ""), Specifier.DEFAULT, null); Boolean empty = Annotations.getValue(node, "nullValue", (Boolean)null); this.ordinal = Annotations.getValue(node, "ordinal", Integer.valueOf(-1)); diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java index bb686fb55..bf6be2c55 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionInfo.java @@ -473,6 +473,8 @@ public void postInject() { String.format("Critical injection failure: %s %s%s in %s failed injection check, %d succeeded of %d allowed.%s", description, this.methodName, this.method.desc, this.mixin, this.injectedCallbackCount, this.maxCallbackCount, extraInfo)); } + + this.slices.postInject(); } /** diff --git a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java index cb4290a94..b41cdd7ce 100644 --- a/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java +++ b/src/main/java/org/spongepowered/asm/mixin/injection/struct/InjectionPointData.java @@ -36,7 +36,7 @@ import org.spongepowered.asm.mixin.injection.At; import org.spongepowered.asm.mixin.injection.IInjectionPointContext; import org.spongepowered.asm.mixin.injection.Inject; -import org.spongepowered.asm.mixin.injection.InjectionPoint.Selector; +import org.spongepowered.asm.mixin.injection.InjectionPoint.Specifier; import org.spongepowered.asm.mixin.injection.modify.LocalVariableDiscriminator; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; import org.spongepowered.asm.mixin.injection.selectors.InvalidSelectorException; @@ -86,7 +86,7 @@ public class InjectionPointData { /** * Selector parsed from the at argument, only used by slices */ - private final Selector selector; + private final Specifier specifier; /** * Target @@ -131,7 +131,7 @@ public InjectionPointData(IInjectionPointContext context, String at, List args) { @@ -172,10 +172,10 @@ public String getType() { } /** - * Get the selector value parsed from the injector + * Get the specifier value parsed from the injector */ - public Selector getSelector() { - return this.selector; + public Specifier getSpecifier() { + return this.specifier; } /** @@ -362,7 +362,7 @@ public String toString() { } private static Pattern createPattern() { - return Pattern.compile(String.format("^(.+?)(:(%s))?$", Joiner.on('|').join(Selector.values()))); + return Pattern.compile(String.format("^(.+?)(:(%s))?$", Joiner.on('|').join(Specifier.values()))); } /** @@ -380,8 +380,8 @@ private static String parseType(Matcher matcher, String at) { return matcher.matches() ? matcher.group(1) : at; } - private static Selector parseSelector(Matcher matcher) { - return matcher.matches() && matcher.group(3) != null ? Selector.valueOf(matcher.group(3)) : Selector.DEFAULT; + private static Specifier parseSpecifier(Matcher matcher) { + return matcher.matches() && matcher.group(3) != null ? Specifier.valueOf(matcher.group(3)) : Specifier.DEFAULT; } private static int parseInt(String string, int defaultValue) { diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/DefaultExtensions.java b/src/main/java/org/spongepowered/asm/mixin/transformer/DefaultExtensions.java index cc4031d11..85a68882d 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/DefaultExtensions.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/DefaultExtensions.java @@ -30,6 +30,7 @@ import org.spongepowered.asm.mixin.transformer.ext.extensions.ExtensionCheckClass; import org.spongepowered.asm.mixin.transformer.ext.extensions.ExtensionCheckInterfaces; import org.spongepowered.asm.mixin.transformer.ext.extensions.ExtensionClassExporter; +import org.spongepowered.asm.mixin.transformer.ext.extensions.ExtensionLVTCleaner; import org.spongepowered.asm.service.ISyntheticClassInfo; import org.spongepowered.asm.util.IConsumer; @@ -54,6 +55,7 @@ public void accept(ISyntheticClassInfo item) { extensions.add(new InnerClassGenerator(registryDelegate, nestHostCoprocessor)); extensions.add(new ExtensionClassExporter(environment)); + extensions.add(new ExtensionLVTCleaner()); extensions.add(new ExtensionCheckClass()); extensions.add(new ExtensionCheckInterfaces()); } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java b/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java index 369dcff8b..88454531c 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/MethodMapper.java @@ -76,6 +76,14 @@ public MethodMapper(MixinEnvironment env, ClassInfo info) { public ClassInfo getClassInfo() { return this.info; } + + /** + * Resets the counters to prepare for application, which can happen multiple times due to hotswap. + */ + public void reset() { + this.nextUniqueMethodIndex = 0; + this.nextUniqueFieldIndex = 0; + } /** * Conforms an injector handler method diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java b/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java index 1e3b32ff8..8f4a33f68 100644 --- a/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/TargetClassContext.java @@ -407,9 +407,10 @@ void applyMixins() { } /** - * Run extensions before apply + * Run extensions before apply and clean up any global state in case this is a hotswap */ private void preApply() { + this.getClassInfo().getMethodMapper().reset(); this.extensions.preApply(this); } diff --git a/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionLVTCleaner.java b/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionLVTCleaner.java new file mode 100644 index 000000000..6729b5360 --- /dev/null +++ b/src/main/java/org/spongepowered/asm/mixin/transformer/ext/extensions/ExtensionLVTCleaner.java @@ -0,0 +1,85 @@ +/* + * This file is part of Mixin, licensed under the MIT License (MIT). + * + * Copyright (c) SpongePowered + * Copyright (c) contributors + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN + * THE SOFTWARE. + */ +package org.spongepowered.asm.mixin.transformer.ext.extensions; + +import org.objectweb.asm.tree.ClassNode; +import org.objectweb.asm.tree.LocalVariableNode; +import org.objectweb.asm.tree.MethodNode; +import org.spongepowered.asm.mixin.MixinEnvironment; +import org.spongepowered.asm.mixin.transformer.ext.IExtension; +import org.spongepowered.asm.mixin.transformer.ext.ITargetClassContext; +import org.spongepowered.asm.util.Locals; + +import java.util.Iterator; + +/** + * Strips synthetic local variables from the LVT after exporting to avoid debuggers becoming confused by them. + */ +public class ExtensionLVTCleaner implements IExtension { + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.transformer.ext.IExtension#checkActive( + * org.spongepowered.asm.mixin.MixinEnvironment) + */ + @Override + public boolean checkActive(MixinEnvironment environment) { + return true; + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.transformer.IMixinTransformerModule + * #preApply(org.spongepowered.asm.mixin.transformer.TargetClassContext) + */ + @Override + public void preApply(ITargetClassContext context) { + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.transformer.IMixinTransformerModule + * #postApply(org.spongepowered.asm.mixin.transformer.TargetClassContext) + */ + @Override + public void postApply(ITargetClassContext context) { + } + + /* (non-Javadoc) + * @see org.spongepowered.asm.mixin.transformer.ext.IExtension + * #export(org.spongepowered.asm.mixin.MixinEnvironment, + * java.lang.String, boolean, org.objectweb.asm.tree.ClassNode) + */ + @Override + public void export(MixinEnvironment env, String name, boolean force, ClassNode classNode) { + for (MethodNode methodNode : classNode.methods) { + if (methodNode.localVariables != null) { + for (Iterator it = methodNode.localVariables.iterator(); it.hasNext(); ) { + if (it.next() instanceof Locals.SyntheticLocalVariableNode) { + it.remove(); + } + } + } + } + } + +} diff --git a/src/main/java/org/spongepowered/asm/util/JavaVersion.java b/src/main/java/org/spongepowered/asm/util/JavaVersion.java index 1197d121d..c95c93302 100644 --- a/src/main/java/org/spongepowered/asm/util/JavaVersion.java +++ b/src/main/java/org/spongepowered/asm/util/JavaVersion.java @@ -96,6 +96,26 @@ public abstract class JavaVersion { * Version number for Java 18 */ public static final double JAVA_18 = 18.0; + + /** + * Version number for Java 19 + */ + public static final double JAVA_19 = 19.0; + + /** + * Version number for Java 20 + */ + public static final double JAVA_20 = 20.0; + + /** + * Version number for Java 21 + */ + public static final double JAVA_21 = 21.0; + + /** + * Version number for Java 22 + */ + public static final double JAVA_22 = 22.0; private static double current = 0.0; diff --git a/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java b/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java index 9cbbce1c4..0840a565b 100644 --- a/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java +++ b/src/modlauncher/java/org/spongepowered/asm/launch/MixinLaunchPluginLegacy.java @@ -235,7 +235,7 @@ public ClassNode getClassNode(String name, boolean runTransformers) throws Class if (classBytes != null && classBytes.length != 0) { ClassNode classNode = new ClassNode(); ClassReader classReader = new MixinClassReader(classBytes, canonicalName); - classReader.accept(classNode, ClassReader.EXPAND_FRAMES); + classReader.accept(classNode, 0); return classNode; } diff --git a/src/modularity/java/module-info.java b/src/modularity/java/module-info.java index d9ba59043..0d434fb11 100644 --- a/src/modularity/java/module-info.java +++ b/src/modularity/java/module-info.java @@ -61,6 +61,12 @@ //requires static com.google.gson; requires static gson; + // Gson's module dependencies + // Optional dependency on java.sql + requires static java.sql; + // Optional dependency on jdk.unsupported for JDK's sun.misc.Unsafe + requires static jdk.unsupported; + // // Exports // @@ -146,4 +152,6 @@ provides org.spongepowered.tools.obfuscation.service.IObfuscationService with org.spongepowered.tools.obfuscation.mcp.ObfuscationServiceMCP, org.spongepowered.tools.obfuscation.fg3.ObfuscationServiceFG3; + + uses org.spongepowered.include.com.google.common.base.PatternCompiler; } diff --git a/src/modularityDummy/java/org/spongepowered/include/com/google/common/base/PatternCompiler.java b/src/modularityDummy/java/org/spongepowered/include/com/google/common/base/PatternCompiler.java new file mode 100644 index 000000000..abd21096c --- /dev/null +++ b/src/modularityDummy/java/org/spongepowered/include/com/google/common/base/PatternCompiler.java @@ -0,0 +1,30 @@ +/* + * 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.include.com.google.common.base; + +// Dummy public class so that the module can declare a uses directive for it +public interface PatternCompiler { +}