github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/wasm/module_test.go (about) 1 package wasm 2 3 import ( 4 "context" 5 "fmt" 6 "math" 7 "testing" 8 9 "github.com/wasilibs/wazerox/api" 10 "github.com/wasilibs/wazerox/experimental" 11 "github.com/wasilibs/wazerox/internal/leb128" 12 "github.com/wasilibs/wazerox/internal/testing/require" 13 "github.com/wasilibs/wazerox/internal/u64" 14 ) 15 16 func TestFunctionType_String(t *testing.T) { 17 tests := []struct { 18 functype *FunctionType 19 exp string 20 }{ 21 {functype: &FunctionType{}, exp: "v_v"}, 22 {functype: &FunctionType{Params: []ValueType{ValueTypeI32}}, exp: "i32_v"}, 23 {functype: &FunctionType{Params: []ValueType{ValueTypeI32, ValueTypeF64}}, exp: "i32f64_v"}, 24 {functype: &FunctionType{Params: []ValueType{ValueTypeF32, ValueTypeI32, ValueTypeF64}}, exp: "f32i32f64_v"}, 25 {functype: &FunctionType{Results: []ValueType{ValueTypeI64}}, exp: "v_i64"}, 26 {functype: &FunctionType{Results: []ValueType{ValueTypeI64, ValueTypeF32}}, exp: "v_i64f32"}, 27 {functype: &FunctionType{Results: []ValueType{ValueTypeF32, ValueTypeI32, ValueTypeF64}}, exp: "v_f32i32f64"}, 28 {functype: &FunctionType{Params: []ValueType{ValueTypeI32}, Results: []ValueType{ValueTypeI64}}, exp: "i32_i64"}, 29 {functype: &FunctionType{Params: []ValueType{ValueTypeI64, ValueTypeF32}, Results: []ValueType{ValueTypeI64, ValueTypeF32}}, exp: "i64f32_i64f32"}, 30 {functype: &FunctionType{Params: []ValueType{ValueTypeI64, ValueTypeF32, ValueTypeF64}, Results: []ValueType{ValueTypeF32, ValueTypeI32, ValueTypeF64}}, exp: "i64f32f64_f32i32f64"}, 31 } 32 33 for _, tt := range tests { 34 tc := tt 35 t.Run(tc.functype.String(), func(t *testing.T) { 36 require.Equal(t, tc.exp, tc.functype.String()) 37 require.Equal(t, tc.exp, tc.functype.key()) 38 require.Equal(t, tc.exp, tc.functype.string) 39 }) 40 } 41 } 42 43 func TestSectionIDName(t *testing.T) { 44 tests := []struct { 45 name string 46 input SectionID 47 expected string 48 }{ 49 {"custom", SectionIDCustom, "custom"}, 50 {"type", SectionIDType, "type"}, 51 {"import", SectionIDImport, "import"}, 52 {"function", SectionIDFunction, "function"}, 53 {"table", SectionIDTable, "table"}, 54 {"memory", SectionIDMemory, "memory"}, 55 {"global", SectionIDGlobal, "global"}, 56 {"export", SectionIDExport, "export"}, 57 {"start", SectionIDStart, "start"}, 58 {"element", SectionIDElement, "element"}, 59 {"code", SectionIDCode, "code"}, 60 {"data", SectionIDData, "data"}, 61 {"unknown", 100, "unknown"}, 62 } 63 64 for _, tt := range tests { 65 tc := tt 66 67 t.Run(tc.name, func(t *testing.T) { 68 require.Equal(t, tc.expected, SectionIDName(tc.input)) 69 }) 70 } 71 } 72 73 func TestMemory_Validate(t *testing.T) { 74 tests := []struct { 75 name string 76 mem *Memory 77 expectedErr string 78 }{ 79 { 80 name: "ok", 81 mem: &Memory{Min: 2, Cap: 2, Max: 2}, 82 }, 83 { 84 name: "cap < min", 85 mem: &Memory{Min: 2, Cap: 1, Max: 2}, 86 expectedErr: "capacity 1 pages (64 Ki) less than minimum 2 pages (128 Ki)", 87 }, 88 { 89 name: "cap > maxLimit", 90 mem: &Memory{Min: 2, Cap: math.MaxUint32, Max: 2}, 91 expectedErr: "capacity 4294967295 pages (3 Ti) over limit of 65536 pages (4 Gi)", 92 }, 93 { 94 name: "max < min", 95 mem: &Memory{Min: 2, Cap: 2, Max: 0, IsMaxEncoded: true}, 96 expectedErr: "min 2 pages (128 Ki) > max 0 pages (0 Ki)", 97 }, 98 { 99 name: "min > limit", 100 mem: &Memory{Min: math.MaxUint32}, 101 expectedErr: "min 4294967295 pages (3 Ti) over limit of 65536 pages (4 Gi)", 102 }, 103 { 104 name: "max > limit", 105 mem: &Memory{Max: math.MaxUint32, IsMaxEncoded: true}, 106 expectedErr: "max 4294967295 pages (3 Ti) over limit of 65536 pages (4 Gi)", 107 }, 108 } 109 110 for _, tt := range tests { 111 tc := tt 112 113 t.Run(tc.name, func(t *testing.T) { 114 err := tc.mem.Validate(MemoryLimitPages) 115 if tc.expectedErr == "" { 116 require.NoError(t, err) 117 } else { 118 require.EqualError(t, err, tc.expectedErr) 119 } 120 }) 121 } 122 } 123 124 func TestModule_allDeclarations(t *testing.T) { 125 tests := []struct { 126 module *Module 127 expectedFunctions []Index 128 expectedGlobals []GlobalType 129 expectedMemory *Memory 130 expectedTables []Table 131 }{ 132 // Functions. 133 { 134 module: &Module{ 135 ImportSection: []Import{{Type: ExternTypeFunc, DescFunc: 10000}}, 136 FunctionSection: []Index{10, 20, 30}, 137 }, 138 expectedFunctions: []Index{10000, 10, 20, 30}, 139 }, 140 { 141 module: &Module{ 142 FunctionSection: []Index{10, 20, 30}, 143 }, 144 expectedFunctions: []Index{10, 20, 30}, 145 }, 146 { 147 module: &Module{ 148 ImportSection: []Import{{Type: ExternTypeFunc, DescFunc: 10000}}, 149 }, 150 expectedFunctions: []Index{10000}, 151 }, 152 // Globals. 153 { 154 module: &Module{ 155 ImportSection: []Import{{Type: ExternTypeGlobal, DescGlobal: GlobalType{Mutable: false}}}, 156 GlobalSection: []Global{{Type: GlobalType{Mutable: true}}}, 157 }, 158 expectedGlobals: []GlobalType{{Mutable: false}, {Mutable: true}}, 159 }, 160 { 161 module: &Module{ 162 GlobalSection: []Global{{Type: GlobalType{Mutable: true}}}, 163 }, 164 expectedGlobals: []GlobalType{{Mutable: true}}, 165 }, 166 { 167 module: &Module{ 168 ImportSection: []Import{{Type: ExternTypeGlobal, DescGlobal: GlobalType{Mutable: false}}}, 169 }, 170 expectedGlobals: []GlobalType{{Mutable: false}}, 171 }, 172 // Memories. 173 { 174 module: &Module{ 175 ImportSection: []Import{{Type: ExternTypeMemory, DescMem: &Memory{Min: 1, Max: 10}}}, 176 }, 177 expectedMemory: &Memory{Min: 1, Max: 10}, 178 }, 179 { 180 module: &Module{ 181 MemorySection: &Memory{Min: 100}, 182 }, 183 expectedMemory: &Memory{Min: 100}, 184 }, 185 // Tables. 186 { 187 module: &Module{ 188 ImportSection: []Import{{Type: ExternTypeTable, DescTable: Table{Min: 1}}}, 189 }, 190 expectedTables: []Table{{Min: 1}}, 191 }, 192 { 193 module: &Module{ 194 TableSection: []Table{{Min: 10}}, 195 }, 196 expectedTables: []Table{{Min: 10}}, 197 }, 198 } 199 200 for i, tt := range tests { 201 tc := tt 202 t.Run(fmt.Sprintf("%d", i), func(t *testing.T) { 203 functions, globals, memory, tables, err := tc.module.AllDeclarations() 204 require.NoError(t, err) 205 require.Equal(t, tc.expectedFunctions, functions) 206 require.Equal(t, tc.expectedGlobals, globals) 207 require.Equal(t, tc.expectedTables, tables) 208 require.Equal(t, tc.expectedMemory, memory) 209 }) 210 } 211 } 212 213 func TestValidateConstExpression(t *testing.T) { 214 t.Run("invalid opcode", func(t *testing.T) { 215 expr := ConstantExpression{Opcode: OpcodeNop} 216 err := validateConstExpression(nil, 0, &expr, valueTypeUnknown) 217 require.Error(t, err) 218 }) 219 for _, vt := range []ValueType{ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64} { 220 t.Run(ValueTypeName(vt), func(t *testing.T) { 221 t.Run("valid", func(t *testing.T) { 222 expr := ConstantExpression{} 223 switch vt { 224 case ValueTypeI32: 225 expr.Data = []byte{1} 226 expr.Opcode = OpcodeI32Const 227 case ValueTypeI64: 228 expr.Data = []byte{2} 229 expr.Opcode = OpcodeI64Const 230 case ValueTypeF32: 231 expr.Data = u64.LeBytes(api.EncodeF32(math.MaxFloat32)) 232 expr.Opcode = OpcodeF32Const 233 case ValueTypeF64: 234 expr.Data = u64.LeBytes(api.EncodeF64(math.MaxFloat64)) 235 expr.Opcode = OpcodeF64Const 236 } 237 238 err := validateConstExpression(nil, 0, &expr, vt) 239 require.NoError(t, err) 240 }) 241 t.Run("invalid", func(t *testing.T) { 242 // Empty data must be failure. 243 expr := ConstantExpression{Data: make([]byte, 0)} 244 switch vt { 245 case ValueTypeI32: 246 expr.Opcode = OpcodeI32Const 247 case ValueTypeI64: 248 expr.Opcode = OpcodeI64Const 249 case ValueTypeF32: 250 expr.Opcode = OpcodeF32Const 251 case ValueTypeF64: 252 expr.Opcode = OpcodeF64Const 253 } 254 err := validateConstExpression(nil, 0, &expr, vt) 255 require.Error(t, err) 256 }) 257 }) 258 } 259 t.Run("ref types", func(t *testing.T) { 260 t.Run("ref.func", func(t *testing.T) { 261 expr := &ConstantExpression{Data: []byte{5}, Opcode: OpcodeRefFunc} 262 err := validateConstExpression(nil, 10, expr, ValueTypeFuncref) 263 require.NoError(t, err) 264 err = validateConstExpression(nil, 2, expr, ValueTypeFuncref) 265 require.EqualError(t, err, "ref.func index out of range [5] with length 1") 266 }) 267 t.Run("ref.null", func(t *testing.T) { 268 err := validateConstExpression(nil, 0, 269 &ConstantExpression{Data: []byte{ValueTypeFuncref}, Opcode: OpcodeRefNull}, 270 ValueTypeFuncref) 271 require.NoError(t, err) 272 err = validateConstExpression(nil, 0, 273 &ConstantExpression{Data: []byte{ValueTypeExternref}, Opcode: OpcodeRefNull}, 274 ValueTypeExternref) 275 require.NoError(t, err) 276 err = validateConstExpression(nil, 0, 277 &ConstantExpression{Data: []byte{0xff}, Opcode: OpcodeRefNull}, 278 ValueTypeExternref) 279 require.EqualError(t, err, "invalid type for ref.null: 0xff") 280 }) 281 }) 282 t.Run("global expr", func(t *testing.T) { 283 t.Run("failed to read global index", func(t *testing.T) { 284 // Empty data for global index is invalid. 285 expr := &ConstantExpression{Data: make([]byte, 0), Opcode: OpcodeGlobalGet} 286 err := validateConstExpression(nil, 0, expr, valueTypeUnknown) 287 require.Error(t, err) 288 }) 289 t.Run("global index out of range", func(t *testing.T) { 290 // Data holds the index in leb128 and this time the value exceeds len(globals) (=0). 291 expr := &ConstantExpression{Data: []byte{1}, Opcode: OpcodeGlobalGet} 292 var globals []GlobalType 293 err := validateConstExpression(globals, 0, expr, valueTypeUnknown) 294 require.Error(t, err) 295 }) 296 297 t.Run("type mismatch", func(t *testing.T) { 298 for _, vt := range []ValueType{ 299 ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64, 300 } { 301 t.Run(ValueTypeName(vt), func(t *testing.T) { 302 // The index specified in Data equals zero. 303 expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet} 304 globals := []GlobalType{{ValType: valueTypeUnknown}} 305 306 err := validateConstExpression(globals, 0, expr, vt) 307 require.Error(t, err) 308 }) 309 } 310 }) 311 t.Run("ok", func(t *testing.T) { 312 for _, vt := range []ValueType{ 313 ValueTypeI32, ValueTypeI64, ValueTypeF32, ValueTypeF64, 314 } { 315 t.Run(ValueTypeName(vt), func(t *testing.T) { 316 // The index specified in Data equals zero. 317 expr := &ConstantExpression{Data: []byte{0}, Opcode: OpcodeGlobalGet} 318 globals := []GlobalType{{ValType: vt}} 319 320 err := validateConstExpression(globals, 0, expr, vt) 321 require.NoError(t, err) 322 }) 323 } 324 }) 325 }) 326 } 327 328 func TestModule_Validate_Errors(t *testing.T) { 329 zero := Index(0) 330 tests := []struct { 331 name string 332 input *Module 333 expectedErr string 334 }{ 335 { 336 name: "StartSection points to an invalid func", 337 input: &Module{ 338 TypeSection: nil, 339 FunctionSection: []uint32{0}, 340 CodeSection: []Code{{Body: []byte{OpcodeEnd}}}, 341 StartSection: &zero, 342 }, 343 expectedErr: "invalid start function: func[0] has an invalid type", 344 }, 345 } 346 347 for _, tt := range tests { 348 tc := tt 349 350 t.Run(tc.name, func(t *testing.T) { 351 err := tc.input.Validate(api.CoreFeaturesV1) 352 require.EqualError(t, err, tc.expectedErr) 353 }) 354 } 355 } 356 357 func TestModule_validateStartSection(t *testing.T) { 358 t.Run("no start section", func(t *testing.T) { 359 m := Module{} 360 err := m.validateStartSection() 361 require.NoError(t, err) 362 }) 363 364 t.Run("invalid type", func(t *testing.T) { 365 for _, ft := range []FunctionType{ 366 {Params: []ValueType{ValueTypeI32}}, 367 {Results: []ValueType{ValueTypeI32}}, 368 {Params: []ValueType{ValueTypeI32}, Results: []ValueType{ValueTypeI32}}, 369 } { 370 t.Run(ft.String(), func(t *testing.T) { 371 index := uint32(0) 372 m := Module{StartSection: &index, FunctionSection: []uint32{0}, TypeSection: []FunctionType{ft}} 373 err := m.validateStartSection() 374 require.Error(t, err) 375 }) 376 } 377 }) 378 t.Run("imported valid func", func(t *testing.T) { 379 index := Index(1) 380 m := Module{ 381 StartSection: &index, 382 TypeSection: []FunctionType{{}, {Results: []ValueType{ValueTypeI32}}}, 383 ImportFunctionCount: 2, 384 ImportSection: []Import{ 385 {Type: ExternTypeFunc, DescFunc: 1}, 386 // import with index 1 is global but this should be skipped when searching imported functions. 387 {Type: ExternTypeGlobal}, 388 {Type: ExternTypeFunc, DescFunc: 0}, // This one must be selected. 389 }, 390 } 391 err := m.validateStartSection() 392 require.NoError(t, err) 393 }) 394 } 395 396 func TestModule_validateGlobals(t *testing.T) { 397 t.Run("too many globals", func(t *testing.T) { 398 m := Module{} 399 err := m.validateGlobals(make([]GlobalType, 10), 0, 9) 400 require.Error(t, err) 401 require.EqualError(t, err, "too many globals in a module") 402 }) 403 t.Run("global index out of range", func(t *testing.T) { 404 m := Module{GlobalSection: []Global{ 405 { 406 Type: GlobalType{ValType: ValueTypeI32}, 407 // Trying to reference globals[1] which is not imported. 408 Init: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{1}}, 409 }, 410 }} 411 err := m.validateGlobals(nil, 0, 9) 412 require.Error(t, err) 413 require.EqualError(t, err, "global index out of range") 414 }) 415 t.Run("invalid const expression", func(t *testing.T) { 416 m := Module{GlobalSection: []Global{ 417 { 418 Type: GlobalType{ValType: valueTypeUnknown}, 419 Init: ConstantExpression{Opcode: OpcodeUnreachable}, 420 }, 421 }} 422 err := m.validateGlobals(nil, 0, 9) 423 require.Error(t, err) 424 require.EqualError(t, err, "invalid opcode for const expression: 0x0") 425 }) 426 t.Run("ok", func(t *testing.T) { 427 m := Module{GlobalSection: []Global{ 428 { 429 Type: GlobalType{ValType: ValueTypeI32}, 430 Init: ConstantExpression{Opcode: OpcodeI32Const, Data: const0}, 431 }, 432 }} 433 err := m.validateGlobals(nil, 0, 9) 434 require.NoError(t, err) 435 }) 436 t.Run("ok with imported global", func(t *testing.T) { 437 m := Module{ 438 ImportGlobalCount: 1, 439 GlobalSection: []Global{ 440 { 441 Type: GlobalType{ValType: ValueTypeI32}, 442 // Trying to reference globals[1] which is imported. 443 Init: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0}}, 444 }, 445 }, 446 ImportSection: []Import{{Type: ExternTypeGlobal}}, 447 } 448 globalDeclarations := []GlobalType{ 449 {ValType: ValueTypeI32}, // Imported one. 450 {}, // the local one trying to validate. 451 } 452 err := m.validateGlobals(globalDeclarations, 0, 9) 453 require.NoError(t, err) 454 }) 455 } 456 457 func TestModule_validateFunctions(t *testing.T) { 458 t.Run("ok", func(t *testing.T) { 459 m := Module{ 460 TypeSection: []FunctionType{v_v}, 461 FunctionSection: []uint32{0}, 462 CodeSection: []Code{{Body: []byte{OpcodeI32Const, 0, OpcodeDrop, OpcodeEnd}}}, 463 } 464 err := m.validateFunctions(api.CoreFeaturesV1, nil, nil, nil, nil, MaximumFunctionIndex) 465 require.NoError(t, err) 466 }) 467 t.Run("too many functions", func(t *testing.T) { 468 m := Module{} 469 err := m.validateFunctions(api.CoreFeaturesV1, []uint32{1, 2, 3, 4}, nil, nil, nil, 3) 470 require.Error(t, err) 471 require.EqualError(t, err, "too many functions (4) in a module") 472 }) 473 t.Run("function, but no code", func(t *testing.T) { 474 m := Module{ 475 TypeSection: []FunctionType{v_v}, 476 FunctionSection: []Index{0}, 477 CodeSection: nil, 478 } 479 err := m.validateFunctions(api.CoreFeaturesV1, nil, nil, nil, nil, MaximumFunctionIndex) 480 require.Error(t, err) 481 require.EqualError(t, err, "code count (0) != function count (1)") 482 }) 483 t.Run("function out of range of code", func(t *testing.T) { 484 m := Module{ 485 TypeSection: []FunctionType{v_v}, 486 FunctionSection: []Index{1}, 487 CodeSection: []Code{{Body: []byte{OpcodeEnd}}}, 488 } 489 err := m.validateFunctions(api.CoreFeaturesV1, nil, nil, nil, nil, MaximumFunctionIndex) 490 require.Error(t, err) 491 require.EqualError(t, err, "invalid function[0]: type section index 1 out of range") 492 }) 493 t.Run("invalid", func(t *testing.T) { 494 m := Module{ 495 TypeSection: []FunctionType{v_v}, 496 FunctionSection: []Index{0}, 497 CodeSection: []Code{{Body: []byte{OpcodeF32Abs}}}, 498 } 499 err := m.validateFunctions(api.CoreFeaturesV1, nil, nil, nil, nil, MaximumFunctionIndex) 500 require.Error(t, err) 501 require.Contains(t, err.Error(), "invalid function[0]: cannot pop the 1st f32 operand") 502 }) 503 t.Run("in- exported", func(t *testing.T) { 504 m := Module{ 505 TypeSection: []FunctionType{v_v}, 506 FunctionSection: []Index{0}, 507 CodeSection: []Code{{Body: []byte{OpcodeF32Abs}}}, 508 ExportSection: []Export{{Name: "f1", Type: ExternTypeFunc, Index: 0}}, 509 } 510 err := m.validateFunctions(api.CoreFeaturesV1, nil, nil, nil, nil, MaximumFunctionIndex) 511 require.Error(t, err) 512 require.Contains(t, err.Error(), `invalid function[0] export["f1"]: cannot pop the 1st f32`) 513 }) 514 t.Run("in- exported after import", func(t *testing.T) { 515 m := Module{ 516 ImportFunctionCount: 1, 517 TypeSection: []FunctionType{v_v}, 518 ImportSection: []Import{{Type: ExternTypeFunc}}, 519 FunctionSection: []Index{0}, 520 CodeSection: []Code{{Body: []byte{OpcodeF32Abs}}}, 521 ExportSection: []Export{{Name: "f1", Type: ExternTypeFunc, Index: 1}}, 522 } 523 err := m.validateFunctions(api.CoreFeaturesV1, nil, nil, nil, nil, MaximumFunctionIndex) 524 require.Error(t, err) 525 require.Contains(t, err.Error(), `invalid function[0] export["f1"]: cannot pop the 1st f32`) 526 }) 527 t.Run("in- exported twice", func(t *testing.T) { 528 m := Module{ 529 TypeSection: []FunctionType{v_v}, 530 FunctionSection: []Index{0}, 531 CodeSection: []Code{{Body: []byte{OpcodeF32Abs}}}, 532 ExportSection: []Export{ 533 {Name: "f1", Type: ExternTypeFunc, Index: 0}, 534 {Name: "f2", Type: ExternTypeFunc, Index: 0}, 535 }, 536 } 537 err := m.validateFunctions(api.CoreFeaturesV1, nil, nil, nil, nil, MaximumFunctionIndex) 538 require.Error(t, err) 539 require.Contains(t, err.Error(), `invalid function[0] export["f1","f2"]: cannot pop the 1st f32`) 540 }) 541 } 542 543 func TestModule_validateMemory(t *testing.T) { 544 t.Run("active data segment exits but memory not declared", func(t *testing.T) { 545 m := Module{DataSection: []DataSegment{{OffsetExpression: ConstantExpression{}}}} 546 err := m.validateMemory(nil, nil, api.CoreFeaturesV1) 547 require.Error(t, err) 548 require.Contains(t, "unknown memory", err.Error()) 549 }) 550 t.Run("invalid const expr", func(t *testing.T) { 551 m := Module{DataSection: []DataSegment{{ 552 OffsetExpression: ConstantExpression{ 553 Opcode: OpcodeUnreachable, // Invalid! 554 }, 555 }}} 556 err := m.validateMemory(&Memory{}, nil, api.CoreFeaturesV1) 557 require.EqualError(t, err, "calculate offset: invalid opcode for const expression: 0x0") 558 }) 559 t.Run("ok", func(t *testing.T) { 560 m := Module{DataSection: []DataSegment{{ 561 Init: []byte{0x1}, 562 OffsetExpression: ConstantExpression{ 563 Opcode: OpcodeI32Const, 564 Data: leb128.EncodeInt32(1), 565 }, 566 }}} 567 err := m.validateMemory(&Memory{}, nil, api.CoreFeaturesV1) 568 require.NoError(t, err) 569 }) 570 } 571 572 func TestModule_validateImports(t *testing.T) { 573 tests := []struct { 574 name string 575 enabledFeatures api.CoreFeatures 576 i *Import 577 expectedErr string 578 }{ 579 {name: "empty import section"}, 580 { 581 name: "reject empty named module", 582 enabledFeatures: api.CoreFeaturesV1, 583 i: &Import{Module: "", Name: "n", Type: ExternTypeFunc, DescFunc: 0}, 584 expectedErr: "import[0] has an empty module name", 585 }, 586 { 587 name: "func", 588 enabledFeatures: api.CoreFeaturesV1, 589 i: &Import{Module: "m", Name: "n", Type: ExternTypeFunc, DescFunc: 0}, 590 }, 591 { 592 name: "func type index out of range ", 593 enabledFeatures: api.CoreFeaturesV1, 594 i: &Import{Module: "m", Name: "n", Type: ExternTypeFunc, DescFunc: 100}, 595 expectedErr: "invalid import[\"m\".\"n\"] function: type index out of range", 596 }, 597 { 598 name: "global var disabled", 599 enabledFeatures: api.CoreFeaturesV1.SetEnabled(api.CoreFeatureMutableGlobal, false), 600 i: &Import{ 601 Module: "m", 602 Name: "n", 603 Type: ExternTypeGlobal, 604 DescGlobal: GlobalType{ValType: ValueTypeI32, Mutable: true}, 605 }, 606 expectedErr: `invalid import["m"."n"] global: feature "mutable-global" is disabled`, 607 }, 608 { 609 name: "table", 610 enabledFeatures: api.CoreFeaturesV1, 611 i: &Import{ 612 Module: "m", 613 Name: "n", 614 Type: ExternTypeTable, 615 DescTable: Table{Min: 1}, 616 }, 617 }, 618 { 619 name: "memory", 620 enabledFeatures: api.CoreFeaturesV1, 621 i: &Import{ 622 Module: "m", 623 Name: "n", 624 Type: ExternTypeMemory, 625 DescMem: &Memory{Min: 1}, 626 }, 627 }, 628 } 629 630 for _, tt := range tests { 631 tc := tt 632 t.Run(tc.name, func(t *testing.T) { 633 m := Module{TypeSection: []FunctionType{{}}} 634 if tc.i != nil { 635 m.ImportSection = []Import{*tc.i} 636 } 637 err := m.validateImports(tc.enabledFeatures) 638 if tc.expectedErr != "" { 639 require.EqualError(t, err, tc.expectedErr) 640 } else { 641 require.NoError(t, err) 642 } 643 }) 644 } 645 } 646 647 func TestModule_validateExports(t *testing.T) { 648 tests := []struct { 649 name string 650 enabledFeatures api.CoreFeatures 651 exportSection []Export 652 functions []Index 653 globals []GlobalType 654 memory *Memory 655 tables []Table 656 expectedErr string 657 }{ 658 {name: "empty export section", exportSection: []Export{}}, 659 { 660 name: "func", 661 enabledFeatures: api.CoreFeaturesV1, 662 exportSection: []Export{{Type: ExternTypeFunc, Index: 0}}, 663 functions: []Index{100 /* arbitrary type id*/}, 664 }, 665 { 666 name: "func out of range", 667 enabledFeatures: api.CoreFeaturesV1, 668 exportSection: []Export{{Type: ExternTypeFunc, Index: 1, Name: "e"}}, 669 functions: []Index{100 /* arbitrary type id*/}, 670 expectedErr: `unknown function for export["e"]`, 671 }, 672 { 673 name: "global const", 674 enabledFeatures: api.CoreFeaturesV1, 675 exportSection: []Export{{Type: ExternTypeGlobal, Index: 0}}, 676 globals: []GlobalType{{ValType: ValueTypeI32}}, 677 }, 678 { 679 name: "global var", 680 enabledFeatures: api.CoreFeaturesV1, 681 exportSection: []Export{{Type: ExternTypeGlobal, Index: 0}}, 682 globals: []GlobalType{{ValType: ValueTypeI32, Mutable: true}}, 683 }, 684 { 685 name: "global var disabled", 686 enabledFeatures: api.CoreFeaturesV1.SetEnabled(api.CoreFeatureMutableGlobal, false), 687 exportSection: []Export{{Type: ExternTypeGlobal, Index: 0, Name: "e"}}, 688 globals: []GlobalType{{ValType: ValueTypeI32, Mutable: true}}, 689 expectedErr: `invalid export["e"] global[0]: feature "mutable-global" is disabled`, 690 }, 691 { 692 name: "global out of range", 693 enabledFeatures: api.CoreFeaturesV1, 694 exportSection: []Export{{Type: ExternTypeGlobal, Index: 1, Name: "e"}}, 695 globals: []GlobalType{{}}, 696 expectedErr: `unknown global for export["e"]`, 697 }, 698 { 699 name: "table", 700 enabledFeatures: api.CoreFeaturesV1, 701 exportSection: []Export{{Type: ExternTypeTable, Index: 0}}, 702 tables: []Table{{}}, 703 }, 704 { 705 name: "multiple tables", 706 enabledFeatures: api.CoreFeaturesV1, 707 exportSection: []Export{{Type: ExternTypeTable, Index: 0}, {Type: ExternTypeTable, Index: 1}, {Type: ExternTypeTable, Index: 2}}, 708 tables: []Table{{}, {}, {}}, 709 }, 710 { 711 name: "table out of range", 712 enabledFeatures: api.CoreFeaturesV1, 713 exportSection: []Export{{Type: ExternTypeTable, Index: 1, Name: "e"}}, 714 tables: []Table{}, 715 expectedErr: `table for export["e"] out of range`, 716 }, 717 { 718 name: "memory", 719 enabledFeatures: api.CoreFeaturesV1, 720 exportSection: []Export{{Type: ExternTypeMemory, Index: 0}}, 721 memory: &Memory{}, 722 }, 723 { 724 name: "memory out of range", 725 enabledFeatures: api.CoreFeaturesV1, 726 exportSection: []Export{{Type: ExternTypeMemory, Index: 0, Name: "e"}}, 727 tables: []Table{}, 728 expectedErr: `memory for export["e"] out of range`, 729 }, 730 } 731 732 for _, tt := range tests { 733 tc := tt 734 t.Run(tc.name, func(t *testing.T) { 735 m := Module{ExportSection: tc.exportSection} 736 err := m.validateExports(tc.enabledFeatures, tc.functions, tc.globals, tc.memory, tc.tables) 737 if tc.expectedErr != "" { 738 require.EqualError(t, err, tc.expectedErr) 739 } else { 740 require.NoError(t, err) 741 } 742 }) 743 } 744 } 745 746 func TestModule_buildGlobals(t *testing.T) { 747 const localFuncRefInstructionIndex = uint32(0xffff) 748 749 minusOne := int32(-1) 750 m := &Module{ 751 ImportGlobalCount: 2, 752 GlobalSection: []Global{ 753 { 754 Type: GlobalType{Mutable: true, ValType: ValueTypeF64}, 755 Init: ConstantExpression{ 756 Opcode: OpcodeF64Const, 757 Data: u64.LeBytes(api.EncodeF64(math.MaxFloat64)), 758 }, 759 }, 760 { 761 Type: GlobalType{Mutable: false, ValType: ValueTypeI32}, 762 Init: ConstantExpression{ 763 Opcode: OpcodeI32Const, 764 Data: leb128.EncodeInt32(math.MaxInt32), 765 }, 766 }, 767 { 768 Type: GlobalType{Mutable: false, ValType: ValueTypeI32}, 769 Init: ConstantExpression{ 770 Opcode: OpcodeI32Const, 771 Data: leb128.EncodeInt32(minusOne), 772 }, 773 }, 774 { 775 Type: GlobalType{Mutable: false, ValType: ValueTypeV128}, 776 Init: ConstantExpression{ 777 Opcode: OpcodeVecV128Const, 778 Data: []byte{ 779 1, 0, 0, 0, 0, 0, 0, 0, 780 2, 0, 0, 0, 0, 0, 0, 0, 781 }, 782 }, 783 }, 784 { 785 Type: GlobalType{Mutable: false, ValType: ValueTypeExternref}, 786 Init: ConstantExpression{Opcode: OpcodeRefNull, Data: []byte{ValueTypeExternref}}, 787 }, 788 { 789 Type: GlobalType{Mutable: false, ValType: ValueTypeFuncref}, 790 Init: ConstantExpression{Opcode: OpcodeRefNull, Data: []byte{ValueTypeFuncref}}, 791 }, 792 { 793 Type: GlobalType{Mutable: false, ValType: ValueTypeFuncref}, 794 Init: ConstantExpression{Opcode: OpcodeRefFunc, Data: leb128.EncodeUint32(localFuncRefInstructionIndex)}, 795 }, 796 { 797 Type: GlobalType{Mutable: false, ValType: ValueTypeExternref}, 798 Init: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{0}}, 799 }, 800 { 801 Type: GlobalType{Mutable: false, ValType: ValueTypeFuncref}, 802 Init: ConstantExpression{Opcode: OpcodeGlobalGet, Data: []byte{1}}, 803 }, 804 }, 805 } 806 807 imported := []*GlobalInstance{ 808 {Type: GlobalType{ValType: ValueTypeExternref}, Val: 0x54321}, 809 {Type: GlobalType{ValType: ValueTypeFuncref}, Val: 0x12345}, 810 } 811 812 mi := &ModuleInstance{ 813 Globals: make([]*GlobalInstance, m.ImportGlobalCount+uint32(len(m.GlobalSection))), 814 } 815 816 mi.Globals[0], mi.Globals[1] = imported[0], imported[1] 817 818 mi.buildGlobals(m, func(funcIndex Index) Reference { 819 require.Equal(t, localFuncRefInstructionIndex, funcIndex) 820 return 0x99999 821 }) 822 expectedGlobals := []*GlobalInstance{ 823 imported[0], imported[1], 824 {Type: GlobalType{ValType: ValueTypeF64, Mutable: true}, Val: api.EncodeF64(math.MaxFloat64)}, 825 {Type: GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: uint64(int32(math.MaxInt32))}, 826 // Higher bits are must be zeroed for i32 globals, not signed-extended. See #656. 827 {Type: GlobalType{ValType: ValueTypeI32, Mutable: false}, Val: uint64(uint32(minusOne))}, 828 {Type: GlobalType{ValType: ValueTypeV128, Mutable: false}, Val: 0x1, ValHi: 0x2}, 829 {Type: GlobalType{ValType: ValueTypeExternref, Mutable: false}, Val: 0}, 830 {Type: GlobalType{ValType: ValueTypeFuncref, Mutable: false}, Val: 0}, 831 {Type: GlobalType{ValType: ValueTypeFuncref, Mutable: false}, Val: 0x99999}, 832 {Type: GlobalType{ValType: ValueTypeExternref, Mutable: false}, Val: 0x54321}, 833 {Type: GlobalType{ValType: ValueTypeFuncref, Mutable: false}, Val: 0x12345}, 834 } 835 require.Equal(t, expectedGlobals, mi.Globals) 836 } 837 838 func TestModule_buildMemoryInstance(t *testing.T) { 839 t.Run("nil", func(t *testing.T) { 840 m := ModuleInstance{} 841 m.buildMemory(&Module{}) 842 require.Nil(t, m.MemoryInstance) 843 }) 844 t.Run("non-nil", func(t *testing.T) { 845 min := uint32(1) 846 max := uint32(10) 847 mDef := MemoryDefinition{moduleName: "foo"} 848 m := ModuleInstance{} 849 m.buildMemory(&Module{ 850 MemorySection: &Memory{Min: min, Cap: min, Max: max}, 851 MemoryDefinitionSection: []MemoryDefinition{mDef}, 852 }) 853 mem := m.MemoryInstance 854 require.Equal(t, min, mem.Min) 855 require.Equal(t, max, mem.Max) 856 require.Equal(t, &mDef, mem.definition) 857 }) 858 } 859 860 func TestModule_validateDataCountSection(t *testing.T) { 861 t.Run("ok", func(t *testing.T) { 862 for _, m := range []*Module{ 863 { 864 DataSection: []DataSegment{}, 865 DataCountSection: nil, 866 }, 867 { 868 DataSection: []DataSegment{{}, {}}, 869 DataCountSection: nil, 870 }, 871 } { 872 err := m.validateDataCountSection() 873 require.NoError(t, err) 874 } 875 }) 876 t.Run("error", func(t *testing.T) { 877 count := uint32(1) 878 for _, m := range []*Module{ 879 { 880 DataSection: []DataSegment{}, 881 DataCountSection: &count, 882 }, 883 { 884 DataSection: []DataSegment{{}, {}}, 885 DataCountSection: &count, 886 }, 887 } { 888 err := m.validateDataCountSection() 889 require.Error(t, err) 890 } 891 }) 892 } 893 894 func TestModule_declaredFunctionIndexes(t *testing.T) { 895 tests := []struct { 896 name string 897 mod *Module 898 exp map[Index]struct{} 899 expErr string 900 }{ 901 { 902 name: "empty", 903 mod: &Module{}, 904 exp: map[uint32]struct{}{}, 905 }, 906 { 907 name: "global", 908 mod: &Module{ 909 ExportSection: []Export{ 910 {Index: 10, Type: ExternTypeFunc}, 911 {Index: 1000, Type: ExternTypeGlobal}, 912 }, 913 }, 914 exp: map[uint32]struct{}{10: {}}, 915 }, 916 { 917 name: "export", 918 mod: &Module{ 919 ExportSection: []Export{ 920 {Index: 1000, Type: ExternTypeGlobal}, 921 {Index: 10, Type: ExternTypeFunc}, 922 }, 923 }, 924 exp: map[uint32]struct{}{10: {}}, 925 }, 926 { 927 name: "element", 928 mod: &Module{ 929 ElementSection: []ElementSegment{ 930 { 931 Mode: ElementModeActive, 932 Init: []Index{0, ElementInitNullReference, 5}, 933 }, 934 { 935 Mode: ElementModeDeclarative, 936 Init: []Index{1, ElementInitNullReference, 5}, 937 }, 938 { 939 Mode: ElementModePassive, 940 Init: []Index{5, 2, ElementInitNullReference, ElementInitNullReference}, 941 }, 942 }, 943 }, 944 exp: map[uint32]struct{}{0: {}, 1: {}, 2: {}, 5: {}}, 945 }, 946 { 947 name: "all", 948 mod: &Module{ 949 ExportSection: []Export{ 950 {Index: 10, Type: ExternTypeGlobal}, 951 {Index: 1000, Type: ExternTypeFunc}, 952 }, 953 GlobalSection: []Global{ 954 { 955 Init: ConstantExpression{ 956 Opcode: OpcodeI32Const, // not funcref. 957 Data: leb128.EncodeInt32(-1), 958 }, 959 }, 960 { 961 Init: ConstantExpression{ 962 Opcode: OpcodeRefFunc, 963 Data: leb128.EncodeInt32(123), 964 }, 965 }, 966 }, 967 ElementSection: []ElementSegment{ 968 { 969 Mode: ElementModeActive, 970 Init: []Index{0, ElementInitNullReference, 5}, 971 }, 972 { 973 Mode: ElementModeDeclarative, 974 Init: []Index{1, ElementInitNullReference, 5}, 975 }, 976 { 977 Mode: ElementModePassive, 978 Init: []Index{5, 2, ElementInitNullReference, ElementInitNullReference}, 979 }, 980 }, 981 }, 982 exp: map[uint32]struct{}{0: {}, 1: {}, 2: {}, 5: {}, 123: {}, 1000: {}}, 983 }, 984 { 985 mod: &Module{ 986 GlobalSection: []Global{ 987 { 988 Init: ConstantExpression{ 989 Opcode: OpcodeRefFunc, 990 Data: nil, 991 }, 992 }, 993 }, 994 }, 995 name: "invalid global", 996 expErr: `global[0] failed to initialize: EOF`, 997 }, 998 } 999 1000 for _, tt := range tests { 1001 tc := tt 1002 t.Run(tc.name, func(t *testing.T) { 1003 actual, err := tc.mod.declaredFunctionIndexes() 1004 if tc.expErr != "" { 1005 require.EqualError(t, err, tc.expErr) 1006 } else { 1007 require.NoError(t, err) 1008 require.Equal(t, tc.exp, actual) 1009 } 1010 }) 1011 } 1012 } 1013 1014 func TestModule_AssignModuleID(t *testing.T) { 1015 getID := func(bin []byte, lsns []experimental.FunctionListener, withEnsureTermination bool) ModuleID { 1016 m := Module{} 1017 m.AssignModuleID(bin, lsns, withEnsureTermination) 1018 return m.ID 1019 } 1020 1021 ml := &mockListener{} 1022 1023 // Ensures that different args always produce the different IDs. 1024 exists := map[ModuleID]struct{}{} 1025 for i, tc := range []struct { 1026 bin []byte 1027 withEnsureTermination bool 1028 listeners []experimental.FunctionListener 1029 }{ 1030 {bin: []byte{1, 2, 3}, withEnsureTermination: false}, 1031 {bin: []byte{1, 2, 3}, withEnsureTermination: true}, 1032 { 1033 bin: []byte{1, 2, 3}, 1034 listeners: []experimental.FunctionListener{ml}, 1035 withEnsureTermination: false, 1036 }, 1037 { 1038 bin: []byte{1, 2, 3}, 1039 listeners: []experimental.FunctionListener{ml}, 1040 withEnsureTermination: true, 1041 }, 1042 { 1043 bin: []byte{1, 2, 3}, 1044 listeners: []experimental.FunctionListener{nil, ml}, 1045 withEnsureTermination: true, 1046 }, 1047 { 1048 bin: []byte{1, 2, 3}, 1049 listeners: []experimental.FunctionListener{ml, ml}, 1050 withEnsureTermination: true, 1051 }, 1052 {bin: []byte{1, 2, 3, 4}, withEnsureTermination: false}, 1053 {bin: []byte{1, 2, 3, 4}, withEnsureTermination: true}, 1054 { 1055 bin: []byte{1, 2, 3, 4}, 1056 listeners: []experimental.FunctionListener{ml}, 1057 withEnsureTermination: false, 1058 }, 1059 { 1060 bin: []byte{1, 2, 3, 4}, 1061 listeners: []experimental.FunctionListener{ml}, 1062 withEnsureTermination: true, 1063 }, 1064 { 1065 bin: []byte{1, 2, 3, 4}, 1066 listeners: []experimental.FunctionListener{nil}, 1067 withEnsureTermination: true, 1068 }, 1069 { 1070 bin: []byte{1, 2, 3, 4}, 1071 listeners: []experimental.FunctionListener{nil, ml}, 1072 withEnsureTermination: true, 1073 }, 1074 { 1075 bin: []byte{1, 2, 3, 4}, 1076 listeners: []experimental.FunctionListener{ml, ml}, 1077 withEnsureTermination: true, 1078 }, 1079 { 1080 bin: []byte{1, 2, 3, 4}, 1081 listeners: []experimental.FunctionListener{ml, ml}, 1082 withEnsureTermination: false, 1083 }, 1084 } { 1085 id := getID(tc.bin, tc.listeners, tc.withEnsureTermination) 1086 _, exist := exists[id] 1087 require.False(t, exist, i) 1088 exists[id] = struct{}{} 1089 } 1090 } 1091 1092 type mockListener struct{} 1093 1094 func (m mockListener) Before(context.Context, api.Module, api.FunctionDefinition, []uint64, experimental.StackIterator) { 1095 } 1096 1097 func (m mockListener) After(context.Context, api.Module, api.FunctionDefinition, []uint64) {} 1098 1099 func (m mockListener) Abort(context.Context, api.Module, api.FunctionDefinition, error) {}