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