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