-
-
Notifications
You must be signed in to change notification settings - Fork 1
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Transform WrapOperation to target instanceof call
- Loading branch information
Showing
17 changed files
with
454 additions
and
180 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
105 changes: 105 additions & 0 deletions
105
definition/src/main/java/org/sinytra/adapter/patch/analysis/MethodLabelComparator.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,105 @@ | ||
package org.sinytra.adapter.patch.analysis; | ||
|
||
import com.mojang.datafixers.util.Pair; | ||
import org.jetbrains.annotations.Nullable; | ||
import org.objectweb.asm.tree.AbstractInsnNode; | ||
import org.objectweb.asm.tree.FrameNode; | ||
import org.objectweb.asm.tree.LabelNode; | ||
import org.objectweb.asm.tree.MethodNode; | ||
import org.sinytra.adapter.patch.api.MethodContext; | ||
|
||
import java.util.*; | ||
import java.util.stream.Stream; | ||
|
||
public class MethodLabelComparator { | ||
public record ComparisonResult(List<List<AbstractInsnNode>> patchedLabels, List<AbstractInsnNode> cleanLabel) {} | ||
|
||
@Nullable | ||
public static ComparisonResult findPatchedLabels(AbstractInsnNode cleanInjectionInsn, MethodContext methodContext) { | ||
List<List<AbstractInsnNode>> cleanLabels = getLabelsInMethod(methodContext.findCleanInjectionTarget().methodNode()); | ||
List<List<AbstractInsnNode>> cleanLabelsOriginal = List.copyOf(cleanLabels); | ||
|
||
List<List<AbstractInsnNode>> cleanMatchedLabels = cleanLabels.stream() | ||
.filter(insns -> insns.contains(cleanInjectionInsn)) | ||
.toList(); | ||
if (cleanMatchedLabels.size() != 1) { | ||
return null; | ||
} | ||
List<AbstractInsnNode> cleanLabel = cleanMatchedLabels.getFirst(); | ||
|
||
List<List<AbstractInsnNode>> dirtyLabels = getLabelsInMethod(methodContext.findDirtyInjectionTarget().methodNode()); | ||
List<List<AbstractInsnNode>> dirtyLabelsOriginal = List.copyOf(dirtyLabels); | ||
|
||
Map<List<AbstractInsnNode>, List<AbstractInsnNode>> matchedLabels = new LinkedHashMap<>(); | ||
for (List<AbstractInsnNode> cleanInsns : cleanLabelsOriginal) { | ||
List<List<AbstractInsnNode>> candidates = new ArrayList<>(); | ||
|
||
for (List<AbstractInsnNode> dirtyInsns : dirtyLabels) { | ||
if (InstructionMatcher.test(cleanInsns, dirtyInsns, InsnComparator.IGNORE_VAR_INDEX | InsnComparator.IGNORE_LINE_NUMBERS)) { | ||
candidates.add(dirtyInsns); | ||
} | ||
} | ||
// TODO Try and come up with something better | ||
// This prevents messing up the order of labels | ||
// 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 | ||
// We disable any duplicated until we can properly handle such cases | ||
if (candidates.size() == 1) { | ||
List<AbstractInsnNode> dirtyInsns = candidates.getFirst(); | ||
matchedLabels.put(cleanInsns, dirtyInsns); | ||
cleanLabels.remove(cleanInsns); | ||
dirtyLabels.remove(dirtyInsns); | ||
} | ||
} | ||
|
||
Pair<List<AbstractInsnNode>, List<AbstractInsnNode>> patchRange = findPatchHunkRange(cleanLabel, cleanLabelsOriginal, matchedLabels); | ||
if (patchRange == null) { | ||
return null; | ||
} | ||
|
||
List<List<AbstractInsnNode>> patchedLabels = dirtyLabelsOriginal.subList(dirtyLabelsOriginal.indexOf(patchRange.getFirst()) + 1, dirtyLabelsOriginal.indexOf(patchRange.getSecond())); | ||
return new ComparisonResult(patchedLabels, cleanLabel); | ||
} | ||
|
||
@Nullable | ||
private static Pair<List<AbstractInsnNode>, List<AbstractInsnNode>> findPatchHunkRange(List<AbstractInsnNode> cleanLabel, List<List<AbstractInsnNode>> cleanLabels, Map<List<AbstractInsnNode>, List<AbstractInsnNode>> matchedLabels) { | ||
// Find last matched dirty label BEFORE the injection point | ||
List<AbstractInsnNode> dirtyLabelBefore = Stream.iterate(cleanLabels.indexOf(cleanLabel), i -> i >= 0, i -> i - 1) | ||
.map(i -> matchedLabels.get(cleanLabels.get(i))) | ||
.filter(Objects::nonNull) | ||
.findFirst() | ||
.orElse(null); | ||
if (dirtyLabelBefore == null) { | ||
return null; | ||
} | ||
|
||
// Find first matched dirty label AFTER the injection point | ||
List<AbstractInsnNode> dirtyLabelAfter = Stream.iterate(cleanLabels.indexOf(cleanLabel), i -> i < cleanLabels.size(), i -> i + 1) | ||
.map(i -> matchedLabels.get(cleanLabels.get(i))) | ||
.filter(Objects::nonNull) | ||
.findFirst() | ||
.orElse(null); | ||
if (dirtyLabelAfter == null) { | ||
return null; | ||
} | ||
|
||
return Pair.of(dirtyLabelBefore, dirtyLabelAfter); | ||
} | ||
|
||
private static List<List<AbstractInsnNode>> getLabelsInMethod(MethodNode methodNode) { | ||
List<List<AbstractInsnNode>> list = new ArrayList<>(); | ||
List<AbstractInsnNode> workingList = null; | ||
for (AbstractInsnNode insn : methodNode.instructions) { | ||
if (insn instanceof FrameNode) { | ||
continue; | ||
} | ||
if (insn instanceof LabelNode) { | ||
if (workingList != null) { | ||
list.add(workingList); | ||
} | ||
workingList = new ArrayList<>(); | ||
} | ||
workingList.add(insn); | ||
} | ||
return list; | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
79 changes: 79 additions & 0 deletions
79
definition/src/main/java/org/sinytra/adapter/patch/transformer/MirrorableExtractMixin.java
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,79 @@ | ||
package org.sinytra.adapter.patch.transformer; | ||
|
||
import com.google.common.collect.ImmutableList; | ||
import org.objectweb.asm.Opcodes; | ||
import org.objectweb.asm.Type; | ||
import org.objectweb.asm.commons.GeneratorAdapter; | ||
import org.objectweb.asm.commons.Method; | ||
import org.objectweb.asm.tree.*; | ||
import org.sinytra.adapter.patch.analysis.MethodCallAnalyzer; | ||
import org.sinytra.adapter.patch.api.*; | ||
import org.sinytra.adapter.patch.util.AdapterUtil; | ||
import org.sinytra.adapter.patch.util.OpcodeUtil; | ||
|
||
import java.util.ArrayList; | ||
import java.util.HashSet; | ||
import java.util.List; | ||
import java.util.stream.Stream; | ||
|
||
public record MirrorableExtractMixin(String destinationClass, MethodInsnNode destinationMethodInvocation) implements MethodTransform { | ||
@Override | ||
public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodContext methodContext, PatchContext context) { | ||
Type selfType = Type.getObjectType(methodContext.findDirtyInjectionTarget().classNode().name); | ||
Type[] params = Type.getArgumentTypes(this.destinationMethodInvocation.desc); | ||
int selfIndex = Stream.of(Stream.iterate(0, i -> i < params.length, i -> i + 1) | ||
.filter(i -> params[i].equals(selfType)) | ||
.toList()) | ||
.filter(list -> list.size() == 1) | ||
.map(List::getFirst) | ||
.findFirst() | ||
.orElse(-1); | ||
if (selfIndex == -1) { | ||
return Patch.Result.PASS; | ||
} | ||
|
||
List<AbstractInsnNode> callInsns = MethodCallAnalyzer.findMethodCallParamInsns(methodContext.findDirtyInjectionTarget().methodNode(), this.destinationMethodInvocation); | ||
if (callInsns == null || callInsns.size() <= selfIndex) { | ||
return Patch.Result.PASS; | ||
} | ||
AbstractInsnNode selfParamInsn = callInsns.get(selfIndex); | ||
if (!(selfParamInsn instanceof VarInsnNode varInsn) || varInsn.getOpcode() != Opcodes.ALOAD || varInsn.var != 0) { | ||
return Patch.Result.PASS; | ||
} | ||
// Cool, out instance is passed into the method. Now let's inject there and call the old mixin method | ||
ClassNode generatedTarget = methodContext.patchContext().environment().classGenerator().getOrGenerateMixinClass(methodContext.getMixinClass(), this.destinationClass, null); | ||
methodContext.patchContext().environment().refmapHolder().copyEntries(methodContext.getMixinClass().name, generatedTarget.name); | ||
// Generate a method with the same injector annotation | ||
MethodNode originalMixinMethod = methodContext.getMixinMethod(); | ||
String name = originalMixinMethod.name + "$adapter$mirror$" + AdapterUtil.randomString(5); | ||
List<Type> originalParams = List.of(Type.getArgumentTypes(originalMixinMethod.desc)); | ||
List<Type> newParams = ImmutableList.<Type>builder().add(Type.getArgumentTypes(this.destinationMethodInvocation.desc)).add(AdapterUtil.CI_TYPE).build(); | ||
// Make sure we have all required params | ||
if (!new HashSet<>(newParams).containsAll(originalParams)) { | ||
return Patch.Result.PASS; | ||
} | ||
|
||
String desc = Type.getMethodDescriptor(Type.VOID_TYPE, newParams.toArray(Type[]::new)); | ||
// Change target | ||
BundledMethodTransform.builder().modifyTarget(this.destinationMethodInvocation.name + this.destinationMethodInvocation.desc).apply(methodContext); | ||
MethodNode invokerMixinMethod = (MethodNode) generatedTarget.visitMethod(Opcodes.ACC_PRIVATE | Opcodes.ACC_STATIC, name, desc, null, null); | ||
invokerMixinMethod.visibleAnnotations = new ArrayList<>(originalMixinMethod.visibleAnnotations); | ||
// Make original mixin a unique public method | ||
originalMixinMethod.access = OpcodeUtil.setAccessVisibility(originalMixinMethod.access, Opcodes.ACC_PUBLIC); | ||
originalMixinMethod.visibleAnnotations.remove(methodContext.methodAnnotation().unwrap()); | ||
originalMixinMethod.visitAnnotation(MixinConstants.UNIQUE, true); | ||
// Now call the original mixin | ||
GeneratorAdapter gen = new GeneratorAdapter(invokerMixinMethod, invokerMixinMethod.access, invokerMixinMethod.name, invokerMixinMethod.desc); | ||
gen.newLabel(); | ||
gen.loadArg(selfIndex); | ||
for (Type type : originalParams) { | ||
gen.loadArg(newParams.indexOf(type)); | ||
} | ||
gen.invokeVirtual(selfType, new Method(originalMixinMethod.name, originalMixinMethod.desc)); | ||
gen.newLabel(); | ||
gen.returnValue(); | ||
gen.newLabel(); | ||
gen.endMethod(); | ||
return Patch.Result.APPLY; | ||
} | ||
} |
Oops, something went wrong.