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