Skip to content

Commit

Permalink
Transform redirects into wrap operations
Browse files Browse the repository at this point in the history
  • Loading branch information
Su5eD committed Jan 24, 2024
1 parent b733514 commit 90d8d57
Show file tree
Hide file tree
Showing 6 changed files with 120 additions and 28 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -142,6 +142,18 @@ public ClassPatchBuilder divertRedirector(Consumer<InstructionAdapter> 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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -79,6 +80,19 @@ public static String getCallQualifier(MethodInsnNode insn) {
return Type.getObjectType(insn.owner).getDescriptor() + insn.name + insn.desc;
}

public static List<List<AbstractInsnNode>> getInvocationInsns(MethodNode methodNode, MethodQualifier qualifier) {
return analyzeMethod(methodNode, (insn, values) -> qualifier.matches(insn) && values.size() > 1, (insn, values) -> {
List<AbstractInsnNode> 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 <T> List<T> analyzeMethod(MethodNode methodNode, NaryOperationHandler<T> handler) {
return analyzeMethod(methodNode, (insn, values) -> true, handler);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,8 @@ default ClassPatchBuilder modifyInjectionPoint(String target) {
ClassPatchBuilder redirectShadowMethod(String original, String target, BiConsumer<MethodInsnNode, InsnList> callFixer);

ClassPatchBuilder divertRedirector(Consumer<InstructionAdapter> patcher);

ClassPatchBuilder updateRedirectTarget(String originalTarget, String newTarget);

ClassPatchBuilder disable();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -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.*;

Expand All @@ -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<String> getAcceptedAnnotations() {
return Set.of(MixinConstants.REDIRECT);
Expand All @@ -23,27 +30,72 @@ public Collection<String> getAcceptedAnnotations() {
@Override
public Patch.Result apply(ClassNode classNode, MethodNode methodNode, MethodContext methodContext, PatchContext context) {
LocalVariableLookup lookup = new LocalVariableLookup(methodNode.localVariables);
Set<Integer> 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<Integer> paramLvtIndices = IntStream.range(offset + 1, Type.getArgumentTypes(methodNode.desc).length + offset)
.map(i -> lookup.getByOrdinal(i).index)
.boxed()
.collect(Collectors.toSet());
List<AbstractInsnNode> insns = MethodCallAnalyzer.analyzeMethod(methodNode, (insn, values) -> this.target.matches(insn) && values.size() > 1, (insn, values) -> MethodCallAnalyzer.getSingleInsn(values, 1));
List<List<AbstractInsnNode>> insns = MethodCallAnalyzer.getInvocationInsns(methodNode, this.originalTarget);
if (insns.isEmpty()) {
return Patch.Result.PASS;
}
List<AbstractInsnNode> flatInsns = insns.stream().flatMap(Collection::stream).toList();
Set<Integer> 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<Type> args = ImmutableList.<Type>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<LocalVariableNode> 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<AbstractInsnNode> list : insns) {
methodNode.instructions.insertBefore(list.get(0), originalCallInsns);
list.forEach(methodNode.instructions::remove);
}

return Patch.Result.APPLY;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<Integer> INSN_TYPE_OFFSETS = List.of(Type.LONG, Type.FLOAT, Type.DOUBLE, Type.OBJECT);
public static final List<Integer> INSN_TYPE_OFFSETS_EXTENDED = List.of(Type.LONG, Type.FLOAT, Type.DOUBLE, Type.OBJECT, Type.VOID);
public static final Map<Type, BoxedType> 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;
Expand All @@ -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);
}
}
}

0 comments on commit 90d8d57

Please sign in to comment.