1 /*
2  * Copyright (C) 2006 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 android.app;
18 
19 import android.compat.annotation.UnsupportedAppUsage;
20 import android.content.pm.SharedLibraryInfo;
21 import android.os.Build;
22 import android.os.GraphicsEnvironment;
23 import android.os.Trace;
24 import android.util.ArrayMap;
25 import android.util.Log;
26 
27 import com.android.internal.os.ClassLoaderFactory;
28 
29 import dalvik.system.PathClassLoader;
30 
31 import java.util.ArrayList;
32 import java.util.Collection;
33 import java.util.HashMap;
34 import java.util.List;
35 import java.util.Map;
36 
37 /** @hide */
38 public class ApplicationLoaders {
39     private static final String TAG = "ApplicationLoaders";
40 
41     @UnsupportedAppUsage
getDefault()42     public static ApplicationLoaders getDefault() {
43         return gApplicationLoaders;
44     }
45 
getClassLoader(String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String classLoaderName)46     ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
47                                String librarySearchPath, String libraryPermittedPath,
48                                ClassLoader parent, String classLoaderName) {
49         return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled,
50                               librarySearchPath, libraryPermittedPath, parent, classLoaderName,
51                               null, null);
52     }
53 
getClassLoaderWithSharedLibraries( String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String classLoaderName, List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries)54     ClassLoader getClassLoaderWithSharedLibraries(
55             String zip, int targetSdkVersion, boolean isBundled,
56             String librarySearchPath, String libraryPermittedPath,
57             ClassLoader parent, String classLoaderName,
58             List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries) {
59         // For normal usage the cache key used is the same as the zip path.
60         return getClassLoader(zip, targetSdkVersion, isBundled, librarySearchPath,
61                               libraryPermittedPath, parent, zip, classLoaderName, sharedLibraries,
62                               nativeSharedLibraries);
63     }
64 
65     /**
66      * Gets a class loader for a shared library. Additional dependent shared libraries are allowed
67      * to be specified (sharedLibraries).
68      *
69      * Additionally, as an optimization, this will return a pre-created ClassLoader if one has
70      * been cached by createAndCacheNonBootclasspathSystemClassLoaders.
71      */
getSharedLibraryClassLoaderWithSharedLibraries(String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String classLoaderName, List<ClassLoader> sharedLibraries)72     ClassLoader getSharedLibraryClassLoaderWithSharedLibraries(String zip, int targetSdkVersion,
73             boolean isBundled, String librarySearchPath, String libraryPermittedPath,
74             ClassLoader parent, String classLoaderName, List<ClassLoader> sharedLibraries) {
75         ClassLoader loader = getCachedNonBootclasspathSystemLib(zip, parent, classLoaderName,
76                 sharedLibraries);
77         if (loader != null) {
78             return loader;
79         }
80 
81         // TODO(b/142191088): allow (Java) shared libraries to have <uses-native-library>
82         // Until that is supported, assume that all native shared libraries are used.
83         // "ALL" is a magic string that libnativeloader uses to unconditionally add all available
84         // native shared libraries to the classloader.
85         List<String> nativeSharedLibraries = new ArrayList<>();
86         nativeSharedLibraries.add("ALL");
87         return getClassLoaderWithSharedLibraries(zip, targetSdkVersion, isBundled,
88               librarySearchPath, libraryPermittedPath, parent, classLoaderName, sharedLibraries,
89               nativeSharedLibraries);
90     }
91 
getClassLoader(String zip, int targetSdkVersion, boolean isBundled, String librarySearchPath, String libraryPermittedPath, ClassLoader parent, String cacheKey, String classLoaderName, List<ClassLoader> sharedLibraries, List<String> nativeSharedLibraries)92     private ClassLoader getClassLoader(String zip, int targetSdkVersion, boolean isBundled,
93                                        String librarySearchPath, String libraryPermittedPath,
94                                        ClassLoader parent, String cacheKey,
95                                        String classLoaderName, List<ClassLoader> sharedLibraries,
96                                        List<String> nativeSharedLibraries) {
97         /*
98          * This is the parent we use if they pass "null" in.  In theory
99          * this should be the "system" class loader; in practice we
100          * don't use that and can happily (and more efficiently) use the
101          * bootstrap class loader.
102          */
103         ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();
104 
105         synchronized (mLoaders) {
106             if (parent == null) {
107                 parent = baseParent;
108             }
109 
110             /*
111              * If we're one step up from the base class loader, find
112              * something in our cache.  Otherwise, we create a whole
113              * new ClassLoader for the zip archive.
114              */
115             if (parent == baseParent) {
116                 ClassLoader loader = mLoaders.get(cacheKey);
117                 if (loader != null) {
118                     return loader;
119                 }
120 
121                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
122 
123                 ClassLoader classloader = ClassLoaderFactory.createClassLoader(
124                         zip,  librarySearchPath, libraryPermittedPath, parent,
125                         targetSdkVersion, isBundled, classLoaderName, sharedLibraries,
126                         nativeSharedLibraries);
127 
128                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
129 
130                 Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "setLayerPaths");
131                 GraphicsEnvironment.getInstance().setLayerPaths(
132                         classloader, librarySearchPath, libraryPermittedPath);
133                 Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
134 
135                 if (cacheKey != null) {
136                     mLoaders.put(cacheKey, classloader);
137                 }
138                 return classloader;
139             }
140 
141             Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
142             ClassLoader loader = ClassLoaderFactory.createClassLoader(
143                     zip, null, parent, classLoaderName, sharedLibraries);
144             Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
145             return loader;
146         }
147     }
148 
149     /**
150      * Caches system library class loaders which are not on the bootclasspath but are still used
151      * by many system apps.
152      *
153      * All libraries in the closure of libraries to be loaded must be in libs. A library can
154      * only depend on libraries that come before it in the list.
155      */
createAndCacheNonBootclasspathSystemClassLoaders(SharedLibraryInfo[] libs)156     public void createAndCacheNonBootclasspathSystemClassLoaders(SharedLibraryInfo[] libs) {
157         if (mSystemLibsCacheMap != null) {
158             throw new IllegalStateException("Already cached.");
159         }
160 
161         mSystemLibsCacheMap = new HashMap<String, CachedClassLoader>();
162 
163         for (SharedLibraryInfo lib : libs) {
164             createAndCacheNonBootclasspathSystemClassLoader(lib);
165         }
166     }
167 
168     /**
169      * Caches a single non-bootclasspath class loader.
170      *
171      * All of this library's dependencies must have previously been cached. Otherwise, an exception
172      * is thrown.
173      */
createAndCacheNonBootclasspathSystemClassLoader(SharedLibraryInfo lib)174     private void createAndCacheNonBootclasspathSystemClassLoader(SharedLibraryInfo lib) {
175         String path = lib.getPath();
176         List<SharedLibraryInfo> dependencies = lib.getDependencies();
177 
178         // get cached classloaders for dependencies
179         ArrayList<ClassLoader> sharedLibraries = null;
180         if (dependencies != null) {
181             sharedLibraries = new ArrayList<ClassLoader>(dependencies.size());
182             for (SharedLibraryInfo dependency : dependencies) {
183                 String dependencyPath = dependency.getPath();
184                 CachedClassLoader cached = mSystemLibsCacheMap.get(dependencyPath);
185 
186                 if (cached == null) {
187                     throw new IllegalStateException("Failed to find dependency " + dependencyPath
188                             + " of cachedlibrary " + path);
189                 }
190 
191                 sharedLibraries.add(cached.loader);
192             }
193         }
194 
195         // assume cached libraries work with current sdk since they are built-in
196         ClassLoader classLoader = getClassLoader(path, Build.VERSION.SDK_INT, true /*isBundled*/,
197                 null /*librarySearchPath*/, null /*libraryPermittedPath*/, null /*parent*/,
198                 null /*cacheKey*/, null /*classLoaderName*/, sharedLibraries /*sharedLibraries*/,
199                 null /* nativeSharedLibraries */);
200 
201         if (classLoader == null) {
202             // bad configuration or break in classloading code
203             throw new IllegalStateException("Failed to cache " + path);
204         }
205 
206         CachedClassLoader cached = new CachedClassLoader();
207         cached.loader = classLoader;
208         cached.sharedLibraries = sharedLibraries;
209 
210         Log.d(TAG, "Created zygote-cached class loader: " + path);
211         mSystemLibsCacheMap.put(path, cached);
212     }
213 
sharedLibrariesEquals(List<ClassLoader> lhs, List<ClassLoader> rhs)214     private static boolean sharedLibrariesEquals(List<ClassLoader> lhs, List<ClassLoader> rhs) {
215         if (lhs == null) {
216             return rhs == null;
217         }
218 
219         return lhs.equals(rhs);
220     }
221 
222     /**
223      * Returns lib cached with createAndCacheNonBootclasspathSystemClassLoader. This is called by
224      * the zygote during caching.
225      *
226      * If there is an error or the cache is not available, this returns null.
227      */
getCachedNonBootclasspathSystemLib(String zip, ClassLoader parent, String classLoaderName, List<ClassLoader> sharedLibraries)228     public ClassLoader getCachedNonBootclasspathSystemLib(String zip, ClassLoader parent,
229             String classLoaderName, List<ClassLoader> sharedLibraries) {
230         if (mSystemLibsCacheMap == null) {
231             return null;
232         }
233 
234         // we only cache top-level libs with the default class loader
235         if (parent != null || classLoaderName != null) {
236             return null;
237         }
238 
239         CachedClassLoader cached = mSystemLibsCacheMap.get(zip);
240         if (cached == null) {
241             return null;
242         }
243 
244         // cached must be built and loaded in the same environment
245         if (!sharedLibrariesEquals(sharedLibraries, cached.sharedLibraries)) {
246             Log.w(TAG, "Unexpected environment loading cached library " + zip + " (real|cached): ("
247                     + sharedLibraries + "|" + cached.sharedLibraries + ")");
248             return null;
249         }
250 
251         Log.d(TAG, "Returning zygote-cached class loader: " + zip);
252         return cached.loader;
253     }
254 
255     /**
256      * Creates a classloader for the WebView APK and places it in the cache of loaders maintained
257      * by this class. This is used in the WebView zygote, where its presence in the cache speeds up
258      * startup and enables memory sharing.
259      */
createAndCacheWebViewClassLoader(String packagePath, String libsPath, String cacheKey)260     public ClassLoader createAndCacheWebViewClassLoader(String packagePath, String libsPath,
261                                                         String cacheKey) {
262         // The correct paths are calculated by WebViewZygote in the system server and passed to
263         // us here. We hardcode the other parameters: WebView always targets the current SDK,
264         // does not need to use non-public system libraries, and uses the base classloader as its
265         // parent to permit usage of the cache.
266         // The cache key is passed separately to enable the stub WebView to be cached under the
267         // stub's APK path, when the actual package path is the donor APK.
268         return getClassLoader(packagePath, Build.VERSION.SDK_INT, false, libsPath, null, null,
269                               cacheKey, null /* classLoaderName */, null /* sharedLibraries */,
270                               null /* nativeSharedLibraries */);
271     }
272 
273     /**
274      * Adds a new path the classpath of the given loader.
275      * @throws IllegalStateException if the provided class loader is not a {@link PathClassLoader}.
276      */
addPath(ClassLoader classLoader, String dexPath)277     void addPath(ClassLoader classLoader, String dexPath) {
278         if (!(classLoader instanceof PathClassLoader)) {
279             throw new IllegalStateException("class loader is not a PathClassLoader");
280         }
281         final PathClassLoader baseDexClassLoader = (PathClassLoader) classLoader;
282         baseDexClassLoader.addDexPath(dexPath);
283     }
284 
285     /**
286      * @hide
287      */
addNative(ClassLoader classLoader, Collection<String> libPaths)288     void addNative(ClassLoader classLoader, Collection<String> libPaths) {
289         if (!(classLoader instanceof PathClassLoader)) {
290             throw new IllegalStateException("class loader is not a PathClassLoader");
291         }
292         final PathClassLoader baseDexClassLoader = (PathClassLoader) classLoader;
293         baseDexClassLoader.addNativePath(libPaths);
294     }
295 
296     @UnsupportedAppUsage
297     private final ArrayMap<String, ClassLoader> mLoaders = new ArrayMap<>();
298 
299     private static final ApplicationLoaders gApplicationLoaders = new ApplicationLoaders();
300 
301     private static class CachedClassLoader {
302         ClassLoader loader;
303 
304         /**
305          * The shared libraries used when constructing loader for verification.
306          */
307         List<ClassLoader> sharedLibraries;
308     }
309 
310     /**
311      * This is a map of zip to associated class loader.
312      */
313     private Map<String, CachedClassLoader> mSystemLibsCacheMap = null;
314 }
315