1/*
2 * Copyright (C) 2012 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 *      http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17package com.android.tools.layoutlib.create;
18
19import com.android.tools.layoutlib.annotations.VisibleForTesting;
20import com.android.tools.layoutlib.annotations.VisibleForTesting.Visibility;
21
22import org.objectweb.asm.AnnotationVisitor;
23import org.objectweb.asm.Attribute;
24import org.objectweb.asm.ClassReader;
25import org.objectweb.asm.ClassVisitor;
26import org.objectweb.asm.FieldVisitor;
27import org.objectweb.asm.Label;
28import org.objectweb.asm.MethodVisitor;
29import org.objectweb.asm.Type;
30import org.objectweb.asm.signature.SignatureReader;
31import org.objectweb.asm.signature.SignatureVisitor;
32
33import java.io.IOException;
34import java.util.ArrayList;
35import java.util.Enumeration;
36import java.util.List;
37import java.util.Map;
38import java.util.Map.Entry;
39import java.util.Set;
40import java.util.TreeMap;
41import java.util.TreeSet;
42import java.util.zip.ZipEntry;
43import java.util.zip.ZipFile;
44
45/**
46 * Analyzes the input JAR using the ASM java bytecode manipulation library
47 * to list the classes and their dependencies. A "dependency" is a class
48 * used by another class.
49 */
50public class DependencyFinder {
51
52    // Note: a bunch of stuff has package-level access for unit tests. Consider it private.
53
54    /** Output logger. */
55    private final Log mLog;
56
57    /**
58     * Creates a new analyzer.
59     *
60     * @param log The log output.
61     */
62    public DependencyFinder(Log log) {
63        mLog = log;
64    }
65
66    /**
67     * Starts the analysis using parameters from the constructor.
68     *
69     * @param osJarPath The input source JARs to parse.
70     * @return A pair: [0]: map { class FQCN => set of FQCN class dependencies }.
71     *                 [1]: map { missing class FQCN => set of FQCN class that uses it. }
72     */
73    public List<Map<String, Set<String>>> findDeps(List<String> osJarPath) throws IOException {
74
75        Map<String, ClassReader> zipClasses = parseZip(osJarPath);
76        mLog.info("Found %d classes in input JAR%s.",
77                zipClasses.size(),
78                osJarPath.size() > 1 ? "s" : "");
79
80        Map<String, Set<String>> deps = findClassesDeps(zipClasses);
81
82        Map<String, Set<String>> missing = findMissingClasses(deps, zipClasses.keySet());
83
84        List<Map<String, Set<String>>> result = new ArrayList<>(2);
85        result.add(deps);
86        result.add(missing);
87        return result;
88    }
89
90    /**
91     * Prints dependencies to the current logger, found stuff and missing stuff.
92     */
93    public void printAllDeps(List<Map<String, Set<String>>> result) {
94        assert result.size() == 2;
95        Map<String, Set<String>> deps = result.get(0);
96        Map<String, Set<String>> missing = result.get(1);
97
98        // Print all dependences found in the format:
99        // +Found: <FQCN from zip>
100        //     uses: FQCN
101
102        mLog.info("++++++ %d Entries found in source JARs", deps.size());
103        mLog.info("");
104
105        for (Entry<String, Set<String>> entry : deps.entrySet()) {
106            mLog.info(    "+Found  : %s", entry.getKey());
107            for (String dep : entry.getValue()) {
108                mLog.info("    uses: %s", dep);
109            }
110
111            mLog.info("");
112        }
113
114
115        // Now print all missing dependences in the format:
116        // -Missing <FQCN>:
117        //     used by: <FQCN>
118
119        mLog.info("");
120        mLog.info("------ %d Entries missing from source JARs", missing.size());
121        mLog.info("");
122
123        for (Entry<String, Set<String>> entry : missing.entrySet()) {
124            mLog.info(    "-Missing  : %s", entry.getKey());
125            for (String dep : entry.getValue()) {
126                mLog.info("   used by: %s", dep);
127            }
128
129            mLog.info("");
130        }
131    }
132
133    /**
134     * Prints only a summary of the missing dependencies to the current logger.
135     */
136    public void printMissingDeps(List<Map<String, Set<String>>> result) {
137        assert result.size() == 2;
138        @SuppressWarnings("unused") Map<String, Set<String>> deps = result.get(0);
139        Map<String, Set<String>> missing = result.get(1);
140
141        for (String fqcn : missing.keySet()) {
142            mLog.info("%s", fqcn);
143        }
144    }
145
146    // ----------------
147
148    /**
149     * Parses a JAR file and returns a list of all classes founds using a map
150     * class name => ASM ClassReader. Class names are in the form "android.view.View".
151     */
152    Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
153        TreeMap<String, ClassReader> classes = new TreeMap<>();
154
155        for (String jarPath : jarPathList) {
156            ZipFile zip = new ZipFile(jarPath);
157            Enumeration<? extends ZipEntry> entries = zip.entries();
158            ZipEntry entry;
159            while (entries.hasMoreElements()) {
160                entry = entries.nextElement();
161                if (entry.getName().endsWith(".class")) {
162                    ClassReader cr = new ClassReader(zip.getInputStream(entry));
163                    String className = classReaderToClassName(cr);
164                    classes.put(className, cr);
165                }
166            }
167        }
168
169        return classes;
170    }
171
172    /**
173     * Utility that returns the fully qualified binary class name for a ClassReader.
174     * E.g. it returns something like android.view.View.
175     */
176    static String classReaderToClassName(ClassReader classReader) {
177        if (classReader == null) {
178            return null;
179        } else {
180            return classReader.getClassName().replace('/', '.');
181        }
182    }
183
184    /**
185     * Utility that returns the fully qualified binary class name from a path-like FQCN.
186     * E.g. it returns android.view.View from android/view/View.
187     */
188    static String internalToBinaryClassName(String className) {
189        if (className == null) {
190            return null;
191        } else {
192            return className.replace('/', '.');
193        }
194    }
195
196    /**
197     * Finds all dependencies for all classes in keepClasses which are also
198     * listed in zipClasses. Returns a map of all the dependencies found.
199     */
200    Map<String, Set<String>> findClassesDeps(Map<String, ClassReader> zipClasses) {
201
202        // The dependencies that we'll collect.
203        // It's a map Class name => uses class names.
204        Map<String, Set<String>> dependencyMap = new TreeMap<>();
205
206        DependencyVisitor visitor = getVisitor();
207
208        int count = 0;
209        try {
210            for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
211                String name = entry.getKey();
212
213                TreeSet<String> set = new TreeSet<>();
214                dependencyMap.put(name, set);
215                visitor.setDependencySet(set);
216
217                ClassReader cr = entry.getValue();
218                cr.accept(visitor, 0 /* flags */);
219
220                visitor.setDependencySet(null);
221
222                mLog.debugNoln("Visited %d classes\r", ++count);
223            }
224        } finally {
225            mLog.debugNoln("\n");
226        }
227
228        return dependencyMap;
229    }
230
231    /**
232     * Computes which classes FQCN were found as dependencies that are NOT listed
233     * in the original JAR classes.
234     *
235     * @param deps The map { FQCN => dependencies[] } returned by {@link #findClassesDeps(Map)}.
236     * @param zipClasses The set of all classes FQCN found in the JAR files.
237     * @return A map { FQCN not found in the zipClasses => classes using it }
238     */
239    private Map<String, Set<String>> findMissingClasses(
240            Map<String, Set<String>> deps,
241            Set<String> zipClasses) {
242        Map<String, Set<String>> missing = new TreeMap<>();
243
244        for (Entry<String, Set<String>> entry : deps.entrySet()) {
245            String name = entry.getKey();
246
247            for (String dep : entry.getValue()) {
248                if (!zipClasses.contains(dep)) {
249                    // This dependency doesn't exist in the zip classes.
250                    Set<String> set = missing.get(dep);
251                    if (set == null) {
252                        set = new TreeSet<>();
253                        missing.put(dep, set);
254                    }
255                    set.add(name);
256                }
257            }
258
259        }
260
261        return missing;
262    }
263
264
265    // ----------------------------------
266
267    /**
268     * Instantiates a new DependencyVisitor. Useful for unit tests.
269     */
270    @VisibleForTesting(visibility=Visibility.PRIVATE)
271    DependencyVisitor getVisitor() {
272        return new DependencyVisitor();
273    }
274
275    /**
276     * Visitor to collect all the type dependencies from a class.
277     */
278    public class DependencyVisitor extends ClassVisitor {
279
280        private Set<String> mCurrentDepSet;
281
282        /**
283         * Creates a new visitor that will find all the dependencies for the visited class.
284         */
285        public DependencyVisitor() {
286            super(Main.ASM_VERSION);
287        }
288
289        /**
290         * Sets the {@link Set} where to record direct dependencies for this class.
291         * This will change before each {@link ClassReader#accept(ClassVisitor, int)} call.
292         */
293        public void setDependencySet(Set<String> set) {
294            mCurrentDepSet = set;
295        }
296
297        /**
298         * Considers the given class name as a dependency.
299         */
300        public void considerName(String className) {
301            if (className == null) {
302                return;
303            }
304
305            className = internalToBinaryClassName(className);
306
307            try {
308                // exclude classes that are part of the default JRE (the one executing this program)
309                // or in java package (we won't be able to load them anyway).
310                if (className.startsWith("java.") ||
311                        getClass().getClassLoader().loadClass(className) != null) {
312                    return;
313                }
314            } catch (ClassNotFoundException e) {
315                // ignore
316            }
317
318            // Add it to the dependency set for the currently visited class, as needed.
319            assert mCurrentDepSet != null;
320            mCurrentDepSet.add(className);
321        }
322
323        /**
324         * Considers this array of names using considerName().
325         */
326        public void considerNames(String[] classNames) {
327            if (classNames != null) {
328                for (String className : classNames) {
329                    considerName(className);
330                }
331            }
332        }
333
334        /**
335         * Considers this signature or type signature by invoking the {@link SignatureVisitor}
336         * on it.
337         */
338        public void considerSignature(String signature) {
339            if (signature != null) {
340                SignatureReader sr = new SignatureReader(signature);
341                // SignatureReader.accept will call accessType so we don't really have
342                // to differentiate where the signature comes from.
343                sr.accept(new MySignatureVisitor());
344            }
345        }
346
347        /**
348         * Considers this {@link Type}. For arrays, the element type is considered.
349         * If the type is an object, it's internal name is considered.
350         */
351        public void considerType(Type t) {
352            if (t != null) {
353                if (t.getSort() == Type.ARRAY) {
354                    t = t.getElementType();
355                }
356                if (t.getSort() == Type.OBJECT) {
357                    considerName(t.getInternalName());
358                }
359            }
360        }
361
362        /**
363         * Considers a descriptor string. The descriptor is converted to a {@link Type}
364         * and then considerType() is invoked.
365         */
366        public boolean considerDesc(String desc) {
367            if (desc != null) {
368                try {
369                    if (desc.length() > 0 && desc.charAt(0) == '(') {
370                        // This is a method descriptor with arguments and a return type.
371                        Type t = Type.getReturnType(desc);
372                        considerType(t);
373
374                        for (Type arg : Type.getArgumentTypes(desc)) {
375                            considerType(arg);
376                        }
377
378                    } else {
379                        Type t = Type.getType(desc);
380                        considerType(t);
381                    }
382                    return true;
383                } catch (ArrayIndexOutOfBoundsException e) {
384                    // ignore, not a valid type.
385                }
386            }
387            return false;
388        }
389
390
391        // ---------------------------------------------------
392        // --- ClassVisitor, FieldVisitor
393        // ---------------------------------------------------
394
395        // Visits a class header
396        @Override
397        public void visit(int version, int access, String name,
398                String signature, String superName, String[] interfaces) {
399            // signature is the signature of this class. May be null if the class is not a generic
400            // one, and does not extend or implement generic classes or interfaces.
401
402            if (signature != null) {
403                considerSignature(signature);
404            }
405
406            // superName is the internal of name of the super class (see getInternalName).
407            // For interfaces, the super class is Object. May be null but only for the Object class.
408            considerName(superName);
409
410            // interfaces is the internal names of the class's interfaces (see getInternalName).
411            // May be null.
412            considerNames(interfaces);
413        }
414
415
416        @Override
417        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
418            // desc is the class descriptor of the annotation class.
419            considerDesc(desc);
420            return new MyAnnotationVisitor();
421        }
422
423        @Override
424        public void visitAttribute(Attribute attr) {
425            // pass
426        }
427
428        // Visits the end of a class
429        @Override
430        public void visitEnd() {
431            // pass
432        }
433
434        private class MyFieldVisitor extends FieldVisitor {
435
436            public MyFieldVisitor() {
437                super(Main.ASM_VERSION);
438            }
439
440            @Override
441            public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
442                // desc is the class descriptor of the annotation class.
443                considerDesc(desc);
444                return new MyAnnotationVisitor();
445            }
446
447            @Override
448            public void visitAttribute(Attribute attr) {
449                // pass
450            }
451
452            // Visits the end of a class
453            @Override
454            public void visitEnd() {
455                // pass
456            }
457        }
458
459        @Override
460        public FieldVisitor visitField(int access, String name, String desc,
461                String signature, Object value) {
462            // desc is the field's descriptor (see Type).
463            considerDesc(desc);
464
465            // signature is the field's signature. May be null if the field's type does not use
466            // generic types.
467            considerSignature(signature);
468
469            return new MyFieldVisitor();
470        }
471
472        @Override
473        public void visitInnerClass(String name, String outerName, String innerName, int access) {
474            // name is the internal name of an inner class (see getInternalName).
475            // Note: outerName/innerName seems to be null when we're reading the
476            // _Original_ClassName classes generated by layoutlib_create.
477            if (outerName != null) {
478                considerName(name);
479            }
480        }
481
482        @Override
483        public MethodVisitor visitMethod(int access, String name, String desc,
484                String signature, String[] exceptions) {
485            // desc is the method's descriptor (see Type).
486            considerDesc(desc);
487            // signature is the method's signature. May be null if the method parameters, return
488            // type and exceptions do not use generic types.
489            considerSignature(signature);
490
491            return new MyMethodVisitor();
492        }
493
494        @Override
495        public void visitOuterClass(String owner, String name, String desc) {
496            // pass
497        }
498
499        @Override
500        public void visitSource(String source, String debug) {
501            // pass
502        }
503
504
505        // ---------------------------------------------------
506        // --- MethodVisitor
507        // ---------------------------------------------------
508
509        private class MyMethodVisitor extends MethodVisitor {
510
511            public MyMethodVisitor() {
512                super(Main.ASM_VERSION);
513            }
514
515
516            @Override
517            public AnnotationVisitor visitAnnotationDefault() {
518                return new MyAnnotationVisitor();
519            }
520
521            @Override
522            public void visitCode() {
523                // pass
524            }
525
526            // field instruction
527            @Override
528            public void visitFieldInsn(int opcode, String owner, String name, String desc) {
529                // owner is the class that declares the field.
530                considerName(owner);
531                // desc is the field's descriptor (see Type).
532                considerDesc(desc);
533            }
534
535            @Override
536            public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) {
537                // pass
538            }
539
540            @Override
541            public void visitIincInsn(int var, int increment) {
542                // pass -- an IINC instruction
543            }
544
545            @Override
546            public void visitInsn(int opcode) {
547                // pass -- a zero operand instruction
548            }
549
550            @Override
551            public void visitIntInsn(int opcode, int operand) {
552                // pass -- a single int operand instruction
553            }
554
555            @Override
556            public void visitJumpInsn(int opcode, Label label) {
557                // pass -- a jump instruction
558            }
559
560            @Override
561            public void visitLabel(Label label) {
562                // pass -- a label target
563            }
564
565            // instruction to load a constant from the stack
566            @Override
567            public void visitLdcInsn(Object cst) {
568                if (cst instanceof Type) {
569                    considerType((Type) cst);
570                }
571            }
572
573            @Override
574            public void visitLineNumber(int line, Label start) {
575                // pass
576            }
577
578            @Override
579            public void visitLocalVariable(String name, String desc,
580                    String signature, Label start, Label end, int index) {
581                // desc is the type descriptor of this local variable.
582                considerDesc(desc);
583                // signature is the type signature of this local variable. May be null if the local
584                // variable type does not use generic types.
585                considerSignature(signature);
586            }
587
588            @Override
589            public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
590                // pass -- a lookup switch instruction
591            }
592
593            @Override
594            public void visitMaxs(int maxStack, int maxLocals) {
595                // pass
596            }
597
598            // instruction that invokes a method
599            @Override
600            public void visitMethodInsn(int opcode, String owner, String name, String desc,
601                    boolean itf) {
602
603                // owner is the internal name of the method's owner class
604                if (!considerDesc(owner) && owner.indexOf('/') != -1) {
605                    considerName(owner);
606                }
607                // desc is the method's descriptor (see Type).
608                considerDesc(desc);
609            }
610
611            // instruction multianewarray, whatever that is
612            @Override
613            public void visitMultiANewArrayInsn(String desc, int dims) {
614
615                // desc an array type descriptor.
616                considerDesc(desc);
617            }
618
619            @Override
620            public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
621                    boolean visible) {
622                // desc is the class descriptor of the annotation class.
623                considerDesc(desc);
624                return new MyAnnotationVisitor();
625            }
626
627            @Override
628            public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
629                // pass -- table switch instruction
630
631            }
632
633            @Override
634            public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
635                // type is the internal name of the type of exceptions handled by the handler,
636                // or null to catch any exceptions (for "finally" blocks).
637                considerName(type);
638            }
639
640            // type instruction
641            @Override
642            public void visitTypeInsn(int opcode, String type) {
643                // type is the operand of the instruction to be visited. This operand must be the
644                // internal name of an object or array class.
645                considerName(type);
646            }
647
648            @Override
649            public void visitVarInsn(int opcode, int var) {
650                // pass -- local variable instruction
651            }
652        }
653
654        private class MySignatureVisitor extends SignatureVisitor {
655
656            public MySignatureVisitor() {
657                super(Main.ASM_VERSION);
658            }
659
660            // ---------------------------------------------------
661            // --- SignatureVisitor
662            // ---------------------------------------------------
663
664            private String mCurrentSignatureClass = null;
665
666            // Starts the visit of a signature corresponding to a class or interface type
667            @Override
668            public void visitClassType(String name) {
669                mCurrentSignatureClass = name;
670                considerName(name);
671            }
672
673            // Visits an inner class
674            @Override
675            public void visitInnerClassType(String name) {
676                if (mCurrentSignatureClass != null) {
677                    mCurrentSignatureClass += "$" + name;
678                    considerName(mCurrentSignatureClass);
679                }
680            }
681
682            @Override
683            public SignatureVisitor visitArrayType() {
684                return new MySignatureVisitor();
685            }
686
687            @Override
688            public void visitBaseType(char descriptor) {
689                // pass -- a primitive type, ignored
690            }
691
692            @Override
693            public SignatureVisitor visitClassBound() {
694                return new MySignatureVisitor();
695            }
696
697            @Override
698            public SignatureVisitor visitExceptionType() {
699                return new MySignatureVisitor();
700            }
701
702            @Override
703            public void visitFormalTypeParameter(String name) {
704                // pass
705            }
706
707            @Override
708            public SignatureVisitor visitInterface() {
709                return new MySignatureVisitor();
710            }
711
712            @Override
713            public SignatureVisitor visitInterfaceBound() {
714                return new MySignatureVisitor();
715            }
716
717            @Override
718            public SignatureVisitor visitParameterType() {
719                return new MySignatureVisitor();
720            }
721
722            @Override
723            public SignatureVisitor visitReturnType() {
724                return new MySignatureVisitor();
725            }
726
727            @Override
728            public SignatureVisitor visitSuperclass() {
729                return new MySignatureVisitor();
730            }
731
732            @Override
733            public SignatureVisitor visitTypeArgument(char wildcard) {
734                return new MySignatureVisitor();
735            }
736
737            @Override
738            public void visitTypeVariable(String name) {
739                // pass
740            }
741
742            @Override
743            public void visitTypeArgument() {
744                // pass
745            }
746        }
747
748
749        // ---------------------------------------------------
750        // --- AnnotationVisitor
751        // ---------------------------------------------------
752
753        private class MyAnnotationVisitor extends AnnotationVisitor {
754
755            public MyAnnotationVisitor() {
756                super(Main.ASM_VERSION);
757            }
758
759            // Visits a primitive value of an annotation
760            @Override
761            public void visit(String name, Object value) {
762                // value is the actual value, whose type must be Byte, Boolean, Character, Short,
763                // Integer, Long, Float, Double, String or Type
764                if (value instanceof Type) {
765                    considerType((Type) value);
766                }
767            }
768
769            @Override
770            public AnnotationVisitor visitAnnotation(String name, String desc) {
771                // desc is the class descriptor of the nested annotation class.
772                considerDesc(desc);
773                return new MyAnnotationVisitor();
774            }
775
776            @Override
777            public AnnotationVisitor visitArray(String name) {
778                return new MyAnnotationVisitor();
779            }
780
781            @Override
782            public void visitEnum(String name, String desc, String value) {
783                // desc is the class descriptor of the enumeration class.
784                considerDesc(desc);
785            }
786        }
787    }
788}
789