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