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