DexoptUtils.java revision e534b2c6f439e2d64d5b3a95b640766c56e5995c
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 java.io.File;
24import java.util.List;
25
26public final class DexoptUtils {
27    private static final String TAG = "DexoptUtils";
28
29    private DexoptUtils() {}
30
31    /**
32     * Creates the class loader context dependencies for each of the application code paths.
33     * The returned array contains the class loader contexts that needs to be passed to dexopt in
34     * order to ensure correct optimizations.
35     *
36     * A class loader context describes how the class loader chain should be built by dex2oat
37     * in order to ensure that classes are resolved during compilation as they would be resolved
38     * at runtime. The context will be encoded in the compiled code. If at runtime the dex file is
39     * loaded in a different context (with a different set of class loaders or a different
40     * classpath), the compiled code will be rejected.
41     *
42     * Note that the class loader context only includes dependencies and not the code path itself.
43     * The contexts are created based on the application split dependency list and
44     * the provided shared libraries.
45     *
46     * All the code paths encoded in the context will be relative to the base directory. This
47     * enables stage compilation where compiler artifacts may be moved around.
48     *
49     * The result is indexed as follows:
50     *   - index 0 contains the context for the base apk
51     *   - index 1 to n contain the context for the splits in the order determined by
52     *     {@code info.getSplitCodePaths()}
53     *
54     * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk}
55     * and pay attention to the way the classpath is created for the non isolated mode in:
56     * {@link android.app.LoadedApk#makePaths(
57     * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}.
58     */
59    public static String[] getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries) {
60        // The base class loader context contains only the shared library.
61        String sharedLibrariesClassPath = encodeClasspath(sharedLibraries);
62        String baseApkContextClassLoader = encodeClassLoader(
63                sharedLibrariesClassPath, "dalvik.system.PathClassLoader");
64
65        if (info.getSplitCodePaths() == null) {
66            // The application has no splits.
67            return new String[] {baseApkContextClassLoader};
68        }
69
70        // The application has splits. Compute their class loader contexts.
71
72        // First, cache the relative paths of the splits and do some sanity checks
73        String[] splitRelativeCodePaths = getSplitRelativeCodePaths(info);
74
75        // The splits have an implicit dependency on the base apk.
76        // This means that we have to add the base apk file in addition to the shared libraries.
77        String baseApkName = new File(info.getBaseCodePath()).getName();
78        String sharedLibrariesAndBaseClassPath =
79                encodeClasspath(sharedLibrariesClassPath, baseApkName);
80
81        // The result is stored in classLoaderContexts.
82        // Index 0 is the class loaded context for the base apk.
83        // Index `i` is the class loader context encoding for split `i`.
84        String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length];
85        classLoaderContexts[0] = baseApkContextClassLoader;
86
87        if (!info.requestsIsolatedSplitLoading() || info.splitDependencies == null) {
88            // If the app didn't request for the splits to be loaded in isolation or if it does not
89            // declare inter-split dependencies, then all the splits will be loaded in the base
90            // apk class loader (in the order of their definition).
91            String classpath = sharedLibrariesAndBaseClassPath;
92            for (int i = 1; i < classLoaderContexts.length; i++) {
93                classLoaderContexts[i] = encodeClassLoader(classpath, "dalvik.system.PathClassLoader");
94                classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]);
95            }
96        } else {
97            // In case of inter-split dependencies, we need to walk the dependency chain of each
98            // split. We do this recursively and store intermediate results in classLoaderContexts.
99
100            // First, look at the split class loaders and cache their individual contexts (i.e.
101            // the class loader + the name of the split). This is an optimization to avoid
102            // re-computing them during the recursive call.
103            // The cache is stored in splitClassLoaderEncodingCache. The difference between this and
104            // classLoaderContexts is that the later contains the full chain of class loaders for
105            // a given split while splitClassLoaderEncodingCache only contains a single class loader
106            // encoding.
107            String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length];
108            for (int i = 0; i < splitRelativeCodePaths.length; i++) {
109                splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i],
110                        "dalvik.system.PathClassLoader");
111            }
112            String splitDependencyOnBase = encodeClassLoader(
113                    sharedLibrariesAndBaseClassPath, "dalvik.system.PathClassLoader");
114            SparseArray<int[]> splitDependencies = info.splitDependencies;
115            for (int i = 1; i < splitDependencies.size(); i++) {
116                getParentDependencies(splitDependencies.keyAt(i), splitClassLoaderEncodingCache,
117                        splitDependencies, classLoaderContexts, splitDependencyOnBase);
118            }
119
120            // At this point classLoaderContexts contains only the parent dependencies.
121            // We also need to add the class loader of the current split which should
122            // come first in the context.
123            for (int i = 1; i < classLoaderContexts.length; i++) {
124                String splitClassLoader = encodeClassLoader("", "dalvik.system.PathClassLoader");
125                classLoaderContexts[i] = encodeClassLoaderChain(
126                        splitClassLoader, classLoaderContexts[i]);
127            }
128        }
129
130        return classLoaderContexts;
131    }
132
133    /**
134     * Recursive method to generate the class loader context dependencies for the split with the
135     * given index. {@param classLoaderContexts} acts as an accumulator. Upton return
136     * {@code classLoaderContexts[index]} will contain the split dependency.
137     * During computation, the method may resolve the dependencies of other splits as it traverses
138     * the entire parent chain. The result will also be stored in {@param classLoaderContexts}.
139     *
140     * Note that {@code index 0} denotes the base apk and it is special handled. When the
141     * recursive call hits {@code index 0} the method returns {@code splitDependencyOnBase}.
142     * {@code classLoaderContexts[0]} is not modified in this method.
143     *
144     * @param index the index of the split (Note that index 0 denotes the base apk)
145     * @param splitClassLoaderEncodingCache the class loader encoding for the individual splits.
146     *    It contains only the split class loader and not the the base. The split
147     *    with {@code index} has its context at {@code splitClassLoaderEncodingCache[index - 1]}.
148     * @param splitDependencies the dependencies for all splits. Note that in this array index 0
149     *    is the base and splits start from index 1.
150     * @param classLoaderContexts the result accumulator. index 0 is the base and never set. Splits
151     *    start at index 1.
152     * @param splitDependencyOnBase the encoding of the implicit split dependency on base.
153     */
154    private static String getParentDependencies(int index, String[] splitClassLoaderEncodingCache,
155            SparseArray<int[]> splitDependencies, String[] classLoaderContexts,
156            String splitDependencyOnBase) {
157        // If we hit the base apk return its custom dependency list which is
158        // sharedLibraries + base.apk
159        if (index == 0) {
160            return splitDependencyOnBase;
161        }
162        // Return the result if we've computed the splitDependencies for this index already.
163        if (classLoaderContexts[index] != null) {
164            return classLoaderContexts[index];
165        }
166        // Get the splitDependencies for the parent of this index and append its path to it.
167        int parent = splitDependencies.get(index)[0];
168        String parentDependencies = getParentDependencies(parent, splitClassLoaderEncodingCache,
169                splitDependencies, classLoaderContexts, splitDependencyOnBase);
170
171        // The split context is: `parent context + parent dependencies context`.
172        String splitContext = (parent == 0) ?
173                parentDependencies :
174                encodeClassLoaderChain(splitClassLoaderEncodingCache[parent - 1], parentDependencies);
175        classLoaderContexts[index] = splitContext;
176        return splitContext;
177    }
178
179    /**
180     * Encodes the shared libraries classpathElements in a format accepted by dexopt.
181     * NOTE: Keep this in sync with the dexopt expectations! Right now that is
182     * a list separated by ':'.
183     */
184    private static String encodeClasspath(String[] classpathElements) {
185        if (classpathElements == null || classpathElements.length == 0) {
186            return "";
187        }
188        StringBuilder sb = new StringBuilder();
189        for (String element : classpathElements) {
190            if (sb.length() != 0) {
191                sb.append(":");
192            }
193            sb.append(element);
194        }
195        return sb.toString();
196    }
197
198    /**
199     * Adds an element to the encoding of an existing classpath.
200     * {@see PackageDexOptimizer.encodeClasspath(String[])}
201     */
202    private static String encodeClasspath(String classpath, String newElement) {
203        return classpath.isEmpty() ? newElement : (classpath + ":" + newElement);
204    }
205
206    /**
207     * Encodes a single class loader dependency starting from {@param path} and
208     * {@param classLoaderName}.
209     * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
210     * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
211     */
212    private static String encodeClassLoader(String classpath, String classLoaderName) {
213        String classLoaderDexoptEncoding = classLoaderName;
214        if ("dalvik.system.PathClassLoader".equals(classLoaderName)) {
215            classLoaderDexoptEncoding = "PCL";
216        } else {
217            Slog.wtf(TAG, "Unsupported classLoaderName: " + classLoaderName);
218        }
219        return classLoaderDexoptEncoding + "[" + classpath + "]";
220    }
221
222    /**
223     * Links to dependencies together in a format accepted by dexopt.
224     * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split
225     * dependencies {@see encodeClassLoader} separated by ';'.
226     */
227    private static String encodeClassLoaderChain(String cl1, String cl2) {
228        return cl1.isEmpty() ? cl2 : (cl1 + ";" + cl2);
229    }
230
231    /**
232     * Returns the relative paths of the splits declared by the application {@code info}.
233     * Assumes that the application declares a non-null array of splits.
234     */
235    private static String[] getSplitRelativeCodePaths(ApplicationInfo info) {
236        String baseCodePath = new File(info.getBaseCodePath()).getParent();
237        String[] splitCodePaths = info.getSplitCodePaths();
238        String[] splitRelativeCodePaths = new String[splitCodePaths.length];
239        for (int i = 0; i < splitCodePaths.length; i++) {
240            File pathFile = new File(splitCodePaths[i]);
241            splitRelativeCodePaths[i] = pathFile.getName();
242            // Sanity check that the base paths of the splits are all the same.
243            String basePath = pathFile.getParent();
244            if (!basePath.equals(baseCodePath)) {
245                Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
246                        baseCodePath);
247            }
248        }
249        return splitRelativeCodePaths;
250    }
251}
252