From 707feead81c185f8c47651d052ccab161129646b Mon Sep 17 00:00:00 2001 From: Su5eD Date: Fri, 9 Aug 2024 23:44:22 +0200 Subject: [PATCH] Propagate method cancellation in split methods --- .../adapter/patch/MethodContextImpl.java | 5 + .../adapter/patch/api/MethodContext.java | 2 + .../adapter/patch/api/MixinConstants.java | 1 + .../transformer/dynfix/DynFixSplitMethod.java | 3 + .../dynfix/SplitMethodCancellationHelper.java | 107 ++++++++++++++++++ .../adapter/patch/util/AdapterUtil.java | 3 +- 6 files changed, 119 insertions(+), 2 deletions(-) create mode 100644 definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/SplitMethodCancellationHelper.java diff --git a/definition/src/main/java/org/sinytra/adapter/patch/MethodContextImpl.java b/definition/src/main/java/org/sinytra/adapter/patch/MethodContextImpl.java index a61d175..01d9cb4 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/MethodContextImpl.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/MethodContextImpl.java @@ -142,6 +142,11 @@ public boolean isStatic() { return (this.methodNode.access & Opcodes.ACC_STATIC) != 0; } + @Override + public boolean isCancellable() { + return methodAnnotation().matchesDesc(MixinConstants.INJECT) && methodAnnotation().getValue("cancellable").map(AnnotationValueHandle::get).orElse(false); + } + @Nullable @Override public List getTargetMethodLocals(TargetPair target) { diff --git a/definition/src/main/java/org/sinytra/adapter/patch/api/MethodContext.java b/definition/src/main/java/org/sinytra/adapter/patch/api/MethodContext.java index 6213a94..adf323c 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/api/MethodContext.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/api/MethodContext.java @@ -61,6 +61,8 @@ public interface MethodContext { boolean isStatic(); + boolean isCancellable(); + @Nullable List getTargetMethodLocals(TargetPair target); diff --git a/definition/src/main/java/org/sinytra/adapter/patch/api/MixinConstants.java b/definition/src/main/java/org/sinytra/adapter/patch/api/MixinConstants.java index 9209581..818b5d3 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/api/MixinConstants.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/api/MixinConstants.java @@ -29,6 +29,7 @@ public class MixinConstants { // Misc public static final String MIXIN = "Lorg/spongepowered/asm/mixin/Mixin;"; public static final String AT = "Lorg/spongepowered/asm/mixin/injection/At;"; + public static final String AT_SHIFT = "Lorg/spongepowered/asm/mixin/injection/At$Shift;"; public static final String UNIQUE = "Lorg/spongepowered/asm/mixin/Unique;"; public static final String SHADOW = "Lorg/spongepowered/asm/mixin/Shadow;"; public static final String COERCE = "Lorg/spongepowered/asm/mixin/injection/Coerce;"; diff --git a/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynFixSplitMethod.java b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynFixSplitMethod.java index 148e6ba..5ce9d18 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynFixSplitMethod.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/DynFixSplitMethod.java @@ -45,6 +45,9 @@ public FixResult apply(ClassNode classNode, MethodNode methodNode, MethodContext MethodNode method = candidates.getFirst().method(); String newTarget = method.name + method.desc; methodContext.recordAudit(this, "Adjusting split method target to %s", newTarget); + if (methodContext.isCancellable()) { + SplitMethodCancellationHelper.handle(this, methodContext, method); + } return FixResult.of(new ModifyInjectionTarget(List.of(newTarget)).apply(methodContext), PatchAuditTrail.Match.FULL); } diff --git a/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/SplitMethodCancellationHelper.java b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/SplitMethodCancellationHelper.java new file mode 100644 index 0000000..f46abd6 --- /dev/null +++ b/definition/src/main/java/org/sinytra/adapter/patch/transformer/dynfix/SplitMethodCancellationHelper.java @@ -0,0 +1,107 @@ +package org.sinytra.adapter.patch.transformer.dynfix; + +import org.objectweb.asm.AnnotationVisitor; +import org.objectweb.asm.Label; +import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; +import org.objectweb.asm.commons.GeneratorAdapter; +import org.objectweb.asm.commons.Method; +import org.objectweb.asm.tree.*; +import org.sinytra.adapter.patch.api.MethodContext; +import org.sinytra.adapter.patch.api.MixinClassGenerator; +import org.sinytra.adapter.patch.api.MixinConstants; +import org.sinytra.adapter.patch.util.AdapterUtil; +import org.sinytra.adapter.patch.util.MethodQualifier; + +import java.util.List; + +public final class SplitMethodCancellationHelper { + + public static void handle(Object transform, MethodContext methodContext, MethodNode newTarget) { + MethodContext.TargetPair originalTarget = methodContext.findDirtyInjectionTarget(); + ClassNode originalClassTarget = originalTarget.classNode(); + MethodNode originalMethodTarget = originalTarget.methodNode(); + + if (!DynFixSplitMethod.isDirtyDeprecatedMethod(methodContext.findCleanInjectionTarget().methodNode(), originalMethodTarget) || Type.getReturnType(originalMethodTarget.desc) != Type.VOID_TYPE) { + return; + } + + MixinClassGenerator generator = methodContext.patchContext().environment().classGenerator(); + ClassNode generatedTarget = generator.getOrGenerateMixinClass(methodContext.getMixinClass(), originalClassTarget.name, null); + + List invocations = DynFixSplitMethod.collectMethodInvocations(originalClassTarget, originalMethodTarget); + if (invocations == null) { + return; + } + int index = invocations.indexOf(newTarget); + + // Generate field + String fieldName = "adapter$canceller$" + originalMethodTarget.name + "$" + AdapterUtil.randomString(5); + FieldNode trackerField = (FieldNode) generatedTarget.visitField(Opcodes.ACC_PRIVATE, fieldName, Type.BOOLEAN_TYPE.getDescriptor(), null, null); + + for (int i = index + 1; i < invocations.size(); i++) { + generateCancellerMethod(generatedTarget, trackerField, originalClassTarget, invocations.get(i), methodContext, i == invocations.size() - 1); + } + + methodContext.recordAudit(transform, "Generate cancellation handler mixin"); + } + + private static void generateCancellerMethod(ClassNode generatedTarget, FieldNode trackerField, ClassNode originalClassTarget, MethodNode newTarget, MethodContext methodContext, boolean reset) { + String name = methodContext.getMixinMethod().name + "$adapter$canceller$" + AdapterUtil.randomString(5); + String desc = Type.getMethodDescriptor(Type.VOID_TYPE, AdapterUtil.CI_TYPE); + MethodNode invokerMixinMethod = (MethodNode) generatedTarget.visitMethod(Opcodes.ACC_PRIVATE | (methodContext.isStatic() ? Opcodes.ACC_STATIC : 0), name, desc, null, null); + { + AnnotationVisitor injectAnn = invokerMixinMethod.visitAnnotation(MixinConstants.INJECT, true); + { + AnnotationVisitor methodValue = injectAnn.visitArray("method"); + methodValue.visit(null, newTarget.name + newTarget.desc); + methodValue.visitEnd(); + } + { + AnnotationVisitor atValue = injectAnn.visitArray("at"); + { + AnnotationVisitor atAnn = atValue.visitAnnotation(null, MixinConstants.AT); + atAnn.visit("value", "HEAD"); + atAnn.visitEnd(); + } + atValue.visitEnd(); + } + injectAnn.visit("cancellable", Boolean.TRUE); + injectAnn.visitEnd(); + } + // Generate logic + GeneratorAdapter gen = new GeneratorAdapter(invokerMixinMethod, invokerMixinMethod.access, invokerMixinMethod.name, invokerMixinMethod.desc); + Label endLabel = new Label(); + gen.newLabel(); + gen.loadThis(); + gen.getField(Type.getObjectType(generatedTarget.name), trackerField.name, Type.BOOLEAN_TYPE); + gen.visitJumpInsn(Opcodes.IFEQ, endLabel); + { + if (reset) { + gen.newLabel(); + gen.loadThis(); + gen.visitInsn(Opcodes.ICONST_0); + gen.putField(Type.getObjectType(generatedTarget.name), trackerField.name, Type.BOOLEAN_TYPE); + } + gen.newLabel(); + gen.loadArg(0); + gen.invokeVirtual(AdapterUtil.CI_TYPE, new Method("cancel", "()V")); + } + gen.visitLabel(endLabel); + gen.returnValue(); + gen.newLabel(); + gen.endMethod(); + // Modify mixin to set the field value + MethodQualifier qualifier = new MethodQualifier(AdapterUtil.CI_TYPE.getDescriptor(), "cancel", "()V"); + InsnList methodInsns = methodContext.getMixinMethod().instructions; + for (AbstractInsnNode insn : methodInsns) { + if (insn instanceof MethodInsnNode minsn && qualifier.matches(minsn)) { + InsnList list = new InsnList(); + list.add(new VarInsnNode(Opcodes.ALOAD, 0)); + list.add(new InsnNode(Opcodes.ICONST_1)); + list.add(new FieldInsnNode(Opcodes.PUTFIELD, originalClassTarget.name, trackerField.name, trackerField.desc)); + methodInsns.insertBefore(minsn, list); + } + } + } +} diff --git a/definition/src/main/java/org/sinytra/adapter/patch/util/AdapterUtil.java b/definition/src/main/java/org/sinytra/adapter/patch/util/AdapterUtil.java index 9627b51..5b87bae 100644 --- a/definition/src/main/java/org/sinytra/adapter/patch/util/AdapterUtil.java +++ b/definition/src/main/java/org/sinytra/adapter/patch/util/AdapterUtil.java @@ -272,6 +272,5 @@ public static Type getMixinCallableReturnType(MethodNode method) { return Type.getReturnType(method.desc) == Type.VOID_TYPE ? CI_TYPE : CIR_TYPE; } - private AdapterUtil() { - } + private AdapterUtil() {} }