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 }