1 package org.objectweb.asm;
2 
3 import java.util.ArrayList;
4 import java.util.HashMap;
5 import java.util.HashSet;
6 import java.util.List;
7 import java.util.Map;
8 import java.util.Set;
9 
10 import org.objectweb.asm.AnnotationVisitor;
11 import org.objectweb.asm.ClassVisitor;
12 import org.objectweb.asm.TypeAnnotationVisitor;
13 import org.objectweb.asm.FieldVisitor;
14 import org.objectweb.asm.MethodVisitor;
15 import org.objectweb.asm.commons.EmptyVisitor;
16 
17 /**
18  * An <code>AnnotationVerifier</code> provides a way to check to see if two
19  * versions of the same class (from two different <code>.class</code> files),
20  * have the same annotations on the same elements.
21  */
22 public class AnnotationVerifier {
23 
24   /**
25    * The "correct" version of the class to verify against.
26    */
27   private ClassRecorder originalVisitor;
28 
29   /**
30    * The uncertain version of the class to verify.
31    */
32   private ClassRecorder newVisitor;
33 
34   /**
35    * Constructs a new <code>AnnotationVerifier</code> that does not yet have
36    * any information about the class.
37    */
38   public AnnotationVerifier() {
39     originalVisitor = new ClassRecorder();
40     newVisitor = new ClassRecorder();
41   }
42 
43   /**
44    * Returns the <code>ClassVisitor</code> which should be made to visit the
45    * version of the class known to be correct.
46    *
47    * @return a visitor for the good version of the class
48    */
49   public ClassVisitor originalVisitor() {
50     return originalVisitor;
51   }
52 
53   /**
54    * Returns the <code>ClassVisitor</code> which should be made to visit the
55    * version of the class being tested.
56    *
57    * @return a visitor the the experimental version of the class
58    */
59   public ClassVisitor newVisitor() {
60     return newVisitor;
61   }
62 
63   /**
64    * Verifies that the visitors returned by {@link #originalVisitor()} and
65    * {@link #newVisitor()} have visited the same class.  This method can only
66    * be called if both visitors have already visited a class.
67    *
68    * @throws AnnotationMismatchException if the two visitors have not visited
69    * two versions of the same class that contain idential annotations.
70    */
71   public void verify() {
72     if(!newVisitor.name.equals(originalVisitor.name)) {
73       throw new AnnotationMismatchException(
74           "Cannot verify two different classes " +
75           newVisitor.name + " cannot be verified against " +
76           originalVisitor.name);
77     }
78     newVisitor.verifyAgainst(originalVisitor);
79   }
80 
81   /**
82    * A ClassRecorder records all the annotations that it visits, and serves
83    * as a ClassVisitor, FieldVisitor and MethodVisitor.
84    */
85   private class ClassRecorder extends EmptyVisitor {
86 
87     private String description;
88 
89     public String name;
90     private String signature;
91 
92     private Map<String, ClassRecorder> fieldRecorders;
93     // key is unparameterized name
94 
95     private Map<String, ClassRecorder> methodRecorders;
96     // key is complete method signature
97 
98     // general annotations
99     private Map<String, AnnotationRecorder> anns;
100     private Map<String, AnnotationRecorder> xanns;
101 
102     //method specific annotations
103     private Set<AnnotationRecorder> danns;
104     private Map<ParameterDescription, AnnotationRecorder> panns;
105 
106     public ClassRecorder() {
107       this("[class: ","","");
108     }
109 
110     public ClassRecorder(String internalDescription, String name, String signature) {
111       this.description = internalDescription;
112       this.name = name;
113       this.signature = signature;
114 
115       fieldRecorders = new HashMap<String, ClassRecorder>();
116       methodRecorders = new HashMap<String, ClassRecorder>();
117 
118       anns = new HashMap<String, AnnotationRecorder>();
119       xanns = new HashMap<String, AnnotationRecorder>();
120 
121       danns = new HashSet<AnnotationRecorder>();
122       panns = new HashMap<ParameterDescription, AnnotationRecorder>();
123     }
124 
125     public void visit(int version, int access, String name, String signature, String superName, String[] interfaces) {
126       this.name = name;
127       this.signature = signature;
128       description = description + name;
129     }
130 
131     public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
132       AnnotationRecorder av = new AnnotationRecorder(
133           description + " annotation: " + desc);
134       anns.put(desc,av);
135       return av;
136     }
137 
138     public TypeAnnotationVisitor visitTypeAnnotation(String desc, boolean visible) {
139       AnnotationRecorder av = new AnnotationRecorder(
140           description + " annotation: " + desc);
141       xanns.put(desc,av);
142       return av;
143     }
144 
145     public FieldVisitor visitField(int access, String name, String desc, String signature, Object value) {
146       ClassRecorder fr =
147         new ClassRecorder(
148             description + " field: " + name,
149             name, signature);
150       fieldRecorders.put(name, fr);
151       return fr;
152     }
153 
154     public MethodVisitor visitMethod(int access, String name, String desc, String signature, String[] exceptions) {
155       ClassRecorder mr =
156         new ClassRecorder(
157             description + " method: " + name + desc, name+desc, signature);
158       methodRecorders.put(name+desc, mr);
159       return mr;
160     }
161 
162     // MethodVisitor methods:
163     public AnnotationVisitor visitAnnotationDefault() {
164       AnnotationRecorder dr = new AnnotationRecorder(
165           description + " default annotation");
166       danns.add(dr);
167       return dr;
168     }
169 
170     public AnnotationVisitor visitParameterAnnotation(int parameter, String desc, boolean visible) {
171       ParameterDescription pd =
172         new ParameterDescription(parameter, desc, visible);
173       AnnotationRecorder pr =
174         new AnnotationRecorder(description +
175             " parameter annotation: " + pd);
176       panns.put(pd, pr);
177       return pr;
178     }
179 
180     public void verifyAgainst(ClassRecorder correct) {
181       // first, ensure all annotations are correct
182       verifyAnns(this.anns, correct.anns);
183       verifyAnns(this.xanns, correct.xanns);
184 
185       // then recurse into any annotations on fields/methods
186       verifyMemberAnns(this.fieldRecorders, correct.fieldRecorders);
187       verifyMemberAnns(this.methodRecorders, correct.methodRecorders);
188     }
189 
190     private void verifyAnns(
191         Map<String, AnnotationRecorder> questionableAnns,
192         Map<String, AnnotationRecorder> correctAnns) {
193       Set<AnnotationRecorder> unresolvedQuestionableAnns =
194         new HashSet<AnnotationRecorder>(questionableAnns.values());
195 
196       for(Map.Entry<String, AnnotationRecorder> entry :
197         correctAnns.entrySet()) {
198         String name = entry.getKey();
199         AnnotationRecorder correctAnn = entry.getValue();
200         AnnotationRecorder questionableAnn = questionableAnns.get(name);
201         if(questionableAnn == null) {
202           throw new AnnotationMismatchException(description +
203               " does not contain expected annotation: " + correctAnn);
204         }
205 
206         questionableAnn.verifyAgainst(correctAnn);
207 
208         unresolvedQuestionableAnns.remove(questionableAnn);
209       }
210 
211       for(AnnotationRecorder unexpectedAnnOnThis : unresolvedQuestionableAnns) {
212         throw new AnnotationMismatchException(description +
213             " contains unexpected annotation : " + unexpectedAnnOnThis);
214       }
215     }
216 
217     private void verifyMemberAnns(
218         Map<String, ClassRecorder> questionableMembers,
219         Map<String, ClassRecorder> correctMembers) {
220       Set<ClassRecorder> unresolvedQuestionableMembers =
221         new HashSet<ClassRecorder>(questionableMembers.values());
222 
223       for(Map.Entry<String, ClassRecorder> entry :
224         correctMembers.entrySet()) {
225         String name = entry.getKey();
226         ClassRecorder correctMember = entry.getValue();
227         ClassRecorder questionableMember = questionableMembers.get(name);
228         if(questionableMember == null) {
229           throw new AnnotationMismatchException(description +
230               " does not contain expected member: " + correctMember);
231         }
232 
233         questionableMember.verifyAgainst(correctMember);
234 
235         unresolvedQuestionableMembers.remove(questionableMember);
236       }
237 
238       for(ClassRecorder unexpectedMemberOnThis : unresolvedQuestionableMembers) {
239         System.out.println("Going to throw exception: ");
240         System.out.println("questionable: " + questionableMembers);
241         System.out.println("correct: " + correctMembers);
242 
243         throw new AnnotationMismatchException(description +
244             " contains unexpected member: " + unexpectedMemberOnThis);
245       }
246     }
247 
248     public String toString() {
249       return description;
250     }
251   }
252 
253   /**
254    * An AnnotationRecorder is an TypeAnnotationVisitor that records all the
255    * information it visits.
256    */
257   private class AnnotationRecorder implements TypeAnnotationVisitor {
258     private String description;
259 
260     private List<String> fieldArgs1;
261     private List<Object> fieldArgs2;
262 
263     private List<String> enumArgs1;
264     private List<String> enumArgs2;
265     private List<String> enumArgs3;
266 
267     private List<String> innerAnnotationArgs1;
268     private List<String> innerAnnotationArgs2;
269     private Map<String, AnnotationRecorder> innerAnnotationMap;
270 
271     private List<String> arrayArgs;
272     private Map<String, AnnotationRecorder> arrayMap;
273 
274     private List<Integer> xIndexArgs;
275     private List<Integer> xLengthArgs;
276     private List<Integer> xLocationArgs;
277     private List<Integer> xLocationLengthArgs;
278     private List<Integer> xOffsetArgs;
279     private List<Integer> xStartPcArgs;
280     private List<Integer> xTargetTypeArgs;
281     private List<Integer> xParamIndexArgs;
282     private List<Integer> xBoundIndexArgs;
283     private List<Integer> xTypeIndexArgs;
284 
285     public AnnotationRecorder(String description) {
286       this.description = description;
287       fieldArgs1 = new ArrayList<String>();
288       fieldArgs2 = new ArrayList<Object>();
289 
290       enumArgs1 = new ArrayList<String>();
291       enumArgs2 = new ArrayList<String>();
292       enumArgs3 = new ArrayList<String>();
293 
294       innerAnnotationArgs1 = new ArrayList<String>();
295       innerAnnotationArgs2 = new ArrayList<String>();
296       innerAnnotationMap = new HashMap<String, AnnotationRecorder>();
297 
298       arrayArgs = new ArrayList<String>();
299       arrayMap = new HashMap<String, AnnotationRecorder>();
300 
301       xIndexArgs = new ArrayList<Integer>();
302       xLengthArgs = new ArrayList<Integer>();
303       xLocationArgs = new ArrayList<Integer>();
304       xLocationLengthArgs = new ArrayList<Integer>();
305       xOffsetArgs = new ArrayList<Integer>();
306       xStartPcArgs = new ArrayList<Integer>();
307       xTargetTypeArgs = new ArrayList<Integer>();
308       xParamIndexArgs = new ArrayList<Integer>();
309       xBoundIndexArgs = new ArrayList<Integer>();
310       xTypeIndexArgs = new ArrayList<Integer>();
311     }
312 
313     public void visitXIndex(int index) {
314       xIndexArgs.add(index);
315     }
316 
317     public void visitXLength(int length) {
318       xLengthArgs.add(length);
319     }
320 
321     public void visitXLocation(int location) {
322       xLocationArgs.add(location);
323     }
324 
325     public void visitXLocationLength(int location_length) {
326      xLocationLengthArgs.add(location_length);
327     }
328 
329     public void visitXOffset(int offset) {
330       xOffsetArgs.add(offset);
331     }
332 
333     @Override
334     public void visitXNumEntries(int num_entries) { }
335 
336     public void visitXStartPc(int start_pc) {
337       xStartPcArgs.add(start_pc);
338     }
339 
340     public void visitXTargetType(int target_type) {
341       xTargetTypeArgs.add(target_type);
342     }
343 
344     public void visitXParamIndex(int param_index) {
345       xParamIndexArgs.add(param_index);
346     }
347 
348     public void visitXBoundIndex(int bound_index) {
349       xBoundIndexArgs.add(bound_index);
350     }
351 
352     public void visitXTypeIndex(int type_index) {
353       xTypeIndexArgs.add(type_index);
354     }
355 
356     public void visit(String name, Object value) {
357       fieldArgs1.add(name);
358       fieldArgs2.add(value);
359     }
360 
361     public AnnotationVisitor visitAnnotation(String name, String desc) {
362       innerAnnotationArgs1.add(name);
363       innerAnnotationArgs2.add(name);
364 
365       AnnotationRecorder av= new AnnotationRecorder(description + name);
366       innerAnnotationMap.put(name, av);
367       return av;
368     }
369 
370     public AnnotationVisitor visitArray(String name) {
371       arrayArgs.add(name);
372       AnnotationRecorder av = new AnnotationRecorder(description + name);
373       arrayMap.put(name, av);
374       return av;
375     }
376 
377     public void visitEnd() {
378     }
379 
380     public void visitEnum(String name, String desc, String value) {
381       enumArgs1.add(name);
382       enumArgs2.add(desc);
383       enumArgs3.add(value);
384     }
385 
386     public String toString() {
387       return description;
388     }
389 
390     /**
391      * Checks that the information passed into this matches the information
392      * passed into another AnnotationRecorder.  For right now, the order in
393      * which information is passed in does matter.  If there is a conflict in
394      * information, an exception will be thrown.
395      *
396      * @param ar an annotation recorder that has visited the correct information
397      *  this should visit
398      * @throws AnnotationMismatchException if the information visited by this
399      *  does not match the information in ar
400      */
401     public void verifyAgainst(AnnotationRecorder ar) {
402       StringBuilder sb = new StringBuilder();
403       verifyList(sb, "visit()", 1, this.fieldArgs1, ar.fieldArgs1);
404       verifyList(sb, "visit()", 2, this.fieldArgs2, ar.fieldArgs2);
405 
406       verifyList(sb, "visitEnum()", 1, this.enumArgs1, ar.enumArgs1);
407       verifyList(sb, "visitEnum()", 2, this.enumArgs2, ar.enumArgs2);
408       verifyList(sb, "visitEnum()", 3, this.enumArgs3, ar.enumArgs3);
409 
410       verifyList(sb, "visitAnnotation()", 1, this.innerAnnotationArgs1, ar.innerAnnotationArgs1);
411       verifyList(sb, "visitAnnotation()", 2, this.innerAnnotationArgs2, ar.innerAnnotationArgs2);
412 
413       verifyList(sb, "visitArray()", 1, this.arrayArgs, ar.arrayArgs);
414 
415       verifyList(sb, "visitXIndexArgs()", 1, this.xIndexArgs, ar.xIndexArgs);
416       verifyList(sb, "visitXLength()", 1, this.xLengthArgs, ar.xIndexArgs);
417       verifyList(sb, "visitXLocation()", 1, this.xLocationArgs, ar.xLocationArgs);
418       verifyList(sb, "visitXLocationLength()", 1, this.xLocationLengthArgs, ar.xLocationLengthArgs);
419       verifyList(sb, "visitXOffset()", 1, this.xOffsetArgs, ar.xOffsetArgs);
420       verifyList(sb, "visitXStartPc()", 1, this.xStartPcArgs, ar.xStartPcArgs);
421       verifyList(sb, "visitXTargetType()", 1, this.xTargetTypeArgs, ar.xTargetTypeArgs);
422       verifyList(sb, "visitXParamIndex()", 1, this.xParamIndexArgs, ar.xParamIndexArgs);
423       verifyList(sb, "visitXBoundIndex()", 1, this.xBoundIndexArgs, ar.xBoundIndexArgs);
424       verifyList(sb, "visitXTypeIndex()", 1, this.xTypeIndexArgs, ar.xTypeIndexArgs);
425 
426       verifyInnerAnnotationRecorder(sb, this.innerAnnotationMap, ar.innerAnnotationMap);
427       verifyInnerAnnotationRecorder(sb, this.arrayMap, ar.arrayMap);
428 
429       if(sb.length() > 0) {
430         throw new AnnotationMismatchException(sb.toString());
431       }
432     }
433 
434     private void verifyList(
435         StringBuilder sb,
436         String methodName,
437         int parameter,
438         List questionable,
439         List correct) {
440       if(!questionable.equals(correct)) {
441         String s = "\n" + description +
442         " was called with unexpected information in parameter: " + parameter +
443         "\nReceived: " + questionable  +
444         "\nExpected: " + correct;
445       }
446     }
447 
448     private void verifyInnerAnnotationRecorder(
449         StringBuilder sb,
450         Map<String, AnnotationRecorder> questionableAR,
451         Map<String, AnnotationRecorder> correctAR) {
452       // checks on arguments passed in to the methods that created these
453       // AnnotationRecorders (i.e. the checks on the String keys on these maps)
454       // ensure that these have identical keys
455       for(Map.Entry<String, AnnotationRecorder> questionableEntry :
456         questionableAR.entrySet()) {
457         questionableEntry.getValue().verifyAgainst(
458             correctAR.get(questionableEntry.getClass()));
459       }
460     }
461 
462   }
463 
464   /**
465    * A ParameterDescription is a convenient class used to keep information about
466    * method parameters.  Parameters are equal if they have the same index,
467    * regardless of their description.
468    */
469   private class ParameterDescription {
470     public final int parameter;
471     public final String desc;
472     public final boolean visible;
473 
474     public ParameterDescription(int parameter, String desc, boolean visible) {
475       this.parameter = parameter;
476       this.desc = desc;
477       this.visible = visible;
478     }
479 
480     public boolean equals(Object o) {
481       if(o instanceof ParameterDescription) {
482         ParameterDescription p = (ParameterDescription) o;
483         return this.parameter == p.parameter;
484       }
485       return false;
486     }
487 
488     public int hashCode() {
489       return parameter * 17;
490     }
491 
492     public String toString() {
493       return
494         "parameter index: " + parameter +
495         " desc: " + desc +
496         " visible: " + visible;
497     }
498   }
499 
500   /**
501    * An AnnotationMismatchException is an Exception that indicates that
502    * two versions of the same class do not have the same annotations on
503    * either the class, its field, or its methods.
504    */
505   public class AnnotationMismatchException extends RuntimeException {
506     private static final long serialVersionUID = 20060714L; // today's date
507 
508     /**
509      * Constructs a new AnnotationMismatchException with the given error message.
510      *
511      * @param msg the error as to why the annotations do not match.
512      */
513     public AnnotationMismatchException(String msg) {
514       super(msg);
515     }
516   }
517 }
518