From 834857497fe338988cf72c0cee50fc720a062785 Mon Sep 17 00:00:00 2001 From: Su5eD Date: Sun, 21 Jul 2024 23:06:48 +0200 Subject: [PATCH] Dynamically resolve parameter type changes --- .../fixes/FieldTypeUsageTransformer.java | 4 +- .../transformer/SoftMethodParamsPatch.java | 2 +- .../dynfix/DynFixParameterTypeAdapter.java | 101 ++++++++++++++++++ .../dynfix/DynamicInjectionPointPatch.java | 3 +- .../test/mixin/DynamicMixinPatchTest.java | 11 ++ .../test/mixin/CrossbowAttackGoalMixin.java | 19 ++++ 6 files changed, 137 insertions(+), 3 deletions(-) create mode 100644 definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynFixParameterTypeAdapter.java diff --git a/definition/src/main/java/org/sinytra/adapter/patch/fixes/FieldTypeUsageTransformer.java b/definition/src/main/java/org/sinytra/adapter/patch/fixes/FieldTypeUsageTransformer.java index 9cd24d6..24f570c 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/fixes/FieldTypeUsageTransformer.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/fixes/FieldTypeUsageTransformer.java @@ -18,6 +18,8 @@ import java.util.List; import java.util.Map; +import static org.sinytra.adapter.patch.PatchInstance.MIXINPATCH; + public class FieldTypeUsageTransformer implements ClassTransform { private static final Logger LOGGER = LogUtils.getLogger(); @@ -89,7 +91,7 @@ public Patch.Result apply(ClassNode classNode, @Nullable AnnotationValueHandle updatedTypes, MethodNode method, FieldInsnNode finsn) { TypeAdapter typeAdapter = bfu.getTypeAdapter(updatedTypes.getSecond(), updatedTypes.getFirst()); if (typeAdapter != null) { - LOGGER.info("Running fixup for field {}.{}{} in method {}{}", finsn.owner, finsn.name, finsn.desc, method.name, method.desc); + LOGGER.debug(MIXINPATCH, "Running fixup for field {}.{}{} in method {}{}", finsn.owner, finsn.name, finsn.desc, method.name, method.desc); finsn.desc = updatedTypes.getSecond().getDescriptor(); typeAdapter.apply(method.instructions, finsn); return true; diff --git a/definition/src/main/java/org/sinytra/adapter/patch/transformer/SoftMethodParamsPatch.java b/definition/src/main/java/org/sinytra/adapter/patch/transformer/SoftMethodParamsPatch.java index db757b8..d3fd863 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/transformer/SoftMethodParamsPatch.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/transformer/SoftMethodParamsPatch.java @@ -31,7 +31,7 @@ public Codec codec() { @Override public Collection getAcceptedAnnotations() { - return Set.of(MixinConstants.INJECT); + return Set.of(MixinConstants.INJECT, MixinConstants.REDIRECT); } @Override diff --git a/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynFixParameterTypeAdapter.java b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynFixParameterTypeAdapter.java new file mode 100644 index 0000000..f89a0fc --- /dev/null +++ b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynFixParameterTypeAdapter.java @@ -0,0 +1,101 @@ +package org.sinytra.adapter.patch.transformer.dynfix; + +import com.mojang.datafixers.util.Pair; +import org.jetbrains.annotations.Nullable; +import org.objectweb.asm.Type; +import org.objectweb.asm.tree.*; +import org.sinytra.adapter.patch.analysis.LocalVariableLookup; +import org.sinytra.adapter.patch.analysis.MethodCallAnalyzer; +import org.sinytra.adapter.patch.analysis.params.EnhancedParamsDiff; +import org.sinytra.adapter.patch.analysis.params.LayeredParamsDiffSnapshot; +import org.sinytra.adapter.patch.analysis.params.ParamsDiffSnapshot; +import org.sinytra.adapter.patch.api.MethodContext; +import org.sinytra.adapter.patch.api.MixinConstants; +import org.sinytra.adapter.patch.api.Patch; +import org.sinytra.adapter.patch.transformer.BundledMethodTransform; +import org.sinytra.adapter.patch.transformer.operation.param.ParamTransformTarget; +import org.sinytra.adapter.patch.util.MethodQualifier; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import java.util.stream.StreamSupport; + +public class DynFixParameterTypeAdapter implements DynamicFixer { + public record Data(MethodInsnNode newCall, ParamsDiffSnapshot diff, Set> modifyCalls, MethodQualifier qualifier) {} + + @Nullable + @Override + public Data prepare(MethodContext methodContext) { + if (methodContext.methodAnnotation().matchesDesc(MixinConstants.REDIRECT)) { + MethodQualifier qualifier = methodContext.getInjectionPointMethodQualifier(); + if (qualifier == null) { + return null; + } + MethodContext.TargetPair dirtyPair = methodContext.findDirtyInjectionTarget(); + if (dirtyPair == null) { + return null; + } + List newMethodCalls = StreamSupport.stream(dirtyPair.methodNode().instructions.spliterator(), false) + .filter(i -> i instanceof MethodInsnNode minsn && minsn.owner.equals(qualifier.internalOwnerName()) && minsn.name.equals(qualifier.name()) && !minsn.desc.equals(qualifier.desc())) + .map(i -> (MethodInsnNode) i) + .toList(); + if (newMethodCalls.size() != 1) { + return null; + } + MethodInsnNode newCall = newMethodCalls.getFirst(); + LayeredParamsDiffSnapshot diff = EnhancedParamsDiff.createLayered(List.of(Type.getArgumentTypes(qualifier.desc())), List.of(Type.getArgumentTypes(newCall.desc))); + Set> modifyCalls = validateReplacements(methodContext, qualifier, diff.replacements()); + if (modifyCalls == null) { + return null; + } + return new Data(newCall, diff, modifyCalls, qualifier); + } + return null; + } + + @Override + public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodContext methodContext, Data data) { + Patch.Result result = BundledMethodTransform.builder() + .modifyInjectionPoint(MethodCallAnalyzer.getCallQualifier(data.newCall())) + .transform(data.diff().asParameterTransformer(ParamTransformTarget.INJECTION_POINT, false)) + .apply(methodContext); + if (result == Patch.Result.PASS) { + return Patch.Result.PASS; + } + + MethodQualifier qualifier = data.qualifier(); + for (List call : data.modifyCalls()) { + for (AbstractInsnNode insn : call) { + if (insn instanceof MethodInsnNode minsn && qualifier.matches(minsn)) { + minsn.desc = data.newCall().desc; + } + } + } + + return result.or(Patch.Result.APPLY); + } + + @Nullable + private static Set> validateReplacements(MethodContext methodContext, MethodQualifier qualifier, List> replacements) { + MethodNode mixinMethod = methodContext.getMixinMethod(); + List> insns = MethodCallAnalyzer.getInvocationInsns(mixinMethod, qualifier); + + LocalVariableLookup lookup = new LocalVariableLookup(methodContext.getMixinMethod()); + Set paramVars = replacements.stream().map(p -> lookup.getByParameterOrdinal(p.getFirst()).index).collect(Collectors.toSet()); + + Set> usedInsns = new HashSet<>(); + for (AbstractInsnNode insn : methodContext.getMixinMethod().instructions) { + if (insn instanceof VarInsnNode varInsn && paramVars.contains(varInsn.var)) { + List call = insns.stream().filter(l -> l.contains(varInsn)).findFirst().orElse(null); + if (call == null) { + return null; + } + usedInsns.add(call); + } + } + + return usedInsns; + } +} diff --git a/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynamicInjectionPointPatch.java b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynamicInjectionPointPatch.java index d5d8a8c..9ef11a2 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynamicInjectionPointPatch.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynamicInjectionPointPatch.java @@ -23,8 +23,9 @@ public class DynamicInjectionPointPatch implements MethodTransform { new DynFixSliceBoundary(), new DynFixAtVariableAssignStore(), new DynFixSplitMethod(), - // Have this one always come last + new DynFixParameterTypeAdapter(), new DynFixMethodComparison(), + // Have this one always come last new DynFixArbitraryInjectionPoint() ); diff --git a/test/src/test/java/org/sinytra/adapter/patch/test/mixin/DynamicMixinPatchTest.java b/test/src/test/java/org/sinytra/adapter/patch/test/mixin/DynamicMixinPatchTest.java index 1d2c715..93ab390 100644 --- a/test/src/test/java/org/sinytra/adapter/patch/test/mixin/DynamicMixinPatchTest.java +++ b/test/src/test/java/org/sinytra/adapter/patch/test/mixin/DynamicMixinPatchTest.java @@ -6,6 +6,7 @@ import org.sinytra.adapter.patch.api.PatchEnvironment; import org.sinytra.adapter.patch.api.RefmapHolder; import org.sinytra.adapter.patch.fixes.FieldTypeUsageTransformer; +import org.sinytra.adapter.patch.transformer.SoftMethodParamsPatch; import org.sinytra.adapter.patch.transformer.dynfix.DynamicInjectionPointPatch; import org.sinytra.adapter.patch.util.provider.ClassLookup; import org.spongepowered.asm.mixin.FabricUtil; @@ -215,6 +216,16 @@ void testModifiedFieldType() throws Exception { ); } + @Test + void testDynamicParameterTypeAdapter() throws Exception { + assertSameCode( + "org/sinytra/adapter/test/mixin/CrossbowAttackGoalMixin", + "redirectedGetHandPossiblyHolding", + assertTargetMethod(), + assertInjectionPoint() + ); + } + @Override protected LoadResult load(String className, List allowedMethods) throws Exception { final ClassNode patched = loadClass(className); diff --git a/test/src/testClasses/java/org/sinytra/adapter/test/mixin/CrossbowAttackGoalMixin.java b/test/src/testClasses/java/org/sinytra/adapter/test/mixin/CrossbowAttackGoalMixin.java index ae595ae..4169ce4 100644 --- a/test/src/testClasses/java/org/sinytra/adapter/test/mixin/CrossbowAttackGoalMixin.java +++ b/test/src/testClasses/java/org/sinytra/adapter/test/mixin/CrossbowAttackGoalMixin.java @@ -1,13 +1,21 @@ package org.sinytra.adapter.test.mixin; +import net.minecraft.world.InteractionHand; +import net.minecraft.world.entity.LivingEntity; import net.minecraft.world.entity.Mob; import net.minecraft.world.entity.ai.goal.Goal; import net.minecraft.world.entity.ai.goal.RangedCrossbowAttackGoal; import net.minecraft.world.entity.monster.CrossbowAttackMob; import net.minecraft.world.entity.monster.Monster; +import net.minecraft.world.entity.projectile.ProjectileUtil; +import net.minecraft.world.item.Item; import org.spongepowered.asm.mixin.Final; import org.spongepowered.asm.mixin.Mixin; import org.spongepowered.asm.mixin.Shadow; +import org.spongepowered.asm.mixin.injection.At; +import org.spongepowered.asm.mixin.injection.Redirect; + +import java.util.function.Predicate; @Mixin(RangedCrossbowAttackGoal.class) public abstract class CrossbowAttackGoalMixin extends Goal { @@ -18,4 +26,15 @@ public abstract class CrossbowAttackGoalMixin item) { + return ProjectileUtil.getWeaponHoldingHand(entity, item); + } }