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