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