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

Commit 64644548 authored by Alan Stokes's avatar Alan Stokes
Browse files

Apply a per-package limit to secondary dex tracking.

This is to avoid a single badly-behaved app blowing up system server
memory usage.

Test: atest -p services/core/java/com/android/server/pm/dex
Change-Id: I7f4d7784f1012783f2212ee8099140dc9730969b
parent c14e1f95
Loading
Loading
Loading
Loading
+20 −8
Original line number Diff line number Diff line
@@ -16,9 +16,9 @@

package com.android.server.pm.dex;

import android.os.Build;
import android.util.AtomicFile;
import android.util.Slog;
import android.os.Build;

import com.android.internal.annotations.GuardedBy;
import com.android.internal.annotations.VisibleForTesting;
@@ -26,26 +26,27 @@ import com.android.internal.util.FastPrintWriter;
import com.android.server.pm.AbstractStatsBase;
import com.android.server.pm.PackageManagerServiceUtils;

import dalvik.system.VMRuntime;

import libcore.io.IoUtils;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.InputStreamReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.Reader;
import java.io.StringWriter;
import java.io.Writer;
import java.util.Collections;
import java.util.Iterator;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Map;
import java.util.Objects;
import java.util.Set;

import dalvik.system.VMRuntime;
import libcore.io.IoUtils;

/**
 * Stat file which store usage information about dex files.
 */
@@ -86,6 +87,13 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
    private static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
            "=UnsupportedClassLoaderContext=";

    /**
     * Limit on how many secondary DEX paths we store for a single owner, to avoid one app causing
     * unbounded memory consumption.
     */
    @VisibleForTesting
    /* package */ static final int MAX_SECONDARY_FILES_PER_OWNER = 100;

    // Map which structures the information we have on a package.
    // Maps package name to package data (which stores info about UsedByOtherApps and
    // secondary dex files.).
@@ -164,8 +172,12 @@ public class PackageDexUsage extends AbstractStatsBase<Void> {
                    DexUseInfo existingData = packageUseInfo.mDexUseInfoMap.get(dexPath);
                    if (existingData == null) {
                        // It's the first time we see this dex file.
                        if (packageUseInfo.mDexUseInfoMap.size() < MAX_SECONDARY_FILES_PER_OWNER) {
                            packageUseInfo.mDexUseInfoMap.put(dexPath, newData);
                            return true;
                        } else {
                            return updateLoadingPackages;
                        }
                    } else {
                        if (ownerUserId != existingData.mOwnerUserId) {
                            // Oups, this should never happen, the DexManager who calls this should
+44 −15
Original line number Diff line number Diff line
@@ -17,6 +17,7 @@
package com.android.server.pm.dex;

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

import static org.junit.Assert.assertEquals;
@@ -31,24 +32,28 @@ import android.os.Build;
import androidx.test.filters.SmallTest;
import androidx.test.runner.AndroidJUnit4;

import dalvik.system.VMRuntime;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;

import dalvik.system.VMRuntime;

import java.io.IOException;
import java.io.StringReader;
import java.io.StringWriter;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;

@RunWith(AndroidJUnit4.class)
@SmallTest
public class PackageDexUsageTests {
    private static final String ISA = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);

    private PackageDexUsage mPackageDexUsage;

    private TestData mFooBaseUser0;
@@ -71,25 +76,23 @@ public class PackageDexUsageTests {
        String fooCodeDir = "/data/app/com.google.foo/";
        String fooDataDir = "/data/user/0/com.google.foo/";

        String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]);

        mFooBaseUser0 = new TestData(fooPackageName,
                fooCodeDir + "base.apk", 0, isa, false, true, fooPackageName);
                fooCodeDir + "base.apk", 0, ISA, false, true, fooPackageName);

        mFooSplit1User0 = new TestData(fooPackageName,
                fooCodeDir + "split-1.apk", 0, isa, false, true, fooPackageName);
                fooCodeDir + "split-1.apk", 0, ISA, false, true, fooPackageName);

        mFooSplit2UsedByOtherApps0 = new TestData(fooPackageName,
                fooCodeDir + "split-2.apk", 0, isa, true, true, "used.by.other.com");
                fooCodeDir + "split-2.apk", 0, ISA, true, true, "used.by.other.com");

        mFooSecondary1User0 = new TestData(fooPackageName,
                fooDataDir + "sec-1.dex", 0, isa, false, false, fooPackageName);
                fooDataDir + "sec-1.dex", 0, ISA, false, false, fooPackageName);

        mFooSecondary1User1 = new TestData(fooPackageName,
                fooDataDir + "sec-1.dex", 1, isa, false, false, fooPackageName);
                fooDataDir + "sec-1.dex", 1, ISA, false, false, fooPackageName);

        mFooSecondary2UsedByOtherApps0 = new TestData(fooPackageName,
                fooDataDir + "sec-2.dex", 0, isa, true, false, "used.by.other.com");
                fooDataDir + "sec-2.dex", 0, ISA, true, false, "used.by.other.com");

        mInvalidIsa = new TestData(fooPackageName,
                fooCodeDir + "base.apk", 0, "INVALID_ISA", false, true, "INALID_USER");
