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