Skip to content

Commit

Permalink
Handle modifiers of misplaced local variables
Browse files Browse the repository at this point in the history
  • Loading branch information
Su5eD committed Apr 11, 2024
1 parent 88a2fe1 commit 1d1c65b
Showing 1 changed file with 110 additions and 11 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,13 @@
import org.objectweb.asm.Type;
import org.objectweb.asm.tree.*;
import org.sinytra.adapter.patch.PatchInstance;
import org.sinytra.adapter.patch.analysis.InstructionMatcher;
import org.sinytra.adapter.patch.analysis.LocalVarAnalyzer;
import org.sinytra.adapter.patch.analysis.MethodCallAnalyzer;
import org.sinytra.adapter.patch.analysis.*;
import org.sinytra.adapter.patch.api.*;
import org.sinytra.adapter.patch.selector.AnnotationHandle;
import org.sinytra.adapter.patch.selector.AnnotationValueHandle;
import org.sinytra.adapter.patch.util.AdapterUtil;
import org.sinytra.adapter.patch.util.GeneratedVariables;
import org.sinytra.adapter.patch.analysis.InsnComparator;
import org.sinytra.adapter.patch.analysis.LocalVariableLookup;
import org.sinytra.adapter.patch.util.SingleValueHandle;
import org.slf4j.Logger;

