// Copyright 2015 the V8 project authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. // Flags: --expose-wasm --experimental-wasm-eh load("test/mjsunit/wasm/wasm-constants.js"); load("test/mjsunit/wasm/wasm-module-builder.js"); // The following method doesn't attempt to catch an raised exception. var test_throw = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_v); builder.addFunction("throw_if_param_not_zero", kSig_i_i) .addBody([ kExprGetLocal, 0, kExprI32Const, 0, kExprI32Ne, kExprIf, kWasmStmt, kExprThrow, 0, kExprEnd, kExprI32Const, 1 ]).exportFunc(); return builder.instantiate(); })(); // Check the test_throw exists. assertFalse(test_throw === undefined); assertFalse(test_throw === null); assertFalse(test_throw === 0); assertEquals("object", typeof test_throw.exports); assertEquals("function", typeof test_throw.exports.throw_if_param_not_zero); // Test expected behavior of throws assertEquals(1, test_throw.exports.throw_if_param_not_zero(0)); assertWasmThrows(0, [], function() { test_throw.exports.throw_if_param_not_zero(10) }); assertWasmThrows(0, [], function() { test_throw.exports.throw_if_param_not_zero(-1) }); // Now that we know throwing works, we test catching the exceptions we raise. var test_catch = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_v); builder.addFunction("simple_throw_catch_to_0_1", kSig_i_i) .addBody([ kExprTry, kWasmI32, kExprGetLocal, 0, kExprI32Eqz, kExprIf, kWasmStmt, kExprThrow, 0, kExprEnd, kExprI32Const, 1, kExprCatch, 0, kExprI32Const, 0, kExprEnd ]).exportFunc(); return builder.instantiate(); })(); // Check the test_catch exists. assertFalse(test_catch === undefined); assertFalse(test_catch === null); assertFalse(test_catch === 0); assertEquals("object", typeof test_catch.exports); assertEquals("function", typeof test_catch.exports.simple_throw_catch_to_0_1); // Test expected behavior of simple catch. assertEquals(0, test_catch.exports.simple_throw_catch_to_0_1(0)); assertEquals(1, test_catch.exports.simple_throw_catch_to_0_1(1)); // Test that we can distinguish which exception was thrown. var test_catch_2 = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_v); builder.addException(kSig_v_v); builder.addException(kSig_v_v); builder.addFunction("catch_different_exceptions", kSig_i_i) .addBody([ kExprTry, kWasmI32, kExprTry, kWasmI32, kExprGetLocal, 0, kExprI32Eqz, kExprIf, kWasmStmt, kExprThrow, 0, kExprElse, kExprGetLocal, 0, kExprI32Const, 1, kExprI32Eq, kExprIf, kWasmStmt, kExprThrow, 1, kExprElse, kExprThrow, 2, kExprEnd, kExprEnd, kExprI32Const, 2, kExprCatch, 0, kExprI32Const, 3, kExprEnd, kExprCatch, 1, kExprI32Const, 4, kExprEnd ]).exportFunc(); return builder.instantiate(); })(); assertFalse(test_catch_2 === undefined); assertFalse(test_catch_2 === null); assertFalse(test_catch_2 === 0); assertEquals("object", typeof test_catch_2.exports); assertEquals("function", typeof test_catch_2.exports.catch_different_exceptions); assertEquals(3, test_catch_2.exports.catch_different_exceptions(0)); assertEquals(4, test_catch_2.exports.catch_different_exceptions(1)); assertWasmThrows(2, [], function() { test_catch_2.exports.catch_different_exceptions(2) }); // Test throwing an exception with multiple values. var test_throw_1_2 = (function() { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_ii); builder.addFunction("throw_1_2", kSig_v_v) .addBody([ kExprI32Const, 1, kExprI32Const, 2, kExprThrow, 0, ]).exportFunc(); return builder.instantiate(); })(); assertFalse(test_throw_1_2 === undefined); assertFalse(test_throw_1_2 === null); assertFalse(test_throw_1_2 === 0); assertEquals("object", typeof test_throw_1_2.exports); assertEquals("function", typeof test_throw_1_2.exports.throw_1_2); assertWasmThrows(0, [0, 1, 0, 2], function() { test_throw_1_2.exports.throw_1_2(); }); // Test throwing/catching the i32 parameter value. var test_throw_catch_param_i = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_i); builder.addFunction("throw_catch_param", kSig_i_i) .addBody([ kExprTry, kWasmI32, kExprGetLocal, 0, kExprThrow, 0, kExprI32Const, 2, kExprCatch, 0, kExprReturn, kExprEnd, ]).exportFunc(); return builder.instantiate(); })(); assertFalse(test_throw_catch_param_i === undefined); assertFalse(test_throw_catch_param_i === null); assertFalse(test_throw_catch_param_i === 0); assertEquals("object", typeof test_throw_catch_param_i.exports); assertEquals("function", typeof test_throw_catch_param_i.exports.throw_catch_param); assertEquals(0, test_throw_catch_param_i.exports.throw_catch_param(0)); assertEquals(1, test_throw_catch_param_i.exports.throw_catch_param(1)); assertEquals(10, test_throw_catch_param_i.exports.throw_catch_param(10)); // Test the encoding of a thrown exception with an integer exception. var test_throw_param_i = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_i); builder.addFunction("throw_param", kSig_v_i) .addBody([ kExprGetLocal, 0, kExprThrow, 0, ]).exportFunc(); return builder.instantiate(); })(); assertFalse(test_throw_param_i === undefined); assertFalse(test_throw_param_i === null); assertFalse(test_throw_param_i === 0); assertEquals("object", typeof test_throw_param_i.exports); assertEquals("function", typeof test_throw_param_i.exports.throw_param); assertWasmThrows(0, [0, 5], function() { test_throw_param_i.exports.throw_param(5); }); assertWasmThrows(0, [6, 31026], function() { test_throw_param_i.exports.throw_param(424242); }); // Test throwing/catching the f32 parameter value. var test_throw_catch_param_f = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_f); builder.addFunction("throw_catch_param", kSig_f_f) .addBody([ kExprTry, kWasmF32, kExprGetLocal, 0, kExprThrow, 0, kExprF32Const, 0, 0, 0, 0, kExprCatch, 0, kExprReturn, kExprEnd, ]).exportFunc(); return builder.instantiate(); })(); assertFalse(test_throw_catch_param_f === undefined); assertFalse(test_throw_catch_param_f === null); assertFalse(test_throw_catch_param_f === 0); assertEquals("object", typeof test_throw_catch_param_f.exports); assertEquals("function", typeof test_throw_catch_param_f.exports.throw_catch_param); assertEquals(5.0, test_throw_catch_param_f.exports.throw_catch_param(5.0)); assertEquals(10.5, test_throw_catch_param_f.exports.throw_catch_param(10.5)); // Test the encoding of a thrown exception with a float value. var test_throw_param_f = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_f); builder.addFunction("throw_param", kSig_v_f) .addBody([ kExprGetLocal, 0, kExprThrow, 0, ]).exportFunc(); return builder.instantiate(); })(); assertFalse(test_throw_param_f === undefined); assertFalse(test_throw_param_f === null); assertFalse(test_throw_param_f === 0); assertEquals("object", typeof test_throw_param_f.exports); assertEquals("function", typeof test_throw_param_f.exports.throw_param); assertWasmThrows(0, [16544, 0], function() { test_throw_param_f.exports.throw_param(5.0); }); assertWasmThrows(0, [16680, 0], function() { test_throw_param_f.exports.throw_param(10.5); }); // Test throwing/catching an I64 value var test_throw_catch_param_l = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_l); builder.addFunction("throw_catch_param", kSig_i_i) .addBody([ kExprGetLocal, 0, kExprI64UConvertI32, kExprSetLocal, 1, kExprTry, kWasmI32, kExprGetLocal, 1, kExprThrow, 0, kExprI32Const, 2, kExprCatch, 0, kExprGetLocal, 1, kExprI64Eq, kExprIf, kWasmI32, kExprI32Const, 1, kExprElse, kExprI32Const, 0, kExprEnd, // TODO(kschimpf): Why is this return necessary? kExprReturn, kExprEnd, ]).addLocals({i64_count: 1}).exportFunc(); return builder.instantiate(); })(); assertFalse(test_throw_catch_param_l === undefined); assertFalse(test_throw_catch_param_l === null); assertFalse(test_throw_catch_param_l === 0); assertEquals("object", typeof test_throw_catch_param_l.exports); assertEquals("function", typeof test_throw_catch_param_l.exports.throw_catch_param); assertEquals(1, test_throw_catch_param_l.exports.throw_catch_param(5)); assertEquals(1, test_throw_catch_param_l.exports.throw_catch_param(0)); assertEquals(1, test_throw_catch_param_l.exports.throw_catch_param(-1)); // Test the encoding of a thrown exception with an I64 value. var test_throw_param_l = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_l); builder.addFunction("throw_param", kSig_v_ii) .addBody([ kExprGetLocal, 0, kExprI64UConvertI32, kExprI64Const, 32, kExprI64Shl, kExprGetLocal, 1, kExprI64UConvertI32, kExprI64Ior, kExprThrow, 0 ]).exportFunc(); return builder.instantiate(); })(); assertFalse(test_throw_param_l === undefined); assertFalse(test_throw_param_l === null); assertFalse(test_throw_param_l === 0); assertEquals("object", typeof test_throw_param_l.exports); assertEquals("function", typeof test_throw_param_l.exports.throw_param); assertWasmThrows(0, [0, 10, 0, 5], function() { test_throw_param_l.exports.throw_param(10, 5); }); assertWasmThrows(0, [65535, 65535, 0, 13], function() { test_throw_param_l.exports.throw_param(-1, 13); }); // Test throwing/catching the F64 parameter value var test_throw_catch_param_d = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_d); builder.addFunction("throw_catch_param", kSig_d_d) .addBody([ kExprTry, kWasmF64, kExprGetLocal, 0, kExprThrow, 0, kExprF64Const, 0, 0, 0, 0, 0, 0, 0, 0, kExprCatch, 0, kExprReturn, kExprEnd, ]).exportFunc(); return builder.instantiate(); })(); assertFalse(test_throw_catch_param_d === undefined); assertFalse(test_throw_catch_param_d === null); assertFalse(test_throw_catch_param_d === 0); assertEquals("object", typeof test_throw_catch_param_d.exports); assertEquals("function", typeof test_throw_catch_param_d.exports.throw_catch_param); assertEquals(5.0, test_throw_catch_param_d.exports.throw_catch_param(5.0)); assertEquals(10.5, test_throw_catch_param_d.exports.throw_catch_param(10.5)); // Test the encoding of a thrown exception with an f64 value. var test_throw_param_d = (function () { var builder = new WasmModuleBuilder(); builder.addException(kSig_v_d); builder.addFunction("throw_param", kSig_v_f) .addBody([ kExprGetLocal, 0, kExprF64ConvertF32, kExprThrow, 0 ]).exportFunc(); return builder.instantiate(); })(); assertFalse(test_throw_param_d === undefined); assertFalse(test_throw_param_d === null); assertFalse(test_throw_param_d === 0); assertEquals("object", typeof test_throw_param_d.exports); assertEquals("function", typeof test_throw_param_d.exports.throw_param); assertWasmThrows(0, [16404, 0, 0, 0], function() { test_throw_param_d.exports.throw_param(5.0); }); assertWasmThrows(0, [16739, 4816, 0, 0], function() { test_throw_param_d.exports.throw_param(10000000.5); }); /* TODO(kschimpf) Convert these tests to work for the proposed exceptions. // The following methods do not attempt to catch the exception they raise. var test_throw = (function () { var builder = new WasmModuleBuilder(); builder.addFunction("throw_expr_with_params", kSig_v_ddi) .addBody([ // p2 * (p0 + min(p0, p1))|0 - 20 kExprGetLocal, 2, kExprGetLocal, 0, kExprGetLocal, 0, kExprGetLocal, 1, kExprF64Min, kExprF64Add, kExprI32SConvertF64, kExprI32Mul, kExprI32Const, 20, kExprI32Sub, kExprThrow, ]) .exportFunc() return builder.instantiate(); })(); // Check the test_throw exists. assertFalse(test_throw === undefined); assertFalse(test_throw === null); assertFalse(test_throw === 0); assertEquals("object", typeof test_throw.exports); assertEquals("function", typeof test_throw.exports.throw_expr_with_params); assertEquals(1, test_throw.exports.throw_param_if_not_zero(0)); assertWasmThrows( -8, function() { test_throw.exports.throw_expr_with_params(1.5, 2.5, 4); }); assertWasmThrows( 12, function() { test_throw.exports.throw_expr_with_params(5.7, 2.5, 4); }); // Now that we know throwing works, we test catching the exceptions we raise. var test_catch = (function () { var builder = new WasmModuleBuilder(); // Helper function for throwing from js. It is imported by the Wasm module // as throw_i. function throw_value(value) { throw value; } var sig_index = builder.addType(kSig_v_i); var kJSThrowI = builder.addImport("", "throw_i", sig_index); // Helper function that throws a string. Wasm should not catch it. function throw_string() { throw "use wasm;"; } sig_index = builder.addType(kSig_v_v); var kJSThrowString = builder.addImport("", "throw_string", sig_index); // Helper function that throws undefined. Wasm should not catch it. function throw_undefined() { throw undefined; } var kJSThrowUndefined = builder.addImport("", "throw_undefined", sig_index); // Helper function that throws an fp. Wasm should not catch it. function throw_fp() { throw 10.5; } var kJSThrowFP = builder.addImport("", "throw_fp", sig_index); // Helper function that throws a large number. Wasm should not catch it. function throw_large() { throw 1e+28; } var kJSThrowLarge = builder.addImport("", "throw_large", sig_index); // Helper function for throwing from WebAssembly. var kWasmThrowFunction = builder.addFunction("throw", kSig_v_i) .addBody([ kExprGetLocal, 0, kExprThrow ]) .index; // Scenario 1: Throw and catch appear on the same function. This should // happen in case of inlining, for example. builder.addFunction("same_scope", kSig_i_i) .addBody([ kExprTry, kWasmI32, kExprGetLocal, 0, kExprI32Const, 0, kExprI32Ne, kExprIf, kWasmStmt, kExprGetLocal, 0, kExprThrow, kExprUnreachable, kExprEnd, kExprI32Const, 63, kExprCatch, 1, kExprGetLocal, 1, kExprEnd ]) .addLocals({i32_count: 1}) .exportFunc() .index; builder.addFunction("same_scope_ignore", kSig_i_i) .addBody([ kExprTry, kWasmI32, kExprGetLocal, 0, kExprThrow, kExprUnreachable, kExprCatch, 1, kExprGetLocal, 0, kExprEnd, ]) .addLocals({i32_count: 1}) .exportFunc(); builder.addFunction("same_scope_multiple", kSig_i_i) // path = 0; // // try { // try { // try { // if (p == 1) // throw 1; // path |= 2 // } catch (v) { // path |= v | 4; // throw path; // } // if (p == 2) // throw path|8; // path |= 16; // } catch (v) { // path |= v | 32; // throw path; // } // if (p == 3) // throw path|64; // path |= 128 // } catch (v) { // path |= v | 256; // } // // return path; // // p == 1 -> path == 293 // p == 2 -> path == 298 // p == 3 -> path == 338 // else -> path == 146 .addBody([ kExprTry, kWasmI32, kExprTry, kWasmI32, kExprTry, kWasmI32, kExprGetLocal, 0, kExprI32Const, 1, kExprI32Eq, kExprIf, kWasmStmt, kExprI32Const, 1, kExprThrow, kExprUnreachable, kExprEnd, kExprI32Const, 2, kExprCatch, 1, kExprGetLocal, 1, kExprI32Const, 4, kExprI32Ior, kExprThrow, kExprUnreachable, kExprEnd, kExprTeeLocal, 2, kExprGetLocal, 0, kExprI32Const, 2, kExprI32Eq, kExprIf, kWasmStmt, kExprGetLocal, 2, kExprI32Const, 8, kExprI32Ior, kExprThrow, kExprUnreachable, kExprEnd, kExprI32Const, 16, kExprI32Ior, kExprCatch, 1, kExprGetLocal, 1, kExprI32Const, 32, kExprI32Ior, kExprThrow, kExprUnreachable, kExprEnd, kExprTeeLocal, 2, kExprGetLocal, 0, kExprI32Const, 3, kExprI32Eq, kExprIf, kWasmStmt, kExprGetLocal, 2, kExprI32Const, / *64=* / 192, 0, kExprI32Ior, kExprThrow, kExprUnreachable, kExprEnd, kExprI32Const, / *128=* / 128, 1, kExprI32Ior, kExprCatch, 1, kExprGetLocal, 1, kExprI32Const, / *256=* / 128, 2, kExprI32Ior, kExprEnd, ]) .addLocals({i32_count: 2}) .exportFunc(); // Scenario 2: Catches an exception raised from the direct callee. var kFromDirectCallee = builder.addFunction("from_direct_callee", kSig_i_i) .addBody([ kExprTry, kWasmI32, kExprGetLocal, 0, kExprCallFunction, kWasmThrowFunction, kExprI32Const, / *-1=* / 127, kExprCatch, 1, kExprGetLocal, 1, kExprEnd ]) .addLocals({i32_count: 1}) .exportFunc() .index; // Scenario 3: Catches an exception raised from an indirect callee. var kFromIndirectCalleeHelper = kFromDirectCallee + 1; builder.addFunction("from_indirect_callee_helper", kSig_v_ii) .addBody([ kExprGetLocal, 0, kExprI32Const, 0, kExprI32GtS, kExprIf, kWasmStmt, kExprGetLocal, 0, kExprI32Const, 1, kExprI32Sub, kExprGetLocal, 1, kExprI32Const, 1, kExprI32Sub, kExprCallFunction, kFromIndirectCalleeHelper, kExprEnd, kExprGetLocal, 1, kExprCallFunction, kWasmThrowFunction, ]); builder.addFunction("from_indirect_callee", kSig_i_i) .addBody([ kExprTry, kWasmI32, kExprGetLocal, 0, kExprI32Const, 0, kExprCallFunction, kFromIndirectCalleeHelper, kExprI32Const, / *-1=* / 127, kExprCatch, 1, kExprGetLocal, 1, kExprEnd ]) .addLocals({i32_count: 1}) .exportFunc(); // Scenario 4: Catches an exception raised in JS. builder.addFunction("from_js", kSig_i_i) .addBody([ kExprTry, kWasmI32, kExprGetLocal, 0, kExprCallFunction, kJSThrowI, kExprI32Const, / *-1=* / 127, kExprCatch, 1, kExprGetLocal, 1, kExprEnd, ]) .addLocals({i32_count: 1}) .exportFunc(); // Scenario 5: Does not catch an exception raised in JS if it is not a // number. builder.addFunction("string_from_js", kSig_v_v) .addBody([ kExprCallFunction, kJSThrowString ]) .exportFunc(); builder.addFunction("fp_from_js", kSig_v_v) .addBody([ kExprCallFunction, kJSThrowFP ]) .exportFunc(); builder.addFunction("large_from_js", kSig_v_v) .addBody([ kExprCallFunction, kJSThrowLarge ]) .exportFunc(); builder.addFunction("undefined_from_js", kSig_v_v) .addBody([ kExprCallFunction, kJSThrowUndefined ]) .exportFunc(); return builder.instantiate({"": { throw_i: throw_value, throw_string: throw_string, throw_fp: throw_fp, throw_large, throw_large, throw_undefined: throw_undefined }}); })(); // Check the test_catch exists. assertFalse(test_catch === undefined); assertFalse(test_catch === null); assertFalse(test_catch === 0); assertEquals("object", typeof test_catch.exports); assertEquals("function", typeof test_catch.exports.same_scope); assertEquals("function", typeof test_catch.exports.same_scope_ignore); assertEquals("function", typeof test_catch.exports.same_scope_multiple); assertEquals("function", typeof test_catch.exports.from_direct_callee); assertEquals("function", typeof test_catch.exports.from_indirect_callee); assertEquals("function", typeof test_catch.exports.from_js); assertEquals("function", typeof test_catch.exports.string_from_js); assertEquals(63, test_catch.exports.same_scope(0)); assertEquals(1024, test_catch.exports.same_scope(1024)); assertEquals(-3, test_catch.exports.same_scope(-3)); assertEquals(-1, test_catch.exports.same_scope_ignore(-1)); assertEquals(1, test_catch.exports.same_scope_ignore(1)); assertEquals(0x7FFFFFFF, test_catch.exports.same_scope_ignore(0x7FFFFFFF)); assertEquals(1024, test_catch.exports.same_scope_ignore(1024)); assertEquals(-1, test_catch.exports.same_scope_ignore(-1)); assertEquals(293, test_catch.exports.same_scope_multiple(1)); assertEquals(298, test_catch.exports.same_scope_multiple(2)); assertEquals(338, test_catch.exports.same_scope_multiple(3)); assertEquals(146, test_catch.exports.same_scope_multiple(0)); assertEquals(-10024, test_catch.exports.from_direct_callee(-10024)); assertEquals(3334333, test_catch.exports.from_direct_callee(3334333)); assertEquals(-1, test_catch.exports.from_direct_callee(0xFFFFFFFF)); assertEquals(0x7FFFFFFF, test_catch.exports.from_direct_callee(0x7FFFFFFF)); assertEquals(-10, test_catch.exports.from_indirect_callee(10)); assertEquals(-77, test_catch.exports.from_indirect_callee(77)); assertEquals(10, test_catch.exports.from_js(10)); assertEquals(-10, test_catch.exports.from_js(-10)); assertThrowsEquals(test_catch.exports.string_from_js, "use wasm;"); assertThrowsEquals(test_catch.exports.large_from_js, 1e+28); assertThrowsEquals(test_catch.exports.undefined_from_js, undefined); */