// Copyright 2024 Google Inc. All rights reserved. // // 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 android import ( "fmt" "reflect" "slices" "strings" "github.com/google/blueprint" ) // ---------------------------------------------------------------------------- // Start of the definitions of exception functions and the lookup table. // // Functions cannot be used as a value passed in providers, because functions are not // hashable. As a workaround, the [exceptionHandleFuncLabel] enum values are passed using providers, // and the corresponding functions are called from [exceptionHandleFunctionsTable] map. // ---------------------------------------------------------------------------- type exceptionHandleFunc func(ModuleContext, Module, Module) bool type StubsAvailableModule interface { IsStubsModule() bool } // Returns true if the dependency module is a stubs module var depIsStubsModule exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool { if stubsModule, ok := dep.(StubsAvailableModule); ok { return stubsModule.IsStubsModule() } return false } // Returns true if the dependency module belongs to any of the apexes. var depIsApexModule exceptionHandleFunc = func(mctx ModuleContext, _, dep Module) bool { depContainersInfo, _ := getContainerModuleInfo(mctx, dep) return InList(ApexContainer, depContainersInfo.belongingContainers) } // Returns true if the module and the dependent module belongs to common apexes. var belongsToCommonApexes exceptionHandleFunc = func(mctx ModuleContext, m, dep Module) bool { mContainersInfo, _ := getContainerModuleInfo(mctx, m) depContainersInfo, _ := getContainerModuleInfo(mctx, dep) return HasIntersection(mContainersInfo.ApexNames(), depContainersInfo.ApexNames()) } // Returns true when all apexes that the module belongs to are non updatable. // For an apex module to be allowed to depend on a non-apex partition module, // all apexes that the module belong to must be non updatable. var belongsToNonUpdatableApex exceptionHandleFunc = func(mctx ModuleContext, m, _ Module) bool { mContainersInfo, _ := getContainerModuleInfo(mctx, m) return !mContainersInfo.UpdatableApex() } // Returns true if the dependency is added via dependency tags that are not used to tag dynamic // dependency tags. var depIsNotDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m, dep Module) bool { mInstallable, _ := m.(InstallableModule) depTag := ctx.OtherModuleDependencyTag(dep) return !InList(depTag, mInstallable.DynamicDependencyTags()) } // Returns true if the dependency is added via dependency tags that are not used to tag static // or dynamic dependency tags. These dependencies do not affect the module in compile time or in // runtime, thus are not significant enough to raise an error. var depIsNotStaticOrDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m, dep Module) bool { mInstallable, _ := m.(InstallableModule) depTag := ctx.OtherModuleDependencyTag(dep) return !InList(depTag, append(mInstallable.StaticDependencyTags(), mInstallable.DynamicDependencyTags()...)) } var globallyAllowlistedDependencies = []string{ // Modules that provide annotations used within the platform and apexes. "aconfig-annotations-lib", "framework-annotations-lib", "unsupportedappusage", // TODO(b/363016634): Remove from the allowlist when the module is converted // to java_sdk_library and the java_aconfig_library modules depend on the stub. "aconfig_storage_stub", // framework-res provides core resources essential for building apps and system UI. // This module is implicitly added as a dependency for java modules even when the // dependency specifies sdk_version. "framework-res", // jacocoagent is implicitly added as a dependency in coverage builds, and is not installed // on the device. "jacocoagent", } // Returns true when the dependency is globally allowlisted for inter-container dependency var depIsGloballyAllowlisted exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool { return InList(dep.Name(), globallyAllowlistedDependencies) } // Labels of exception functions, which are used to determine special dependencies that allow // otherwise restricted inter-container dependencies type exceptionHandleFuncLabel int const ( checkStubs exceptionHandleFuncLabel = iota checkApexModule checkInCommonApexes checkApexIsNonUpdatable checkNotDynamicDepTag checkNotStaticOrDynamicDepTag checkGlobalAllowlistedDep ) // Map of [exceptionHandleFuncLabel] to the [exceptionHandleFunc] var exceptionHandleFunctionsTable = map[exceptionHandleFuncLabel]exceptionHandleFunc{ checkStubs: depIsStubsModule, checkApexModule: depIsApexModule, checkInCommonApexes: belongsToCommonApexes, checkApexIsNonUpdatable: belongsToNonUpdatableApex, checkNotDynamicDepTag: depIsNotDynamicDepTag, checkNotStaticOrDynamicDepTag: depIsNotStaticOrDynamicDepTag, checkGlobalAllowlistedDep: depIsGloballyAllowlisted, } // ---------------------------------------------------------------------------- // Start of the definitions of container determination functions. // // Similar to the above section, below defines the functions used to determine // the container of each modules. // ---------------------------------------------------------------------------- type containerBoundaryFunc func(mctx ModuleContext) bool var vendorContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { m, ok := mctx.Module().(ImageInterface) return mctx.Module().InstallInVendor() || (ok && m.VendorVariantNeeded(mctx)) } var systemContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { module := mctx.Module() return !module.InstallInTestcases() && !module.InstallInData() && !module.InstallInRamdisk() && !module.InstallInVendorRamdisk() && !module.InstallInDebugRamdisk() && !module.InstallInRecovery() && !module.InstallInVendor() && !module.InstallInOdm() && !module.InstallInProduct() && determineModuleKind(module.base(), mctx.blueprintBaseModuleContext()) == platformModule } var productContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { m, ok := mctx.Module().(ImageInterface) return mctx.Module().InstallInProduct() || (ok && m.ProductVariantNeeded(mctx)) } var apexContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { _, ok := ModuleProvider(mctx, AllApexInfoProvider) return ok } var ctsContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { props := mctx.Module().GetProperties() for _, prop := range props { val := reflect.ValueOf(prop).Elem() if val.Kind() == reflect.Struct { testSuites := val.FieldByName("Test_suites") if testSuites.IsValid() && testSuites.Kind() == reflect.Slice && slices.Contains(testSuites.Interface().([]string), "cts") { return true } } } return false } type unstableInfo struct { // Determines if the module contains the private APIs of the platform. ContainsPlatformPrivateApis bool } var unstableInfoProvider = blueprint.NewProvider[unstableInfo]() func determineUnstableModule(mctx ModuleContext) bool { module := mctx.Module() unstableModule := module.Name() == "framework-minus-apex" if installable, ok := module.(InstallableModule); ok { for _, staticDepTag := range installable.StaticDependencyTags() { mctx.VisitDirectDepsWithTag(staticDepTag, func(dep Module) { if unstableInfo, ok := OtherModuleProvider(mctx, dep, unstableInfoProvider); ok { unstableModule = unstableModule || unstableInfo.ContainsPlatformPrivateApis } }) } } return unstableModule } var unstableContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool { return determineUnstableModule(mctx) } // Map of [*container] to the [containerBoundaryFunc] var containerBoundaryFunctionsTable = map[*container]containerBoundaryFunc{ VendorContainer: vendorContainerBoundaryFunc, SystemContainer: systemContainerBoundaryFunc, ProductContainer: productContainerBoundaryFunc, ApexContainer: apexContainerBoundaryFunc, CtsContainer: ctsContainerBoundaryFunc, UnstableContainer: unstableContainerBoundaryFunc, } // ---------------------------------------------------------------------------- // End of the definitions of container determination functions. // ---------------------------------------------------------------------------- type InstallableModule interface { StaticDependencyTags() []blueprint.DependencyTag DynamicDependencyTags() []blueprint.DependencyTag } type restriction struct { // container of the dependency dependency *container // Error message to be emitted to the user when the dependency meets this restriction errorMessage string // List of labels of allowed exception functions that allows bypassing this restriction. // If any of the functions mapped to each labels returns true, this dependency would be // considered allowed and an error will not be thrown. allowedExceptions []exceptionHandleFuncLabel } type container struct { // The name of the container i.e. partition, api domain name string // Map of dependency restricted containers. restricted []restriction } var ( VendorContainer = &container{ name: VendorVariation, restricted: nil, } SystemContainer = &container{ name: "system", restricted: []restriction{ { dependency: VendorContainer, errorMessage: "Module belonging to the system partition other than HALs is " + "not allowed to depend on the vendor partition module, in order to support " + "independent development/update cycles and to support the Generic System " + "Image. Try depending on HALs, VNDK or AIDL instead.", allowedExceptions: []exceptionHandleFuncLabel{ checkStubs, checkNotDynamicDepTag, checkGlobalAllowlistedDep, }, }, }, } ProductContainer = &container{ name: ProductVariation, restricted: []restriction{ { dependency: VendorContainer, errorMessage: "Module belonging to the product partition is not allowed to " + "depend on the vendor partition module, as this may lead to security " + "vulnerabilities. Try depending on the HALs or utilize AIDL instead.", allowedExceptions: []exceptionHandleFuncLabel{ checkStubs, checkNotDynamicDepTag, checkGlobalAllowlistedDep, }, }, }, } ApexContainer = initializeApexContainer() CtsContainer = &container{ name: "cts", restricted: []restriction{ { dependency: UnstableContainer, errorMessage: "CTS module should not depend on the modules that contain the " + "platform implementation details, including \"framework\". Depending on these " + "modules may lead to disclosure of implementation details and regression " + "due to API changes across platform versions. Try depending on the stubs instead " + "and ensure that the module sets an appropriate 'sdk_version'.", allowedExceptions: []exceptionHandleFuncLabel{ checkStubs, checkNotStaticOrDynamicDepTag, checkGlobalAllowlistedDep, }, }, }, } // Container signifying that the module contains unstable platform private APIs UnstableContainer = &container{ name: "unstable", restricted: nil, } allContainers = []*container{ VendorContainer, SystemContainer, ProductContainer, ApexContainer, CtsContainer, UnstableContainer, } ) func initializeApexContainer() *container { apexContainer := &container{ name: "apex", restricted: []restriction{ { dependency: SystemContainer, errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " + "modules belonging to the system partition. Either statically depend on the " + "module or convert the depending module to java_sdk_library and depend on " + "the stubs.", allowedExceptions: []exceptionHandleFuncLabel{ checkStubs, checkApexModule, checkInCommonApexes, checkApexIsNonUpdatable, checkNotStaticOrDynamicDepTag, checkGlobalAllowlistedDep, }, }, }, } apexContainer.restricted = append(apexContainer.restricted, restriction{ dependency: apexContainer, errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " + "modules belonging to other Apex(es). Either include the depending " + "module in the Apex or convert the depending module to java_sdk_library " + "and depend on its stubs.", allowedExceptions: []exceptionHandleFuncLabel{ checkStubs, checkInCommonApexes, checkNotStaticOrDynamicDepTag, checkGlobalAllowlistedDep, }, }) return apexContainer } type ContainersInfo struct { belongingContainers []*container belongingApexes []ApexInfo } func (c *ContainersInfo) BelongingContainers() []*container { return c.belongingContainers } func (c *ContainersInfo) ApexNames() (ret []string) { for _, apex := range c.belongingApexes { ret = append(ret, apex.InApexVariants...) } slices.Sort(ret) return ret } // Returns true if any of the apex the module belongs to is updatable. func (c *ContainersInfo) UpdatableApex() bool { for _, apex := range c.belongingApexes { if apex.Updatable { return true } } return false } var ContainersInfoProvider = blueprint.NewProvider[ContainersInfo]() func satisfyAllowedExceptions(ctx ModuleContext, allowedExceptionLabels []exceptionHandleFuncLabel, m, dep Module) bool { for _, label := range allowedExceptionLabels { if exceptionHandleFunctionsTable[label](ctx, m, dep) { return true } } return false } func (c *ContainersInfo) GetViolations(mctx ModuleContext, m, dep Module, depInfo ContainersInfo) []string { var violations []string // Any containers that the module belongs to but the dependency does not belong to must be examined. _, containersUniqueToModule, _ := ListSetDifference(c.belongingContainers, depInfo.belongingContainers) // Apex container should be examined even if both the module and the dependency belong to // the apex container to check that the two modules belong to the same apex. if InList(ApexContainer, c.belongingContainers) && !InList(ApexContainer, containersUniqueToModule) { containersUniqueToModule = append(containersUniqueToModule, ApexContainer) } for _, containerUniqueToModule := range containersUniqueToModule { for _, restriction := range containerUniqueToModule.restricted { if InList(restriction.dependency, depInfo.belongingContainers) { if !satisfyAllowedExceptions(mctx, restriction.allowedExceptions, m, dep) { violations = append(violations, restriction.errorMessage) } } } } return violations } func generateContainerInfo(ctx ModuleContext) ContainersInfo { var containers []*container for _, cnt := range allContainers { if containerBoundaryFunctionsTable[cnt](ctx) { containers = append(containers, cnt) } } var belongingApexes []ApexInfo if apexInfo, ok := ModuleProvider(ctx, AllApexInfoProvider); ok { belongingApexes = apexInfo.ApexInfos } return ContainersInfo{ belongingContainers: containers, belongingApexes: belongingApexes, } } func getContainerModuleInfo(ctx ModuleContext, module Module) (ContainersInfo, bool) { if ctx.Module() == module { return ctx.getContainersInfo(), true } return OtherModuleProvider(ctx, module, ContainersInfoProvider) } func setContainerInfo(ctx ModuleContext) { // Required to determine the unstable container. This provider is set here instead of the // unstableContainerBoundaryFunc in order to prevent setting the provider multiple times. SetProvider(ctx, unstableInfoProvider, unstableInfo{ ContainsPlatformPrivateApis: determineUnstableModule(ctx), }) if _, ok := ctx.Module().(InstallableModule); ok { containersInfo := generateContainerInfo(ctx) ctx.setContainersInfo(containersInfo) SetProvider(ctx, ContainersInfoProvider, containersInfo) } } func checkContainerViolations(ctx ModuleContext) { if _, ok := ctx.Module().(InstallableModule); ok { containersInfo, _ := getContainerModuleInfo(ctx, ctx.Module()) ctx.VisitDirectDeps(func(dep Module) { if !dep.Enabled(ctx) { return } // Pre-existing violating dependencies are tracked in containerDependencyViolationAllowlist. // If this dependency is allowlisted, do not check for violation. // If not, check if this dependency matches any restricted dependency and // satisfies any exception functions, which allows bypassing the // restriction. If all of the exceptions are not satisfied, throw an error. if depContainersInfo, ok := getContainerModuleInfo(ctx, dep); ok { if allowedViolations, ok := ContainerDependencyViolationAllowlist[ctx.ModuleName()]; ok && InList(dep.Name(), allowedViolations) { return } else { violations := containersInfo.GetViolations(ctx, ctx.Module(), dep, depContainersInfo) if len(violations) > 0 { errorMessage := fmt.Sprintf("%s cannot depend on %s. ", ctx.ModuleName(), dep.Name()) errorMessage += strings.Join(violations, " ") ctx.ModuleErrorf(errorMessage) } } } }) } }