load(libdir + "wasm-binary.js"); const v2vSig = {args:[], ret:VoidCode}; const v2vSigSection = sigSection([v2vSig]); // 'ref.func' parses, validates and returns a non-null value wasmFullPass(` (module (elem declare func $run) (func $run (result i32) ref.func $run ref.is_null ) (export "run" (func $run)) ) `, 0); // function returning reference to itself { let {f1} = wasmEvalText(` (module (elem declare func $f1) (func $f1 (result funcref) ref.func $f1) (export "f1" (func $f1)) ) `).exports; assertEq(f1(), f1); } // function returning reference to a different function { let {f1, f2} = wasmEvalText(` (module (elem declare func $f1) (func $f1) (func $f2 (result funcref) ref.func $f1) (export "f1" (func $f1)) (export "f2" (func $f2)) ) `).exports; assertEq(f2(), f1); } // function returning reference to function in a different module { let i1 = wasmEvalText(` (module (elem declare func $f1) (func $f1) (export "f1" (func $f1)) ) `); let i2 = wasmEvalText(` (module (import "" "f1" (func $f1)) (elem declare func $f1) (func $f2 (result funcref) ref.func $f1) (export "f1" (func $f1)) (export "f2" (func $f2)) ) `, {"": i1.exports}); let f1 = i1.exports.f1; let f2 = i2.exports.f2; assertEq(f2(), f1); } // function index must be valid assertErrorMessage(() => { wasmEvalText(` (module (func (result funcref) ref.func 10) ) `); }, WebAssembly.CompileError, /(function index out of range)|(function index out of bounds)/); function validFuncRefText(forwardDeclare, tbl_type) { return wasmEvalText(` (module (table 1 ${tbl_type}) (func $test (result funcref) ref.func $referenced) (func $referenced) ${forwardDeclare} ) `); } // referenced function must be forward declared somehow assertErrorMessage(() => validFuncRefText('', 'funcref'), WebAssembly.CompileError, /(function index is not declared in a section before the code section)|(undeclared function reference)/); // referenced function can be forward declared via segments assertEq(validFuncRefText('(elem 0 (i32.const 0) func $referenced)', 'funcref') instanceof WebAssembly.Instance, true); assertEq(validFuncRefText('(elem func $referenced)', 'funcref') instanceof WebAssembly.Instance, true); assertEq(validFuncRefText('(elem declare func $referenced)', 'funcref') instanceof WebAssembly.Instance, true); // also when the segment is passive or active 'funcref' assertEq(validFuncRefText('(elem 0 (i32.const 0) funcref (ref.func $referenced))', 'funcref') instanceof WebAssembly.Instance, true); assertEq(validFuncRefText('(elem funcref (ref.func $referenced))', 'funcref') instanceof WebAssembly.Instance, true); // reference function can be forward declared via globals assertEq(validFuncRefText('(global funcref (ref.func $referenced))', 'externref') instanceof WebAssembly.Instance, true); // reference function can be forward declared via export assertEq(validFuncRefText('(export "referenced" (func $referenced))', 'externref') instanceof WebAssembly.Instance, true); // reference function cannot be forward declared via start assertErrorMessage(() => validFuncRefText('(start $referenced)', 'externref'), WebAssembly.CompileError, /(function index is not declared in a section before the code section)|(undeclared function reference)/); // Tests not expressible in the text format. // element segment with elemexpr can carry non-reference type, but this must be // rejected. assertErrorMessage(() => new WebAssembly.Module( moduleWithSections([generalElemSection([{ flag: PassiveElemExpr, typeCode: I32Code, elems: [] }])])), WebAssembly.CompileError, /bad type/); // Test case for bug 1596026: when taking the ref.func of an imported function, // the value obtained should not be the JS function. This would assert (even in // a release build), so the test is merely that the code runs. var ins = new WebAssembly.Instance(new WebAssembly.Module(wasmTextToBinary(` (module (import "m" "f" (func $f (param i32) (result i32))) (elem declare func $f) (table 1 funcref) (func (export "f") (table.set 0 (i32.const 0) (ref.func $f))))`)), {m:{f:(x) => 37+x}}); ins.exports.f(); // Verification that we can handle encoding errors for passive element segments // properly. function checkPassiveElemSegment(mangle, err) { let bin = moduleWithSections( [v2vSigSection, declSection([0]), // One function tableSection(1), // One table { name: elemId, // One passive segment body: (function () { let body = []; body.push(1); // 1 element segment body.push(0x1 | 0x4); // Flags: Passive and uses element expression body.push(mangle == "type" ? BadType : AnyFuncCode); // always anyfunc body.push(1); // Element count body.push(mangle == "ref.func" ? BadType : RefFuncCode); // always ref.func body.push(0); // func index body.push(EndCode + (mangle == "end" ? 1 : 0)); return body; })() }, bodySection( // Empty function [funcBody( {locals:[], body:[]})]) ]); if (err) { assertErrorMessage(() => new WebAssembly.Module(bin), WebAssembly.CompileError, err); } else { new WebAssembly.Module(bin); } } checkPassiveElemSegment(""); checkPassiveElemSegment("type", /bad type/); checkPassiveElemSegment("ref.func", /unrecognized opcode/); checkPassiveElemSegment("end", /unrecognized opcode/); // Passive element segments can contain literal null values. { let txt = `(module (table (export "t") 10 funcref) (elem (i32.const 1) $m) (elem (i32.const 3) $m) (elem (i32.const 6) $m) (elem (i32.const 8) $m) (elem funcref (ref.func $f) (ref.null func) (ref.func $g) (ref.null func) (ref.func $h)) (func $m) (func $f) (func $g) (func $h) (func (export "doit") (param $idx i32) (table.init 4 (local.get $idx) (i32.const 0) (i32.const 5))))`; let ins = wasmEvalText(txt); ins.exports.doit(0); ins.exports.doit(5); assertEq(typeof ins.exports.t.get(0), "function"); assertEq(ins.exports.t.get(1), null); assertEq(typeof ins.exports.t.get(2), "function"); assertEq(ins.exports.t.get(0) == ins.exports.t.get(2), false); assertEq(ins.exports.t.get(3), null); assertEq(typeof ins.exports.t.get(4), "function"); assertEq(typeof ins.exports.t.get(5), "function"); assertEq(ins.exports.t.get(6), null); assertEq(typeof ins.exports.t.get(7), "function"); assertEq(ins.exports.t.get(8), null); assertEq(typeof ins.exports.t.get(9), "function"); } // Test ref.func in global initializer expressions for (let mutable of [true, false]) { for (let imported of [true, false]) { for (let exported of [true, false]) { let globalType = mutable ? `(mut funcref)` : `funcref`; let imports = {}; if (imported) { imports = wasmEvalText(` (module (global $g (export "g") ${globalType} (ref.func $f)) (func $f (export "f") (result i32) i32.const 42) ) `).exports; } let exports = wasmEvalText(` (module (global $g ${exported ? `(export "g")` : ``} ${imported ? `(import "" "g")` : ``} ${globalType} ${imported ? `` : `(ref.func $f)`}) ${exported ? `` : `(func (export "get_g") (result funcref) global.get $g)`} (func $f (export "f") (result i32) i32.const 42) ) `, { "": imports }).exports; let targetFunc = imported ? imports.f : exports.f; let globalVal = exported ? exports.g.value : exports.get_g(); assertEq(targetFunc(), 42); assertEq(globalVal(), 42); assertEq(targetFunc, globalVal); if (imported && exported) { assertEq(imports.g, exports.g); } } } } // Test invalid ref.func indices function testCodeRefFuncIndex(index) { assertErrorMessage(() => { new WebAssembly.Module(moduleWithSections( [v2vSigSection, declSection([0]), // One function bodySection( [funcBody( {locals:[], body:[ RefFuncCode, ...varU32(index), DropCode ]})]) ])) }, WebAssembly.CompileError, /(function index out of range)|(function index out of bounds)/); } testCodeRefFuncIndex(1); testCodeRefFuncIndex(2); testCodeRefFuncIndex(10); testCodeRefFuncIndex(1000); function testGlobalRefFuncIndex(index) { assertErrorMessage(() => { new WebAssembly.Module(moduleWithSections( [v2vSigSection, globalSection([ { valType: AnyFuncCode, flags: 0, initExpr: [RefFuncCode, ...varU32(index), EndCode], } ]) ])) }, WebAssembly.CompileError, /(function index out of range)|(function index out of bounds)/); } testGlobalRefFuncIndex(1); testGlobalRefFuncIndex(2); testGlobalRefFuncIndex(10); testGlobalRefFuncIndex(1000);