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