1/*
2 * Copyright (C) 2011 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 dalvik.system;
18
19import java.io.File;
20import java.net.URL;
21import java.nio.ByteBuffer;
22import java.util.ArrayList;
23import java.util.Collection;
24import java.util.Enumeration;
25import java.util.List;
26
27/**
28 * Base class for common functionality between various dex-based
29 * {@link ClassLoader} implementations.
30 */
31public class BaseDexClassLoader extends ClassLoader {
32
33    /**
34     * Hook for customizing how dex files loads are reported.
35     *
36     * This enables the framework to monitor the use of dex files. The
37     * goal is to simplify the mechanism for optimizing foreign dex files and
38     * enable further optimizations of secondary dex files.
39     *
40     * The reporting happens only when new instances of BaseDexClassLoader
41     * are constructed and will be active only after this field is set with
42     * {@link BaseDexClassLoader#setReporter}.
43     */
44    /* @NonNull */ private static volatile Reporter reporter = null;
45
46    private final DexPathList pathList;
47
48    /**
49     * Constructs an instance.
50     * Note that all the *.jar and *.apk files from {@code dexPath} might be
51     * first extracted in-memory before the code is loaded. This can be avoided
52     * by passing raw dex files (*.dex) in the {@code dexPath}.
53     *
54     * @param dexPath the list of jar/apk files containing classes and
55     * resources, delimited by {@code File.pathSeparator}, which
56     * defaults to {@code ":"} on Android.
57     * @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
58     * @param librarySearchPath the list of directories containing native
59     * libraries, delimited by {@code File.pathSeparator}; may be
60     * {@code null}
61     * @param parent the parent class loader
62     */
63    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
64            String librarySearchPath, ClassLoader parent) {
65        this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
66    }
67
68    /**
69     * @hide
70     */
71    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
72            String librarySearchPath, ClassLoader parent, boolean isTrusted) {
73        super(parent);
74        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
75
76        if (reporter != null) {
77            reportClassLoaderChain();
78        }
79    }
80
81    /**
82     * Reports the current class loader chain to the registered {@code reporter}.
83     * The chain is reported only if all its elements are {@code BaseDexClassLoader}.
84     */
85    private void reportClassLoaderChain() {
86        ArrayList<BaseDexClassLoader> classLoadersChain = new ArrayList<>();
87        ArrayList<String> classPaths = new ArrayList<>();
88
89        classLoadersChain.add(this);
90        classPaths.add(String.join(File.pathSeparator, pathList.getDexPaths()));
91
92        boolean onlySawSupportedClassLoaders = true;
93        ClassLoader bootClassLoader = ClassLoader.getSystemClassLoader().getParent();
94        ClassLoader current = getParent();
95
96        while (current != null && current != bootClassLoader) {
97            if (current instanceof BaseDexClassLoader) {
98                BaseDexClassLoader bdcCurrent = (BaseDexClassLoader) current;
99                classLoadersChain.add(bdcCurrent);
100                classPaths.add(String.join(File.pathSeparator, bdcCurrent.pathList.getDexPaths()));
101            } else {
102                onlySawSupportedClassLoaders = false;
103                break;
104            }
105            current = current.getParent();
106        }
107
108        if (onlySawSupportedClassLoaders) {
109            reporter.report(classLoadersChain, classPaths);
110        }
111    }
112
113    /**
114     * Constructs an instance.
115     *
116     * dexFile must be an in-memory representation of a full dexFile.
117     *
118     * @param dexFiles the array of in-memory dex files containing classes.
119     * @param parent the parent class loader
120     *
121     * @hide
122     */
123    public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
124        // TODO We should support giving this a library search path maybe.
125        super(parent);
126        this.pathList = new DexPathList(this, dexFiles);
127    }
128
129    @Override
130    protected Class<?> findClass(String name) throws ClassNotFoundException {
131        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
132        Class c = pathList.findClass(name, suppressedExceptions);
133        if (c == null) {
134            ClassNotFoundException cnfe = new ClassNotFoundException(
135                    "Didn't find class \"" + name + "\" on path: " + pathList);
136            for (Throwable t : suppressedExceptions) {
137                cnfe.addSuppressed(t);
138            }
139            throw cnfe;
140        }
141        return c;
142    }
143
144    /**
145     * @hide
146     */
147    public void addDexPath(String dexPath) {
148        addDexPath(dexPath, false /*isTrusted*/);
149    }
150
151    /**
152     * @hide
153     */
154    public void addDexPath(String dexPath, boolean isTrusted) {
155        pathList.addDexPath(dexPath, null /*optimizedDirectory*/, isTrusted);
156    }
157
158    /**
159     * Adds additional native paths for consideration in subsequent calls to
160     * {@link #findLibrary(String)}
161     * @hide
162     */
163    public void addNativePath(Collection<String> libPaths) {
164        pathList.addNativePath(libPaths);
165    }
166
167    @Override
168    protected URL findResource(String name) {
169        return pathList.findResource(name);
170    }
171
172    @Override
173    protected Enumeration<URL> findResources(String name) {
174        return pathList.findResources(name);
175    }
176
177    @Override
178    public String findLibrary(String name) {
179        return pathList.findLibrary(name);
180    }
181
182    /**
183     * Returns package information for the given package.
184     * Unfortunately, instances of this class don't really have this
185     * information, and as a non-secure {@code ClassLoader}, it isn't
186     * even required to, according to the spec. Yet, we want to
187     * provide it, in order to make all those hopeful callers of
188     * {@code myClass.getPackage().getName()} happy. Thus we construct
189     * a {@code Package} object the first time it is being requested
190     * and fill most of the fields with dummy values. The {@code
191     * Package} object is then put into the {@code ClassLoader}'s
192     * package cache, so we see the same one next time. We don't
193     * create {@code Package} objects for {@code null} arguments or
194     * for the default package.
195     *
196     * <p>There is a limited chance that we end up with multiple
197     * {@code Package} objects representing the same package: It can
198     * happen when when a package is scattered across different JAR
199     * files which were loaded by different {@code ClassLoader}
200     * instances. This is rather unlikely, and given that this whole
201     * thing is more or less a workaround, probably not worth the
202     * effort to address.
203     *
204     * @param name the name of the class
205     * @return the package information for the class, or {@code null}
206     * if there is no package information available for it
207     */
208    @Override
209    protected synchronized Package getPackage(String name) {
210        if (name != null && !name.isEmpty()) {
211            Package pack = super.getPackage(name);
212
213            if (pack == null) {
214                pack = definePackage(name, "Unknown", "0.0", "Unknown",
215                        "Unknown", "0.0", "Unknown", null);
216            }
217
218            return pack;
219        }
220
221        return null;
222    }
223
224    /**
225     * @hide
226     */
227    public String getLdLibraryPath() {
228        StringBuilder result = new StringBuilder();
229        for (File directory : pathList.getNativeLibraryDirectories()) {
230            if (result.length() > 0) {
231                result.append(':');
232            }
233            result.append(directory);
234        }
235
236        return result.toString();
237    }
238
239    @Override public String toString() {
240        return getClass().getName() + "[" + pathList + "]";
241    }
242
243    /**
244     * Sets the reporter for dex load notifications.
245     * Once set, all new instances of BaseDexClassLoader will report upon
246     * constructions the loaded dex files.
247     *
248     * @param newReporter the new Reporter. Setting null will cancel reporting.
249     * @hide
250     */
251    public static void setReporter(Reporter newReporter) {
252        reporter = newReporter;
253    }
254
255    /**
256     * @hide
257     */
258    public static Reporter getReporter() {
259        return reporter;
260    }
261
262    /**
263     * @hide
264     */
265    public interface Reporter {
266        /**
267         * Reports the construction of a BaseDexClassLoader and provides information about the
268         * class loader chain.
269         * Note that this only reports if all class loader in the chain are BaseDexClassLoader.
270         *
271         * @param classLoadersChain the chain of class loaders used during the construction of the
272         *     class loader. The first element is the BaseDexClassLoader being constructed,
273         *     the second element is its parent, and so on.
274         * @param classPaths the class paths of the class loaders present in
275         *     {@param classLoadersChain}. The first element corresponds to the first class
276         *     loader and so on. A classpath is represented as a list of dex files separated by
277         *     {@code File.pathSeparator}.
278         */
279        void report(List<BaseDexClassLoader> classLoadersChain, List<String> classPaths);
280    }
281}
282