github.com/tetratelabs/wazero@v1.2.1/internal/wasm/store.go (about) 1 package wasm 2 3 import ( 4 "context" 5 "encoding/binary" 6 "fmt" 7 "sync" 8 9 "github.com/tetratelabs/wazero/api" 10 "github.com/tetratelabs/wazero/internal/internalapi" 11 "github.com/tetratelabs/wazero/internal/leb128" 12 internalsys "github.com/tetratelabs/wazero/internal/sys" 13 "github.com/tetratelabs/wazero/sys" 14 ) 15 16 // nameToModuleShrinkThreshold is the size the nameToModule map can grow to 17 // before it starts to be monitored for shrinking. 18 // The capacity will never be smaller than this once the threshold is met. 19 const nameToModuleShrinkThreshold = 100 20 21 type ( 22 // Store is the runtime representation of "instantiated" Wasm module and objects. 23 // Multiple modules can be instantiated within a single store, and each instance, 24 // (e.g. function instance) can be referenced by other module instances in a Store via Module.ImportSection. 25 // 26 // Every type whose name ends with "Instance" suffix belongs to exactly one store. 27 // 28 // Note that store is not thread (concurrency) safe, meaning that using single Store 29 // via multiple goroutines might result in race conditions. In that case, the invocation 30 // and access to any methods and field of Store must be guarded by mutex. 31 // 32 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#store%E2%91%A0 33 Store struct { 34 // moduleList ensures modules are closed in reverse initialization order. 35 moduleList *ModuleInstance // guarded by mux 36 37 // nameToModule holds the instantiated Wasm modules by module name from Instantiate. 38 // It ensures no race conditions instantiating two modules of the same name. 39 nameToModule map[string]*ModuleInstance // guarded by mux 40 41 // nameToModuleCap tracks the growth of the nameToModule map in order to 42 // track when to shrink it. 43 nameToModuleCap int // guarded by mux 44 45 // EnabledFeatures are read-only to allow optimizations. 46 EnabledFeatures api.CoreFeatures 47 48 // Engine is a global context for a Store which is in responsible for compilation and execution of Wasm modules. 49 Engine Engine 50 51 // typeIDs maps each FunctionType.String() to a unique FunctionTypeID. This is used at runtime to 52 // do type-checks on indirect function calls. 53 typeIDs map[string]FunctionTypeID 54 55 // functionMaxTypes represents the limit on the number of function types in a store. 56 // Note: this is fixed to 2^27 but have this a field for testability. 57 functionMaxTypes uint32 58 59 // mux is used to guard the fields from concurrent access. 60 mux sync.RWMutex 61 } 62 63 // ModuleInstance represents instantiated wasm module. 64 // The difference from the spec is that in wazero, a ModuleInstance holds pointers 65 // to the instances, rather than "addresses" (i.e. index to Store.Functions, Globals, etc) for convenience. 66 // 67 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#syntax-moduleinst 68 // 69 // This implements api.Module. 70 ModuleInstance struct { 71 internalapi.WazeroOnlyType 72 73 // Closed is used both to guard moduleEngine.CloseWithExitCode and to store the exit code. 74 // 75 // The update value is closedType + exitCode << 32. This ensures an exit code of zero isn't mistaken for never closed. 76 // 77 // Note: Exclusively reading and updating this with atomics guarantees cross-goroutine observations. 78 // See /RATIONALE.md 79 // 80 // TODO: Retype this to atomic.Unit64 when Go 1.18 is no longer supported. Until then, keep Closed at the top of 81 // this struct. See PR #1299 for an implementation and discussion. 82 Closed uint64 83 84 ModuleName string 85 Exports map[string]*Export 86 Globals []*GlobalInstance 87 MemoryInstance *MemoryInstance 88 Tables []*TableInstance 89 90 // Engine implements function calls for this module. 91 Engine ModuleEngine 92 93 // TypeIDs is index-correlated with types and holds typeIDs which is uniquely assigned to a type by store. 94 // This is necessary to achieve fast runtime type checking for indirect function calls at runtime. 95 TypeIDs []FunctionTypeID 96 97 // DataInstances holds data segments bytes of the module. 98 // This is only used by bulk memory operations. 99 // 100 // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances 101 DataInstances []DataInstance 102 103 // ElementInstances holds the element instance, and each holds the references to either functions 104 // or external objects (unimplemented). 105 ElementInstances []ElementInstance 106 107 // Sys is exposed for use in special imports such as WASI, assemblyscript 108 // and gojs. 109 // 110 // # Notes 111 // 112 // - This is a part of ModuleInstance so that scope and Close is coherent. 113 // - This is not exposed outside this repository (as a host function 114 // parameter) because we haven't thought through capabilities based 115 // security implications. 116 Sys *internalsys.Context 117 118 // CodeCloser is non-nil when the code should be closed after this module. 119 CodeCloser api.Closer 120 121 // s is the Store on which this module is instantiated. 122 s *Store 123 // prev and next hold the nodes in the linked list of ModuleInstance held by Store. 124 prev, next *ModuleInstance 125 // Source is a pointer to the Module from which this ModuleInstance derives. 126 Source *Module 127 } 128 129 // DataInstance holds bytes corresponding to the data segment in a module. 130 // 131 // https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/exec/runtime.html#data-instances 132 DataInstance = []byte 133 134 // GlobalInstance represents a global instance in a store. 135 // See https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#global-instances%E2%91%A0 136 GlobalInstance struct { 137 Type GlobalType 138 // Val holds a 64-bit representation of the actual value. 139 Val uint64 140 // ValHi is only used for vector type globals, and holds the higher bits of the vector. 141 ValHi uint64 142 // ^^ TODO: this should be guarded with atomics when mutable 143 } 144 145 // FunctionTypeID is a uniquely assigned integer for a function type. 146 // This is wazero specific runtime object and specific to a store, 147 // and used at runtime to do type-checks on indirect function calls. 148 FunctionTypeID uint32 149 ) 150 151 // The wazero specific limitations described at RATIONALE.md. 152 const maximumFunctionTypes = 1 << 27 153 154 // GetFunctionTypeID is used by emscripten. 155 func (m *ModuleInstance) GetFunctionTypeID(t *FunctionType) (FunctionTypeID, error) { 156 return m.s.GetFunctionTypeID(t) 157 } 158 159 func (m *ModuleInstance) buildElementInstances(elements []ElementSegment) { 160 m.ElementInstances = make([]ElementInstance, len(elements)) 161 for i, elm := range elements { 162 if elm.Type == RefTypeFuncref && elm.Mode == ElementModePassive { 163 // Only passive elements can be access as element instances. 164 // See https://www.w3.org/TR/2022/WD-wasm-core-2-20220419/syntax/modules.html#element-segments 165 inits := elm.Init 166 elemInst := &m.ElementInstances[i] 167 elemInst.References = make([]Reference, len(inits)) 168 elemInst.Type = RefTypeFuncref 169 for j, idx := range inits { 170 if idx != ElementInitNullReference { 171 elemInst.References[j] = m.Engine.FunctionInstanceReference(idx) 172 } 173 } 174 } 175 } 176 } 177 178 func (m *ModuleInstance) applyElements(elems []ElementSegment) { 179 for elemI := range elems { 180 elem := &elems[elemI] 181 if !elem.IsActive() || 182 // Per https://github.com/WebAssembly/spec/issues/1427 init can be no-op. 183 len(elem.Init) == 0 { 184 continue 185 } 186 var offset uint32 187 if elem.OffsetExpr.Opcode == OpcodeGlobalGet { 188 // Ignore error as it's already validated. 189 globalIdx, _, _ := leb128.LoadUint32(elem.OffsetExpr.Data) 190 global := m.Globals[globalIdx] 191 offset = uint32(global.Val) 192 } else { 193 // Ignore error as it's already validated. 194 o, _, _ := leb128.LoadInt32(elem.OffsetExpr.Data) 195 offset = uint32(o) 196 } 197 198 table := m.Tables[elem.TableIndex] 199 references := table.References 200 if int(offset)+len(elem.Init) > len(references) { 201 // ErrElementOffsetOutOfBounds is the error raised when the active element offset exceeds the table length. 202 // Before CoreFeatureReferenceTypes, this was checked statically before instantiation, after the proposal, 203 // this must be raised as runtime error (as in assert_trap in spectest), not even an instantiation error. 204 // https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L264-L274 205 // 206 // In wazero, we ignore it since in any way, the instantiated module and engines are fine and can be used 207 // for function invocations. 208 return 209 } 210 211 if table.Type == RefTypeExternref { 212 for i := 0; i < len(elem.Init); i++ { 213 references[offset+uint32(i)] = Reference(0) 214 } 215 } else { 216 for i, init := range elem.Init { 217 if init == ElementInitNullReference { 218 continue 219 } 220 221 var ref Reference 222 if index, ok := unwrapElementInitGlobalReference(init); ok { 223 global := m.Globals[index] 224 ref = Reference(global.Val) 225 } else { 226 ref = m.Engine.FunctionInstanceReference(index) 227 } 228 references[offset+uint32(i)] = ref 229 } 230 } 231 } 232 } 233 234 // validateData ensures that data segments are valid in terms of memory boundary. 235 // Note: this is used only when bulk-memory/reference type feature is disabled. 236 func (m *ModuleInstance) validateData(data []DataSegment) (err error) { 237 for i := range data { 238 d := &data[i] 239 if !d.IsPassive() { 240 offset := int(executeConstExpressionI32(m.Globals, &d.OffsetExpression)) 241 ceil := offset + len(d.Init) 242 if offset < 0 || ceil > len(m.MemoryInstance.Buffer) { 243 return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i) 244 } 245 } 246 } 247 return 248 } 249 250 // applyData uses the given data segments and mutate the memory according to the initial contents on it 251 // and populate the `DataInstances`. This is called after all the validation phase passes and out of 252 // bounds memory access error here is not a validation error, but rather a runtime error. 253 func (m *ModuleInstance) applyData(data []DataSegment) error { 254 m.DataInstances = make([][]byte, len(data)) 255 for i := range data { 256 d := &data[i] 257 m.DataInstances[i] = d.Init 258 if !d.IsPassive() { 259 offset := executeConstExpressionI32(m.Globals, &d.OffsetExpression) 260 if offset < 0 || int(offset)+len(d.Init) > len(m.MemoryInstance.Buffer) { 261 return fmt.Errorf("%s[%d]: out of bounds memory access", SectionIDName(SectionIDData), i) 262 } 263 copy(m.MemoryInstance.Buffer[offset:], d.Init) 264 } 265 } 266 return nil 267 } 268 269 // GetExport returns an export of the given name and type or errs if not exported or the wrong type. 270 func (m *ModuleInstance) getExport(name string, et ExternType) (*Export, error) { 271 exp, ok := m.Exports[name] 272 if !ok { 273 return nil, fmt.Errorf("%q is not exported in module %q", name, m.ModuleName) 274 } 275 if exp.Type != et { 276 return nil, fmt.Errorf("export %q in module %q is a %s, not a %s", name, m.ModuleName, ExternTypeName(exp.Type), ExternTypeName(et)) 277 } 278 return exp, nil 279 } 280 281 func NewStore(enabledFeatures api.CoreFeatures, engine Engine) *Store { 282 return &Store{ 283 nameToModule: map[string]*ModuleInstance{}, 284 nameToModuleCap: nameToModuleShrinkThreshold, 285 EnabledFeatures: enabledFeatures, 286 Engine: engine, 287 typeIDs: map[string]FunctionTypeID{}, 288 functionMaxTypes: maximumFunctionTypes, 289 } 290 } 291 292 // Instantiate uses name instead of the Module.NameSection ModuleName as it allows instantiating the same module under 293 // different names safely and concurrently. 294 // 295 // * ctx: the default context used for function calls. 296 // * name: the name of the module. 297 // * sys: the system context, which will be closed (SysContext.Close) on ModuleInstance.Close. 298 // 299 // Note: Module.Validate must be called prior to instantiation. 300 func (s *Store) Instantiate( 301 ctx context.Context, 302 module *Module, 303 name string, 304 sys *internalsys.Context, 305 typeIDs []FunctionTypeID, 306 ) (*ModuleInstance, error) { 307 // Instantiate the module and add it to the store so that other modules can import it. 308 m, err := s.instantiate(ctx, module, name, sys, typeIDs) 309 if err != nil { 310 return nil, err 311 } 312 313 // Now that the instantiation is complete without error, add it. 314 if err = s.registerModule(m); err != nil { 315 _ = m.Close(ctx) 316 return nil, err 317 } 318 return m, nil 319 } 320 321 func (s *Store) instantiate( 322 ctx context.Context, 323 module *Module, 324 name string, 325 sysCtx *internalsys.Context, 326 typeIDs []FunctionTypeID, 327 ) (m *ModuleInstance, err error) { 328 m = &ModuleInstance{ModuleName: name, TypeIDs: typeIDs, Sys: sysCtx, s: s, Source: module} 329 330 m.Tables = make([]*TableInstance, int(module.ImportTableCount)+len(module.TableSection)) 331 m.Globals = make([]*GlobalInstance, int(module.ImportGlobalCount)+len(module.GlobalSection)) 332 m.Engine, err = s.Engine.NewModuleEngine(module, m) 333 if err != nil { 334 return nil, err 335 } 336 337 if err = m.resolveImports(module); err != nil { 338 return nil, err 339 } 340 341 err = m.buildTables(module, 342 // As of reference-types proposal, boundary check must be done after instantiation. 343 s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes)) 344 if err != nil { 345 return nil, err 346 } 347 348 m.buildGlobals(module, m.Engine.FunctionInstanceReference) 349 m.buildMemory(module) 350 m.Exports = module.Exports 351 352 // As of reference types proposal, data segment validation must happen after instantiation, 353 // and the side effect must persist even if there's out of bounds error after instantiation. 354 // https://github.com/WebAssembly/spec/blob/d39195773112a22b245ffbe864bab6d1182ccb06/test/core/linking.wast#L395-L405 355 if !s.EnabledFeatures.IsEnabled(api.CoreFeatureReferenceTypes) { 356 if err = m.validateData(module.DataSection); err != nil { 357 return nil, err 358 } 359 } 360 361 // After engine creation, we can create the funcref element instances and initialize funcref type globals. 362 m.buildElementInstances(module.ElementSection) 363 364 // Now all the validation passes, we are safe to mutate memory instances (possibly imported ones). 365 if err = m.applyData(module.DataSection); err != nil { 366 return nil, err 367 } 368 369 m.applyElements(module.ElementSection) 370 371 // Execute the start function. 372 if module.StartSection != nil { 373 funcIdx := *module.StartSection 374 ce := m.Engine.NewFunction(funcIdx) 375 _, err = ce.Call(ctx) 376 if exitErr, ok := err.(*sys.ExitError); ok { // Don't wrap an exit error! 377 return nil, exitErr 378 } else if err != nil { 379 return nil, fmt.Errorf("start %s failed: %w", module.funcDesc(SectionIDFunction, funcIdx), err) 380 } 381 } 382 return 383 } 384 385 func (m *ModuleInstance) resolveImports(module *Module) (err error) { 386 for moduleName, imports := range module.ImportPerModule { 387 var importedModule *ModuleInstance 388 importedModule, err = m.s.module(moduleName) 389 if err != nil { 390 return err 391 } 392 393 for _, i := range imports { 394 var imported *Export 395 imported, err = importedModule.getExport(i.Name, i.Type) 396 if err != nil { 397 return 398 } 399 400 switch i.Type { 401 case ExternTypeFunc: 402 expectedType := &module.TypeSection[i.DescFunc] 403 src := importedModule.Source 404 actual := src.typeOfFunction(imported.Index) 405 if !actual.EqualsSignature(expectedType.Params, expectedType.Results) { 406 err = errorInvalidImport(i, fmt.Errorf("signature mismatch: %s != %s", expectedType, actual)) 407 return 408 } 409 410 m.Engine.ResolveImportedFunction(i.IndexPerType, imported.Index, importedModule.Engine) 411 case ExternTypeTable: 412 expected := i.DescTable 413 importedTable := importedModule.Tables[imported.Index] 414 if expected.Type != importedTable.Type { 415 err = errorInvalidImport(i, fmt.Errorf("table type mismatch: %s != %s", 416 RefTypeName(expected.Type), RefTypeName(importedTable.Type))) 417 return 418 } 419 420 if expected.Min > importedTable.Min { 421 err = errorMinSizeMismatch(i, expected.Min, importedTable.Min) 422 return 423 } 424 425 if expected.Max != nil { 426 expectedMax := *expected.Max 427 if importedTable.Max == nil { 428 err = errorNoMax(i, expectedMax) 429 return 430 } else if expectedMax < *importedTable.Max { 431 err = errorMaxSizeMismatch(i, expectedMax, *importedTable.Max) 432 return 433 } 434 } 435 m.Tables[i.IndexPerType] = importedTable 436 case ExternTypeMemory: 437 expected := i.DescMem 438 importedMemory := importedModule.MemoryInstance 439 440 if expected.Min > memoryBytesNumToPages(uint64(len(importedMemory.Buffer))) { 441 err = errorMinSizeMismatch(i, expected.Min, importedMemory.Min) 442 return 443 } 444 445 if expected.Max < importedMemory.Max { 446 err = errorMaxSizeMismatch(i, expected.Max, importedMemory.Max) 447 return 448 } 449 m.MemoryInstance = importedMemory 450 case ExternTypeGlobal: 451 expected := i.DescGlobal 452 importedGlobal := importedModule.Globals[imported.Index] 453 454 if expected.Mutable != importedGlobal.Type.Mutable { 455 err = errorInvalidImport(i, fmt.Errorf("mutability mismatch: %t != %t", 456 expected.Mutable, importedGlobal.Type.Mutable)) 457 return 458 } 459 460 if expected.ValType != importedGlobal.Type.ValType { 461 err = errorInvalidImport(i, fmt.Errorf("value type mismatch: %s != %s", 462 ValueTypeName(expected.ValType), ValueTypeName(importedGlobal.Type.ValType))) 463 return 464 } 465 m.Globals[i.IndexPerType] = importedGlobal 466 } 467 } 468 } 469 return 470 } 471 472 func errorMinSizeMismatch(i *Import, expected, actual uint32) error { 473 return errorInvalidImport(i, fmt.Errorf("minimum size mismatch: %d > %d", expected, actual)) 474 } 475 476 func errorNoMax(i *Import, expected uint32) error { 477 return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d, but actual has no max", expected)) 478 } 479 480 func errorMaxSizeMismatch(i *Import, expected, actual uint32) error { 481 return errorInvalidImport(i, fmt.Errorf("maximum size mismatch: %d < %d", expected, actual)) 482 } 483 484 func errorInvalidImport(i *Import, err error) error { 485 return fmt.Errorf("import %s[%s.%s]: %w", ExternTypeName(i.Type), i.Module, i.Name, err) 486 } 487 488 // executeConstExpressionI32 executes the ConstantExpression which returns ValueTypeI32. 489 // The validity of the expression is ensured when calling this function as this is only called 490 // during instantiation phrase, and the validation happens in compilation (validateConstExpression). 491 func executeConstExpressionI32(importedGlobals []*GlobalInstance, expr *ConstantExpression) (ret int32) { 492 switch expr.Opcode { 493 case OpcodeI32Const: 494 ret, _, _ = leb128.LoadInt32(expr.Data) 495 case OpcodeGlobalGet: 496 id, _, _ := leb128.LoadUint32(expr.Data) 497 g := importedGlobals[id] 498 ret = int32(g.Val) 499 } 500 return 501 } 502 503 // initialize initializes the value of this global instance given the const expr and imported globals. 504 // funcRefResolver is called to get the actual funcref (engine specific) from the OpcodeRefFunc const expr. 505 // 506 // Global initialization constant expression can only reference the imported globals. 507 // See the note on https://www.w3.org/TR/2019/REC-wasm-core-1-20191205/#constant-expressions%E2%91%A0 508 func (g *GlobalInstance) initialize(importedGlobals []*GlobalInstance, expr *ConstantExpression, funcRefResolver func(funcIndex Index) Reference) { 509 switch expr.Opcode { 510 case OpcodeI32Const: 511 // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md 512 v, _, _ := leb128.LoadInt32(expr.Data) 513 g.Val = uint64(uint32(v)) 514 case OpcodeI64Const: 515 // Treat constants as signed as their interpretation is not yet known per /RATIONALE.md 516 v, _, _ := leb128.LoadInt64(expr.Data) 517 g.Val = uint64(v) 518 case OpcodeF32Const: 519 g.Val = uint64(binary.LittleEndian.Uint32(expr.Data)) 520 case OpcodeF64Const: 521 g.Val = binary.LittleEndian.Uint64(expr.Data) 522 case OpcodeGlobalGet: 523 id, _, _ := leb128.LoadUint32(expr.Data) 524 importedG := importedGlobals[id] 525 switch importedG.Type.ValType { 526 case ValueTypeI32: 527 g.Val = uint64(uint32(importedG.Val)) 528 case ValueTypeI64: 529 g.Val = importedG.Val 530 case ValueTypeF32: 531 g.Val = importedG.Val 532 case ValueTypeF64: 533 g.Val = importedG.Val 534 case ValueTypeV128: 535 g.Val, g.ValHi = importedG.Val, importedG.ValHi 536 case ValueTypeFuncref, ValueTypeExternref: 537 g.Val = importedG.Val 538 } 539 case OpcodeRefNull: 540 switch expr.Data[0] { 541 case ValueTypeExternref, ValueTypeFuncref: 542 g.Val = 0 // Reference types are opaque 64bit pointer at runtime. 543 } 544 case OpcodeRefFunc: 545 v, _, _ := leb128.LoadUint32(expr.Data) 546 g.Val = uint64(funcRefResolver(v)) 547 case OpcodeVecV128Const: 548 g.Val, g.ValHi = binary.LittleEndian.Uint64(expr.Data[0:8]), binary.LittleEndian.Uint64(expr.Data[8:16]) 549 } 550 } 551 552 // String implements api.Global. 553 func (g *GlobalInstance) String() string { 554 switch g.Type.ValType { 555 case ValueTypeI32, ValueTypeI64: 556 return fmt.Sprintf("global(%d)", g.Val) 557 case ValueTypeF32: 558 return fmt.Sprintf("global(%f)", api.DecodeF32(g.Val)) 559 case ValueTypeF64: 560 return fmt.Sprintf("global(%f)", api.DecodeF64(g.Val)) 561 default: 562 panic(fmt.Errorf("BUG: unknown value type %X", g.Type.ValType)) 563 } 564 } 565 566 func (s *Store) GetFunctionTypeIDs(ts []FunctionType) ([]FunctionTypeID, error) { 567 ret := make([]FunctionTypeID, len(ts)) 568 for i := range ts { 569 t := &ts[i] 570 inst, err := s.GetFunctionTypeID(t) 571 if err != nil { 572 return nil, err 573 } 574 ret[i] = inst 575 } 576 return ret, nil 577 } 578 579 func (s *Store) GetFunctionTypeID(t *FunctionType) (FunctionTypeID, error) { 580 s.mux.RLock() 581 key := t.key() 582 id, ok := s.typeIDs[key] 583 s.mux.RUnlock() 584 if !ok { 585 s.mux.Lock() 586 defer s.mux.Unlock() 587 // Check again in case another goroutine has already added the type. 588 if id, ok = s.typeIDs[key]; ok { 589 return id, nil 590 } 591 l := len(s.typeIDs) 592 if uint32(l) >= s.functionMaxTypes { 593 return 0, fmt.Errorf("too many function types in a store") 594 } 595 id = FunctionTypeID(l) 596 s.typeIDs[key] = id 597 } 598 return id, nil 599 } 600 601 // CloseWithExitCode implements the same method as documented on wazero.Runtime. 602 func (s *Store) CloseWithExitCode(ctx context.Context, exitCode uint32) (err error) { 603 s.mux.Lock() 604 defer s.mux.Unlock() 605 // Close modules in reverse initialization order. 606 for m := s.moduleList; m != nil; m = m.next { 607 // If closing this module errs, proceed anyway to close the others. 608 if e := m.closeWithExitCode(ctx, exitCode); e != nil && err == nil { 609 // TODO: use multiple errors handling in Go 1.20. 610 err = e // first error 611 } 612 } 613 s.moduleList = nil 614 s.nameToModule = nil 615 s.nameToModuleCap = 0 616 s.typeIDs = nil 617 return 618 }