wa-lang.org/wazero@v1.0.2/internal/wasm/store_test.go (about) 1 package wasm 2 3 import ( 4 "context" 5 "errors" 6 "fmt" 7 "math" 8 "strconv" 9 "testing" 10 11 "wa-lang.org/wazero/api" 12 "wa-lang.org/wazero/experimental" 13 "wa-lang.org/wazero/internal/leb128" 14 "wa-lang.org/wazero/internal/sys" 15 "wa-lang.org/wazero/internal/testing/hammer" 16 "wa-lang.org/wazero/internal/testing/require" 17 "wa-lang.org/wazero/internal/u64" 18 ) 19 20 func TestModuleInstance_Memory(t *testing.T) { 21 tests := []struct { 22 name string 23 input *Module 24 expected bool 25 expectedLen uint32 26 }{ 27 { 28 name: "no memory", 29 input: &Module{}, 30 }, 31 { 32 name: "memory not exported", 33 input: &Module{MemorySection: &Memory{Min: 1, Cap: 1}}, 34 }, 35 { 36 name: "memory not exported, one page", 37 input: &Module{MemorySection: &Memory{Min: 1, Cap: 1}}, 38 }, 39 { 40 name: "memory exported, different name", 41 input: &Module{ 42 MemorySection: &Memory{Min: 1, Cap: 1}, 43 ExportSection: []*Export{{Type: ExternTypeMemory, Name: "momory", Index: 0}}, 44 }, 45 }, 46 { 47 name: "memory exported, but zero length", 48 input: &Module{ 49 MemorySection: &Memory{}, 50 ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}}, 51 }, 52 expected: true, 53 }, 54 { 55 name: "memory exported, one page", 56 input: &Module{ 57 MemorySection: &Memory{Min: 1, Cap: 1}, 58 ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}}, 59 }, 60 expected: true, 61 expectedLen: 65536, 62 }, 63 { 64 name: "memory exported, two pages", 65 input: &Module{ 66 MemorySection: &Memory{Min: 2, Cap: 2}, 67 ExportSection: []*Export{{Type: ExternTypeMemory, Name: "memory", Index: 0}}, 68 }, 69 expected: true, 70 expectedLen: 65536 * 2, 71 }, 72 } 73 74 for _, tt := range tests { 75 tc := tt 76 77 t.Run(tc.name, func(t *testing.T) { 78 s, ns := newStore() 79 80 instance, err := s.Instantiate(testCtx, ns, tc.input, "test", nil) 81 require.NoError(t, err) 82 83 mem := instance.ExportedMemory("memory") 84 if tc.expected { 85 require.Equal(t, tc.expectedLen, mem.Size(testCtx)) 86 } else { 87 require.Nil(t, mem) 88 } 89 }) 90 } 91 } 92 93 func TestStore_Instantiate(t *testing.T) { 94 s, ns := newStore() 95 m, err := NewHostModule("", map[string]interface{}{"fn": func() {}}, nil, api.CoreFeaturesV1) 96 require.NoError(t, err) 97 98 sysCtx := sys.DefaultContext(nil) 99 mod, err := s.Instantiate(testCtx, ns, m, "", sysCtx) 100 require.NoError(t, err) 101 defer mod.Close(testCtx) 102 103 t.Run("CallContext defaults", func(t *testing.T) { 104 require.Equal(t, ns.modules[""], mod.module) 105 require.Equal(t, ns.modules[""].Memory, mod.memory) 106 require.Equal(t, ns, mod.ns) 107 require.Equal(t, sysCtx, mod.Sys) 108 }) 109 } 110 111 func TestStore_CloseWithExitCode(t *testing.T) { 112 const importedModuleName = "imported" 113 const importingModuleName = "test" 114 115 tests := []struct { 116 name string 117 testClosed bool 118 }{ 119 { 120 name: "nothing closed", 121 testClosed: false, 122 }, 123 { 124 name: "partially closed", 125 testClosed: true, 126 }, 127 } 128 129 for _, tt := range tests { 130 tc := tt 131 t.Run(tc.name, func(t *testing.T) { 132 s, ns := newStore() 133 134 _, err := s.Instantiate(testCtx, ns, &Module{ 135 TypeSection: []*FunctionType{v_v}, 136 FunctionSection: []uint32{0}, 137 CodeSection: []*Code{{Body: []byte{OpcodeEnd}}}, 138 ExportSection: []*Export{{Type: ExternTypeFunc, Index: 0, Name: "fn"}}, 139 FunctionDefinitionSection: []*FunctionDefinition{{funcType: v_v}}, 140 }, importedModuleName, nil) 141 require.NoError(t, err) 142 143 m2, err := s.Instantiate(testCtx, ns, &Module{ 144 TypeSection: []*FunctionType{v_v}, 145 ImportSection: []*Import{{Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}}, 146 MemorySection: &Memory{Min: 1, Cap: 1}, 147 GlobalSection: []*Global{{Type: &GlobalType{}, Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1}}}, 148 TableSection: []*Table{{Min: 10}}, 149 }, importingModuleName, nil) 150 require.NoError(t, err) 151 152 if tc.testClosed { 153 err = m2.CloseWithExitCode(testCtx, 2) 154 require.NoError(t, err) 155 } 156 157 err = s.CloseWithExitCode(testCtx, 2) 158 require.NoError(t, err) 159 160 // If Namespace.CloseWithExitCode was dispatched properly, modules should be empty 161 require.Zero(t, len(ns.modules)) 162 163 // Store state zeroed 164 require.Zero(t, len(s.namespaces)) 165 require.Zero(t, len(s.typeIDs)) 166 }) 167 } 168 } 169 170 func TestStore_hammer(t *testing.T) { 171 const importedModuleName = "imported" 172 173 m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func() {}}, nil, api.CoreFeaturesV1) 174 require.NoError(t, err) 175 176 s, ns := newStore() 177 imported, err := s.Instantiate(testCtx, ns, m, importedModuleName, nil) 178 require.NoError(t, err) 179 180 _, ok := ns.modules[imported.Name()] 181 require.True(t, ok) 182 183 importingModule := &Module{ 184 TypeSection: []*FunctionType{v_v}, 185 FunctionSection: []uint32{0}, 186 CodeSection: []*Code{{Body: []byte{OpcodeEnd}}}, 187 MemorySection: &Memory{Min: 1, Cap: 1}, 188 GlobalSection: []*Global{{ 189 Type: &GlobalType{ValType: ValueTypeI32}, 190 Init: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(1)}, 191 }}, 192 TableSection: []*Table{{Min: 10}}, 193 ImportSection: []*Import{ 194 {Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}, 195 }, 196 } 197 importingModule.BuildFunctionDefinitions() 198 199 // Concurrent instantiate, close should test if locks work on the ns. If they don't, we should see leaked modules 200 // after all of these complete, or an error raised. 201 P := 8 // max count of goroutines 202 N := 1000 // work per goroutine 203 if testing.Short() { // Adjust down if `-test.short` 204 P = 4 205 N = 100 206 } 207 hammer.NewHammer(t, P, N).Run(func(name string) { 208 mod, instantiateErr := s.Instantiate(testCtx, ns, importingModule, name, sys.DefaultContext(nil)) 209 require.NoError(t, instantiateErr) 210 require.NoError(t, mod.Close(testCtx)) 211 }, nil) 212 if t.Failed() { 213 return // At least one test failed, so return now. 214 } 215 216 // Close the imported module. 217 require.NoError(t, imported.Close(testCtx)) 218 219 // All instances are freed. 220 require.Zero(t, len(ns.modules)) 221 } 222 223 func TestStore_Instantiate_Errors(t *testing.T) { 224 const importedModuleName = "imported" 225 const importingModuleName = "test" 226 227 m, err := NewHostModule(importedModuleName, map[string]interface{}{"fn": func() {}}, nil, api.CoreFeaturesV1) 228 require.NoError(t, err) 229 230 t.Run("Fails if module name already in use", func(t *testing.T) { 231 s, ns := newStore() 232 _, err = s.Instantiate(testCtx, ns, m, importedModuleName, nil) 233 require.NoError(t, err) 234 235 // Trying to register it again should fail 236 _, err = s.Instantiate(testCtx, ns, m, importedModuleName, nil) 237 require.EqualError(t, err, "module[imported] has already been instantiated") 238 }) 239 240 t.Run("fail resolve import", func(t *testing.T) { 241 s, ns := newStore() 242 _, err = s.Instantiate(testCtx, ns, m, importedModuleName, nil) 243 require.NoError(t, err) 244 245 hm := ns.modules[importedModuleName] 246 require.NotNil(t, hm) 247 248 _, err = s.Instantiate(testCtx, ns, &Module{ 249 TypeSection: []*FunctionType{v_v}, 250 ImportSection: []*Import{ 251 // The first import resolve succeeds -> increment hm.dependentCount. 252 {Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}, 253 // But the second one tries to import uninitialized-module -> 254 {Type: ExternTypeFunc, Module: "non-exist", Name: "fn", DescFunc: 0}, 255 }, 256 }, importingModuleName, nil) 257 require.EqualError(t, err, "module[non-exist] not instantiated") 258 }) 259 260 t.Run("creating engine failed", func(t *testing.T) { 261 s, ns := newStore() 262 263 _, err = s.Instantiate(testCtx, ns, m, importedModuleName, nil) 264 require.NoError(t, err) 265 266 hm := ns.modules[importedModuleName] 267 require.NotNil(t, hm) 268 269 engine := s.Engine.(*mockEngine) 270 engine.shouldCompileFail = true 271 272 importingModule := &Module{ 273 TypeSection: []*FunctionType{v_v}, 274 FunctionSection: []uint32{0, 0}, 275 CodeSection: []*Code{ 276 {Body: []byte{OpcodeEnd}}, 277 {Body: []byte{OpcodeEnd}}, 278 }, 279 ImportSection: []*Import{ 280 {Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}, 281 }, 282 } 283 importingModule.BuildFunctionDefinitions() 284 285 _, err = s.Instantiate(testCtx, ns, importingModule, importingModuleName, nil) 286 require.EqualError(t, err, "some engine creation error") 287 }) 288 289 t.Run("start func failed", func(t *testing.T) { 290 s, ns := newStore() 291 engine := s.Engine.(*mockEngine) 292 engine.callFailIndex = 1 293 294 _, err = s.Instantiate(testCtx, ns, m, importedModuleName, nil) 295 require.NoError(t, err) 296 297 hm := ns.modules[importedModuleName] 298 require.NotNil(t, hm) 299 300 startFuncIndex := uint32(1) 301 importingModule := &Module{ 302 TypeSection: []*FunctionType{v_v}, 303 FunctionSection: []uint32{0}, 304 CodeSection: []*Code{{Body: []byte{OpcodeEnd}}}, 305 StartSection: &startFuncIndex, 306 ImportSection: []*Import{ 307 {Type: ExternTypeFunc, Module: importedModuleName, Name: "fn", DescFunc: 0}, 308 }, 309 } 310 importingModule.BuildFunctionDefinitions() 311 312 _, err = s.Instantiate(testCtx, ns, importingModule, importingModuleName, nil) 313 require.EqualError(t, err, "start function[1] failed: call failed") 314 }) 315 } 316 317 func TestCallContext_ExportedFunction(t *testing.T) { 318 host, err := NewHostModule("host", map[string]interface{}{"host_fn": func() {}}, nil, api.CoreFeaturesV1) 319 require.NoError(t, err) 320 321 s, ns := newStore() 322 323 // Add the host module 324 imported, err := s.Instantiate(testCtx, ns, host, host.NameSection.ModuleName, nil) 325 require.NoError(t, err) 326 defer imported.Close(testCtx) 327 328 t.Run("imported function", func(t *testing.T) { 329 importing, err := s.Instantiate(testCtx, ns, &Module{ 330 TypeSection: []*FunctionType{v_v}, 331 ImportSection: []*Import{{Type: ExternTypeFunc, Module: "host", Name: "host_fn", DescFunc: 0}}, 332 MemorySection: &Memory{Min: 1, Cap: 1}, 333 ExportSection: []*Export{{Type: ExternTypeFunc, Name: "host.fn", Index: 0}}, 334 }, "test", nil) 335 require.NoError(t, err) 336 defer importing.Close(testCtx) 337 338 fn := importing.ExportedFunction("host.fn") 339 require.NotNil(t, fn) 340 341 require.Equal(t, fn.(*importedFn).importedFn, imported.ExportedFunction("host_fn").(*function).fi) 342 require.Equal(t, fn.(*importedFn).importingModule, importing) 343 }) 344 } 345 346 type mockEngine struct { 347 shouldCompileFail bool 348 callFailIndex int 349 } 350 351 type mockModuleEngine struct { 352 name string 353 callFailIndex int 354 functionRefs map[Index]Reference 355 } 356 357 type mockCallEngine struct { 358 f *FunctionInstance 359 callFailIndex int 360 } 361 362 func newStore() (*Store, *Namespace) { 363 return NewStore(api.CoreFeaturesV1, &mockEngine{shouldCompileFail: false, callFailIndex: -1}) 364 } 365 366 // CompileModule implements the same method as documented on wasm.Engine. 367 func (e *mockEngine) CompileModule(context.Context, *Module, []experimental.FunctionListener) error { 368 return nil 369 } 370 371 // LookupFunction implements the same method as documented on wasm.Engine. 372 func (e *mockModuleEngine) LookupFunction(*TableInstance, FunctionTypeID, Index) (Index, error) { 373 return 0, nil 374 } 375 376 // CompiledModuleCount implements the same method as documented on wasm.Engine. 377 func (e *mockEngine) CompiledModuleCount() uint32 { return 0 } 378 379 // DeleteCompiledModule implements the same method as documented on wasm.Engine. 380 func (e *mockEngine) DeleteCompiledModule(*Module) {} 381 382 // NewModuleEngine implements the same method as documented on wasm.Engine. 383 func (e *mockEngine) NewModuleEngine(_ string, _ *Module, _, _ []*FunctionInstance) (ModuleEngine, error) { 384 if e.shouldCompileFail { 385 return nil, fmt.Errorf("some engine creation error") 386 } 387 return &mockModuleEngine{callFailIndex: e.callFailIndex}, nil 388 } 389 390 // FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine. 391 func (e *mockModuleEngine) FunctionInstanceReference(i Index) Reference { 392 return e.functionRefs[i] 393 } 394 395 // NewCallEngine implements the same method as documented on wasm.ModuleEngine. 396 func (e *mockModuleEngine) NewCallEngine(callCtx *CallContext, f *FunctionInstance) (CallEngine, error) { 397 return &mockCallEngine{f: f, callFailIndex: e.callFailIndex}, nil 398 } 399 400 // CreateFuncElementInstance implements the same method as documented on wasm.ModuleEngine. 401 func (e *mockModuleEngine) CreateFuncElementInstance([]*Index) *ElementInstance { 402 return nil 403 } 404 405 // InitializeFuncrefGlobals implements the same method as documented on wasm.ModuleEngine. 406 func (e *mockModuleEngine) InitializeFuncrefGlobals(globals []*GlobalInstance) {} 407 408 // Name implements the same method as documented on wasm.ModuleEngine. 409 func (e *mockModuleEngine) Name() string { 410 return e.name 411 } 412 413 // Close implements the same method as documented on wasm.ModuleEngine. 414 func (e *mockModuleEngine) Close(context.Context) { 415 } 416 417 // Call implements the same method as documented on wasm.ModuleEngine. 418 func (ce *mockCallEngine) Call(ctx context.Context, callCtx *CallContext, _ []uint64) (results []uint64, err error) { 419 if ce.callFailIndex >= 0 && ce.f.Definition.Index() == Index(ce.callFailIndex) { 420 err = errors.New("call failed") 421 return 422 } 423 return 424 } 425 426 func TestStore_getFunctionTypeID(t *testing.T) { 427 t.Run("too many functions", func(t *testing.T) { 428 s, _ := newStore() 429 const max = 10 430 s.functionMaxTypes = max 431 s.typeIDs = make(map[string]FunctionTypeID) 432 for i := 0; i < max; i++ { 433 s.typeIDs[strconv.Itoa(i)] = 0 434 } 435 _, err := s.getFunctionTypeID(&FunctionType{}) 436 require.Error(t, err) 437 }) 438 t.Run("ok", func(t *testing.T) { 439 tests := []*FunctionType{ 440 {Params: []ValueType{}}, 441 {Params: []ValueType{ValueTypeF32}}, 442 {Results: []ValueType{ValueTypeF64}}, 443 {Params: []ValueType{ValueTypeI32}, Results: []ValueType{ValueTypeI64}}, 444 } 445 446 for _, tt := range tests { 447 tc := tt 448 t.Run(tc.String(), func(t *testing.T) { 449 s, _ := newStore() 450 actual, err := s.getFunctionTypeID(tc) 451 require.NoError(t, err) 452 453 expectedTypeID, ok := s.typeIDs[tc.String()] 454 require.True(t, ok) 455 require.Equal(t, expectedTypeID, actual) 456 }) 457 } 458 }) 459 } 460 461 func TestExecuteConstExpression(t *testing.T) { 462 t.Run("basic type const expr", func(t *testing.T) { 463 for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} { 464 t.Run(ValueTypeName(vt), func(t *testing.T) { 465 expr := &ConstantExpression{} 466 switch vt { 467 case ValueTypeI32: 468 expr.Data = []byte{1} 469 expr.Opcode = OpcodeI32Const 470 case ValueTypeI64: 471 expr.Data = []byte{2} 472 expr.Opcode = OpcodeI64Const 473 case ValueTypeF32: 474 expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32)) 475 expr.Opcode = OpcodeF32Const 476 case ValueTypeF64: 477 expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64)) 478 expr.Opcode = OpcodeF64Const 479 } 480 481 raw := executeConstExpression(nil, expr) 482 require.NotNil(t, raw) 483 484 switch vt { 485 case ValueTypeI32: 486 actual, ok := raw.(int32) 487 require.True(t, ok) 488 require.Equal(t, int32(1), actual) 489 case ValueTypeI64: 490 actual, ok := raw.(int64) 491 require.True(t, ok) 492 require.Equal(t, int64(2), actual) 493 case ValueTypeF32: 494 actual, ok := raw.(float32) 495 require.True(t, ok) 496 require.Equal(t, float32(math.MaxFloat32), actual) 497 case ValueTypeF64: 498 actual, ok := raw.(float64) 499 require.True(t, ok) 500 require.Equal(t, float64(math.MaxFloat64), actual) 501 } 502 }) 503 } 504 }) 505 t.Run("reference types", func(t *testing.T) { 506 tests := []struct { 507 name string 508 expr *ConstantExpression 509 exp interface{} 510 }{ 511 { 512 name: "ref.null (externref)", 513 expr: &ConstantExpression{ 514 Opcode: OpcodeRefNull, 515 Data: []byte{RefTypeExternref}, 516 }, 517 exp: int64(0), 518 }, 519 { 520 name: "ref.null (funcref)", 521 expr: &ConstantExpression{ 522 Opcode: OpcodeRefNull, 523 Data: []byte{RefTypeFuncref}, 524 }, 525 exp: GlobalInstanceNullFuncRefValue, 526 }, 527 { 528 name: "ref.func", 529 expr: &ConstantExpression{ 530 Opcode: OpcodeRefFunc, 531 Data: []byte{1}, 532 }, 533 exp: uint32(1), 534 }, 535 { 536 name: "ref.func", 537 expr: &ConstantExpression{ 538 Opcode: OpcodeRefFunc, 539 Data: []byte{0x5d}, 540 }, 541 exp: uint32(93), 542 }, 543 } 544 545 for _, tt := range tests { 546 tc := tt 547 t.Run(tc.name, func(t *testing.T) { 548 val := executeConstExpression(nil, tc.expr) 549 require.Equal(t, tc.exp, val) 550 }) 551 } 552 }) 553 t.Run("global expr", func(t *testing.T) { 554 tests := []struct { 555 valueType ValueType 556 val, valHi uint64 557 }{ 558 {valueType: ValueTypeI32, val: 10}, 559 {valueType: ValueTypeI64, val: 20}, 560 {valueType: ValueTypeF32, val: uint64(math.Float32bits(634634432.12311))}, 561 {valueType: ValueTypeF64, val: math.Float64bits(1.12312311)}, 562 {valueType: ValueTypeV128, val: 0x1, valHi: 0x2}, 563 } 564 565 for _, tt := range tests { 566 tc := tt 567 t.Run(ValueTypeName(tc.valueType), func(t *testing.T) { 568 // The index specified in Data equals zero. 569 expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet} 570 globals := []*GlobalInstance{{Val: tc.val, ValHi: tc.valHi, Type: &GlobalType{ValType: tc.valueType}}} 571 572 val := executeConstExpression(globals, expr) 573 require.NotNil(t, val) 574 575 switch tc.valueType { 576 case ValueTypeI32: 577 actual, ok := val.(int32) 578 require.True(t, ok) 579 require.Equal(t, int32(tc.val), actual) 580 case ValueTypeI64: 581 actual, ok := val.(int64) 582 require.True(t, ok) 583 require.Equal(t, int64(tc.val), actual) 584 case ValueTypeF32: 585 actual, ok := val.(float32) 586 require.True(t, ok) 587 require.Equal(t, api.DecodeF32(tc.val), actual) 588 case ValueTypeF64: 589 actual, ok := val.(float64) 590 require.True(t, ok) 591 require.Equal(t, api.DecodeF64(tc.val), actual) 592 case ValueTypeV128: 593 vector, ok := val.([2]uint64) 594 require.True(t, ok) 595 require.Equal(t, uint64(0x1), vector[0]) 596 require.Equal(t, uint64(0x2), vector[1]) 597 } 598 }) 599 } 600 }) 601 602 t.Run("vector", func(t *testing.T) { 603 expr := &ConstantExpression{Data: []byte{ 604 1, 0, 0, 0, 0, 0, 0, 0, 605 2, 0, 0, 0, 0, 0, 0, 0, 606 }, Opcode: OpcodeVecV128Const} 607 val := executeConstExpression(nil, expr) 608 require.NotNil(t, val) 609 vector, ok := val.([2]uint64) 610 require.True(t, ok) 611 require.Equal(t, uint64(0x1), vector[0]) 612 require.Equal(t, uint64(0x2), vector[1]) 613 }) 614 } 615 616 func Test_resolveImports(t *testing.T) { 617 const moduleName = "test" 618 const name = "target" 619 620 t.Run("module not instantiated", func(t *testing.T) { 621 modules := map[string]*ModuleInstance{} 622 _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: "unknown", Name: "unknown"}}}, modules) 623 require.EqualError(t, err, "module[unknown] not instantiated") 624 }) 625 t.Run("export instance not found", func(t *testing.T) { 626 modules := map[string]*ModuleInstance{ 627 moduleName: {Exports: map[string]*ExportInstance{}, Name: moduleName}, 628 } 629 _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: "unknown"}}}, modules) 630 require.EqualError(t, err, "\"unknown\" is not exported in module \"test\"") 631 }) 632 t.Run("func", func(t *testing.T) { 633 t.Run("ok", func(t *testing.T) { 634 f := &FunctionInstance{ 635 Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeF32}}}, 636 } 637 g := &FunctionInstance{ 638 Definition: &FunctionDefinition{funcType: &FunctionType{Results: []ValueType{ValueTypeI32}}}, 639 } 640 modules := map[string]*ModuleInstance{ 641 moduleName: { 642 Exports: map[string]*ExportInstance{ 643 name: {Function: f}, 644 "": {Function: g}, 645 }, 646 Name: moduleName, 647 }, 648 } 649 m := &Module{ 650 TypeSection: []*FunctionType{{Results: []ValueType{ValueTypeF32}}, {Results: []ValueType{ValueTypeI32}}}, 651 ImportSection: []*Import{ 652 {Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}, 653 {Module: moduleName, Name: "", Type: ExternTypeFunc, DescFunc: 1}, 654 }, 655 } 656 functions, _, _, _, err := resolveImports(m, modules) 657 require.NoError(t, err) 658 require.True(t, functionsContain(functions, f), "expected to find %v in %v", f, functions) 659 require.True(t, functionsContain(functions, g), "expected to find %v in %v", g, functions) 660 }) 661 t.Run("type out of range", func(t *testing.T) { 662 modules := map[string]*ModuleInstance{ 663 moduleName: {Exports: map[string]*ExportInstance{name: {}}, Name: moduleName}, 664 } 665 _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 100}}}, modules) 666 require.EqualError(t, err, "import[0] func[test.target]: function type out of range") 667 }) 668 t.Run("signature mismatch", func(t *testing.T) { 669 modules := map[string]*ModuleInstance{ 670 moduleName: {Exports: map[string]*ExportInstance{name: { 671 Function: &FunctionInstance{Definition: &FunctionDefinition{funcType: &FunctionType{}}}, 672 }}, Name: moduleName}, 673 } 674 m := &Module{ 675 TypeSection: []*FunctionType{{Results: []ValueType{ValueTypeF32}}}, 676 ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}}, 677 } 678 _, _, _, _, err := resolveImports(m, modules) 679 require.EqualError(t, err, "import[0] func[test.target]: signature mismatch: v_f32 != v_v") 680 }) 681 }) 682 t.Run("global", func(t *testing.T) { 683 t.Run("ok", func(t *testing.T) { 684 g := &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32}} 685 modules := map[string]*ModuleInstance{ 686 moduleName: {Exports: map[string]*ExportInstance{name: {Type: ExternTypeGlobal, Global: g}}, Name: moduleName}, 687 } 688 _, globals, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: g.Type}}}, modules) 689 require.NoError(t, err) 690 require.True(t, globalsContain(globals, g), "expected to find %v in %v", g, globals) 691 }) 692 t.Run("mutability mismatch", func(t *testing.T) { 693 modules := map[string]*ModuleInstance{ 694 moduleName: {Exports: map[string]*ExportInstance{name: { 695 Type: ExternTypeGlobal, 696 Global: &GlobalInstance{Type: &GlobalType{Mutable: false}}, 697 }}, Name: moduleName}, 698 } 699 _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: &GlobalType{Mutable: true}}}}, modules) 700 require.EqualError(t, err, "import[0] global[test.target]: mutability mismatch: true != false") 701 }) 702 t.Run("type mismatch", func(t *testing.T) { 703 modules := map[string]*ModuleInstance{ 704 moduleName: {Exports: map[string]*ExportInstance{name: { 705 Type: ExternTypeGlobal, 706 Global: &GlobalInstance{Type: &GlobalType{ValType: ValueTypeI32}}, 707 }}, Name: moduleName}, 708 } 709 _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: &GlobalType{ValType: ValueTypeF64}}}}, modules) 710 require.EqualError(t, err, "import[0] global[test.target]: value type mismatch: f64 != i32") 711 }) 712 }) 713 t.Run("memory", func(t *testing.T) { 714 t.Run("ok", func(t *testing.T) { 715 max := uint32(10) 716 memoryInst := &MemoryInstance{Max: max} 717 modules := map[string]*ModuleInstance{ 718 moduleName: {Exports: map[string]*ExportInstance{name: { 719 Type: ExternTypeMemory, 720 Memory: memoryInst, 721 }}, Name: moduleName}, 722 } 723 _, _, _, memory, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}}}, modules) 724 require.NoError(t, err) 725 require.Equal(t, memory, memoryInst) 726 }) 727 t.Run("minimum size mismatch", func(t *testing.T) { 728 importMemoryType := &Memory{Min: 2, Cap: 2} 729 modules := map[string]*ModuleInstance{ 730 moduleName: {Exports: map[string]*ExportInstance{name: { 731 Type: ExternTypeMemory, 732 Memory: &MemoryInstance{Min: importMemoryType.Min - 1, Cap: 2}, 733 }}, Name: moduleName}, 734 } 735 _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, modules) 736 require.EqualError(t, err, "import[0] memory[test.target]: minimum size mismatch: 2 > 1") 737 }) 738 t.Run("maximum size mismatch", func(t *testing.T) { 739 max := uint32(10) 740 importMemoryType := &Memory{Max: max} 741 modules := map[string]*ModuleInstance{ 742 moduleName: {Exports: map[string]*ExportInstance{name: { 743 Type: ExternTypeMemory, 744 Memory: &MemoryInstance{Max: MemoryLimitPages}, 745 }}, Name: moduleName}, 746 } 747 _, _, _, _, err := resolveImports(&Module{ImportSection: []*Import{{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, modules) 748 require.EqualError(t, err, "import[0] memory[test.target]: maximum size mismatch: 10 < 65536") 749 }) 750 }) 751 } 752 753 func TestModuleInstance_validateData(t *testing.T) { 754 m := &ModuleInstance{Memory: &MemoryInstance{Buffer: make([]byte, 5)}} 755 tests := []struct { 756 name string 757 data []*DataSegment 758 expErr string 759 }{ 760 { 761 name: "ok", 762 data: []*DataSegment{ 763 {OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []byte{0}}, 764 {OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []byte{0}}, 765 }, 766 }, 767 { 768 name: "out of bounds - single one byte", 769 data: []*DataSegment{ 770 {OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(5)}, Init: []byte{0}}, 771 }, 772 expErr: "data[0]: out of bounds memory access", 773 }, 774 { 775 name: "out of bounds - multi bytes", 776 data: []*DataSegment{ 777 {OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(0)}, Init: []byte{0}}, 778 {OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(3)}, Init: []byte{0, 1, 2}}, 779 }, 780 expErr: "data[1]: out of bounds memory access", 781 }, 782 } 783 784 for _, tt := range tests { 785 tc := tt 786 t.Run(tc.name, func(t *testing.T) { 787 err := m.validateData(tc.data) 788 if tc.expErr != "" { 789 require.EqualError(t, err, tc.expErr) 790 } else { 791 require.NoError(t, err) 792 } 793 }) 794 } 795 } 796 797 func TestModuleInstance_applyData(t *testing.T) { 798 t.Run("ok", func(t *testing.T) { 799 m := &ModuleInstance{Memory: &MemoryInstance{Buffer: make([]byte, 10)}} 800 err := m.applyData([]*DataSegment{ 801 {OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []byte{0xa, 0xf}}, 802 {OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{0x1, 0x5}}, 803 }) 804 require.NoError(t, err) 805 require.Equal(t, []byte{0xa, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5}, m.Memory.Buffer) 806 require.Equal(t, [][]byte{{0xa, 0xf}, {0x1, 0x5}}, m.DataInstances) 807 }) 808 t.Run("error", func(t *testing.T) { 809 m := &ModuleInstance{Memory: &MemoryInstance{Buffer: make([]byte, 5)}} 810 err := m.applyData([]*DataSegment{ 811 {OffsetExpression: &ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{}}, 812 }) 813 require.EqualError(t, err, "data[0]: out of bounds memory access") 814 }) 815 } 816 817 func globalsContain(globals []*GlobalInstance, want *GlobalInstance) bool { 818 for _, f := range globals { 819 if f == want { 820 return true 821 } 822 } 823 return false 824 } 825 826 func functionsContain(functions []*FunctionInstance, want *FunctionInstance) bool { 827 for _, f := range functions { 828 if f == want { 829 return true 830 } 831 } 832 return false 833 } 834 835 func TestModuleInstance_applyTableInits(t *testing.T) { 836 t.Run("extenref", func(t *testing.T) { 837 tables := []*TableInstance{{Type: RefTypeExternref, References: make([]Reference, 10)}} 838 for i := range tables[0].References { 839 tables[0].References[i] = 0xffff // non-null ref. 840 } 841 m := &ModuleInstance{} 842 843 // This shouldn't panic. 844 m.applyTableInits(tables, []tableInitEntry{{offset: 100}}) 845 m.applyTableInits(tables, []tableInitEntry{ 846 {offset: 0, nullExternRefCount: 3}, 847 {offset: 100}, // Iteration stops at this point, so the offset:5 below shouldn't be applied. 848 {offset: 5, nullExternRefCount: 5}, 849 }) 850 require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 851 tables[0].References) 852 m.applyTableInits(tables, []tableInitEntry{ 853 {offset: 5, nullExternRefCount: 5}, 854 }) 855 require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0, 0, 0, 0, 0}, tables[0].References) 856 }) 857 t.Run("funcref", func(t *testing.T) { 858 e := &mockEngine{} 859 me, err := e.NewModuleEngine("", nil, nil, nil) 860 me.(*mockModuleEngine).functionRefs = map[Index]Reference{0: 0xa, 1: 0xaa, 2: 0xaaa, 3: 0xaaaa} 861 require.NoError(t, err) 862 m := &ModuleInstance{Engine: me} 863 864 tables := []*TableInstance{{Type: RefTypeFuncref, References: make([]Reference, 10)}} 865 for i := range tables[0].References { 866 tables[0].References[i] = 0xffff // non-null ref. 867 } 868 869 // This shouldn't panic. 870 m.applyTableInits(tables, []tableInitEntry{{offset: 100}}) 871 m.applyTableInits(tables, []tableInitEntry{ 872 {offset: 0, functionIndexes: []*Index{uint32Ptr(0), uint32Ptr(1), uint32Ptr(2)}}, 873 {offset: 100}, // Iteration stops at this point, so the offset:5 below shouldn't be applied. 874 {offset: 5, nullExternRefCount: 5}, 875 }) 876 require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 877 tables[0].References) 878 m.applyTableInits(tables, []tableInitEntry{ 879 {offset: 5, functionIndexes: []*Index{uint32Ptr(0), nil, uint32Ptr(2)}}, 880 }) 881 require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xa, 0xffff, 0xaaa, 0xffff, 0xffff}, 882 tables[0].References) 883 }) 884 }