Fix #678: Add WASM exception support to Emscripten build (#689)

ChaiScript relies heavily on C++ exceptions for error propagation, but
the Emscripten build was missing the -fwasm-exceptions flag. Without it,
any C++ exception in the WASM module causes an abort instead of being
catchable by JavaScript. Added -fwasm-exceptions as both a compile and
link option, and added a regression test verifying exception propagation
through the eval wrapper functions.

Co-authored-by: leftibot <leftibot@users.noreply.github.com>
Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
leftibot 2026-04-13 22:40:26 -06:00 committed by GitHub
parent e61be76531
commit 0fd9cab654
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3 changed files with 81 additions and 0 deletions

View File

@ -440,6 +440,10 @@ if(BUILD_TESTING)
target_link_libraries(emscripten_eval_test ${LIBS}) target_link_libraries(emscripten_eval_test ${LIBS})
add_test(NAME Emscripten_Eval_Test COMMAND emscripten_eval_test) add_test(NAME Emscripten_Eval_Test COMMAND emscripten_eval_test)
add_executable(emscripten_exception_test unittests/emscripten_exception_test.cpp)
target_link_libraries(emscripten_exception_test ${LIBS})
add_test(NAME Emscripten_Exception_Test COMMAND emscripten_exception_test)
add_executable(threading_config_test unittests/threading_config_test.cpp) add_executable(threading_config_test unittests/threading_config_test.cpp)
target_link_libraries(threading_config_test ${LIBS}) target_link_libraries(threading_config_test ${LIBS})
add_test(NAME Threading_Config_Test COMMAND threading_config_test) add_test(NAME Threading_Config_Test COMMAND threading_config_test)

View File

@ -17,9 +17,14 @@ add_definitions(-DCHAISCRIPT_NO_THREADS -DCHAISCRIPT_NO_DYNLOAD)
add_executable(chaiscript chaiscript_em.cpp) add_executable(chaiscript chaiscript_em.cpp)
target_include_directories(chaiscript PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include) target_include_directories(chaiscript PRIVATE ${CMAKE_CURRENT_SOURCE_DIR}/../include)
# Enable WASM exception handling ChaiScript relies on C++ exceptions for
# error propagation; without this flag exceptions cause an abort in WASM.
target_compile_options(chaiscript PRIVATE -fwasm-exceptions)
# Emscripten link flags: enable embind, allow memory growth, export as ES module-compatible # Emscripten link flags: enable embind, allow memory growth, export as ES module-compatible
target_link_options(chaiscript PRIVATE target_link_options(chaiscript PRIVATE
--bind --bind
-fwasm-exceptions
-sALLOW_MEMORY_GROWTH=1 -sALLOW_MEMORY_GROWTH=1
-sEXPORT_ES6=0 -sEXPORT_ES6=0
-sMODULARIZE=0 -sMODULARIZE=0

View File

@ -0,0 +1,72 @@
// Test that validates exception propagation through the Emscripten eval wrapper.
// Without proper exception support flags (-fwasm-exceptions) in the WASM build,
// C++ exceptions would cause an abort instead of being catchable.
#ifndef CHAISCRIPT_NO_THREADS
#define CHAISCRIPT_NO_THREADS
#endif
#ifndef CHAISCRIPT_NO_DYNLOAD
#define CHAISCRIPT_NO_DYNLOAD
#endif
#include <chaiscript/chaiscript.hpp>
#include "../emscripten/chaiscript_eval.hpp"
#include <cassert>
#include <iostream>
#include <stdexcept>
#include <string>
int main() {
// Verify that ChaiScript evaluation errors propagate as exceptions
// through the eval wrapper functions. In WASM builds without exception
// support, these would abort instead of throwing.
bool caught = false;
// Test 1: eval with undefined variable should throw
caught = false;
try {
chaiscript_eval("this_variable_does_not_exist");
} catch (const chaiscript::exception::eval_error &) {
caught = true;
}
assert(caught && "eval of undefined variable must throw eval_error");
// Test 2: evalString with a type mismatch should throw
caught = false;
try {
chaiscript_eval_string("1 + 2");
} catch (const chaiscript::exception::bad_boxed_cast &) {
caught = true;
}
assert(caught && "evalString with non-string result must throw bad_boxed_cast");
// Test 3: evalInt with invalid syntax should throw
caught = false;
try {
chaiscript_eval_int("def {}");
} catch (const chaiscript::exception::eval_error &) {
caught = true;
}
assert(caught && "evalInt with syntax error must throw eval_error");
// Test 4: eval with throw statement should propagate exception
caught = false;
try {
chaiscript_eval("throw(\"user exception\")");
} catch (const chaiscript::Boxed_Value &) {
caught = true;
} catch (...) {
caught = true;
}
assert(caught && "ChaiScript throw must propagate as an exception");
// Test 5: Verify normal operation still works after caught exceptions
chaiscript_eval("var post_exception_test = 100");
const int result = chaiscript_eval_int("post_exception_test");
assert(result == 100 && "normal eval must work after caught exceptions");
std::cout << "All emscripten exception tests passed.\n";
return 0;
}