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

Commit 7b3b969f authored by Alan Stokes's avatar Alan Stokes Committed by Android (Google) Code Review
Browse files

Merge "DexManager records PackageDynamicCodeLoading."

parents 3ec349a4 67782914
Loading
Loading
Loading
Loading
+82 −35
Original line number Diff line number Diff line
@@ -16,6 +16,11 @@

package com.android.server.pm.dex;

import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;

import android.content.ContentResolver;
import android.content.Context;
import android.content.pm.ApplicationInfo;
@@ -26,9 +31,9 @@ import android.database.ContentObserver;
import android.os.Build;
import android.os.FileUtils;
import android.os.RemoteException;
import android.os.storage.StorageManager;
import android.os.SystemProperties;
import android.os.UserHandle;
import android.os.storage.StorageManager;
import android.provider.Settings.Global;
import android.util.Log;
import android.util.Slog;
@@ -48,18 +53,14 @@ import java.io.IOException;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.zip.ZipEntry;

import static com.android.server.pm.InstructionSets.getAppDexInstructionSets;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;

/**
 * This class keeps track of how dex files are used.
 * Every time it gets a notification about a dex file being loaded it tracks
@@ -89,6 +90,12 @@ public class DexManager {
    // encode and save the dex usage data.
    private final PackageDexUsage mPackageDexUsage;

    // PackageDynamicCodeLoading handles recording of dynamic code loading -
    // which is similar to PackageDexUsage but records a different aspect of the data.
    // (It additionally includes DEX files loaded with unsupported class loaders, and doesn't
    // record class loaders or ISAs.)
    private final PackageDynamicCodeLoading mPackageDynamicCodeLoading;

    private final IPackageManager mPackageManager;
    private final PackageDexOptimizer mPackageDexOptimizer;
    private final Object mInstallLock;
@@ -129,6 +136,7 @@ public class DexManager {
        mContext = context;
        mPackageCodeLocationsCache = new HashMap<>();
        mPackageDexUsage = new PackageDexUsage();
        mPackageDynamicCodeLoading = new PackageDynamicCodeLoading();
        mPackageManager = pms;
        mPackageDexOptimizer = pdo;
        mInstaller = installer;
@@ -207,7 +215,6 @@ public class DexManager {
                Slog.i(TAG, loadingAppInfo.packageName +
                        " uses unsupported class loader in " + classLoaderNames);
            }
            return;
        }

        int dexPathIndex = 0;
@@ -236,9 +243,17 @@ public class DexManager {
                    continue;
                }

                // Record dex file usage. If the current usage is a new pattern (e.g. new secondary,
                // or UsedByOtherApps), 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.
                if (mPackageDynamicCodeLoading.record(searchResult.mOwningPackageName, dexPath,
                        PackageDynamicCodeLoading.FILE_TYPE_DEX, loaderUserId,
                        loadingAppInfo.packageName)) {
                    mPackageDynamicCodeLoading.maybeWriteAsync();
                }

                if (classLoaderContexts != null) {

                    // Record dex file usage. If the current usage is a new pattern (e.g. new
                    // secondary, or UsedByOtherApps), 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.

                    String classLoaderContext = classLoaderContexts[dexPathIndex];
                    if (mPackageDexUsage.record(searchResult.mOwningPackageName,
@@ -246,6 +261,7 @@ public class DexManager {
                            loadingAppInfo.packageName, classLoaderContext)) {
                        mPackageDexUsage.maybeWriteAsync();
                    }
                }
            } else {
                // 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.
@@ -268,8 +284,8 @@ public class DexManager {
            loadInternal(existingPackages);
        } catch (Exception e) {
            mPackageDexUsage.clear();
            Slog.w(TAG, "Exception while loading package dex usage. " +
                    "Starting with a fresh state.", e);
            mPackageDynamicCodeLoading.clear();
            Slog.w(TAG, "Exception while loading. Starting with a fresh state.", e);
        }
    }

@@ -311,16 +327,25 @@ public class DexManager {
     * all usage information for the package will be removed.
     */
    public void notifyPackageDataDestroyed(String packageName, int userId) {
        boolean updated = userId == UserHandle.USER_ALL
            ? mPackageDexUsage.removePackage(packageName)
            : mPackageDexUsage.removeUserPackage(packageName, userId);
        // In case there was an update, write the package use info to disk async.
        // Note that we do the writing here and not in PackageDexUsage in order to be
        // Note that we do the writing here and not in the lower level classes in order to be
        // consistent with other methods in DexManager (e.g. reconcileSecondaryDexFiles performs
        // multiple updates in PackageDexUsage before writing it).
        if (updated) {
        if (userId == UserHandle.USER_ALL) {
            if (mPackageDexUsage.removePackage(packageName)) {
                mPackageDexUsage.maybeWriteAsync();
            }
            if (mPackageDynamicCodeLoading.removePackage(packageName)) {
                mPackageDynamicCodeLoading.maybeWriteAsync();
            }
        } else {
            if (mPackageDexUsage.removeUserPackage(packageName, userId)) {
                mPackageDexUsage.maybeWriteAsync();
            }
            if (mPackageDynamicCodeLoading.removeUserPackage(packageName, userId)) {
                mPackageDynamicCodeLoading.maybeWriteAsync();
            }
        }
    }

    /**
@@ -388,8 +413,23 @@ public class DexManager {
            }
        }

        try {
            mPackageDexUsage.read();
            mPackageDexUsage.syncData(packageToUsersMap, packageToCodePaths);
        } catch (Exception e) {
            mPackageDexUsage.clear();
            Slog.w(TAG, "Exception while loading package dex usage. "
                    + "Starting with a fresh state.", e);
        }

        try {
            mPackageDynamicCodeLoading.read();
            mPackageDynamicCodeLoading.syncData(packageToUsersMap);
        } catch (Exception e) {
            mPackageDynamicCodeLoading.clear();
            Slog.w(TAG, "Exception while loading package dynamic code usage. "
                    + "Starting with a fresh state.", e);
        }
    }

    /**
@@ -415,10 +455,16 @@ public class DexManager {
     * TODO(calin): maybe we should not (prune) so we can have an accurate view when we try
     * to access the package use.
     */
    @VisibleForTesting
    /*package*/ boolean hasInfoOnPackage(String packageName) {
        return mPackageDexUsage.getPackageUseInfo(packageName) != null;
    }

    @VisibleForTesting
    /*package*/ PackageDynamicCode getPackageDynamicCodeInfo(String packageName) {
        return mPackageDynamicCodeLoading.getPackageDynamicCodeInfo(packageName);
    }

    /**
     * Perform dexopt on with the given {@code options} on the secondary dex files.
     * @return true if all secondary dex files were processed successfully (compiled or skipped
@@ -652,7 +698,7 @@ public class DexManager {
            // to load dex files through it.
            try {
                String dexPathReal = PackageManagerServiceUtils.realpath(new File(dexPath));
                if (dexPathReal != dexPath) {
                if (!dexPath.equals(dexPathReal)) {
                    Slog.d(TAG, "Dex loaded with symlink. dexPath=" +
                            dexPath + " dexPathReal=" + dexPathReal);
                }
@@ -675,6 +721,7 @@ public class DexManager {
     */
    public void writePackageDexUsageNow() {
        mPackageDexUsage.writeNow();
        mPackageDynamicCodeLoading.writeNow();
    }

    private void registerSettingObserver() {
+1 −1
Original line number Diff line number Diff line
@@ -96,7 +96,7 @@ class PackageDynamicCodeLoading extends AbstractStatsBase<Void> {
     * @param ownerUserId the user id which runs the code loading the file
     * @param loadingPackageName the package performing the load
     * @return whether new information has been recorded
     * @throw IllegalArgumentException if clearly invalid information is detected
     * @throws IllegalArgumentException if clearly invalid information is detected
     */
    boolean record(String owningPackageName, String filePath, int fileType, int ownerUserId,
            String loadingPackageName) {
+99 −12
Original line number Diff line number Diff line
@@ -18,10 +18,14 @@ package com.android.server.pm.dex;

import static com.android.server.pm.dex.PackageDexUsage.DexUseInfo;
import static com.android.server.pm.dex.PackageDexUsage.PackageUseInfo;
import static com.android.server.pm.dex.PackageDynamicCodeLoading.DynamicCodeFile;
import static com.android.server.pm.dex.PackageDynamicCodeLoading.PackageDynamicCode;

import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotEquals;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyInt;
@@ -41,6 +45,10 @@ import androidx.test.runner.AndroidJUnit4;

import com.android.server.pm.Installer;

import dalvik.system.DelegateLastClassLoader;
import dalvik.system.PathClassLoader;
import dalvik.system.VMRuntime;

import org.junit.Before;
import org.junit.Rule;
import org.junit.Test;
@@ -50,10 +58,6 @@ import org.mockito.junit.MockitoJUnit;
import org.mockito.junit.MockitoRule;
import org.mockito.quality.Strictness;

import dalvik.system.DelegateLastClassLoader;
import dalvik.system.PathClassLoader;
import dalvik.system.VMRuntime;

import java.io.File;
import java.util.ArrayList;
import java.util.Arrays;
@@ -129,6 +133,9 @@ public class DexManagerTests {

        // Package is not used by others, so we should get nothing back.
        assertNoUseInfo(mFooUser0);

        // A package loading its own code is not stored as DCL.
        assertNoDclInfo(mFooUser0);
    }

    @Test
@@ -140,6 +147,8 @@ public class DexManagerTests {
        PackageUseInfo pui = getPackageUseInfo(mBarUser0);
        assertIsUsedByOtherApps(mBarUser0, pui, true);
        assertTrue(pui.getDexUseInfoMap().isEmpty());

        assertHasDclInfo(mBarUser0, mFooUser0, mBarUser0.getBaseAndSplitDexPaths());
    }

    @Test
@@ -152,6 +161,8 @@ public class DexManagerTests {
        assertIsUsedByOtherApps(mFooUser0, pui, false);
        assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
        assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);

        assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
    }

    @Test
@@ -164,6 +175,8 @@ public class DexManagerTests {
        assertIsUsedByOtherApps(mBarUser0, pui, false);
        assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size());
        assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0);

        assertHasDclInfo(mBarUser0, mFooUser0, barSecondaries);
    }

    @Test
@@ -200,9 +213,10 @@ public class DexManagerTests {
    }

    @Test
    public void testPackageUseInfoNotFound() {
    public void testNoNotify() {
        // Assert we don't get back data we did not previously record.
        assertNoUseInfo(mFooUser0);
        assertNoDclInfo(mFooUser0);
    }

    @Test
@@ -210,6 +224,7 @@ public class DexManagerTests {
        // Notifying with an invalid ISA should be ignored.
        notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0);
        assertNoUseInfo(mInvalidIsa);
        assertNoDclInfo(mInvalidIsa);
    }

    @Test
@@ -218,6 +233,7 @@ public class DexManagerTests {
        // register in DexManager#load should be ignored.
        notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0);
        assertNoUseInfo(mDoesNotExist);
        assertNoDclInfo(mDoesNotExist);
    }

    @Test
