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

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

Use the class loader context when optimizing secondary dex files

Record the class loader context for secondary dex loads and pass it to
dexopt during compilation.

The class loader context is passed from libcore every time a
BaseDexClassLoader is created and its recorded in the package dex usage
file.

Note that the context may be:
- unknown: if the dex file was not use after the the upgrade and its
context was not yet updated
- unsupported: if any of the class loaders from the loading context is
unsupported (only PathClassLoader and DelegateLastClassLoader are
supported).
- variable: if it changes over time, form one run to another.

In all the above cases the old compilation behavior is preserved for
now.(i.e. the dex file with be compiled with SKIP_SHARED_LIBRARY_CHECK)

(cherry picked from commit f1ff36f0)

Bug: 38138251
Test: runtest -x
services/tests/servicestests/src/com/android/server/pm/dex/
      adb shell cmd package compile -f -m quicken ^Csecondary-dex
com.google.android.gms

(cherry picked from commit 3bec94d7)

Merged-In: Ie8b78c7c0d5de43733b3d116f8dcb3a65324cca8
Change-Id: Ie8b78c7c0d5de43733b3d116f8dcb3a65324cca8
parent 916c7114
Loading
Loading
Loading
Loading
+8 −3
Original line number Diff line number Diff line
@@ -31,7 +31,7 @@ import libcore.io.Libcore;

