// |jit-test| skip-if: !wasmFunctionReferencesEnabled() // Generates combinations of different block types and operations for // non-defaultable locals (local.set / .tee / .get). // See the function references specification on the updated algorithm // for validating non-null references in locals. const KINDS = [ "block", "loop", "try", "catch", "delegate", "if", "else", ] const INITIALIZED = [ "nowhere", "outer", "inner", "outer-tee", "inner-tee", ]; const USED = [ "outer", "inner", "after-inner", "after-outer", ]; function generateBlock(kind, contents) { switch (kind) { case "block": { return `block\n${contents}end\n` } case "loop": { return `loop\n${contents}end\n` } case "try": { return `try\n${contents}end\n` } case "catch": { return `try\ncatch_all\n${contents}end\n` } case "delegate": { return `try\n${contents}\ndelegate 0\n` } case "if": { return `i32.const 0\nif\n${contents}end\n` } case "else": { return `i32.const 0\nif\nelse\n${contents}end\n` } } } // Generate a variation of the module below: // // (func // (block // $outer // (block // $inner // ) // $after-inner // ) // $after-outer // ) // // Where a local is used and initialized at different points depending on the // parameters. The block kinds of the inner and outer block may also be // customized. function generateModule(outerBlockKind, innerBlockKind, initializedWhere, usedWhere) { const INITIALIZE_STMT = '(local.set 0 ref.func 0)\n'; const INITIALIZE_STMT2 = '(drop (local.tee 0 ref.func 0))\n'; const USE_STMT = '(drop local.get 0)\n'; // inner block let innerBlockContents = ''; if (initializedWhere === 'inner') { innerBlockContents += INITIALIZE_STMT; } else if (initializedWhere === 'inner-tee') { innerBlockContents += INITIALIZE_STMT2; } if (usedWhere === 'inner') { innerBlockContents += USE_STMT; } let innerBlock = generateBlock(innerBlockKind, innerBlockContents); // outer block let outerBlockContents = ''; if (initializedWhere === 'outer') { outerBlockContents += INITIALIZE_STMT; } else if (initializedWhere === 'outer-tee') { outerBlockContents += INITIALIZE_STMT2; } if (usedWhere === 'outer') { outerBlockContents += USE_STMT; } outerBlockContents += innerBlock; if (usedWhere === 'after-inner') { outerBlockContents += USE_STMT; } let outerBlock = generateBlock(outerBlockKind, outerBlockContents); // after outer block let afterOuterBlock = ''; if (usedWhere === 'after-outer') { afterOuterBlock += USE_STMT; } return `(module (type $t (func)) (func (export "test") (local (ref $t)) ${outerBlock}${afterOuterBlock} ) )`; } const LOGGING = false; for (let outer of KINDS) { for (let inner of KINDS) { for (let initialized of INITIALIZED) { for (let used of USED) { let text = generateModule(outer, inner, initialized, used); let expectPass; switch (initialized) { case "outer": case "outer-tee": { // Defining the local in the outer block makes it valid // in the outer block, the inner block, and after the // inner block expectPass = used !== "after-outer"; break; } case "inner": case "inner-tee": { // Defining the local in the inner block makes it valid // in the inner block // // NOTE: an extension to typing could make this valid // after the inner block in some cases expectPass = used === "inner"; break; } case "nowhere": { // Not defining the local makes it always invalid to // use expectPass = false; break; } } if (LOGGING) { console.log(); console.log(`TEST: outer=${outer}, inner=${inner}, initialized=${initialized}, used=${used}`); console.log(expectPass ? "EXPECT PASS" : "EXPECT FAIL"); console.log(text); } let binary = wasmTextToBinary(text); assertEq(WebAssembly.validate(binary), expectPass); if (!expectPass) { // Check if the error message is right. try { new WebAssembly.Module(binary); } catch (ex) { assertEq(true, /local\.get read from unset local/.test(ex.message)); } } } } } }