Skip to content

Commit

Permalink
Wrap operation upgrades
Browse files Browse the repository at this point in the history
  • Loading branch information
Su5eD committed Jul 20, 2024
1 parent dbae5f0 commit 2d5468d
Show file tree
Hide file tree
Showing 11 changed files with 175 additions and 27 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
import java.util.stream.Stream;

public class MethodLabelComparator {
public record ComparisonResult(List<List<AbstractInsnNode>> patchedLabels, List<AbstractInsnNode> cleanLabel) {}
public record ComparisonResult(List<List<AbstractInsnNode>> patchedLabels, List<AbstractInsnNode> cleanLabel) {
}

@Nullable
public static ComparisonResult findPatchedLabels(AbstractInsnNode cleanInjectionInsn, MethodContext methodContext) {
Expand Down Expand Up @@ -55,21 +56,28 @@ public static ComparisonResult findPatchedLabels(AbstractInsnNode cleanInjection
if (patchRange == null) {
return null;
}

List<List<AbstractInsnNode>> patchedLabels = dirtyLabelsOriginal.subList(dirtyLabelsOriginal.indexOf(patchRange.getFirst()) + 1, dirtyLabelsOriginal.indexOf(patchRange.getSecond()));

int to = dirtyLabelsOriginal.indexOf(patchRange.getSecond());
List<List<AbstractInsnNode>> patchedLabels = patchRange.getFirst() == null ? dirtyLabelsOriginal.subList(0, to) : dirtyLabelsOriginal.subList(dirtyLabelsOriginal.indexOf(patchRange.getFirst()) + 1, to);
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) {
private static Pair<@Nullable 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;
List<AbstractInsnNode> dirtyLabelBefore;
int cleanLabelOrdinal = cleanLabels.indexOf(cleanLabel);
if (cleanLabelOrdinal == 0) {
dirtyLabelBefore = null;
} else {
dirtyLabelBefore = Stream.iterate(cleanLabelOrdinal, 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,18 @@ public interface MethodTransformBuilder<T extends MethodTransformBuilder<T>> {
T transformMethods(List<MethodTransform> transformers);

T chain(Consumer<T> consumer);

interface Class<T extends Class<T>> extends MethodTransformBuilder<T> {
default T modifyInjectionPoint(String value, String target) {
return modifyInjectionPoint(value, target, false);
}

T modifyInjectionPoint(String value, String target, boolean resetValues);

T modifyInjectionPoint(String value, String target, boolean resetValues, boolean dontUpgrade);

default T modifyInjectionPoint(String target) {
return modifyInjectionPoint(null, target);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -64,7 +64,7 @@ interface Builder<T extends Builder<T>> extends MethodTransformBuilder<T> {
PatchInstance build();
}

interface ClassPatchBuilder extends Builder<ClassPatchBuilder> {
interface ClassPatchBuilder extends Builder<ClassPatchBuilder>, MethodTransformBuilder.Class<ClassPatchBuilder> {
ClassPatchBuilder targetMethod(String... targets);

default ClassPatchBuilder targetInjectionPoint(String target) {
Expand All @@ -85,18 +85,6 @@ default ClassPatchBuilder targetConstant(double doubleValue) {
.orElse(false)));
}

default ClassPatchBuilder modifyInjectionPoint(String value, String target) {
return modifyInjectionPoint(value, target, false);
}

ClassPatchBuilder modifyInjectionPoint(String value, String target, boolean resetValues);

ClassPatchBuilder modifyInjectionPoint(String value, String target, boolean resetValues, boolean dontUpgrade);

default ClassPatchBuilder modifyInjectionPoint(String target) {
return modifyInjectionPoint(null, target);
}

ClassPatchBuilder divertRedirector(Consumer<InstructionAdapter> patcher);

ClassPatchBuilder disable();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ public static Builder builder() {
return new Builder();
}

public static class Builder extends MethodTransformBuilderImpl<Builder> {
public static class Builder extends MethodTransformBuilderImpl.ClassImpl<Builder> {
private Builder() {}

public MethodTransform build() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodCont

if (methodContext.methodAnnotation().matchesDesc(MixinConstants.WRAP_OPERATION)) {
return handleWrapOperationToInstanceOf(cleanInjectionInsn, comparisonResult.cleanLabel(), hunkLabels, methodContext)
.orElseGet(() -> handleWrapOpertationNewInjectionPoint(cleanInjectionInsn, comparisonResult.cleanLabel(), hunkLabels, methodContext))
.orElseGet(() -> handleTargetModification(hunkLabels, methodContext));
}

Expand Down Expand Up @@ -85,6 +86,26 @@ public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodCont
return Patch.Result.PASS;
}

private static Patch.Result handleWrapOpertationNewInjectionPoint(AbstractInsnNode cleanInjectionInsn, List<AbstractInsnNode> cleanLabel, List<List<AbstractInsnNode>> hunkLabels, MethodContext methodContext) {
if (!(cleanInjectionInsn instanceof MethodInsnNode minsn) || hunkLabels.size() != 1) {
return Patch.Result.PASS;
}
Type cleanReturnType = Type.getReturnType(minsn.desc);
List<AbstractInsnNode> dirtyLabel = hunkLabels.getFirst();
List<String> cleanMethodCalls = cleanLabel.stream().filter(i -> i instanceof MethodInsnNode m && m.owner.equals(minsn.owner) && Type.getReturnType(m.desc).equals(cleanReturnType)).map(i -> ((MethodInsnNode) i).name).toList();
List<MethodInsnNode> methodCalls = dirtyLabel.stream()
.filter(i -> i instanceof MethodInsnNode m && m.owner.equals(minsn.owner) && Type.getReturnType(m.desc).equals(cleanReturnType) && !cleanMethodCalls.contains(m.name))
.map(i -> (MethodInsnNode) i)
.toList();
if (methodCalls.size() != 1) {
return Patch.Result.PASS;
}
MethodInsnNode dirtyMinsn = methodCalls.getFirst();
return BundledMethodTransform.builder()
.modifyInjectionPoint("INVOKE", MethodCallAnalyzer.getCallQualifier(dirtyMinsn))
.apply(methodContext);
}

private static Patch.Result handleWrapOperationToInstanceOf(AbstractInsnNode cleanInjectionInsn, List<AbstractInsnNode> cleanLabel, List<List<AbstractInsnNode>> hunkLabels, MethodContext methodContext) {
if (!(cleanInjectionInsn instanceof MethodInsnNode minsn) || hunkLabels.size() != 1 || !(cleanLabel.getLast() instanceof JumpInsnNode) || cleanLabel.stream().anyMatch(i -> i instanceof TypeInsnNode)) {
return Patch.Result.PASS;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,13 @@ public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodCont
int nextOp = insn.getNext().getOpcode();
if (bfu != null && nextOp != Opcodes.IFNULL && nextOp != Opcodes.IFNONNULL) {
TypeAdapter typeFix = bfu.getTypeAdapter(type, originalType);
// If this is a wrap operation, make an educated guess and try adapting the instance type
if (typeFix == null && annotation.matchesDesc(MixinConstants.WRAP_OPERATION)) {
typeFix = bfu.getTypeAdapter(params[0], originalType);
if (typeFix != null) {
varInsn.var = lvtLookup.getByParameterOrdinal(0).index;
}
}
if (typeFix != null) {
typeFix.apply(methodNode.instructions, varInsn);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

import org.sinytra.adapter.patch.api.MethodTransform;
import org.sinytra.adapter.patch.api.MethodTransformBuilder;
import org.sinytra.adapter.patch.transformer.*;
import org.sinytra.adapter.patch.transformer.ModifyVarUpgradeToModifyExprVal;
import org.sinytra.adapter.patch.transformer.operation.*;
import org.sinytra.adapter.patch.transformer.operation.param.TransformParameters;

Expand Down Expand Up @@ -80,4 +80,16 @@ public T chain(Consumer<T> consumer) {
private T coerce() {
return (T) this;
}

public static class ClassImpl<T extends MethodTransformBuilder.Class<T>> extends MethodTransformBuilderImpl<T> implements MethodTransformBuilder.Class<T> {
@Override
public T modifyInjectionPoint(String value, String target, boolean resetValues) {
return modifyInjectionPoint(value, target, resetValues, false);
}

@Override
public T modifyInjectionPoint(String value, String target, boolean resetValues, boolean dontUpgrade) {
return transform(new ModifyInjectionPoint(value, target, resetValues, dontUpgrade));
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.sinytra.adapter.patch.test.mixin;

import org.objectweb.asm.Opcodes;
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.MethodInsnNode;
import org.sinytra.adapter.patch.fixes.BytecodeFixerUpper;
import org.sinytra.adapter.patch.fixes.SimpleTypeAdapter;
import org.sinytra.adapter.patch.fixes.TypeAdapter;

import java.util.List;
import java.util.Map;

public class BytecodeFixerUpperTestFrontend {
private static final List<TypeAdapter> FIELD_TYPE_ADAPTERS = List.of(
new SimpleTypeAdapter(Type.getObjectType("net/minecraft/world/item/ItemStack"), Type.getObjectType("net/minecraft/world/item/Item"), (list, insn) ->
list.insert(insn, new MethodInsnNode(Opcodes.INVOKEVIRTUAL, "net/minecraft/world/item/ItemStack", "getItem", "()Lnet/minecraft/world/item/Item;")))
);

private final BytecodeFixerUpper bfu;

public BytecodeFixerUpperTestFrontend() {
this.bfu = new BytecodeFixerUpper(Map.of(), FIELD_TYPE_ADAPTERS);
}

public BytecodeFixerUpper unwrap() {
return this.bfu;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -184,6 +184,26 @@ void testModifiedToInstanceOfCall() throws Exception {
);
}

@Test
void testModifiedWrapOperationTarget() throws Exception {
assertSameCode(
"org/sinytra/adapter/test/mixin/PumpkinBlockMixin",
"isShears",
assertTargetMethod(),
assertInjectionPoint()
);
}

@Test
void testModifiedWrapOperationTarget2() throws Exception {
assertSameCode(
"org/sinytra/adapter/test/mixin/TripWireBlockMixin",
"isShears",
assertTargetMethod(),
assertInjectionPoint()
);
}

@Override
protected LoadResult load(String className, List<String> allowedMethods) throws Exception {
final ClassNode patched = loadClass(className);
Expand All @@ -201,7 +221,7 @@ public void copyEntries(String from, String to) {
},
createCleanLookup(),
createDirtyLookup(),
null,
new BytecodeFixerUpperTestFrontend().unwrap(),
FabricUtil.COMPATIBILITY_LATEST
);
DYNAMIC_PATCHES.forEach(p -> p.apply(patched, env));
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.sinytra.adapter.test.mixin;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.PumpkinBlock;
import net.neoforged.neoforge.common.ItemAbility;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;

@Mixin(PumpkinBlock.class)
public class PumpkinBlockMixin {
// https://github.com/quiqueck/BCLib/blob/53349085e5d3adb20a023f29d9aab85acce58332/src/main/java/org/betterx/bclib/mixin/common/shears/PumpkinBlockMixin.java#L17
@WrapOperation(method = "useItemOn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;is(Lnet/minecraft/world/item/Item;)Z"))
private boolean isShears(ItemStack instance, Item item, Operation<Boolean> original) {
return original.call(instance, item) || item == Items.SHEARS && !instance.isEmpty();
}

@WrapOperation(method = "useItemOn", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;canPerformAction(Lnet/neoforged/neoforge/common/ItemAbility;)Z"))
private boolean isShearsExpected(ItemStack instance, ItemAbility item, Operation<Boolean> original) {
return original.call(instance, item) || instance.getItem() == Items.SHEARS && !instance.isEmpty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package org.sinytra.adapter.test.mixin;

import com.llamalad7.mixinextras.injector.wrapoperation.Operation;
import com.llamalad7.mixinextras.injector.wrapoperation.WrapOperation;
import net.minecraft.world.item.Item;
import net.minecraft.world.item.ItemStack;
import net.minecraft.world.item.Items;
import net.minecraft.world.level.block.TripWireBlock;
import net.neoforged.neoforge.common.ItemAbility;
import org.spongepowered.asm.mixin.Mixin;
import org.spongepowered.asm.mixin.injection.At;

@Mixin(TripWireBlock.class)
public class TripWireBlockMixin {
// https://github.com/quiqueck/BCLib/blob/53349085e5d3adb20a023f29d9aab85acce58332/src/main/java/org/betterx/bclib/mixin/common/shears/TripWireBlockMixin.java#L17
@WrapOperation(method = "playerWillDestroy", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;is(Lnet/minecraft/world/item/Item;)Z"))
private boolean isShears(ItemStack instance, Item item, Operation<Boolean> original) {
return original.call(instance, item) || item == Items.SHEARS && instance.isEmpty();
}

@WrapOperation(method = "playerWillDestroy", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/item/ItemStack;canPerformAction(Lnet/neoforged/neoforge/common/ItemAbility;)Z"))
private boolean isShearsExpected(ItemStack instance, ItemAbility item, Operation<Boolean> original) {
return original.call(instance, item) || instance.getItem() == Items.SHEARS && instance.isEmpty();
}
}

0 comments on commit 2d5468d

Please sign in to comment.