Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit ee41710e authored by Calin Juravle's avatar Calin Juravle Committed by android-build-merger
Browse files

Merge "Handle configuration splits when creating the class loader context"

am: 88946d4c

Change-Id: I1ed3175ef2094979477dca44b5f0bf56eb34d880
parents 1c6eeaec 88946d4c
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -688,6 +688,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;
+13 −3
Original line number Diff line number Diff line
@@ -159,8 +159,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) {
@@ -176,10 +181,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) {
+36 −8
Original line number Diff line number Diff line
@@ -33,7 +33,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
@@ -58,7 +60,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(
@@ -84,7 +87,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
@@ -92,7 +95,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, "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 {
@@ -114,18 +125,35 @@ public final class DexoptUtils {
            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,
                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("", "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;
                }
            }
        }

+126 −34
Original line number Diff line number Diff line
@@ -46,24 +46,46 @@ 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.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
@@ -71,18 +93,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("PCL[];PCL[base-2.dex];PCL[base-4.dex];PCL[a.dex:b.dex:base.dex]",
                contexts[1]);
@@ -91,15 +119,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]);
@@ -111,15 +142,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]);
@@ -129,14 +166,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("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]);
@@ -144,16 +187,19 @@ public class DexoptUtilsTest {
        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]);
@@ -161,13 +207,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]);
@@ -175,9 +224,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]);
@@ -185,10 +235,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("PCL[a.dex:b.dex]", contexts[0]);
@@ -196,8 +247,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]);
@@ -205,14 +257,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("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(
@@ -226,8 +318,8 @@ public class DexoptUtilsTest {
        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