Skip to content

Commit 38825f7

Browse files
authored
Allow interface Mixins (#51)
* Allow mixining interfaces Reapplies #13 Fixes #34 Includes (a form of) SpongePowered#413 and SpongePowered#415 * Fix mods using static vagueness Whilst SpongePowered#415 is generating things more correctly, there are mods (such as Better End) which rely on the current behaviour * Fix lambdas in interface Mixins Fixes #43
1 parent cf6c9a2 commit 38825f7

File tree

9 files changed

+54
-35
lines changed

9 files changed

+54
-35
lines changed

src/ap/java/org/spongepowered/tools/obfuscation/AnnotatedMixinElementHandlerInjector.java

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -152,10 +152,6 @@ public void notifyRemapped() {
152152
}
153153

154154
public void registerInjector(AnnotatedElementInjector elem) {
155-
if (this.mixin.isInterface()) {
156-
this.ap.printMessage(Kind.ERROR, "Injector in interface is unsupported", elem.getElement());
157-
}
158-
159155
for (String reference : elem.getAnnotation().<String>getList("method")) {
160156
ITargetSelector targetSelector = TargetSelector.parse(reference);
161157

@@ -260,10 +256,6 @@ private boolean registerInjector(AnnotatedElementInjector elem, String reference
260256
* and process the references
261257
*/
262258
public void registerInjectionPoint(AnnotatedElementInjectionPoint elem, String format) {
263-
if (this.mixin.isInterface()) {
264-
this.ap.printMessage(Kind.ERROR, "Injector in interface is unsupported", elem.getElement());
265-
}
266-
267259
if (!elem.shouldRemap()) {
268260
return;
269261
}

src/main/java/org/spongepowered/asm/mixin/gen/AccessorGenerator.java

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -26,11 +26,11 @@
2626

2727
import java.util.ArrayList;
2828

29+
import org.apache.logging.log4j.LogManager;
2930
import org.objectweb.asm.Opcodes;
3031
import org.objectweb.asm.tree.AnnotationNode;
3132
import org.objectweb.asm.tree.MethodNode;
32-
import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException;
33-
import org.spongepowered.asm.mixin.refmap.IMixinContext;
33+
import org.spongepowered.asm.mixin.gen.throwables.InvalidAccessorException;
3434
import org.spongepowered.asm.util.asm.ASM;
3535

3636
/**
@@ -54,10 +54,12 @@ public AccessorGenerator(AccessorInfo info, boolean isStatic) {
5454
}
5555

5656
protected void checkModifiers() {
57-
if (this.info.isStatic() && !this.targetIsStatic) {
58-
IMixinContext context = this.info.getContext();
59-
throw new InvalidInjectionException(context, String.format("%s is invalid. Accessor method is%s static but the target is not.",
60-
this.info, this.info.isStatic() ? "" : " not"));
57+
if (this.info.isStatic() != this.targetIsStatic) {
58+
if (!this.targetIsStatic) {
59+
throw new InvalidAccessorException(this.info, String.format("%s is invalid. Accessor method is static but the target is not.", this.info));
60+
} else {
61+
LogManager.getLogger("mixin").info("{} should be static as its target is", this.info);
62+
}
6163
}
6264
}
6365

src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorFieldSetter.java

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@
3131
import org.objectweb.asm.tree.MethodNode;
3232
import org.objectweb.asm.tree.VarInsnNode;
3333
import org.spongepowered.asm.mixin.MixinEnvironment.Option;
34+
import org.spongepowered.asm.mixin.gen.throwables.InvalidAccessorException;
3435
import org.spongepowered.asm.mixin.Mutable;
3536
import org.spongepowered.asm.mixin.transformer.ClassInfo.Method;
3637
import org.spongepowered.asm.mixin.transformer.MixinTargetContext;
@@ -52,6 +53,12 @@ public AccessorGeneratorFieldSetter(AccessorInfo info) {
5253

5354
@Override
5455
public void validate() {
56+
if (Bytecode.hasFlag(this.info.getClassNode(), Opcodes.ACC_INTERFACE)) {
57+
//This will result in a ClassFormatError when the class is verified
58+
throw new InvalidAccessorException(info, String.format("%s tried to change interface field %s::%s",
59+
this.info, this.info.getClassNode().name, this.targetField.name));
60+
}
61+
5562
super.validate();
5663

5764
Method method = this.info.getClassInfo().findMethod(this.info.getMethod());

src/main/java/org/spongepowered/asm/mixin/gen/AccessorGeneratorMethodProxy.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,10 @@ public MethodNode generate() {
7575
method.instructions.add(new VarInsnNode(Opcodes.ALOAD, 0));
7676
}
7777
Bytecode.loadArgs(this.argTypes, method.instructions, this.targetIsStatic ? 0 : 1);
78+
boolean isInterface = Bytecode.hasFlag(this.info.getClassNode(), Opcodes.ACC_INTERFACE);
7879
boolean isPrivate = Bytecode.hasFlag(this.targetMethod, Opcodes.ACC_PRIVATE);
79-
int opcode = this.targetIsStatic ? Opcodes.INVOKESTATIC : (isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL);
80-
method.instructions.add(new MethodInsnNode(opcode, this.info.getClassNode().name, this.targetMethod.name, this.targetMethod.desc, false));
80+
int opcode = this.targetIsStatic ? Opcodes.INVOKESTATIC : isInterface ? Opcodes.INVOKEINTERFACE : (isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL);
81+
method.instructions.add(new MethodInsnNode(opcode, this.info.getClassNode().name, this.targetMethod.name, this.targetMethod.desc, isInterface));
8182
method.instructions.add(new InsnNode(this.returnType.getOpcode(Opcodes.IRETURN)));
8283
return method;
8384
}

src/main/java/org/spongepowered/asm/mixin/injection/code/Injector.java

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -405,9 +405,10 @@ protected AbstractInsnNode invokeHandler(InsnList insns) {
405405
* @return injected insn node
406406
*/
407407
protected AbstractInsnNode invokeHandler(InsnList insns, MethodNode handler) {
408+
boolean isInterface = Bytecode.hasFlag(classNode, Opcodes.ACC_INTERFACE);
408409
boolean isPrivate = (handler.access & Opcodes.ACC_PRIVATE) != 0;
409-
int invokeOpcode = this.isStatic ? Opcodes.INVOKESTATIC : isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL;
410-
MethodInsnNode insn = new MethodInsnNode(invokeOpcode, this.classNode.name, handler.name, handler.desc, false);
410+
int invokeOpcode = this.isStatic ? Opcodes.INVOKESTATIC : isInterface ? Opcodes.INVOKEINTERFACE : isPrivate ? Opcodes.INVOKESPECIAL : Opcodes.INVOKEVIRTUAL;
411+
MethodInsnNode insn = new MethodInsnNode(invokeOpcode, this.classNode.name, handler.name, handler.desc, isInterface);
411412
insns.add(insn);
412413
this.info.addCallbackInvocation(handler);
413414
return insn;

src/main/java/org/spongepowered/asm/mixin/struct/MemberRef.java

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import org.objectweb.asm.Opcodes;
2828
import org.objectweb.asm.tree.FieldInsnNode;
2929
import org.objectweb.asm.tree.MethodInsnNode;
30+
import org.spongepowered.asm.mixin.transformer.ClassInfo;
3031
import org.spongepowered.asm.mixin.transformer.throwables.MixinTransformerError;
3132
import org.spongepowered.asm.util.Bytecode;
3233

@@ -241,7 +242,7 @@ public void setOpcode(int opcode) {
241242
if (tag == 0) {
242243
throw new MixinTransformerError("Invalid opcode " + Bytecode.getOpcodeName(opcode) + " for method handle " + this.handle + ".");
243244
}
244-
boolean itf = tag == Opcodes.H_INVOKEINTERFACE;
245+
boolean itf = this.handle.isInterface();
245246
this.handle = new org.objectweb.asm.Handle(tag, this.handle.getOwner(), this.handle.getName(), this.handle.getDesc(), itf);
246247
}
247248

@@ -252,7 +253,8 @@ public String getOwner() {
252253

253254
@Override
254255
public void setOwner(String owner) {
255-
boolean itf = this.handle.getTag() == Opcodes.H_INVOKEINTERFACE;
256+
boolean itf = this.handle.isInterface();
257+
assert ClassInfo.forName(owner).isInterface() == itf;
256258
this.handle = new org.objectweb.asm.Handle(this.handle.getTag(), owner, this.handle.getName(), this.handle.getDesc(), itf);
257259
}
258260

@@ -263,7 +265,7 @@ public String getName() {
263265

264266
@Override
265267
public void setName(String name) {
266-
boolean itf = this.handle.getTag() == Opcodes.H_INVOKEINTERFACE;
268+
boolean itf = this.handle.isInterface();
267269
this.handle = new org.objectweb.asm.Handle(this.handle.getTag(), this.handle.getOwner(), name, this.handle.getDesc(), itf);
268270
}
269271

@@ -274,7 +276,7 @@ public String getDesc() {
274276

275277
@Override
276278
public void setDesc(String desc) {
277-
boolean itf = this.handle.getTag() == Opcodes.H_INVOKEINTERFACE;
279+
boolean itf = this.handle.isInterface();
278280
this.handle = new org.objectweb.asm.Handle(this.handle.getTag(), this.handle.getOwner(), this.handle.getName(), desc, itf);
279281
}
280282
}

src/main/java/org/spongepowered/asm/mixin/transformer/MixinApplicatorInterface.java

Lines changed: 19 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -24,10 +24,13 @@
2424
*/
2525
package org.spongepowered.asm.mixin.transformer;
2626

27+
import java.lang.reflect.Modifier;
2728
import java.util.Map.Entry;
2829

2930
import org.objectweb.asm.tree.FieldNode;
3031
import org.objectweb.asm.tree.MethodNode;
32+
import org.spongepowered.asm.mixin.MixinEnvironment;
33+
import org.spongepowered.asm.mixin.MixinEnvironment.CompatibilityLevel.LanguageFeature;
3134
import org.spongepowered.asm.mixin.injection.struct.InjectionInfo;
3235
import org.spongepowered.asm.mixin.injection.throwables.InvalidInjectionException;
3336
import org.spongepowered.asm.mixin.transformer.ClassInfo.Field;
@@ -96,28 +99,34 @@ protected void applyInitialisers(MixinTargetContext mixin) {
9699
*/
97100
@Override
98101
protected void prepareInjections(MixinTargetContext mixin) {
99-
// disabled for interface mixins
100102
for (MethodNode method : this.targetClass.methods) {
101103
try {
102104
InjectionInfo injectInfo = InjectionInfo.parse(mixin, method);
103105
if (injectInfo != null) {
104-
throw new InvalidInterfaceMixinException(mixin, injectInfo + " is not supported on interface mixin method " + method.name);
106+
//Make sure we're running on a Java version which supports interfaces having method bodies
107+
if (!MixinEnvironment.getCompatibilityLevel().supports(LanguageFeature.METHODS_IN_INTERFACES)) {
108+
throw new InvalidInterfaceMixinException(mixin, injectInfo + " is not supported on interface mixin method " + method.name);
109+
}
105110
}
106111
} catch (InvalidInjectionException ex) {
107112
String description = ex.getInjectionInfo() != null ? ex.getInjectionInfo().toString() : "Injection";
108113
throw new InvalidInterfaceMixinException(mixin, description + " is not supported in interface mixin");
109114
}
110115
}
116+
117+
super.prepareInjections(mixin);
111118
}
112119

113-
/* (non-Javadoc)
114-
* @see org.spongepowered.asm.mixin.transformer.MixinApplicator
115-
* #applyInjections(
116-
* org.spongepowered.asm.mixin.transformer.MixinTargetContext)
117-
*/
118120
@Override
119-
protected void applyInjections(MixinTargetContext mixin) {
120-
// Do nothing
121-
}
121+
protected void checkMethodVisibility(MixinTargetContext mixin, MethodNode mixinMethod) {
122+
//Allow injecting into static interface methods where it isn't possible to control the access of the injection method
123+
if (Modifier.isStatic(mixinMethod.access) && !MixinEnvironment.getCompatibilityLevel().supports(LanguageFeature.PRIVATE_METHODS_IN_INTERFACES)) {
124+
InjectionInfo injectInfo = InjectionInfo.parse(mixin, mixinMethod);
125+
if (injectInfo != null) {
126+
return;
127+
}
128+
}
122129

130+
super.checkMethodVisibility(mixin, mixinMethod);
131+
}
123132
}

src/main/java/org/spongepowered/asm/mixin/transformer/MixinPostProcessor.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -213,7 +213,7 @@ private static void createProxy(MethodNode methodNode, ClassInfo targetClass, Me
213213
Type[] args = Type.getArgumentTypes(methodNode.desc);
214214
Type returnType = Type.getReturnType(methodNode.desc);
215215
Bytecode.loadArgs(args, methodNode.instructions, 0);
216-
methodNode.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, targetClass.getName(), method.getName(), methodNode.desc, false));
216+
methodNode.instructions.add(new MethodInsnNode(Opcodes.INVOKESTATIC, targetClass.getName(), method.getName(), methodNode.desc, targetClass.isInterface()));
217217
methodNode.instructions.add(new InsnNode(returnType.getOpcode(Opcodes.IRETURN)));
218218
methodNode.maxStack = Bytecode.getFirstNonArgLocalIndex(args, false);
219219
methodNode.maxLocals = 0;

src/main/java/org/spongepowered/asm/mixin/transformer/MixinPreProcessorInterface.java

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@
2727
import org.objectweb.asm.Opcodes;
2828
import org.objectweb.asm.tree.AnnotationNode;
2929
import org.objectweb.asm.tree.FieldNode;
30+
import org.spongepowered.asm.mixin.MixinEnvironment;
31+
import org.spongepowered.asm.mixin.MixinEnvironment.CompatibilityLevel.LanguageFeature;
3032
import org.spongepowered.asm.mixin.transformer.ClassInfo.Method;
3133
import org.spongepowered.asm.mixin.transformer.MixinInfo.MixinClassNode;
3234
import org.spongepowered.asm.mixin.transformer.MixinInfo.MixinMethodNode;
@@ -58,8 +60,11 @@ class MixinPreProcessorInterface extends MixinPreProcessorStandard {
5860
protected void prepareMethod(MixinMethodNode mixinMethod, Method method) {
5961
// Userland interfaces should not have non-public methods except for lambda bodies
6062
if (!Bytecode.hasFlag(mixinMethod, Opcodes.ACC_PUBLIC) && !Bytecode.hasFlag(mixinMethod, Opcodes.ACC_SYNTHETIC)) {
61-
throw new InvalidInterfaceMixinException(this.mixin, "Interface mixin contains a non-public method! Found " + method + " in "
62-
+ this.mixin);
63+
//On versions that support it private methods are also allowed
64+
if (!Bytecode.hasFlag(mixinMethod, Opcodes.ACC_PRIVATE) || !MixinEnvironment.getCompatibilityLevel().supports(LanguageFeature.PRIVATE_METHODS_IN_INTERFACES)) {
65+
throw new InvalidInterfaceMixinException(this.mixin, "Interface mixin contains a non-public method! Found " + method + " in "
66+
+ this.mixin);
67+
}
6368
}
6469

6570
super.prepareMethod(mixinMethod, method);

0 commit comments

Comments
 (0)