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