Skip to content

Commit

Permalink
Propagate method cancellation in split methods
Browse files Browse the repository at this point in the history
  • Loading branch information
Su5eD committed Aug 9, 2024
1 parent 05e2aa1 commit 707feea
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 2 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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().<Boolean>getValue("cancellable").map(AnnotationValueHandle::get).orElse(false);
}

@Nullable
@Override
public List<LocalVariable> getTargetMethodLocals(TargetPair target) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,8 @@ public interface MethodContext {

boolean isStatic();

boolean isCancellable();

@Nullable
List<LocalVariable> getTargetMethodLocals(TargetPair target);

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

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

0 comments on commit 707feea

Please sign in to comment.