1919
2020package 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 ;
2923import org .objectweb .asm .ClassVisitor ;
30- import org .objectweb .asm .ClassWriter ;
24+ import org .objectweb .asm .MethodVisitor ;
3125import org .objectweb .asm .Type ;
3226import 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