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