blob: 99e6a307502e6a88d890ed60a2d82324598f5aee [file] [log] [blame]
Jihoon Kangc3d4e112024-06-24 22:16:27 +00001// Copyright 2024 Google Inc. All rights reserved.
2//
3// Licensed under the Apache License, Version 2.0 (the "License");
4// you may not use this file except in compliance with the License.
5// You may obtain a copy of the License at
6//
7// http://www.apache.org/licenses/LICENSE-2.0
8//
9// Unless required by applicable law or agreed to in writing, software
10// distributed under the License is distributed on an "AS IS" BASIS,
11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12// See the License for the specific language governing permissions and
13// limitations under the License.
14
15package android
16
17import (
Jihoon Kang85bc1932024-07-01 17:04:46 +000018 "fmt"
Jihoon Kangc3d4e112024-06-24 22:16:27 +000019 "reflect"
20 "slices"
Jihoon Kang85bc1932024-07-01 17:04:46 +000021 "strings"
Jihoon Kangc3d4e112024-06-24 22:16:27 +000022
23 "github.com/google/blueprint"
24)
25
Jihoon Kang17a61d72024-08-12 22:26:52 +000026// ----------------------------------------------------------------------------
27// Start of the definitions of exception functions and the lookup table.
28//
29// Functions cannot be used as a value passed in providers, because functions are not
30// hashable. As a workaround, the [exceptionHandleFuncLabel] enum values are passed using providers,
31// and the corresponding functions are called from [exceptionHandleFunctionsTable] map.
32// ----------------------------------------------------------------------------
33
34type exceptionHandleFunc func(ModuleContext, Module, Module) bool
35
Jihoon Kangc3d4e112024-06-24 22:16:27 +000036type StubsAvailableModule interface {
37 IsStubsModule() bool
38}
39
40// Returns true if the dependency module is a stubs module
Jihoon Kang17a61d72024-08-12 22:26:52 +000041var depIsStubsModule exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool {
Jihoon Kangc3d4e112024-06-24 22:16:27 +000042 if stubsModule, ok := dep.(StubsAvailableModule); ok {
43 return stubsModule.IsStubsModule()
44 }
45 return false
46}
47
Jihoon Kang224ea082024-08-12 22:38:16 +000048// Returns true if the dependency module belongs to any of the apexes.
49var depIsApexModule exceptionHandleFunc = func(mctx ModuleContext, _, dep Module) bool {
50 depContainersInfo, _ := getContainerModuleInfo(mctx, dep)
51 return InList(ApexContainer, depContainersInfo.belongingContainers)
52}
53
54// Returns true if the module and the dependent module belongs to common apexes.
55var belongsToCommonApexes exceptionHandleFunc = func(mctx ModuleContext, m, dep Module) bool {
56 mContainersInfo, _ := getContainerModuleInfo(mctx, m)
57 depContainersInfo, _ := getContainerModuleInfo(mctx, dep)
58
59 return HasIntersection(mContainersInfo.ApexNames(), depContainersInfo.ApexNames())
60}
61
62// Returns true when all apexes that the module belongs to are non updatable.
63// For an apex module to be allowed to depend on a non-apex partition module,
64// all apexes that the module belong to must be non updatable.
65var belongsToNonUpdatableApex exceptionHandleFunc = func(mctx ModuleContext, m, _ Module) bool {
66 mContainersInfo, _ := getContainerModuleInfo(mctx, m)
67
68 return !mContainersInfo.UpdatableApex()
69}
70
71// Returns true if the dependency is added via dependency tags that are not used to tag dynamic
72// dependency tags.
73var depIsNotDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m, dep Module) bool {
74 mInstallable, _ := m.(InstallableModule)
75 depTag := ctx.OtherModuleDependencyTag(dep)
76 return !InList(depTag, mInstallable.DynamicDependencyTags())
77}
78
79// Returns true if the dependency is added via dependency tags that are not used to tag static
80// or dynamic dependency tags. These dependencies do not affect the module in compile time or in
81// runtime, thus are not significant enough to raise an error.
82var depIsNotStaticOrDynamicDepTag exceptionHandleFunc = func(ctx ModuleContext, m, dep Module) bool {
83 mInstallable, _ := m.(InstallableModule)
84 depTag := ctx.OtherModuleDependencyTag(dep)
85 return !InList(depTag, append(mInstallable.StaticDependencyTags(), mInstallable.DynamicDependencyTags()...))
86}
87
88var globallyAllowlistedDependencies = []string{
89 // Modules that provide annotations used within the platform and apexes.
90 "aconfig-annotations-lib",
91 "framework-annotations-lib",
92 "unsupportedappusage",
93
Jihoon Kangbf65d1b2024-08-29 18:49:00 +000094 // TODO(b/363016634): Remove from the allowlist when the module is converted
95 // to java_sdk_library and the java_aconfig_library modules depend on the stub.
zhidouaed2e742024-11-20 03:55:07 +000096 "aconfig_storage_stub",
Jihoon Kangbf65d1b2024-08-29 18:49:00 +000097
Jihoon Kang224ea082024-08-12 22:38:16 +000098 // framework-res provides core resources essential for building apps and system UI.
99 // This module is implicitly added as a dependency for java modules even when the
100 // dependency specifies sdk_version.
101 "framework-res",
Jihoon Kang601939d2024-08-27 17:27:52 +0000102
103 // jacocoagent is implicitly added as a dependency in coverage builds, and is not installed
104 // on the device.
105 "jacocoagent",
Jihoon Kang224ea082024-08-12 22:38:16 +0000106}
107
108// Returns true when the dependency is globally allowlisted for inter-container dependency
109var depIsGloballyAllowlisted exceptionHandleFunc = func(_ ModuleContext, _, dep Module) bool {
110 return InList(dep.Name(), globallyAllowlistedDependencies)
111}
112
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000113// Labels of exception functions, which are used to determine special dependencies that allow
114// otherwise restricted inter-container dependencies
115type exceptionHandleFuncLabel int
116
117const (
118 checkStubs exceptionHandleFuncLabel = iota
Jihoon Kang224ea082024-08-12 22:38:16 +0000119 checkApexModule
120 checkInCommonApexes
121 checkApexIsNonUpdatable
122 checkNotDynamicDepTag
123 checkNotStaticOrDynamicDepTag
124 checkGlobalAllowlistedDep
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000125)
126
Jihoon Kang224ea082024-08-12 22:38:16 +0000127// Map of [exceptionHandleFuncLabel] to the [exceptionHandleFunc]
Jihoon Kang17a61d72024-08-12 22:26:52 +0000128var exceptionHandleFunctionsTable = map[exceptionHandleFuncLabel]exceptionHandleFunc{
Jihoon Kang224ea082024-08-12 22:38:16 +0000129 checkStubs: depIsStubsModule,
130 checkApexModule: depIsApexModule,
131 checkInCommonApexes: belongsToCommonApexes,
132 checkApexIsNonUpdatable: belongsToNonUpdatableApex,
133 checkNotDynamicDepTag: depIsNotDynamicDepTag,
134 checkNotStaticOrDynamicDepTag: depIsNotStaticOrDynamicDepTag,
135 checkGlobalAllowlistedDep: depIsGloballyAllowlisted,
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000136}
137
Jihoon Kang17a61d72024-08-12 22:26:52 +0000138// ----------------------------------------------------------------------------
139// Start of the definitions of container determination functions.
140//
141// Similar to the above section, below defines the functions used to determine
142// the container of each modules.
143// ----------------------------------------------------------------------------
144
145type containerBoundaryFunc func(mctx ModuleContext) bool
146
147var vendorContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
148 m, ok := mctx.Module().(ImageInterface)
149 return mctx.Module().InstallInVendor() || (ok && m.VendorVariantNeeded(mctx))
150}
151
152var systemContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
153 module := mctx.Module()
154
155 return !module.InstallInTestcases() &&
156 !module.InstallInData() &&
157 !module.InstallInRamdisk() &&
158 !module.InstallInVendorRamdisk() &&
159 !module.InstallInDebugRamdisk() &&
160 !module.InstallInRecovery() &&
161 !module.InstallInVendor() &&
162 !module.InstallInOdm() &&
163 !module.InstallInProduct() &&
164 determineModuleKind(module.base(), mctx.blueprintBaseModuleContext()) == platformModule
165}
166
167var productContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
168 m, ok := mctx.Module().(ImageInterface)
169 return mctx.Module().InstallInProduct() || (ok && m.ProductVariantNeeded(mctx))
170}
171
172var apexContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
173 _, ok := ModuleProvider(mctx, AllApexInfoProvider)
174 return ok
175}
176
177var ctsContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
178 props := mctx.Module().GetProperties()
179 for _, prop := range props {
180 val := reflect.ValueOf(prop).Elem()
181 if val.Kind() == reflect.Struct {
182 testSuites := val.FieldByName("Test_suites")
183 if testSuites.IsValid() && testSuites.Kind() == reflect.Slice && slices.Contains(testSuites.Interface().([]string), "cts") {
184 return true
185 }
186 }
187 }
188 return false
189}
190
Jihoon Kang0f3b1a72024-08-12 22:47:01 +0000191type unstableInfo struct {
192 // Determines if the module contains the private APIs of the platform.
193 ContainsPlatformPrivateApis bool
194}
195
196var unstableInfoProvider = blueprint.NewProvider[unstableInfo]()
197
198func determineUnstableModule(mctx ModuleContext) bool {
199 module := mctx.Module()
Makoto Onuki31db53a2024-12-06 16:40:47 -0800200
201 // TODO(b/383559945) Remove "framework-minus-apex_jarjar-sharded" once
202 // we remove this module.
203 unstableModule := module.Name() == "framework-minus-apex" || module.Name() == "framework-minus-apex_jarjar-sharded"
Jihoon Kang0f3b1a72024-08-12 22:47:01 +0000204 if installable, ok := module.(InstallableModule); ok {
205 for _, staticDepTag := range installable.StaticDependencyTags() {
206 mctx.VisitDirectDepsWithTag(staticDepTag, func(dep Module) {
207 if unstableInfo, ok := OtherModuleProvider(mctx, dep, unstableInfoProvider); ok {
208 unstableModule = unstableModule || unstableInfo.ContainsPlatformPrivateApis
209 }
210 })
211 }
212 }
213 return unstableModule
214}
215
216var unstableContainerBoundaryFunc containerBoundaryFunc = func(mctx ModuleContext) bool {
217 return determineUnstableModule(mctx)
218}
219
Jihoon Kang17a61d72024-08-12 22:26:52 +0000220// Map of [*container] to the [containerBoundaryFunc]
221var containerBoundaryFunctionsTable = map[*container]containerBoundaryFunc{
Jihoon Kang0f3b1a72024-08-12 22:47:01 +0000222 VendorContainer: vendorContainerBoundaryFunc,
223 SystemContainer: systemContainerBoundaryFunc,
224 ProductContainer: productContainerBoundaryFunc,
225 ApexContainer: apexContainerBoundaryFunc,
226 CtsContainer: ctsContainerBoundaryFunc,
227 UnstableContainer: unstableContainerBoundaryFunc,
Jihoon Kang17a61d72024-08-12 22:26:52 +0000228}
229
230// ----------------------------------------------------------------------------
231// End of the definitions of container determination functions.
232// ----------------------------------------------------------------------------
233
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000234type InstallableModule interface {
Jihoon Kang224ea082024-08-12 22:38:16 +0000235 StaticDependencyTags() []blueprint.DependencyTag
236 DynamicDependencyTags() []blueprint.DependencyTag
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000237}
238
239type restriction struct {
240 // container of the dependency
241 dependency *container
242
243 // Error message to be emitted to the user when the dependency meets this restriction
244 errorMessage string
245
246 // List of labels of allowed exception functions that allows bypassing this restriction.
247 // If any of the functions mapped to each labels returns true, this dependency would be
248 // considered allowed and an error will not be thrown.
249 allowedExceptions []exceptionHandleFuncLabel
250}
251type container struct {
252 // The name of the container i.e. partition, api domain
253 name string
254
255 // Map of dependency restricted containers.
256 restricted []restriction
257}
258
259var (
260 VendorContainer = &container{
261 name: VendorVariation,
262 restricted: nil,
263 }
Jihoon Kang17a61d72024-08-12 22:26:52 +0000264
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000265 SystemContainer = &container{
266 name: "system",
267 restricted: []restriction{
268 {
269 dependency: VendorContainer,
270 errorMessage: "Module belonging to the system partition other than HALs is " +
271 "not allowed to depend on the vendor partition module, in order to support " +
272 "independent development/update cycles and to support the Generic System " +
273 "Image. Try depending on HALs, VNDK or AIDL instead.",
Jihoon Kang224ea082024-08-12 22:38:16 +0000274 allowedExceptions: []exceptionHandleFuncLabel{
275 checkStubs,
276 checkNotDynamicDepTag,
277 checkGlobalAllowlistedDep,
278 },
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000279 },
280 },
281 }
Jihoon Kang17a61d72024-08-12 22:26:52 +0000282
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000283 ProductContainer = &container{
284 name: ProductVariation,
285 restricted: []restriction{
286 {
287 dependency: VendorContainer,
288 errorMessage: "Module belonging to the product partition is not allowed to " +
289 "depend on the vendor partition module, as this may lead to security " +
290 "vulnerabilities. Try depending on the HALs or utilize AIDL instead.",
Jihoon Kang224ea082024-08-12 22:38:16 +0000291 allowedExceptions: []exceptionHandleFuncLabel{
292 checkStubs,
293 checkNotDynamicDepTag,
294 checkGlobalAllowlistedDep,
295 },
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000296 },
297 },
298 }
Jihoon Kang17a61d72024-08-12 22:26:52 +0000299
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000300 ApexContainer = initializeApexContainer()
Jihoon Kang17a61d72024-08-12 22:26:52 +0000301
302 CtsContainer = &container{
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000303 name: "cts",
304 restricted: []restriction{
305 {
Jihoon Kang0f3b1a72024-08-12 22:47:01 +0000306 dependency: UnstableContainer,
307 errorMessage: "CTS module should not depend on the modules that contain the " +
308 "platform implementation details, including \"framework\". Depending on these " +
309 "modules may lead to disclosure of implementation details and regression " +
310 "due to API changes across platform versions. Try depending on the stubs instead " +
311 "and ensure that the module sets an appropriate 'sdk_version'.",
Jihoon Kang224ea082024-08-12 22:38:16 +0000312 allowedExceptions: []exceptionHandleFuncLabel{
313 checkStubs,
314 checkNotStaticOrDynamicDepTag,
315 checkGlobalAllowlistedDep,
316 },
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000317 },
318 },
319 }
Jihoon Kang17a61d72024-08-12 22:26:52 +0000320
Jihoon Kang0f3b1a72024-08-12 22:47:01 +0000321 // Container signifying that the module contains unstable platform private APIs
322 UnstableContainer = &container{
323 name: "unstable",
324 restricted: nil,
325 }
326
Jihoon Kang17a61d72024-08-12 22:26:52 +0000327 allContainers = []*container{
328 VendorContainer,
329 SystemContainer,
330 ProductContainer,
331 ApexContainer,
332 CtsContainer,
Jihoon Kang0f3b1a72024-08-12 22:47:01 +0000333 UnstableContainer,
Jihoon Kang17a61d72024-08-12 22:26:52 +0000334 }
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000335)
336
337func initializeApexContainer() *container {
338 apexContainer := &container{
339 name: "apex",
340 restricted: []restriction{
341 {
342 dependency: SystemContainer,
343 errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
344 "modules belonging to the system partition. Either statically depend on the " +
345 "module or convert the depending module to java_sdk_library and depend on " +
346 "the stubs.",
Jihoon Kang224ea082024-08-12 22:38:16 +0000347 allowedExceptions: []exceptionHandleFuncLabel{
348 checkStubs,
349 checkApexModule,
350 checkInCommonApexes,
351 checkApexIsNonUpdatable,
352 checkNotStaticOrDynamicDepTag,
353 checkGlobalAllowlistedDep,
354 },
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000355 },
356 },
357 }
358
359 apexContainer.restricted = append(apexContainer.restricted, restriction{
360 dependency: apexContainer,
361 errorMessage: "Module belonging to Apex(es) is not allowed to depend on the " +
362 "modules belonging to other Apex(es). Either include the depending " +
363 "module in the Apex or convert the depending module to java_sdk_library " +
364 "and depend on its stubs.",
Jihoon Kang224ea082024-08-12 22:38:16 +0000365 allowedExceptions: []exceptionHandleFuncLabel{
366 checkStubs,
367 checkInCommonApexes,
368 checkNotStaticOrDynamicDepTag,
369 checkGlobalAllowlistedDep,
370 },
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000371 })
372
373 return apexContainer
374}
375
376type ContainersInfo struct {
377 belongingContainers []*container
378
379 belongingApexes []ApexInfo
380}
381
382func (c *ContainersInfo) BelongingContainers() []*container {
383 return c.belongingContainers
384}
385
Jihoon Kang17a61d72024-08-12 22:26:52 +0000386func (c *ContainersInfo) ApexNames() (ret []string) {
387 for _, apex := range c.belongingApexes {
Colin Crossf6978172024-11-19 11:29:46 -0800388 ret = append(ret, apex.InApexVariants...)
Jihoon Kang17a61d72024-08-12 22:26:52 +0000389 }
390 slices.Sort(ret)
391 return ret
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000392}
393
Jihoon Kang17a61d72024-08-12 22:26:52 +0000394// Returns true if any of the apex the module belongs to is updatable.
395func (c *ContainersInfo) UpdatableApex() bool {
396 for _, apex := range c.belongingApexes {
397 if apex.Updatable {
398 return true
399 }
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000400 }
Jihoon Kang17a61d72024-08-12 22:26:52 +0000401 return false
402}
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000403
Jihoon Kang17a61d72024-08-12 22:26:52 +0000404var ContainersInfoProvider = blueprint.NewProvider[ContainersInfo]()
405
Jihoon Kang85bc1932024-07-01 17:04:46 +0000406func satisfyAllowedExceptions(ctx ModuleContext, allowedExceptionLabels []exceptionHandleFuncLabel, m, dep Module) bool {
407 for _, label := range allowedExceptionLabels {
408 if exceptionHandleFunctionsTable[label](ctx, m, dep) {
409 return true
410 }
411 }
412 return false
413}
414
415func (c *ContainersInfo) GetViolations(mctx ModuleContext, m, dep Module, depInfo ContainersInfo) []string {
416 var violations []string
417
418 // Any containers that the module belongs to but the dependency does not belong to must be examined.
419 _, containersUniqueToModule, _ := ListSetDifference(c.belongingContainers, depInfo.belongingContainers)
420
421 // Apex container should be examined even if both the module and the dependency belong to
422 // the apex container to check that the two modules belong to the same apex.
423 if InList(ApexContainer, c.belongingContainers) && !InList(ApexContainer, containersUniqueToModule) {
424 containersUniqueToModule = append(containersUniqueToModule, ApexContainer)
425 }
426
427 for _, containerUniqueToModule := range containersUniqueToModule {
428 for _, restriction := range containerUniqueToModule.restricted {
429 if InList(restriction.dependency, depInfo.belongingContainers) {
430 if !satisfyAllowedExceptions(mctx, restriction.allowedExceptions, m, dep) {
431 violations = append(violations, restriction.errorMessage)
432 }
433 }
434 }
435 }
436
437 return violations
438}
439
Jihoon Kang17a61d72024-08-12 22:26:52 +0000440func generateContainerInfo(ctx ModuleContext) ContainersInfo {
441 var containers []*container
442
443 for _, cnt := range allContainers {
444 if containerBoundaryFunctionsTable[cnt](ctx) {
445 containers = append(containers, cnt)
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000446 }
447 }
448
449 var belongingApexes []ApexInfo
450 if apexInfo, ok := ModuleProvider(ctx, AllApexInfoProvider); ok {
451 belongingApexes = apexInfo.ApexInfos
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000452 }
453
454 return ContainersInfo{
455 belongingContainers: containers,
456 belongingApexes: belongingApexes,
457 }
458}
459
Jihoon Kang224ea082024-08-12 22:38:16 +0000460func getContainerModuleInfo(ctx ModuleContext, module Module) (ContainersInfo, bool) {
461 if ctx.Module() == module {
Yu Liu9a993132024-08-27 23:21:06 +0000462 return ctx.getContainersInfo(), true
Jihoon Kang224ea082024-08-12 22:38:16 +0000463 }
464
465 return OtherModuleProvider(ctx, module, ContainersInfoProvider)
466}
467
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000468func setContainerInfo(ctx ModuleContext) {
Jihoon Kang0f3b1a72024-08-12 22:47:01 +0000469 // Required to determine the unstable container. This provider is set here instead of the
470 // unstableContainerBoundaryFunc in order to prevent setting the provider multiple times.
471 SetProvider(ctx, unstableInfoProvider, unstableInfo{
472 ContainsPlatformPrivateApis: determineUnstableModule(ctx),
473 })
474
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000475 if _, ok := ctx.Module().(InstallableModule); ok {
476 containersInfo := generateContainerInfo(ctx)
Yu Liu9a993132024-08-27 23:21:06 +0000477 ctx.setContainersInfo(containersInfo)
Jihoon Kangc3d4e112024-06-24 22:16:27 +0000478 SetProvider(ctx, ContainersInfoProvider, containersInfo)
479 }
480}
Jihoon Kang85bc1932024-07-01 17:04:46 +0000481
482func checkContainerViolations(ctx ModuleContext) {
483 if _, ok := ctx.Module().(InstallableModule); ok {
484 containersInfo, _ := getContainerModuleInfo(ctx, ctx.Module())
Colin Cross648daea2024-09-12 14:35:29 -0700485 ctx.VisitDirectDeps(func(dep Module) {
Jihoon Kang85bc1932024-07-01 17:04:46 +0000486 if !dep.Enabled(ctx) {
487 return
488 }
489
490 // Pre-existing violating dependencies are tracked in containerDependencyViolationAllowlist.
491 // If this dependency is allowlisted, do not check for violation.
492 // If not, check if this dependency matches any restricted dependency and
493 // satisfies any exception functions, which allows bypassing the
494 // restriction. If all of the exceptions are not satisfied, throw an error.
495 if depContainersInfo, ok := getContainerModuleInfo(ctx, dep); ok {
496 if allowedViolations, ok := ContainerDependencyViolationAllowlist[ctx.ModuleName()]; ok && InList(dep.Name(), allowedViolations) {
497 return
498 } else {
499 violations := containersInfo.GetViolations(ctx, ctx.Module(), dep, depContainersInfo)
500 if len(violations) > 0 {
501 errorMessage := fmt.Sprintf("%s cannot depend on %s. ", ctx.ModuleName(), dep.Name())
502 errorMessage += strings.Join(violations, " ")
503 ctx.ModuleErrorf(errorMessage)
504 }
505 }
506 }
507 })
508 }
509}