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