1// Copyright 2016 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 cc 16 17import ( 18 "fmt" 19 "path/filepath" 20 "runtime" 21 "strings" 22 "sync" 23 24 "github.com/google/blueprint" 25 "github.com/google/blueprint/proptools" 26 27 "android/soong/android" 28 "android/soong/bazel" 29 "android/soong/cc/config" 30) 31 32func init() { 33 pctx.HostBinToolVariable("ndkStubGenerator", "ndkstubgen") 34 pctx.HostBinToolVariable("abidiff", "abidiff") 35 pctx.HostBinToolVariable("abitidy", "abitidy") 36 pctx.HostBinToolVariable("abidw", "abidw") 37} 38 39var ( 40 genStubSrc = pctx.AndroidStaticRule("genStubSrc", 41 blueprint.RuleParams{ 42 Command: "$ndkStubGenerator --arch $arch --api $apiLevel " + 43 "--api-map $apiMap $flags $in $out", 44 CommandDeps: []string{"$ndkStubGenerator"}, 45 }, "arch", "apiLevel", "apiMap", "flags") 46 47 abidw = pctx.AndroidStaticRule("abidw", 48 blueprint.RuleParams{ 49 Command: "$abidw --type-id-style hash --no-corpus-path " + 50 "--no-show-locs --no-comp-dir-path -w $symbolList " + 51 "$in --out-file $out", 52 CommandDeps: []string{"$abidw"}, 53 }, "symbolList") 54 55 abitidy = pctx.AndroidStaticRule("abitidy", 56 blueprint.RuleParams{ 57 Command: "$abitidy --all $flags -i $in -o $out", 58 CommandDeps: []string{"$abitidy"}, 59 }, "flags") 60 61 abidiff = pctx.AndroidStaticRule("abidiff", 62 blueprint.RuleParams{ 63 // Need to create *some* output for ninja. We don't want to use tee 64 // because we don't want to spam the build output with "nothing 65 // changed" messages, so redirect output message to $out, and if 66 // changes were detected print the output and fail. 67 Command: "$abidiff $args $in > $out || (cat $out && false)", 68 CommandDeps: []string{"$abidiff"}, 69 }, "args") 70 71 ndkLibrarySuffix = ".ndk" 72 73 ndkKnownLibsKey = android.NewOnceKey("ndkKnownLibsKey") 74 // protects ndkKnownLibs writes during parallel BeginMutator. 75 ndkKnownLibsLock sync.Mutex 76 77 stubImplementation = dependencyTag{name: "stubImplementation"} 78) 79 80// The First_version and Unversioned_until properties of this struct should not 81// be used directly, but rather through the ApiLevel returning methods 82// firstVersion() and unversionedUntil(). 83 84// Creates a stub shared library based on the provided version file. 85// 86// Example: 87// 88// ndk_library { 89// 90// name: "libfoo", 91// symbol_file: "libfoo.map.txt", 92// first_version: "9", 93// 94// } 95type libraryProperties struct { 96 // Relative path to the symbol map. 97 // An example file can be seen here: TODO(danalbert): Make an example. 98 Symbol_file *string `android:"path"` 99 100 // The first API level a library was available. A library will be generated 101 // for every API level beginning with this one. 102 First_version *string 103 104 // The first API level that library should have the version script applied. 105 // This defaults to the value of first_version, and should almost never be 106 // used. This is only needed to work around platform bugs like 107 // https://github.com/android-ndk/ndk/issues/265. 108 Unversioned_until *string 109 110 // If true, does not emit errors when APIs lacking type information are 111 // found. This is false by default and should not be enabled outside bionic, 112 // where it is enabled pending a fix for http://b/190554910 (no debug info 113 // for asm implemented symbols). 114 Allow_untyped_symbols *bool 115 116 // Headers presented by this library to the Public API Surface 117 Export_header_libs []string 118} 119 120type stubDecorator struct { 121 *libraryDecorator 122 123 properties libraryProperties 124 125 versionScriptPath android.ModuleGenPath 126 parsedCoverageXmlPath android.ModuleOutPath 127 installPath android.Path 128 abiDumpPath android.OutputPath 129 abiDiffPaths android.Paths 130 131 apiLevel android.ApiLevel 132 firstVersion android.ApiLevel 133 unversionedUntil android.ApiLevel 134} 135 136var _ versionedInterface = (*stubDecorator)(nil) 137 138func shouldUseVersionScript(ctx BaseModuleContext, stub *stubDecorator) bool { 139 return stub.apiLevel.GreaterThanOrEqualTo(stub.unversionedUntil) 140} 141 142func (stub *stubDecorator) implementationModuleName(name string) string { 143 return strings.TrimSuffix(name, ndkLibrarySuffix) 144} 145 146func ndkLibraryVersions(ctx android.BaseMutatorContext, from android.ApiLevel) []string { 147 var versions []android.ApiLevel 148 versionStrs := []string{} 149 for _, version := range ctx.Config().AllSupportedApiLevels() { 150 if version.GreaterThanOrEqualTo(from) { 151 versions = append(versions, version) 152 versionStrs = append(versionStrs, version.String()) 153 } 154 } 155 versionStrs = append(versionStrs, android.FutureApiLevel.String()) 156 157 return versionStrs 158} 159 160func (this *stubDecorator) stubsVersions(ctx android.BaseMutatorContext) []string { 161 if !ctx.Module().Enabled() { 162 return nil 163 } 164 if ctx.Target().NativeBridge == android.NativeBridgeEnabled { 165 ctx.Module().Disable() 166 return nil 167 } 168 firstVersion, err := nativeApiLevelFromUser(ctx, 169 String(this.properties.First_version)) 170 if err != nil { 171 ctx.PropertyErrorf("first_version", err.Error()) 172 return nil 173 } 174 return ndkLibraryVersions(ctx, firstVersion) 175} 176 177func (this *stubDecorator) initializeProperties(ctx BaseModuleContext) bool { 178 this.apiLevel = nativeApiLevelOrPanic(ctx, this.stubsVersion()) 179 180 var err error 181 this.firstVersion, err = nativeApiLevelFromUser(ctx, 182 String(this.properties.First_version)) 183 if err != nil { 184 ctx.PropertyErrorf("first_version", err.Error()) 185 return false 186 } 187 188 str := proptools.StringDefault(this.properties.Unversioned_until, "minimum") 189 this.unversionedUntil, err = nativeApiLevelFromUser(ctx, str) 190 if err != nil { 191 ctx.PropertyErrorf("unversioned_until", err.Error()) 192 return false 193 } 194 195 return true 196} 197 198func getNDKKnownLibs(config android.Config) *[]string { 199 return config.Once(ndkKnownLibsKey, func() interface{} { 200 return &[]string{} 201 }).(*[]string) 202} 203 204func (c *stubDecorator) compilerInit(ctx BaseModuleContext) { 205 c.baseCompiler.compilerInit(ctx) 206 207 name := ctx.baseModuleName() 208 if strings.HasSuffix(name, ndkLibrarySuffix) { 209 ctx.PropertyErrorf("name", "Do not append %q manually, just use the base name", ndkLibrarySuffix) 210 } 211 212 ndkKnownLibsLock.Lock() 213 defer ndkKnownLibsLock.Unlock() 214 ndkKnownLibs := getNDKKnownLibs(ctx.Config()) 215 for _, lib := range *ndkKnownLibs { 216 if lib == name { 217 return 218 } 219 } 220 *ndkKnownLibs = append(*ndkKnownLibs, name) 221} 222 223var stubLibraryCompilerFlags = []string{ 224 // We're knowingly doing some otherwise unsightly things with builtin 225 // functions here. We're just generating stub libraries, so ignore it. 226 "-Wno-incompatible-library-redeclaration", 227 "-Wno-incomplete-setjmp-declaration", 228 "-Wno-builtin-requires-header", 229 "-Wno-invalid-noreturn", 230 "-Wall", 231 "-Werror", 232 // These libraries aren't actually used. Don't worry about unwinding 233 // (avoids the need to link an unwinder into a fake library). 234 "-fno-unwind-tables", 235} 236 237func init() { 238 config.ExportStringList("StubLibraryCompilerFlags", stubLibraryCompilerFlags) 239} 240 241func addStubLibraryCompilerFlags(flags Flags) Flags { 242 flags.Global.CFlags = append(flags.Global.CFlags, stubLibraryCompilerFlags...) 243 // All symbols in the stubs library should be visible. 244 if inList("-fvisibility=hidden", flags.Local.CFlags) { 245 flags.Local.CFlags = append(flags.Local.CFlags, "-fvisibility=default") 246 } 247 return flags 248} 249 250func (stub *stubDecorator) compilerFlags(ctx ModuleContext, flags Flags, deps PathDeps) Flags { 251 flags = stub.baseCompiler.compilerFlags(ctx, flags, deps) 252 return addStubLibraryCompilerFlags(flags) 253} 254 255type ndkApiOutputs struct { 256 stubSrc android.ModuleGenPath 257 versionScript android.ModuleGenPath 258 symbolList android.ModuleGenPath 259} 260 261func parseNativeAbiDefinition(ctx ModuleContext, symbolFile string, 262 apiLevel android.ApiLevel, genstubFlags string) ndkApiOutputs { 263 264 stubSrcPath := android.PathForModuleGen(ctx, "stub.c") 265 versionScriptPath := android.PathForModuleGen(ctx, "stub.map") 266 symbolFilePath := android.PathForModuleSrc(ctx, symbolFile) 267 symbolListPath := android.PathForModuleGen(ctx, "abi_symbol_list.txt") 268 apiLevelsJson := android.GetApiLevelsJson(ctx) 269 ctx.Build(pctx, android.BuildParams{ 270 Rule: genStubSrc, 271 Description: "generate stubs " + symbolFilePath.Rel(), 272 Outputs: []android.WritablePath{stubSrcPath, versionScriptPath, 273 symbolListPath}, 274 Input: symbolFilePath, 275 Implicits: []android.Path{apiLevelsJson}, 276 Args: map[string]string{ 277 "arch": ctx.Arch().ArchType.String(), 278 "apiLevel": apiLevel.String(), 279 "apiMap": apiLevelsJson.String(), 280 "flags": genstubFlags, 281 }, 282 }) 283 284 return ndkApiOutputs{ 285 stubSrc: stubSrcPath, 286 versionScript: versionScriptPath, 287 symbolList: symbolListPath, 288 } 289} 290 291func compileStubLibrary(ctx ModuleContext, flags Flags, src android.Path) Objects { 292 // libc/libm stubs libraries end up mismatching with clang's internal definition of these 293 // functions (which have noreturn attributes and other things). Because we just want to create a 294 // stub with symbol definitions, and types aren't important in C, ignore the mismatch. 295 flags.Local.ConlyFlags = append(flags.Local.ConlyFlags, "-fno-builtin") 296 return compileObjs(ctx, flagsToBuilderFlags(flags), "", 297 android.Paths{src}, nil, nil, nil, nil) 298} 299 300func (this *stubDecorator) findImplementationLibrary(ctx ModuleContext) android.Path { 301 dep := ctx.GetDirectDepWithTag(strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix), 302 stubImplementation) 303 if dep == nil { 304 ctx.ModuleErrorf("Could not find implementation for stub") 305 return nil 306 } 307 impl, ok := dep.(*Module) 308 if !ok { 309 ctx.ModuleErrorf("Implementation for stub is not correct module type") 310 return nil 311 } 312 output := impl.UnstrippedOutputFile() 313 if output == nil { 314 ctx.ModuleErrorf("implementation module (%s) has no output", impl) 315 return nil 316 } 317 318 return output 319} 320 321func (this *stubDecorator) libraryName(ctx ModuleContext) string { 322 return strings.TrimSuffix(ctx.ModuleName(), ndkLibrarySuffix) 323} 324 325func (this *stubDecorator) findPrebuiltAbiDump(ctx ModuleContext, 326 apiLevel android.ApiLevel) android.OptionalPath { 327 328 subpath := filepath.Join("prebuilts/abi-dumps/ndk", apiLevel.String(), 329 ctx.Arch().ArchType.String(), this.libraryName(ctx), "abi.xml") 330 return android.ExistentPathForSource(ctx, subpath) 331} 332 333// Feature flag. 334func canDumpAbi(config android.Config) bool { 335 if runtime.GOOS == "darwin" { 336 return false 337 } 338 // abidw doesn't currently handle top-byte-ignore correctly. Disable ABI 339 // dumping for those configs while we wait for a fix. We'll still have ABI 340 // checking coverage from non-hwasan builds. 341 // http://b/190554910 342 if android.InList("hwaddress", config.SanitizeDevice()) { 343 return false 344 } 345 // http://b/156513478 346 // http://b/277624006 347 // This step is expensive. We're not able to do anything with the outputs of 348 // this step yet (canDiffAbi is flagged off because libabigail isn't able to 349 // handle all our libraries), disable it. There's no sense in protecting 350 // against checking in code that breaks abidw since by the time any of this 351 // can be turned on we'll need to migrate to STG anyway. 352 return false 353} 354 355// Feature flag to disable diffing against prebuilts. 356func canDiffAbi() bool { 357 return false 358} 359 360func (this *stubDecorator) dumpAbi(ctx ModuleContext, symbolList android.Path) { 361 implementationLibrary := this.findImplementationLibrary(ctx) 362 abiRawPath := getNdkAbiDumpInstallBase(ctx).Join(ctx, 363 this.apiLevel.String(), ctx.Arch().ArchType.String(), 364 this.libraryName(ctx), "abi.raw.xml") 365 ctx.Build(pctx, android.BuildParams{ 366 Rule: abidw, 367 Description: fmt.Sprintf("abidw %s", implementationLibrary), 368 Input: implementationLibrary, 369 Output: abiRawPath, 370 Implicit: symbolList, 371 Args: map[string]string{ 372 "symbolList": symbolList.String(), 373 }, 374 }) 375 376 this.abiDumpPath = getNdkAbiDumpInstallBase(ctx).Join(ctx, 377 this.apiLevel.String(), ctx.Arch().ArchType.String(), 378 this.libraryName(ctx), "abi.xml") 379 untypedFlag := "--abort-on-untyped-symbols" 380 if proptools.BoolDefault(this.properties.Allow_untyped_symbols, false) { 381 untypedFlag = "" 382 } 383 ctx.Build(pctx, android.BuildParams{ 384 Rule: abitidy, 385 Description: fmt.Sprintf("abitidy %s", implementationLibrary), 386 Input: abiRawPath, 387 Output: this.abiDumpPath, 388 Args: map[string]string{ 389 "flags": untypedFlag, 390 }, 391 }) 392} 393 394func findNextApiLevel(ctx ModuleContext, apiLevel android.ApiLevel) *android.ApiLevel { 395 apiLevels := append(ctx.Config().AllSupportedApiLevels(), 396 android.FutureApiLevel) 397 for _, api := range apiLevels { 398 if api.GreaterThan(apiLevel) { 399 return &api 400 } 401 } 402 return nil 403} 404 405func (this *stubDecorator) diffAbi(ctx ModuleContext) { 406 // Catch any ABI changes compared to the checked-in definition of this API 407 // level. 408 abiDiffPath := android.PathForModuleOut(ctx, "abidiff.timestamp") 409 prebuiltAbiDump := this.findPrebuiltAbiDump(ctx, this.apiLevel) 410 missingPrebuiltError := fmt.Sprintf( 411 "Did not find prebuilt ABI dump for %q (%q). Generate with "+ 412 "//development/tools/ndk/update_ndk_abi.sh.", this.libraryName(ctx), 413 prebuiltAbiDump.InvalidReason()) 414 if !prebuiltAbiDump.Valid() { 415 ctx.Build(pctx, android.BuildParams{ 416 Rule: android.ErrorRule, 417 Output: abiDiffPath, 418 Args: map[string]string{ 419 "error": missingPrebuiltError, 420 }, 421 }) 422 } else { 423 ctx.Build(pctx, android.BuildParams{ 424 Rule: abidiff, 425 Description: fmt.Sprintf("abidiff %s %s", prebuiltAbiDump, 426 this.abiDumpPath), 427 Output: abiDiffPath, 428 Inputs: android.Paths{prebuiltAbiDump.Path(), this.abiDumpPath}, 429 }) 430 } 431 this.abiDiffPaths = append(this.abiDiffPaths, abiDiffPath) 432 433 // Also ensure that the ABI of the next API level (if there is one) matches 434 // this API level. *New* ABI is allowed, but any changes to APIs that exist 435 // in this API level are disallowed. 436 if !this.apiLevel.IsCurrent() { 437 nextApiLevel := findNextApiLevel(ctx, this.apiLevel) 438 if nextApiLevel == nil { 439 panic(fmt.Errorf("could not determine which API level follows "+ 440 "non-current API level %s", this.apiLevel)) 441 } 442 nextAbiDiffPath := android.PathForModuleOut(ctx, 443 "abidiff_next.timestamp") 444 nextAbiDump := this.findPrebuiltAbiDump(ctx, *nextApiLevel) 445 if !nextAbiDump.Valid() { 446 ctx.Build(pctx, android.BuildParams{ 447 Rule: android.ErrorRule, 448 Output: nextAbiDiffPath, 449 Args: map[string]string{ 450 "error": missingPrebuiltError, 451 }, 452 }) 453 } else { 454 ctx.Build(pctx, android.BuildParams{ 455 Rule: abidiff, 456 Description: fmt.Sprintf("abidiff %s %s", this.abiDumpPath, 457 nextAbiDump), 458 Output: nextAbiDiffPath, 459 Inputs: android.Paths{this.abiDumpPath, nextAbiDump.Path()}, 460 Args: map[string]string{ 461 "args": "--no-added-syms", 462 }, 463 }) 464 } 465 this.abiDiffPaths = append(this.abiDiffPaths, nextAbiDiffPath) 466 } 467} 468 469func (c *stubDecorator) compile(ctx ModuleContext, flags Flags, deps PathDeps) Objects { 470 if !strings.HasSuffix(String(c.properties.Symbol_file), ".map.txt") { 471 ctx.PropertyErrorf("symbol_file", "must end with .map.txt") 472 } 473 474 if !c.buildStubs() { 475 // NDK libraries have no implementation variant, nothing to do 476 return Objects{} 477 } 478 479 if !c.initializeProperties(ctx) { 480 // Emits its own errors, so we don't need to. 481 return Objects{} 482 } 483 484 symbolFile := String(c.properties.Symbol_file) 485 nativeAbiResult := parseNativeAbiDefinition(ctx, symbolFile, c.apiLevel, "") 486 objs := compileStubLibrary(ctx, flags, nativeAbiResult.stubSrc) 487 c.versionScriptPath = nativeAbiResult.versionScript 488 if canDumpAbi(ctx.Config()) { 489 c.dumpAbi(ctx, nativeAbiResult.symbolList) 490 if canDiffAbi() { 491 c.diffAbi(ctx) 492 } 493 } 494 if c.apiLevel.IsCurrent() && ctx.PrimaryArch() { 495 c.parsedCoverageXmlPath = parseSymbolFileForAPICoverage(ctx, symbolFile) 496 } 497 return objs 498} 499 500// Add a dependency on the header modules of this ndk_library 501func (linker *stubDecorator) linkerDeps(ctx DepsContext, deps Deps) Deps { 502 return Deps{ 503 HeaderLibs: linker.properties.Export_header_libs, 504 } 505} 506 507func (linker *stubDecorator) Name(name string) string { 508 return name + ndkLibrarySuffix 509} 510 511func (stub *stubDecorator) linkerFlags(ctx ModuleContext, flags Flags) Flags { 512 stub.libraryDecorator.libName = ctx.baseModuleName() 513 return stub.libraryDecorator.linkerFlags(ctx, flags) 514} 515 516func (stub *stubDecorator) link(ctx ModuleContext, flags Flags, deps PathDeps, 517 objs Objects) android.Path { 518 519 if !stub.buildStubs() { 520 // NDK libraries have no implementation variant, nothing to do 521 return nil 522 } 523 524 if shouldUseVersionScript(ctx, stub) { 525 linkerScriptFlag := "-Wl,--version-script," + stub.versionScriptPath.String() 526 flags.Local.LdFlags = append(flags.Local.LdFlags, linkerScriptFlag) 527 flags.LdFlagsDeps = append(flags.LdFlagsDeps, stub.versionScriptPath) 528 } 529 530 stub.libraryDecorator.skipAPIDefine = true 531 return stub.libraryDecorator.link(ctx, flags, deps, objs) 532} 533 534func (stub *stubDecorator) nativeCoverage() bool { 535 return false 536} 537 538// Returns the install path for unversioned NDK libraries (currently only static 539// libraries). 540func getUnversionedLibraryInstallPath(ctx ModuleContext) android.InstallPath { 541 return getNdkSysrootBase(ctx).Join(ctx, "usr/lib", config.NDKTriple(ctx.toolchain())) 542} 543 544// Returns the install path for versioned NDK libraries. These are most often 545// stubs, but the same paths are used for CRT objects. 546func getVersionedLibraryInstallPath(ctx ModuleContext, apiLevel android.ApiLevel) android.InstallPath { 547 return getUnversionedLibraryInstallPath(ctx).Join(ctx, apiLevel.String()) 548} 549 550func (stub *stubDecorator) install(ctx ModuleContext, path android.Path) { 551 installDir := getVersionedLibraryInstallPath(ctx, stub.apiLevel) 552 stub.installPath = ctx.InstallFile(installDir, path.Base(), path) 553} 554 555func newStubLibrary() *Module { 556 module, library := NewLibrary(android.DeviceSupported) 557 library.BuildOnlyShared() 558 module.stl = nil 559 module.sanitize = nil 560 library.disableStripping() 561 562 stub := &stubDecorator{ 563 libraryDecorator: library, 564 } 565 module.compiler = stub 566 module.linker = stub 567 module.installer = stub 568 module.library = stub 569 570 module.Properties.AlwaysSdk = true 571 module.Properties.Sdk_version = StringPtr("current") 572 573 module.AddProperties(&stub.properties, &library.MutatedProperties) 574 575 return module 576} 577 578// ndk_library creates a library that exposes a stub implementation of functions 579// and variables for use at build time only. 580func NdkLibraryFactory() android.Module { 581 module := newStubLibrary() 582 android.InitAndroidArchModule(module, android.DeviceSupported, android.MultilibBoth) 583 android.InitBazelModule(module) 584 return module 585} 586 587type bazelCcApiContributionAttributes struct { 588 Api bazel.LabelAttribute 589 Api_surfaces bazel.StringListAttribute 590 Hdrs bazel.LabelListAttribute 591 Library_name string 592} 593 594// Names of the cc_api_header targets in the bp2build workspace 595func apiHeaderLabels(ctx android.TopDownMutatorContext, hdrLibs []string) bazel.LabelList { 596 addSuffix := func(ctx android.BazelConversionPathContext, module blueprint.Module) string { 597 label := android.BazelModuleLabel(ctx, module) 598 return android.ApiContributionTargetName(label) 599 } 600 return android.BazelLabelForModuleDepsWithFn(ctx, hdrLibs, addSuffix) 601} 602 603func ndkLibraryBp2build(ctx android.TopDownMutatorContext, m *Module) { 604 props := bazel.BazelTargetModuleProperties{ 605 Rule_class: "cc_api_contribution", 606 Bzl_load_location: "//build/bazel/rules/apis:cc_api_contribution.bzl", 607 } 608 stubLibrary := m.compiler.(*stubDecorator) 609 attrs := &bazelCcApiContributionAttributes{ 610 Library_name: stubLibrary.implementationModuleName(m.Name()), 611 Api_surfaces: bazel.MakeStringListAttribute( 612 []string{android.PublicApi.String()}), 613 } 614 if symbolFile := stubLibrary.properties.Symbol_file; symbolFile != nil { 615 apiLabel := android.BazelLabelForModuleSrcSingle(ctx, proptools.String(symbolFile)).Label 616 attrs.Api = *bazel.MakeLabelAttribute(apiLabel) 617 } 618 apiHeaders := apiHeaderLabels(ctx, stubLibrary.properties.Export_header_libs) 619 attrs.Hdrs = bazel.MakeLabelListAttribute(apiHeaders) 620 apiContributionTargetName := android.ApiContributionTargetName(ctx.ModuleName()) 621 ctx.CreateBazelTargetModule(props, android.CommonAttributes{Name: apiContributionTargetName}, attrs) 622} 623