github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/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(p, n int) { 224 mod, instantiateErr := s.Instantiate(testCtx, importingModule, fmt.Sprintf("%d:%d", p, n), 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(p, n int) { 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 importedMemModEngine ModuleEngine 416 lookupEntries map[Index]mockModuleEngineLookupEntry 417 } 418 419 type mockModuleEngineLookupEntry struct { 420 m *ModuleInstance 421 index Index 422 } 423 424 type mockCallEngine struct { 425 internalapi.WazeroOnlyType 426 index Index 427 callFailIndex int 428 } 429 430 func newStore() *Store { 431 return NewStore(api.CoreFeaturesV1, &mockEngine{shouldCompileFail: false, callFailIndex: -1}) 432 } 433 434 // CompileModule implements the same method as documented on wasm.Engine. 435 func (e *mockEngine) Close() error { 436 return nil 437 } 438 439 // CompileModule implements the same method as documented on wasm.Engine. 440 func (e *mockEngine) CompileModule(context.Context, *Module, []experimental.FunctionListener, bool) error { 441 return nil 442 } 443 444 // LookupFunction implements the same method as documented on wasm.Engine. 445 func (e *mockModuleEngine) LookupFunction(_ *TableInstance, _ FunctionTypeID, offset Index) (*ModuleInstance, Index) { 446 if entry, ok := e.lookupEntries[offset]; ok { 447 return entry.m, entry.index 448 } 449 return nil, 0 450 } 451 452 // CompiledModuleCount implements the same method as documented on wasm.Engine. 453 func (e *mockEngine) CompiledModuleCount() uint32 { return 0 } 454 455 // DeleteCompiledModule implements the same method as documented on wasm.Engine. 456 func (e *mockEngine) DeleteCompiledModule(*Module) {} 457 458 // NewModuleEngine implements the same method as documented on wasm.Engine. 459 func (e *mockEngine) NewModuleEngine(_ *Module, _ *ModuleInstance) (ModuleEngine, error) { 460 if e.shouldCompileFail { 461 return nil, fmt.Errorf("some engine creation error") 462 } 463 return &mockModuleEngine{callFailIndex: e.callFailIndex, resolveImportsCalled: map[Index]Index{}}, nil 464 } 465 466 // GetGlobalValue implements the same method as documented on wasm.ModuleEngine. 467 func (e *mockModuleEngine) GetGlobalValue(idx Index) (lo, hi uint64) { panic("BUG") } 468 469 // SetGlobalValue implements the same method as documented on wasm.ModuleEngine. 470 func (e *mockModuleEngine) SetGlobalValue(idx Index, lo, hi uint64) { panic("BUG") } 471 472 // OwnsGlobals implements the same method as documented on wasm.ModuleEngine. 473 func (e *mockModuleEngine) OwnsGlobals() bool { return false } 474 475 // DoneInstantiation implements the same method as documented on wasm.ModuleEngine. 476 func (e *mockModuleEngine) DoneInstantiation() {} 477 478 // FunctionInstanceReference implements the same method as documented on wasm.ModuleEngine. 479 func (e *mockModuleEngine) FunctionInstanceReference(i Index) Reference { 480 return e.functionRefs[i] 481 } 482 483 // ResolveImportedFunction implements the same method as documented on wasm.ModuleEngine. 484 func (e *mockModuleEngine) ResolveImportedFunction(index, importedIndex Index, _ ModuleEngine) { 485 e.resolveImportsCalled[index] = importedIndex 486 } 487 488 // ResolveImportedMemory implements the same method as documented on wasm.ModuleEngine. 489 func (e *mockModuleEngine) ResolveImportedMemory(imp ModuleEngine) { 490 e.importedMemModEngine = imp 491 } 492 493 // NewFunction implements the same method as documented on wasm.ModuleEngine. 494 func (e *mockModuleEngine) NewFunction(index Index) api.Function { 495 return &mockCallEngine{index: index, callFailIndex: e.callFailIndex} 496 } 497 498 // InitializeFuncrefGlobals implements the same method as documented on wasm.ModuleEngine. 499 func (e *mockModuleEngine) InitializeFuncrefGlobals(globals []*GlobalInstance) {} 500 501 // Name implements the same method as documented on wasm.ModuleEngine. 502 func (e *mockModuleEngine) Name() string { 503 return e.name 504 } 505 506 // Close implements the same method as documented on wasm.ModuleEngine. 507 func (e *mockModuleEngine) Close(context.Context) { 508 } 509 510 // Call implements the same method as documented on api.Function. 511 func (ce *mockCallEngine) Definition() api.FunctionDefinition { return nil } 512 513 // Call implements the same method as documented on api.Function. 514 func (ce *mockCallEngine) Call(ctx context.Context, _ ...uint64) (results []uint64, err error) { 515 return nil, ce.CallWithStack(ctx, nil) 516 } 517 518 // CallWithStack implements the same method as documented on api.Function. 519 func (ce *mockCallEngine) CallWithStack(_ context.Context, _ []uint64) error { 520 if ce.callFailIndex >= 0 && ce.index == Index(ce.callFailIndex) { 521 return errors.New("call failed") 522 } 523 return nil 524 } 525 526 func TestStore_getFunctionTypeID(t *testing.T) { 527 t.Run("too many functions", func(t *testing.T) { 528 s := newStore() 529 const max = 10 530 s.functionMaxTypes = max 531 s.typeIDs = make(map[string]FunctionTypeID) 532 for i := 0; i < max; i++ { 533 s.typeIDs[strconv.Itoa(i)] = 0 534 } 535 _, err := s.GetFunctionTypeID(&FunctionType{}) 536 require.Error(t, err) 537 }) 538 t.Run("ok", func(t *testing.T) { 539 tests := []FunctionType{ 540 {Params: []ValueType{}}, 541 {Params: []ValueType{ValueTypeF32}}, 542 {Results: []ValueType{ValueTypeF64}}, 543 {Params: []ValueType{ValueTypeI32}, Results: []ValueType{ValueTypeI64}}, 544 } 545 546 for _, tt := range tests { 547 tc := tt 548 t.Run(tc.String(), func(t *testing.T) { 549 s := newStore() 550 actual, err := s.GetFunctionTypeID(&tc) 551 require.NoError(t, err) 552 553 expectedTypeID, ok := s.typeIDs[tc.String()] 554 require.True(t, ok) 555 require.Equal(t, expectedTypeID, actual) 556 }) 557 } 558 }) 559 } 560 561 func TestGlobalInstance_initialize(t *testing.T) { 562 t.Run("basic type const expr", func(t *testing.T) { 563 for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} { 564 t.Run(ValueTypeName(vt), func(t *testing.T) { 565 g := &GlobalInstance{Type: GlobalType{ValType: vt}} 566 expr := &ConstantExpression{} 567 switch vt { 568 case ValueTypeI32: 569 expr.Data = []byte{1} 570 expr.Opcode = OpcodeI32Const 571 case ValueTypeI64: 572 expr.Data = []byte{2} 573 expr.Opcode = OpcodeI64Const 574 case ValueTypeF32: 575 expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32)) 576 expr.Opcode = OpcodeF32Const 577 case ValueTypeF64: 578 expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64)) 579 expr.Opcode = OpcodeF64Const 580 } 581 582 g.initialize(nil, expr, nil) 583 584 switch vt { 585 case ValueTypeI32: 586 require.Equal(t, int32(1), int32(g.Val)) 587 case ValueTypeI64: 588 require.Equal(t, int64(2), int64(g.Val)) 589 case ValueTypeF32: 590 require.Equal(t, float32(math.MaxFloat32), math.Float32frombits(uint32(g.Val))) 591 case ValueTypeF64: 592 require.Equal(t, math.MaxFloat64, math.Float64frombits(g.Val)) 593 } 594 }) 595 } 596 }) 597 t.Run("ref.null", func(t *testing.T) { 598 tests := []struct { 599 name string 600 expr *ConstantExpression 601 }{ 602 { 603 name: "ref.null (externref)", 604 expr: &ConstantExpression{ 605 Opcode: OpcodeRefNull, 606 Data: []byte{RefTypeExternref}, 607 }, 608 }, 609 { 610 name: "ref.null (funcref)", 611 expr: &ConstantExpression{ 612 Opcode: OpcodeRefNull, 613 Data: []byte{RefTypeFuncref}, 614 }, 615 }, 616 } 617 618 for _, tt := range tests { 619 tc := tt 620 t.Run(tc.name, func(t *testing.T) { 621 g := GlobalInstance{} 622 g.Type.ValType = tc.expr.Data[0] 623 g.initialize(nil, tc.expr, nil) 624 require.Equal(t, uint64(0), g.Val) 625 }) 626 } 627 }) 628 t.Run("ref.func", func(t *testing.T) { 629 g := GlobalInstance{Type: GlobalType{ValType: RefTypeFuncref}} 630 g.initialize(nil, 631 &ConstantExpression{Opcode: OpcodeRefFunc, Data: []byte{1}}, 632 func(funcIndex Index) Reference { 633 require.Equal(t, Index(1), funcIndex) 634 return 0xdeadbeaf 635 }, 636 ) 637 require.Equal(t, uint64(0xdeadbeaf), g.Val) 638 }) 639 t.Run("global expr", func(t *testing.T) { 640 tests := []struct { 641 valueType ValueType 642 val, valHi uint64 643 }{ 644 {valueType: ValueTypeI32, val: 10}, 645 {valueType: ValueTypeI64, val: 20}, 646 {valueType: ValueTypeF32, val: uint64(math.Float32bits(634634432.12311))}, 647 {valueType: ValueTypeF64, val: math.Float64bits(1.12312311)}, 648 {valueType: ValueTypeV128, val: 0x1, valHi: 0x2}, 649 {valueType: ValueTypeExternref, val: 0x12345}, 650 {valueType: ValueTypeFuncref, val: 0x54321}, 651 } 652 653 for _, tt := range tests { 654 tc := tt 655 t.Run(ValueTypeName(tc.valueType), func(t *testing.T) { 656 // The index specified in Data equals zero. 657 expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet} 658 globals := []*GlobalInstance{{Val: tc.val, ValHi: tc.valHi, Type: GlobalType{ValType: tc.valueType}}} 659 660 g := &GlobalInstance{Type: GlobalType{ValType: tc.valueType}} 661 g.initialize(globals, expr, nil) 662 663 switch tc.valueType { 664 case ValueTypeI32: 665 require.Equal(t, int32(tc.val), int32(g.Val)) 666 case ValueTypeI64: 667 require.Equal(t, int64(tc.val), int64(g.Val)) 668 case ValueTypeF32: 669 require.Equal(t, tc.val, g.Val) 670 case ValueTypeF64: 671 require.Equal(t, tc.val, g.Val) 672 case ValueTypeV128: 673 require.Equal(t, uint64(0x1), g.Val) 674 require.Equal(t, uint64(0x2), g.ValHi) 675 case ValueTypeFuncref, ValueTypeExternref: 676 require.Equal(t, tc.val, g.Val) 677 } 678 }) 679 } 680 }) 681 682 t.Run("vector", func(t *testing.T) { 683 expr := &ConstantExpression{Data: []byte{ 684 1, 0, 0, 0, 0, 0, 0, 0, 685 2, 0, 0, 0, 0, 0, 0, 0, 686 }, Opcode: OpcodeVecV128Const} 687 g := GlobalInstance{Type: GlobalType{ValType: ValueTypeV128}} 688 g.initialize(nil, expr, nil) 689 require.Equal(t, uint64(0x1), g.Val) 690 require.Equal(t, uint64(0x2), g.ValHi) 691 }) 692 } 693 694 func Test_resolveImports(t *testing.T) { 695 const moduleName = "test" 696 const name = "target" 697 698 t.Run("module not instantiated", func(t *testing.T) { 699 m := &ModuleInstance{s: newStore()} 700 err := m.resolveImports(&Module{ImportPerModule: map[string][]*Import{"unknown": {{}}}}) 701 require.EqualError(t, err, "module[unknown] not instantiated") 702 }) 703 t.Run("export instance not found", func(t *testing.T) { 704 m := &ModuleInstance{s: newStore()} 705 m.s.nameToModule[moduleName] = &ModuleInstance{Exports: map[string]*Export{}, ModuleName: moduleName} 706 err := m.resolveImports(&Module{ImportPerModule: map[string][]*Import{moduleName: {{Name: "unknown"}}}}) 707 require.EqualError(t, err, "\"unknown\" is not exported in module \"test\"") 708 }) 709 t.Run("func", func(t *testing.T) { 710 t.Run("ok", func(t *testing.T) { 711 s := newStore() 712 s.nameToModule[moduleName] = &ModuleInstance{ 713 Exports: map[string]*Export{ 714 name: {Type: ExternTypeFunc, Index: 2}, 715 "": {Type: ExternTypeFunc, Index: 4}, 716 }, 717 ModuleName: moduleName, 718 Source: &Module{ 719 FunctionSection: []Index{0, 0, 1, 0, 0}, 720 TypeSection: []FunctionType{ 721 {Params: []ValueType{ExternTypeFunc}}, 722 {Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}}, 723 }, 724 }, 725 } 726 727 module := &Module{ 728 TypeSection: []FunctionType{ 729 {Params: []ValueType{i32}, Results: []ValueType{ValueTypeV128}}, 730 {Params: []ValueType{ExternTypeFunc}}, 731 }, 732 ImportFunctionCount: 2, 733 ImportPerModule: map[string][]*Import{ 734 moduleName: { 735 {Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0, IndexPerType: 0}, 736 {Module: moduleName, Name: "", Type: ExternTypeFunc, DescFunc: 1, IndexPerType: 1}, 737 }, 738 }, 739 } 740 741 m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}, s: s, Source: module} 742 err := m.resolveImports(module) 743 require.NoError(t, err) 744 745 me := m.Engine.(*mockModuleEngine) 746 require.Equal(t, me.resolveImportsCalled[0], Index(2)) 747 require.Equal(t, me.resolveImportsCalled[1], Index(4)) 748 }) 749 t.Run("signature mismatch", func(t *testing.T) { 750 s := newStore() 751 s.nameToModule[moduleName] = &ModuleInstance{ 752 Exports: map[string]*Export{ 753 name: {Type: ExternTypeFunc, Index: 0}, 754 }, 755 ModuleName: moduleName, 756 TypeIDs: []FunctionTypeID{123435}, 757 Source: &Module{ 758 FunctionSection: []Index{0}, 759 TypeSection: []FunctionType{ 760 {Params: []ValueType{}}, 761 }, 762 }, 763 } 764 module := &Module{ 765 TypeSection: []FunctionType{{Results: []ValueType{ValueTypeF32}}}, 766 ImportPerModule: map[string][]*Import{ 767 moduleName: {{Module: moduleName, Name: name, Type: ExternTypeFunc, DescFunc: 0}}, 768 }, 769 } 770 771 m := &ModuleInstance{Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}, s: s, Source: module} 772 err := m.resolveImports(module) 773 require.EqualError(t, err, "import func[test.target]: signature mismatch: v_f32 != v_v") 774 }) 775 }) 776 t.Run("global", func(t *testing.T) { 777 t.Run("ok", func(t *testing.T) { 778 s := newStore() 779 g := &GlobalInstance{Type: GlobalType{ValType: ValueTypeI32}} 780 m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s} 781 s.nameToModule[moduleName] = &ModuleInstance{ 782 Globals: []*GlobalInstance{g}, 783 Exports: map[string]*Export{name: {Type: ExternTypeGlobal, Index: 0}}, ModuleName: moduleName, 784 } 785 err := m.resolveImports( 786 &Module{ 787 ImportPerModule: map[string][]*Import{moduleName: {{Name: name, Type: ExternTypeGlobal, DescGlobal: g.Type}}}, 788 }, 789 ) 790 require.NoError(t, err) 791 require.True(t, globalsContain(m.Globals, g), "expected to find %v in %v", g, m.Globals) 792 }) 793 t.Run("mutability mismatch", func(t *testing.T) { 794 s := newStore() 795 s.nameToModule[moduleName] = &ModuleInstance{ 796 Globals: []*GlobalInstance{{Type: GlobalType{Mutable: false}}}, 797 Exports: map[string]*Export{name: { 798 Type: ExternTypeGlobal, 799 Index: 0, 800 }}, 801 ModuleName: moduleName, 802 } 803 m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s} 804 err := m.resolveImports(&Module{ 805 ImportPerModule: map[string][]*Import{moduleName: { 806 {Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{Mutable: true}}, 807 }}, 808 }) 809 require.EqualError(t, err, "import global[test.target]: mutability mismatch: true != false") 810 }) 811 t.Run("type mismatch", func(t *testing.T) { 812 s := newStore() 813 s.nameToModule[moduleName] = &ModuleInstance{ 814 Globals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}}}, 815 Exports: map[string]*Export{name: { 816 Type: ExternTypeGlobal, 817 Index: 0, 818 }}, 819 ModuleName: moduleName, 820 } 821 m := &ModuleInstance{Globals: make([]*GlobalInstance, 1), s: s} 822 err := m.resolveImports(&Module{ 823 ImportPerModule: map[string][]*Import{moduleName: { 824 {Module: moduleName, Name: name, Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeF64}}, 825 }}, 826 }) 827 require.EqualError(t, err, "import global[test.target]: value type mismatch: f64 != i32") 828 }) 829 }) 830 t.Run("memory", func(t *testing.T) { 831 t.Run("ok", func(t *testing.T) { 832 max := uint32(10) 833 memoryInst := &MemoryInstance{Max: max} 834 s := newStore() 835 importedME := &mockModuleEngine{} 836 s.nameToModule[moduleName] = &ModuleInstance{ 837 MemoryInstance: memoryInst, 838 Exports: map[string]*Export{name: { 839 Type: ExternTypeMemory, 840 }}, 841 ModuleName: moduleName, 842 Engine: importedME, 843 } 844 m := &ModuleInstance{s: s, Engine: &mockModuleEngine{resolveImportsCalled: map[Index]Index{}}} 845 err := m.resolveImports(&Module{ 846 ImportPerModule: map[string][]*Import{ 847 moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: &Memory{Max: max}}}, 848 }, 849 }) 850 require.NoError(t, err) 851 require.Equal(t, m.MemoryInstance, memoryInst) 852 require.Equal(t, importedME, m.Engine.(*mockModuleEngine).importedMemModEngine) 853 }) 854 t.Run("minimum size mismatch", func(t *testing.T) { 855 importMemoryType := &Memory{Min: 2, Cap: 2} 856 s := newStore() 857 s.nameToModule[moduleName] = &ModuleInstance{ 858 MemoryInstance: &MemoryInstance{Min: importMemoryType.Min - 1, Cap: 2}, 859 Exports: map[string]*Export{name: { 860 Type: ExternTypeMemory, 861 }}, 862 ModuleName: moduleName, 863 } 864 m := &ModuleInstance{s: s} 865 err := m.resolveImports(&Module{ 866 ImportPerModule: map[string][]*Import{ 867 moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}, 868 }, 869 }) 870 require.EqualError(t, err, "import memory[test.target]: minimum size mismatch: 2 > 1") 871 }) 872 t.Run("maximum size mismatch", func(t *testing.T) { 873 s := newStore() 874 s.nameToModule[moduleName] = &ModuleInstance{ 875 MemoryInstance: &MemoryInstance{Max: MemoryLimitPages}, 876 Exports: map[string]*Export{name: { 877 Type: ExternTypeMemory, 878 }}, 879 ModuleName: moduleName, 880 } 881 882 max := uint32(10) 883 importMemoryType := &Memory{Max: max} 884 m := &ModuleInstance{s: s} 885 err := m.resolveImports(&Module{ 886 ImportPerModule: map[string][]*Import{moduleName: {{Module: moduleName, Name: name, Type: ExternTypeMemory, DescMem: importMemoryType}}}, 887 }) 888 require.EqualError(t, err, "import memory[test.target]: maximum size mismatch: 10 < 65536") 889 }) 890 }) 891 } 892 893 func TestModuleInstance_validateData(t *testing.T) { 894 m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 5)}} 895 tests := []struct { 896 name string 897 data []DataSegment 898 expErr string 899 }{ 900 { 901 name: "ok", 902 data: []DataSegment{ 903 {OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []byte{0}}, 904 {OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []byte{0}}, 905 }, 906 }, 907 { 908 name: "out of bounds - single one byte", 909 data: []DataSegment{ 910 {OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(5)}, Init: []byte{0}}, 911 }, 912 expErr: "data[0]: out of bounds memory access", 913 }, 914 { 915 name: "out of bounds - multi bytes", 916 data: []DataSegment{ 917 {OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(0)}, Init: []byte{0}}, 918 {OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(3)}, Init: []byte{0, 1, 2}}, 919 }, 920 expErr: "data[1]: out of bounds memory access", 921 }, 922 } 923 924 for _, tt := range tests { 925 tc := tt 926 t.Run(tc.name, func(t *testing.T) { 927 err := m.validateData(tc.data) 928 if tc.expErr != "" { 929 require.EqualError(t, err, tc.expErr) 930 } else { 931 require.NoError(t, err) 932 } 933 }) 934 } 935 } 936 937 func TestModuleInstance_applyData(t *testing.T) { 938 t.Run("ok", func(t *testing.T) { 939 m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 10)}} 940 err := m.applyData([]DataSegment{ 941 {OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []byte{0xa, 0xf}}, 942 {OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{0x1, 0x5}}, 943 }) 944 require.NoError(t, err) 945 require.Equal(t, []byte{0xa, 0xf, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1, 0x5}, m.MemoryInstance.Buffer) 946 require.Equal(t, [][]byte{{0xa, 0xf}, {0x1, 0x5}}, m.DataInstances) 947 }) 948 t.Run("error", func(t *testing.T) { 949 m := &ModuleInstance{MemoryInstance: &MemoryInstance{Buffer: make([]byte, 5)}} 950 err := m.applyData([]DataSegment{ 951 {OffsetExpression: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeUint32(8)}, Init: []byte{}}, 952 }) 953 require.EqualError(t, err, "data[0]: out of bounds memory access") 954 }) 955 } 956 957 func globalsContain(globals []*GlobalInstance, want *GlobalInstance) bool { 958 for _, f := range globals { 959 if f == want { 960 return true 961 } 962 } 963 return false 964 } 965 966 func TestModuleInstance_applyElements(t *testing.T) { 967 leb128_100 := leb128.EncodeInt32(100) 968 969 t.Run("extenref", func(t *testing.T) { 970 m := &ModuleInstance{} 971 m.Tables = []*TableInstance{{Type: RefTypeExternref, References: make([]Reference, 10)}} 972 for i := range m.Tables[0].References { 973 m.Tables[0].References[i] = 0xffff // non-null ref. 974 } 975 976 // This shouldn't panic. 977 m.applyElements([]ElementSegment{{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}}}) 978 m.applyElements([]ElementSegment{ 979 {Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: make([]Index, 3)}, 980 {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. 981 {Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)}, 982 }) 983 require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff}, 984 m.Tables[0].References) 985 m.applyElements([]ElementSegment{ 986 {Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)}, 987 }) 988 require.Equal(t, []Reference{0, 0, 0, 0xffff, 0xffff, 0, 0, 0, 0, 0}, m.Tables[0].References) 989 }) 990 t.Run("funcref", func(t *testing.T) { 991 e := &mockEngine{} 992 me, err := e.NewModuleEngine(nil, nil) 993 me.(*mockModuleEngine).functionRefs = map[Index]Reference{0: 0xa, 1: 0xaa, 2: 0xaaa, 3: 0xaaaa} 994 require.NoError(t, err) 995 m := &ModuleInstance{Engine: me, Globals: []*GlobalInstance{{}, {Val: 0xabcde}}} 996 997 m.Tables = []*TableInstance{{Type: RefTypeFuncref, References: make([]Reference, 10)}} 998 for i := range m.Tables[0].References { 999 m.Tables[0].References[i] = 0xffff // non-null ref. 1000 } 1001 1002 // This shouldn't panic. 1003 m.applyElements([]ElementSegment{{Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128_100}, Init: []Index{1, 2, 3}}}) 1004 m.applyElements([]ElementSegment{ 1005 {Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0, 1, 2}}, 1006 {Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{9}}, Init: []Index{1 | elementInitImportedGlobalReferenceType}}, 1007 {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. 1008 {Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: make([]Index, 5)}, 1009 }) 1010 require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xffff, 0xabcde}, 1011 m.Tables[0].References) 1012 m.applyElements([]ElementSegment{ 1013 {Mode: ElementModeActive, OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: []Index{0, ElementInitNullReference, 2}}, 1014 }) 1015 require.Equal(t, []Reference{0xa, 0xaa, 0xaaa, 0xffff, 0xffff, 0xa, 0xffff, 0xaaa, 0xffff, 0xabcde}, 1016 m.Tables[0].References) 1017 }) 1018 }