Dependencies.java revision 3170:dc017a37aac5
1/*
2 * Copyright (c) 2009, 2013, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.  Oracle designates this
8 * particular file as subject to the "Classpath" exception as provided
9 * by Oracle in the LICENSE file that accompanied this code.
10 *
11 * This code is distributed in the hope that it will be useful, but WITHOUT
12 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
13 * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
14 * version 2 for more details (a copy is included in the LICENSE file that
15 * accompanied this code).
16 *
17 * You should have received a copy of the GNU General Public License version
18 * 2 along with this work; if not, write to the Free Software Foundation,
19 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
20 *
21 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
22 * or visit www.oracle.com if you need additional information or have any
23 * questions.
24 */
25
26package com.sun.tools.classfile;
27
28import java.util.Deque;
29import java.util.HashMap;
30import java.util.HashSet;
31import java.util.LinkedList;
32import java.util.List;
33import java.util.Map;
34import java.util.Objects;
35import java.util.Set;
36import java.util.regex.Pattern;
37
38import com.sun.tools.classfile.Dependency.Filter;
39import com.sun.tools.classfile.Dependency.Finder;
40import com.sun.tools.classfile.Dependency.Location;
41import com.sun.tools.classfile.Type.ArrayType;
42import com.sun.tools.classfile.Type.ClassSigType;
43import com.sun.tools.classfile.Type.ClassType;
44import com.sun.tools.classfile.Type.MethodType;
45import com.sun.tools.classfile.Type.SimpleType;
46import com.sun.tools.classfile.Type.TypeParamType;
47import com.sun.tools.classfile.Type.WildcardType;
48
49import static com.sun.tools.classfile.ConstantPool.*;
50
51/**
52 * A framework for determining {@link Dependency dependencies} between class files.
53 *
54 * A {@link Dependency.Finder finder} is used to identify the dependencies of
55 * individual classes. Some finders may return subtypes of {@code Dependency} to
56 * further characterize the type of dependency, such as a dependency on a
57 * method within a class.
58 *
59 * A {@link Dependency.Filter filter} may be used to restrict the set of
60 * dependencies found by a finder.
61 *
62 * Dependencies that are found may be passed to a {@link Dependencies.Recorder
63 * recorder} so that the dependencies can be stored in a custom data structure.
64 */
65public class Dependencies {
66    /**
67     * Thrown when a class file cannot be found.
68     */
69    public static class ClassFileNotFoundException extends Exception {
70        private static final long serialVersionUID = 3632265927794475048L;
71
72        public ClassFileNotFoundException(String className) {
73            super(className);
74            this.className = className;
75        }
76
77        public ClassFileNotFoundException(String className, Throwable cause) {
78            this(className);
79            initCause(cause);
80        }
81
82        public final String className;
83    }
84
85    /**
86     * Thrown when an exception is found processing a class file.
87     */
88    public static class ClassFileError extends Error {
89        private static final long serialVersionUID = 4111110813961313203L;
90
91        public ClassFileError(Throwable cause) {
92            initCause(cause);
93        }
94    }
95
96    /**
97     * Service provider interface to locate and read class files.
98     */
99    public interface ClassFileReader {
100        /**
101         * Get the ClassFile object for a specified class.
102         * @param className the name of the class to be returned.
103         * @return the ClassFile for the given class
104         * @throws Dependencies.ClassFileNotFoundException if the classfile cannot be
105         *   found
106         */
107        public ClassFile getClassFile(String className)
108                throws ClassFileNotFoundException;
109    }
110
111    /**
112     * Service provide interface to handle results.
113     */
114    public interface Recorder {
115        /**
116         * Record a dependency that has been found.
117         * @param d
118         */
119        public void addDependency(Dependency d);
120    }
121
122    /**
123     * Get the  default finder used to locate the dependencies for a class.
124     * @return the default finder
125     */
126    public static Finder getDefaultFinder() {
127        return new APIDependencyFinder(AccessFlags.ACC_PRIVATE);
128    }
129
130    /**
131     * Get a finder used to locate the API dependencies for a class.
132     * These include the superclass, superinterfaces, and classes referenced in
133     * the declarations of fields and methods.  The fields and methods that
134     * are checked can be limited according to a specified access.
135     * The access parameter must be one of {@link AccessFlags#ACC_PUBLIC ACC_PUBLIC},
136     * {@link AccessFlags#ACC_PRIVATE ACC_PRIVATE},
137     * {@link AccessFlags#ACC_PROTECTED ACC_PROTECTED}, or 0 for
138     * package private access. Members with greater than or equal accessibility
139     * to that specified will be searched for dependencies.
140     * @param access the access of members to be checked
141     * @return an API finder
142     */
143    public static Finder getAPIFinder(int access) {
144        return new APIDependencyFinder(access);
145    }
146
147    /**
148     * Get a finder to do class dependency analysis.
149     *
150     * @return a Class dependency finder
151     */
152    public static Finder getClassDependencyFinder() {
153        return new ClassDependencyFinder();
154    }
155
156    /**
157     * Get the finder used to locate the dependencies for a class.
158     * @return the finder
159     */
160    public Finder getFinder() {
161        if (finder == null)
162            finder = getDefaultFinder();
163        return finder;
164    }
165
166    /**
167     * Set the finder used to locate the dependencies for a class.
168     * @param f the finder
169     */
170    public void setFinder(Finder f) {
171        finder = Objects.requireNonNull(f);
172    }
173
174    /**
175     * Get the default filter used to determine included when searching
176     * the transitive closure of all the dependencies.
177     * Unless overridden, the default filter accepts all dependencies.
178     * @return the default filter.
179     */
180    public static Filter getDefaultFilter() {
181        return DefaultFilter.instance();
182    }
183
184    /**
185     * Get a filter which uses a regular expression on the target's class name
186     * to determine if a dependency is of interest.
187     * @param pattern the pattern used to match the target's class name
188     * @return a filter for matching the target class name with a regular expression
189     */
190    public static Filter getRegexFilter(Pattern pattern) {
191        return new TargetRegexFilter(pattern);
192    }
193
194    /**
195     * Get a filter which checks the package of a target's class name
196     * to determine if a dependency is of interest. The filter checks if the
197     * package of the target's class matches any of a set of given package
198     * names. The match may optionally match subpackages of the given names as well.
199     * @param packageNames the package names used to match the target's class name
200     * @param matchSubpackages whether or not to match subpackages as well
201     * @return a filter for checking the target package name against a list of package names
202     */
203    public static Filter getPackageFilter(Set<String> packageNames, boolean matchSubpackages) {
204        return new TargetPackageFilter(packageNames, matchSubpackages);
205    }
206
207    /**
208     * Get the filter used to determine the dependencies included when searching
209     * the transitive closure of all the dependencies.
210     * Unless overridden, the default filter accepts all dependencies.
211     * @return the filter
212     */
213    public Filter getFilter() {
214        if (filter == null)
215            filter = getDefaultFilter();
216        return filter;
217    }
218
219    /**
220     * Set the filter used to determine the dependencies included when searching
221     * the transitive closure of all the dependencies.
222     * @param f the filter
223     */
224    public void setFilter(Filter f) {
225        filter = Objects.requireNonNull(f);
226    }
227
228    /**
229     * Find the dependencies of a class, using the current
230     * {@link Dependencies#getFinder finder} and
231     * {@link Dependencies#getFilter filter}.
232     * The search may optionally include the transitive closure of all the
233     * filtered dependencies, by also searching in the classes named in those
234     * dependencies.
235     * @param classFinder a finder to locate class files
236     * @param rootClassNames the names of the root classes from which to begin
237     *      searching
238     * @param transitiveClosure whether or not to also search those classes
239     *      named in any filtered dependencies that are found.
240     * @return the set of dependencies that were found
241     * @throws ClassFileNotFoundException if a required class file cannot be found
242     * @throws ClassFileError if an error occurs while processing a class file,
243     *      such as an error in the internal class file structure.
244     */
245    public Set<Dependency> findAllDependencies(
246            ClassFileReader classFinder, Set<String> rootClassNames,
247            boolean transitiveClosure)
248            throws ClassFileNotFoundException {
249        final Set<Dependency> results = new HashSet<>();
250        Recorder r = new Recorder() {
251            public void addDependency(Dependency d) {
252                results.add(d);
253            }
254        };
255        findAllDependencies(classFinder, rootClassNames, transitiveClosure, r);
256        return results;
257    }
258
259    /**
260     * Find the dependencies of a class, using the current
261     * {@link Dependencies#getFinder finder} and
262     * {@link Dependencies#getFilter filter}.
263     * The search may optionally include the transitive closure of all the
264     * filtered dependencies, by also searching in the classes named in those
265     * dependencies.
266     * @param classFinder a finder to locate class files
267     * @param rootClassNames the names of the root classes from which to begin
268     *      searching
269     * @param transitiveClosure whether or not to also search those classes
270     *      named in any filtered dependencies that are found.
271     * @param recorder a recorder for handling the results
272     * @throws ClassFileNotFoundException if a required class file cannot be found
273     * @throws ClassFileError if an error occurs while processing a class file,
274     *      such as an error in the internal class file structure.
275     */
276    public void findAllDependencies(
277            ClassFileReader classFinder, Set<String> rootClassNames,
278            boolean transitiveClosure, Recorder recorder)
279            throws ClassFileNotFoundException {
280        Set<String> doneClasses = new HashSet<>();
281
282        getFinder();  // ensure initialized
283        getFilter();  // ensure initialized
284
285        // Work queue of names of classfiles to be searched.
286        // Entries will be unique, and for classes that do not yet have
287        // dependencies in the results map.
288        Deque<String> deque = new LinkedList<>(rootClassNames);
289
290        String className;
291        while ((className = deque.poll()) != null) {
292            assert (!doneClasses.contains(className));
293            doneClasses.add(className);
294
295            ClassFile cf = classFinder.getClassFile(className);
296
297            // The following code just applies the filter to the dependencies
298            // followed for the transitive closure.
299            for (Dependency d: finder.findDependencies(cf)) {
300                recorder.addDependency(d);
301                if (transitiveClosure && filter.accepts(d)) {
302                    String cn = d.getTarget().getClassName();
303                    if (!doneClasses.contains(cn))
304                        deque.add(cn);
305                }
306            }
307        }
308    }
309
310    private Filter filter;
311    private Finder finder;
312
313    /**
314     * A location identifying a class.
315     */
316    static class SimpleLocation implements Location {
317        public SimpleLocation(String name) {
318            this.name = name;
319            this.className = name.replace('/', '.');
320        }
321
322        public String getName() {
323            return name;
324        }
325
326        public String getClassName() {
327            return className;
328        }
329
330        public String getPackageName() {
331            int i = name.lastIndexOf('/');
332            return (i > 0) ? name.substring(0, i).replace('/', '.') : "";
333        }
334
335        @Override
336        public boolean equals(Object other) {
337            if (this == other)
338                return true;
339            if (!(other instanceof SimpleLocation))
340                return false;
341            return (name.equals(((SimpleLocation) other).name));
342        }
343
344        @Override
345        public int hashCode() {
346            return name.hashCode();
347        }
348
349        @Override
350        public String toString() {
351            return name;
352        }
353
354        private String name;
355        private String className;
356    }
357
358    /**
359     * A dependency of one class on another.
360     */
361    static class SimpleDependency implements Dependency {
362        public SimpleDependency(Location origin, Location target) {
363            this.origin = origin;
364            this.target = target;
365        }
366
367        public Location getOrigin() {
368            return origin;
369        }
370
371        public Location getTarget() {
372            return target;
373        }
374
375        @Override
376        public boolean equals(Object other) {
377            if (this == other)
378                return true;
379            if (!(other instanceof SimpleDependency))
380                return false;
381            SimpleDependency o = (SimpleDependency) other;
382            return (origin.equals(o.origin) && target.equals(o.target));
383        }
384
385        @Override
386        public int hashCode() {
387            return origin.hashCode() * 31 + target.hashCode();
388        }
389
390        @Override
391        public String toString() {
392            return origin + ":" + target;
393        }
394
395        private Location origin;
396        private Location target;
397    }
398
399
400    /**
401     * This class accepts all dependencies.
402     */
403    static class DefaultFilter implements Filter {
404        private static DefaultFilter instance;
405
406        static DefaultFilter instance() {
407            if (instance == null)
408                instance = new DefaultFilter();
409            return instance;
410        }
411
412        public boolean accepts(Dependency dependency) {
413            return true;
414        }
415    }
416
417    /**
418     * This class accepts those dependencies whose target's class name matches a
419     * regular expression.
420     */
421    static class TargetRegexFilter implements Filter {
422        TargetRegexFilter(Pattern pattern) {
423            this.pattern = pattern;
424        }
425
426        public boolean accepts(Dependency dependency) {
427            return pattern.matcher(dependency.getTarget().getClassName()).matches();
428        }
429
430        private final Pattern pattern;
431    }
432
433    /**
434     * This class accepts those dependencies whose class name is in a given
435     * package.
436     */
437    static class TargetPackageFilter implements Filter {
438        TargetPackageFilter(Set<String> packageNames, boolean matchSubpackages) {
439            for (String pn: packageNames) {
440                if (pn.length() == 0) // implies null check as well
441                    throw new IllegalArgumentException();
442            }
443            this.packageNames = packageNames;
444            this.matchSubpackages = matchSubpackages;
445        }
446
447        public boolean accepts(Dependency dependency) {
448            String pn = dependency.getTarget().getPackageName();
449            if (packageNames.contains(pn))
450                return true;
451
452            if (matchSubpackages) {
453                for (String n: packageNames) {
454                    if (pn.startsWith(n + "."))
455                        return true;
456                }
457            }
458
459            return false;
460        }
461
462        private final Set<String> packageNames;
463        private final boolean matchSubpackages;
464    }
465
466    /**
467     * This class identifies class names directly or indirectly in the constant pool.
468     */
469    static class ClassDependencyFinder extends BasicDependencyFinder {
470        public Iterable<? extends Dependency> findDependencies(ClassFile classfile) {
471            Visitor v = new Visitor(classfile);
472            for (CPInfo cpInfo: classfile.constant_pool.entries()) {
473                v.scan(cpInfo);
474            }
475            try {
476                v.addClass(classfile.super_class);
477                v.addClasses(classfile.interfaces);
478                v.scan(classfile.attributes);
479
480                for (Field f : classfile.fields) {
481                    v.scan(f.descriptor, f.attributes);
482                }
483                for (Method m : classfile.methods) {
484                    v.scan(m.descriptor, m.attributes);
485                    Exceptions_attribute e =
486                        (Exceptions_attribute)m.attributes.get(Attribute.Exceptions);
487                    if (e != null) {
488                        v.addClasses(e.exception_index_table);
489                    }
490                }
491            } catch (ConstantPoolException e) {
492                throw new ClassFileError(e);
493            }
494
495            return v.deps;
496        }
497    }
498
499    /**
500     * This class identifies class names in the signatures of classes, fields,
501     * and methods in a class.
502     */
503    static class APIDependencyFinder extends BasicDependencyFinder {
504        APIDependencyFinder(int access) {
505            switch (access) {
506                case AccessFlags.ACC_PUBLIC:
507                case AccessFlags.ACC_PROTECTED:
508                case AccessFlags.ACC_PRIVATE:
509                case 0:
510                    showAccess = access;
511                    break;
512                default:
513                    throw new IllegalArgumentException("invalid access 0x"
514                            + Integer.toHexString(access));
515            }
516        }
517
518        public Iterable<? extends Dependency> findDependencies(ClassFile classfile) {
519            try {
520                Visitor v = new Visitor(classfile);
521                v.addClass(classfile.super_class);
522                v.addClasses(classfile.interfaces);
523                // inner classes?
524                for (Field f : classfile.fields) {
525                    if (checkAccess(f.access_flags))
526                        v.scan(f.descriptor, f.attributes);
527                }
528                for (Method m : classfile.methods) {
529                    if (checkAccess(m.access_flags)) {
530                        v.scan(m.descriptor, m.attributes);
531                        Exceptions_attribute e =
532                                (Exceptions_attribute) m.attributes.get(Attribute.Exceptions);
533                        if (e != null)
534                            v.addClasses(e.exception_index_table);
535                    }
536                }
537                return v.deps;
538            } catch (ConstantPoolException e) {
539                throw new ClassFileError(e);
540            }
541        }
542
543        boolean checkAccess(AccessFlags flags) {
544            // code copied from javap.Options.checkAccess
545            boolean isPublic = flags.is(AccessFlags.ACC_PUBLIC);
546            boolean isProtected = flags.is(AccessFlags.ACC_PROTECTED);
547            boolean isPrivate = flags.is(AccessFlags.ACC_PRIVATE);
548            boolean isPackage = !(isPublic || isProtected || isPrivate);
549
550            if ((showAccess == AccessFlags.ACC_PUBLIC) && (isProtected || isPrivate || isPackage))
551                return false;
552            else if ((showAccess == AccessFlags.ACC_PROTECTED) && (isPrivate || isPackage))
553                return false;
554            else if ((showAccess == 0) && (isPrivate))
555                return false;
556            else
557                return true;
558        }
559
560        private int showAccess;
561    }
562
563    static abstract class BasicDependencyFinder implements Finder {
564        private Map<String,Location> locations = new HashMap<>();
565
566        Location getLocation(String className) {
567            Location l = locations.get(className);
568            if (l == null)
569                locations.put(className, l = new SimpleLocation(className));
570            return l;
571        }
572
573        class Visitor implements ConstantPool.Visitor<Void,Void>, Type.Visitor<Void, Void> {
574            private ConstantPool constant_pool;
575            private Location origin;
576            Set<Dependency> deps;
577
578            Visitor(ClassFile classFile) {
579                try {
580                    constant_pool = classFile.constant_pool;
581                    origin = getLocation(classFile.getName());
582                    deps = new HashSet<>();
583                } catch (ConstantPoolException e) {
584                    throw new ClassFileError(e);
585                }
586            }
587
588            void scan(Descriptor d, Attributes attrs) {
589                try {
590                    scan(new Signature(d.index).getType(constant_pool));
591                    scan(attrs);
592                } catch (ConstantPoolException e) {
593                    throw new ClassFileError(e);
594                }
595            }
596
597            void scan(CPInfo cpInfo) {
598                cpInfo.accept(this, null);
599            }
600
601            void scan(Type t) {
602                t.accept(this, null);
603            }
604
605            void scan(Attributes attrs) {
606                try {
607                    Signature_attribute sa = (Signature_attribute)attrs.get(Attribute.Signature);
608                    if (sa != null)
609                        scan(sa.getParsedSignature().getType(constant_pool));
610
611                    scan((RuntimeVisibleAnnotations_attribute)
612                            attrs.get(Attribute.RuntimeVisibleAnnotations));
613                    scan((RuntimeVisibleParameterAnnotations_attribute)
614                            attrs.get(Attribute.RuntimeVisibleParameterAnnotations));
615                } catch (ConstantPoolException e) {
616                    throw new ClassFileError(e);
617                }
618            }
619
620            private void scan(RuntimeAnnotations_attribute attr) throws ConstantPoolException {
621                if (attr == null) {
622                    return;
623                }
624                for (int i = 0; i < attr.annotations.length; i++) {
625                    int index = attr.annotations[i].type_index;
626                    scan(new Signature(index).getType(constant_pool));
627                }
628            }
629
630            private void scan(RuntimeParameterAnnotations_attribute attr) throws ConstantPoolException {
631                if (attr == null) {
632                    return;
633                }
634                for (int param = 0; param < attr.parameter_annotations.length; param++) {
635                    for (int i = 0; i < attr.parameter_annotations[param].length; i++) {
636                        int index = attr.parameter_annotations[param][i].type_index;
637                        scan(new Signature(index).getType(constant_pool));
638                    }
639                }
640            }
641
642            void addClass(int index) throws ConstantPoolException {
643                if (index != 0) {
644                    String name = constant_pool.getClassInfo(index).getBaseName();
645                    if (name != null)
646                        addDependency(name);
647                }
648            }
649
650            void addClasses(int[] indices) throws ConstantPoolException {
651                for (int i: indices)
652                    addClass(i);
653            }
654
655            private void addDependency(String name) {
656                deps.add(new SimpleDependency(origin, getLocation(name)));
657            }
658
659            // ConstantPool.Visitor methods
660
661            public Void visitClass(CONSTANT_Class_info info, Void p) {
662                try {
663                    if (info.getName().startsWith("["))
664                        new Signature(info.name_index).getType(constant_pool).accept(this, null);
665                    else
666                        addDependency(info.getBaseName());
667                    return null;
668                } catch (ConstantPoolException e) {
669                    throw new ClassFileError(e);
670                }
671            }
672
673            public Void visitDouble(CONSTANT_Double_info info, Void p) {
674                return null;
675            }
676
677            public Void visitFieldref(CONSTANT_Fieldref_info info, Void p) {
678                return visitRef(info, p);
679            }
680
681            public Void visitFloat(CONSTANT_Float_info info, Void p) {
682                return null;
683            }
684
685            public Void visitInteger(CONSTANT_Integer_info info, Void p) {
686                return null;
687            }
688
689            public Void visitInterfaceMethodref(CONSTANT_InterfaceMethodref_info info, Void p) {
690                return visitRef(info, p);
691            }
692
693            public Void visitInvokeDynamic(CONSTANT_InvokeDynamic_info info, Void p) {
694                return null;
695            }
696
697            public Void visitLong(CONSTANT_Long_info info, Void p) {
698                return null;
699            }
700
701            public Void visitMethodHandle(CONSTANT_MethodHandle_info info, Void p) {
702                return null;
703            }
704
705            public Void visitMethodType(CONSTANT_MethodType_info info, Void p) {
706                return null;
707            }
708
709            public Void visitMethodref(CONSTANT_Methodref_info info, Void p) {
710                return visitRef(info, p);
711            }
712
713            public Void visitNameAndType(CONSTANT_NameAndType_info info, Void p) {
714                try {
715                    new Signature(info.type_index).getType(constant_pool).accept(this, null);
716                    return null;
717                } catch (ConstantPoolException e) {
718                    throw new ClassFileError(e);
719                }
720            }
721
722            public Void visitString(CONSTANT_String_info info, Void p) {
723                return null;
724            }
725
726            public Void visitUtf8(CONSTANT_Utf8_info info, Void p) {
727                return null;
728            }
729
730            private Void visitRef(CPRefInfo info, Void p) {
731                try {
732                    visitClass(info.getClassInfo(), p);
733                    return null;
734                } catch (ConstantPoolException e) {
735                    throw new ClassFileError(e);
736                }
737            }
738
739            // Type.Visitor methods
740
741            private void findDependencies(Type t) {
742                if (t != null)
743                    t.accept(this, null);
744            }
745
746            private void findDependencies(List<? extends Type> ts) {
747                if (ts != null) {
748                    for (Type t: ts)
749                        t.accept(this, null);
750                }
751            }
752
753            public Void visitSimpleType(SimpleType type, Void p) {
754                return null;
755            }
756
757            public Void visitArrayType(ArrayType type, Void p) {
758                findDependencies(type.elemType);
759                return null;
760            }
761
762            public Void visitMethodType(MethodType type, Void p) {
763                findDependencies(type.paramTypes);
764                findDependencies(type.returnType);
765                findDependencies(type.throwsTypes);
766                findDependencies(type.typeParamTypes);
767                return null;
768            }
769
770            public Void visitClassSigType(ClassSigType type, Void p) {
771                findDependencies(type.superclassType);
772                findDependencies(type.superinterfaceTypes);
773                return null;
774            }
775
776            public Void visitClassType(ClassType type, Void p) {
777                findDependencies(type.outerType);
778                addDependency(type.getBinaryName());
779                findDependencies(type.typeArgs);
780                return null;
781            }
782
783            public Void visitTypeParamType(TypeParamType type, Void p) {
784                findDependencies(type.classBound);
785                findDependencies(type.interfaceBounds);
786                return null;
787            }
788
789            public Void visitWildcardType(WildcardType type, Void p) {
790                findDependencies(type.boundType);
791                return null;
792            }
793        }
794    }
795}
796