/**
* 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
* {@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(
// 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
// apk class loader (in the order of their definition).
String classpath = sharedLibrariesAndBaseClassPath;
for (int i = 1; i < classLoaderContexts.length; i++) {
- classLoaderContexts[i] = encodeClassLoader(classpath, "dalvik.system.PathClassLoader");
+ classLoaderContexts[i] = pathsWithCode[i]
+ ? encodeClassLoader(classpath, "dalvik.system.PathClassLoader") : 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 {
String splitDependencyOnBase = encodeClassLoader(
sharedLibrariesAndBaseClassPath, "dalvik.system.PathClassLoader");
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.
// come first in the context.
for (int i = 1; i < classLoaderContexts.length; i++) {
String splitClassLoader = encodeClassLoader("", "dalvik.system.PathClassLoader");
- 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;
+ }
}
}
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.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"});
+
+ String[] splitClassLoaderNames = new String[]{
+ PATH_CLASS_LOADER_NAME,
+ PATH_CLASS_LOADER_NAME,
+ PATH_CLASS_LOADER_NAME,
+ PATH_CLASS_LOADER_NAME,
+ PATH_CLASS_LOADER_NAME,
+ 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<>(6 + 1);
+ ai.splitDependencies = new SparseArray<>(splitClassLoaderNames.length + 1);
ai.splitDependencies.put(0, new int[] {-1}); // base has no dependency
ai.splitDependencies.put(1, new int[] {2}); // split 1 depends on 2
ai.splitDependencies.put(2, new int[] {4}); // split 2 depends on 4
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("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]",
contexts[1]);
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]);
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]);
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("PCL[]", contexts[0]);
assertEquals("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[base.dex]", contexts[1]);
assertEquals("PCL[];PCL[base-4.dex];PCL[base.dex]", contexts[2]);
assertEquals("PCL[];PCL[base.dex]", contexts[4]);
assertEquals("PCL[];PCL[base.dex]", contexts[5]);
assertEquals("PCL[];PCL[base-5.dex];PCL[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("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]);
assertEquals("PCL[];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[2]);
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]);
@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]);
@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("PCL[a.dex:b.dex]", contexts[0]);
@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]);
@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("PCL[]", 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("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]", contexts[1]);
+ assertEquals("PCL[];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<String> classLoaders = Arrays.asList(
DELEGATE_LAST_CLASS_LOADER_NAME,
String[] context = DexoptUtils.processContextForDexLoad(classLoaders, classPaths);
assertNotNull(context);
assertEquals(2, context.length);
- assertEquals("DLC[];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[0]);
- assertEquals("DLC[foo.dex];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[1]);
+ assertEquals("PCL[];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[0]);
+ assertEquals("PCL[foo.dex];PCL[parent1.dex];PCL[parent2.dex:parent3.dex]", context[1]);
}
@Test