001 package net.sourceforge.retroweaver; 002 003 import java.io.File; 004 import java.io.FileInputStream; 005 import java.io.IOException; 006 import java.io.InputStream; 007 import java.lang.ref.SoftReference; 008 import java.util.ArrayList; 009 import java.util.HashMap; 010 import java.util.HashSet; 011 import java.util.LinkedList; 012 import java.util.List; 013 import java.util.Map; 014 import java.util.Set; 015 import java.util.StringTokenizer; 016 import java.util.jar.JarEntry; 017 import java.util.jar.JarFile; 018 019 import net.sourceforge.retroweaver.event.VerifierListener; 020 021 import org.objectweb.asm.AnnotationVisitor; 022 import org.objectweb.asm.Attribute; 023 import org.objectweb.asm.ClassAdapter; 024 import org.objectweb.asm.ClassReader; 025 import org.objectweb.asm.ClassVisitor; 026 import org.objectweb.asm.FieldVisitor; 027 import org.objectweb.asm.Label; 028 import org.objectweb.asm.MethodAdapter; 029 import org.objectweb.asm.MethodVisitor; 030 import org.objectweb.asm.Opcodes; 031 import org.objectweb.asm.Type; 032 import org.objectweb.asm.commons.EmptyVisitor; 033 034 035 /** 036 * Reads through a class file searching for references to classes, methods, or 037 * fields, which don't exist on the specified classpath. This is primarily 038 * useful when trying to target one JDK while using the compiler for another. 039 */ 040 public class RefVerifier extends ClassAdapter { 041 042 private final int target; 043 044 private String currentclassName; 045 046 private final RetroWeaverClassLoader classLoader; 047 048 private final List<String> classPathArray; 049 050 private Set<String> failedClasses; 051 052 private final VerifierListener listener; 053 054 private int warningCount; 055 056 private final List<String> classes; 057 058 private final Map<String, SoftReference<ClassReader>> classReaderCache = new HashMap<String, SoftReference<ClassReader>>(); 059 060 private static final String nl = System.getProperty("line.separator"); 061 062 public RefVerifier(int target, ClassVisitor cv, List<String> classPathArray, VerifierListener listener) { 063 super(cv); 064 classLoader = new RetroWeaverClassLoader(); 065 this.classPathArray = classPathArray; 066 067 this.listener = listener; 068 this.target = target; 069 070 classes = new LinkedList<String>(); 071 } 072 073 public void addClass(String className) { 074 classes.add(className); 075 } 076 077 public void verifyJarFile(String jarFileName) throws IOException { 078 JarFile jarFile = new JarFile(jarFileName); 079 080 int count = classes.size(); 081 if (count > 0) { 082 listener.verifyPathStarted("Verifying " + count + (count == 1?" class":" classes")); 083 } 084 classLoader.setClassPath(classPathArray); 085 086 for (String name : classes) { 087 JarEntry entry = jarFile.getJarEntry(name); 088 InputStream is = jarFile.getInputStream(entry); 089 verifyClass(is); 090 } 091 } 092 093 public void verifyFiles() throws IOException { 094 int count = classes.size(); 095 if (count > 0) { 096 listener.verifyPathStarted("Verifying " + count + (count == 1?" class":" classes")); 097 } 098 classLoader.setClassPath(classPathArray); 099 100 for (String sourcePath : classes) { 101 verifyClass(new FileInputStream(sourcePath)); 102 } 103 } 104 105 private void verifySingleClass(String classFileName) throws IOException { 106 classLoader.setClassPath(classPathArray); 107 108 verifyClass(new FileInputStream(classFileName)); 109 } 110 111 private void verifyClass(InputStream sourceStream) 112 throws IOException { 113 114 failedClasses = new HashSet<String>(); 115 116 ClassReader cr = new ClassReader(sourceStream); 117 cr.accept(this, 0); 118 } 119 120 private void unknowClassWarning(String className, String msg) { 121 StringBuffer report = new StringBuffer().append(currentclassName) 122 .append(": unknown class ").append(className); 123 124 if (msg != null) { 125 report.append(": ").append(msg); 126 } 127 128 warning(report); 129 } 130 131 private void unknownFieldWarning(String name, String desc, String msg) { 132 StringBuffer report = new StringBuffer().append(currentclassName) 133 .append(": unknown field ").append(name).append('/').append(desc.replace('/', '.')); 134 135 if (msg != null) { 136 report.append(", ").append(msg); 137 } 138 139 warning(report); 140 } 141 142 private void unknownMethodWarning(String name, String desc, String msg) { 143 StringBuffer report = new StringBuffer().append(currentclassName) 144 .append(": unknown method ").append(name).append('/').append(desc.replace('/', '.')); 145 146 if (msg != null) { 147 report.append(", ").append(msg); 148 } 149 150 warning(report); 151 } 152 153 private void invalidClassVersion(String className, int target, int version) { 154 StringBuffer report = new StringBuffer().append(className) 155 .append(": invalid class version ").append(version).append(", target is ").append(target); 156 157 warning(report); 158 } 159 160 private void warning(StringBuffer report) { 161 warningCount++; 162 listener.acceptWarning(report.toString()); 163 } 164 165 public void displaySummary() { 166 if (warningCount != 0) { 167 listener.displaySummary(warningCount); 168 } 169 } 170 171 private ClassReader getClassReader(String className) throws ClassNotFoundException { 172 ClassReader reader = null; 173 SoftReference<ClassReader> ref = classReaderCache.get(className); 174 if (ref != null) { 175 reader = ref.get(); 176 } 177 178 if (reader == null) { 179 byte b[] = classLoader.getClassData(className); 180 181 reader = new ClassReader(b); 182 183 classReaderCache.put(className, new SoftReference<ClassReader>(reader)); 184 185 // class file version should not be higher than target 186 int version = reader.readShort(6); // get major number only 187 if (version > target) { 188 invalidClassVersion(className.replace('/', '.'), target, version); 189 } 190 } 191 return reader; 192 } 193 194 public static String getUsage() { 195 return "Usage: RefVerifier <options>" + nl + " Options: " + nl 196 + " -class <path to class to verify> (required) " + nl 197 + " -cp <classpath containing valid classes> (required)"; 198 } 199 200 public static void main(String[] args) throws IOException { 201 202 List<String> classpath = new ArrayList<String>(); 203 String classfile = null; 204 205 for (int i = 0; i < args.length; ++i) { 206 String command = args[i]; 207 ++i; 208 209 if ("-class".equals(command)) { 210 classfile = args[i]; 211 } else if ("-cp".equals(command)) { 212 String path = args[i]; 213 StringTokenizer st = new StringTokenizer(path, 214 File.pathSeparator); 215 while (st.hasMoreTokens()) { 216 classpath.add(st.nextToken()); 217 } 218 } else { 219 System.out.println("I don't understand the command: " + command); // NOPMD by xlv 220 System.out.println(); // NOPMD by xlv 221 System.out.println(getUsage()); // NOPMD by xlv 222 return; 223 } 224 } 225 226 if (classfile == null) { 227 System.out.println("Option \"-class\" is required."); // NOPMD by xlv 228 System.out.println(); // NOPMD by xlv 229 System.out.println(getUsage()); // NOPMD by xlv 230 return; 231 } 232 233 RefVerifier vr = new RefVerifier(Weaver.VERSION_1_4, EMPTY_VISITOR, classpath, 234 new DefaultListener(true)); 235 vr.verifySingleClass(classfile); 236 vr.displaySummary(); 237 } 238 239 private void checkClassName(String className) { 240 Type t = Type.getType(className); 241 String name; 242 243 switch (t.getSort()) { 244 case Type.ARRAY: 245 t = t.getElementType(); 246 if (t.getSort() != Type.OBJECT) { 247 return; 248 } 249 250 // fall through to object processing 251 case Type.OBJECT: 252 name = t.getClassName(); 253 break; 254 default: 255 return; 256 } 257 258 checkSimpleClassName(name); 259 } 260 261 private void checkClassNameInType(String className) { 262 switch (className.charAt(0)) { 263 case 'L': 264 case '[': 265 checkClassName(className); 266 break; 267 default: 268 checkSimpleClassName(className); 269 } 270 } 271 272 private void checkSimpleClassName(String className) { 273 String name = className.replace('.', '/'); 274 try { 275 getClassReader(name); 276 } catch (ClassNotFoundException e) { 277 failedClasses.add(name); 278 unknowClassWarning(name.replace('/', '.'), null); 279 } 280 } 281 282 // visitor methods 283 284 public void visit( 285 final int version, 286 final int access, 287 final String name, 288 final String signature, 289 final String superName, 290 final String[] interfaces) 291 { 292 listener.verifyClassStarted("Verifying " + name); 293 294 currentclassName = name.replace('/', '.'); 295 296 if (superName != null) { 297 checkSimpleClassName(superName); 298 } 299 if (interfaces != null) { 300 for (int i = 0; i < interfaces.length; ++i) { 301 checkSimpleClassName(interfaces[i]); 302 } 303 } 304 305 cv.visit(version, access, name, signature, superName, interfaces); 306 } 307 308 public void visitOuterClass( 309 final String owner, 310 final String name, 311 final String desc) 312 { 313 checkSimpleClassName(owner); 314 315 cv.visitOuterClass(owner, name, desc); 316 } 317 318 public void visitInnerClass( 319 final String name, 320 final String outerName, 321 final String innerName, 322 final int access) 323 { 324 if (name != null) { 325 checkSimpleClassName(name); 326 } 327 if (outerName != null) { 328 checkSimpleClassName(outerName); 329 } 330 331 cv.visitInnerClass(name, outerName, innerName, access); 332 } 333 334 public MethodVisitor visitMethod( 335 final int access, 336 final String name, 337 final String desc, 338 final String signature, 339 final String[] exceptions) 340 { 341 if (exceptions != null) { 342 for (String s: exceptions) { 343 checkSimpleClassName(s); 344 } 345 } 346 347 return new MethodVerifier(cv.visitMethod(access, name, desc, signature, exceptions)); 348 } 349 350 private class MethodVerifier extends MethodAdapter { 351 352 MethodVerifier(MethodVisitor mv) { 353 super(mv); 354 } 355 356 public void visitTypeInsn(int opcode, String desc) { 357 checkClassNameInType(desc); 358 359 mv.visitTypeInsn(opcode, desc); 360 } 361 362 public void visitFieldInsn(int opcode, String owner, String name, String desc) { 363 // Don't report a field error, about a class for which we've 364 // already shown an error 365 if (!failedClasses.contains(owner)) { 366 try { 367 if (!findField(owner, name, desc)) { 368 unknownFieldWarning(name,desc, "Field not found in " + owner.replace('/', '.')); 369 } 370 } catch (ClassNotFoundException e) { 371 unknownFieldWarning(name,desc, "The class, " + owner.replace('/', '.') 372 + ", could not be located: " + e.getMessage()); 373 } 374 } 375 mv.visitFieldInsn(opcode, owner, name, desc); 376 } 377 378 public void visitMethodInsn(int opcode, String owner, String name, String desc) { 379 if (!failedClasses.contains(owner) && owner.charAt(0) != '[') { 380 // Don't report a method error, about a class for which we've 381 // already shown an error. 382 // We just ignore methods called on arrays, because we know 383 // they must exist 384 385 try { 386 if (!findMethod(owner, name, desc)) { 387 unknownMethodWarning(name, desc, "Method not found in " + owner.replace('/', '.')); 388 } 389 } catch (ClassNotFoundException e) { 390 unknownMethodWarning(name, desc, "The class, " + owner.replace('/', '.') 391 + ", could not be located: " + e.getMessage()); 392 } 393 } 394 395 mv.visitMethodInsn(opcode, owner, name, desc); 396 } 397 398 public void visitMultiANewArrayInsn(String desc, int dims) { 399 checkClassName(desc); 400 401 mv.visitMultiANewArrayInsn(desc, dims); 402 } 403 404 public void visitLocalVariable( 405 String name, 406 String desc, 407 String signature, 408 Label start, 409 Label end, 410 int index) { 411 checkClassName(desc); 412 413 mv.visitLocalVariable(name, desc, signature, start, end, index); 414 } 415 416 } 417 418 private boolean findField(String owner, final String name, final String c) throws ClassNotFoundException { 419 String javaClassName = owner; 420 while (true) { 421 ClassReader reader = getClassReader(javaClassName); 422 FindFieldOrMethodClassVisitor visitor = new FindFieldOrMethodClassVisitor(false, name, c); 423 424 try { 425 reader.accept(visitor, 0); 426 } catch (Success s) { 427 return true; 428 } 429 String[] is = visitor.classInterfaces; 430 for (String i : is) { 431 if (findField(i, name, c)) { 432 return true; 433 } 434 } 435 436 if ("java/lang/Object".equals(javaClassName)) { 437 return false; 438 } 439 javaClassName = visitor.superClassName; 440 } 441 } 442 443 private boolean findMethod(final String owner, final String name, final String desc) throws ClassNotFoundException { 444 String javaClassName = owner; 445 while (true) { 446 ClassReader reader = getClassReader(javaClassName); 447 FindFieldOrMethodClassVisitor visitor = new FindFieldOrMethodClassVisitor(true, name, desc); 448 try { 449 reader.accept(visitor, 0); 450 } catch (Success s) { 451 return true; 452 } 453 454 if (visitor.isInterface || visitor.isAbstract) { 455 String[] is = visitor.classInterfaces; 456 for (String i : is) { 457 if (findMethod(i, name, desc)) { 458 return true; 459 } 460 } 461 if (visitor.isInterface) { 462 return false; 463 } 464 } 465 466 if ("java/lang/Object".equals(javaClassName)) { 467 return false; 468 } 469 javaClassName = visitor.superClassName; 470 } 471 } 472 473 private static final EmptyVisitor EMPTY_VISITOR = new EmptyVisitor(); 474 475 private static class Success extends RuntimeException {}; 476 477 // Visitor to search for fields or methods in supplier classes 478 479 private static class FindFieldOrMethodClassVisitor implements ClassVisitor { 480 FindFieldOrMethodClassVisitor(boolean methdodMatcher, final String name, final String desc) { 481 this.searchedName = name; 482 this.searchedDesc = desc; 483 this.methdodMatcher = methdodMatcher; 484 } 485 private final boolean methdodMatcher; 486 private final String searchedName; 487 private final String searchedDesc; 488 489 protected String classInterfaces[]; 490 protected String superClassName; 491 protected boolean isInterface; 492 protected boolean isAbstract; 493 494 public void visit( 495 final int version, 496 final int access, 497 final String name, 498 final String signature, 499 final String superName, 500 final String[] interfaces) 501 { 502 classInterfaces = interfaces; 503 superClassName = superName; 504 isInterface = (access & Opcodes.ACC_INTERFACE) != 0; 505 isAbstract = (access & Opcodes.ACC_ABSTRACT) != 0; 506 } 507 508 public void visitSource(final String source, final String debug) { 509 } 510 511 public void visitOuterClass( 512 final String owner, 513 final String name, 514 final String desc) 515 { 516 } 517 518 public AnnotationVisitor visitAnnotation( 519 final String desc, 520 final boolean visible) 521 { 522 return EMPTY_VISITOR; 523 } 524 525 public void visitAttribute(final Attribute attr) { 526 } 527 528 public void visitInnerClass( 529 final String name, 530 final String outerName, 531 final String innerName, 532 final int access) 533 { 534 } 535 536 public FieldVisitor visitField( 537 final int access, 538 final String name, 539 final String desc, 540 final String signature, 541 final Object value) { 542 if (!methdodMatcher && name.equals(searchedName) && desc.equals(searchedDesc)) { 543 throw new Success(); 544 } 545 return null; 546 } 547 548 public MethodVisitor visitMethod( 549 int access, 550 String name, 551 String desc, 552 String signature, 553 String[] exceptions) { 554 if (methdodMatcher && name.equals(searchedName) && desc.equals(searchedDesc)) { 555 throw new Success(); 556 } 557 return null; 558 } 559 560 public void visitEnd() { 561 } 562 } 563 564 public static class DefaultListener implements VerifierListener { 565 566 private final boolean verbose; 567 568 DefaultListener(boolean verbose) { 569 this.verbose = verbose; 570 } 571 572 public void verifyPathStarted(String msg) { 573 System.out.println("[RefVerifier] " + msg); // NOPMD by xlv 574 } 575 576 public void verifyClassStarted(String msg) { 577 if (verbose) { 578 System.out.println("[RefVerifier] " + msg); // NOPMD by xlv 579 } 580 } 581 582 public void acceptWarning(String msg) { 583 System.out.println("[RefVerifier] " + msg); // NOPMD by xlv 584 } 585 586 public void displaySummary(int warningCount) { 587 System.out.println("[RefVerifier] Verification complete, " + warningCount + " warning(s)."); // NOPMD by xlv 588 } 589 590 } 591 592 }