Skip to content

Commit a2064f4

Browse files
committed
Patch STORE injection points
1 parent 9ced1d5 commit a2064f4

13 files changed

Lines changed: 175 additions & 13 deletions

File tree

definition/src/next/java/org/sinytra/adapter/next/env/ann/MixinAnnotationConstants.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,4 +3,7 @@
33
public class MixinAnnotationConstants {
44
public static final String AT_METHOD = "method";
55
public static final String AT_VAL_INVOKE = "INVOKE";
6+
public static final String AT_VAL_STORE = "STORE";
7+
8+
public static final String PROPERTY_ORDINAL = "ordinal";
69
}

definition/src/next/java/org/sinytra/adapter/next/env/ann/ModifyVariableMixinData.java

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,16 +2,24 @@
22

33
import org.sinytra.adapter.patch.util.MethodQualifier;
44

5+
import java.util.OptionalInt;
6+
57
public class ModifyVariableMixinData extends MixinData {
68
private final boolean argsOnly;
9+
private final Integer ordinal;
710

8-
public ModifyVariableMixinData(ClassTarget targetClass, MethodQualifier targetMethod, AtData atData, boolean argsOnly) {
11+
public ModifyVariableMixinData(ClassTarget targetClass, MethodQualifier targetMethod, AtData atData, boolean argsOnly, Integer ordinal) {
912
super(targetClass, targetMethod, atData);
1013

1114
this.argsOnly = argsOnly;
15+
this.ordinal = ordinal;
1216
}
1317

1418
public boolean argsOnly() {
1519
return this.argsOnly;
1620
}
21+
22+
public OptionalInt ordinal() {
23+
return this.ordinal != null ? OptionalInt.of(this.ordinal) : OptionalInt.empty();
24+
}
1725
}

definition/src/next/java/org/sinytra/adapter/next/pipeline/processor/Processors.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,5 +13,6 @@ private void registerDefaultProcessors() {
1313
add(new InjectionTargetProcessor());
1414
add(new MixinMethodParametersProcessor());
1515
add(new ReturnTypeProcessor());
16+
add(new PropertyProcessor());
1617
}
1718
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
package org.sinytra.adapter.next.pipeline.processor;
2+
3+
import org.sinytra.adapter.next.env.MixinContext;
4+
import org.sinytra.adapter.next.env.ann.MixinData;
5+
import org.sinytra.adapter.next.pipeline.Recipe;
6+
import org.sinytra.adapter.next.pipeline.TxResult;
7+
import org.sinytra.adapter.next.pipeline.config.Configuration;
8+
import org.sinytra.adapter.patch.analysis.selector.AnnotationHandle;
9+
10+
import static org.sinytra.adapter.next.env.ann.MixinAnnotationConstants.PROPERTY_ORDINAL;
11+
12+
public class PropertyProcessor implements Processor {
13+
@Override
14+
public TxResult process(MixinData mixin, MixinContext context, Configuration dirty, Recipe recipe) {
15+
if (dirty.getAtData() == null) return TxResult.FAIL;
16+
17+
AnnotationHandle handle = context.methodAnnotation();
18+
dirty.<Integer>getProperty(PROPERTY_ORDINAL)
19+
.ifPresent(o -> handle.setOrAppendNonNull(PROPERTY_ORDINAL, o));
20+
21+
return TxResult.SUCCESS;
22+
}
23+
}

definition/src/next/java/org/sinytra/adapter/next/pipeline/resolver/InjectionTargetResolver.java

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -25,16 +25,16 @@
2525
public class InjectionTargetResolver implements Resolver {
2626
private static final int INSN_RANGE = 5;
2727

28+
private final List<Resolver> subResolvers = new ArrayList<>();
29+
30+
public void addSubResolver(Resolver subResolver) {
31+
this.subResolvers.add(subResolver);
32+
}
33+
2834
@Override
2935
public TxResult resolve(MixinData mixin, MixinContext context, Configuration clean, MutableConfiguration dirty, Recipe recipe) {
3036
if (dirty.getAtData() != null) return TxResult.PASS;
3137

32-
// Only support INVOKE for now
33-
if (!clean.getAtData().getValue().equals(AT_VAL_INVOKE)) {
34-
dirty.inheritAtData();
35-
return TxResult.SUCCESS;
36-
}
37-
3838
MethodQualifier dirtyQualifier = dirty.getTargetMethod();
3939
if (dirtyQualifier == null) return TxResult.FAIL;
4040

@@ -48,6 +48,19 @@ public TxResult resolve(MixinData mixin, MixinContext context, Configuration cle
4848
return TxResult.SUCCESS;
4949
}
5050

51+
for (Resolver subResolver : this.subResolvers) {
52+
TxResult result = subResolver.resolve(mixin, context, clean, dirty, recipe);
53+
if (result != TxResult.PASS) {
54+
return result;
55+
}
56+
}
57+
58+
// Only support INVOKE for now
59+
if (!clean.getAtData().getValue().equals(AT_VAL_INVOKE)) {
60+
dirty.inheritAtData();
61+
return TxResult.SUCCESS;
62+
}
63+
5164
// Find replacements
5265
if (findReplacedType(context, clean.getTargetMethod(), pair.methodNode(), clean.getAtData(), dirty)) {
5366
return TxResult.SUCCESS;
Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
package org.sinytra.adapter.next.pipeline.resolver;
2+
3+
import org.objectweb.asm.Type;
4+
import org.objectweb.asm.tree.InsnList;
5+
import org.objectweb.asm.tree.LocalVariableNode;
6+
import org.objectweb.asm.tree.MethodNode;
7+
import org.sinytra.adapter.next.env.MixinContext;
8+
import org.sinytra.adapter.next.env.ann.MixinData;
9+
import org.sinytra.adapter.next.env.ann.ModifyVariableMixinData;
10+
import org.sinytra.adapter.next.pipeline.Recipe;
11+
import org.sinytra.adapter.next.pipeline.TxResult;
12+
import org.sinytra.adapter.next.pipeline.config.Configuration;
13+
import org.sinytra.adapter.next.pipeline.config.MutableConfiguration;
14+
import org.sinytra.adapter.patch.analysis.InstructionMatcher;
15+
import org.sinytra.adapter.patch.analysis.locals.LocalVarAnalyzer;
16+
import org.sinytra.adapter.patch.analysis.locals.LocalVariableLookup;
17+
import org.sinytra.adapter.patch.api.MethodContext;
18+
19+
import java.util.List;
20+
21+
import static org.sinytra.adapter.next.env.ann.MixinAnnotationConstants.AT_VAL_STORE;
22+
import static org.sinytra.adapter.next.env.ann.MixinAnnotationConstants.PROPERTY_ORDINAL;
23+
24+
public class ModifyVarInjectionTargetSubResolver implements Resolver {
25+
26+
@Override
27+
public TxResult resolve(MixinData mixin, MixinContext context, Configuration clean, MutableConfiguration dirty, Recipe recipe) {
28+
if (!(mixin instanceof ModifyVariableMixinData mvdata) || !clean.getAtData().getValue().equals(AT_VAL_STORE)) {
29+
return TxResult.PASS;
30+
}
31+
32+
MethodContext.TargetPair pair = context.methods().findOwnMethodPair(context.dirtyLookup(), dirty.getTargetMethod());
33+
34+
// Find replacement
35+
if (findComparableReplacement(mvdata, context, clean, pair, dirty)) {
36+
return TxResult.SUCCESS;
37+
}
38+
39+
return TxResult.PASS;
40+
}
41+
42+
private static boolean findComparableReplacement(ModifyVariableMixinData mixin, MixinContext context, Configuration clean, MethodContext.TargetPair dirtyPair, MutableConfiguration dirty) {
43+
if (mixin.ordinal().isEmpty()) return false;
44+
int ordinal = mixin.ordinal().getAsInt();
45+
Type varType = Type.getReturnType(context.methodNode().desc);
46+
47+
MethodContext.TargetPair cleanPair = context.methods().findOwnMethodPair(context.cleanLookup(), clean.getTargetMethod());
48+
LocalVariableLookup lookup = new LocalVariableLookup(cleanPair.methodNode());
49+
LocalVariableNode desired = lookup.getByTypedOrdinal(varType, ordinal).orElseThrow();
50+
51+
// Find variable initializer insns
52+
InsnList desiredInitializerInsns = LocalVarAnalyzer.findInitializerInsns(cleanPair.methodNode(), desired.index);
53+
54+
// Get all matching variables
55+
for (MethodNode method : dirtyPair.classNode().methods) {
56+
LocalVariableLookup dirtyLookup = new LocalVariableLookup(method);
57+
List<LocalVariableNode> lvs = method.localVariables.stream()
58+
.filter(lvn -> desired.desc.equals(lvn.desc))
59+
.filter(lvn -> {
60+
InsnList insns = LocalVarAnalyzer.findInitializerInsns(method, lvn.index);
61+
return InstructionMatcher.test(desiredInitializerInsns, insns);
62+
})
63+
.toList();
64+
if (lvs.size() == 1) {
65+
int dirtyOrdinal = dirtyLookup.getOrdinal(lvs.getFirst());
66+
dirty.setProperty(PROPERTY_ORDINAL, dirtyOrdinal);
67+
dirty.setTargetMethod(method);
68+
dirty.inheritAtData();
69+
return true;
70+
}
71+
}
72+
return false;
73+
}
74+
}

definition/src/next/java/org/sinytra/adapter/next/pipeline/resolver/TargetMethodResolver.java

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.sinytra.adapter.next.pipeline.config.Configuration;
1515
import org.sinytra.adapter.next.pipeline.config.MutableConfiguration;
1616
import org.sinytra.adapter.patch.api.MethodContext;
17+
import org.sinytra.adapter.patch.util.AdapterUtil;
1718
import org.sinytra.adapter.patch.util.MethodQualifier;
1819

1920
import java.util.ArrayList;
@@ -111,7 +112,17 @@ private static MethodNode resolveReplacementCandidate(MixinContext context, Clas
111112
valid.add(method);
112113
}
113114
}
115+
if (valid.size() == 1) {
116+
return valid.getFirst();
117+
}
118+
119+
List<MethodNode> nonDeprecated = methods.stream()
120+
.filter(m -> !AdapterUtil.hasAnnotation(m.visibleAnnotations, "Ljava/lang/Deprecated;"))
121+
.toList();
122+
if (nonDeprecated.size() == 1) {
123+
return nonDeprecated.getFirst();
124+
}
114125

115-
return valid.size() == 1 ? valid.getFirst() : null;
126+
return null;
116127
}
117128
}

definition/src/next/java/org/sinytra/adapter/next/type/ModifyVariableMixin.java

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,8 @@
99
import org.sinytra.adapter.next.pipeline.Recipe;
1010
import org.sinytra.adapter.next.pipeline.config.Configuration;
1111
import org.sinytra.adapter.next.pipeline.config.MutableConfiguration;
12+
import org.sinytra.adapter.next.pipeline.resolver.InjectionTargetResolver;
13+
import org.sinytra.adapter.next.pipeline.resolver.ModifyVarInjectionTargetSubResolver;
1214
import org.sinytra.adapter.patch.analysis.selector.AnnotationHandle;
1315
import org.sinytra.adapter.patch.analysis.selector.AnnotationValueHandle;
1416
import org.sinytra.adapter.patch.fixes.TypeAdapter;
@@ -24,12 +26,15 @@ public class ModifyVariableMixin implements MixinType<ModifyVariableMixinData> {
2426
@Override
2527
public ModifyVariableMixinData parse(MixinContext context, ClassTarget targetClass, MethodQualifier targetMethod, AtData atData, AnnotationHandle handle) {
2628
boolean argsOnly = handle.<Boolean>getValue("argsOnly").map(AnnotationValueHandle::get).orElse(false);
27-
return new ModifyVariableMixinData(targetClass, targetMethod, atData, argsOnly);
29+
Integer ordinal = handle.<Integer>getValue("ordinal").map(AnnotationValueHandle::get).orElse(null);
30+
return new ModifyVariableMixinData(targetClass, targetMethod, atData, argsOnly, ordinal);
2831
}
2932

3033
@Override
3134
public void preProcess(ModifyVariableMixinData mixin, MixinContext context, MutableConfiguration clean, Recipe recipe) {
3235
clean.setParameters(MethodParameters.create(context.methodNode().desc, List.of(SINGLE_ANY, LOCALS)));
36+
37+
recipe.resolvers().getOrThrow(InjectionTargetResolver.class).addSubResolver(new ModifyVarInjectionTargetSubResolver());
3338
}
3439

3540
@Override
@@ -50,6 +55,10 @@ public void postProcess(ModifyVariableMixinData mixin, MixinContext context, Con
5055
dirty.setParameters(newParams);
5156
dirty.setReturnType(dirtyVarType);
5257
}
58+
return;
5359
}
60+
61+
dirty.inheritParameters();
62+
dirty.inheritReturnType();
5463
}
5564
}

gradlew

100644100755
File mode changed.

test/build.gradle.kts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ configurations {
2424
}
2525

2626
dependencies {
27-
testCompileOnly(group = "org.sinytra.adapter", name = "definition")
27+
testImplementation(group = "org.sinytra.adapter", name = "definition")
2828
"neoForgeTestLibraries"(group = "org.sinytra.adapter", name = "definition") {
2929
isTransitive = false
3030
}

0 commit comments

Comments
 (0)