diff --git a/CMakeLists.txt b/CMakeLists.txt index 240426c363a..fd3c94035a5 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -580,6 +580,11 @@ if(EMSCRIPTEN) # Avoid catching exit as that can confuse error reporting in Node, # see https://github.com/emscripten-core/emscripten/issues/17228 target_link_libraries(binaryen_wasm PRIVATE "-sNODEJS_CATCH_EXIT=0") + # Don't exit the process on a fatal error, instead throw, so that JS can + # catch. + add_compile_flag("-DTHROW_ON_FATAL") + # Add support for printing C++ exceptions from JS. + target_link_libraries(binaryen_wasm PRIVATE "-sEXPORT_EXCEPTION_HANDLING_HELPERS") install(TARGETS binaryen_wasm DESTINATION ${CMAKE_INSTALL_BINDIR}) # binaryen.js JavaScript variant @@ -632,6 +637,8 @@ if(EMSCRIPTEN) # Avoid catching exit as that can confuse error reporting in Node, # see https://github.com/emscripten-core/emscripten/issues/17228 target_link_libraries(binaryen_js PRIVATE "-sNODEJS_CATCH_EXIT=0") + add_compile_flag("-DTHROW_ON_FATAL") + target_link_libraries(binaryen_js PRIVATE "-sEXPORT_EXCEPTION_HANDLING_HELPERS") install(TARGETS binaryen_js DESTINATION ${CMAKE_INSTALL_BINDIR}) endif() diff --git a/src/js/binaryen.js-post.js b/src/js/binaryen.js-post.js index 948dec21d92..0541b171f22 100644 --- a/src/js/binaryen.js-post.js +++ b/src/js/binaryen.js-post.js @@ -3254,6 +3254,27 @@ Module['emitText'] = function(expr) { return ret; }; +// Calls a function, wrapping it in error handling code so that if it hits a +// fatal error, we throw a JS exception (which JS can handle) rather than +// abort the entire process (which would not be a friendly behavior for a +// library like binaryen.js). +function handleFatalError(func) { + try { + return func(); + } catch (e) { + // C++ exceptions are thrown as pointers (numbers). + if (typeof e === 'number') { + // Fatal errors begin with that prefix. Strip it out, and the newline. + var [_, message] = getExceptionMessage(e); + if (message?.startsWith('Fatal: ')) { + throw new Error(message.substr(7).trim()); + } + } + // Rethrow anything else. + throw e; + } +} + // Parses a binary to a module // If building with Emscripten ASSERTIONS, there is a property added to @@ -3264,7 +3285,7 @@ Object.defineProperty(Module, 'readBinary', { writable: true }); Module['readBinary'] = function(data) { const buffer = _malloc(data.length); HEAP8.set(data, buffer); - const ptr = Module['_BinaryenModuleRead'](buffer, data.length); + const ptr = handleFatalError(() => Module['_BinaryenModuleRead'](buffer, data.length)); _free(buffer); return wrapModule(ptr); }; @@ -3273,7 +3294,7 @@ Module['readBinary'] = function(data) { Module['parseText'] = function(text) { const buffer = _malloc(text.length + 1); stringToAscii(text, buffer); - const ptr = Module['_BinaryenModuleParse'](buffer); + const ptr = handleFatalError(() => Module['_BinaryenModuleParse'](buffer)); _free(buffer); return wrapModule(ptr); }; diff --git a/test/binaryen.js/errors.js b/test/binaryen.js/errors.js new file mode 100644 index 00000000000..096863591cb --- /dev/null +++ b/test/binaryen.js/errors.js @@ -0,0 +1,17 @@ +// Parse invalid text and get a JS exception. +var caughtAsExpected = false; +try { + console.log('parsing invalid text...'); + binaryen.parseText(`(module + ;; error on next line + (func $foo (param__error $x i32)) + )`) + console.log('no error - invalid'); +} catch (e) { + assert(e.message === '3:16: error: unrecognized instruction'); + caughtAsExpected = true; +} +assert(caughtAsExpected); + +console.log('success.'); + diff --git a/test/binaryen.js/errors.js.txt b/test/binaryen.js/errors.js.txt new file mode 100644 index 00000000000..b32bb74d20e --- /dev/null +++ b/test/binaryen.js/errors.js.txt @@ -0,0 +1 @@ +success.