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 {
+}