@@ -226,6 +242,8 @@ public class DexManagerTests {
        // Request should be ignored.
        notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1);
        assertNoUseInfo(mBarUser1);

        assertNoDclInfo(mBarUser1);
    }

    @Test
@@ -235,6 +253,10 @@ public class DexManagerTests {
        // still check that nothing goes unexpected in DexManager.
        notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1);
        assertNoUseInfo(mBarUser1);
        assertNoUseInfo(mFooUser0);

        assertNoDclInfo(mBarUser1);
        assertNoDclInfo(mFooUser0);
    }

    @Test
@@ -247,6 +269,7 @@ public class DexManagerTests {
        // is trying to load something from it we should not find it.
        notifyDexLoad(mFooUser0, newSecondaries, mUser0);
        assertNoUseInfo(newPackage);
        assertNoDclInfo(newPackage);

        // Notify about newPackage install and let mFoo load its dexes.
        mDexManager.notifyPackageInstalled(newPackage.mPackageInfo, mUser0);
@@ -257,6 +280,7 @@ public class DexManagerTests {
        assertIsUsedByOtherApps(newPackage, pui, false);
        assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
        assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0);
        assertHasDclInfo(newPackage, mFooUser0, newSecondaries);
    }

    @Test
@@ -273,6 +297,7 @@ public class DexManagerTests {
        assertIsUsedByOtherApps(newPackage, pui, false);
        assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size());
        assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0);
        assertHasDclInfo(newPackage, newPackage, newSecondaries);
    }

    @Test
