1/*
2 * Copyright (C) 2008 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 org.objectweb.asm.AnnotationVisitor;
20import org.objectweb.asm.Attribute;
21import org.objectweb.asm.ClassReader;
22import org.objectweb.asm.ClassVisitor;
23import org.objectweb.asm.FieldVisitor;
24import org.objectweb.asm.Label;
25import org.objectweb.asm.MethodVisitor;
26import org.objectweb.asm.Type;
27import org.objectweb.asm.signature.SignatureReader;
28import org.objectweb.asm.signature.SignatureVisitor;
29
30import java.io.IOException;
31import java.util.ArrayList;
32import java.util.Enumeration;
33import java.util.List;
34import java.util.Map;
35import java.util.TreeMap;
36import java.util.Map.Entry;
37import java.util.regex.Pattern;
38import java.util.zip.ZipEntry;
39import java.util.zip.ZipFile;
40
41/**
42 * Analyzes the input JAR using the ASM java bytecode manipulation library
43 * to list the desired classes and their dependencies.
44 */
45public class AsmAnalyzer {
46
47    // Note: a bunch of stuff has package-level access for unit tests. Consider it private.
48
49    /** Output logger. */
50    private final Log mLog;
51    /** The input source JAR to parse. */
52    private final List<String> mOsSourceJar;
53    /** The generator to fill with the class list and dependency list. */
54    private final AsmGenerator mGen;
55    /** Keep all classes that derive from these one (these included). */
56    private final String[] mDeriveFrom;
57    /** Glob patterns of classes to keep, e.g. "com.foo.*" */
58    private final String[] mIncludeGlobs;
59
60    /**
61     * Creates a new analyzer.
62     *
63     * @param log The log output.
64     * @param osJarPath The input source JARs to parse.
65     * @param gen The generator to fill with the class list and dependency list.
66     * @param deriveFrom Keep all classes that derive from these one (these included).
67     * @param includeGlobs Glob patterns of classes to keep, e.g. "com.foo.*"
68     *        ("*" does not matches dots whilst "**" does, "." and "$" are interpreted as-is)
69     */
70    public AsmAnalyzer(Log log, List<String> osJarPath, AsmGenerator gen,
71            String[] deriveFrom, String[] includeGlobs) {
72        mLog = log;
73        mGen = gen;
74        mOsSourceJar = osJarPath != null ? osJarPath : new ArrayList<String>();
75        mDeriveFrom = deriveFrom != null ? deriveFrom : new String[0];
76        mIncludeGlobs = includeGlobs != null ? includeGlobs : new String[0];
77    }
78
79    /**
80     * Starts the analysis using parameters from the constructor.
81     * Fills the generator with classes & dependencies found.
82     */
83    public void analyze() throws IOException, LogAbortException {
84
85        AsmAnalyzer visitor = this;
86
87        Map<String, ClassReader> zipClasses = parseZip(mOsSourceJar);
88        mLog.info("Found %d classes in input JAR%s.", zipClasses.size(),
89                mOsSourceJar.size() > 1 ? "s" : "");
90
91        Map<String, ClassReader> found = findIncludes(zipClasses);
92        Map<String, ClassReader> deps = findDeps(zipClasses, found);
93
94        if (mGen != null) {
95            mGen.setKeep(found);
96            mGen.setDeps(deps);
97        }
98    }
99
100    /**
101     * Parses a JAR file and returns a list of all classes founds using a map
102     * class name => ASM ClassReader. Class names are in the form "android.view.View".
103     */
104    Map<String,ClassReader> parseZip(List<String> jarPathList) throws IOException {
105        TreeMap<String, ClassReader> classes = new TreeMap<String, ClassReader>();
106
107        for (String jarPath : jarPathList) {
108            ZipFile zip = new ZipFile(jarPath);
109            Enumeration<? extends ZipEntry> entries = zip.entries();
110            ZipEntry entry;
111            while (entries.hasMoreElements()) {
112                entry = entries.nextElement();
113                if (entry.getName().endsWith(".class")) {
114                    ClassReader cr = new ClassReader(zip.getInputStream(entry));
115                    String className = classReaderToClassName(cr);
116                    classes.put(className, cr);
117                }
118            }
119        }
120
121        return classes;
122    }
123
124    /**
125     * Utility that returns the fully qualified binary class name for a ClassReader.
126     * E.g. it returns something like android.view.View.
127     */
128    static String classReaderToClassName(ClassReader classReader) {
129        if (classReader == null) {
130            return null;
131        } else {
132            return classReader.getClassName().replace('/', '.');
133        }
134    }
135
136    /**
137     * Utility that returns the fully qualified binary class name from a path-like FQCN.
138     * E.g. it returns android.view.View from android/view/View.
139     */
140    static String internalToBinaryClassName(String className) {
141        if (className == null) {
142            return null;
143        } else {
144            return className.replace('/', '.');
145        }
146    }
147
148    /**
149     * Process the "includes" arrays.
150     * <p/>
151     * This updates the in_out_found map.
152     */
153    Map<String, ClassReader> findIncludes(Map<String, ClassReader> zipClasses)
154            throws LogAbortException {
155        TreeMap<String, ClassReader> found = new TreeMap<String, ClassReader>();
156
157        mLog.debug("Find classes to include.");
158
159        for (String s : mIncludeGlobs) {
160            findGlobs(s, zipClasses, found);
161        }
162        for (String s : mDeriveFrom) {
163            findClassesDerivingFrom(s, zipClasses, found);
164        }
165
166        return found;
167    }
168
169
170    /**
171     * Uses ASM to find the class reader for the given FQCN class name.
172     * If found, insert it in the in_out_found map.
173     * Returns the class reader object.
174     */
175    ClassReader findClass(String className, Map<String, ClassReader> zipClasses,
176            Map<String, ClassReader> inOutFound) throws LogAbortException {
177        ClassReader classReader = zipClasses.get(className);
178        if (classReader == null) {
179            throw new LogAbortException("Class %s not found by ASM in %s",
180                    className, mOsSourceJar);
181        }
182
183        inOutFound.put(className, classReader);
184        return classReader;
185    }
186
187    /**
188     * Insert in the inOutFound map all classes found in zipClasses that match the
189     * given glob pattern.
190     * <p/>
191     * The glob pattern is not a regexp. It only accepts the "*" keyword to mean
192     * "anything but a period". The "." and "$" characters match themselves.
193     * The "**" keyword means everything including ".".
194     * <p/>
195     * Examples:
196     * <ul>
197     * <li>com.foo.* matches all classes in the package com.foo but NOT sub-packages.
198     * <li>com.foo*.*$Event matches all internal Event classes in a com.foo*.* class.
199     * </ul>
200     */
201    void findGlobs(String globPattern, Map<String, ClassReader> zipClasses,
202            Map<String, ClassReader> inOutFound) throws LogAbortException {
203        // transforms the glob pattern in a regexp:
204        // - escape "." with "\."
205        // - replace "*" by "[^.]*"
206        // - escape "$" with "\$"
207        // - add end-of-line match $
208        globPattern = globPattern.replaceAll("\\$", "\\\\\\$");
209        globPattern = globPattern.replaceAll("\\.", "\\\\.");
210        // prevent ** from being altered by the next rule, then process the * rule and finally
211        // the real ** rule (which is now @)
212        globPattern = globPattern.replaceAll("\\*\\*", "@");
213        globPattern = globPattern.replaceAll("\\*", "[^.]*");
214        globPattern = globPattern.replaceAll("@", ".*");
215        globPattern += "$";
216
217        Pattern regexp = Pattern.compile(globPattern);
218
219        for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
220            String class_name = entry.getKey();
221            if (regexp.matcher(class_name).matches()) {
222                findClass(class_name, zipClasses, inOutFound);
223            }
224        }
225    }
226
227    /**
228     * Checks all the classes defined in the JarClassName instance and uses BCEL to
229     * determine if they are derived from the given FQCN super class name.
230     * Inserts the super class and all the class objects found in the map.
231     */
232    void findClassesDerivingFrom(String super_name, Map<String, ClassReader> zipClasses,
233            Map<String, ClassReader> inOutFound) throws LogAbortException {
234        ClassReader super_clazz = findClass(super_name, zipClasses, inOutFound);
235
236        for (Entry<String, ClassReader> entry : zipClasses.entrySet()) {
237            String className = entry.getKey();
238            if (super_name.equals(className)) {
239                continue;
240            }
241            ClassReader classReader = entry.getValue();
242            ClassReader parent_cr = classReader;
243            while (parent_cr != null) {
244                String parent_name = internalToBinaryClassName(parent_cr.getSuperName());
245                if (parent_name == null) {
246                    // not found
247                    break;
248                } else if (super_name.equals(parent_name)) {
249                    inOutFound.put(className, classReader);
250                    break;
251                }
252                parent_cr = zipClasses.get(parent_name);
253            }
254        }
255    }
256
257    /**
258     * Instantiates a new DependencyVisitor. Useful for unit tests.
259     */
260    DependencyVisitor getVisitor(Map<String, ClassReader> zipClasses,
261            Map<String, ClassReader> inKeep,
262            Map<String, ClassReader> outKeep,
263            Map<String, ClassReader> inDeps,
264            Map<String, ClassReader> outDeps) {
265        return new DependencyVisitor(zipClasses, inKeep, outKeep, inDeps, outDeps);
266    }
267
268    /**
269     * Finds all dependencies for all classes in keepClasses which are also
270     * listed in zipClasses. Returns a map of all the dependencies found.
271     */
272    Map<String, ClassReader> findDeps(Map<String, ClassReader> zipClasses,
273            Map<String, ClassReader> inOutKeepClasses) {
274
275        TreeMap<String, ClassReader> deps = new TreeMap<String, ClassReader>();
276        TreeMap<String, ClassReader> new_deps = new TreeMap<String, ClassReader>();
277        TreeMap<String, ClassReader> new_keep = new TreeMap<String, ClassReader>();
278        TreeMap<String, ClassReader> temp = new TreeMap<String, ClassReader>();
279
280        DependencyVisitor visitor = getVisitor(zipClasses,
281                inOutKeepClasses, new_keep,
282                deps, new_deps);
283
284        for (ClassReader cr : inOutKeepClasses.values()) {
285            cr.accept(visitor, 0 /* flags */);
286        }
287
288        while (new_deps.size() > 0 || new_keep.size() > 0) {
289            deps.putAll(new_deps);
290            inOutKeepClasses.putAll(new_keep);
291
292            temp.clear();
293            temp.putAll(new_deps);
294            temp.putAll(new_keep);
295            new_deps.clear();
296            new_keep.clear();
297            mLog.debug("Found %1$d to keep, %2$d dependencies.",
298                    inOutKeepClasses.size(), deps.size());
299
300            for (ClassReader cr : temp.values()) {
301                cr.accept(visitor, 0 /* flags */);
302            }
303        }
304
305        mLog.info("Found %1$d classes to keep, %2$d class dependencies.",
306                inOutKeepClasses.size(), deps.size());
307
308        return deps;
309    }
310
311
312
313    // ----------------------------------
314
315    /**
316     * Visitor to collect all the type dependencies from a class.
317     */
318    public class DependencyVisitor
319        implements ClassVisitor, FieldVisitor, MethodVisitor, SignatureVisitor, AnnotationVisitor {
320
321        /** All classes found in the source JAR. */
322        private final Map<String, ClassReader> mZipClasses;
323        /** Classes from which dependencies are to be found. */
324        private final Map<String, ClassReader> mInKeep;
325        /** Dependencies already known. */
326        private final Map<String, ClassReader> mInDeps;
327        /** New dependencies found by this visitor. */
328        private final Map<String, ClassReader> mOutDeps;
329        /** New classes to keep as-is found by this visitor. */
330        private final Map<String, ClassReader> mOutKeep;
331
332        /**
333         * Creates a new visitor that will find all the dependencies for the visited class.
334         * Types which are already in the zipClasses, keepClasses or inDeps are not marked.
335         * New dependencies are marked in outDeps.
336         *
337         * @param zipClasses All classes found in the source JAR.
338         * @param inKeep Classes from which dependencies are to be found.
339         * @param inDeps Dependencies already known.
340         * @param outDeps New dependencies found by this visitor.
341         */
342        public DependencyVisitor(Map<String, ClassReader> zipClasses,
343                Map<String, ClassReader> inKeep,
344                Map<String, ClassReader> outKeep,
345                Map<String,ClassReader> inDeps,
346                Map<String,ClassReader> outDeps) {
347            mZipClasses = zipClasses;
348            mInKeep = inKeep;
349            mOutKeep = outKeep;
350            mInDeps = inDeps;
351            mOutDeps = outDeps;
352        }
353
354        /**
355         * Considers the given class name as a dependency.
356         * If it does, add to the mOutDeps map.
357         */
358        public void considerName(String className) {
359            if (className == null) {
360                return;
361            }
362
363            className = internalToBinaryClassName(className);
364
365            // exclude classes that have already been found
366            if (mInKeep.containsKey(className) ||
367                    mOutKeep.containsKey(className) ||
368                    mInDeps.containsKey(className) ||
369                    mOutDeps.containsKey(className)) {
370                return;
371            }
372
373            // exclude classes that are not part of the JAR file being examined
374            ClassReader cr = mZipClasses.get(className);
375            if (cr == null) {
376                return;
377            }
378
379            try {
380                // exclude classes that are part of the default JRE (the one executing this program)
381                if (getClass().getClassLoader().loadClass(className) != null) {
382                    return;
383                }
384            } catch (ClassNotFoundException e) {
385                // ignore
386            }
387
388            // accept this class:
389            // - android classes are added to dependencies
390            // - non-android classes are added to the list of classes to keep as-is (they don't need
391            //   to be stubbed).
392            if (className.indexOf("android") >= 0) {  // TODO make configurable
393                mOutDeps.put(className, cr);
394            } else {
395                mOutKeep.put(className, cr);
396            }
397        }
398
399        /**
400         * Considers this array of names using considerName().
401         */
402        public void considerNames(String[] classNames) {
403            if (classNames != null) {
404                for (String className : classNames) {
405                    considerName(className);
406                }
407            }
408        }
409
410        /**
411         * Considers this signature or type signature by invoking the {@link SignatureVisitor}
412         * on it.
413         */
414        public void considerSignature(String signature) {
415            if (signature != null) {
416                SignatureReader sr = new SignatureReader(signature);
417                // SignatureReader.accept will call accessType so we don't really have
418                // to differentiate where the signature comes from.
419                sr.accept(this);
420            }
421        }
422
423        /**
424         * Considers this {@link Type}. For arrays, the element type is considered.
425         * If the type is an object, it's internal name is considered.
426         */
427        public void considerType(Type t) {
428            if (t != null) {
429                if (t.getSort() == Type.ARRAY) {
430                    t = t.getElementType();
431                }
432                if (t.getSort() == Type.OBJECT) {
433                    considerName(t.getInternalName());
434                }
435            }
436        }
437
438        /**
439         * Considers a descriptor string. The descriptor is converted to a {@link Type}
440         * and then considerType() is invoked.
441         */
442        public void considerDesc(String desc) {
443            if (desc != null) {
444                try {
445                    Type t = Type.getType(desc);
446                    considerType(t);
447                } catch (ArrayIndexOutOfBoundsException e) {
448                    // ignore, not a valid type.
449                }
450            }
451        }
452
453
454        // ---------------------------------------------------
455        // --- ClassVisitor, FieldVisitor
456        // ---------------------------------------------------
457
458        // Visits a class header
459        public void visit(int version, int access, String name,
460                String signature, String superName, String[] interfaces) {
461            // signature is the signature of this class. May be null if the class is not a generic
462            // one, and does not extend or implement generic classes or interfaces.
463
464            if (signature != null) {
465                considerSignature(signature);
466            }
467
468            // superName is the internal of name of the super class (see getInternalName).
469            // For interfaces, the super class is Object. May be null but only for the Object class.
470            considerName(superName);
471
472            // interfaces is the internal names of the class's interfaces (see getInternalName).
473            // May be null.
474            considerNames(interfaces);
475        }
476
477        public AnnotationVisitor visitAnnotation(String desc, boolean visible) {
478            // desc is the class descriptor of the annotation class.
479            considerDesc(desc);
480            return this; // return this to visit annotion values
481        }
482
483        public void visitAttribute(Attribute attr) {
484            // pass
485        }
486
487        // Visits the end of a class
488        public void visitEnd() {
489            // pass
490        }
491
492        public FieldVisitor visitField(int access, String name, String desc,
493                String signature, Object value) {
494            // desc is the field's descriptor (see Type).
495            considerDesc(desc);
496
497            // signature is the field's signature. May be null if the field's type does not use
498            // generic types.
499            considerSignature(signature);
500
501            return this; // a visitor to visit field annotations and attributes
502        }
503
504        public void visitInnerClass(String name, String outerName, String innerName, int access) {
505            // name is the internal name of an inner class (see getInternalName).
506            considerName(name);
507        }
508
509        public MethodVisitor visitMethod(int access, String name, String desc,
510                String signature, String[] exceptions) {
511            // desc is the method's descriptor (see Type).
512            considerDesc(desc);
513            // signature is the method's signature. May be null if the method parameters, return
514            // type and exceptions do not use generic types.
515            considerSignature(signature);
516
517            return this; // returns this to visit the method
518        }
519
520        public void visitOuterClass(String owner, String name, String desc) {
521            // pass
522        }
523
524        public void visitSource(String source, String debug) {
525            // pass
526        }
527
528
529        // ---------------------------------------------------
530        // --- MethodVisitor
531        // ---------------------------------------------------
532
533        public AnnotationVisitor visitAnnotationDefault() {
534            return this; // returns this to visit the default value
535        }
536
537
538        public void visitCode() {
539            // pass
540        }
541
542        // field instruction
543        public void visitFieldInsn(int opcode, String owner, String name, String desc) {
544            // name is the field's name.
545            considerName(name);
546            // desc is the field's descriptor (see Type).
547            considerDesc(desc);
548        }
549
550        public void visitFrame(int type, int local, Object[] local2, int stack, Object[] stack2) {
551            // pass
552        }
553
554        public void visitIincInsn(int var, int increment) {
555            // pass -- an IINC instruction
556        }
557
558        public void visitInsn(int opcode) {
559            // pass -- a zero operand instruction
560        }
561
562        public void visitIntInsn(int opcode, int operand) {
563            // pass -- a single int operand instruction
564        }
565
566        public void visitJumpInsn(int opcode, Label label) {
567            // pass -- a jump instruction
568        }
569
570        public void visitLabel(Label label) {
571            // pass -- a label target
572        }
573
574        // instruction to load a constant from the stack
575        public void visitLdcInsn(Object cst) {
576            if (cst instanceof Type) {
577                considerType((Type) cst);
578            }
579        }
580
581        public void visitLineNumber(int line, Label start) {
582            // pass
583        }
584
585        public void visitLocalVariable(String name, String desc,
586                String signature, Label start, Label end, int index) {
587            // desc is the type descriptor of this local variable.
588            considerDesc(desc);
589            // signature is the type signature of this local variable. May be null if the local
590            // variable type does not use generic types.
591            considerSignature(signature);
592        }
593
594        public void visitLookupSwitchInsn(Label dflt, int[] keys, Label[] labels) {
595            // pass -- a lookup switch instruction
596        }
597
598        public void visitMaxs(int maxStack, int maxLocals) {
599            // pass
600        }
601
602        // instruction that invokes a method
603        public void visitMethodInsn(int opcode, String owner, String name, String desc) {
604
605            // owner is the internal name of the method's owner class
606            considerName(owner);
607            // desc is the method's descriptor (see Type).
608            considerDesc(desc);
609        }
610
611        // instruction multianewarray, whatever that is
612        public void visitMultiANewArrayInsn(String desc, int dims) {
613
614            // desc an array type descriptor.
615            considerDesc(desc);
616        }
617
618        public AnnotationVisitor visitParameterAnnotation(int parameter, String desc,
619                boolean visible) {
620            // desc is the class descriptor of the annotation class.
621            considerDesc(desc);
622            return this; // return this to visit annotation values
623        }
624
625        public void visitTableSwitchInsn(int min, int max, Label dflt, Label[] labels) {
626            // pass -- table switch instruction
627
628        }
629
630        public void visitTryCatchBlock(Label start, Label end, Label handler, String type) {
631            // type is the internal name of the type of exceptions handled by the handler,
632            // or null to catch any exceptions (for "finally" blocks).
633            considerName(type);
634        }
635
636        // type instruction
637        public void visitTypeInsn(int opcode, String type) {
638            // type is the operand of the instruction to be visited. This operand must be the
639            // internal name of an object or array class.
640            considerName(type);
641        }
642
643        public void visitVarInsn(int opcode, int var) {
644            // pass -- local variable instruction
645        }
646
647
648        // ---------------------------------------------------
649        // --- SignatureVisitor
650        // ---------------------------------------------------
651
652        private String mCurrentSignatureClass = null;
653
654        // Starts the visit of a signature corresponding to a class or interface type
655        public void visitClassType(String name) {
656            mCurrentSignatureClass = name;
657            considerName(name);
658        }
659
660        // Visits an inner class
661        public void visitInnerClassType(String name) {
662            if (mCurrentSignatureClass != null) {
663                mCurrentSignatureClass += "$" + name;
664                considerName(mCurrentSignatureClass);
665            }
666        }
667
668        public SignatureVisitor visitArrayType() {
669            return this; // returns this to visit the signature of the array element type
670        }
671
672        public void visitBaseType(char descriptor) {
673            // pass -- a primitive type, ignored
674        }
675
676        public SignatureVisitor visitClassBound() {
677            return this; // returns this to visit the signature of the class bound
678        }
679
680        public SignatureVisitor visitExceptionType() {
681            return this; // return this to visit the signature of the exception type.
682        }
683
684        public void visitFormalTypeParameter(String name) {
685            // pass
686        }
687
688        public SignatureVisitor visitInterface() {
689            return this; // returns this to visit the signature of the interface type
690        }
691
692        public SignatureVisitor visitInterfaceBound() {
693            return this; // returns this to visit the signature of the interface bound
694        }
695
696        public SignatureVisitor visitParameterType() {
697            return this; // returns this to visit the signature of the parameter type
698        }
699
700        public SignatureVisitor visitReturnType() {
701            return this; // returns this to visit the signature of the return type
702        }
703
704        public SignatureVisitor visitSuperclass() {
705            return this; // returns this to visit the signature of the super class type
706        }
707
708        public SignatureVisitor visitTypeArgument(char wildcard) {
709            return this; // returns this to visit the signature of the type argument
710        }
711
712        public void visitTypeVariable(String name) {
713            // pass
714        }
715
716        public void visitTypeArgument() {
717            // pass
718        }
719
720
721        // ---------------------------------------------------
722        // --- AnnotationVisitor
723        // ---------------------------------------------------
724
725
726        // Visits a primitive value of an annotation
727        public void visit(String name, Object value) {
728            // value is the actual value, whose type must be Byte, Boolean, Character, Short,
729            // Integer, Long, Float, Double, String or Type
730            if (value instanceof Type) {
731                considerType((Type) value);
732            }
733        }
734
735        public AnnotationVisitor visitAnnotation(String name, String desc) {
736            // desc is the class descriptor of the nested annotation class.
737            considerDesc(desc);
738            return this; // returns this to visit the actual nested annotation value
739        }
740
741        public AnnotationVisitor visitArray(String name) {
742            return this; // returns this to visit the actual array value elements
743        }
744
745        public void visitEnum(String name, String desc, String value) {
746            // desc is the class descriptor of the enumeration class.
747            considerDesc(desc);
748        }
749
750    }
751}
752