validation.js (14062B)
1 // Test wasm type validation for exception handling instructions. 2 3 load(libdir + "wasm-binary.js"); 4 5 function wasmValid(mod) { 6 assertEq(WebAssembly.validate(mod), true); 7 } 8 9 function wasmInvalid(mod, pattern) { 10 assertEq(WebAssembly.validate(mod), false); 11 assertErrorMessage( 12 () => new WebAssembly.Module(mod), 13 WebAssembly.CompileError, 14 pattern 15 ); 16 } 17 18 const emptyType = { args: [], ret: VoidCode }; 19 const i32Type = { args: [I32Code], ret: VoidCode }; 20 const toi32Type = { args: [], ret: I32Code }; 21 const i32Toi32Type = { args: [I32Code], ret: I32Code }; 22 const i32Toi64Type = { args: [I32Code], ret: I64Code }; 23 const i32i32Toi32Type = { args: [I32Code, I32Code], ret: I32Code }; 24 25 function testValidateDecode() { 26 // Try blocks must have a block type code. 27 wasmInvalid( 28 moduleWithSections([ 29 sigSection([emptyType]), 30 declSection([0]), 31 tagSection([{ type: 0 }]), 32 bodySection([ 33 funcBody({ 34 locals: [], 35 body: [ 36 TryCode, 37 // Missing type code. 38 I32ConstCode, 39 0x01, 40 CatchCode, 41 0x00, 42 EndCode, 43 DropCode, 44 ReturnCode, 45 ], 46 }), 47 ]), 48 ]), 49 /bad type/ 50 ); 51 52 // Catch must have a tag index. 53 wasmInvalid( 54 moduleWithSections([ 55 sigSection([emptyType]), 56 declSection([0]), 57 tagSection([{ type: 0 }]), 58 bodySection([ 59 funcBody( 60 { 61 locals: [], 62 body: [ 63 TryCode, 64 I32Code, 65 I32ConstCode, 66 0x01, 67 CatchCode, 68 // Index missing. 69 ], 70 }, 71 (withEndCode = false) 72 ), 73 ]), 74 ]), 75 /expected tag index/ 76 ); 77 78 // Rethrow must have a depth argument. 79 wasmInvalid( 80 moduleWithSections([ 81 sigSection([emptyType]), 82 declSection([0]), 83 tagSection([{ type: 0 }]), 84 bodySection([ 85 funcBody( 86 { 87 locals: [], 88 body: [ 89 RethrowCode, 90 // Index missing. 91 ], 92 }, 93 (withEndCode = false) 94 ), 95 ]), 96 ]), 97 /unable to read rethrow depth/ 98 ); 99 100 // Delegate must have a depth argument. 101 wasmInvalid( 102 moduleWithSections([ 103 sigSection([emptyType]), 104 declSection([0]), 105 tagSection([{ type: 0 }]), 106 bodySection([ 107 funcBody( 108 { 109 locals: [], 110 body: [ 111 TryCode, 112 I32Code, 113 I32ConstCode, 114 0x01, 115 DelegateCode, 116 // Index missing. 117 ], 118 }, 119 (withEndCode = false) 120 ), 121 ]), 122 ]), 123 /unable to read delegate depth/ 124 ); 125 } 126 127 function testValidateThrow() { 128 valid = `(module 129 (type (func (param i32))) 130 (func $exn-zero 131 i32.const 0 132 throw $exn1) 133 (tag $exn1 (type 0)))`; 134 135 validSimd = `(module 136 (tag $exn (param v128)) 137 (func (export "f") (param v128) (result v128) 138 try (result v128) 139 (v128.const f64x2 1 2) 140 (throw $exn) 141 catch $exn 142 end))`; 143 144 invalid0 = `(module 145 (type (func (param i32))) 146 (func $exn-zero 147 throw $exn1) 148 (tag $exn1 (type 0)))`; 149 error0 = /popping value from empty stack/; 150 151 invalid1 = `(module 152 (type (func (param i32))) 153 (func $exn-zero 154 i64.const 0 155 throw $exn1) 156 (tag $exn1 (type 0)))`; 157 error1 = /expression has type i64 but expected i32/; 158 159 invalid2 = `(module 160 (type (func (param i32))) 161 (func $exn-zero 162 i32.const 0 163 throw 1) 164 (tag $exn1 (type 0)))`; 165 error2 = /tag index out of range/; 166 167 wasmValidateText(valid); 168 if (wasmSimdEnabled()) { 169 wasmValidateText(validSimd); 170 } 171 wasmFailValidateText(invalid0, error0); 172 wasmFailValidateText(invalid1, error1); 173 wasmFailValidateText(invalid2, error2); 174 } 175 176 function testValidateTryCatch() { 177 function mod_with(fbody) { 178 return moduleWithSections([ 179 sigSection([emptyType, i32Type, i32i32Toi32Type]), 180 declSection([0]), 181 tagSection([{ type: 0 }, { type: 1 }]), 182 bodySection([ 183 funcBody({ 184 locals: [], 185 body: fbody, 186 }), 187 ]), 188 ]); 189 } 190 191 const body1 = [ 192 // try (result i32) 193 TryCode, 194 I32Code, 195 // (i32.const 1) 196 I32ConstCode, 197 varU32(1), 198 // catch 1 199 CatchCode, 200 varU32(1), 201 ]; 202 203 const valid1 = mod_with(body1.concat([EndCode, DropCode, ReturnCode])); 204 const invalid1 = mod_with( 205 body1.concat([I32ConstCode, varU32(2), EndCode, DropCode, ReturnCode]) 206 ); 207 208 const valid2 = mod_with([ 209 // (i32.const 0) (i32.const 0) 210 I32ConstCode, 211 varU32(0), 212 I32ConstCode, 213 varU32(0), 214 // try (param i32 i32) (result i32) drop drop (i32.const 1) 215 TryCode, 216 varS32(2), 217 DropCode, 218 DropCode, 219 I32ConstCode, 220 varU32(1), 221 // catch 0 (i32.const 2) end drop return 222 CatchCode, 223 varU32(0), 224 I32ConstCode, 225 varU32(2), 226 EndCode, 227 DropCode, 228 ReturnCode, 229 ]); 230 231 wasmValid(valid1); 232 wasmInvalid(invalid1, /unused values not explicitly dropped/); 233 wasmValid(valid2); 234 235 // Test handler-less try blocks. 236 wasmValidateText( 237 `(module (func try end))` 238 ); 239 240 wasmValidateText( 241 `(module (func (result i32) try (result i32) (i32.const 1) end))` 242 ); 243 244 wasmValidateText( 245 `(module 246 (func (result i32) 247 try (result i32) (i32.const 1) (br 0) end))` 248 ); 249 250 wasmFailValidateText( 251 `(module 252 (func try (result i32) end))`, 253 /popping value from empty stack/ 254 ); 255 } 256 257 function testValidateCatch() { 258 wasmInvalid( 259 moduleWithSections([ 260 sigSection([emptyType]), 261 declSection([0]), 262 bodySection([ 263 funcBody({ 264 locals: [], 265 body: [TryCode, VoidCode, CatchCode, varU32(0), EndCode], 266 }), 267 ]), 268 ]), 269 /tag index out of range/ 270 ); 271 } 272 273 function testValidateCatchAll() { 274 wasmValidateText( 275 `(module 276 (tag $exn) 277 (func try catch $exn catch_all end))` 278 ); 279 280 wasmValidateText( 281 `(module 282 (func (result i32) 283 try (result i32) 284 (i32.const 0) 285 catch_all 286 (i32.const 1) 287 end))` 288 ); 289 290 wasmFailValidateText( 291 `(module 292 (tag $exn) 293 (func try catch_all catch 0 end))`, 294 /catch cannot follow a catch_all/ 295 ); 296 297 wasmFailValidateText( 298 `(module 299 (tag $exn) 300 (func try (result i32) (i32.const 1) catch_all end drop))`, 301 /popping value from empty stack/ 302 ); 303 304 wasmFailValidateText( 305 `(module 306 (tag $exn (param i32)) 307 (func try catch $exn drop catch_all drop end))`, 308 /popping value from empty stack/ 309 ); 310 311 // We can't distinguish `else` and `catch_all` in error messages since they 312 // share the binary opcode. 313 wasmFailValidateText( 314 `(module 315 (tag $exn) 316 (func try catch_all catch_all end))`, 317 /catch_all can only be used within a try/ 318 ); 319 320 wasmFailValidateText( 321 `(module 322 (tag $exn) 323 (func catch_all))`, 324 /catch_all can only be used within a try/ 325 ); 326 } 327 328 function testValidateExnPayload() { 329 valid0 = moduleWithSections([ 330 sigSection([i32Type, i32Toi32Type]), 331 declSection([1]), 332 // (tag $exn (param i32)) 333 tagSection([{ type: 0 }]), 334 bodySection([ 335 // (func (param i32) (result i32) ... 336 funcBody({ 337 locals: [], 338 body: [ 339 // try (result i32) (local.get 0) (throw $exn) (i32.const 1) 340 TryCode, 341 I32Code, 342 LocalGetCode, 343 varU32(0), 344 ThrowCode, 345 varU32(0), 346 I32ConstCode, 347 varU32(1), 348 // catch $exn (i32.const 1) (i32.add) end 349 CatchCode, 350 varU32(0), 351 I32ConstCode, 352 varU32(1), 353 I32AddCode, 354 EndCode, 355 ], 356 }), 357 ]), 358 ]); 359 360 // This is to ensure the following sentence from the spec overview holds: 361 // > "the operand stack is popped back to the size the operand stack had 362 // > when the try block was entered" 363 valid1 = moduleWithSections([ 364 sigSection([i32Type, toi32Type]), 365 declSection([1]), 366 // (tag $exn (param i32)) 367 tagSection([{ type: 0 }]), 368 bodySection([ 369 // (func (result i32) ... 370 funcBody({ 371 locals: [], 372 body: [ 373 // try (result i32) (i32.const 0) (i32.const 1) (throw $exn) drop 374 TryCode, 375 I32Code, 376 I32ConstCode, 377 varU32(0), 378 I32ConstCode, 379 varU32(1), 380 ThrowCode, 381 varU32(0), 382 DropCode, 383 // catch $exn drop (i32.const 2) end 384 CatchCode, 385 varU32(0), 386 DropCode, 387 I32ConstCode, 388 varU32(2), 389 EndCode, 390 ], 391 }), 392 ]), 393 ]); 394 395 invalid0 = moduleWithSections([ 396 sigSection([i32Type, i32Toi64Type]), 397 declSection([1]), 398 // (tag $exn (param i32)) 399 tagSection([{ type: 0 }]), 400 bodySection([ 401 // (func (param i32) (result i64) ... 402 funcBody({ 403 locals: [], 404 body: [ 405 // try (result i64) (local.get 0) (throw $exn) (i64.const 0) 406 TryCode, 407 I64Code, 408 LocalGetCode, 409 varU32(0), 410 ThrowCode, 411 varU32(0), 412 I64ConstCode, 413 varU32(0), 414 // catch $exn end 415 CatchCode, 416 varU32(0), 417 EndCode, 418 ], 419 }), 420 ]), 421 ]); 422 423 invalid1 = moduleWithSections([ 424 // (type (func)) 425 sigSection([emptyType]), 426 declSection([0]), 427 // (tag $exn (type 0)) 428 tagSection([{ type: 0 }]), 429 bodySection([ 430 // (func ... 431 funcBody({ 432 locals: [], 433 body: [ 434 // try catch 1 end 435 TryCode, 436 VoidCode, 437 CatchCode, 438 varU32(1), 439 EndCode, 440 ], 441 }), 442 ]), 443 ]); 444 445 wasmValid(valid0); 446 wasmValid(valid1); 447 wasmInvalid(invalid0, /has type i32 but expected i64/); 448 wasmInvalid(invalid1, /tag index out of range/); 449 } 450 451 function testValidateRethrow() { 452 wasmValidateText( 453 `(module 454 (tag $exn (param)) 455 (func 456 try 457 nop 458 catch $exn 459 rethrow 0 460 end))` 461 ); 462 463 wasmValidateText( 464 `(module 465 (tag $exn (param)) 466 (func 467 try 468 nop 469 catch_all 470 rethrow 0 471 end))` 472 ); 473 474 wasmValidateText( 475 `(module 476 (func (result i32) 477 try (result i32) 478 (i32.const 1) 479 catch_all 480 rethrow 0 481 end))` 482 ); 483 484 wasmValidateText( 485 `(module 486 (tag $exn (param)) 487 (func 488 try 489 nop 490 catch $exn 491 block 492 try 493 catch $exn 494 rethrow 0 495 end 496 end 497 end))` 498 ); 499 500 wasmValidateText( 501 `(module 502 (tag $exn (param)) 503 (func 504 try 505 nop 506 catch $exn 507 block 508 try 509 catch $exn 510 rethrow 2 511 end 512 end 513 end))` 514 ); 515 516 wasmFailValidateText( 517 `(module 518 (tag $exn (param)) 519 (func 520 try 521 nop 522 catch $exn 523 block 524 try 525 catch $exn 526 rethrow 1 527 end 528 end 529 end))`, 530 /rethrow target was not a catch block/ 531 ); 532 533 wasmFailValidateText( 534 `(module (func rethrow 0))`, 535 /rethrow target was not a catch block/ 536 ); 537 538 wasmFailValidateText( 539 `(module (func try rethrow 0 end))`, 540 /rethrow target was not a catch block/ 541 ); 542 543 wasmFailValidateText( 544 `(module (func try rethrow 0 catch_all end))`, 545 /rethrow target was not a catch block/ 546 ); 547 548 wasmFailValidateText( 549 `(module 550 (tag $exn (param)) 551 (func 552 try 553 nop 554 catch $exn 555 block 556 try 557 catch $exn 558 rethrow 4 559 end 560 end 561 end))`, 562 /rethrow depth exceeds current nesting level/ 563 ); 564 } 565 566 function testValidateDelegate() { 567 wasmValidateText( 568 `(module 569 (tag $exn (param)) 570 (func 571 try 572 try 573 throw $exn 574 delegate 0 575 catch $exn 576 end))` 577 ); 578 579 wasmValidateText( 580 `(module 581 (tag $exn (param)) 582 (func 583 try 584 try 585 throw $exn 586 delegate 1 587 catch $exn 588 end))` 589 ); 590 591 wasmValidateText( 592 `(module 593 (tag $exn (param)) 594 (func 595 block 596 try 597 throw $exn 598 delegate 0 599 end))` 600 ); 601 602 wasmValidateText( 603 `(module 604 (tag $exn (param)) 605 (func 606 try 607 catch $exn 608 try 609 throw $exn 610 delegate 0 611 end))` 612 ); 613 614 wasmFailValidateText( 615 `(module 616 (tag $exn (param)) 617 (func (result i32) 618 try 619 throw $exn 620 delegate 0 621 (i64.const 0) 622 end))`, 623 /type mismatch: expression has type i64 but expected i32/ 624 ); 625 626 wasmFailValidateText( 627 `(module 628 (tag $exn (param)) 629 (func 630 try (result i32) 631 (i64.const 0) 632 delegate 0))`, 633 /type mismatch: expression has type i64 but expected i32/ 634 ); 635 636 wasmFailValidateText( 637 `(module 638 (tag $exn (param)) 639 (func 640 try 641 try 642 throw $exn 643 delegate 2 644 catch $exn 645 end))`, 646 /delegate depth exceeds current nesting level/ 647 ); 648 649 wasmFailValidateText( 650 `(module (func delegate 0))`, 651 /delegate can only be used within a try/ 652 ); 653 } 654 655 testValidateDecode(); 656 testValidateThrow(); 657 testValidateTryCatch(); 658 testValidateCatch(); 659 testValidateCatchAll(); 660 testValidateExnPayload(); 661 testValidateRethrow(); 662 testValidateDelegate();