Handle configuration splits when creating the class loader context

Configuration splits have no dependencies which can lead to exceptions
when computing their class loader context.

In general, we do not need to compute the class loader context for apks
without code.

This CL addresses the issue by ignoring "code" paths with no actual code.

Bug: 65159159
Test: adb install-multiple config_splits
      runtest -x
services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java

Change-Id: Ida1eb901eecba4a4266de73022f6ee4659367873
diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
index 698d387..0f580d8 100644
--- a/services/core/java/com/android/server/pm/PackageDexOptimizer.java
+++ b/services/core/java/com/android/server/pm/PackageDexOptimizer.java
@@ -147,8 +147,13 @@
         // Get the class loader context dependencies.
         // For each code path in the package, this array contains the class loader context that
         // needs to be passed to dexopt in order to ensure correct optimizations.
+        boolean[] pathsWithCode = new boolean[paths.size()];
+        pathsWithCode[0] = (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) != 0;
+        for (int i = 1; i < paths.size(); i++) {
+            pathsWithCode[i] = (pkg.splitFlags[i - 1] & ApplicationInfo.FLAG_HAS_CODE) != 0;
+        }
         String[] classLoaderContexts = DexoptUtils.getClassLoaderContexts(
-                pkg.applicationInfo, sharedLibraries);
+                pkg.applicationInfo, sharedLibraries, pathsWithCode);
 
         // Sanity check that we do not call dexopt with inconsistent data.
         if (paths.size() != classLoaderContexts.length) {
@@ -164,10 +169,15 @@
         int result = DEX_OPT_SKIPPED;
         for (int i = 0; i < paths.size(); i++) {
             // Skip paths that have no code.
-            if ((i == 0 && (pkg.applicationInfo.flags & ApplicationInfo.FLAG_HAS_CODE) == 0) ||
-                    (i != 0 && (pkg.splitFlags[i - 1] & ApplicationInfo.FLAG_HAS_CODE) == 0)) {
+            if (!pathsWithCode[i]) {
                 continue;
             }
+            if (classLoaderContexts[i] == null) {
+                throw new IllegalStateException("Inconsistent information in the "
+                        + "package structure. A split is marked to contain code "
+                        + "but has no dependency listed. Index=" + i + " path=" + paths.get(i));
+            }
+
             // Append shared libraries with split dependencies for this split.
             String path = paths.get(i);
             if (options.getSplitName() != null) {
diff --git a/services/core/java/com/android/server/pm/dex/DexoptUtils.java b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
index 0196212..ef2ee4a 100644
--- a/services/core/java/com/android/server/pm/dex/DexoptUtils.java
+++ b/services/core/java/com/android/server/pm/dex/DexoptUtils.java
@@ -35,7 +35,9 @@
     /**
      * 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.
+     * 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
@@ -60,7 +62,8 @@
      * {@link android.app.LoadedApk#makePaths(
      * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}.
      */
-    public static String[] getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries) {
+    public static String[] getClassLoaderContexts(ApplicationInfo info,
+            String[] sharedLibraries, boolean[] pathsWithCode) {
         // The base class loader context contains only the shared library.
         String sharedLibrariesClassPath = encodeClasspath(sharedLibraries);
         String baseApkContextClassLoader = encodeClassLoader(
@@ -86,7 +89,7 @@
         // Index 0 is the class loaded 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] = baseApkContextClassLoader;
+        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
@@ -94,7 +97,15 @@
             // apk class loader (in the order of their definition).
             String classpath = sharedLibrariesAndBaseClassPath;
             for (int i = 1; i < classLoaderContexts.length; i++) {
-                classLoaderContexts[i] = encodeClassLoader(classpath, info.classLoaderName);
+                classLoaderContexts[i] = pathsWithCode[i]
+                        ? encodeClassLoader(classpath, info.classLoaderName) : 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 {
@@ -116,9 +127,17 @@
             String splitDependencyOnBase = encodeClassLoader(
                     sharedLibrariesAndBaseClassPath, 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++) {
-                getParentDependencies(splitDependencies.keyAt(i), splitClassLoaderEncodingCache,
-                        splitDependencies, classLoaderContexts, splitDependencyOnBase);
+                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.
@@ -126,8 +145,17 @@
             // come first in the context.
             for (int i = 1; i < classLoaderContexts.length; i++) {
                 String splitClassLoader = encodeClassLoader("", info.splitClassLoaderNames[i - 1]);
-                classLoaderContexts[i] = encodeClassLoaderChain(
-                        splitClassLoader, classLoaderContexts[i]);
+                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]);
+                } else {
+                    // This is a split without code, it has no dependency and it is not compiled.
+                    // Its context will be null.
+                    classLoaderContexts[i] = null;
+                }
             }
         }