diff --git a/core/java/android/content/pm/ApplicationInfo.java b/core/java/android/content/pm/ApplicationInfo.java index ad7a5ab10e41f..664bcbca6aba6 100644 --- a/core/java/android/content/pm/ApplicationInfo.java +++ b/core/java/android/content/pm/ApplicationInfo.java @@ -718,6 +718,10 @@ public class ApplicationInfo extends PackageItemInfo implements Parcelable { * Cycles do not exist because they are illegal and screened for during installation. * * May be null if no splits are installed, or if no dependencies exist between them. + * + * NOTE: Any change to the way split dependencies are stored must update the logic that + * creates the class loader context for dexopt (DexoptUtils#getClassLoaderContexts). + * * @hide */ public SparseArray splitDependencies; diff --git a/services/core/java/com/android/server/pm/PackageDexOptimizer.java b/services/core/java/com/android/server/pm/PackageDexOptimizer.java index 698d3871e3c51..0f580d818f914 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 @@ public class PackageDexOptimizer { // 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 @@ public class PackageDexOptimizer { 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 0196212d6bc29..ef2ee4a41555a 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 @@ public final class 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. + * 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 @@ public final class DexoptUtils { * {@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 @@ public final class DexoptUtils { // 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 @@ public final class DexoptUtils { // 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 @@ public final class DexoptUtils { String splitDependencyOnBase = encodeClassLoader( sharedLibrariesAndBaseClassPath, info.classLoaderName); SparseArray 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 @@ public final class DexoptUtils { // 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; + } } } diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java index 2c56a8263a8c0..c8c8eb1aa5c6e 100644 --- a/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java +++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java @@ -46,22 +46,35 @@ public class DexoptUtilsTest { private static final String DELEGATE_LAST_CLASS_LOADER_NAME = DelegateLastClassLoader.class.getName(); - private ApplicationInfo createMockApplicationInfo(String baseClassLoader, boolean addSplits, + private static class TestData { + ApplicationInfo info; + boolean[] pathsWithCode; + } + + private TestData createMockApplicationInfo(String baseClassLoader, boolean addSplits, boolean addSplitDependencies) { ApplicationInfo ai = new ApplicationInfo(); String codeDir = "/data/app/mock.android.com"; ai.setBaseCodePath(codeDir + "/base.dex"); ai.classLoaderName = baseClassLoader; ai.privateFlags = ai.privateFlags | ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING; + boolean[] pathsWithCode; + if (!addSplits) { + pathsWithCode = new boolean[] {true}; + } else { + pathsWithCode = new boolean[9]; + Arrays.fill(pathsWithCode, true); + pathsWithCode[7] = false; // config split - if (addSplits) { ai.setSplitCodePaths(new String[]{ codeDir + "/base-1.dex", codeDir + "/base-2.dex", codeDir + "/base-3.dex", codeDir + "/base-4.dex", codeDir + "/base-5.dex", - codeDir + "/base-6.dex"}); + codeDir + "/base-6.dex", + codeDir + "/config-split-7.dex", + codeDir + "/feature-no-deps.dex"}); ai.splitClassLoaderNames = new String[]{ DELEGATE_LAST_CLASS_LOADER_NAME, @@ -69,7 +82,9 @@ public class DexoptUtilsTest { PATH_CLASS_LOADER_NAME, DEX_CLASS_LOADER_NAME, PATH_CLASS_LOADER_NAME, - null}; // A null class loader name should default to PathClassLoader. + null, // A null class loader name should default to PathClassLoader. + null, // The config split gets a null class loader. + null}; // The feature split with no dependency and no specified class loader. if (addSplitDependencies) { ai.splitDependencies = new SparseArray<>(ai.splitClassLoaderNames.length + 1); ai.splitDependencies.put(0, new int[] {-1}); // base has no dependency @@ -79,18 +94,24 @@ public class DexoptUtilsTest { ai.splitDependencies.put(4, new int[] {0}); // split 4 depends on base ai.splitDependencies.put(5, new int[] {0}); // split 5 depends on base ai.splitDependencies.put(6, new int[] {5}); // split 6 depends on 5 + // Do not add the config split to the dependency list. + // Do not add the feature split with no dependency to the dependency list. } } - return ai; + TestData data = new TestData(); + data.info = ai; + data.pathsWithCode = pathsWithCode; + return data; } @Test public void testSplitChain() { - ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); - assertEquals(7, contexts.length); + assertEquals(9, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); @@ -99,15 +120,18 @@ public class DexoptUtilsTest { assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals("PCL[]", contexts[8]); // feature split with no dependency } @Test public void testSplitChainNoSplitDependencies() { - ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false); + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, false); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); - assertEquals(7, contexts.length); + assertEquals(9, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); assertEquals("PCL[a.dex:b.dex:base.dex]", contexts[1]); assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex]", contexts[2]); @@ -119,15 +143,21 @@ public class DexoptUtilsTest { assertEquals( "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals( + "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex:base-6.dex:config-split-7.dex]", + contexts[8]); // feature split with no dependency } @Test public void testSplitChainNoIsolationNoSharedLibrary() { - ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); - ai.privateFlags = ai.privateFlags & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING); - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, true, true); + data.info.privateFlags = data.info.privateFlags + & (~ApplicationInfo.PRIVATE_FLAG_ISOLATED_SPLIT_LOADING); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); - assertEquals(7, contexts.length); + assertEquals(9, contexts.length); assertEquals("PCL[]", contexts[0]); assertEquals("PCL[base.dex]", contexts[1]); assertEquals("PCL[base.dex:base-1.dex]", contexts[2]); @@ -137,14 +167,20 @@ public class DexoptUtilsTest { assertEquals( "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals( + "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex:base-6.dex:config-split-7.dex]", + contexts[8]); // feature split with no dependency } + @Test public void testSplitChainNoSharedLibraries() { - ApplicationInfo ai = createMockApplicationInfo( + TestData data = createMockApplicationInfo( DELEGATE_LAST_CLASS_LOADER_NAME, true, true); - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); - assertEquals(7, contexts.length); + assertEquals(9, contexts.length); assertEquals("DLC[]", contexts[0]); assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];DLC[base.dex]", contexts[1]); assertEquals("DLC[];PCL[base-4.dex];DLC[base.dex]", contexts[2]); @@ -152,16 +188,19 @@ public class DexoptUtilsTest { assertEquals("PCL[];DLC[base.dex]", contexts[4]); assertEquals("PCL[];DLC[base.dex]", contexts[5]); assertEquals("PCL[];PCL[base-5.dex];DLC[base.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals("PCL[]", contexts[8]); // feature split with no dependency } @Test public void testSplitChainWithNullPrimaryClassLoader() { // A null classLoaderName should mean PathClassLoader. - ApplicationInfo ai = createMockApplicationInfo(null, true, true); + TestData data = createMockApplicationInfo(null, true, true); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); - assertEquals(7, contexts.length); + assertEquals(9, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); assertEquals("DLC[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); @@ -169,13 +208,16 @@ public class DexoptUtilsTest { assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + assertEquals(null, contexts[7]); // config split + assertEquals("PCL[]", contexts[8]); // feature split with no dependency } @Test public void tesNoSplits() { - ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); assertEquals(1, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); @@ -183,9 +225,10 @@ public class DexoptUtilsTest { @Test public void tesNoSplitsNullClassLoaderName() { - ApplicationInfo ai = createMockApplicationInfo(null, false, false); + TestData data = createMockApplicationInfo(null, false, false); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); assertEquals(1, contexts.length); assertEquals("PCL[a.dex:b.dex]", contexts[0]); @@ -193,10 +236,11 @@ public class DexoptUtilsTest { @Test public void tesNoSplitDelegateLast() { - ApplicationInfo ai = createMockApplicationInfo( + TestData data = createMockApplicationInfo( DELEGATE_LAST_CLASS_LOADER_NAME, false, false); String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, sharedLibrary); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); assertEquals(1, contexts.length); assertEquals("DLC[a.dex:b.dex]", contexts[0]); @@ -204,8 +248,9 @@ public class DexoptUtilsTest { @Test public void tesNoSplitsNoSharedLibraries() { - ApplicationInfo ai = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + TestData data = createMockApplicationInfo(PATH_CLASS_LOADER_NAME, false, false); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); assertEquals(1, contexts.length); assertEquals("PCL[]", contexts[0]); @@ -213,14 +258,54 @@ public class DexoptUtilsTest { @Test public void tesNoSplitDelegateLastNoSharedLibraries() { - ApplicationInfo ai = createMockApplicationInfo( + TestData data = createMockApplicationInfo( DELEGATE_LAST_CLASS_LOADER_NAME, false, false); - String[] contexts = DexoptUtils.getClassLoaderContexts(ai, null); + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, null, data.pathsWithCode); assertEquals(1, contexts.length); assertEquals("DLC[]", contexts[0]); } + @Test + public void testContextWithNoCode() { + TestData data = createMockApplicationInfo(null, true, false); + Arrays.fill(data.pathsWithCode, false); + + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals(null, contexts[0]); + assertEquals(null, contexts[1]); + assertEquals(null, contexts[2]); + assertEquals(null, contexts[3]); + assertEquals(null, contexts[4]); + assertEquals(null, contexts[5]); + assertEquals(null, contexts[6]); + assertEquals(null, contexts[7]); + } + + @Test + public void testContextBaseNoCode() { + TestData data = createMockApplicationInfo(null, true, true); + data.pathsWithCode[0] = false; + String[] sharedLibrary = new String[] {"a.dex", "b.dex"}; + String[] contexts = DexoptUtils.getClassLoaderContexts( + data.info, sharedLibrary, data.pathsWithCode); + + assertEquals(9, contexts.length); + assertEquals(null, contexts[0]); + assertEquals("DLC[];DLC[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]); + assertEquals("DLC[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]); + assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[3]); + assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[4]); + assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[5]); + assertEquals("PCL[];PCL[base-5.dex];PCL[a.dex:b.dex:base.dex]", contexts[6]); + assertEquals(null, contexts[7]); + } + @Test public void testProcessContextForDexLoad() { List classLoaders = Arrays.asList(