Merge "Handle configuration splits when creating the class loader context" into oc-mr1-dev

am: ed54b41e3c

Change-Id: I34d37f77255226ec46bb833b64d8196b746f413e
This commit is contained in:
Calin Juravle
2017-09-09 00:00:14 +00:00
committed by android-build-merger
4 changed files with 169 additions and 42 deletions

View File

@@ -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<int[]> splitDependencies;

View File

@@ -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) {

View File

@@ -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<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 @@ 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;
}
}
}

View File

@@ -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<String> classLoaders = Arrays.asList(