1 /*
2  * Copyright (C) 2017 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 com.android.server.pm.dex;
18 
19 import android.content.pm.ApplicationInfo;
20 import android.content.pm.SharedLibraryInfo;
21 import com.android.server.pm.parsing.pkg.AndroidPackage;
22 import android.util.Slog;
23 import android.util.SparseArray;
24 
25 import com.android.internal.os.ClassLoaderFactory;
26 import com.android.internal.util.ArrayUtils;
27 
28 import java.io.File;
29 import java.util.List;
30 
31 public final class DexoptUtils {
32     private static final String TAG = "DexoptUtils";
33 
34     // Shared libraries have more or less followed PCL behavior due to the way
35     // they were added to the classpath pre Q.
36     private static final String SHARED_LIBRARY_LOADER_TYPE =
37             ClassLoaderFactory.getPathClassLoaderName();
38 
DexoptUtils()39     private DexoptUtils() {}
40 
41     /**
42      * Creates the class loader context dependencies for each of the application code paths.
43      * The returned array contains the class loader contexts that needs to be passed to dexopt in
44      * order to ensure correct optimizations. "Code" paths with no actual code, as specified by
45      * {@param pathsWithCode}, are ignored and will have null as their context in the returned array
46      * (configuration splits are an example of paths without code).
47      *
48      * A class loader context describes how the class loader chain should be built by dex2oat
49      * in order to ensure that classes are resolved during compilation as they would be resolved
50      * at runtime. The context will be encoded in the compiled code. If at runtime the dex file is
51      * loaded in a different context (with a different set of class loaders or a different
52      * classpath), the compiled code will be rejected.
53      *
54      * Note that the class loader context only includes dependencies and not the code path itself.
55      * The contexts are created based on the application split dependency list and
56      * the provided shared libraries.
57      *
58      * All the code paths encoded in the context will be relative to the base directory. This
59      * enables stage compilation where compiler artifacts may be moved around.
60      *
61      * The result is indexed as follows:
62      *   - index 0 contains the context for the base apk
63      *   - index 1 to n contain the context for the splits in the order determined by
64      *     {@code info.getSplitCodePaths()}
65      *
66      * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk}
67      * and pay attention to the way the classpath is created for the non isolated mode in:
68      * {@link android.app.LoadedApk#makePaths(
69      * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}.
70      */
getClassLoaderContexts(AndroidPackage pkg, List<SharedLibraryInfo> sharedLibraries, boolean[] pathsWithCode)71     public static String[] getClassLoaderContexts(AndroidPackage pkg,
72             List<SharedLibraryInfo> sharedLibraries, boolean[] pathsWithCode) {
73         // The base class loader context contains only the shared library.
74         String sharedLibrariesContext = "";
75         if (sharedLibraries != null) {
76             sharedLibrariesContext = encodeSharedLibraries(sharedLibraries);
77         }
78 
79         String baseApkContextClassLoader = encodeClassLoader(
80                 "", pkg.getClassLoaderName(), sharedLibrariesContext);
81         if (pkg.getSplitCodePaths() == null) {
82             // The application has no splits.
83             return new String[] {baseApkContextClassLoader};
84         }
85 
86         // The application has splits. Compute their class loader contexts.
87 
88         // First, cache the relative paths of the splits and do some sanity checks
89         String[] splitRelativeCodePaths = getSplitRelativeCodePaths(pkg);
90 
91         // The splits have an implicit dependency on the base apk.
92         // This means that we have to add the base apk file in addition to the shared libraries.
93         String baseApkName = new File(pkg.getBaseCodePath()).getName();
94         String baseClassPath = baseApkName;
95 
96         // The result is stored in classLoaderContexts.
97         // Index 0 is the class loader context for the base apk.
98         // Index `i` is the class loader context encoding for split `i`.
99         String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length];
100         classLoaderContexts[0] = pathsWithCode[0] ? baseApkContextClassLoader : null;
101 
102         SparseArray<int[]> splitDependencies = pkg.getSplitDependencies();
103 
104         if (!pkg.isIsolatedSplitLoading()
105                 || splitDependencies == null
106                 || splitDependencies.size() == 0) {
107             // If the app didn't request for the splits to be loaded in isolation or if it does not
108             // declare inter-split dependencies, then all the splits will be loaded in the base
109             // apk class loader (in the order of their definition).
110             String classpath = baseClassPath;
111             for (int i = 1; i < classLoaderContexts.length; i++) {
112                 if (pathsWithCode[i]) {
113                     classLoaderContexts[i] = encodeClassLoader(
114                             classpath, pkg.getClassLoaderName(), sharedLibrariesContext);
115                 } else {
116                     classLoaderContexts[i] = null;
117                 }
118                 // Note that the splits with no code are not removed from the classpath computation.
119                 // i.e. split_n might get the split_n-1 in its classpath dependency even
120                 // if split_n-1 has no code.
121                 // The splits with no code do not matter for the runtime which ignores
122                 // apks without code when doing the classpath checks. As such we could actually
123                 // filter them but we don't do it in order to keep consistency with how the apps
124                 // are loaded.
125                 classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]);
126             }
127         } else {
128             // In case of inter-split dependencies, we need to walk the dependency chain of each
129             // split. We do this recursively and store intermediate results in classLoaderContexts.
130 
131             // First, look at the split class loaders and cache their individual contexts (i.e.
132             // the class loader + the name of the split). This is an optimization to avoid
133             // re-computing them during the recursive call.
134             // The cache is stored in splitClassLoaderEncodingCache. The difference between this and
135             // classLoaderContexts is that the later contains the full chain of class loaders for
136             // a given split while splitClassLoaderEncodingCache only contains a single class loader
137             // encoding.
138             String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length];
139             for (int i = 0; i < splitRelativeCodePaths.length; i++) {
140                 splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i],
141                         pkg.getSplitClassLoaderNames()[i]);
142             }
143             String splitDependencyOnBase = encodeClassLoader(
144                     baseClassPath, pkg.getClassLoaderName());
145 
146             // Note that not all splits have dependencies (e.g. configuration splits)
147             // The splits without dependencies will have classLoaderContexts[config_split_index]
148             // set to null after this step.
149             for (int i = 1; i < splitDependencies.size(); i++) {
150                 int splitIndex = splitDependencies.keyAt(i);
151                 if (pathsWithCode[splitIndex]) {
152                     // Compute the class loader context only for the splits with code.
153                     getParentDependencies(splitIndex, splitClassLoaderEncodingCache,
154                             splitDependencies, classLoaderContexts, splitDependencyOnBase);
155                 }
156             }
157 
158             // At this point classLoaderContexts contains only the parent dependencies.
159             // We also need to add the class loader of the current split which should
160             // come first in the context.
161             for (int i = 1; i < classLoaderContexts.length; i++) {
162                 String splitClassLoader = encodeClassLoader("",
163                         pkg.getSplitClassLoaderNames()[i - 1]);
164                 if (pathsWithCode[i]) {
165                     // If classLoaderContexts[i] is null it means that the split does not have
166                     // any dependency. In this case its context equals its declared class loader.
167                     classLoaderContexts[i] = classLoaderContexts[i] == null
168                             ? splitClassLoader
169                             : encodeClassLoaderChain(splitClassLoader, classLoaderContexts[i])
170                                     + sharedLibrariesContext;
171                 } else {
172                     // This is a split without code, it has no dependency and it is not compiled.
173                     // Its context will be null.
174                     classLoaderContexts[i] = null;
175                 }
176             }
177         }
178 
179         return classLoaderContexts;
180     }
181 
182     /**
183      * Creates the class loader context for the given shared library.
184      */
getClassLoaderContext(SharedLibraryInfo info)185     public static String getClassLoaderContext(SharedLibraryInfo info) {
186         String sharedLibrariesContext = "";
187         if (info.getDependencies() != null) {
188             sharedLibrariesContext = encodeSharedLibraries(info.getDependencies());
189         }
190         return encodeClassLoader(
191                 "", SHARED_LIBRARY_LOADER_TYPE, sharedLibrariesContext);
192     }
193 
194     /**
195      * Recursive method to generate the class loader context dependencies for the split with the
196      * given index. {@param classLoaderContexts} acts as an accumulator. Upton return
197      * {@code classLoaderContexts[index]} will contain the split dependency.
198      * During computation, the method may resolve the dependencies of other splits as it traverses
199      * the entire parent chain. The result will also be stored in {@param classLoaderContexts}.
200      *
201      * Note that {@code index 0} denotes the base apk and it is special handled. When the
202      * recursive call hits {@code index 0} the method returns {@code splitDependencyOnBase}.
203      * {@code classLoaderContexts[0]} is not modified in this method.
204      *
205      * @param index the index of the split (Note that index 0 denotes the base apk)
206      * @param splitClassLoaderEncodingCache the class loader encoding for the individual splits.
207      *    It contains only the split class loader and not the the base. The split
208      *    with {@code index} has its context at {@code splitClassLoaderEncodingCache[index - 1]}.
209      * @param splitDependencies the dependencies for all splits. Note that in this array index 0
210      *    is the base and splits start from index 1.
211      * @param classLoaderContexts the result accumulator. index 0 is the base and never set. Splits
212      *    start at index 1.
213      * @param splitDependencyOnBase the encoding of the implicit split dependency on base.
214      */
getParentDependencies(int index, String[] splitClassLoaderEncodingCache, SparseArray<int[]> splitDependencies, String[] classLoaderContexts, String splitDependencyOnBase)215     private static String getParentDependencies(int index, String[] splitClassLoaderEncodingCache,
216             SparseArray<int[]> splitDependencies, String[] classLoaderContexts,
217             String splitDependencyOnBase) {
218         // If we hit the base apk return its custom dependency list which is
219         // sharedLibraries + base.apk
220         if (index == 0) {
221             return splitDependencyOnBase;
222         }
223         // Return the result if we've computed the splitDependencies for this index already.
224         if (classLoaderContexts[index] != null) {
225             return classLoaderContexts[index];
226         }
227         // Get the splitDependencies for the parent of this index and append its path to it.
228         int parent = splitDependencies.get(index)[0];
229         String parentDependencies = getParentDependencies(parent, splitClassLoaderEncodingCache,
230                 splitDependencies, classLoaderContexts, splitDependencyOnBase);
231 
232         // The split context is: `parent context + parent dependencies context`.
233         String splitContext = (parent == 0) ?
234                 parentDependencies :
235                 encodeClassLoaderChain(splitClassLoaderEncodingCache[parent - 1], parentDependencies);
236         classLoaderContexts[index] = splitContext;
237         return splitContext;
238     }
239 
encodeSharedLibrary(SharedLibraryInfo sharedLibrary)240     private static String encodeSharedLibrary(SharedLibraryInfo sharedLibrary) {
241         List<String> paths = sharedLibrary.getAllCodePaths();
242         String classLoaderSpec = encodeClassLoader(
243                 encodeClasspath(paths.toArray(new String[paths.size()])),
244                 SHARED_LIBRARY_LOADER_TYPE);
245         if (sharedLibrary.getDependencies() != null) {
246             classLoaderSpec += encodeSharedLibraries(sharedLibrary.getDependencies());
247         }
248         return classLoaderSpec;
249     }
250 
encodeSharedLibraries(List<SharedLibraryInfo> sharedLibraries)251     private static String encodeSharedLibraries(List<SharedLibraryInfo> sharedLibraries) {
252         String sharedLibrariesContext = "{";
253         boolean first = true;
254         for (SharedLibraryInfo info : sharedLibraries) {
255             if (!first) {
256                 sharedLibrariesContext += "#";
257             }
258             first = false;
259             sharedLibrariesContext += encodeSharedLibrary(info);
260         }
261         sharedLibrariesContext += "}";
262         return sharedLibrariesContext;
263     }
264 
265     /**
266      * Encodes the shared libraries classpathElements in a format accepted by dexopt.
267      * NOTE: Keep this in sync with the dexopt expectations! Right now that is
268      * a list separated by ':'.
269      */
encodeClasspath(String[] classpathElements)270     private static String encodeClasspath(String[] classpathElements) {
271         if (classpathElements == null || classpathElements.length == 0) {
272             return "";
273         }
274         StringBuilder sb = new StringBuilder();
275         for (String element : classpathElements) {
276             if (sb.length() != 0) {
277                 sb.append(":");
278             }
279             sb.append(element);
280         }
281         return sb.toString();
282     }
283 
284     /**
285      * Adds an element to the encoding of an existing classpath.
286      * {@see PackageDexOptimizer.encodeClasspath(String[])}
287      */
encodeClasspath(String classpath, String newElement)288     private static String encodeClasspath(String classpath, String newElement) {
289         return classpath.isEmpty() ? newElement : (classpath + ":" + newElement);
290     }
291 
292     /**
293      * Encodes a single class loader dependency starting from {@param path} and
294      * {@param classLoaderName}.
295      * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
296      * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
297      */
encodeClassLoader(String classpath, String classLoaderName)298     /*package*/ static String encodeClassLoader(String classpath, String classLoaderName) {
299         classpath.getClass();  // Throw NPE if classpath is null
300         String classLoaderDexoptEncoding = classLoaderName;
301         if (ClassLoaderFactory.isPathClassLoaderName(classLoaderName)) {
302             classLoaderDexoptEncoding = "PCL";
303         } else if (ClassLoaderFactory.isDelegateLastClassLoaderName(classLoaderName)) {
304             classLoaderDexoptEncoding = "DLC";
305         } else {
306             Slog.wtf(TAG, "Unsupported classLoaderName: " + classLoaderName);
307         }
308         return classLoaderDexoptEncoding + "[" + classpath + "]";
309     }
310 
311     /**
312      * Same as above, but appends {@param sharedLibraries} to the result.
313      */
encodeClassLoader(String classpath, String classLoaderName, String sharedLibraries)314     private static String encodeClassLoader(String classpath, String classLoaderName,
315             String sharedLibraries) {
316         return encodeClassLoader(classpath, classLoaderName) + sharedLibraries;
317     }
318 
319     /**
320      * Links to dependencies together in a format accepted by dexopt.
321      * For the special case when either of cl1 or cl2 equals
322      * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split
323      * dependencies {@see encodeClassLoader} separated by ';'.
324      */
encodeClassLoaderChain(String cl1, String cl2)325     /*package*/ static String encodeClassLoaderChain(String cl1, String cl2) {
326         if (cl1.isEmpty()) return cl2;
327         if (cl2.isEmpty()) return cl1;
328         return cl1 + ";" + cl2;
329     }
330 
331     /**
332      * Compute the class loader context for the dex files present in the classpath of the first
333      * class loader from the given list (referred in the code as the {@code loadingClassLoader}).
334      * Each dex files gets its own class loader context in the returned array.
335      *
336      * Example:
337      *    If classLoadersNames = {"dalvik.system.DelegateLastClassLoader",
338      *    "dalvik.system.PathClassLoader"} and classPaths = {"foo.dex:bar.dex", "other.dex"}
339      *    The output will be
340      *    {"DLC[];PCL[other.dex]", "DLC[foo.dex];PCL[other.dex]"}
341      *    with "DLC[];PCL[other.dex]" being the context for "foo.dex"
342      *    and "DLC[foo.dex];PCL[other.dex]" the context for "bar.dex".
343      *
344      * If any of the class loaders names is unsupported the method will return null.
345      *
346      * The argument lists must be non empty and of the same size.
347      *
348      * @param classLoadersNames the names of the class loaders present in the loading chain. The
349      *    list encodes the class loader chain in the natural order. The first class loader has
350      *    the second one as its parent and so on.
351      * @param classPaths the class paths for the elements of {@param classLoadersNames}. The
352      *     the first element corresponds to the first class loader and so on. A classpath is
353      *     represented as a list of dex files separated by {@code File.pathSeparator}.
354      *     The return context will be for the dex files found in the first class path.
355      */
processContextForDexLoad(List<String> classLoadersNames, List<String> classPaths)356     /*package*/ static String[] processContextForDexLoad(List<String> classLoadersNames,
357             List<String> classPaths) {
358         if (classLoadersNames.size() != classPaths.size()) {
359             throw new IllegalArgumentException(
360                     "The size of the class loader names and the dex paths do not match.");
361         }
362         if (classLoadersNames.isEmpty()) {
363             throw new IllegalArgumentException("Empty classLoadersNames");
364         }
365 
366         // Compute the context for the parent class loaders.
367         String parentContext = "";
368         // We know that these lists are actually ArrayLists so getting the elements by index
369         // is fine (they come over binder). Even if something changes we expect the sizes to be
370         // very small and it shouldn't matter much.
371         for (int i = 1; i < classLoadersNames.size(); i++) {
372             if (!ClassLoaderFactory.isValidClassLoaderName(classLoadersNames.get(i))
373                 || classPaths.get(i) == null) {
374                 return null;
375             }
376             String classpath = encodeClasspath(classPaths.get(i).split(File.pathSeparator));
377             parentContext = encodeClassLoaderChain(parentContext,
378                     encodeClassLoader(classpath, classLoadersNames.get(i)));
379         }
380 
381         // Now compute the class loader context for each dex file from the first classpath.
382         String loadingClassLoader = classLoadersNames.get(0);
383         if (!ClassLoaderFactory.isValidClassLoaderName(loadingClassLoader)) {
384             return null;
385         }
386         String[] loadedDexPaths = classPaths.get(0).split(File.pathSeparator);
387         String[] loadedDexPathsContext = new String[loadedDexPaths.length];
388         String currentLoadedDexPathClasspath = "";
389         for (int i = 0; i < loadedDexPaths.length; i++) {
390             String dexPath = loadedDexPaths[i];
391             String currentContext = encodeClassLoader(
392                     currentLoadedDexPathClasspath, loadingClassLoader);
393             loadedDexPathsContext[i] = encodeClassLoaderChain(currentContext, parentContext);
394             currentLoadedDexPathClasspath = encodeClasspath(currentLoadedDexPathClasspath, dexPath);
395         }
396         return loadedDexPathsContext;
397     }
398 
399     /**
400      * Returns the relative paths of the splits declared by the application {@code info}.
401      * Assumes that the application declares a non-null array of splits.
402      */
getSplitRelativeCodePaths(AndroidPackage pkg)403     private static String[] getSplitRelativeCodePaths(AndroidPackage pkg) {
404         String baseCodePath = new File(pkg.getBaseCodePath()).getParent();
405         String[] splitCodePaths = pkg.getSplitCodePaths();
406         String[] splitRelativeCodePaths = new String[ArrayUtils.size(splitCodePaths)];
407         for (int i = 0; i < splitRelativeCodePaths.length; i++) {
408             File pathFile = new File(splitCodePaths[i]);
409             splitRelativeCodePaths[i] = pathFile.getName();
410             // Sanity check that the base paths of the splits are all the same.
411             String basePath = pathFile.getParent();
412             if (!basePath.equals(baseCodePath)) {
413                 Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
414                         baseCodePath);
415             }
416         }
417         return splitRelativeCodePaths;
418     }
419 }
420