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