import java.util.*;
Expand Down Expand Up @@ -50,7 +48,7 @@ public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodCont
boolean applied = false;
for (HandlerInstance instance : offsetHandlers) {
AnnotationValueHandle<Integer> ordinal = instance.ordinal();
OptionalInt updatedOrdinal = instance.handler().getUpdatedOrdinal(context, methodNode, instance.target(), cleanTarget, dirtyTarget, ordinal.get());
OptionalInt updatedOrdinal = instance.handler().getUpdatedOrdinal(methodContext, methodNode, instance.target(), cleanTarget, dirtyTarget, ordinal.get());
if (updatedOrdinal.isPresent()) {
int index = updatedOrdinal.getAsInt();
LOGGER.info(PatchInstance.MIXINPATCH, "Updating injection point ordinal of {}.{} from {} to {}", classNode.name, methodNode.name, ordinal.get(), index);
Expand Down Expand Up @@ -88,7 +86,7 @@ default boolean requiresTarget() {
return false;
}

OptionalInt getUpdatedOrdinal(PatchContext context, MethodNode methodNode, String target, MethodContext.TargetPair cleanTarget, MethodContext.TargetPair dirtyTarget, int ordinal);
OptionalInt getUpdatedOrdinal(MethodContext methodContext, MethodNode methodNode, String target, MethodContext.TargetPair cleanTarget, MethodContext.TargetPair dirtyTarget, int ordinal);
}

private static class InvokeOffsetHandler implements OffsetHandler {
Expand All @@ -100,10 +98,11 @@ public boolean requiresTarget() {
}

@Override
public OptionalInt getUpdatedOrdinal(PatchContext context, MethodNode methodNode, String target, MethodContext.TargetPair cleanTarget, MethodContext.TargetPair dirtyTarget, int ordinal) {
public OptionalInt getUpdatedOrdinal(MethodContext methodContext, MethodNode methodNode, String target, MethodContext.TargetPair cleanTarget, MethodContext.TargetPair dirtyTarget, int ordinal) {
Multimap<String, MethodInsnNode> cleanCallsMap = MethodCallAnalyzer.getMethodCalls(cleanTarget.methodNode(), new ArrayList<>());
Multimap<String, MethodInsnNode> dirtyCallsMap = MethodCallAnalyzer.getMethodCalls(dirtyTarget.methodNode(), new ArrayList<>());

PatchContext context = methodContext.patchContext();
String cleanValue = context.remap(target);
Collection<? extends AbstractInsnNode> cleanCalls = cleanCallsMap.get(cleanValue);
String dirtyValue = context.remap(target);
Expand All @@ -124,6 +123,8 @@ public OptionalInt getUpdatedOrdinal(PatchContext context, MethodNode methodNode
}
}
}


return OptionalInt.empty();
}
}
Expand All @@ -133,7 +134,7 @@ private static class ReturnOffsetHandler implements OffsetHandler {
private static final Set<Integer> RETURN_OPCODES = Set.of(Opcodes.RETURN, Opcodes.ARETURN, Opcodes.IRETURN, Opcodes.FRETURN, Opcodes.DRETURN, Opcodes.LRETURN);

@Override
public OptionalInt getUpdatedOrdinal(PatchContext context, MethodNode methodNode, String target, MethodContext.TargetPair cleanTarget, MethodContext.TargetPair dirtyTarget, int ordinal) {
public OptionalInt getUpdatedOrdinal(MethodContext methodContext, MethodNode methodNode, String target, MethodContext.TargetPair cleanTarget, MethodContext.TargetPair dirtyTarget, int ordinal) {
List<AbstractInsnNode> cleanReturnInsns = findReturnInsns(cleanTarget.methodNode());
List<AbstractInsnNode> dirtyReturnInsns = findReturnInsns(dirtyTarget.methodNode());

Expand Down Expand Up @@ -186,16 +187,113 @@ private static class ModifyVariableOffsetHandler implements OffsetHandler {
private static final OffsetHandler INSTANCE = new ModifyVariableOffsetHandler();

@Override
public OptionalInt getUpdatedOrdinal(PatchContext context, MethodNode methodNode, String target, MethodContext.TargetPair cleanTarget, MethodContext.TargetPair dirtyTarget, int ordinal) {
public OptionalInt getUpdatedOrdinal(MethodContext methodContext, MethodNode methodNode, String target, MethodContext.TargetPair cleanTarget, MethodContext.TargetPair dirtyTarget, int ordinal) {
Type[] args = Type.getArgumentTypes(methodNode.desc);
if (args.length < 1) {
return OptionalInt.empty();
}
Type targetType = args[0];
// Gradually expand supported types over time as necessary
if (targetType != Type.BOOLEAN_TYPE && targetType != Type.INT_TYPE) {
if (targetType != Type.BOOLEAN_TYPE && targetType != Type.INT_TYPE && targetType != Type.FLOAT_TYPE) {
return OptionalInt.empty();
}

OptionalInt updatedIndex = tryFindUpdatedIndex(targetType, cleanTarget, dirtyTarget, ordinal);
if (updatedIndex.isPresent()) {
return updatedIndex;
}

OptionalInt synthVarIndex = tryFindSyntheticVariableIndex(methodContext, methodNode, cleanTarget, dirtyTarget, ordinal);
if (synthVarIndex.isPresent()) {
return synthVarIndex;
}

return OptionalInt.empty();
}

/**
* Handle situations where a mixin is attempting to modify a variable that is used immediately after its modified.
* However, due to the nature of binary patches, a new variable might have been introduced earlier in the method, which is being used in its place now.
* In these cases, we'll find the new variable and update the mixin's index
* <p>
* As an example, let's have a look at LivingEntity#actuallyHurt
* <pre>{@code
* == Original code ==
* INVOKEVIRTUAL net/minecraft/world/entity/player/Player.getHealth ()F
* FLOAD 2
* FSUB
* INVOKEVIRTUAL net/minecraft/world/entity/player/Player.setHealth (F)V
* == Patched code ==
* >> INVOKESPECIAL <modifyvar>
* >> FSTORE 2
* INVOKEVIRTUAL net/minecraft/world/entity/player/Player.getHealth ()F
* != FLOAD 3
* FSUB
* INVOKEVIRTUAL net/minecraft/world/entity/player/Player.setHealth (F)V
* == Resulting code ==
* >> INVOKESPECIAL <modifyvar>
* >> FSTORE 3
* INVOKEVIRTUAL net/minecraft/world/entity/player/Player.getHealth ()F
* FLOAD 3
* FSUB
* INVOKEVIRTUAL net/minecraft/world/entity/player/Player.setHealth (F)V
* }</pre>
*/
private static OptionalInt tryFindSyntheticVariableIndex(MethodContext methodContext, MethodNode methodNode, MethodContext.TargetPair cleanTarget, MethodContext.TargetPair dirtyTarget, int ordinal) {
Type variableType = Type.getReturnType(methodNode.desc);
LocalVariableLookup cleanTable = new LocalVariableLookup(cleanTarget.methodNode());
LocalVariableLookup dirtyTable = new LocalVariableLookup(dirtyTarget.methodNode());
if (cleanTable.getForType(variableType).size() == dirtyTable.getForType(variableType).size()) {
List<LocalVariableNode> available = dirtyTable.getForType(variableType);
if (available.size() > ordinal) {
int variableIndex = available.get(ordinal).index;
List<AbstractInsnNode> cleanInsns = methodContext.findInjectionTargetInsns(cleanTarget);
List<AbstractInsnNode> dirtyInsns = methodContext.findInjectionTargetInsns(dirtyTarget);
if (cleanInsns.size() == 1 && dirtyInsns.size() == 1) {
for (AbstractInsnNode insn = cleanInsns.get(0); insn != null; insn = insn.getNext()) {
if (insn instanceof LabelNode) {
break;
}
SingleValueHandle<Integer> handle = AdapterUtil.handleLocalVarInsnValue(insn);
if (handle != null && handle.get() == variableIndex) {
// We found out the variable is used right after our injection point
// Now let's check if it its index remain the same in the dirty target
List<SingleValueHandle<Integer>> dirtyVars = getUsedVariablesInLabel(dirtyInsns.get(0), insn.getOpcode());
if (dirtyVars.size() == 1) {
int dirtyIndex = dirtyVars.get(0).get();
if (dirtyIndex != variableIndex) {
methodContext.methodAnnotation().<Boolean>getValue("argsOnly")
.ifPresent(h -> h.set(false));
// Find new ordinal by index
return dirtyTable.getTypedOrdinal(dirtyTable.getByIndex(dirtyIndex));
}
}
break;
}
}
}
}
}
return OptionalInt.empty();
}

private static List<SingleValueHandle<Integer>> getUsedVariablesInLabel(AbstractInsnNode start, int opcode) {
List<SingleValueHandle<Integer>> list = new ArrayList<>();
for (AbstractInsnNode insn = start; insn != null; insn = insn.getNext()) {
if (insn instanceof LabelNode) {
break;
}
if (insn.getOpcode() == opcode) {
SingleValueHandle<Integer> handle = AdapterUtil.handleLocalVarInsnValue(insn);
if (handle != null) {
list.add(handle);
}
}
}
return list;
}

private static OptionalInt tryFindUpdatedIndex(Type targetType, MethodContext.TargetPair cleanTarget, MethodContext.TargetPair dirtyTarget, int ordinal) {
List<LocalVariableNode> cleanLocals = cleanTarget.methodNode().localVariables.stream()
.filter(l -> Type.getType(l.desc) == targetType)
.sorted(Comparator.comparingInt(l -> l.index))
Expand Down Expand Up @@ -226,6 +324,7 @@ public OptionalInt getUpdatedOrdinal(PatchContext context, MethodNode methodNode
if (actual.size() == 1) {
return OptionalInt.of(dirtyLocals.indexOf(actual.get(0)));
}

return OptionalInt.empty();
}

Expand Down

0 comments on commit 1d1c65b

Please sign in to comment.