@@ -305,6 +330,7 @@ public class DexManagerTests {
        // We shouldn't find yet the new split as we didn't notify the package update.
        notifyDexLoad(mFooUser0, newSplits, mUser0);
        assertNoUseInfo(mBarUser0);
        assertNoDclInfo(mBarUser0);

        // Notify that bar is updated. splitSourceDirs will contain the updated path.
        mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(),
@@ -314,8 +340,8 @@ public class DexManagerTests {
        // Now, when the split is loaded we will find it and we should mark Bar as usedByOthers.
        notifyDexLoad(mFooUser0, newSplits, mUser0);
        PackageUseInfo pui = getPackageUseInfo(mBarUser0);
        assertNotNull(pui);
        assertIsUsedByOtherApps(newSplits, pui, true);
        assertHasDclInfo(mBarUser0, mFooUser0, newSplits);
    }

    @Test
@@ -326,11 +352,15 @@ public class DexManagerTests {

        mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0);

        // Bar should not be around since it was removed for all users.
        // Data for user 1 should still be present
        PackageUseInfo pui = getPackageUseInfo(mBarUser1);
        assertNotNull(pui);
        assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(),
                /*isUsedByOtherApps*/false, mUser1);
        assertHasDclInfo(mBarUser1, mBarUser1, mBarUser1.getSecondaryDexPaths());

        // But not user 0
        assertNoUseInfo(mBarUser0, mUser0);
        assertNoDclInfo(mBarUser0, mUser0);
    }

    @Test
