Skip to content

Commit 57f1a16

Browse files
committed
Fix ParameterAnnotationFixer on modern Minecraft
ProGuard has been updated since the inception of this fix in ModCoderPack/MCInjector#3 and no longer has the original bug. Mojang also appears to be using newer versions of ProGuard where the bug is not present. The fixer is now properly programmed to change nothing if it doesn't detect the broken PG behavior. The fixer is now also optimized to use the visitor system instead of populating a ClassNode; it has been rewritten to do so.
1 parent 179b62c commit 57f1a16

File tree

3 files changed

+116
-154
lines changed

3 files changed

+116
-154
lines changed

src/main/java/net/minecraftforge/fart/api/Transformer.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ public static Factory identifierFixerFactory(final IdentifierFixerConfig config)
106106
* @return a factory for a parameter annotation-fixing transformer
107107
*/
108108
public static Factory parameterAnnotationFixerFactory() {
109-
return ctx -> new ParameterAnnotationFixer(ctx.getLog(), ctx.getDebug());
109+
return ctx -> ParameterAnnotationFixer.INSTANCE;
110110
}
111111

112112
/**

src/main/java/net/minecraftforge/fart/internal/IdentifierFixer.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,10 +40,6 @@ public Fixer(IdentifierFixerConfig config, ClassVisitor parent) {
4040
this.config = config;
4141
}
4242

43-
public boolean madeChange() {
44-
return this.madeChange;
45-
}
46-
4743
@Override
4844
public final MethodVisitor visitMethod(final int access, final String name, final String descriptor, final String signature, final String[] exceptions) {
4945
MethodVisitor parent = super.visitMethod(access, name, descriptor, signature, exceptions);

src/main/java/net/minecraftforge/fart/internal/ParameterAnnotationFixer.java

Lines changed: 115 additions & 149 deletions
Original file line numberDiff line numberDiff line change
@@ -19,152 +19,78 @@
1919

2020
package net.minecraftforge.fart.internal;
2121

22-
import static org.objectweb.asm.Opcodes.*;
23-
24-
import java.util.Arrays;
25-
import java.util.List;
26-
import java.util.function.Consumer;
27-
28-
import org.objectweb.asm.ClassReader;
22+
import org.objectweb.asm.AnnotationVisitor;
2923
import org.objectweb.asm.ClassVisitor;
30-
import org.objectweb.asm.ClassWriter;
24+
import org.objectweb.asm.MethodVisitor;
3125
import org.objectweb.asm.Type;
3226
import org.objectweb.asm.tree.AnnotationNode;
33-
import org.objectweb.asm.tree.ClassNode;
34-
import org.objectweb.asm.tree.InnerClassNode;
35-
import org.objectweb.asm.tree.MethodNode;
36-
37-
import net.minecraftforge.fart.api.Transformer;
3827

39-
public final class ParameterAnnotationFixer implements Transformer {
40-
private final Consumer<String> log;
41-
private final Consumer<String> debug;
28+
import static org.objectweb.asm.Opcodes.*;
4229

43-
public ParameterAnnotationFixer(Consumer<String> log, Consumer<String> debug) {
44-
this.log = log;
45-
this.debug = debug;
46-
}
30+
public final class ParameterAnnotationFixer extends OptionalChangeTransformer {
31+
public static final ParameterAnnotationFixer INSTANCE = new ParameterAnnotationFixer();
4732

48-
@Override
49-
public ClassEntry process(ClassEntry entry) {
50-
final ClassReader reader = new ClassReader(entry.getData());
51-
final ClassWriter writer = new ClassWriter(reader, 0);
52-
final ClassNode node = new ClassNode();
53-
reader.accept(new Visitor(node), 0);
54-
node.accept(writer);
55-
return ClassEntry.create(entry.getName(), entry.getTime(), writer.toByteArray());
33+
private ParameterAnnotationFixer() {
34+
super(Fixer::new);
5635
}
5736

58-
private class Visitor extends ClassVisitor {
59-
private final ClassNode node;
37+
private static class Fixer extends OptionalChangeTransformer.ClassFixer {
38+
private String name;
39+
private boolean isEnum;
40+
private String outerName;
6041

61-
public Visitor(ClassNode cn) {
62-
super(RenamerImpl.MAX_ASM_VERSION, cn);
63-
this.node = cn;
42+
public Fixer(ClassVisitor parent) {
43+
super(parent);
6444
}
6545

66-
private void debug(String message) {
67-
debug.accept(message);
68-
}
46+
@Override
47+
public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
48+
this.name = name;
49+
if ((access & ACC_ENUM) != 0)
50+
this.isEnum = true;
6951

70-
private void log(String message) {
71-
log.accept(message);
52+
super.visit(version, access, name, signature, superName, interfaces);
7253
}
7354

7455
@Override
75-
public void visitEnd() {
76-
super.visitEnd();
77-
78-
Type[] syntheticParams = getExpectedSyntheticParams(node);
79-
if (syntheticParams != null) {
80-
for (MethodNode mn : node.methods) {
81-
if (mn.name.equals("<init>"))
82-
processConstructor(node, mn, syntheticParams);
56+
public void visitInnerClass(String name, String outerName, String innerName, int access) {
57+
if ((access & (ACC_STATIC | ACC_INTERFACE)) == 0 && this.name.equals(name) && innerName != null) {
58+
// We know it is an inner class here
59+
if (outerName == null) {
60+
int idx = name.lastIndexOf('$');
61+
if (idx != -1)
62+
this.outerName = name.substring(0, idx);
63+
} else {
64+
this.outerName = outerName;
8365
}
8466
}
67+
68+
super.visitInnerClass(name, outerName, innerName, access);
8569
}
8670

87-
/**
88-
* Checks if the given class might have synthetic parameters in the
89-
* constructor. There are two cases where this might happen:
90-
* <ol>
91-
* <li>If the given class is an inner class, the first parameter is the
92-
* instance of the outer class.</li>
93-
* <li>If the given class is an enum, the first parameter is the enum
94-
* constant name and the second parameter is its ordinal.</li>
95-
* </ol>
96-
*
97-
* @return An array of types for synthetic parameters if the class can have
98-
* synthetic parameters, otherwise null.
99-
*/
100-
private Type[] getExpectedSyntheticParams(ClassNode cls) {
101-
// Check for enum
102-
// http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/1ff9d5118aae/src/share/classes/com/sun/tools/javac/comp/Lower.java#l2866
103-
if ((cls.access & ACC_ENUM) != 0) {
104-
debug(" Considering " + cls.name + " for extra parameter annotations as it is an enum");
105-
return new Type[] { Type.getObjectType("java/lang/String"), Type.INT_TYPE };
106-
}
71+
@Override
72+
public MethodVisitor visitMethod(int access, String name, String descriptor, String signature, String[] exceptions) {
73+
MethodVisitor methodVisitor = super.visitMethod(access, name, descriptor, signature, exceptions);
10774

108-
// Check for inner class
109-
InnerClassNode info = null;
110-
for (InnerClassNode node : cls.innerClasses) { // note: cls.innerClasses is never null
111-
if (node.name.equals(cls.name)) {
112-
info = node;
113-
break;
114-
}
115-
}
116-
// http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/1ff9d5118aae/src/share/classes/com/sun/tools/javac/code/Symbol.java#l398
117-
if (info == null) {
118-
debug(" Not considering " + cls.name + " for extra parameter annotations as it is not an inner class");
119-
return null; // It's not an inner class
120-
}
121-
if ((info.access & (ACC_STATIC | ACC_INTERFACE)) != 0) {
122-
debug(" Not considering " + cls.name + " for extra parameter annotations as is an interface or static");
123-
return null; // It's static or can't have a constructor
124-
}
75+
if (!name.equals("<init>"))
76+
return methodVisitor;
12577

126-
// http://hg.openjdk.java.net/jdk8/jdk8/langtools/file/1ff9d5118aae/src/share/classes/com/sun/tools/javac/jvm/ClassReader.java#l2011
127-
if (info.innerName == null) {
128-
debug(" Not considering " + cls.name + " for extra parameter annotations as it is annonymous");
129-
return null; // It's an anonymous class
130-
}
78+
Type[] syntheticParams = null;
13179

132-
if (info.outerName == null) {
133-
int idx = cls.name.lastIndexOf('$');
134-
if (idx == -1) {
135-
debug(" Not cosidering " + cls.name + " for extra parameter annotations as it does not appear to be an inner class");
136-
return null;
137-
}
138-
debug(" Considering " + cls.name + " for extra parameter annotations as its name appears to be an inner class of " + cls.name.substring(0, idx));
139-
return new Type[] { Type.getObjectType(cls.name.substring(0, idx)) };
80+
if (this.isEnum) {
81+
syntheticParams = new Type[]{Type.getObjectType("java/lang/String"), Type.INT_TYPE};
82+
} else if (this.outerName != null) {
83+
syntheticParams = new Type[]{Type.getObjectType(this.outerName)};
14084
}
14185

142-
debug(" Considering " + cls.name + " for extra parameter annotations as it is an inner class of " + info.outerName);
143-
return new Type[] { Type.getObjectType(info.outerName) };
144-
}
86+
if (syntheticParams == null)
87+
return methodVisitor;
14588

146-
/**
147-
* Removes the parameter annotations for the given synthetic parameters,
148-
* if there are parameter annotations and the synthetic parameters exist.
149-
*/
150-
private void processConstructor(ClassNode cls, MethodNode mn, Type[] syntheticParams) {
151-
String methodInfo = mn.name + mn.desc + " in " + cls.name;
152-
Type[] params = Type.getArgumentTypes(mn.desc);
153-
154-
if (beginsWith(params, syntheticParams)) {
155-
mn.visibleParameterAnnotations = process(methodInfo, "RuntimeVisibleParameterAnnotations", params.length, syntheticParams.length, mn.visibleParameterAnnotations);
156-
mn.invisibleParameterAnnotations = process(methodInfo, "RuntimeInvisibleParameterAnnotations", params.length, syntheticParams.length, mn.invisibleParameterAnnotations);
157-
// ASM uses this value, not the length of the array
158-
// Note that this was added in ASM 6.1
159-
if (mn.visibleParameterAnnotations != null)
160-
mn.visibleAnnotableParameterCount = mn.visibleParameterAnnotations.length;
161-
if (mn.invisibleParameterAnnotations != null)
162-
mn.invisibleAnnotableParameterCount = mn.invisibleParameterAnnotations.length;
163-
} else
164-
log("Unexpected lack of synthetic args to the constructor: expected " + Arrays.toString(syntheticParams) + " at the start of " + methodInfo);
89+
Type[] argumentTypes = Type.getArgumentTypes(descriptor);
90+
return beginsWith(argumentTypes, syntheticParams) ? new MethodFixer(argumentTypes.length, syntheticParams.length, methodVisitor) : methodVisitor;
16591
}
16692

167-
private boolean beginsWith(Type[] values, Type[] prefix) {
93+
private static boolean beginsWith(Type[] values, Type[] prefix) {
16894
if (values.length < prefix.length)
16995
return false;
17096
for (int i = 0; i < prefix.length; i++) {
@@ -174,39 +100,79 @@ private boolean beginsWith(Type[] values, Type[] prefix) {
174100
return true;
175101
}
176102

177-
/**
178-
* Removes annotation nodes corresponding to synthetic parameters, after
179-
* the existence of synthetic parameters has already been checked.
180-
*
181-
* @param methodInfo
182-
* A description of the method, for logging
183-
* @param attributeName
184-
* The name of the attribute, for logging
185-
* @param numParams
186-
* The number of parameters in the method
187-
* @param numSynthetic
188-
* The number of synthetic parameters (should not be 0)
189-
* @param annotations
190-
* The current array of annotation nodes, may be null
191-
* @return The new array of annotation nodes, may be null
192-
*/
193-
private List<AnnotationNode>[] process(String methodInfo, String attributeName, int numParams, int numSynthetic, List<AnnotationNode>[] annotations) {
194-
if (annotations == null) {
195-
debug(" " + methodInfo + " does not have a " + attributeName + " attribute");
196-
return null;
103+
private class MethodFixer extends MethodVisitor {
104+
private final int argumentsLength;
105+
private final int numSynthetic;
106+
private final AnnotationHolder[] annotations;
107+
private int parameterAnnotationCount;
108+
private boolean visibleParamAnnotations;
109+
private boolean hasParamAnnotation;
110+
111+
MethodFixer(int argumentsLength, int numSynthetic, MethodVisitor methodVisitor) {
112+
super(RenamerImpl.MAX_ASM_VERSION, methodVisitor);
113+
this.argumentsLength = argumentsLength;
114+
this.numSynthetic = numSynthetic;
115+
this.annotations = new AnnotationHolder[argumentsLength];
116+
}
117+
118+
@Override
119+
public void visitAnnotableParameterCount(int parameterCount, boolean visible) {
120+
this.parameterAnnotationCount = parameterCount;
121+
this.visibleParamAnnotations = visible;
122+
123+
// Don't call super yet
197124
}
198125

199-
int numAnnotations = annotations.length;
200-
if (numParams == numAnnotations) {
201-
log("Found extra " + attributeName + " entries in " + methodInfo + ": removing " + numSynthetic);
202-
return Arrays.copyOfRange(annotations, numSynthetic, numAnnotations);
203-
} else if (numParams == numAnnotations - numSynthetic) {
204-
debug("Number of " + attributeName + " entries in " + methodInfo + " is already as we want");
205-
return annotations;
206-
} else {
207-
log("Unexpected number of " + attributeName + " entries in " + methodInfo + ": " + numAnnotations);
208-
return annotations;
126+
@Override
127+
public AnnotationVisitor visitParameterAnnotation(int parameter, String descriptor, boolean visible) {
128+
this.hasParamAnnotation = true;
129+
AnnotationNode node = new AnnotationNode(RenamerImpl.MAX_ASM_VERSION, descriptor);
130+
this.annotations[parameter] = new AnnotationHolder(parameter, descriptor, visible, node);
131+
return node; // Don't call super yet
209132
}
133+
134+
@Override
135+
public void visitEnd() {
136+
int offset = 0;
137+
if (this.hasParamAnnotation && this.parameterAnnotationCount == this.argumentsLength) {
138+
// The ProGuard bug only applies if parameterAnnotationCount and the length of the argument types exactly match.
139+
// See https://github.com/Guardsquare/proguard/blob/b0db59bc59fca1fc3f0083dd2e354a71b544d77c/core/src/proguard/classfile/io/ProgramClassReader.java#L745 for the bug.
140+
// parameterAnnotationCount is the number of parameter annotations read from the bytecode, whereas the length of the argument types is the number of parameters.
141+
// The ProGuard bug always forcefully reassigns the parameterCount from bytecode to the length of the argument types alongside the other problematic bits of code.
142+
// If it didn't do that, then we know the buggy ProGuard version is not in use.
143+
144+
offset = this.numSynthetic;
145+
}
146+
147+
if (offset != 0)
148+
Fixer.this.madeChange = true;
149+
150+
// Offer the data to the parent visitor; potentially with our fixes applied
151+
super.visitAnnotableParameterCount(this.parameterAnnotationCount - offset, this.visibleParamAnnotations);
152+
for (AnnotationHolder holder : this.annotations) {
153+
if (holder != null) {
154+
int parameter = holder.parameter - offset;
155+
if (parameter >= 0) // Although synthetic parameters should never have annotations, let's ensure against out-of-bounds just in case
156+
holder.node.accept(super.visitParameterAnnotation(parameter, holder.descriptor, holder.visible));
157+
}
158+
}
159+
160+
super.visitEnd();
161+
}
162+
}
163+
}
164+
165+
private static class AnnotationHolder {
166+
final int parameter;
167+
final String descriptor;
168+
final boolean visible;
169+
final AnnotationNode node;
170+
171+
AnnotationHolder(int parameter, String descriptor, boolean visible, AnnotationNode node) {
172+
this.parameter = parameter;
173+
this.descriptor = descriptor;
174+
this.visible = visible;
175+
this.node = node;
210176
}
211177
}
212178
}

0 commit comments

Comments
 (0)