From 90d8d57d79f91468a30258aca13b5809df92d60e Mon Sep 17 00:00:00 2001 From: Su5eD Date: Wed, 24 Jan 2024 17:03:08 +0100 Subject: [PATCH] Transform redirects into wrap operations --- .../adapter/patch/ClassPatchInstance.java | 22 ++++-- .../patch/analysis/MethodCallAnalyzer.java | 14 ++++ .../sinytra/adapter/patch/api/Patch.java | 2 + .../transformer/ModifyDelegatingRedirect.java | 68 ++++++++++++++++--- .../param/ParameterTransformer.java | 17 +---- .../adapter/patch/util/OpcodeUtil.java | 25 +++++++ 6 files changed, 120 insertions(+), 28 deletions(-) diff --git a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/ClassPatchInstance.java b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/ClassPatchInstance.java index f969351..7b5bb2d 100644 --- a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/ClassPatchInstance.java +++ b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/ClassPatchInstance.java @@ -7,12 +7,12 @@ import dev.su5ed.sinytra.adapter.patch.api.MethodTransform; import dev.su5ed.sinytra.adapter.patch.api.MixinConstants; import dev.su5ed.sinytra.adapter.patch.api.PatchEnvironment; -import dev.su5ed.sinytra.adapter.patch.selector.*; +import dev.su5ed.sinytra.adapter.patch.selector.AnnotationHandle; +import dev.su5ed.sinytra.adapter.patch.selector.AnnotationValueHandle; +import dev.su5ed.sinytra.adapter.patch.selector.InjectionPointMatcher; +import dev.su5ed.sinytra.adapter.patch.selector.MethodMatcher; import dev.su5ed.sinytra.adapter.patch.serialization.MethodTransformSerialization; -import dev.su5ed.sinytra.adapter.patch.transformer.DivertRedirectorTransform; -import dev.su5ed.sinytra.adapter.patch.transformer.DisableMixin; -import dev.su5ed.sinytra.adapter.patch.transformer.ModifyInjectionPoint; -import dev.su5ed.sinytra.adapter.patch.transformer.RedirectShadowMethod; +import dev.su5ed.sinytra.adapter.patch.transformer.*; import dev.su5ed.sinytra.adapter.patch.util.MethodQualifier; import org.objectweb.asm.commons.InstructionAdapter; import org.objectweb.asm.tree.AnnotationNode; @@ -142,6 +142,18 @@ public ClassPatchBuilder divertRedirector(Consumer patcher) return transform(new DivertRedirectorTransform(patcher)); } + @Override + public ClassPatchBuilder updateRedirectTarget(String originalTarget, String newTarget) { + return targetInjectionPoint(originalTarget) + .transform(new ModifyDelegatingRedirect( + MethodQualifier.create(originalTarget).orElseThrow(), + MethodQualifier.create(newTarget).orElseThrow() + )) + .modifyMixinType(MixinConstants.WRAP_OPERATION, b -> b + .sameTarget() + .injectionPoint("INVOKE", newTarget)); + } + @Override public ClassPatchBuilder disable() { return transform(DisableMixin.INSTANCE); diff --git a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/analysis/MethodCallAnalyzer.java b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/analysis/MethodCallAnalyzer.java index 5474aa7..160a0b8 100644 --- a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/analysis/MethodCallAnalyzer.java +++ b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/analysis/MethodCallAnalyzer.java @@ -3,6 +3,7 @@ import com.google.common.collect.ImmutableMultimap; import com.google.common.collect.Multimap; import dev.su5ed.sinytra.adapter.patch.api.GlobalReferenceMapper; +import dev.su5ed.sinytra.adapter.patch.util.MethodQualifier; import org.jetbrains.annotations.Nullable; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; @@ -79,6 +80,19 @@ public static String getCallQualifier(MethodInsnNode insn) { return Type.getObjectType(insn.owner).getDescriptor() + insn.name + insn.desc; } + public static List> getInvocationInsns(MethodNode methodNode, MethodQualifier qualifier) { + return analyzeMethod(methodNode, (insn, values) -> qualifier.matches(insn) && values.size() > 1, (insn, values) -> { + List insns = new ArrayList<>(); + for (SourceValue value : values) { + if (value.insns.size() == 1) { + insns.add(value.insns.iterator().next()); + } + } + insns.add(insn); + return insns; + }); + } + public static List analyzeMethod(MethodNode methodNode, NaryOperationHandler handler) { return analyzeMethod(methodNode, (insn, values) -> true, handler); } diff --git a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/api/Patch.java b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/api/Patch.java index 65b4d6f..63f8b56 100644 --- a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/api/Patch.java +++ b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/api/Patch.java @@ -122,6 +122,8 @@ default ClassPatchBuilder modifyInjectionPoint(String target) { ClassPatchBuilder redirectShadowMethod(String original, String target, BiConsumer callFixer); ClassPatchBuilder divertRedirector(Consumer patcher); + + ClassPatchBuilder updateRedirectTarget(String originalTarget, String newTarget); ClassPatchBuilder disable(); } diff --git a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/transformer/ModifyDelegatingRedirect.java b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/transformer/ModifyDelegatingRedirect.java index 452b0a3..2a815e0 100644 --- a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/transformer/ModifyDelegatingRedirect.java +++ b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/transformer/ModifyDelegatingRedirect.java @@ -1,9 +1,14 @@ package dev.su5ed.sinytra.adapter.patch.transformer; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Lists; import dev.su5ed.sinytra.adapter.patch.analysis.MethodCallAnalyzer; import dev.su5ed.sinytra.adapter.patch.api.*; +import dev.su5ed.sinytra.adapter.patch.util.AdapterUtil; import dev.su5ed.sinytra.adapter.patch.util.LocalVariableLookup; import dev.su5ed.sinytra.adapter.patch.util.MethodQualifier; +import dev.su5ed.sinytra.adapter.patch.util.OpcodeUtil; +import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.*; @@ -14,7 +19,9 @@ import java.util.stream.Collectors; import java.util.stream.IntStream; -public record ModifyDelegatingRedirect(MethodQualifier target) implements MethodTransform { +public record ModifyDelegatingRedirect(MethodQualifier originalTarget, MethodQualifier newTarget) implements MethodTransform { + private static final Type OPERATION_TYPE = Type.getObjectType("com/llamalad7/mixinextras/injector/wrapoperation/Operation"); + @Override public Collection getAcceptedAnnotations() { return Set.of(MixinConstants.REDIRECT); @@ -23,27 +30,72 @@ public Collection getAcceptedAnnotations() { @Override public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodContext methodContext, PatchContext context) { LocalVariableLookup lookup = new LocalVariableLookup(methodNode.localVariables); - Set paramLvtIndices = IntStream.range(1, Type.getArgumentTypes(methodNode.desc).length) + Type newOwnerType = Type.getType(this.newTarget.owner()); + boolean sameOwnerType = Type.getType(this.originalTarget.owner()).equals(newOwnerType); + + int offset = ((methodNode.access & Opcodes.ACC_STATIC) == 0 ? 1 : 0) + + (!sameOwnerType ? -1 : 0); + Set paramLvtIndices = IntStream.range(offset + 1, Type.getArgumentTypes(methodNode.desc).length + offset) .map(i -> lookup.getByOrdinal(i).index) .boxed() .collect(Collectors.toSet()); - List insns = MethodCallAnalyzer.analyzeMethod(methodNode, (insn, values) -> this.target.matches(insn) && values.size() > 1, (insn, values) -> MethodCallAnalyzer.getSingleInsn(values, 1)); + List> insns = MethodCallAnalyzer.getInvocationInsns(methodNode, this.originalTarget); if (insns.isEmpty()) { return Patch.Result.PASS; } + List flatInsns = insns.stream().flatMap(Collection::stream).toList(); Set loadedLocals = insns.stream() .map(i -> i instanceof VarInsnNode varInsn ? varInsn.var : null) .filter(Objects::nonNull) .collect(Collectors.toSet()); for (AbstractInsnNode insn : methodNode.instructions) { - if (insn instanceof VarInsnNode varInsn && paramLvtIndices.contains(varInsn.var) && !loadedLocals.contains(varInsn.var)) { + if (insn instanceof VarInsnNode varInsn && paramLvtIndices.contains(varInsn.var) && !loadedLocals.contains(varInsn.var) && !flatInsns.contains(insn)) { return Patch.Result.PASS; } } - - - - return Patch.Result.PASS; + + Type returnType = Type.getReturnType(this.newTarget.desc()); + List args = ImmutableList.builder() + .add(sameOwnerType ? new Type[0] : new Type[]{newOwnerType}) + .add(Type.getArgumentTypes(this.newTarget.desc())) + .build(); + ModifyMethodParams patch = ModifyMethodParams.builder() + .chain(b -> { + for (int i = offset; i < Type.getArgumentTypes(methodNode.desc).length; i++) { + b.remove(i); + } + b.insert(1, OPERATION_TYPE); + for (Type type : Lists.reverse(args)) { + b.insert(1, type); + } + }) + .build(); + patch.apply(classNode, methodNode, methodContext, context); + + LocalVariableLookup updatedLookup = new LocalVariableLookup(methodNode.localVariables); + List localVars = List.of(updatedLookup.getByOrdinal(1)); // TODO Unhardcode + InsnList originalCallInsns = AdapterUtil.insnsWithAdapter(a -> { + a.load(offset + 2, OPERATION_TYPE); + a.iconst(localVars.size()); + a.newarray(Type.getObjectType("java/lang/Object")); + for (int i = 0; i < localVars.size(); i++) { + LocalVariableNode lvn = localVars.get(i); + Type type = Type.getType(lvn.desc); + + a.dup(); + a.iconst(i); + a.load(lvn.index, type); + a.visitInsn(Opcodes.AASTORE); + } + a.invokeinterface("com/llamalad7/mixinextras/injector/wrapoperation/Operation", "call", "([Ljava/lang/Object;)Ljava/lang/Object;"); + OpcodeUtil.castObjectType(returnType, a); + }); + for (List list : insns) { + methodNode.instructions.insertBefore(list.get(0), originalCallInsns); + list.forEach(methodNode.instructions::remove); + } + + return Patch.Result.APPLY; } } diff --git a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/transformer/param/ParameterTransformer.java b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/transformer/param/ParameterTransformer.java index e9fc3aa..ae7f921 100644 --- a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/transformer/param/ParameterTransformer.java +++ b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/transformer/param/ParameterTransformer.java @@ -2,7 +2,6 @@ import com.mojang.serialization.Codec; import dev.su5ed.sinytra.adapter.patch.api.MethodContext; -import dev.su5ed.sinytra.adapter.patch.api.MethodTransform; import dev.su5ed.sinytra.adapter.patch.api.MixinConstants; import dev.su5ed.sinytra.adapter.patch.api.Patch; import dev.su5ed.sinytra.adapter.patch.api.PatchContext; @@ -12,22 +11,10 @@ import dev.su5ed.sinytra.adapter.patch.util.MethodQualifier; import it.unimi.dsi.fastutil.ints.IntArrayList; import it.unimi.dsi.fastutil.ints.IntList; -import org.jetbrains.annotations.VisibleForTesting; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; -import org.objectweb.asm.tree.AbstractInsnNode; -import org.objectweb.asm.tree.ClassNode; -import org.objectweb.asm.tree.InsnList; -import org.objectweb.asm.tree.InsnNode; -import org.objectweb.asm.tree.MethodInsnNode; -import org.objectweb.asm.tree.MethodNode; -import org.objectweb.asm.tree.TypeInsnNode; -import org.objectweb.asm.tree.VarInsnNode; -import org.objectweb.asm.util.Textifier; -import org.objectweb.asm.util.TraceMethodVisitor; - -import java.io.PrintWriter; -import java.io.StringWriter; +import org.objectweb.asm.tree.*; + import java.util.ArrayList; import java.util.HashMap; import java.util.List; diff --git a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/util/OpcodeUtil.java b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/util/OpcodeUtil.java index 227c33e..c90348e 100644 --- a/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/util/OpcodeUtil.java +++ b/definition/src/main/java/dev/su5ed/sinytra/adapter/patch/util/OpcodeUtil.java @@ -1,14 +1,28 @@ package dev.su5ed.sinytra.adapter.patch.util; +import org.objectweb.asm.MethodVisitor; import org.objectweb.asm.Opcodes; import org.objectweb.asm.Type; import org.objectweb.asm.tree.MethodNode; import java.util.List; +import java.util.Map; public class OpcodeUtil { public static final List INSN_TYPE_OFFSETS = List.of(Type.LONG, Type.FLOAT, Type.DOUBLE, Type.OBJECT); public static final List INSN_TYPE_OFFSETS_EXTENDED = List.of(Type.LONG, Type.FLOAT, Type.DOUBLE, Type.OBJECT, Type.VOID); + public static final Map BOXED_TYPES = Map.of( + Type.BYTE_TYPE, new BoxedType(byte.class, Byte.class), + Type.CHAR_TYPE, new BoxedType(char.class, Character.class), + Type.BOOLEAN_TYPE, new BoxedType(boolean.class, Boolean.class), + Type.SHORT_TYPE, new BoxedType(short.class, Short.class), + Type.INT_TYPE, new BoxedType(int.class, Integer.class), + Type.LONG_TYPE, new BoxedType(long.class, Long.class), + Type.FLOAT_TYPE, new BoxedType(float.class, Float.class), + Type.DOUBLE_TYPE, new BoxedType(double.class, Double.class) + ); + + public record BoxedType(Class primitiveClass, Class boxedClass) {} public static boolean isStoreOpcode(int opcode) { return opcode >= Opcodes.ISTORE && opcode <= Opcodes.ASTORE; @@ -33,4 +47,15 @@ public static int getReturnOpcode(MethodNode methodNode) { public static int getReturnOpcode(int sort) { return Opcodes.IRETURN + INSN_TYPE_OFFSETS_EXTENDED.indexOf(sort) + 1; } + + public static void castObjectType(Type to, MethodVisitor visitor) { + BoxedType boxed = BOXED_TYPES.get(to); + if (boxed != null) { + String boxedName = boxed.boxedClass().getName().replace('.', '/'); + String conversionMethod = boxed.primitiveClass().getName() + "Value"; + String conversionDesc = Type.getMethodDescriptor(to); + visitor.visitTypeInsn(Opcodes.CHECKCAST, boxedName); + visitor.visitMethodInsn(Opcodes.INVOKEVIRTUAL, boxedName, conversionMethod, conversionDesc, false); + } + } }