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