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