Merge "[DexLoadReporter] Report classloader contexts directly from classloader" am: 5a01991fef
Change-Id: Ida294271950c3f330f1acb2150ac3892db88ab64
diff --git a/core/java/android/app/DexLoadReporter.java b/core/java/android/app/DexLoadReporter.java
index 229bee5..5bc9992 100644
--- a/core/java/android/app/DexLoadReporter.java
+++ b/core/java/android/app/DexLoadReporter.java
@@ -28,9 +28,8 @@
import java.io.File;
import java.io.IOException;
-import java.util.ArrayList;
import java.util.HashSet;
-import java.util.List;
+import java.util.Map;
import java.util.Set;
/**
@@ -87,50 +86,32 @@
}
@Override
- public void report(List<ClassLoader> classLoadersChain, List<String> classPaths) {
- if (classLoadersChain.size() != classPaths.size()) {
- Slog.wtf(TAG, "Bad call to DexLoadReporter: argument size mismatch");
- return;
- }
- if (classPaths.isEmpty()) {
- Slog.wtf(TAG, "Bad call to DexLoadReporter: empty dex paths");
- return;
- }
-
- // The first element of classPaths is the list of dex files that should be registered.
- // The classpath is represented as a list of dex files separated by File.pathSeparator.
- String[] dexPathsForRegistration = classPaths.get(0).split(File.pathSeparator);
- if (dexPathsForRegistration.length == 0) {
- // No dex files to register.
+ public void report(Map<String, String> classLoaderContextMap) {
+ if (classLoaderContextMap.isEmpty()) {
+ Slog.wtf(TAG, "Bad call to DexLoadReporter: empty classLoaderContextMap");
return;
}
// Notify the package manager about the dex loads unconditionally.
// The load might be for either a primary or secondary dex file.
- notifyPackageManager(classLoadersChain, classPaths);
+ notifyPackageManager(classLoaderContextMap);
// Check for secondary dex files and register them for profiling if possible.
// Note that we only register the dex paths belonging to the first class loader.
- registerSecondaryDexForProfiling(dexPathsForRegistration);
+ registerSecondaryDexForProfiling(classLoaderContextMap.keySet());
}
- private void notifyPackageManager(List<ClassLoader> classLoadersChain,
- List<String> classPaths) {
+ private void notifyPackageManager(Map<String, String> classLoaderContextMap) {
// Get the class loader names for the binder call.
- List<String> classLoadersNames = new ArrayList<>(classPaths.size());
- for (ClassLoader classLoader : classLoadersChain) {
- classLoadersNames.add(classLoader.getClass().getName());
- }
String packageName = ActivityThread.currentPackageName();
try {
- ActivityThread.getPackageManager().notifyDexLoad(
- packageName, classLoadersNames, classPaths,
- VMRuntime.getRuntime().vmInstructionSet());
+ ActivityThread.getPackageManager().notifyDexLoad(packageName,
+ classLoaderContextMap, VMRuntime.getRuntime().vmInstructionSet());
} catch (RemoteException re) {
Slog.e(TAG, "Failed to notify PM about dex load for package " + packageName, re);
}
}
- private void registerSecondaryDexForProfiling(String[] dexPaths) {
+ private void registerSecondaryDexForProfiling(Set<String> dexPaths) {
if (!SystemProperties.getBoolean("dalvik.vm.dexopt.secondary", false)) {
return;
}
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index 429a6e5..6d051e4 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -529,19 +529,12 @@
* 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 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}, or null if the class loader's classpath is not known.
- * The dex files found in the first class path will be recorded in the usage file.
+ * @param classLoaderContextMap a map from file paths to dex files that have been loaded to
+ * the class loader context that was used to load them.
* @param loaderIsa the ISA of the loader process
*/
- oneway void notifyDexLoad(String loadingPackageName, in List<String> classLoadersNames,
- in List<String> classPaths, String loaderIsa);
+ oneway void notifyDexLoad(String loadingPackageName,
+ in Map<String, String> classLoaderContextMap, String loaderIsa);
/**
* Register an application dex module with the package manager.
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index 28a8213..ce0c21c 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -9764,8 +9764,8 @@
}
@Override
- public void notifyDexLoad(String loadingPackageName, List<String> classLoaderNames,
- List<String> classPaths, String loaderIsa) {
+ public void notifyDexLoad(String loadingPackageName, Map<String, String> classLoaderContextMap,
+ String loaderIsa) {
int userId = UserHandle.getCallingUserId();
ApplicationInfo ai = getApplicationInfo(loadingPackageName, /*flags*/ 0, userId);
if (ai == null) {
@@ -9773,7 +9773,7 @@
+ loadingPackageName + ", user=" + userId);
return;
}
- mDexManager.notifyDexLoad(ai, classLoaderNames, classPaths, loaderIsa, userId);
+ mDexManager.notifyDexLoad(ai, classLoaderContextMap, loaderIsa, userId);
}
@Override
diff --git a/services/core/java/com/android/server/pm/dex/DexManager.java b/services/core/java/com/android/server/pm/dex/DexManager.java
index 9e86a4b..441fa75 100644
--- a/services/core/java/com/android/server/pm/dex/DexManager.java
+++ b/services/core/java/com/android/server/pm/dex/DexManager.java
@@ -44,6 +44,8 @@
import com.android.server.pm.PackageManagerService;
import com.android.server.pm.PackageManagerServiceUtils;
+import dalvik.system.VMRuntime;
+
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
@@ -143,22 +145,15 @@
* return as fast as possible.
*
* @param loadingAppInfo the package performing the load
- * @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}, or null if the class loader's classpath is not known.
- * The dex files found in the first class path will be recorded in the usage file.
+ * @param classLoaderContextMap a map from file paths to dex files that have been loaded to
+ * the class loader context that was used to load them.
* @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> classLoadersNames,
- List<String> classPaths, String loaderIsa, int loaderUserId) {
+ public void notifyDexLoad(ApplicationInfo loadingAppInfo,
+ Map<String, String> classLoaderContextMap, String loaderIsa, int loaderUserId) {
try {
- notifyDexLoadInternal(loadingAppInfo, classLoadersNames, classPaths, loaderIsa,
+ notifyDexLoadInternal(loadingAppInfo, classLoaderContextMap, loaderIsa,
loaderUserId);
} catch (Exception e) {
Slog.w(TAG, "Exception while notifying dex load for package " +
@@ -168,46 +163,23 @@
@VisibleForTesting
/*package*/ void notifyDexLoadInternal(ApplicationInfo loadingAppInfo,
- List<String> classLoaderNames, List<String> classPaths, String loaderIsa,
+ Map<String, String> classLoaderContextMap, String loaderIsa,
int loaderUserId) {
- if (classLoaderNames.size() != classPaths.size()) {
- Slog.wtf(TAG, "Bad call to noitfyDexLoad: args have different size");
+ if (classLoaderContextMap == null) {
return;
}
- if (classLoaderNames.isEmpty()) {
+ if (classLoaderContextMap.isEmpty()) {
Slog.wtf(TAG, "Bad call to notifyDexLoad: class loaders list is empty");
return;
}
if (!PackageManagerServiceUtils.checkISA(loaderIsa)) {
- Slog.w(TAG, "Loading dex files " + classPaths + " in unsupported ISA: " +
- loaderIsa + "?");
+ Slog.w(TAG, "Loading dex files " + classLoaderContextMap.keySet()
+ + " in unsupported ISA: " + loaderIsa + "?");
return;
}
- // The first classpath should never be null because the first classloader
- // should always be an instance of BaseDexClassLoader.
- String firstClassPath = classPaths.get(0);
- if (firstClassPath == null) {
- return;
- }
- // The classpath is represented as a list of dex files separated by File.pathSeparator.
- String[] dexPathsToRegister = firstClassPath.split(File.pathSeparator);
-
- // Encode the class loader contexts for the dexPathsToRegister.
- String[] classLoaderContexts = DexoptUtils.processContextForDexLoad(
- classLoaderNames, classPaths);
-
- // A null classLoaderContexts means that there are unsupported class loaders in the
- // chain.
- if (classLoaderContexts == null) {
- if (DEBUG) {
- Slog.i(TAG, loadingAppInfo.packageName +
- " uses unsupported class loader in " + classLoaderNames);
- }
- }
-
- int dexPathIndex = 0;
- for (String dexPath : dexPathsToRegister) {
+ for (Map.Entry<String, String> mapping : classLoaderContextMap.entrySet()) {
+ String dexPath = mapping.getKey();
// Find the owning package name.
DexSearchResult searchResult = getDexPackage(loadingAppInfo, dexPath, loaderUserId);
@@ -229,7 +201,6 @@
// If the dex file is the primary apk (or a split) and not isUsedByOtherApps
// do not record it. This case does not bring any new usable information
// and can be safely skipped.
- dexPathIndex++;
continue;
}
@@ -239,13 +210,13 @@
searchResult.mOwningPackageName, loadingAppInfo.packageName);
}
- if (classLoaderContexts != null) {
-
+ String classLoaderContext = mapping.getValue();
+ if (classLoaderContext != null
+ && VMRuntime.isValidClassLoaderContext(classLoaderContext)) {
// 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,
dexPath, loaderUserId, loaderIsa, isUsedByOtherApps, primaryOrSplit,
loadingAppInfo.packageName, classLoaderContext)) {
@@ -259,7 +230,6 @@
Slog.i(TAG, "Could not find owning package for dex file: " + dexPath);
}
}
- dexPathIndex++;
}
}
diff --git a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
index e68c238..08763e7 100644
--- a/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
+++ b/services/core/java/com/android/server/pm/dex/PackageDexUsage.java
@@ -83,8 +83,9 @@
"=UnknownClassLoaderContext=";
// The marker used for unsupported class loader contexts (no longer written, may occur in old
- // files so discarded on read).
- private static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
+ // files so discarded on read). Note: this matches
+ // ClassLoaderContext::kUnsupportedClassLoaderContextEncoding in the runtime.
+ /*package*/ static final String UNSUPPORTED_CLASS_LOADER_CONTEXT =
"=UnsupportedClassLoaderContext=";
/**
@@ -133,6 +134,9 @@
if (classLoaderContext == null) {
throw new IllegalArgumentException("Null classLoaderContext");
}
+ if (classLoaderContext.equals(UNSUPPORTED_CLASS_LOADER_CONTEXT)) {
+ return false;
+ }
synchronized (mPackageUseInfoMap) {
PackageUseInfo packageUseInfo = mPackageUseInfoMap.get(owningPackageName);
@@ -843,10 +847,11 @@
boolean updateLoadingPackages = mLoadingPackages.addAll(dexUseInfo.mLoadingPackages);
String oldClassLoaderContext = mClassLoaderContext;
- if (UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext)) {
+ if (isUnknownOrUnsupportedContext(mClassLoaderContext)) {
// Can happen if we read a previous version.
mClassLoaderContext = dexUseInfo.mClassLoaderContext;
- } else if (!Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) {
+ } else if (!isUnknownOrUnsupportedContext(dexUseInfo.mClassLoaderContext)
+ && !Objects.equals(mClassLoaderContext, dexUseInfo.mClassLoaderContext)) {
// We detected a context change.
mClassLoaderContext = VARIABLE_CLASS_LOADER_CONTEXT;
}
@@ -857,6 +862,13 @@
|| !Objects.equals(oldClassLoaderContext, mClassLoaderContext);
}
+ private static boolean isUnknownOrUnsupportedContext(String context) {
+ // TODO: Merge UNKNOWN_CLASS_LOADER_CONTEXT & UNSUPPORTED_CLASS_LOADER_CONTEXT cases
+ // into UNSUPPORTED_CLASS_LOADER_CONTEXT.
+ return UNKNOWN_CLASS_LOADER_CONTEXT.equals(context)
+ || UNSUPPORTED_CLASS_LOADER_CONTEXT.equals(context);
+ }
+
public boolean isUsedByOtherApps() {
return mIsUsedByOtherApps;
}
@@ -878,7 +890,7 @@
public boolean isUnknownClassLoaderContext() {
// The class loader context may be unknown if we loaded the data from a previous version
// which didn't save the context.
- return UNKNOWN_CLASS_LOADER_CONTEXT.equals(mClassLoaderContext);
+ return isUnknownOrUnsupportedContext(mClassLoaderContext);
}
public boolean isVariableClassLoaderContext() {
diff --git a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
index a4ba056..cb20b65 100644
--- a/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
+++ b/services/tests/servicestests/src/com/android/server/pm/dex/DexManagerTests.java
@@ -532,6 +532,61 @@
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);
+ }
+
private void assertSecondaryUse(TestData testData, PackageUseInfo pui,
List<String> secondaries, boolean isUsedByOtherApps, int ownerUserId,
String[] expectedContexts) {
@@ -572,17 +627,43 @@
// 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.asList((String) null)
- : Arrays.asList(String.join(File.pathSeparator, dexPaths));
+ 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.
- mDexManager.notifyDexLoadInternal(testData.mPackageInfo.applicationInfo, classLoaders,
- classPaths, testData.mLoaderIsa, loaderUserId);
+ 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) {