From f9c98034902ea8499db318f4bdd5c953e56026bf Mon Sep 17 00:00:00 2001 From: Rongmario Date: Thu, 2 Jul 2026 17:00:14 +0100 Subject: [PATCH] Feature: Allow Coerce annotation to effect accessor return types --- .../AnnotatedMixinElementHandlerAccessor.java | 28 +++++++++++++++---- .../gen/AccessorGeneratorFieldSetter.java | 5 ++++ .../asm/mixin/gen/AccessorInfo.java | 17 ++++++++++- .../asm/mixin/gen/InvokerInfo.java | 7 +++-- 4 files changed, 47 insertions(+), 10 deletions(-) diff --git a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java index f91add4af..5175b41c1 100644 --- a/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java +++ b/src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerAccessor.java @@ -32,6 +32,7 @@ import org.spongepowered.asm.mixin.gen.AccessorInfo; import org.spongepowered.asm.mixin.gen.AccessorInfo.AccessorName; import org.spongepowered.asm.mixin.gen.AccessorInfo.AccessorType; +import org.spongepowered.asm.mixin.injection.Coerce; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelectorRemappable; import org.spongepowered.asm.mixin.injection.struct.MemberInfo; import org.spongepowered.asm.mixin.refmap.IMixinContext; @@ -60,8 +61,8 @@ class AnnotatedMixinElementHandlerAccessor extends AnnotatedMixinElementHandler static class AnnotatedElementAccessor extends AnnotatedElementExecutable { protected final boolean shouldRemap; - protected final TypeMirror returnType; + protected final boolean coerce; protected String targetName; @@ -69,6 +70,7 @@ public AnnotatedElementAccessor(ExecutableElement element, AnnotationHandle anno super(element, annotation, context, "value"); this.shouldRemap = shouldRemap; this.returnType = this.getElement().getReturnType(); + this.coerce = AnnotationHandle.of(element, Coerce.class).exists(); } public void attach(TypeHandle target) { @@ -94,10 +96,16 @@ public TypeMirror getTargetType() { } public String getTargetTypeName() { + if (this.coerce) { + return ""; + } return TypeUtils.getTypeName(this.getTargetType()); } public String getTargetDesc() { + if (this.coerce) { + return ""; + } return TypeUtils.getInternalName(this.getTargetType()); } @@ -187,6 +195,9 @@ public boolean shouldRemap() { @Override public String getTargetDesc() { + if (this.coerce) { + return ""; + } return this.getDesc(); } @@ -197,6 +208,9 @@ public AccessorType getAccessorType() { @Override public String getTargetTypeName() { + if (this.coerce) { + return ""; + } return TypeUtils.getJavaSignature(this.getElement()); } @@ -310,11 +324,13 @@ private void registerInvokerForTarget(AnnotatedElementInvoker elem, TypeHandle t } private void registerFactoryForTarget(AnnotatedElementInvoker elem, TypeHandle target) { - String returnType = TypeUtils.getTypeName(elem.getReturnType()); - if (!returnType.equals(target.toString())) { - elem.printMessage(this.ap, MessageType.FACTORY_INVOKER_RETURN_TYPE, "Invalid Factory @Invoker return type, expected " - + target + " but found " + returnType); - return; + if (!elem.coerce) { + String returnType = TypeUtils.getTypeName(elem.getReturnType()); + if (!returnType.equals(target.toString())) { + elem.printMessage(this.ap, MessageType.FACTORY_INVOKER_RETURN_TYPE, "Invalid Factory @Invoker return type, expected " + + target + " but found " + returnType); + return; + } } if (!elem.isStatic()) { elem.printMessage(this.ap, MessageType.FACTORY_INVOKER_NONSTATIC, "Factory @Invoker must be static"); diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldSetter.java b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldSetter.java index 96398d74f..4af87225f 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldSetter.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldSetter.java @@ -25,9 +25,11 @@ package org.spongepowered.asm.mixin.gen; import org.objectweb.asm.Opcodes; +import org.objectweb.asm.Type; import org.objectweb.asm.tree.FieldInsnNode; import org.objectweb.asm.tree.InsnNode; import org.objectweb.asm.tree.MethodNode; +import org.objectweb.asm.tree.TypeInsnNode; import org.objectweb.asm.tree.VarInsnNode; import org.spongepowered.asm.mixin.MixinEnvironment; import org.spongepowered.asm.mixin.gen.throwables.InvalidAccessorException; @@ -99,6 +101,9 @@ public MethodNode generate() { method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0)); } method.instructions.add(new VarInsnNode(this.targetType.getOpcode(Opcodes.ILOAD), stackSpace)); + if (this.info.isCoerceReturnType() && this.targetType.getSort() == Type.OBJECT) { + method.instructions.add(new TypeInsnNode(Opcodes.CHECKCAST, this.targetType.getInternalName())); + } int opcode = this.targetIsStatic ? Opcodes.PUTSTATIC : Opcodes.PUTFIELD; method.instructions.add(new FieldInsnNode(opcode, this.info.getTargetClassNode().name, this.targetField.name, this.targetField.desc)); method.instructions.add(new InsnNode(Opcodes.RETURN)); diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorInfo.java b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorInfo.java index d15d11b86..d070e8d79 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/AccessorInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/AccessorInfo.java @@ -38,6 +38,7 @@ import org.objectweb.asm.tree.MethodNode; import org.spongepowered.asm.mixin.MixinEnvironment.Option; import org.spongepowered.asm.mixin.gen.throwables.InvalidAccessorException; +import org.spongepowered.asm.mixin.injection.Coerce; import org.spongepowered.asm.mixin.injection.selectors.ElementNode; import org.spongepowered.asm.mixin.injection.selectors.ISelectorContext; import org.spongepowered.asm.mixin.injection.selectors.ITargetSelector; @@ -251,6 +252,11 @@ private static String getPrefixList() { * Accessor method staticness */ protected final boolean isStatic; + + /** + * Whether the return type should be coerced to a supertype + */ + protected final boolean coerceReturnType; /** * Name specified in the attached annotation, can be null @@ -300,6 +306,7 @@ protected AccessorInfo(MixinTargetContext mixin, MethodNode method, ClassgetValue(this.annotation); this.type = this.initType(); this.targetFieldType = this.initTargetFieldType(); @@ -334,7 +341,8 @@ protected Type initTargetFieldType() { } protected ITargetSelector initTarget() { - return new MemberInfo(this.getTargetName(this.specifiedName), null, this.targetFieldType.getDescriptor()); + String desc = this.coerceReturnType ? null : (this.targetFieldType != null ? this.targetFieldType.getDescriptor() : null); + return new MemberInfo(this.getTargetName(this.specifiedName), null, desc); } protected String getTargetName(String name) { @@ -460,6 +468,13 @@ public boolean isStatic() { return this.isStatic; } + /** + * Get whether the return type is coerced + */ + public boolean isCoerceReturnType() { + return this.coerceReturnType; + } + @Override public String toString() { String typeString = this.type != null ? this.type.toString() : "UNPARSED_ACCESSOR"; diff --git a/src/main/java/org/spongepowered/asm/mixin/gen/InvokerInfo.java b/src/main/java/org/spongepowered/asm/mixin/gen/InvokerInfo.java index 64b6def92..4edad11ad 100644 --- a/src/main/java/org/spongepowered/asm/mixin/gen/InvokerInfo.java +++ b/src/main/java/org/spongepowered/asm/mixin/gen/InvokerInfo.java @@ -69,7 +69,7 @@ protected AccessorType initType() { private AccessorType initType(String targetName, String targetClassName) { if (Constants.CTOR.equals(targetName) || targetClassName.equals(targetName)) { - if (!this.returnType.equals(this.mixin.getTargetClassInfo().getType())) { + if (!this.coerceReturnType && !this.returnType.equals(this.mixin.getTargetClassInfo().getType())) { throw new InvalidAccessorException(this.mixin, String.format("%s appears to have an invalid return type. %s requires matching return type. Found %s expected %s", this, AccessorType.OBJECT_FACTORY, Bytecode.getSimpleName(this.returnType), this.mixin.getTargetClassInfo().getSimpleName())); @@ -94,8 +94,9 @@ protected ITargetSelector initTarget() { if (this.type == AccessorType.OBJECT_FACTORY) { return new MemberInfo(Constants.CTOR, null, Bytecode.changeDescriptorReturnType(this.method.desc, "V")); } - - return new MemberInfo(this.getTargetName(this.specifiedName), null, this.method.desc); + + String desc = this.coerceReturnType ? null : this.method.desc; + return new MemberInfo(this.getTargetName(this.specifiedName), null, desc); } @Override