24
24
import java .util .stream .Stream ;
25
25
26
26
public class DynFixMethodComparison implements DynamicFixer <DynFixMethodComparison .Data > {
27
+ private static final Set <String > ACCEPTED_ANNOTATIONS = Set .of (MixinConstants .INJECT , MixinConstants .WRAP_OPERATION );
28
+
27
29
public record Data (AbstractInsnNode cleanInjectionInsn ) {}
28
30
29
31
@ Nullable
30
32
@ Override
31
33
public Data prepare (MethodContext methodContext ) {
32
- if (methodContext .methodAnnotation ().matchesDesc ( MixinConstants . INJECT ) && methodContext .findDirtyInjectionTarget () != null ) {
34
+ if (methodContext .methodAnnotation ().matchesAny ( ACCEPTED_ANNOTATIONS ) && methodContext .findDirtyInjectionTarget () != null ) {
33
35
MethodContext .TargetPair cleanInjectionTarget = methodContext .findCleanInjectionTarget ();
34
36
if (cleanInjectionTarget == null ) {
35
37
return null ;
36
38
}
37
39
List <AbstractInsnNode > cleanInsns = methodContext .findInjectionTargetInsns (cleanInjectionTarget );
38
- if (cleanInsns .size () == 1 ) {
40
+ if (! cleanInsns .isEmpty () ) {
39
41
return new Data (cleanInsns .getFirst ());
40
42
}
41
43
}
@@ -61,14 +63,23 @@ public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodCont
61
63
62
64
Map <List <AbstractInsnNode >, List <AbstractInsnNode >> matchedLabels = new LinkedHashMap <>();
63
65
for (List <AbstractInsnNode > cleanInsns : cleanLabelsOriginal ) {
66
+ List <List <AbstractInsnNode >> candidates = new ArrayList <>();
67
+
64
68
for (List <AbstractInsnNode > dirtyInsns : dirtyLabels ) {
65
69
if (InstructionMatcher .test (cleanInsns , dirtyInsns , InsnComparator .IGNORE_VAR_INDEX | InsnComparator .IGNORE_LINE_NUMBERS )) {
66
- matchedLabels .put (cleanInsns , dirtyInsns );
67
- cleanLabels .remove (cleanInsns );
68
- dirtyLabels .remove (dirtyInsns );
69
- break ;
70
+ candidates .add (dirtyInsns );
70
71
}
71
72
}
73
+ // TODO Try and come up with something better
74
+ // This prevents messing up the order of labels
75
+ // Without this countermeasure, it might happen that a label that was deleted will match a seemingly identical label somewhere else in the method, which is wrong
76
+ // We disable any duplicated until we can properly handle such cases
77
+ if (candidates .size () == 1 ) {
78
+ List <AbstractInsnNode > dirtyInsns = candidates .getFirst ();
79
+ matchedLabels .put (cleanInsns , dirtyInsns );
80
+ cleanLabels .remove (cleanInsns );
81
+ dirtyLabels .remove (dirtyInsns );
82
+ }
72
83
}
73
84
74
85
Pair <List <AbstractInsnNode >, List <AbstractInsnNode >> patchRange = findPatchHunkRange (cleanLabel , cleanLabelsOriginal , matchedLabels );
@@ -78,6 +89,11 @@ public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodCont
78
89
79
90
ClassNode cleanTargetClass = methodContext .findCleanInjectionTarget ().classNode ();
80
91
List <List <AbstractInsnNode >> hunkLabels = dirtyLabelsOriginal .subList (dirtyLabelsOriginal .indexOf (patchRange .getFirst ()) + 1 , dirtyLabelsOriginal .indexOf (patchRange .getSecond ()));
92
+
93
+ if (methodContext .methodAnnotation ().matchesDesc (MixinConstants .WRAP_OPERATION )) {
94
+ return handleTargetModification (hunkLabels , methodContext );
95
+ }
96
+
81
97
for (List <AbstractInsnNode > insns : hunkLabels ) {
82
98
for (AbstractInsnNode insn : insns ) {
83
99
if (insn instanceof MethodInsnNode minsn && minsn .getOpcode () == Opcodes .INVOKESTATIC && !minsn .owner .equals (cleanTargetClass .name )) {
@@ -102,6 +118,21 @@ public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodCont
102
118
return Patch .Result .PASS ;
103
119
}
104
120
121
+ private static Patch .Result handleTargetModification (List <List <AbstractInsnNode >> hunkLabels , MethodContext methodContext ) {
122
+ ClassNode dirtyTarget = methodContext .findDirtyInjectionTarget ().classNode ();
123
+ for (List <AbstractInsnNode > insns : hunkLabels ) {
124
+ for (AbstractInsnNode insn : insns ) {
125
+ if (insn instanceof MethodInsnNode minsn && minsn .owner .equals (dirtyTarget .name )) {
126
+ MethodNode method = MethodCallAnalyzer .findMethodByUniqueName (dirtyTarget , minsn .name );
127
+ if (!methodContext .findInjectionTargetInsns (new MethodContext .TargetPair (dirtyTarget , method )).isEmpty ()) {
128
+ return BundledMethodTransform .builder ().modifyTarget (minsn .name + minsn .desc ).apply (methodContext );
129
+ }
130
+ }
131
+ }
132
+ }
133
+ return Patch .Result .PASS ;
134
+ }
135
+
105
136
private static Patch .Result attemptExtractMixin (MethodInsnNode minsn , MethodContext methodContext ) {
106
137
// Looks like some code was moved into a static method outside this class
107
138
// Attempt extracting mixin
@@ -193,6 +224,9 @@ private static Patch.Result createInjectionPoint(ClassNode newTargetClass, Metho
193
224
// TODO This should be an automatic upgrade tbh
194
225
private static void adjustInjectorOrdinalForNewMethod (MethodInsnNode minsn , MethodContext methodContext ) {
195
226
AnnotationValueHandle <Integer > handle = methodContext .injectionPointAnnotationOrThrow ().<Integer >getValue ("ordinal" ).orElse (null );
227
+ if (handle == null ) {
228
+ return ;
229
+ }
196
230
int originalOrdinal = handle .get ();
197
231
// Temporarily adjust ordinal to account for previous calls that have not been moved to the new class
198
232
if (handle != null ) {
0 commit comments