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