@@ -349,6 +379,8 @@ public class DexManagerTests {
        PackageUseInfo pui = getPackageUseInfo(mFooUser0);
        assertIsUsedByOtherApps(mFooUser0, pui, true);
        assertTrue(pui.getDexUseInfoMap().isEmpty());

        assertNoDclInfo(mFooUser0);
    }

    @Test
@@ -362,6 +394,7 @@ public class DexManagerTests {
        // Foo should not be around since all its secondary dex info were deleted
        // and it is not used by other apps.
        assertNoUseInfo(mFooUser0);
        assertNoDclInfo(mFooUser0);
    }

    @Test
@@ -374,6 +407,7 @@ public class DexManagerTests {

        // Bar should not be around since it was removed for all users.
        assertNoUseInfo(mBarUser0);
        assertNoDclInfo(mBarUser0);
    }

    @Test
@@ -383,6 +417,7 @@ public class DexManagerTests {
        notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0);
        // The dex file should not be recognized as a package.
        assertFalse(mDexManager.hasInfoOnPackage(frameworkDex));
        assertNull(mDexManager.getPackageDynamicCodeInfo(frameworkDex));
    }

    @Test
@@ -395,6 +430,8 @@ public class DexManagerTests {
        assertIsUsedByOtherApps(mFooUser0, pui, false);
        assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size());
        assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0);

        assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries);
    }

    @Test
@@ -402,7 +439,12 @@ public class DexManagerTests {
        List<String> secondaries = mBarUser0UnsupportedClassLoader.getSecondaryDexPaths();
        notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0);

        // We don't record the dex usage
        assertNoUseInfo(mBarUser0UnsupportedClassLoader);

        // But we do record this as an intance of dynamic code loading
        assertHasDclInfo(
                mBarUser0UnsupportedClassLoader, mBarUser0UnsupportedClassLoader, secondaries);
    }

    @Test
@@ -414,6 +456,8 @@ public class DexManagerTests {
        notifyDexLoad(mBarUser0, classLoaders, classPaths, mUser0);

        assertNoUseInfo(mBarUser0);

        assertHasDclInfo(mBarUser0, mBarUser0, mBarUser0.getSecondaryDexPaths());
    }

    @Test
@@ -421,6 +465,7 @@ public class DexManagerTests {
        notifyDexLoad(mBarUser0, null, mUser0);

        assertNoUseInfo(mBarUser0);
        assertNoDclInfo(mBarUser0);
    }

    @Test
