Skip to content

Commit

Permalink
Dynamically resolve parameter type changes
Browse files Browse the repository at this point in the history
  • Loading branch information
Su5eD committed Jul 21, 2024
1 parent 80a5ada commit 8348574
Show file tree
Hide file tree
Showing 6 changed files with 137 additions and 3 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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();

Expand Down Expand Up @@ -89,7 +91,7 @@ public Patch.Result apply(ClassNode classNode, @Nullable AnnotationValueHandle<?
private static boolean runFieldFix(BytecodeFixerUpper bfu, Pair<Type, Type> 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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ public Codec<? extends MethodTransform> codec() {

@Override
public Collection<String> getAcceptedAnnotations() {
return Set.of(MixinConstants.INJECT);
return Set.of(MixinConstants.INJECT, MixinConstants.REDIRECT);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
@@ -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<DynFixParameterTypeAdapter.Data> {
public record Data(MethodInsnNode newCall, ParamsDiffSnapshot diff, Set<List<AbstractInsnNode>> 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<MethodInsnNode> 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<List<AbstractInsnNode>> 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<AbstractInsnNode> 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<List<AbstractInsnNode>> validateReplacements(MethodContext methodContext, MethodQualifier qualifier, List<Pair<Integer, Type>> replacements) {
MethodNode mixinMethod = methodContext.getMixinMethod();
List<List<AbstractInsnNode>> insns = MethodCallAnalyzer.getInvocationInsns(mixinMethod, qualifier);

LocalVariableLookup lookup = new LocalVariableLookup(methodContext.getMixinMethod());
Set<Integer> paramVars = replacements.stream().map(p -> lookup.getByParameterOrdinal(p.getFirst()).index).collect(Collectors.toSet());

Set<List<AbstractInsnNode>> usedInsns = new HashSet<>();
for (AbstractInsnNode insn : methodContext.getMixinMethod().instructions) {
if (insn instanceof VarInsnNode varInsn && paramVars.contains(varInsn.var)) {
List<AbstractInsnNode> call = insns.stream().filter(l -> l.contains(varInsn)).findFirst().orElse(null);
if (call == null) {
return null;
}
usedInsns.add(call);
}
}

return usedInsns;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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()
);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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<String> allowedMethods) throws Exception {
final ClassNode patched = loadClass(className);
Expand Down
Original file line number Diff line number Diff line change
@@ -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<T extends Monster & CrossbowAttackMob> extends Goal {
Expand All @@ -18,4 +26,15 @@ public abstract class CrossbowAttackGoalMixin<T extends Monster & CrossbowAttack
@Shadow
@Final
private Mob mobExpected;

// https://github.com/SolipIngen/minecraft.progressivearchery/blob/7af8bdb7ddc24d73163d17de082add89716755cb/src/main/java/solipingen/progressivearchery/mixin/entity/ai/goal/CrossbowAttackGoalMixin.java#L42
@Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/ProjectileUtil;getWeaponHoldingHand(Lnet/minecraft/world/entity/LivingEntity;Lnet/minecraft/world/item/Item;)Lnet/minecraft/world/InteractionHand;"))
private InteractionHand redirectedGetHandPossiblyHolding(LivingEntity entity, Item item) {
return ProjectileUtil.getWeaponHoldingHand(entity, item);
}

@Redirect(method = "tick", at = @At(value = "INVOKE", target = "Lnet/minecraft/world/entity/projectile/ProjectileUtil;getWeaponHoldingHand(Lnet/minecraft/world/entity/LivingEntity;Ljava/util/function/Predicate;)Lnet/minecraft/world/InteractionHand;"))
private InteractionHand redirectedGetHandPossiblyHoldingExpected(LivingEntity entity, Predicate<Item> item) {
return ProjectileUtil.getWeaponHoldingHand(entity, item);
}
}

0 comments on commit 8348574

Please sign in to comment.