content: /*
   * Copyright (C) 2017 The Android Open Source Project
   *
   * Licensed under the Apache License, Version 2.0 (the "License");
   * you may not use this file except in compliance with the License.
   * You may obtain a copy of the License at
   *
   *      http://www.apache.org/licenses/LICENSE-2.0
   *
   * Unless required by applicable law or agreed to in writing, software
   * distributed under the License is distributed on an "AS IS" BASIS,
   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
   * See the License for the specific language governing permissions and
   * limitations under the License
   */
  
  package com.android.server.pm.dex;
  
  import android.content.pm.ApplicationInfo;
  import android.content.pm.SharedLibraryInfo;
  import android.util.Slog;
  import android.util.SparseArray;
  
  import com.android.internal.os.ClassLoaderFactory;
  
  import java.io.File;
  import java.util.List;
  
  public final class DexoptUtils {
      private static final String TAG = "DexoptUtils";
  
      // Shared libraries have more or less followed PCL behavior due to the way
      // they were added to the classpath pre Q.
      private static final String SHARED_LIBRARY_LOADER_TYPE =
              ClassLoaderFactory.getPathClassLoaderName();
  
      private DexoptUtils() {}
  
      /**
       * Creates the class loader context dependencies for each of the application code paths.
       * The returned array contains the class loader contexts that needs to be passed to dexopt in
       * order to ensure correct optimizations. "Code" paths with no actual code, as specified by
       * {@param pathsWithCode}, are ignored and will have null as their context in the returned array
       * (configuration splits are an example of paths without code).
       *
       * A class loader context describes how the class loader chain should be built by dex2oat
       * in order to ensure that classes are resolved during compilation as they would be resolved
       * at runtime. The context will be encoded in the compiled code. If at runtime the dex file is
       * loaded in a different context (with a different set of class loaders or a different
       * classpath), the compiled code will be rejected.
       *
       * Note that the class loader context only includes dependencies and not the code path itself.
       * The contexts are created based on the application split dependency list and
       * the provided shared libraries.
       *
       * All the code paths encoded in the context will be relative to the base directory. This
       * enables stage compilation where compiler artifacts may be moved around.
       *
       * The result is indexed as follows:
       *   - index 0 contains the context for the base apk
       *   - index 1 to n contain the context for the splits in the order determined by
       *     {@code info.getSplitCodePaths()}
       *
       * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk}
       * and pay attention to the way the classpath is created for the non isolated mode in:
       * {@link android.app.LoadedApk#makePaths(
       * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}.
       */
      public static String[] getClassLoaderContexts(ApplicationInfo info,
              List<SharedLibraryInfo> sharedLibraries, boolean[] pathsWithCode) {
          // The base class loader context contains only the shared library.
          String sharedLibrariesContext = "";
          if (sharedLibraries != null) {
              sharedLibrariesContext = encodeSharedLibraries(sharedLibraries);
          }
  
          String baseApkContextClassLoader = encodeClassLoader(
                  "", info.classLoaderName, sharedLibrariesContext);
          if (info.getSplitCodePaths() == null) {
              // The application has no splits.
              return new String[] {baseApkContextClassLoader};
          }
  
          // The application has splits. Compute their class loader contexts.
  
          // First, cache the relative paths of the splits and do some sanity checks
          String[] splitRelativeCodePaths = getSplitRelativeCodePaths(info);
  
          // The splits have an implicit dependency on the base apk.
          // This means that we have to add the base apk file in addition to the shared libraries.
          String baseApkName = new File(info.getBaseCodePath()).getName();
          String baseClassPath = baseApkName;
  
          // The result is stored in classLoaderContexts.
          // Index 0 is the class loader context for the base apk.
          // Index `i` is the class loader context encoding for split `i`.
          String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length];
          classLoaderContexts[0] = pathsWithCode[0] ? baseApkContextClassLoader : null;
  
          if (!info.requestsIsolatedSplitLoading() || info.splitDependencies == null) {
              // If the app didn't request for the splits to be loaded in isolation or if it does not
              // declare inter-split dependencies, then all the splits will be loaded in the base
              // apk class loader (in the order of their definition).
              String classpath = baseClassPath;
              for (int i = 1; i < classLoaderContexts.length; i++) {
                  if (pathsWithCode[i]) {
                      classLoaderContexts[i] = encodeClassLoader(
                              classpath, info.classLoaderName, sharedLibrariesContext);
                  } else {
                      classLoaderContexts[i] = null;
                  }
                  // Note that the splits with no code are not removed from the classpath computation.
                  // i.e. split_n might get the split_n-1 in its classpath dependency even
                  // if split_n-1 has no code.
                  // The splits with no code do not matter for the runtime which ignores
                  // apks without code when doing the classpath checks. As such we could actually
                  // filter them but we don't do it in order to keep consistency with how the apps
                  // are loaded.
                  classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]);
              }
          } else {
              // In case of inter-split dependencies, we need to walk the dependency chain of each
              // split. We do this recursively and store intermediate results in classLoaderContexts.
  
              // First, look at the split class loaders and cache their individual contexts (i.e.
              // the class loader + the name of the split). This is an optimization to avoid
              // re-computing them during the recursive call.
              // The cache is stored in splitClassLoaderEncodingCache. The difference between this and
              // classLoaderContexts is that the later contains the full chain of class loaders for
              // a given split while splitClassLoaderEncodingCache only contains a single class loader
              // encoding.
              String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length];
              for (int i = 0; i < splitRelativeCodePaths.length; i++) {
                  splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i],
                          info.splitClassLoaderNames[i]);
              }
              String splitDependencyOnBase = encodeClassLoader(
                      baseClassPath, info.classLoaderName);
              SparseArray<int[]> splitDependencies = info.splitDependencies;
  
              // Note that not all splits have dependencies (e.g. configuration splits)
              // The splits without dependencies will have classLoaderContexts[config_split_index]
              // set to null after this step.
              for (int i = 1; i < splitDependencies.size(); i++) {
                  int splitIndex = splitDependencies.keyAt(i);
                  if (pathsWithCode[splitIndex]) {
                      // Compute the class loader context only for the splits with code.
                      getParentDependencies(splitIndex, splitClassLoaderEncodingCache,
                              splitDependencies, classLoaderContexts, splitDependencyOnBase);
                  }
              }
  
              // At this point classLoaderContexts contains only the parent dependencies.
              // We also need to add the class loader of the current split which should
              // come first in the context.
              for (int i = 1; i < classLoaderContexts.length; i++) {
                  String splitClassLoader = encodeClassLoader("", info.splitClassLoaderNames[i - 1]);
                  if (pathsWithCode[i]) {
                      // If classLoaderContexts[i] is null it means that the split does not have
                      // any dependency. In this case its context equals its declared class loader.
                      classLoaderContexts[i] = classLoaderContexts[i] == null
                              ? splitClassLoader
                              : encodeClassLoaderChain(splitClassLoader, classLoaderContexts[i])
                                      + sharedLibrariesContext;
                  } else {
                      // This is a split without code, it has no dependency and it is not compiled.
                      // Its context will be null.
                      classLoaderContexts[i] = null;
                  }
              }
          }
  
          return classLoaderContexts;
      }
  
      /**
       * Creates the class loader context for the given shared library.
       */
      public static String getClassLoaderContext(SharedLibraryInfo info) {
          String sharedLibrariesContext = "";
          if (info.getDependencies() != null) {
              sharedLibrariesContext = encodeSharedLibraries(info.getDependencies());
          }
          return encodeClassLoader(
                  "", SHARED_LIBRARY_LOADER_TYPE, sharedLibrariesContext);
      }
  
      /**
       * Recursive method to generate the class loader context dependencies for the split with the
       * given index. {@param classLoaderContexts} acts as an accumulator. Upton return
       * {@code classLoaderContexts[index]} will contain the split dependency.
       * During computation, the method may resolve the dependencies of other splits as it traverses
       * the entire parent chain. The result will also be stored in {@param classLoaderContexts}.
       *
       * Note that {@code index 0} denotes the base apk and it is special handled. When the
       * recursive call hits {@code index 0} the method returns {@code splitDependencyOnBase}.
       * {@code classLoaderContexts[0]} is not modified in this method.
       *
       * @param index the index of the split (Note that index 0 denotes the base apk)
       * @param splitClassLoaderEncodingCache the class loader encoding for the individual splits.
       *    It contains only the split class loader and not the the base. The split
       *    with {@code index} has its context at {@code splitClassLoaderEncodingCache[index - 1]}.
       * @param splitDependencies the dependencies for all splits. Note that in this array index 0
       *    is the base and splits start from index 1.
       * @param classLoaderContexts the result accumulator. index 0 is the base and never set. Splits
       *    start at index 1.
       * @param splitDependencyOnBase the encoding of the implicit split dependency on base.
       */
      private static String getParentDependencies(int index, String[] splitClassLoaderEncodingCache,
              SparseArray<int[]> splitDependencies, String[] classLoaderContexts,
              String splitDependencyOnBase) {
          // If we hit the base apk return its custom dependency list which is
          // sharedLibraries + base.apk
          if (index == 0) {
              return splitDependencyOnBase;
          }
          // Return the result if we've computed the splitDependencies for this index already.
          if (classLoaderContexts[index] != null) {
              return classLoaderContexts[index];
          }
          // Get the splitDependencies for the parent of this index and append its path to it.
          int parent = splitDependencies.get(index)[0];
          String parentDependencies = getParentDependencies(parent, splitClassLoaderEncodingCache,
                  splitDependencies, classLoaderContexts, splitDependencyOnBase);
  
          // The split context is: `parent context + parent dependencies context`.
          String splitContext = (parent == 0) ?
                  parentDependencies :
                  encodeClassLoaderChain(splitClassLoaderEncodingCache[parent - 1], parentDependencies);
          classLoaderContexts[index] = splitContext;
          return splitContext;
      }
  
      private static String encodeSharedLibrary(SharedLibraryInfo sharedLibrary) {
          List<String> paths = sharedLibrary.getAllCodePaths();
          String classLoaderSpec = encodeClassLoader(
                  encodeClasspath(paths.toArray(new String[paths.size()])),
                  SHARED_LIBRARY_LOADER_TYPE);
          if (sharedLibrary.getDependencies() != null) {
              classLoaderSpec += encodeSharedLibraries(sharedLibrary.getDependencies());
          }
          return classLoaderSpec;
      }
  
      private static String encodeSharedLibraries(List<SharedLibraryInfo> sharedLibraries) {
          String sharedLibrariesContext = "{";
          boolean first = true;
          for (SharedLibraryInfo info : sharedLibraries) {
              if (!first) {
                  sharedLibrariesContext += "#";
              }
              first = false;
              sharedLibrariesContext += encodeSharedLibrary(info);
          }
          sharedLibrariesContext += "}";
          return sharedLibrariesContext;
      }
  
      /**
       * Encodes the shared libraries classpathElements in a format accepted by dexopt.
       * NOTE: Keep this in sync with the dexopt expectations! Right now that is
       * a list separated by ':'.
       */
      private static String encodeClasspath(String[] classpathElements) {
          if (classpathElements == null || classpathElements.length == 0) {
              return "";
          }
          StringBuilder sb = new StringBuilder();
          for (String element : classpathElements) {
              if (sb.length() != 0) {
                  sb.append(":");
              }
              sb.append(element);
          }
          return sb.toString();
      }
  
      /**
       * Adds an element to the encoding of an existing classpath.
       * {@see PackageDexOptimizer.encodeClasspath(String[])}
       */
      private static String encodeClasspath(String classpath, String newElement) {
          return classpath.isEmpty() ? newElement : (classpath + ":" + newElement);
      }
  
      /**
       * Encodes a single class loader dependency starting from {@param path} and
       * {@param classLoaderName}.
       * NOTE: Keep this in sync with the dexopt expectations! Right now that is either "PCL[path]"
       * for a PathClassLoader or "DLC[path]" for a DelegateLastClassLoader.
       */
      /*package*/ static String encodeClassLoader(String classpath, String classLoaderName) {
          classpath.getClass();  // Throw NPE if classpath is null
          String classLoaderDexoptEncoding = classLoaderName;
          if (ClassLoaderFactory.isPathClassLoaderName(classLoaderName)) {
              classLoaderDexoptEncoding = "PCL";
          } else if (ClassLoaderFactory.isDelegateLastClassLoaderName(classLoaderName)) {
              classLoaderDexoptEncoding = "DLC";
          } else {
              Slog.wtf(TAG, "Unsupported classLoaderName: " + classLoaderName);
          }
          return classLoaderDexoptEncoding + "[" + classpath + "]";
      }
  
      /**
       * Same as above, but appends {@param sharedLibraries} to the result.
       */
      private static String encodeClassLoader(String classpath, String classLoaderName,
              String sharedLibraries) {
          return encodeClassLoader(classpath, classLoaderName) + sharedLibraries;
      }
  
      /**
       * Links to dependencies together in a format accepted by dexopt.
       * For the special case when either of cl1 or cl2 equals
       * NOTE: Keep this in sync with the dexopt expectations! Right now that is a list of split
       * dependencies {@see encodeClassLoader} separated by ';'.
       */
      /*package*/ static String encodeClassLoaderChain(String cl1, String cl2) {
          if (cl1.isEmpty()) return cl2;
          if (cl2.isEmpty()) return cl1;
          return cl1 + ";" + cl2;
      }
  
      /**
       * Compute the class loader context for the dex files present in the classpath of the first
       * class loader from the given list (referred in the code as the {@code loadingClassLoader}).
       * Each dex files gets its own class loader context in the returned array.
       *
       * Example:
       *    If classLoadersNames = {"dalvik.system.DelegateLastClassLoader",
       *    "dalvik.system.PathClassLoader"} and classPaths = {"foo.dex:bar.dex", "other.dex"}
       *    The output will be
       *    {"DLC[];PCL[other.dex]", "DLC[foo.dex];PCL[other.dex]"}
       *    with "DLC[];PCL[other.dex]" being the context for "foo.dex"
       *    and "DLC[foo.dex];PCL[other.dex]" the context for "bar.dex".
       *
       * If any of the class loaders names is unsupported the method will return null.
       *
       * The argument lists must be non empty and of the same size.
       *
       * @param classLoadersNames the names of the class loaders present in the loading chain. The
       *    list encodes the class loader chain in the natural order. The first class loader has
       *    the second one as its parent and so on.
       * @param classPaths the class paths for the elements of {@param classLoadersNames}. The
       *     the first element corresponds to the first class loader and so on. A classpath is
       *     represented as a list of dex files separated by {@code File.pathSeparator}.
       *     The return context will be for the dex files found in the first class path.
       */
      /*package*/ static String[] processContextForDexLoad(List<String> classLoadersNames,
              List<String> classPaths) {
          if (classLoadersNames.size() != classPaths.size()) {
              throw new IllegalArgumentException(
                      "The size of the class loader names and the dex paths do not match.");
          }
          if (classLoadersNames.isEmpty()) {
              throw new IllegalArgumentException("Empty classLoadersNames");
          }
  
          // Compute the context for the parent class loaders.
          String parentContext = "";
          // We know that these lists are actually ArrayLists so getting the elements by index
          // is fine (they come over binder). Even if something changes we expect the sizes to be
          // very small and it shouldn't matter much.
          for (int i = 1; i < classLoadersNames.size(); i++) {
              if (!ClassLoaderFactory.isValidClassLoaderName(classLoadersNames.get(i))
                  || classPaths.get(i) == null) {
                  return null;
              }
              String classpath = encodeClasspath(classPaths.get(i).split(File.pathSeparator));
              parentContext = encodeClassLoaderChain(parentContext,
                      encodeClassLoader(classpath, classLoadersNames.get(i)));
          }
  
          // Now compute the class loader context for each dex file from the first classpath.
          String loadingClassLoader = classLoadersNames.get(0);
          if (!ClassLoaderFactory.isValidClassLoaderName(loadingClassLoader)) {
              return null;
          }
          String[] loadedDexPaths = classPaths.get(0).split(File.pathSeparator);
          String[] loadedDexPathsContext = new String[loadedDexPaths.length];
          String currentLoadedDexPathClasspath = "";
          for (int i = 0; i < loadedDexPaths.length; i++) {
              String dexPath = loadedDexPaths[i];
              String currentContext = encodeClassLoader(
                      currentLoadedDexPathClasspath, loadingClassLoader);
              loadedDexPathsContext[i] = encodeClassLoaderChain(currentContext, parentContext);
              currentLoadedDexPathClasspath = encodeClasspath(currentLoadedDexPathClasspath, dexPath);
          }
          return loadedDexPathsContext;
      }
  
      /**
       * Returns the relative paths of the splits declared by the application {@code info}.
       * Assumes that the application declares a non-null array of splits.
       */
      private static String[] getSplitRelativeCodePaths(ApplicationInfo info) {
          String baseCodePath = new File(info.getBaseCodePath()).getParent();
          String[] splitCodePaths = info.getSplitCodePaths();
          String[] splitRelativeCodePaths = new String[splitCodePaths.length];
          for (int i = 0; i < splitCodePaths.length; i++) {
              File pathFile = new File(splitCodePaths[i]);
              splitRelativeCodePaths[i] = pathFile.getName();
              // Sanity check that the base paths of the splits are all the same.
              String basePath = pathFile.getParent();
              if (!basePath.equals(baseCodePath)) {
                  Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
                          baseCodePath);
              }
          }
          return splitRelativeCodePaths;
      }
  }
  