@@ -100,11 +103,11 @@ public class PackageDexUsageTests {
        String barDataDir1 = "/data/user/1/com.google.bar/";

        mBarBaseUser0 = new TestData(barPackageName,
                barCodeDir + "base.apk", 0, isa, false, true, barPackageName);
                barCodeDir + "base.apk", 0, ISA, false, true, barPackageName);
        mBarSecondary1User0 = new TestData(barPackageName,
                barDataDir + "sec-1.dex", 0, isa, false, false, barPackageName);
                barDataDir + "sec-1.dex", 0, ISA, false, false, barPackageName);
        mBarSecondary2User1 = new TestData(barPackageName,
                barDataDir1 + "sec-2.dex", 1, isa, false, false, barPackageName);
                barDataDir1 + "sec-2.dex", 1, ISA, false, false, barPackageName);
    }

    @Test
@@ -182,6 +185,25 @@ public class PackageDexUsageTests {
                mFooSplit2UsedByOtherApps0, mFooSecondary1User0, mFooSecondary2UsedByOtherApps0);
    }

    @Test
    public void testRecordTooManySecondaries() {
        int tooManyFiles = MAX_SECONDARY_FILES_PER_OWNER + 1;
        List<TestData> expectedSecondaries = new ArrayList<>();
        for (int i = 1; i <= tooManyFiles; i++) {
            String fooPackageName = "com.google.foo";
            TestData testData = new TestData(fooPackageName,
                    "/data/user/0/" + fooPackageName + "/sec-" + i + "1.dex", 0, ISA, false, false,
                    fooPackageName);
            if (i < tooManyFiles) {
                assertTrue("Adding " + testData.mDexFile, record(testData));
                expectedSecondaries.add(testData);
            } else {
                assertFalse("Adding " + testData.mDexFile, record(testData));
            }
            assertPackageDexUsage(mPackageDexUsage, null, null, expectedSecondaries);
        }
    }

    @Test
    public void testMultiplePackages() {
        assertTrue(record(mFooBaseUser0));
@@ -540,7 +562,14 @@ public class PackageDexUsageTests {

    private void assertPackageDexUsage(PackageDexUsage packageDexUsage, Set<String> users,
            TestData primary, TestData... secondaries) {
        String packageName = primary == null ? secondaries[0].mPackageName : primary.mPackageName;
        assertPackageDexUsage(packageDexUsage, users, primary, Arrays.asList(secondaries));
    }

    private void assertPackageDexUsage(PackageDexUsage packageDexUsage, Set<String> users,
            TestData primary, List<TestData> secondaries) {
        String packageName = primary == null
                ? secondaries.get(0).mPackageName
                : primary.mPackageName;
        boolean primaryUsedByOtherApps = primary != null && primary.mUsedByOtherApps;
        PackageUseInfo pInfo = packageDexUsage.getPackageUseInfo(packageName);

@@ -554,7 +583,7 @@ public class PackageDexUsageTests {
        }

        Map<String, DexUseInfo> dexUseInfoMap = pInfo.getDexUseInfoMap();
        assertEquals(secondaries.length, dexUseInfoMap.size());
        assertEquals(secondaries.size(), dexUseInfoMap.size());

        // Check dex use info
        for (TestData testData : secondaries) {