DexoptUtils.java revision e534b2c6f439e2d64d5b3a95b640766c56e5995c
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.List; 25 26public final class DexoptUtils { 27 private static final String TAG = "DexoptUtils"; 28 29 private DexoptUtils() {} 30 31 /** 32 * Creates the class loader context dependencies for each of the application code paths. 33 * The returned array contains the class loader contexts that needs to be passed to dexopt in 34 * order to ensure correct optimizations. 35 * 36 * A class loader context describes how the class loader chain should be built by dex2oat 37 * in order to ensure that classes are resolved during compilation as they would be resolved 38 * at runtime. The context will be encoded in the compiled code. If at runtime the dex file is 39 * loaded in a different context (with a different set of class loaders or a different 40 * classpath), the compiled code will be rejected. 41 * 42 * Note that the class loader context only includes dependencies and not the code path itself. 43 * The contexts are created based on the application split dependency list and 44 * the provided shared libraries. 45 * 46 * All the code paths encoded in the context will be relative to the base directory. This 47 * enables stage compilation where compiler artifacts may be moved around. 48 * 49 * The result is indexed as follows: 50 * - index 0 contains the context for the base apk 51 * - index 1 to n contain the context for the splits in the order determined by 52 * {@code info.getSplitCodePaths()} 53 * 54 * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk} 55 * and pay attention to the way the classpath is created for the non isolated mode in: 56 * {@link android.app.LoadedApk#makePaths( 57 * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}. 58 */ 59 public static String[] getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries) { 60 // The base class loader context contains only the shared library. 61 String sharedLibrariesClassPath = encodeClasspath(sharedLibraries); 62 String baseApkContextClassLoader = encodeClassLoader( 63 sharedLibrariesClassPath, "dalvik.system.PathClassLoader"); 64 65 if (info.getSplitCodePaths() == null) { 66 // The application has no splits. 67 return new String[] {baseApkContextClassLoader}; 68 } 69 70 // The application has splits. Compute their class loader contexts. 71 72 // First, cache the relative paths of the splits and do some sanity checks 73 String[] splitRelativeCodePaths = getSplitRelativeCodePaths(info); 74 75 // The splits have an implicit dependency on the base apk. 76 // This means that we have to add the base apk file in addition to the shared libraries. 77 String baseApkName = new File(info.getBaseCodePath()).getName(); 78 String sharedLibrariesAndBaseClassPath = 79 encodeClasspath(sharedLibrariesClassPath, baseApkName); 80 81 // The result is stored in classLoaderContexts. 82 // Index 0 is the class loaded context for the base apk. 83 // Index `i` is the class loader context encoding for split `i`. 84 String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length]; 85 classLoaderContexts[0] = baseApkContextClassLoader; 86 87 if (!info.requestsIsolatedSplitLoading() || info.splitDependencies == null) { 88 // If the app didn't request for the splits to be loaded in isolation or if it does not 89 // declare inter-split dependencies, then all the splits will be loaded in the base 90 // apk class loader (in the order of their definition). 91 String classpath = sharedLibrariesAndBaseClassPath; 92 for (int i = 1; i < classLoaderContexts.length; i++) { 93 classLoaderContexts[i] = encodeClassLoader(classpath, "dalvik.system.PathClassLoader"); 94 classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]); 95 } 96 } else { 97 // In case of inter-split dependencies, we need to walk the dependency chain of each 98 // split. We do this recursively and store intermediate results in classLoaderContexts. 99 100 // First, look at the split class loaders and cache their individual contexts (i.e. 101 // the class loader + the name of the split). This is an optimization to avoid 102 // re-computing them during the recursive call. 103 // The cache is stored in splitClassLoaderEncodingCache. The difference between this and 104 // classLoaderContexts is that the later contains the full chain of class loaders for 105 // a given split while splitClassLoaderEncodingCache only contains a single class loader 106 // encoding. 107 String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length]; 108 for (int i = 0; i < splitRelativeCodePaths.length; i++) { 109 splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i], 110 "dalvik.system.PathClassLoader"); 111 } 112 String splitDependencyOnBase = encodeClassLoader( 113 sharedLibrariesAndBaseClassPath, "dalvik.system.PathClassLoader"); 114 SparseArray<int[]> splitDependencies = info.splitDependencies; 115 for (int i = 1; i < splitDependencies.size(); i++) { 116 getParentDependencies(splitDependencies.keyAt(i), splitClassLoaderEncodingCache, 117 splitDependencies, classLoaderContexts, splitDependencyOnBase); 118 } 119 120 // At this point classLoaderContexts contains only the parent dependencies. 121 // We also need to add the class loader of the current split which should 122 // come first in the context. 123 for (int i = 1; i < classLoaderContexts.length; i++) { 124 String splitClassLoader = encodeClassLoader("", "dalvik.system.PathClassLoader"); 125 classLoaderContexts[i] = encodeClassLoaderChain( 126 splitClassLoader, classLoaderContexts[i]); 127 } 128 } 129 130 return classLoaderContexts; 131 } 132 133 /** 134 * Recursive method to generate the class loader context dependencies for the split with the 135 * given index. {@param classLoaderContexts} acts as an accumulator. Upton return 136 * {@code classLoaderContexts[index]} will contain the split dependency. 137 * During computation, the method may resolve the dependencies of other splits as it traverses 138 * the entire parent chain. The result will also be stored in {@param classLoaderContexts}. 139 * 140 * Note that {@code index 0} denotes the base apk and it is special handled. When the 141 * recursive call hits {@code index 0} the method returns {@code splitDependencyOnBase}. 142 * {@code classLoaderContexts[0]} is not modified in this method. 143 * 144 * @param index the index of the split (Note that index 0 denotes the base apk) 145 * @param splitClassLoaderEncodingCache the class loader encoding for the individual splits. 146 * It contains only the split class loader and not the the base. The split 147 * with {@code index} has its context at {@code splitClassLoaderEncodingCache[index - 1]}. 148 * @param splitDependencies the dependencies for all splits. Note that in this array index 0 149 * is the base and splits start from index 1. 150 * @param classLoaderContexts the result accumulator. index 0 is the base and never set. Splits 151 * start at index 1. 152 * @param splitDependencyOnBase the encoding of the implicit split dependency on base. 153 */ 154 private static String getParentDependencies(int index, String[] splitClassLoaderEncodingCache, 155 SparseArray<int[]> splitDependencies, String[] classLoaderContexts, 156 String splitDependencyOnBase) { 157 // If we hit the base apk return its custom dependency list which is 158 // sharedLibraries + base.apk 159 if (index == 0) { 160 return splitDependencyOnBase; 161 } 162 // Return the result if we've computed the splitDependencies for this index already. 163 if (classLoaderContexts[index] != null) { 164 return classLoaderContexts[index]; 165 } 166 // Get the splitDependencies for the parent of this index and append its path to it. 167 int parent = splitDependencies.get(index)[0]; 168 String parentDependencies = getParentDependencies(parent, splitClassLoaderEncodingCache, 169 splitDependencies, classLoaderContexts, splitDependencyOnBase); 170 171 // The split context is: `parent context + parent dependencies context`. 172 String splitContext = (parent == 0) ? 173 parentDependencies : 174 encodeClassLoaderChain(splitClassLoaderEncodingCache[parent - 1], parentDependencies); 175 classLoaderContexts[index] = splitContext; 176 return splitContext; 177 } 178 179 /** 180 * Encodes the shared libraries classpathElements in a format accepted by dexopt. 181 * NOTE: Keep this in sync with the dexopt expectations! Right now that is 182 * a list separated by ':'. 183 */ 184 private static String encodeClasspath(String[] classpathElements) { 185 if (classpathElements == null || classpathElements.length == 0) { 186 return ""; 187 } 188 StringBuilder sb = new StringBuilder(); 189 for (String element : classpathElements) { 190 if (sb.length() != 0) { 191 sb.append(":"); 192 } 193 sb.append(element); 194 } 195 return sb.toString(); 196 } 197 198 /** 199 * Adds an element to the encoding of an existing classpath. 200 * {@see PackageDexOptimizer.encodeClasspath(String[])} 201 */ 202 private static String encodeClasspath(String classpath, String newElement) { 203 return classpath.isEmpty() ? newElement : (classpath + ":" + newElement); 204 } 205 206 /** 207 * Encodes a single class loader dependency starting from {@param path} and 208 * {@param classLoaderName}. 209 * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]" 210 * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader. 211 */ 212 private static String encodeClassLoader(String classpath, String classLoaderName) { 213 String classLoaderDexoptEncoding = classLoaderName; 214 if ("dalvik.system.PathClassLoader".equals(classLoaderName)) { 215 classLoaderDexoptEncoding = "PCL"; 216 } else { 217 Slog.wtf(TAG, "Unsupported classLoaderName: " + classLoaderName); 218 } 219 return classLoaderDexoptEncoding + "[" + classpath + "]"; 220 } 221 222 /** 223 * Links to dependencies together in a format accepted by dexopt. 224 * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split 225 * dependencies {@see encodeClassLoader} separated by ';'. 226 */ 227 private static String encodeClassLoaderChain(String cl1, String cl2) { 228 return cl1.isEmpty() ? cl2 : (cl1 + ";" + cl2); 229 } 230 231 /** 232 * Returns the relative paths of the splits declared by the application {@code info}. 233 * Assumes that the application declares a non-null array of splits. 234 */ 235 private static String[] getSplitRelativeCodePaths(ApplicationInfo info) { 236 String baseCodePath = new File(info.getBaseCodePath()).getParent(); 237 String[] splitCodePaths = info.getSplitCodePaths(); 238 String[] splitRelativeCodePaths = new String[splitCodePaths.length]; 239 for (int i = 0; i < splitCodePaths.length; i++) { 240 File pathFile = new File(splitCodePaths[i]); 241 splitRelativeCodePaths[i] = pathFile.getName(); 242 // Sanity check that the base paths of the splits are all the same. 243 String basePath = pathFile.getParent(); 244 if (!basePath.equals(baseCodePath)) { 245 Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " + 246 baseCodePath); 247 } 248 } 249 return splitRelativeCodePaths; 250 } 251} 252