import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
@@ -116,13 +116,18 @@ import java.util.Set;
        registerSecondaryDexForProfiling(dexPathsForRegistration);
    }

    private void notifyPackageManager(List<BaseDexClassLoader> ignored,
    private void notifyPackageManager(List<BaseDexClassLoader> classLoadersChain,
            List<String> classPaths) {
        // Get the class loader names for the binder call.
        List<String> classLoadersNames = new ArrayList<>(classPaths.size());
        for (BaseDexClassLoader bdc : classLoadersChain) {
            classLoadersNames.add(bdc.getClass().getName());
        }
        String packageName = ActivityThread.currentPackageName();
        try {
            // Notify only the paths of the first class loader for now.
            ActivityThread.getPackageManager().notifyDexLoad(
                    packageName, Arrays.asList(classPaths.get(0).split(File.pathSeparator)),
                    packageName, classLoadersNames, classPaths,
                    VMRuntime.getRuntime().vmInstructionSet());
        } catch (RemoteException re) {
            Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
+11 −3
Original line number Diff line number Diff line
@@ -469,11 +469,19 @@ interface IPackageManager {
     * Notify the package manager that a list of dex files have been loaded.
     *
     * @param loadingPackageName the name of the package who performs the load
     * @param dexPats the list of the dex files paths that have been loaded
     * @param classLoadersNames the names of the class loaders present in the loading chain. The
     *    list encodes the class loader chain in the natural order. The first class loader has
     *    the second one as its parent and so on. The dex files present in the class path of the
     *    first class loader will be recorded in the usage file.
     * @param classPaths the class paths corresponding to the class loaders names from
     *     {@param classLoadersNames}. The the first element corresponds to the first class loader
     *     and so on. A classpath is represented as a list of dex files separated by
     *     {@code File.pathSeparator}.
     *     The dex files found in the first class path will be recorded in the usage file.
     * @param loaderIsa the ISA of the loader process
     */
    oneway void notifyDexLoad(String loadingPackageName, in List<String> dexPaths,
            String loaderIsa);
    oneway void notifyDexLoad(String loadingPackageName, in List<String> classLoadersNames,
            in List<String> classPaths, String loaderIsa);

    /**
     * Register an application dex module with the package manager.
+30 −12
Original line number Diff line number Diff line
@@ -34,12 +34,12 @@ import com.android.internal.util.IndentingPrintWriter;
import com.android.server.pm.Installer.InstallerException;
import com.android.server.pm.dex.DexoptOptions;
import com.android.server.pm.dex.DexoptUtils;
import com.android.server.pm.dex.PackageDexUsage;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import dalvik.system.DexFile;

@@ -263,13 +263,12 @@ public class PackageDexOptimizer {
     * throwing exceptions). Or maybe make a separate call to installd to get DexOptNeeded, though
     * that seems wasteful.
     */
    public int dexOptSecondaryDexPath(ApplicationInfo info, String path, Set<String> isas,
            String compilerFilter, boolean isUsedByOtherApps, boolean downgrade) {
    public int dexOptSecondaryDexPath(ApplicationInfo info, String path,
            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
        synchronized (mInstallLock) {
            final long acquireTime = acquireWakeLockLI(info.uid);
            try {
                return dexOptSecondaryDexPathLI(info, path, isas, compilerFilter,
                        isUsedByOtherApps, downgrade);
                return dexOptSecondaryDexPathLI(info, path, dexUseInfo, options);
            } finally {
                releaseWakeLockLI(acquireTime);
            }
@@ -310,9 +309,16 @@ public class PackageDexOptimizer {
    }

    @GuardedBy("mInstallLock")
    private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path, Set<String> isas,
            String compilerFilter, boolean isUsedByOtherApps, boolean downgrade) {
        compilerFilter = getRealCompilerFilter(info, compilerFilter, isUsedByOtherApps);
    private int dexOptSecondaryDexPathLI(ApplicationInfo info, String path,
            PackageDexUsage.DexUseInfo dexUseInfo, DexoptOptions options) {
        if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) {
            // We are asked to optimize only the dex files used by other apps and this is not
            // on of them: skip it.
            return DEX_OPT_SKIPPED;
        }

        String compilerFilter = getRealCompilerFilter(info, options.getCompilerFilter(),
                dexUseInfo.isUsedByOtherApps());
        // Get the dexopt flags after getRealCompilerFilter to make sure we get the correct flags.
        // Secondary dex files are currently not compiled at boot.
        int dexoptFlags = getDexFlags(info, compilerFilter, /* bootComplete */ true)
@@ -329,20 +335,32 @@ public class PackageDexOptimizer {
            return DEX_OPT_FAILED;
        }
        Log.d(TAG, "Running dexopt on: " + path
                + " pkg=" + info.packageName + " isa=" + isas
                + " pkg=" + info.packageName + " isa=" + dexUseInfo.getLoaderIsas()
                + " dexoptFlags=" + printDexoptFlags(dexoptFlags)
                + " target-filter=" + compilerFilter);

        String classLoaderContext;
        if (dexUseInfo.isUnknownClassLoaderContext() ||
                dexUseInfo.isUnsupportedClassLoaderContext() ||
                dexUseInfo.isVariableClassLoaderContext()) {
            // If we have an unknown (not yet set), unsupported (custom class loaders), or a
            // variable class loader chain, compile without a context and mark the oat file with
            // SKIP_SHARED_LIBRARY_CHECK. Note that his might lead to a incorrect compilation.
            // TODO(calin): We should just extract in this case.
            classLoaderContext = SKIP_SHARED_LIBRARY_CHECK;
        } else {
            classLoaderContext = dexUseInfo.getClassLoaderContext();
        }
        try {
            for (String isa : isas) {
            for (String isa : dexUseInfo.getLoaderIsas()) {
                // Reuse the same dexopt path as for the primary apks. We don't need all the
                // arguments as some (dexopNeeded and oatDir) will be computed by installd because
                // system server cannot read untrusted app content.
                // TODO(calin): maybe add a separate call.
                mInstaller.dexopt(path, info.uid, info.packageName, isa, /*dexoptNeeded*/ 0,
                        /*oatDir*/ null, dexoptFlags,
                        compilerFilter, info.volumeUuid, SKIP_SHARED_LIBRARY_CHECK, info.seInfoUser,
                        downgrade);
                        compilerFilter, info.volumeUuid, classLoaderContext, info.seInfoUser,
                        options.isDowngrade());
            }

            return DEX_OPT_PERFORMED;
+3 −2
Original line number Diff line number Diff line
@@ -9461,7 +9461,8 @@ public class PackageManagerService extends IPackageManager.Stub
    }
    @Override
    public void notifyDexLoad(String loadingPackageName, List<String> dexPaths, String loaderIsa) {
    public void notifyDexLoad(String loadingPackageName, List<String> classLoaderNames,
            List<String> classPaths, String loaderIsa) {
        int userId = UserHandle.getCallingUserId();
        ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
        if (ai == null) {
@@ -9469,7 +9470,7 @@ public class PackageManagerService extends IPackageManager.Stub
                + loadingPackageName + ", user=" + userId);
            return;
        }
        mDexManager.notifyDexLoad(ai, dexPaths, loaderIsa, userId);
        mDexManager.notifyDexLoad(ai, classLoaderNames, classPaths, loaderIsa, userId);
    }
    @Override
+56 −26
Original line number Diff line number Diff line
@@ -97,29 +97,55 @@ public class DexManager {
     * return as fast as possible.
     *
     * @param loadingAppInfo the package performing the load
     * @param dexPaths the list of dex files being loaded
     * @param classLoadersNames the names of the class loaders present in the loading chain. The
     *    list encodes the class loader chain in the natural order. The first class loader has
     *    the second one as its parent and so on. The dex files present in the class path of the
     *    first class loader will be recorded in the usage file.
     * @param classPaths the class paths corresponding to the class loaders names from
     *     {@param classLoadersNames}. The the first element corresponds to the first class loader
     *     and so on. A classpath is represented as a list of dex files separated by
     *     {@code File.pathSeparator}.
     *     The dex files found in the first class path will be recorded in the usage file.
     * @param loaderIsa the ISA of the app loading the dex files
     * @param loaderUserId the user id which runs the code loading the dex files
     */
    public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> dexPaths,
            String loaderIsa, int loaderUserId) {
    public void notifyDexLoad(ApplicationInfo loadingAppInfo, List<String> classLoadersNames,
            List<String> classPaths, String loaderIsa, int loaderUserId) {
        try {
            notifyDexLoadInternal(loadingAppInfo, dexPaths, loaderIsa, loaderUserId);
            notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa,
                    loaderUserId);
        } catch (Exception e) {
            Slog.w(TAG, "Exception while notifying dex load for package " +
                    loadingAppInfo.packageName, e);
        }
    }

    private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo, List<String> dexPaths,
            String loaderIsa, int loaderUserId) {
    private void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
            List<String> classLoaderNames, List<String> classPaths, String loaderIsa,
            int loaderUserId) {
        if (classLoaderNames.size() != classPaths.size()) {
            Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size");
            return;
        }
        if (classLoaderNames.isEmpty()) {
            Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
            return;
        }
        if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
            Slog.w(TAG, "Loading dex files " + dexPaths + " in unsupported ISA: " +
            Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " +
                    loaderIsa + "?");
            return;
        }

        for (String dexPath : dexPaths) {
        // The classpath is represented as a list of dex files separated by File.pathSeparator.
        String[] dexPathsToRegister = classPaths.get(0).split(File.pathSeparator);

        // Encode the class loader contexts for the dexPathsToRegister.
        String[] classLoaderContexts = DexoptUtils.processContextForDexLoad(
                classLoaderNames, classPaths);

        int dexPathIndex = 0;
        for (String dexPath : dexPathsToRegister) {
            // Find the owning package name.
            DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);

@@ -147,24 +173,25 @@ public class DexManager {
                // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
                // or UsedBytOtherApps), record will return true and we trigger an async write
                // to disk to make sure we don't loose the data in case of a reboot.

                // A null classLoaderContexts means that there are unsupported class loaders in the
                // chain.
                String classLoaderContext = classLoaderContexts == null
                        ? PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT
                        : classLoaderContexts[dexPathIndex];
                if (mPackageDexUsage.record(searchResult.mOwningPackageName,
                        dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
                        loadingAppInfo.packageName)) {
                        loadingAppInfo.packageName, classLoaderContext)) {
                    mPackageDexUsage.maybeWriteAsync();
                }
            } else {
                // This can happen in a few situations:
                // - bogus dex loads
                // - recent installs/uninstalls that we didn't detect.
                // - new installed splits
                // If we can't find the owner of the dex we simply do not track it. The impact is
                // that the dex file will not be considered for offline optimizations.
                // TODO(calin): add hooks for move/uninstall notifications to
                // capture package moves or obsolete packages.
                if (DEBUG) {
                    Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
                }
            }
            dexPathIndex++;
        }
    }

@@ -328,10 +355,8 @@ public class DexManager {
        for (Map.Entry<String, DexUseInfo> entry : useInfo.getDexUseInfoMap().entrySet()) {
            String dexPath = entry.getKey();
            DexUseInfo dexUseInfo = entry.getValue();
            if (options.isDexoptOnlySharedDex() && !dexUseInfo.isUsedByOtherApps()) {
                continue;
            }
            PackageInfo pkg = null;

            PackageInfo pkg;
            try {
                pkg = mPackageManager.getPackageInfo(packageName, /*flags*/0,
                    dexUseInfo.getOwnerUserId());
@@ -350,8 +375,7 @@ public class DexManager {
            }

            int result = pdo.dexOptSecondaryDexPath(pkg.applicationInfo, dexPath,
                    dexUseInfo.getLoaderIsas(), options.getCompilerFilter(),
                    dexUseInfo.isUsedByOtherApps(), options.isDowngrade());
                    dexUseInfo, options);
            success = success && (result != PackageDexOptimizer.DEX_OPT_FAILED);
        }
        return success;
@@ -434,6 +458,8 @@ public class DexManager {
        }
    }

    // TODO(calin): questionable API in the presence of class loaders context. Needs amends as the
    // compilation happening here will use a pessimistic context.
    public RegisterDexModuleResult registerDexModule(ApplicationInfo info, String dexPath,
            boolean isUsedByOtherApps, int userId) {
        // Find the owning package record.
@@ -452,12 +478,11 @@ public class DexManager {

        // We found the package. Now record the usage for all declared ISAs.
        boolean update = false;
        Set<String> isas = new HashSet<>();
        for (String isa : getAppDexInstructionSets(info)) {
            isas.add(isa);
            boolean newUpdate = mPackageDexUsage.record(searchResult.mOwningPackageName,
                    dexPath, userId, isa, isUsedByOtherApps, /*primaryOrSplit*/ false,
                    searchResult.mOwningPackageName);
                    searchResult.mOwningPackageName,
                    PackageDexUsage.UNKNOWN_CLASS_LOADER_CONTEXT);
            update |= newUpdate;
        }
        if (update) {
@@ -467,8 +492,13 @@ public class DexManager {
        // Try to optimize the package according to the install reason.
        String compilerFilter = PackageManagerServiceCompilerMapping.getCompilerFilterForReason(
                PackageManagerService.REASON_INSTALL);
        int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, isas,
                compilerFilter, isUsedByOtherApps, /* downgrade */ false);
        DexUseInfo dexUseInfo = mPackageDexUsage.getPackageUseInfo(searchResult.mOwningPackageName)
                .getDexUseInfoMap().get(dexPath);

        DexoptOptions options = new DexoptOptions(info.packageName, compilerFilter, /*flags*/0);

        int result = mPackageDexOptimizer.dexOptSecondaryDexPath(info, dexPath, dexUseInfo,
                options);

        // If we fail to optimize the package log an error but don't propagate the error
        // back to the app. The app cannot do much about it and the background job
Loading