From 06530576031ce4222e84e2ba21beb9ae538d8ec9 Mon Sep 17 00:00:00 2001 From: Paul Duffin Date: Thu, 3 Feb 2022 17:54:15 +0000 Subject: [PATCH] Add support for excluding libraries from class loader contexts A number of tests in the cts/tests/signature/api-check check for the accessibility of classes from the android.test.base, android.test.runner and android.test.mock libraries. Some tests expect to find the classes other do not. Unfortunately, the tests use libraries, specifically compatibility-device-util-axt, that depend on the android.test... libraries which causes Soong to implicitly add entries to the manifest so that they will be accessible at runtime. That causes the tests that do not expect to find the classes to fail. Bug: 209607558 Test: m nothing Change-Id: I54c194ab23d5a70df790ece3fe98f2b3d6a1c1f6 --- dexpreopt/class_loader_context.go | 89 +++++++++++++++++++++ dexpreopt/class_loader_context_test.go | 105 +++++++++++++++++++++++++ java/aar.go | 8 +- java/app.go | 20 ++++- java/rro.go | 2 +- 5 files changed, 220 insertions(+), 4 deletions(-) diff --git a/dexpreopt/class_loader_context.go b/dexpreopt/class_loader_context.go index 658e8e2dc..36513b64b 100644 --- a/dexpreopt/class_loader_context.go +++ b/dexpreopt/class_loader_context.go @@ -210,6 +210,34 @@ type ClassLoaderContext struct { Subcontexts []*ClassLoaderContext } +// excludeLibs excludes the libraries from this ClassLoaderContext. +// +// This treats the supplied context as being immutable (as it may come from a dependency). So, it +// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used +// within this context then this will return a deep copy of this without those libraries. +// +// If this ClassLoaderContext matches one of the libraries to exclude then this returns (nil, true) +// to indicate that this context should be excluded from the containing list. +// +// If any of this ClassLoaderContext's Subcontexts reference the excluded libraries then this +// returns a pointer to a copy of this without the excluded libraries and true to indicate that this +// was copied. +// +// Otherwise, this returns a pointer to this and false to indicate that this was not copied. +func (c *ClassLoaderContext) excludeLibs(excludedLibs []string) (*ClassLoaderContext, bool) { + if android.InList(c.Name, excludedLibs) { + return nil, true + } + + if excludedList, modified := excludeLibsFromCLCList(c.Subcontexts, excludedLibs); modified { + clcCopy := *c + clcCopy.Subcontexts = excludedList + return &clcCopy, true + } + + return c, false +} + // ClassLoaderContextMap is a map from SDK version to CLC. There is a special entry with key // AnySdkVersion that stores unconditional CLC that is added regardless of the target SDK version. // @@ -408,6 +436,67 @@ func (clcMap ClassLoaderContextMap) Dump() string { return string(bytes) } +// excludeLibsFromCLCList excludes the libraries from the ClassLoaderContext in this list. +// +// This treats the supplied list as being immutable (as it may come from a dependency). So, it +// implements copy-on-exclusion logic. That means that if any of the excluded libraries are used +// within the contexts in the list then this will return a deep copy of the list without those +// libraries. +// +// If any of the ClassLoaderContext in the list reference the excluded libraries then this returns a +// copy of this list without the excluded libraries and true to indicate that this was copied. +// +// Otherwise, this returns the list and false to indicate that this was not copied. +func excludeLibsFromCLCList(clcList []*ClassLoaderContext, excludedLibs []string) ([]*ClassLoaderContext, bool) { + modifiedList := false + copiedList := make([]*ClassLoaderContext, 0, len(clcList)) + for _, clc := range clcList { + resultClc, modifiedClc := clc.excludeLibs(excludedLibs) + if resultClc != nil { + copiedList = append(copiedList, resultClc) + } + modifiedList = modifiedList || modifiedClc + } + + if modifiedList { + return copiedList, true + } else { + return clcList, false + } +} + +// ExcludeLibs excludes the libraries from the ClassLoaderContextMap. +// +// If the list o libraries is empty then this returns the ClassLoaderContextMap. +// +// This treats the ClassLoaderContextMap as being immutable (as it may come from a dependency). So, +// it implements copy-on-exclusion logic. That means that if any of the excluded libraries are used +// within the contexts in the map then this will return a deep copy of the map without those +// libraries. +// +// Otherwise, this returns the map unchanged. +func (clcMap ClassLoaderContextMap) ExcludeLibs(excludedLibs []string) ClassLoaderContextMap { + if len(excludedLibs) == 0 { + return clcMap + } + + excludedClcMap := make(ClassLoaderContextMap) + modifiedMap := false + for sdkVersion, clcList := range clcMap { + excludedList, modifiedList := excludeLibsFromCLCList(clcList, excludedLibs) + if len(excludedList) != 0 { + excludedClcMap[sdkVersion] = excludedList + } + modifiedMap = modifiedMap || modifiedList + } + + if modifiedMap { + return excludedClcMap + } else { + return clcMap + } +} + // Now that the full unconditional context is known, reconstruct conditional context. // Apply filters for individual libraries, mirroring what the PackageManager does when it // constructs class loader context on device. diff --git a/dexpreopt/class_loader_context_test.go b/dexpreopt/class_loader_context_test.go index 4a3d390c2..5d3a9d943 100644 --- a/dexpreopt/class_loader_context_test.go +++ b/dexpreopt/class_loader_context_test.go @@ -284,6 +284,111 @@ func TestCLCSdkVersionOrder(t *testing.T) { }) } +func TestCLCMExcludeLibs(t *testing.T) { + ctx := testContext() + const optional = false + const implicit = true + + excludeLibs := func(t *testing.T, m ClassLoaderContextMap, excluded_libs ...string) ClassLoaderContextMap { + // Dump the CLCM before creating a new copy that excludes a specific set of libraries. + before := m.Dump() + + // Create a new CLCM that excludes some libraries. + c := m.ExcludeLibs(excluded_libs) + + // Make sure that the original CLCM was not changed. + after := m.Dump() + android.AssertStringEquals(t, "input CLCM modified", before, after) + + return c + } + + t.Run("exclude nothing", func(t *testing.T) { + m := make(ClassLoaderContextMap) + m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil) + + a := excludeLibs(t, m) + + android.AssertStringEquals(t, "output CLCM ", `{ + "28": [ + { + "Name": "a", + "Optional": false, + "Implicit": true, + "Host": "out/soong/a.jar", + "Device": "/system/a.jar", + "Subcontexts": [] + } + ] +}`, a.Dump()) + }) + + t.Run("one item from list", func(t *testing.T) { + m := make(ClassLoaderContextMap) + m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil) + m.AddContext(ctx, 28, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil) + + a := excludeLibs(t, m, "a") + + expected := `{ + "28": [ + { + "Name": "b", + "Optional": false, + "Implicit": true, + "Host": "out/soong/b.jar", + "Device": "/system/b.jar", + "Subcontexts": [] + } + ] +}` + android.AssertStringEquals(t, "output CLCM ", expected, a.Dump()) + }) + + t.Run("all items from a list", func(t *testing.T) { + m := make(ClassLoaderContextMap) + m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), nil) + m.AddContext(ctx, 28, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil) + + a := excludeLibs(t, m, "a", "b") + + android.AssertStringEquals(t, "output CLCM ", `{}`, a.Dump()) + }) + + t.Run("items from a subcontext", func(t *testing.T) { + s := make(ClassLoaderContextMap) + s.AddContext(ctx, AnySdkVersion, "b", optional, implicit, buildPath(ctx, "b"), installPath(ctx, "b"), nil) + s.AddContext(ctx, AnySdkVersion, "c", optional, implicit, buildPath(ctx, "c"), installPath(ctx, "c"), nil) + + m := make(ClassLoaderContextMap) + m.AddContext(ctx, 28, "a", optional, implicit, buildPath(ctx, "a"), installPath(ctx, "a"), s) + + a := excludeLibs(t, m, "b") + + android.AssertStringEquals(t, "output CLCM ", `{ + "28": [ + { + "Name": "a", + "Optional": false, + "Implicit": true, + "Host": "out/soong/a.jar", + "Device": "/system/a.jar", + "Subcontexts": [ + { + "Name": "c", + "Optional": false, + "Implicit": true, + "Host": "out/soong/c.jar", + "Device": "/system/c.jar", + "Subcontexts": [] + } + ] + } + ] +}`, a.Dump()) + }) +} + func checkError(t *testing.T, have error, want string) { if have == nil { t.Errorf("\nwant error: '%s'\nhave: none", want) diff --git a/java/aar.go b/java/aar.go index 4687424c2..51aad8da0 100644 --- a/java/aar.go +++ b/java/aar.go @@ -267,11 +267,15 @@ var extractAssetsRule = pctx.AndroidStaticRule("extractAssets", }) func (a *aapt) buildActions(ctx android.ModuleContext, sdkContext android.SdkContext, - classLoaderContexts dexpreopt.ClassLoaderContextMap, extraLinkFlags ...string) { + classLoaderContexts dexpreopt.ClassLoaderContextMap, excludedLibs []string, + extraLinkFlags ...string) { transitiveStaticLibs, transitiveStaticLibManifests, staticRRODirs, assetPackages, libDeps, libFlags := aaptLibs(ctx, sdkContext, classLoaderContexts) + // Exclude any libraries from the supplied list. + classLoaderContexts = classLoaderContexts.ExcludeLibs(excludedLibs) + // App manifest file manifestFile := proptools.StringDefault(a.aaptProperties.Manifest, "AndroidManifest.xml") manifestSrcPath := android.PathForModuleSrc(ctx, manifestFile) @@ -530,7 +534,7 @@ func (a *AndroidLibrary) DepsMutator(ctx android.BottomUpMutatorContext) { func (a *AndroidLibrary) GenerateAndroidBuildActions(ctx android.ModuleContext) { a.aapt.isLibrary = true a.classLoaderContexts = a.usesLibrary.classLoaderContextForUsesLibDeps(ctx) - a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts) + a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, nil) a.hideApexVariantFromMake = !ctx.Provider(android.ApexInfoProvider).(android.ApexInfo).IsForPlatform() diff --git a/java/app.go b/java/app.go index 9f2f99a16..e4432ff4b 100755 --- a/java/app.go +++ b/java/app.go @@ -425,7 +425,8 @@ func (a *AndroidApp) aaptBuildActions(ctx android.ModuleContext) { a.aapt.splitNames = a.appProperties.Package_splits a.aapt.LoggingParent = String(a.overridableAppProperties.Logging_parent) - a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, aaptLinkFlags...) + a.aapt.buildActions(ctx, android.SdkContext(a), a.classLoaderContexts, + a.usesLibraryProperties.Exclude_uses_libs, aaptLinkFlags...) // apps manifests are handled by aapt, don't let Module see them a.properties.Manifest = nil @@ -1211,6 +1212,23 @@ type UsesLibraryProperties struct { // libraries, because SDK ones are automatically picked up by Soong. The name // normally is the same as the module name, but there are exceptions. Provides_uses_lib *string + + // A list of shared library names to exclude from the classpath of the APK. Adding a library here + // will prevent it from being used when precompiling the APK and prevent it from being implicitly + // added to the APK's manifest's elements. + // + // Care must be taken when using this as it could result in runtime errors if the APK actually + // uses classes provided by the library and which are not provided in any other way. + // + // This is primarily intended for use by various CTS tests that check the runtime handling of the + // android.test.base shared library (and related libraries) but which depend on some common + // libraries that depend on the android.test.base library. Without this those tests will end up + // with a in their manifest which would either + // render the tests worthless (as they would be testing the wrong behavior), or would break the + // test altogether by providing access to classes that the tests were not expecting. Those tests + // provide the android.test.base statically and use jarjar to rename them so they do not collide + // with the classes provided by the android.test.base library. + Exclude_uses_libs []string } // usesLibrary provides properties and helper functions for AndroidApp and AndroidAppImport to verify that the diff --git a/java/rro.go b/java/rro.go index 0b4d0916a..be84afffc 100644 --- a/java/rro.go +++ b/java/rro.go @@ -139,7 +139,7 @@ func (r *RuntimeResourceOverlay) GenerateAndroidBuildActions(ctx android.ModuleC aaptLinkFlags = append(aaptLinkFlags, "--rename-overlay-target-package "+*r.overridableProperties.Target_package_name) } - r.aapt.buildActions(ctx, r, nil, aaptLinkFlags...) + r.aapt.buildActions(ctx, r, nil, nil, aaptLinkFlags...) // Sign the built package _, certificates := collectAppDeps(ctx, r, false, false)