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    }