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 
17 package dalvik.system;
18 
19 import dalvik.annotation.compat.UnsupportedAppUsage;
20 import java.io.File;
21 import java.io.IOException;
22 import java.net.URL;
23 import java.nio.ByteBuffer;
24 import java.util.ArrayList;
25 import java.util.Arrays;
26 import java.util.Collection;
27 import java.util.Enumeration;
28 import java.util.List;
29 import sun.misc.CompoundEnumeration;
30 
31 /**
32  * Base class for common functionality between various dex-based
33  * {@link ClassLoader} implementations.
34  */
35 public class BaseDexClassLoader extends ClassLoader {
36 
37     /**
38      * Hook for customizing how dex files loads are reported.
39      *
40      * This enables the framework to monitor the use of dex files. The
41      * goal is to simplify the mechanism for optimizing foreign dex files and
42      * enable further optimizations of secondary dex files.
43      *
44      * The reporting happens only when new instances of BaseDexClassLoader
45      * are constructed and will be active only after this field is set with
46      * {@link BaseDexClassLoader#setReporter}.
47      */
48     /* @NonNull */ private static volatile Reporter reporter = null;
49 
50     @UnsupportedAppUsage
51     private final DexPathList pathList;
52 
53     /**
54      * Array of ClassLoaders that can be used to load classes and resources that the code in
55      * {@code pathList} may depend on. This is used to implement Android's
56      * <a href=https://developer.android.com/guide/topics/manifest/uses-library-element>
57      * shared libraries</a> feature.
58      * <p>The shared library loaders are always checked before the {@code pathList} when looking
59      * up classes and resources.
60      *
61      * <p>{@code null} if the class loader has no shared library.
62      *
63      * @hide
64      */
65     protected final ClassLoader[] sharedLibraryLoaders;
66 
67     /**
68      * Constructs an instance.
69      * Note that all the *.jar and *.apk files from {@code dexPath} might be
70      * first extracted in-memory before the code is loaded. This can be avoided
71      * by passing raw dex files (*.dex) in the {@code dexPath}.
72      *
73      * @param dexPath the list of jar/apk files containing classes and
74      * resources, delimited by {@code File.pathSeparator}, which
75      * defaults to {@code ":"} on Android.
76      * @param optimizedDirectory this parameter is deprecated and has no effect since API level 26.
77      * @param librarySearchPath the list of directories containing native
78      * libraries, delimited by {@code File.pathSeparator}; may be
79      * {@code null}
80      * @param parent the parent class loader
81      */
BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent)82     public BaseDexClassLoader(String dexPath, File optimizedDirectory,
83             String librarySearchPath, ClassLoader parent) {
84         this(dexPath, librarySearchPath, parent, null, false);
85     }
86 
87     /**
88      * @hide
89      */
90     @UnsupportedAppUsage
BaseDexClassLoader(String dexPath, File optimizedDirectory, String librarySearchPath, ClassLoader parent, boolean isTrusted)91     public BaseDexClassLoader(String dexPath, File optimizedDirectory,
92             String librarySearchPath, ClassLoader parent, boolean isTrusted) {
93         this(dexPath, librarySearchPath, parent, null, isTrusted);
94     }
95 
96     /**
97      * @hide
98      */
BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] libraries)99     public BaseDexClassLoader(String dexPath,
100             String librarySearchPath, ClassLoader parent, ClassLoader[] libraries) {
101         this(dexPath, librarySearchPath, parent, libraries, false);
102     }
103 
104     /**
105      * BaseDexClassLoader implements the Android
106      * <a href=https://developer.android.com/guide/topics/manifest/uses-library-element>
107      * shared libraries</a> feature by changing the typical parent delegation mechanism
108      * of class loaders.
109      * <p> Each shared library is associated with its own class loader, which is added to a list of
110      * class loaders this BaseDexClassLoader tries to load from in order, immediately checking
111      * after the parent.
112      * The shared library loaders are always checked before the {@code pathList} when looking
113      * up classes and resources.
114      *
115      * @hide
116      */
BaseDexClassLoader(String dexPath, String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders, boolean isTrusted)117     public BaseDexClassLoader(String dexPath,
118             String librarySearchPath, ClassLoader parent, ClassLoader[] sharedLibraryLoaders,
119             boolean isTrusted) {
120         super(parent);
121         // Setup shared libraries before creating the path list. ART relies on the class loader
122         // hierarchy being finalized before loading dex files.
123         this.sharedLibraryLoaders = sharedLibraryLoaders == null
124                 ? null
125                 : Arrays.copyOf(sharedLibraryLoaders, sharedLibraryLoaders.length);
126         this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
127 
128         if (reporter != null) {
129             reportClassLoaderChain();
130         }
131     }
132 
133     /**
134      * Reports the current class loader chain to the registered {@code reporter}.
135      */
reportClassLoaderChain()136     private void reportClassLoaderChain() {
137         ArrayList<ClassLoader> classLoadersChain = new ArrayList<>();
138         ArrayList<String> classPaths = new ArrayList<>();
139 
140         classLoadersChain.add(this);
141         classPaths.add(String.join(File.pathSeparator, pathList.getDexPaths()));
142 
143         ClassLoader bootClassLoader = ClassLoader.getSystemClassLoader().getParent();
144         ClassLoader current = getParent();
145 
146         while (current != null && current != bootClassLoader) {
147             classLoadersChain.add(current);
148             if (current instanceof BaseDexClassLoader) {
149                 BaseDexClassLoader bdcCurrent = (BaseDexClassLoader) current;
150                 classPaths.add(String.join(File.pathSeparator, bdcCurrent.pathList.getDexPaths()));
151             } else {
152                 // We can't determine the classpath for arbitrary class loaders.
153                 classPaths.add(null);
154             }
155             current = current.getParent();
156         }
157 
158         reporter.report(classLoadersChain, classPaths);
159     }
160 
161     /**
162      * Constructs an instance.
163      *
164      * dexFile must be an in-memory representation of a full dexFile.
165      *
166      * @param dexFiles the array of in-memory dex files containing classes.
167      * @param librarySearchPath the list of directories containing native
168      *   libraries, delimited by {@code File.pathSeparator}; may be {@code null}
169      * @param parent the parent class loader
170      *
171      * @hide
172      */
BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent)173     public BaseDexClassLoader(ByteBuffer[] dexFiles, String librarySearchPath, ClassLoader parent) {
174         super(parent);
175         this.sharedLibraryLoaders = null;
176         this.pathList = new DexPathList(this, librarySearchPath);
177         this.pathList.initByteBufferDexPath(dexFiles);
178     }
179 
180     @Override
findClass(String name)181     protected Class<?> findClass(String name) throws ClassNotFoundException {
182         // First, check whether the class is present in our shared libraries.
183         if (sharedLibraryLoaders != null) {
184             for (ClassLoader loader : sharedLibraryLoaders) {
185                 try {
186                     return loader.loadClass(name);
187                 } catch (ClassNotFoundException ignored) {
188                 }
189             }
190         }
191         // Check whether the class in question is present in the dexPath that
192         // this classloader operates on.
193         List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
194         Class c = pathList.findClass(name, suppressedExceptions);
195         if (c == null) {
196             ClassNotFoundException cnfe = new ClassNotFoundException(
197                     "Didn't find class \"" + name + "\" on path: " + pathList);
198             for (Throwable t : suppressedExceptions) {
199                 cnfe.addSuppressed(t);
200             }
201             throw cnfe;
202         }
203         return c;
204     }
205 
206     /**
207      * @hide
208      */
209     @UnsupportedAppUsage
210     @libcore.api.CorePlatformApi
addDexPath(String dexPath)211     public void addDexPath(String dexPath) {
212         addDexPath(dexPath, false /*isTrusted*/);
213     }
214 
215     /**
216      * @hide
217      */
218     @UnsupportedAppUsage
addDexPath(String dexPath, boolean isTrusted)219     public void addDexPath(String dexPath, boolean isTrusted) {
220         pathList.addDexPath(dexPath, null /*optimizedDirectory*/, isTrusted);
221     }
222 
223     /**
224      * Adds additional native paths for consideration in subsequent calls to
225      * {@link #findLibrary(String)}
226      * @hide
227      */
228     @libcore.api.CorePlatformApi
addNativePath(Collection<String> libPaths)229     public void addNativePath(Collection<String> libPaths) {
230         pathList.addNativePath(libPaths);
231     }
232 
233     @Override
findResource(String name)234     protected URL findResource(String name) {
235         if (sharedLibraryLoaders != null) {
236             for (ClassLoader loader : sharedLibraryLoaders) {
237                 URL url = loader.getResource(name);
238                 if (url != null) {
239                     return url;
240                 }
241             }
242         }
243         return pathList.findResource(name);
244     }
245 
246     @Override
findResources(String name)247     protected Enumeration<URL> findResources(String name) {
248         Enumeration<URL> myResources = pathList.findResources(name);
249         if (sharedLibraryLoaders == null) {
250           return myResources;
251         }
252 
253         Enumeration<URL>[] tmp =
254             (Enumeration<URL>[]) new Enumeration<?>[sharedLibraryLoaders.length + 1];
255         // This will add duplicate resources if a shared library is loaded twice, but that's ok
256         // as we don't guarantee uniqueness.
257         for (int i = 0; i < sharedLibraryLoaders.length; i++) {
258             try {
259                 tmp[i] = sharedLibraryLoaders[i].getResources(name);
260             } catch (IOException e) {
261                 // Ignore.
262             }
263         }
264         tmp[sharedLibraryLoaders.length] = myResources;
265         return new CompoundEnumeration<>(tmp);
266     }
267 
268     @Override
findLibrary(String name)269     public String findLibrary(String name) {
270         return pathList.findLibrary(name);
271     }
272 
273     /**
274      * Returns package information for the given package.
275      * Unfortunately, instances of this class don't really have this
276      * information, and as a non-secure {@code ClassLoader}, it isn't
277      * even required to, according to the spec. Yet, we want to
278      * provide it, in order to make all those hopeful callers of
279      * {@code myClass.getPackage().getName()} happy. Thus we construct
280      * a {@code Package} object the first time it is being requested
281      * and fill most of the fields with dummy values. The {@code
282      * Package} object is then put into the {@code ClassLoader}'s
283      * package cache, so we see the same one next time. We don't
284      * create {@code Package} objects for {@code null} arguments or
285      * for the default package.
286      *
287      * <p>There is a limited chance that we end up with multiple
288      * {@code Package} objects representing the same package: It can
289      * happen when when a package is scattered across different JAR
290      * files which were loaded by different {@code ClassLoader}
291      * instances. This is rather unlikely, and given that this whole
292      * thing is more or less a workaround, probably not worth the
293      * effort to address.
294      *
295      * @param name the name of the class
296      * @return the package information for the class, or {@code null}
297      * if there is no package information available for it
298      */
299     @Override
getPackage(String name)300     protected synchronized Package getPackage(String name) {
301         if (name != null && !name.isEmpty()) {
302             Package pack = super.getPackage(name);
303 
304             if (pack == null) {
305                 pack = definePackage(name, "Unknown", "0.0", "Unknown",
306                         "Unknown", "0.0", "Unknown", null);
307             }
308 
309             return pack;
310         }
311 
312         return null;
313     }
314 
315     /**
316      * @hide
317      */
318     @UnsupportedAppUsage
319     @libcore.api.CorePlatformApi
getLdLibraryPath()320     public String getLdLibraryPath() {
321         StringBuilder result = new StringBuilder();
322         for (File directory : pathList.getNativeLibraryDirectories()) {
323             if (result.length() > 0) {
324                 result.append(':');
325             }
326             result.append(directory);
327         }
328 
329         return result.toString();
330     }
331 
toString()332     @Override public String toString() {
333         return getClass().getName() + "[" + pathList + "]";
334     }
335 
336     /**
337      * Sets the reporter for dex load notifications.
338      * Once set, all new instances of BaseDexClassLoader will report upon
339      * constructions the loaded dex files.
340      *
341      * @param newReporter the new Reporter. Setting null will cancel reporting.
342      * @hide
343      */
344     @libcore.api.CorePlatformApi
setReporter(Reporter newReporter)345     public static void setReporter(Reporter newReporter) {
346         reporter = newReporter;
347     }
348 
349     /**
350      * @hide
351      */
getReporter()352     public static Reporter getReporter() {
353         return reporter;
354     }
355 
356     /**
357      * @hide
358      */
359     @libcore.api.CorePlatformApi
360     public interface Reporter {
361         /**
362          * Reports the construction of a BaseDexClassLoader and provides information about the
363          * class loader chain.
364          *
365          * @param classLoadersChain the chain of class loaders used during the construction of the
366          *     class loader. The first element is the BaseDexClassLoader being constructed,
367          *     the second element is its parent, and so on.
368          * @param classPaths the class paths of the class loaders present in
369          *     {@param classLoadersChain}. The first element corresponds to the first class
370          *     loader and so on. A classpath is represented as a list of dex files separated by
371          *     {@code File.pathSeparator}. If the class loader is not a BaseDexClassLoader the
372          *     classpath will be null.
373          */
374         @libcore.api.CorePlatformApi
report(List<ClassLoader> classLoadersChain, List<String> classPaths)375         void report(List<ClassLoader> classLoadersChain, List<String> classPaths);
376     }
377 }
378