| /* |
| * Copyright (C) 2016 The Android Open Source Project |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License"); |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License |
| */ |
| |
| 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 android.content.pm.ApplicationInfo; |
| import android.content.pm.IPackageManager; |
| import android.content.pm.PackageInfo; |
| import android.os.Build; |
| import android.os.UserHandle; |
| |
| import androidx.test.filters.SmallTest; |
| 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; |
| import org.junit.runner.RunWith; |
| import org.mockito.Mock; |
| import org.mockito.junit.MockitoJUnit; |
| import org.mockito.junit.MockitoRule; |
| import org.mockito.quality.Strictness; |
| |
| import java.io.File; |
| import java.util.ArrayList; |
| import java.util.Arrays; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashMap; |
| import java.util.List; |
| import java.util.Map; |
| |
| @RunWith(AndroidJUnit4.class) |
| @SmallTest |
| public class DexManagerTests { |
| private static final String PATH_CLASS_LOADER_NAME = PathClassLoader.class.getName(); |
| private static final String DELEGATE_LAST_CLASS_LOADER_NAME = |
| DelegateLastClassLoader.class.getName(); |
| private static final String UNSUPPORTED_CLASS_LOADER_NAME = "unsupported.class_loader"; |
| |
| @Rule public MockitoRule mockito = MockitoJUnit.rule().strictness(Strictness.STRICT_STUBS); |
| @Mock Installer mInstaller; |
| @Mock IPackageManager mPM; |
| private final Object mInstallLock = new Object(); |
| |
| private DexManager mDexManager; |
| |
| private TestData mFooUser0; |
| private TestData mBarUser0; |
| private TestData mBarUser1; |
| private TestData mInvalidIsa; |
| private TestData mDoesNotExist; |
| |
| private TestData mBarUser0UnsupportedClassLoader; |
| private TestData mBarUser0DelegateLastClassLoader; |
| |
| private TestData mSystemServerJar; |
| private TestData mSystemServerJarUpdatedContext; |
| private TestData mSystemServerJarInvalid; |
| |
| private int mUser0; |
| private int mUser1; |
| |
| @Before |
| public void setup() { |
| mUser0 = 0; |
| mUser1 = 1; |
| |
| String isa = VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]); |
| String foo = "foo"; |
| String bar = "bar"; |
| |
| mFooUser0 = new TestData(foo, isa, mUser0, PATH_CLASS_LOADER_NAME); |
| mBarUser0 = new TestData(bar, isa, mUser0, PATH_CLASS_LOADER_NAME); |
| mBarUser1 = new TestData(bar, isa, mUser1, PATH_CLASS_LOADER_NAME); |
| mInvalidIsa = new TestData("INVALID", "INVALID_ISA", mUser0); |
| mDoesNotExist = new TestData("DOES.NOT.EXIST", isa, mUser1); |
| |
| mBarUser0UnsupportedClassLoader = new TestData(bar, isa, mUser0, |
| UNSUPPORTED_CLASS_LOADER_NAME); |
| mBarUser0DelegateLastClassLoader = new TestData(bar, isa, mUser0, |
| DELEGATE_LAST_CLASS_LOADER_NAME); |
| |
| mSystemServerJar = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME); |
| mSystemServerJarInvalid = new TestData("android", isa, mUser0, PATH_CLASS_LOADER_NAME); |
| mSystemServerJarUpdatedContext = new TestData("android", isa, mUser0, |
| DELEGATE_LAST_CLASS_LOADER_NAME); |
| |
| mDexManager = new DexManager(/*Context*/ null, mPM, /*PackageDexOptimizer*/ null, |
| mInstaller, mInstallLock); |
| |
| // Foo and Bar are available to user0. |
| // Only Bar is available to user1; |
| Map<Integer, List<PackageInfo>> existingPackages = new HashMap<>(); |
| existingPackages.put(mUser0, Arrays.asList(mFooUser0.mPackageInfo, mBarUser0.mPackageInfo)); |
| existingPackages.put(mUser1, Arrays.asList(mBarUser1.mPackageInfo)); |
| mDexManager.load(existingPackages); |
| } |
| |
| @Test |
| public void testNotifyPrimaryUse() { |
| // The main dex file and splits are re-loaded by the app. |
| notifyDexLoad(mFooUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser0); |
| |
| // 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 |
| public void testNotifyPrimaryForeignUse() { |
| // Foo loads Bar main apks. |
| notifyDexLoad(mFooUser0, mBarUser0.getBaseAndSplitDexPaths(), mUser0); |
| |
| // Bar is used by others now and should be in our records |
| PackageUseInfo pui = getPackageUseInfo(mBarUser0); |
| assertIsUsedByOtherApps(mBarUser0, pui, true); |
| assertTrue(pui.getDexUseInfoMap().isEmpty()); |
| |
| // A package loading another package's APK is not DCL (it's not app data). |
| assertNoDclInfo(mBarUser0); |
| } |
| |
| @Test |
| public void testNotifySecondary() { |
| // Foo loads its own secondary files. |
| List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths(); |
| notifyDexLoad(mFooUser0, fooSecondaries, mUser0); |
| |
| PackageUseInfo pui = getPackageUseInfo(mFooUser0); |
| assertIsUsedByOtherApps(mFooUser0, pui, false); |
| assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); |
| assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); |
| |
| assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); |
| } |
| |
| @Test |
| public void testNotifyPrimaryAndSecondary() { |
| List<String> dexFiles = mFooUser0.getBaseAndSplitDexPaths(); |
| List<String> secondaries = mFooUser0.getSecondaryDexPaths(); |
| int baseAndSplitCount = dexFiles.size(); |
| dexFiles.addAll(secondaries); |
| |
| notifyDexLoad(mFooUser0, dexFiles, mUser0); |
| |
| PackageUseInfo pui = getPackageUseInfo(mFooUser0); |
| assertIsUsedByOtherApps(mFooUser0, pui, false); |
| assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); |
| |
| String[] allExpectedContexts = DexoptUtils.processContextForDexLoad( |
| Arrays.asList(mFooUser0.mClassLoader), |
| Arrays.asList(String.join(File.pathSeparator, dexFiles))); |
| String[] secondaryExpectedContexts = Arrays.copyOfRange(allExpectedContexts, |
| baseAndSplitCount, dexFiles.size()); |
| |
| assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0, |
| secondaryExpectedContexts); |
| |
| assertHasDclInfo(mFooUser0, mFooUser0, secondaries); |
| } |
| |
| @Test |
| public void testNotifySecondaryForeign() { |
| // Foo loads bar secondary files. |
| List<String> barSecondaries = mBarUser0.getSecondaryDexPaths(); |
| notifyDexLoad(mFooUser0, barSecondaries, mUser0); |
| |
| PackageUseInfo pui = getPackageUseInfo(mBarUser0); |
| assertIsUsedByOtherApps(mBarUser0, pui, false); |
| assertEquals(barSecondaries.size(), pui.getDexUseInfoMap().size()); |
| assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); |
| |
| assertHasDclInfo(mBarUser0, mFooUser0, barSecondaries); |
| } |
| |
| @Test |
| public void testNotifySequence() { |
| // Foo loads its own secondary files. |
| List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths(); |
| notifyDexLoad(mFooUser0, fooSecondaries, mUser0); |
| // Foo loads Bar own secondary files. |
| List<String> barSecondaries = mBarUser0.getSecondaryDexPaths(); |
| notifyDexLoad(mFooUser0, barSecondaries, mUser0); |
| // Foo loads Bar primary files. |
| notifyDexLoad(mFooUser0, mBarUser0.getBaseAndSplitDexPaths(), mUser0); |
| // Bar loads its own secondary files. |
| notifyDexLoad(mBarUser0, barSecondaries, mUser0); |
| // Bar loads some own secondary files which foo didn't load. |
| List<String> barSecondariesForOwnUse = mBarUser0.getSecondaryDexPathsForOwnUse(); |
| notifyDexLoad(mBarUser0, barSecondariesForOwnUse, mUser0); |
| |
| // Check bar usage. Should be used by other app (for primary and barSecondaries). |
| PackageUseInfo pui = getPackageUseInfo(mBarUser0); |
| assertIsUsedByOtherApps(mBarUser0, pui, true); |
| assertEquals(barSecondaries.size() + barSecondariesForOwnUse.size(), |
| pui.getDexUseInfoMap().size()); |
| |
| assertSecondaryUse(mFooUser0, pui, barSecondaries, /*isUsedByOtherApps*/true, mUser0); |
| assertSecondaryUse(mFooUser0, pui, barSecondariesForOwnUse, |
| /*isUsedByOtherApps*/false, mUser0); |
| |
| // Check foo usage. Should not be used by other app. |
| pui = getPackageUseInfo(mFooUser0); |
| assertIsUsedByOtherApps(mFooUser0, pui, false); |
| assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); |
| assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); |
| } |
| |
| @Test |
| public void testNoNotify() { |
| // Assert we don't get back data we did not previously record. |
| assertNoUseInfo(mFooUser0); |
| assertNoDclInfo(mFooUser0); |
| } |
| |
| @Test |
| public void testInvalidIsa() { |
| // Notifying with an invalid ISA should be ignored. |
| notifyDexLoad(mInvalidIsa, mInvalidIsa.getSecondaryDexPaths(), mUser0); |
| assertNoUseInfo(mInvalidIsa); |
| assertNoDclInfo(mInvalidIsa); |
| } |
| |
| @Test |
| public void testNotExistingPackage() { |
| // Notifying about the load of a package which was previously not |
| // register in DexManager#load should be ignored. |
| notifyDexLoad(mDoesNotExist, mDoesNotExist.getBaseAndSplitDexPaths(), mUser0); |
| assertNoUseInfo(mDoesNotExist); |
| assertNoDclInfo(mDoesNotExist); |
| } |
| |
| @Test |
| public void testCrossUserAttempt() { |
| // Bar from User1 tries to load secondary dex files from User0 Bar. |
| // Request should be ignored. |
| notifyDexLoad(mBarUser1, mBarUser0.getSecondaryDexPaths(), mUser1); |
| assertNoUseInfo(mBarUser1); |
| |
| assertNoDclInfo(mBarUser1); |
| } |
| |
| @Test |
| public void testPackageNotInstalledForUser() { |
| // User1 tries to load Foo which is installed for User0 but not for User1. |
| // Note that the PackageManagerService already filters this out but we |
| // still check that nothing goes unexpected in DexManager. |
| notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser1); |
| assertNoUseInfo(mBarUser1); |
| assertNoUseInfo(mFooUser0); |
| |
| assertNoDclInfo(mBarUser1); |
| assertNoDclInfo(mFooUser0); |
| } |
| |
| @Test |
| public void testNotifyPackageInstallUsedByOther() { |
| TestData newPackage = new TestData("newPackage", |
| VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]), mUser0); |
| |
| List<String> newSecondaries = newPackage.getSecondaryDexPaths(); |
| // Before we notify about the installation of the newPackage if mFoo |
| // 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); |
| notifyDexLoad(mFooUser0, newSecondaries, mUser0); |
| |
| // We should get back the right info. |
| PackageUseInfo pui = getPackageUseInfo(newPackage); |
| assertIsUsedByOtherApps(newPackage, pui, false); |
| assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); |
| assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/true, mUser0); |
| assertHasDclInfo(newPackage, mFooUser0, newSecondaries); |
| } |
| |
| @Test |
| public void testNotifyPackageInstallSelfUse() { |
| TestData newPackage = new TestData("newPackage", |
| VMRuntime.getInstructionSet(Build.SUPPORTED_ABIS[0]), mUser0); |
| |
| List<String> newSecondaries = newPackage.getSecondaryDexPaths(); |
| // Packages should be able to find their own dex files even if the notification about |
| // their installation is delayed. |
| notifyDexLoad(newPackage, newSecondaries, mUser0); |
| |
| PackageUseInfo pui = getPackageUseInfo(newPackage); |
| assertIsUsedByOtherApps(newPackage, pui, false); |
| assertEquals(newSecondaries.size(), pui.getDexUseInfoMap().size()); |
| assertSecondaryUse(newPackage, pui, newSecondaries, /*isUsedByOtherApps*/false, mUser0); |
| assertHasDclInfo(newPackage, newPackage, newSecondaries); |
| } |
| |
| @Test |
| public void testNotifyPackageUpdated() { |
| // Foo loads Bar main apks. |
| notifyDexLoad(mFooUser0, mBarUser0.getBaseAndSplitDexPaths(), mUser0); |
| |
| // Bar is used by others now and should be in our records. |
| PackageUseInfo pui = getPackageUseInfo(mBarUser0); |
| assertIsUsedByOtherApps(mBarUser0, pui, true); |
| assertTrue(pui.getDexUseInfoMap().isEmpty()); |
| |
| // Notify that bar is updated. |
| mDexManager.notifyPackageUpdated(mBarUser0.getPackageName(), |
| mBarUser0.mPackageInfo.applicationInfo.sourceDir, |
| mBarUser0.mPackageInfo.applicationInfo.splitSourceDirs); |
| |
| // The usedByOtherApps flag should be clear now. |
| pui = getPackageUseInfo(mBarUser0); |
| assertIsUsedByOtherApps(mBarUser0, pui, false); |
| } |
| |
| @Test |
| public void testNotifyPackageUpdatedCodeLocations() { |
| // Simulate a split update. |
| String newSplit = mBarUser0.replaceLastSplit(); |
| List<String> newSplits = new ArrayList<>(); |
| newSplits.add(newSplit); |
| |
| // 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(), |
| mBarUser0.mPackageInfo.applicationInfo.sourceDir, |
| mBarUser0.mPackageInfo.applicationInfo.splitSourceDirs); |
| |
| // 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); |
| assertIsUsedByOtherApps(newSplits, pui, true); |
| |
| // Primary and split APKs are not recorded as DCL. |
| assertNoDclInfo(mBarUser0); |
| } |
| |
| @Test |
| public void testNotifyPackageDataDestroyForOne() { |
| // Bar loads its own secondary files. |
| notifyDexLoad(mBarUser0, mBarUser0.getSecondaryDexPaths(), mUser0); |
| notifyDexLoad(mBarUser1, mBarUser1.getSecondaryDexPaths(), mUser1); |
| |
| mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), mUser0); |
| |
| // Data for user 1 should still be present |
| PackageUseInfo pui = getPackageUseInfo(mBarUser1); |
| assertSecondaryUse(mBarUser1, pui, mBarUser1.getSecondaryDexPaths(), |
| /*isUsedByOtherApps*/false, mUser1); |
| assertHasDclInfo(mBarUser1, mBarUser1, mBarUser1.getSecondaryDexPaths()); |
| |
| // But not user 0 |
| assertNoUseInfo(mBarUser0, mUser0); |
| assertNoDclInfo(mBarUser0, mUser0); |
| } |
| |
| @Test |
| public void testNotifyPackageDataDestroyForeignUse() { |
| // Foo loads its own secondary files. |
| List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths(); |
| notifyDexLoad(mFooUser0, fooSecondaries, mUser0); |
| |
| // Bar loads Foo main apks. |
| notifyDexLoad(mBarUser0, mFooUser0.getBaseAndSplitDexPaths(), mUser0); |
| |
| mDexManager.notifyPackageDataDestroyed(mFooUser0.getPackageName(), mUser0); |
| |
| // Foo should still be around since it's used by other apps but with no |
| // secondary dex info. |
| PackageUseInfo pui = getPackageUseInfo(mFooUser0); |
| assertIsUsedByOtherApps(mFooUser0, pui, true); |
| assertTrue(pui.getDexUseInfoMap().isEmpty()); |
| |
| assertNoDclInfo(mFooUser0); |
| } |
| |
| @Test |
| public void testNotifyPackageDataDestroyComplete() { |
| // Foo loads its own secondary files. |
| List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths(); |
| notifyDexLoad(mFooUser0, fooSecondaries, mUser0); |
| |
| mDexManager.notifyPackageDataDestroyed(mFooUser0.getPackageName(), mUser0); |
| |
| // 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 |
| public void testNotifyPackageDataDestroyForAll() { |
| // Foo loads its own secondary files. |
| notifyDexLoad(mBarUser0, mBarUser0.getSecondaryDexPaths(), mUser0); |
| notifyDexLoad(mBarUser1, mBarUser1.getSecondaryDexPaths(), mUser1); |
| |
| mDexManager.notifyPackageDataDestroyed(mBarUser0.getPackageName(), UserHandle.USER_ALL); |
| |
| // Bar should not be around since it was removed for all users. |
| assertNoUseInfo(mBarUser0); |
| assertNoDclInfo(mBarUser0); |
| } |
| |
| @Test |
| public void testNotifyFrameworkLoad() { |
| String frameworkDex = "/system/framework/com.android.location.provider.jar"; |
| // Load a dex file from framework. |
| notifyDexLoad(mFooUser0, Arrays.asList(frameworkDex), mUser0); |
| // The dex file should not be recognized as owned by the package. |
| assertFalse(mDexManager.hasInfoOnPackage(mFooUser0.getPackageName())); |
| |
| assertNull(getPackageDynamicCodeInfo(mFooUser0)); |
| } |
| |
| @Test |
| public void testNotifySecondaryFromProtected() { |
| // Foo loads its own secondary files. |
| List<String> fooSecondaries = mFooUser0.getSecondaryDexPathsFromProtectedDirs(); |
| notifyDexLoad(mFooUser0, fooSecondaries, mUser0); |
| |
| PackageUseInfo pui = getPackageUseInfo(mFooUser0); |
| assertIsUsedByOtherApps(mFooUser0, pui, false); |
| assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); |
| assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0); |
| |
| assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); |
| } |
| |
| @Test |
| public void testNotifyUnsupportedClassLoader() { |
| 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 |
| public void testNotifySupportedAndUnsupportedClassLoader() { |
| String classPath = String.join(File.pathSeparator, mBarUser0.getSecondaryDexPaths()); |
| List<String> classLoaders = |
| Arrays.asList(PATH_CLASS_LOADER_NAME, UNSUPPORTED_CLASS_LOADER_NAME); |
| List<String> classPaths = Arrays.asList(classPath, classPath); |
| notifyDexLoad(mBarUser0, classLoaders, classPaths, mUser0); |
| |
| assertNoUseInfo(mBarUser0); |
| |
| assertHasDclInfo(mBarUser0, mBarUser0, mBarUser0.getSecondaryDexPaths()); |
| } |
| |
| @Test |
| public void testNotifyNullClassPath() { |
| notifyDexLoad(mBarUser0, null, mUser0); |
| |
| assertNoUseInfo(mBarUser0); |
| assertNoDclInfo(mBarUser0); |
| } |
| |
| @Test |
| public void testNotifyVariableClassLoader() { |
| // Record bar secondaries with the default PathClassLoader. |
| List<String> secondaries = mBarUser0.getSecondaryDexPaths(); |
| |
| notifyDexLoad(mBarUser0, secondaries, mUser0); |
| PackageUseInfo pui = getPackageUseInfo(mBarUser0); |
| assertIsUsedByOtherApps(mBarUser0, pui, false); |
| assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); |
| assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); |
| |
| // Record bar secondaries again with a different class loader. This will change the context. |
| notifyDexLoad(mBarUser0DelegateLastClassLoader, secondaries, mUser0); |
| |
| pui = getPackageUseInfo(mBarUser0); |
| assertIsUsedByOtherApps(mBarUser0, pui, false); |
| assertEquals(secondaries.size(), pui.getDexUseInfoMap().size()); |
| // We expect that all the contexts to be changed to variable now. |
| String[] expectedContexts = |
| Collections.nCopies(secondaries.size(), |
| PackageDexUsage.VARIABLE_CLASS_LOADER_CONTEXT).toArray(new String[0]); |
| assertSecondaryUse(mFooUser0, pui, secondaries, /*isUsedByOtherApps*/false, mUser0, |
| expectedContexts); |
| } |
| |
| @Test |
| public void testSystemServerOverwritesContext() { |
| // Record bar secondaries with the default PathClassLoader. |
| List<String> secondaries = mSystemServerJar.getSecondaryDexPaths(); |
| |
| notifyDexLoad(mSystemServerJar, secondaries, mUser0); |
| PackageUseInfo pui = getPackageUseInfo(mSystemServerJar); |
| assertSecondaryUse(mSystemServerJar, pui, secondaries, /*isUsedByOtherApps*/false, mUser0); |
| |
| // Record bar secondaries again with a different class loader. This will change the context. |
| notifyDexLoad(mSystemServerJarUpdatedContext, secondaries, mUser0); |
| |
| pui = getPackageUseInfo(mSystemServerJar); |
| // We expect that all the contexts to be updated according to the last notify. |
| assertSecondaryUse(mSystemServerJarUpdatedContext, pui, secondaries, |
| /*isUsedByOtherApps*/false, mUser0); |
| } |
| |
| @Test |
| public void testNotifyUnsupportedClassLoaderDoesNotChangeExisting() { |
| List<String> secondaries = mBarUser0.getSecondaryDexPaths(); |
| |
| 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 |
| public void testPrimaryAndSecondaryDexLoad() { |
| // Foo loads both primary and secondary dexes |
| List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths(); |
| List<String> fooDexes = new ArrayList<>(mFooUser0.getBaseAndSplitDexPaths()); |
| int primaryCount = fooDexes.size(); |
| fooDexes.addAll(fooSecondaries); |
| |
| notifyDexLoad(mFooUser0, fooDexes, mUser0); |
| |
| PackageUseInfo pui = getPackageUseInfo(mFooUser0); |
| assertIsUsedByOtherApps(mFooUser0, pui, false); |
| assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); |
| |
| // Below we want to verify that the secondary dex files within fooDexes have been correctly |
| // reported and their class loader contexts were correctly recorded. |
| // |
| // In order to achieve this we first use DexoptUtils.processContextForDexLoad to compute the |
| // class loader contexts for all the dex files. |
| String[] allClassLoaderContexts = DexoptUtils.processContextForDexLoad( |
| Arrays.asList(mFooUser0.mClassLoader), |
| Arrays.asList(String.join(File.pathSeparator, fooDexes))); |
| // Next we filter out the class loader contexts corresponding to non-secondary dex files. |
| String[] secondaryClassLoaderContexts = Arrays.copyOfRange(allClassLoaderContexts, |
| primaryCount, allClassLoaderContexts.length); |
| assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0, |
| secondaryClassLoaderContexts); |
| |
| assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); |
| } |
| |
| @Test |
| public void testNotifySecondary_withSharedLibrary() { |
| // Foo loads its own secondary files. |
| List<String> fooSecondaries = mFooUser0.getSecondaryDexPaths(); |
| |
| String contextSuffix = "{PCL[/system/framework/org.apache.http.legacy.jar]}"; |
| String[] expectedContexts = DexoptUtils.processContextForDexLoad( |
| Arrays.asList(mFooUser0.mClassLoader), |
| Arrays.asList(String.join(File.pathSeparator, fooSecondaries))); |
| for (int i = 0; i < expectedContexts.length; i++) { |
| expectedContexts[i] += contextSuffix; |
| } |
| |
| notifyDexLoad(mFooUser0, fooSecondaries, expectedContexts, mUser0); |
| |
| PackageUseInfo pui = getPackageUseInfo(mFooUser0); |
| assertIsUsedByOtherApps(mFooUser0, pui, false); |
| assertEquals(fooSecondaries.size(), pui.getDexUseInfoMap().size()); |
| assertSecondaryUse(mFooUser0, pui, fooSecondaries, /*isUsedByOtherApps*/false, mUser0, |
| expectedContexts); |
| |
| assertHasDclInfo(mFooUser0, mFooUser0, fooSecondaries); |
| } |
| |
| |
| @Test |
| public void testNotifySystemServerUse() { |
| List<String> dexFiles = new ArrayList<String>(); |
| dexFiles.add("/system/framework/foo"); |
| notifyDexLoad(mSystemServerJar, dexFiles, mUser0); |
| PackageUseInfo pui = getPackageUseInfo(mSystemServerJar); |
| assertIsUsedByOtherApps(mSystemServerJar, pui, false); |
| } |
| |
| @Test |
| public void testNotifySystemServerInvalidUse() { |
| List<String> dexFiles = new ArrayList<String>(); |
| dexFiles.add("/data/foo"); |
| notifyDexLoad(mSystemServerJarInvalid, dexFiles, mUser0); |
| assertNoUseInfo(mSystemServerJarInvalid); |
| assertNoDclInfo(mSystemServerJarInvalid); |
| } |
| |
| private void assertSecondaryUse(TestData testData, PackageUseInfo pui, |
| List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId, |
| String[] expectedContexts) { |
| assertNotNull(expectedContexts); |
| assertEquals(expectedContexts.length, secondaries.size()); |
| int index = 0; |
| for (String dex : secondaries) { |
| DexUseInfo dui = pui.getDexUseInfoMap().get(dex); |
| assertNotNull(dui); |
| assertEquals(isUsedByOtherApps, dui.isUsedByOtherApps()); |
| assertEquals(ownerUserId, dui.getOwnerUserId()); |
| assertEquals(1, dui.getLoaderIsas().size()); |
| assertTrue(dui.getLoaderIsas().contains(testData.mLoaderIsa)); |
| assertEquals(expectedContexts[index++], dui.getClassLoaderContext()); |
| } |
| } |
| private void assertSecondaryUse(TestData testData, PackageUseInfo pui, |
| List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId) { |
| String[] expectedContexts = DexoptUtils.processContextForDexLoad( |
| Arrays.asList(testData.mClassLoader), |
| Arrays.asList(String.join(File.pathSeparator, secondaries))); |
| assertSecondaryUse(testData, pui, secondaries, isUsedByOtherApps, ownerUserId, |
| expectedContexts); |
| } |
| |
| private void assertIsUsedByOtherApps(TestData testData, PackageUseInfo pui, |
| boolean isUsedByOtherApps) { |
| assertIsUsedByOtherApps(testData.getBaseAndSplitDexPaths(), pui, isUsedByOtherApps); |
| } |
| |
| private void assertIsUsedByOtherApps(List<String> codePaths, PackageUseInfo pui, |
| boolean isUsedByOtherApps) { |
| for (String codePath : codePaths) { |
| assertEquals(codePath, isUsedByOtherApps, pui.isUsedByOtherApps(codePath)); |
| } |
| } |
| private void notifyDexLoad(TestData testData, List<String> dexPaths, int loaderUserId) { |
| // By default, assume a single class loader in the chain. |
| // This makes writing tests much easier. |
| List<String> classLoaders = Arrays.asList(testData.mClassLoader); |
| List<String> classPaths = dexPaths != null |
| ? Arrays.<String>asList(String.join(File.pathSeparator, dexPaths)) : null; |
| notifyDexLoad(testData, classLoaders, classPaths, loaderUserId); |
| } |
| |
| private void notifyDexLoad(TestData testData, List<String> classLoaders, |
| List<String> classPaths, int loaderUserId) { |
| String[] classLoaderContexts = computeClassLoaderContexts(classLoaders, classPaths); |
| // We call the internal function so any exceptions thrown cause test failures. |
| List<String> dexPaths = classPaths != null |
| ? Arrays.asList(classPaths.get(0).split(File.pathSeparator)) : Arrays.asList(); |
| notifyDexLoad(testData, dexPaths, classLoaderContexts, loaderUserId); |
| } |
| |
| private void notifyDexLoad(TestData testData, List<String> dexPaths, |
| String[] classLoaderContexts, int loaderUserId) { |
| assertTrue(dexPaths.size() == classLoaderContexts.length); |
| HashMap<String, String> dexPathMapping = new HashMap<>(dexPaths.size()); |
| for (int i = 0; i < dexPaths.size(); i++) { |
| dexPathMapping.put(dexPaths.get(i), classLoaderContexts != null |
| ? classLoaderContexts[i] : PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT); |
| } |
| mDexManager.notifyDexLoadInternal(testData.mPackageInfo.applicationInfo, dexPathMapping, |
| testData.mLoaderIsa, loaderUserId); |
| } |
| |
| private String[] computeClassLoaderContexts(List<String> classLoaders, |
| List<String> classPaths) { |
| if (classPaths == null) { |
| return new String[0]; |
| } |
| String[] results = DexoptUtils.processContextForDexLoad(classLoaders, classPaths); |
| if (results == null) { |
| results = new String[classPaths.get(0).split(File.pathSeparator).length]; |
| Arrays.fill(results, PackageDexUsage.UNSUPPORTED_CLASS_LOADER_CONTEXT); |
| } |
| return results; |
| } |
| |
| private PackageUseInfo getPackageUseInfo(TestData testData) { |
| assertTrue(mDexManager.hasInfoOnPackage(testData.getPackageName())); |
| PackageUseInfo pui = mDexManager.getPackageUseInfoOrDefault(testData.getPackageName()); |
| assertNotNull(pui); |
| return pui; |
| } |
| |
| private PackageDynamicCode getPackageDynamicCodeInfo(TestData testData) { |
| return mDexManager.getDynamicCodeLogger() |
| .getPackageDynamicCodeInfo(testData.getPackageName()); |
| } |
| |
| 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(getPackageDynamicCodeInfo(testData)); |
| } |
| |
| private void assertNoDclInfo(TestData testData, int userId) { |
| PackageDynamicCode info = getPackageDynamicCodeInfo(testData); |
| 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 = getPackageDynamicCodeInfo(owner); |
| 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; |
| pi.applicationInfo = getMockApplicationInfo(packageName, userId); |
| return pi; |
| } |
| |
| private static ApplicationInfo getMockApplicationInfo(String packageName, int userId) { |
| ApplicationInfo ai = new ApplicationInfo(); |
| String codeDir = "/data/app/" + packageName; |
| ai.setBaseCodePath(codeDir + "/base.dex"); |
| ai.setSplitCodePaths(new String[] {codeDir + "/split-1.dex", codeDir + "/split-2.dex"}); |
| ai.dataDir = "/data/user/" + userId + "/" + packageName; |
| ai.deviceProtectedDataDir = "/data/user_de/" + userId + "/" + packageName; |
| ai.credentialProtectedDataDir = "/data/user_ce/" + userId + "/" + packageName; |
| ai.packageName = packageName; |
| return ai; |
| } |
| |
| private static class TestData { |
| 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) { |
| this(packageName, loaderIsa, userId, PATH_CLASS_LOADER_NAME); |
| } |
| |
| private String getPackageName() { |
| return mPackageInfo.packageName; |
| } |
| |
| List<String> getSecondaryDexPaths() { |
| List<String> paths = new ArrayList<>(); |
| paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary1.dex"); |
| paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary2.dex"); |
| paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary3.dex"); |
| return paths; |
| } |
| |
| List<String> getSecondaryDexPathsForOwnUse() { |
| List<String> paths = new ArrayList<>(); |
| paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary4.dex"); |
| paths.add(mPackageInfo.applicationInfo.dataDir + "/secondary5.dex"); |
| return paths; |
| } |
| |
| List<String> getSecondaryDexPathsFromProtectedDirs() { |
| List<String> paths = new ArrayList<>(); |
| paths.add(mPackageInfo.applicationInfo.deviceProtectedDataDir + "/secondary6.dex"); |
| paths.add(mPackageInfo.applicationInfo.credentialProtectedDataDir + "/secondary7.dex"); |
| return paths; |
| } |
| |
| List<String> getBaseAndSplitDexPaths() { |
| List<String> paths = new ArrayList<>(); |
| paths.add(mPackageInfo.applicationInfo.sourceDir); |
| Collections.addAll(paths, mPackageInfo.applicationInfo.splitSourceDirs); |
| return paths; |
| } |
| |
| String replaceLastSplit() { |
| int length = mPackageInfo.applicationInfo.splitSourceDirs.length; |
| // Add an extra bogus dex extension to simulate a new split name. |
| mPackageInfo.applicationInfo.splitSourceDirs[length - 1] += ".dex"; |
| return mPackageInfo.applicationInfo.splitSourceDirs[length - 1]; |
| } |
| } |
| |
| private boolean shouldPackageRunOob(boolean isDefaultEnabled, String whitelist, |
| Collection<String> packageNamesInSameProcess) { |
| return DexManager.isPackageSelectedToRunOobInternal( |
| isDefaultEnabled, whitelist, packageNamesInSameProcess); |
| } |
| |
| @Test |
| public void testOobPackageSelectionDefault() { |
| // Feature is off by default, not overriden |
| assertFalse(shouldPackageRunOob(false, "ALL", null)); |
| } |
| |
| @Test |
| public void testOobPackageSelectionWhitelist() { |
| // Various allowlist of apps to run in OOB mode. |
| final String kWhitelistApp0 = "com.priv.app0"; |
| final String kWhitelistApp1 = "com.priv.app1"; |
| final String kWhitelistApp2 = "com.priv.app2"; |
| final String kWhitelistApp1AndApp2 = "com.priv.app1,com.priv.app2"; |
| |
| // Packages that shares the targeting process. |
| final Collection<String> runningPackages = Arrays.asList("com.priv.app1", "com.priv.app2"); |
| |
| // Feature is off, allowlist does not matter |
| assertFalse(shouldPackageRunOob(false, kWhitelistApp0, runningPackages)); |
| assertFalse(shouldPackageRunOob(false, kWhitelistApp1, runningPackages)); |
| assertFalse(shouldPackageRunOob(false, "", runningPackages)); |
| assertFalse(shouldPackageRunOob(false, "ALL", runningPackages)); |
| |
| // Feature is on, app not in allowlist |
| assertFalse(shouldPackageRunOob(true, kWhitelistApp0, runningPackages)); |
| assertFalse(shouldPackageRunOob(true, "", runningPackages)); |
| |
| // Feature is on, app in allowlist |
| assertTrue(shouldPackageRunOob(true, kWhitelistApp1, runningPackages)); |
| assertTrue(shouldPackageRunOob(true, kWhitelistApp2, runningPackages)); |
| assertTrue(shouldPackageRunOob(true, kWhitelistApp1AndApp2, runningPackages)); |
| assertTrue(shouldPackageRunOob(true, "ALL", runningPackages)); |
| } |
| } |