@@ -455,12 +500,14 @@ public class DexManagerTests {
        notifyDexLoad(mBarUser0, secondaries, mUser0);
        PackageUseInfo pui = getPackageUseInfo(mBarUser0);
        assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0);
        assertHasDclInfo(mBarUser0, mBarUser0, secondaries);

        // Record bar secondaries again with an unsupported class loader. This should not change the
        // context.
        notifyDexLoad(mBarUser0UnsupportedClassLoader, secondaries, mUser0);
        pui = getPackageUseInfo(mBarUser0);
        assertSecondaryUse(mBarUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0);
        assertHasDclInfo(mBarUser0, mBarUser0, secondaries);
    }

    @Test
@@ -533,13 +580,53 @@ public class DexManagerTests {

    private PackageUseInfo getPackageUseInfo(TestData testData) {
        assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName()));
        return mDexManager.getPackageUseInfoOrDefault(testData.getPackageName());
        PackageUseInfo pui = mDexManager.getPackageUseInfoOrDefault(testData.getPackageName());
        assertNotNull(pui);
        return pui;
    }

    private void assertNoUseInfo(TestData testData) {
        assertFalse(mDexManager.hasInfoOnPackage(testData.getPackageName()));
    }

    private void assertNoUseInfo(TestData testData, int userId) {
        if (!mDexManager.hasInfoOnPackage(testData.getPackageName())) {
            return;
        }
        PackageUseInfo pui = getPackageUseInfo(testData);
        for (DexUseInfo dexUseInfo : pui.getDexUseInfoMap().values()) {
            assertNotEquals(userId, dexUseInfo.getOwnerUserId());
        }
    }

    private void assertNoDclInfo(TestData testData) {
        assertNull(mDexManager.getPackageDynamicCodeInfo(testData.getPackageName()));
    }

    private void assertNoDclInfo(TestData testData, int userId) {
        PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(testData.getPackageName());
        if (info == null) {
            return;
        }

        for (DynamicCodeFile fileInfo : info.mFileUsageMap.values()) {
            assertNotEquals(userId, fileInfo.mUserId);
        }
    }

    private void assertHasDclInfo(TestData owner, TestData loader, List<String> paths) {
        PackageDynamicCode info = mDexManager.getPackageDynamicCodeInfo(owner.getPackageName());
        assertNotNull("No DCL data for owner " + owner.getPackageName(), info);
        for (String path : paths) {
            DynamicCodeFile fileInfo = info.mFileUsageMap.get(path);
            assertNotNull("No DCL data for path " + path, fileInfo);
            assertEquals(PackageDynamicCodeLoading.FILE_TYPE_DEX, fileInfo.mFileType);
            assertEquals(owner.mUserId, fileInfo.mUserId);
            assertTrue("No DCL data for loader " + loader.getPackageName(),
                    fileInfo.mLoadingPackages.contains(loader.getPackageName()));
        }
    }

    private static PackageInfo getMockPackageInfo(String packageName, int userId) {
        PackageInfo pi = new PackageInfo();
        pi.packageName = packageName;
@@ -563,11 +650,13 @@ public class DexManagerTests {
        private final PackageInfo mPackageInfo;
        private final String mLoaderIsa;
        private final String mClassLoader;
        private final int mUserId;

        private TestData(String packageName, String loaderIsa, int userId, String classLoader) {
            mPackageInfo = getMockPackageInfo(packageName, userId);
            mLoaderIsa = loaderIsa;
            mClassLoader = classLoader;
            mUserId = userId;
        }

        private TestData(String packageName, String loaderIsa, int userId) {
@@ -603,9 +692,7 @@ public class DexManagerTests {
        List<String> getBaseAndSplitDexPaths() {
            List<String> paths = new ArrayList<>();
            paths.add(mPackageInfo.applicationInfo.sourceDir);
            for (String split : mPackageInfo.applicationInfo.splitSourceDirs) {
                paths.add(split);
            }
            Collections.addAll(paths, mPackageInfo.applicationInfo.splitSourceDirs);
            return paths;
        }