github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/wasm/table_test.go (about) 1 package wasm 2 3 import ( 4 "math" 5 "testing" 6 7 "github.com/tetratelabs/wazero/api" 8 "github.com/tetratelabs/wazero/internal/leb128" 9 "github.com/tetratelabs/wazero/internal/testing/require" 10 ) 11 12 // Test_ElementInitNullReference_valid ensures it is actually safe to use ElementInitNullReference 13 // as a null reference, and it won't collide with the actual function Index. 14 func Test_ElementInitNullReference_valid(t *testing.T) { 15 require.True(t, MaximumFunctionIndex < ElementInitNullReference) 16 } 17 18 func Test_resolveImports_table(t *testing.T) { 19 const moduleName = "test" 20 const name = "target" 21 22 t.Run("ok", func(t *testing.T) { 23 max := uint32(10) 24 tableInst := &TableInstance{Max: &max, involvingModuleInstances: []*ModuleInstance{{}}} 25 s := newStore() 26 s.nameToModule[moduleName] = &ModuleInstance{ 27 Tables: []*TableInstance{tableInst}, 28 Exports: map[string]*Export{name: {Type: ExternTypeTable, Index: 0}}, 29 ModuleName: moduleName, 30 } 31 m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s} 32 err := m.resolveImports(&Module{ 33 ImportPerModule: map[string][]*Import{ 34 moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: Table{Max: &max}}}, 35 }, 36 }) 37 require.NoError(t, err) 38 require.Equal(t, m.Tables[0], tableInst) 39 require.Equal(t, m.Tables[0].involvingModuleInstances[1], m) 40 }) 41 t.Run("minimum size mismatch", func(t *testing.T) { 42 s := newStore() 43 importTableType := Table{Min: 2} 44 s.nameToModule[moduleName] = &ModuleInstance{ 45 Tables: []*TableInstance{{Min: importTableType.Min - 1}}, 46 Exports: map[string]*Export{name: {Type: ExternTypeTable}}, 47 ModuleName: moduleName, 48 } 49 m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s} 50 err := m.resolveImports(&Module{ 51 ImportPerModule: map[string][]*Import{ 52 moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}, 53 }, 54 }) 55 require.EqualError(t, err, "import table[test.target]: minimum size mismatch: 2 > 1") 56 }) 57 t.Run("maximum size mismatch", func(t *testing.T) { 58 max := uint32(10) 59 importTableType := Table{Max: &max} 60 s := newStore() 61 s.nameToModule[moduleName] = &ModuleInstance{ 62 Tables: []*TableInstance{{Min: importTableType.Min - 1}}, 63 Exports: map[string]*Export{name: {Type: ExternTypeTable}}, 64 ModuleName: moduleName, 65 } 66 m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s} 67 err := m.resolveImports(&Module{ 68 ImportPerModule: map[string][]*Import{ 69 moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: importTableType}}, 70 }, 71 }) 72 require.EqualError(t, err, "import table[test.target]: maximum size mismatch: 10, but actual has no max") 73 }) 74 t.Run("type mismatch", func(t *testing.T) { 75 s := newStore() 76 s.nameToModule[moduleName] = &ModuleInstance{ 77 Tables: []*TableInstance{{Type: RefTypeFuncref}}, 78 Exports: map[string]*Export{name: {Type: ExternTypeTable}}, 79 ModuleName: moduleName, 80 } 81 m := &ModuleInstance{Tables: make([]*TableInstance, 1), s: s} 82 err := m.resolveImports(&Module{ 83 ImportPerModule: map[string][]*Import{ 84 moduleName: {{Module: moduleName, Name: name, Type: ExternTypeTable, DescTable: Table{Type: RefTypeExternref}}}, 85 }, 86 }) 87 require.EqualError(t, err, "import table[test.target]: table type mismatch: externref != funcref") 88 }) 89 } 90 91 var codeEnd = Code{Body: []byte{OpcodeEnd}} 92 93 func TestModule_validateTable(t *testing.T) { 94 const maxTableIndex = 5 95 three := uint32(3) 96 tests := []struct { 97 name string 98 input *Module 99 }{ 100 { 101 name: "empty", 102 input: &Module{}, 103 }, 104 { 105 name: "min zero", 106 input: &Module{TableSection: []Table{{}}}, 107 }, 108 { 109 name: "maximum number of tables", 110 input: &Module{TableSection: []Table{{}, {}, {}, {}, {}}}, 111 }, 112 { 113 name: "min/max", 114 input: &Module{TableSection: []Table{{Min: 1, Max: &three}}}, 115 }, 116 { // See: https://github.com/WebAssembly/spec/issues/1427 117 name: "constant derived element offset=0 and no index", 118 input: &Module{ 119 TypeSection: []FunctionType{{}}, 120 TableSection: []Table{{Min: 1, Type: RefTypeFuncref}}, 121 FunctionSection: []Index{0}, 122 CodeSection: []Code{codeEnd}, 123 ElementSection: []ElementSegment{ 124 { 125 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, 126 Type: RefTypeFuncref, 127 }, 128 }, 129 }, 130 }, 131 { 132 name: "constant derived element offset=0 and one index", 133 input: &Module{ 134 TypeSection: []FunctionType{{}}, 135 TableSection: []Table{{Min: 1, Type: RefTypeFuncref}}, 136 FunctionSection: []Index{0}, 137 CodeSection: []Code{codeEnd}, 138 ElementSection: []ElementSegment{ 139 { 140 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, 141 Init: []Index{0}, 142 Type: RefTypeFuncref, 143 }, 144 }, 145 }, 146 }, 147 { 148 name: "constant derived element offset - ignores min on imported table", 149 input: &Module{ 150 ImportTableCount: 1, 151 TypeSection: []FunctionType{{}}, 152 ImportSection: []Import{{Type: ExternTypeTable, DescTable: Table{Type: RefTypeFuncref}}}, 153 FunctionSection: []Index{0}, 154 CodeSection: []Code{codeEnd}, 155 ElementSection: []ElementSegment{ 156 { 157 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, 158 Init: []Index{0}, 159 Type: RefTypeFuncref, 160 }, 161 }, 162 }, 163 }, 164 { 165 name: "constant derived element offset=0 and one index - imported table", 166 input: &Module{ 167 TypeSection: []FunctionType{{}}, 168 ImportSection: []Import{{Type: ExternTypeTable, DescTable: Table{Min: 1, Type: RefTypeFuncref}}}, 169 FunctionSection: []Index{0}, 170 CodeSection: []Code{codeEnd}, 171 ElementSection: []ElementSegment{ 172 { 173 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, 174 Init: []Index{0}, 175 Type: RefTypeFuncref, 176 }, 177 }, 178 }, 179 }, 180 { 181 name: "constant derived element offset and two indices", 182 input: &Module{ 183 TypeSection: []FunctionType{{}}, 184 TableSection: []Table{{Min: 3, Type: RefTypeFuncref}}, 185 FunctionSection: []Index{0, 0, 0, 0}, 186 CodeSection: []Code{codeEnd, codeEnd, codeEnd, codeEnd}, 187 ElementSection: []ElementSegment{ 188 { 189 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, 190 Init: []Index{0, 2}, 191 Type: RefTypeFuncref, 192 }, 193 }, 194 }, 195 }, 196 { // See: https://github.com/WebAssembly/spec/issues/1427 197 name: "imported global derived element offset and no index", 198 input: &Module{ 199 TypeSection: []FunctionType{{}}, 200 ImportSection: []Import{ 201 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 202 }, 203 TableSection: []Table{{Min: 1, Type: RefTypeFuncref}}, 204 FunctionSection: []Index{0}, 205 CodeSection: []Code{codeEnd}, 206 ElementSection: []ElementSegment{ 207 { 208 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, 209 Type: RefTypeFuncref, 210 }, 211 }, 212 }, 213 }, 214 { 215 name: "imported global derived element offset and one index", 216 input: &Module{ 217 TypeSection: []FunctionType{{}}, 218 ImportSection: []Import{ 219 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 220 }, 221 TableSection: []Table{{Min: 1, Type: RefTypeFuncref}}, 222 FunctionSection: []Index{0}, 223 CodeSection: []Code{codeEnd}, 224 ElementSection: []ElementSegment{ 225 { 226 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, 227 Init: []Index{0}, 228 Type: RefTypeFuncref, 229 }, 230 }, 231 }, 232 }, 233 { 234 name: "imported global derived element offset and one index - imported table", 235 input: &Module{ 236 TypeSection: []FunctionType{{}}, 237 ImportSection: []Import{ 238 {Type: ExternTypeTable, DescTable: Table{Min: 1, Type: RefTypeFuncref}}, 239 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 240 }, 241 FunctionSection: []Index{0}, 242 CodeSection: []Code{codeEnd}, 243 ElementSection: []ElementSegment{ 244 { 245 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, 246 Init: []Index{0}, 247 Type: RefTypeFuncref, 248 }, 249 }, 250 }, 251 }, 252 { 253 name: "imported global derived element offset - ignores min on imported table", 254 input: &Module{ 255 TypeSection: []FunctionType{{}}, 256 ImportSection: []Import{ 257 {Type: ExternTypeTable, DescTable: Table{Type: RefTypeFuncref}}, 258 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 259 }, 260 FunctionSection: []Index{0}, 261 CodeSection: []Code{codeEnd}, 262 ElementSection: []ElementSegment{ 263 { 264 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, 265 Init: []Index{0}, 266 Type: RefTypeFuncref, 267 }, 268 }, 269 }, 270 }, 271 { 272 name: "imported global derived element offset - two indices", 273 input: &Module{ 274 TypeSection: []FunctionType{{}}, 275 ImportSection: []Import{ 276 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI64}}, 277 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 278 }, 279 TableSection: []Table{{Min: 3, Type: RefTypeFuncref}}, 280 FunctionSection: []Index{0, 0, 0, 0}, 281 CodeSection: []Code{codeEnd, codeEnd, codeEnd, codeEnd}, 282 ElementSection: []ElementSegment{ 283 { 284 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x1}}, 285 Init: []Index{0, 2}, 286 Type: RefTypeFuncref, 287 }, 288 }, 289 }, 290 }, 291 { 292 name: "constant offset - two inits from globals - funcref", 293 input: &Module{ 294 TypeSection: []FunctionType{{}}, 295 ImportSection: []Import{ 296 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeFuncref}}, 297 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeFuncref}}, 298 }, 299 ImportGlobalCount: 2, 300 TableSection: []Table{{Min: 10, Type: RefTypeFuncref}}, 301 ElementSection: []ElementSegment{ 302 { 303 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x5}}, 304 Init: []Index{WrapGlobalIndexAsElementInit(0), WrapGlobalIndexAsElementInit(1)}, 305 Type: RefTypeFuncref, 306 }, 307 }, 308 }, 309 }, 310 { 311 name: "constant offset - two inits from globals - externref", 312 input: &Module{ 313 TypeSection: []FunctionType{{}}, 314 ImportSection: []Import{ 315 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeExternref}}, 316 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeExternref}}, 317 }, 318 ImportGlobalCount: 2, 319 TableSection: []Table{{Min: 10, Type: RefTypeExternref}}, 320 ElementSection: []ElementSegment{ 321 { 322 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0x5}}, 323 Init: []Index{elementInitImportedGlobalReferenceType, elementInitImportedGlobalReferenceType | 1}, 324 Type: RefTypeExternref, 325 }, 326 }, 327 }, 328 }, 329 { 330 name: "mixed elementSegments - const before imported global", 331 input: &Module{ 332 TypeSection: []FunctionType{{}}, 333 ImportSection: []Import{ 334 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI64}}, 335 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 336 }, 337 TableSection: []Table{{Min: 3, Type: RefTypeFuncref}}, 338 FunctionSection: []Index{0, 0, 0, 0}, 339 CodeSection: []Code{codeEnd, codeEnd, codeEnd, codeEnd}, 340 ElementSection: []ElementSegment{ 341 { 342 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, 343 Init: []Index{0, 2}, 344 Type: RefTypeFuncref, 345 }, 346 { 347 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x1}}, 348 Init: []Index{1, 2}, 349 Type: RefTypeFuncref, 350 }, 351 }, 352 }, 353 }, 354 } 355 356 for _, tt := range tests { 357 tc := tt 358 359 t.Run(tc.name, func(t *testing.T) { 360 _, _, _, tables, err := tc.input.AllDeclarations() 361 require.NoError(t, err) 362 363 err = tc.input.validateTable(api.CoreFeaturesV1, tables, maxTableIndex) 364 require.NoError(t, err) 365 366 err = tc.input.validateTable(api.CoreFeaturesV1, tables, maxTableIndex) 367 require.NoError(t, err) 368 }) 369 } 370 } 371 372 func TestModule_validateTable_Errors(t *testing.T) { 373 const maxTableIndex = 5 374 tests := []struct { 375 name string 376 input *Module 377 expectedErr string 378 }{ 379 { 380 name: "too many tables", 381 input: &Module{ 382 TableSection: []Table{{}, {}, {}, {}, {}, {}}, 383 }, 384 expectedErr: "too many tables in a module: 6 given with limit 5", 385 }, 386 { 387 name: "type mismatch: unknown ref type", 388 input: &Module{ 389 TableSection: []Table{{Type: RefTypeFuncref}}, 390 ElementSection: []ElementSegment{ 391 { 392 OffsetExpr: ConstantExpression{ 393 Opcode: OpcodeI32Const, 394 Data: leb128.EncodeUint64(math.MaxUint64), 395 }, 396 Type: 0xff, 397 }, 398 }, 399 }, 400 expectedErr: "element type mismatch: table has funcref but element has unknown(0xff)", 401 }, 402 { 403 name: "type mismatch: funcref elem on extern table", 404 input: &Module{ 405 TableSection: []Table{{Type: RefTypeExternref}}, 406 ElementSection: []ElementSegment{ 407 { 408 OffsetExpr: ConstantExpression{ 409 Opcode: OpcodeI32Const, 410 Data: leb128.EncodeUint64(math.MaxUint64), 411 }, 412 Type: RefTypeFuncref, 413 }, 414 }, 415 }, 416 expectedErr: "element type mismatch: table has externref but element has funcref", 417 }, 418 { 419 name: "type mismatch: extern elem on funcref table", 420 input: &Module{ 421 TableSection: []Table{{Type: RefTypeFuncref}}, 422 ElementSection: []ElementSegment{ 423 { 424 OffsetExpr: ConstantExpression{ 425 Opcode: OpcodeI32Const, 426 Data: leb128.EncodeUint64(math.MaxUint64), 427 }, 428 Type: RefTypeExternref, 429 }, 430 }, 431 }, 432 expectedErr: "element type mismatch: table has funcref but element has externref", 433 }, 434 { 435 name: "non-nil non-global externref", 436 input: &Module{ 437 TableSection: []Table{{Type: RefTypeFuncref}}, 438 ElementSection: []ElementSegment{ 439 { 440 OffsetExpr: ConstantExpression{ 441 Opcode: OpcodeI32Const, 442 Data: leb128.EncodeUint64(math.MaxUint64), 443 }, 444 Type: RefTypeExternref, 445 Init: []Index{0}, 446 }, 447 }, 448 }, 449 expectedErr: "element[0].init[0] must be ref.null but was 0", 450 }, 451 { 452 name: "constant derived element offset - decode error", 453 input: &Module{ 454 TypeSection: []FunctionType{{}}, 455 TableSection: []Table{{Type: RefTypeFuncref}}, 456 FunctionSection: []Index{0}, 457 CodeSection: []Code{codeEnd}, 458 ElementSection: []ElementSegment{ 459 { 460 OffsetExpr: ConstantExpression{ 461 Opcode: OpcodeI32Const, 462 Data: leb128.EncodeUint64(math.MaxUint64), 463 }, 464 Init: []Index{0}, 465 Type: RefTypeFuncref, 466 }, 467 }, 468 }, 469 expectedErr: "element[0] couldn't read i32.const parameter: overflows a 32-bit integer", 470 }, 471 { 472 name: "constant derived element offset - wrong ValType", 473 input: &Module{ 474 TypeSection: []FunctionType{{}}, 475 TableSection: []Table{{Type: RefTypeFuncref}}, 476 FunctionSection: []Index{0}, 477 CodeSection: []Code{codeEnd}, 478 ElementSection: []ElementSegment{ 479 { 480 OffsetExpr: ConstantExpression{Opcode: OpcodeI64Const, Data: const0}, Init: []Index{0}, 481 Type: RefTypeFuncref, 482 }, 483 }, 484 }, 485 expectedErr: "element[0] has an invalid const expression: i64.const", 486 }, 487 { 488 name: "constant derived element offset - missing table", 489 input: &Module{ 490 TypeSection: []FunctionType{{}}, 491 FunctionSection: []Index{0}, 492 CodeSection: []Code{codeEnd}, 493 ElementSection: []ElementSegment{ 494 { 495 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, Init: []Index{0}, 496 Type: RefTypeFuncref, 497 }, 498 }, 499 }, 500 expectedErr: "unknown table 0 as active element target", 501 }, 502 { 503 name: "constant derived element offset exceeds table min", 504 input: &Module{ 505 TypeSection: []FunctionType{{}}, 506 TableSection: []Table{{Min: 1, Type: RefTypeFuncref}}, 507 FunctionSection: []Index{0}, 508 CodeSection: []Code{codeEnd}, 509 ElementSection: []ElementSegment{ 510 { 511 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, Init: []Index{0}, 512 Type: RefTypeFuncref, 513 }, 514 }, 515 }, 516 expectedErr: "element[0].init exceeds min table size", 517 }, 518 { 519 name: "constant derived element offset puts init beyond table min", 520 input: &Module{ 521 TypeSection: []FunctionType{{}}, 522 TableSection: []Table{{Min: 2, Type: RefTypeFuncref}}, 523 FunctionSection: []Index{0}, 524 CodeSection: []Code{codeEnd}, 525 ElementSection: []ElementSegment{ 526 { 527 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0}, 528 Type: RefTypeFuncref, 529 }, 530 { 531 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0, 0}, 532 Type: RefTypeFuncref, 533 }, 534 }, 535 }, 536 expectedErr: "element[1].init exceeds min table size", 537 }, 538 { // See: https://github.com/WebAssembly/spec/issues/1427 539 name: "constant derived element offset beyond table min - no init elements", 540 input: &Module{ 541 TypeSection: []FunctionType{{}}, 542 TableSection: []Table{{Min: 1, Type: RefTypeFuncref}}, 543 FunctionSection: []Index{0}, 544 CodeSection: []Code{codeEnd}, 545 ElementSection: []ElementSegment{ 546 { 547 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: leb128.EncodeInt32(2)}, 548 Type: RefTypeFuncref, 549 }, 550 }, 551 }, 552 expectedErr: "element[0].init exceeds min table size", 553 }, 554 { 555 name: "constant derived element offset - func index out of range", 556 input: &Module{ 557 TypeSection: []FunctionType{{}}, 558 TableSection: []Table{{Min: 1}}, 559 FunctionSection: []Index{0}, 560 CodeSection: []Code{codeEnd}, 561 ElementSection: []ElementSegment{ 562 { 563 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{0, 1}, 564 Type: RefTypeFuncref, 565 }, 566 }, 567 }, 568 expectedErr: "element[0].init[1] func index 1 out of range", 569 }, 570 { 571 name: "constant derived element offset - global out of range", 572 input: &Module{ 573 ImportGlobalCount: 50, 574 TypeSection: []FunctionType{{}}, 575 TableSection: []Table{{Min: 1}}, 576 FunctionSection: []Index{0}, 577 CodeSection: []Code{codeEnd}, 578 ElementSection: []ElementSegment{ 579 { 580 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: const1}, Init: []Index{ 581 elementInitImportedGlobalReferenceType | 1, 582 elementInitImportedGlobalReferenceType | 100, 583 }, 584 Type: RefTypeFuncref, 585 }, 586 }, 587 }, 588 expectedErr: "element[0].init[1] global index 100 out of range", 589 }, 590 { 591 name: "imported global derived element offset - missing table", 592 input: &Module{ 593 TypeSection: []FunctionType{{}}, 594 ImportSection: []Import{ 595 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 596 }, 597 FunctionSection: []Index{0}, 598 CodeSection: []Code{codeEnd}, 599 ElementSection: []ElementSegment{ 600 { 601 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0}, 602 Type: RefTypeFuncref, 603 }, 604 }, 605 }, 606 expectedErr: "unknown table 0 as active element target", 607 }, 608 { 609 name: "imported global derived element offset - func index out of range", 610 input: &Module{ 611 TypeSection: []FunctionType{{}}, 612 ImportSection: []Import{ 613 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 614 }, 615 TableSection: []Table{{Min: 1}}, 616 FunctionSection: []Index{0}, 617 CodeSection: []Code{codeEnd}, 618 ElementSection: []ElementSegment{ 619 { 620 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0, 1}, 621 Type: RefTypeFuncref, 622 }, 623 }, 624 }, 625 expectedErr: "element[0].init[1] func index 1 out of range", 626 }, 627 { 628 name: "imported global derived element offset - wrong ValType", 629 input: &Module{ 630 TypeSection: []FunctionType{{}}, 631 ImportSection: []Import{ 632 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI64}}, 633 }, 634 TableSection: []Table{{Type: RefTypeFuncref}}, 635 FunctionSection: []Index{0}, 636 CodeSection: []Code{codeEnd}, 637 ElementSection: []ElementSegment{ 638 { 639 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0}, 640 Type: RefTypeFuncref, 641 }, 642 }, 643 }, 644 expectedErr: "element[0] (global.get 0): import[0].global.ValType != i32", 645 }, 646 { 647 name: "imported global derived element offset - decode error", 648 input: &Module{ 649 TypeSection: []FunctionType{{}}, 650 ImportSection: []Import{ 651 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 652 }, 653 TableSection: []Table{{Type: RefTypeFuncref}}, 654 FunctionSection: []Index{0}, 655 CodeSection: []Code{codeEnd}, 656 ElementSection: []ElementSegment{ 657 { 658 OffsetExpr: ConstantExpression{ 659 Opcode: OpcodeGlobalGet, 660 Data: leb128.EncodeUint64(math.MaxUint64), 661 }, 662 Init: []Index{0}, 663 Type: RefTypeFuncref, 664 }, 665 }, 666 }, 667 expectedErr: "element[0] couldn't read global.get parameter: overflows a 32-bit integer", 668 }, 669 { 670 name: "imported global derived element offset - no imports", 671 input: &Module{ 672 TypeSection: []FunctionType{{}}, 673 TableSection: []Table{{Type: RefTypeFuncref}}, 674 FunctionSection: []Index{0}, 675 GlobalSection: []Global{{Type: GlobalType{ValType: ValueTypeI32}}}, // ignored as not imported 676 CodeSection: []Code{codeEnd}, 677 ElementSection: []ElementSegment{ 678 { 679 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0}, 680 Type: RefTypeFuncref, 681 }, 682 }, 683 }, 684 expectedErr: "element[0] (global.get 0): out of range of imported globals", 685 }, 686 { 687 name: "imported global derived element offset - no imports are globals", 688 input: &Module{ 689 TypeSection: []FunctionType{{}}, 690 ImportSection: []Import{ 691 {Type: ExternTypeFunc, DescFunc: 0}, 692 }, 693 TableSection: []Table{{Type: RefTypeFuncref}}, 694 FunctionSection: []Index{0}, 695 GlobalSection: []Global{{Type: GlobalType{ValType: ValueTypeI32}}}, // ignored as not imported 696 CodeSection: []Code{codeEnd}, 697 ElementSection: []ElementSegment{ 698 { 699 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, Init: []Index{0}, 700 Type: RefTypeFuncref, 701 }, 702 }, 703 }, 704 expectedErr: "element[0] (global.get 0): out of range of imported globals", 705 }, 706 } 707 708 for _, tt := range tests { 709 tc := tt 710 711 t.Run(tc.name, func(t *testing.T) { 712 _, _, _, tables, err := tc.input.AllDeclarations() 713 require.NoError(t, err) 714 err = tc.input.validateTable(api.CoreFeaturesV1, tables, maxTableIndex) 715 require.EqualError(t, err, tc.expectedErr) 716 }) 717 } 718 } 719 720 var ( 721 const0 = leb128.EncodeInt32(0) 722 const1 = leb128.EncodeInt32(1) 723 ) 724 725 func TestModule_buildTables(t *testing.T) { 726 three := uint32(3) 727 tests := []struct { 728 name string 729 module *Module 730 importedTables []*TableInstance 731 importedGlobals []*GlobalInstance 732 expectedTables []*TableInstance 733 }{ 734 { 735 name: "empty", 736 module: &Module{ 737 ElementSection: []ElementSegment{}, 738 }, 739 }, 740 { 741 name: "min zero", 742 module: &Module{ 743 TableSection: []Table{{Type: RefTypeFuncref}}, 744 ElementSection: []ElementSegment{}, 745 }, 746 expectedTables: []*TableInstance{{References: make([]Reference, 0), Min: 0, Type: RefTypeFuncref}}, 747 }, 748 { 749 name: "min/max", 750 module: &Module{ 751 TableSection: []Table{{Min: 1, Max: &three}}, 752 ElementSection: []ElementSegment{}, 753 }, 754 expectedTables: []*TableInstance{{References: make([]Reference, 1), Min: 1, Max: &three}}, 755 }, 756 { // See: https://github.com/WebAssembly/spec/issues/1427 757 name: "constant derived element offset=0 and no index", 758 module: &Module{ 759 TypeSection: []FunctionType{{}}, 760 TableSection: []Table{{Min: 1}}, 761 FunctionSection: []Index{0}, 762 CodeSection: []Code{codeEnd}, 763 ElementSection: []ElementSegment{}, 764 }, 765 expectedTables: []*TableInstance{{References: make([]Reference, 1), Min: 1}}, 766 }, 767 { 768 name: "null extern refs", 769 module: &Module{ 770 TableSection: []Table{{Min: 10, Type: RefTypeExternref}}, 771 ElementSection: []ElementSegment{ 772 {OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{5}}, Init: []Index{ElementInitNullReference, ElementInitNullReference, ElementInitNullReference}}, // three null refs. 773 }, 774 }, 775 expectedTables: []*TableInstance{{References: make([]Reference, 10), Min: 10, Type: RefTypeExternref}}, 776 }, 777 { 778 name: "constant derived element offset=0 and one index", 779 module: &Module{ 780 TypeSection: []FunctionType{{}}, 781 TableSection: []Table{{Min: 1}}, 782 FunctionSection: []Index{0}, 783 CodeSection: []Code{codeEnd}, 784 ElementSection: []ElementSegment{ 785 {OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}}, 786 }, 787 }, 788 expectedTables: []*TableInstance{{References: make([]Reference, 1), Min: 1}}, 789 }, 790 { 791 name: "constant derived element offset - imported table", 792 module: &Module{ 793 TypeSection: []FunctionType{{}}, 794 FunctionSection: []Index{0}, 795 CodeSection: []Code{codeEnd}, 796 ElementSection: []ElementSegment{ 797 {OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}}, 798 }, 799 }, 800 importedTables: []*TableInstance{{Min: 2}}, 801 expectedTables: []*TableInstance{{Min: 2}}, 802 }, 803 { 804 name: "constant derived element offset=0 and one index - imported table", 805 module: &Module{ 806 TypeSection: []FunctionType{{}}, 807 ImportSection: []Import{{Type: ExternTypeTable, DescTable: Table{Min: 1}}}, 808 FunctionSection: []Index{0}, 809 CodeSection: []Code{codeEnd}, 810 ElementSection: []ElementSegment{ 811 {OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}}, 812 }, 813 }, 814 importedTables: []*TableInstance{{Min: 1}}, 815 expectedTables: []*TableInstance{{Min: 1}}, 816 }, 817 { 818 name: "constant derived element offset and two indices", 819 module: &Module{ 820 TypeSection: []FunctionType{{}}, 821 TableSection: []Table{{Min: 3}}, 822 FunctionSection: []Index{0, 0, 0, 0}, 823 CodeSection: []Code{codeEnd, codeEnd, codeEnd, codeEnd}, 824 ElementSection: []ElementSegment{ 825 {OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{1}}, Init: []Index{0, 2}}, 826 }, 827 }, 828 expectedTables: []*TableInstance{{References: make([]Reference, 3), Min: 3}}, 829 }, 830 { // See: https://github.com/WebAssembly/spec/issues/1427 831 name: "imported global derived element offset and no index", 832 module: &Module{ 833 TypeSection: []FunctionType{{}}, 834 ImportSection: []Import{ 835 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 836 }, 837 TableSection: []Table{{Min: 1}}, 838 FunctionSection: []Index{0}, 839 CodeSection: []Code{codeEnd}, 840 ElementSection: []ElementSegment{}, 841 }, 842 importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}}, 843 expectedTables: []*TableInstance{{References: make([]Reference, 1), Min: 1}}, 844 }, 845 { 846 name: "imported global derived element offset and one index", 847 module: &Module{ 848 TypeSection: []FunctionType{{}}, 849 ImportSection: []Import{ 850 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 851 }, 852 TableSection: []Table{{Min: 2}}, 853 FunctionSection: []Index{0}, 854 CodeSection: []Code{codeEnd}, 855 ElementSection: []ElementSegment{ 856 {OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}}, 857 }, 858 }, 859 importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}}, 860 expectedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}}, 861 }, 862 { 863 name: "imported global derived element offset and one index - imported table", 864 module: &Module{ 865 TypeSection: []FunctionType{{}}, 866 ImportSection: []Import{ 867 {Type: ExternTypeTable, DescTable: Table{Min: 1}}, 868 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 869 }, 870 FunctionSection: []Index{0}, 871 CodeSection: []Code{codeEnd}, 872 ElementSection: []ElementSegment{ 873 {OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}}, 874 }, 875 }, 876 importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}}, 877 importedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}}, 878 expectedTables: []*TableInstance{{Min: 2, References: []Reference{0, 0}}}, 879 }, 880 { 881 name: "imported global derived element offset - ignores min on imported table", 882 module: &Module{ 883 TypeSection: []FunctionType{{}}, 884 ImportSection: []Import{ 885 {Type: ExternTypeTable, DescTable: Table{}}, 886 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 887 }, 888 FunctionSection: []Index{0}, 889 CodeSection: []Code{codeEnd}, 890 ElementSection: []ElementSegment{ 891 {OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{0}}, Init: []Index{0}}, 892 }, 893 }, 894 importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 1}}, 895 importedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}}, 896 expectedTables: []*TableInstance{{Min: 2, References: []Reference{0, 0}}}, 897 }, 898 { 899 name: "imported global derived element offset - two indices", 900 module: &Module{ 901 TypeSection: []FunctionType{{}}, 902 ImportSection: []Import{ 903 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI64}}, 904 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 905 }, 906 TableSection: []Table{{Min: 3}, {Min: 100}}, 907 FunctionSection: []Index{0, 0, 0, 0}, 908 CodeSection: []Code{codeEnd, codeEnd, codeEnd, codeEnd}, 909 ElementSection: []ElementSegment{ 910 { 911 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, 912 Init: []Index{ElementInitNullReference, 2}, 913 TableIndex: 1, 914 }, 915 { 916 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x1}}, 917 Init: []Index{0, 2}, 918 TableIndex: 0, 919 }, 920 }, 921 }, 922 importedGlobals: []*GlobalInstance{ 923 {Type: GlobalType{ValType: ValueTypeI64}, Val: 3}, 924 {Type: GlobalType{ValType: ValueTypeI32}, Val: 1}, 925 }, 926 expectedTables: []*TableInstance{ 927 {References: make([]Reference, 3), Min: 3}, 928 {References: make([]Reference, 100), Min: 100}, 929 }, 930 }, 931 { 932 name: "mixed elementSegments - const before imported global", 933 module: &Module{ 934 TypeSection: []FunctionType{{}}, 935 ImportSection: []Import{ 936 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI64}}, 937 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 938 }, 939 TableSection: []Table{{Min: 3}}, 940 FunctionSection: []Index{0, 0, 0, 0}, 941 CodeSection: []Code{codeEnd, codeEnd, codeEnd, codeEnd}, 942 ElementSection: []ElementSegment{ 943 {OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{1}}, Init: []Index{0, 2}}, 944 {OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{1}}, Init: []Index{1, 2}}, 945 }, 946 }, 947 importedGlobals: []*GlobalInstance{ 948 {Type: GlobalType{ValType: ValueTypeI64}, Val: 3}, 949 {Type: GlobalType{ValType: ValueTypeI32}, Val: 1}, 950 }, 951 expectedTables: []*TableInstance{{References: make([]Reference, 3), Min: 3}}, 952 }, 953 } 954 955 for _, tt := range tests { 956 tc := tt 957 958 t.Run(tc.name, func(t *testing.T) { 959 m := &ModuleInstance{ 960 Tables: append(tc.importedTables, make([]*TableInstance, len(tc.module.TableSection))...), 961 Globals: tc.importedGlobals, 962 } 963 err := m.buildTables(tc.module, false) 964 require.NoError(t, err) 965 966 require.Equal(t, tc.expectedTables, m.Tables) 967 }) 968 } 969 } 970 971 // TestModule_buildTable_Errors covers the only late error conditions possible. 972 func TestModule_buildTable_Errors(t *testing.T) { 973 tests := []struct { 974 name string 975 module *Module 976 importedTables []*TableInstance 977 importedGlobals []*GlobalInstance 978 expectedErr string 979 }{ 980 { 981 name: "constant derived element offset exceeds table min - imported table", 982 module: &Module{ 983 TypeSection: []FunctionType{{}}, 984 ImportSection: []Import{{Type: ExternTypeTable, DescTable: Table{}}}, 985 FunctionSection: []Index{0}, 986 CodeSection: []Code{codeEnd}, 987 ElementSection: []ElementSegment{ 988 { 989 OffsetExpr: ConstantExpression{Opcode: OpcodeI32Const, Data: []byte{2}}, 990 Init: []Index{0}, 991 }, 992 }, 993 }, 994 importedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}}, 995 expectedErr: "element[0].init exceeds min table size", 996 }, 997 { 998 name: "imported global derived element offset exceeds table min", 999 module: &Module{ 1000 TypeSection: []FunctionType{{}}, 1001 ImportSection: []Import{ 1002 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 1003 }, 1004 TableSection: []Table{{Min: 2}}, 1005 FunctionSection: []Index{0}, 1006 CodeSection: []Code{codeEnd}, 1007 ElementSection: []ElementSegment{ 1008 { 1009 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, 1010 Init: []Index{0}, 1011 }, 1012 }, 1013 }, 1014 importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 2}}, 1015 expectedErr: "element[0].init exceeds min table size", 1016 }, 1017 { 1018 name: "imported global derived element offset exceeds table min imported table", 1019 module: &Module{ 1020 TypeSection: []FunctionType{{}}, 1021 ImportSection: []Import{ 1022 {Type: ExternTypeTable, DescTable: Table{}}, 1023 {Type: ExternTypeGlobal, DescGlobal: GlobalType{ValType: ValueTypeI32}}, 1024 }, 1025 TableSection: []Table{{Min: 2}}, 1026 FunctionSection: []Index{0}, 1027 CodeSection: []Code{codeEnd}, 1028 ElementSection: []ElementSegment{ 1029 { 1030 OffsetExpr: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0x0}}, 1031 Init: []Index{0}, 1032 }, 1033 }, 1034 }, 1035 importedTables: []*TableInstance{{References: make([]Reference, 2), Min: 2}}, 1036 importedGlobals: []*GlobalInstance{{Type: GlobalType{ValType: ValueTypeI32}, Val: 2}}, 1037 expectedErr: "element[0].init exceeds min table size", 1038 }, 1039 } 1040 1041 for _, tt := range tests { 1042 tc := tt 1043 1044 t.Run(tc.name, func(t *testing.T) { 1045 m := &ModuleInstance{ 1046 Tables: append(tc.importedTables, make([]*TableInstance, len(tc.module.TableSection))...), 1047 Globals: tc.importedGlobals, 1048 } 1049 err := m.buildTables(tc.module, false) 1050 require.EqualError(t, err, tc.expectedErr) 1051 }) 1052 } 1053 } 1054 1055 func TestTableInstance_Grow(t *testing.T) { 1056 expOnErr := uint32(0xffff_ffff) // -1 as signed i32. 1057 max10 := uint32(10) 1058 tests := []struct { 1059 name string 1060 currentLen int 1061 max *uint32 1062 delta, exp uint32 1063 }{ 1064 { 1065 name: "growing ousside 32-bit range", 1066 currentLen: 0x10, 1067 delta: 0xffff_fff0, 1068 exp: expOnErr, 1069 }, 1070 { 1071 name: "growing zero", 1072 currentLen: 0, 1073 delta: 0, 1074 exp: 0, 1075 }, 1076 { 1077 name: "growing zero on non zero table", 1078 currentLen: 5, 1079 delta: 0, 1080 exp: 5, 1081 }, 1082 { 1083 name: "grow zero on max", 1084 currentLen: 10, 1085 delta: 0, 1086 max: &max10, 1087 exp: 10, 1088 }, 1089 { 1090 name: "grow out of range beyond max", 1091 currentLen: 10, 1092 delta: 1, 1093 max: &max10, 1094 exp: expOnErr, 1095 }, 1096 { 1097 name: "grow out of range beyond max part2", 1098 currentLen: 10, 1099 delta: 100, 1100 max: &max10, 1101 exp: expOnErr, 1102 }, 1103 } 1104 1105 for _, tt := range tests { 1106 tc := tt 1107 t.Run(tc.name, func(t *testing.T) { 1108 table := &TableInstance{References: make([]uintptr, tc.currentLen), Max: tc.max} 1109 actual := table.Grow(tc.delta, 0) 1110 require.Equal(t, tc.exp, actual) 1111 }) 1112 } 1113 } 1114 1115 func Test_unwrapElementInitGlobalReference(t *testing.T) { 1116 actual, ok := unwrapElementInitGlobalReference(12345 | elementInitImportedGlobalReferenceType) 1117 require.True(t, ok) 1118 require.Equal(t, actual, uint32(12345)) 1119 1120 actual, ok = unwrapElementInitGlobalReference(12345) 1121 require.False(t, ok) 1122 require.Equal(t, actual, uint32(12345)) 1123 } 1124 1125 // Test_ElementInitSpecials ensures these special consts are larger than MaximumFunctionIndex so that 1126 // they won't collide with the actual index. 1127 func Test_ElementInitSpecials(t *testing.T) { 1128 require.True(t, ElementInitNullReference > MaximumFunctionIndex) 1129 require.True(t, elementInitImportedGlobalReferenceType > MaximumFunctionIndex) 1130 }