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

Commit e534b2c6 authored by Calin Juravle's avatar Calin Juravle Committed by Andreas Gampe
Browse files

Fix splits class loader context for non dependant splits

If the app doesn't request for splits to be loaded in isolation or does
not declare inter-split dependencies, then all the splits are loaded in
the base apk class loader (in the order of they are defined).

Fix the class loader context passed to dex2oat to reflect the runtime
loading logic.

(cherry picked from commit 305aeea3)

Bug: 38138251
Test: runtest -x
services/tests/servicestests/src/com/android/server/pm/dex/DexoptUtilsTest.java

Merged-In: Ia0623d38883ae244fd16c0afb053fef016bf260a
Change-Id: Ia0623d38883ae244fd16c0afb053fef016bf260a
parent 0a267a88
Loading
Loading
Loading
Loading
+55 −34
Original line number Diff line number Diff line
@@ -16,12 +16,12 @@

package com.android.server.pm.dex;


import android.content.pm.ApplicationInfo;
import android.util.Slog;
import android.util.SparseArray;

import java.io.File;
import java.util.List;

public final class DexoptUtils {
    private static final String TAG = "DexoptUtils";
@@ -50,6 +50,11 @@ public final class DexoptUtils {
     *   - index 0 contains the context for the base apk
     *   - index 1 to n contain the context for the splits in the order determined by
     *     {@code info.getSplitCodePaths()}
     *
     * IMPORTANT: keep this logic in sync with the loading code in {@link android.app.LoadedApk}
     * and pay attention to the way the classpath is created for the non isolated mode in:
     * {@link android.app.LoadedApk#makePaths(
     * android.app.ActivityThread, boolean, ApplicationInfo, List, List)}.
     */
    public static String[] getClassLoaderContexts(ApplicationInfo info, String[] sharedLibraries) {
        // The base class loader context contains only the shared library.
@@ -57,35 +62,36 @@ public final class DexoptUtils {
        String baseApkContextClassLoader = encodeClassLoader(
                sharedLibrariesClassPath, "dalvik.system.PathClassLoader");

        String[] splitCodePaths = info.getSplitCodePaths();

        if (splitCodePaths == null) {
        if (info.getSplitCodePaths() == null) {
            // The application has no splits.
            return new String[] {baseApkContextClassLoader};
        }

        // The application has splits. Compute their class loader contexts.

        // First, cache the relative paths of the splits and do some sanity checks
        String[] splitRelativeCodePaths = getSplitRelativeCodePaths(info);

        // The splits have an implicit dependency on the base apk.
        // This means that we have to add the base apk file in addition to the shared libraries.
        String baseApkName = new File(info.getBaseCodePath()).getName();
        String splitDependencyOnBase = encodeClassLoader(
                encodeClasspath(sharedLibrariesClassPath, baseApkName),
                "dalvik.system.PathClassLoader");
        String sharedLibrariesAndBaseClassPath =
                encodeClasspath(sharedLibrariesClassPath, baseApkName);

        // The result is stored in classLoaderContexts.
        // 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 + splitCodePaths.length];
        String[] classLoaderContexts = new String[/*base apk*/ 1 + splitRelativeCodePaths.length];
        classLoaderContexts[0] = baseApkContextClassLoader;

        SparseArray<int[]> splitDependencies = info.splitDependencies;

        if (splitDependencies == null) {
            // If there are no inter-split dependencies, populate the result with the implicit
            // dependency on the base apk.
        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
            // declare inter-split dependencies, then all the splits will be loaded in the base
            // apk class loader (in the order of their definition).
            String classpath = sharedLibrariesAndBaseClassPath;
            for (int i = 1; i < classLoaderContexts.length; i++) {
                classLoaderContexts[i] = splitDependencyOnBase;
                classLoaderContexts[i] = encodeClassLoader(classpath, "dalvik.system.PathClassLoader");
                classpath = encodeClasspath(classpath, splitRelativeCodePaths[i - 1]);
            }
        } else {
            // In case of inter-split dependencies, we need to walk the dependency chain of each
@@ -98,25 +104,18 @@ public final class DexoptUtils {
            // classLoaderContexts is that the later contains the full chain of class loaders for
            // a given split while splitClassLoaderEncodingCache only contains a single class loader
            // encoding.
            String baseCodePath = new File(info.getBaseCodePath()).getParent();
            String[] splitClassLoaderEncodingCache = new String[splitCodePaths.length];
            for (int i = 0; i < splitCodePaths.length; i++) {
                File pathFile = new File(splitCodePaths[i]);
                String fileName = pathFile.getName();
                splitClassLoaderEncodingCache[i] = encodeClassLoader(fileName,
            String[] splitClassLoaderEncodingCache = new String[splitRelativeCodePaths.length];
            for (int i = 0; i < splitRelativeCodePaths.length; i++) {
                splitClassLoaderEncodingCache[i] = encodeClassLoader(splitRelativeCodePaths[i],
                        "dalvik.system.PathClassLoader");
                // Sanity check that the base paths of the splits are all the same.
                String basePath = pathFile.getParent();
                if (!basePath.equals(baseCodePath)) {
                    Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
                            baseCodePath);
                }
            }
            String splitDependencyOnBase = encodeClassLoader(
                    sharedLibrariesAndBaseClassPath, "dalvik.system.PathClassLoader");
            SparseArray<int[]> splitDependencies = info.splitDependencies;
            for (int i = 1; i < splitDependencies.size(); i++) {
                getParentDependencies(splitDependencies.keyAt(i), 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
@@ -126,6 +125,7 @@ public final class DexoptUtils {
                classLoaderContexts[i] = encodeClassLoaderChain(
                        splitClassLoader, classLoaderContexts[i]);
            }
        }

        return classLoaderContexts;
    }
@@ -227,4 +227,25 @@ public final class DexoptUtils {
    private static String encodeClassLoaderChain(String cl1, String cl2) {
        return cl1.isEmpty() ? cl2 : (cl1 + ";" + cl2);
    }

    /**
     * Returns the relative paths of the splits declared by the application {@code info}.
     * Assumes that the application declares a non-null array of splits.
     */
    private static String[] getSplitRelativeCodePaths(ApplicationInfo info) {
        String baseCodePath = new File(info.getBaseCodePath()).getParent();
        String[] splitCodePaths = info.getSplitCodePaths();
        String[] splitRelativeCodePaths = new String[splitCodePaths.length];
        for (int i = 0; i < splitCodePaths.length; i++) {
            File pathFile = new File(splitCodePaths[i]);
            splitRelativeCodePaths[i] = pathFile.getName();
            // Sanity check that the base paths of the splits are all the same.
            String basePath = pathFile.getParent();
            if (!basePath.equals(baseCodePath)) {
                Slog.wtf(TAG, "Split paths have different base paths: " + basePath + " and " +
                        baseCodePath);
            }
        }
        return splitRelativeCodePaths;
    }
}
+30 −7
Original line number Diff line number Diff line
@@ -41,6 +41,7 @@ public class DexoptUtilsTest {
        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;

        if (addSplits) {
            ai.setSplitCodePaths(new String[]{
@@ -73,7 +74,8 @@ public class DexoptUtilsTest {

        assertEquals(7, 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-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]);
@@ -89,14 +91,35 @@ public class DexoptUtilsTest {

        assertEquals(7, contexts.length);
        assertEquals("PCL[a.dex:b.dex]", contexts[0]);
        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[1]);
        assertEquals("PCL[];PCL[a.dex:b.dex:base.dex]", contexts[2]);
        assertEquals("PCL[];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[a.dex:b.dex:base.dex]", contexts[6]);
        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]", contexts[3]);
        assertEquals("PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]);
        assertEquals(
                "PCL[a.dex:b.dex:base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]",
                contexts[5]);
        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]);
    }

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

        assertEquals(7, 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]", contexts[3]);
        assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex]", contexts[4]);
        assertEquals("PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex]", contexts[5]);
        assertEquals(
                "PCL[base.dex:base-1.dex:base-2.dex:base-3.dex:base-4.dex:base-5.dex]",
                contexts[6]);
    }
    @Test
    public void testSplitChainNoSharedLibraries() {
        ApplicationInfo ai = createMockApplicationInfo(