diff --git a/include/chaiscript/language/chaiscript_common.hpp b/include/chaiscript/language/chaiscript_common.hpp index fb75a933..6c99a5d7 100644 --- a/include/chaiscript/language/chaiscript_common.hpp +++ b/include/chaiscript/language/chaiscript_common.hpp @@ -11,6 +11,7 @@ #define CHAISCRIPT_COMMON_HPP_ #include +#include #include #include #include @@ -296,7 +297,7 @@ namespace chaiscript { }; /// Errors generated during parsing or evaluation - struct eval_error : std::runtime_error { + struct eval_error : std::runtime_error, std::nested_exception { std::string reason; File_Position start_position; std::string filename; diff --git a/include/chaiscript/language/chaiscript_engine.hpp b/include/chaiscript/language/chaiscript_engine.hpp index 4afd0449..0e2c38b2 100644 --- a/include/chaiscript/language/chaiscript_engine.hpp +++ b/include/chaiscript/language/chaiscript_engine.hpp @@ -695,7 +695,7 @@ namespace chaiscript { if (t_handler) { t_handler->handle(bv, m_engine); } - throw; + throw exception::eval_error("Exception thrown during evaluation"); } } diff --git a/unittests/compiled_tests.cpp b/unittests/compiled_tests.cpp index 7b601b15..037f6b44 100644 --- a/unittests/compiled_tests.cpp +++ b/unittests/compiled_tests.cpp @@ -166,9 +166,15 @@ TEST_CASE("Generic exception handling with C++") { try { chai.eval("throw(runtime_error(\"error\"));"); REQUIRE(false); - } catch (const chaiscript::Boxed_Value &bv) { - const std::exception &e = chai.boxed_cast(bv); - CHECK(e.what() == std::string("error")); + } catch (const chaiscript::exception::eval_error &ee) { + REQUIRE(ee.nested_ptr() != nullptr); + try { + ee.rethrow_nested(); + REQUIRE(false); + } catch (const chaiscript::Boxed_Value &bv) { + const std::exception &e = chai.boxed_cast(bv); + CHECK(e.what() == std::string("error")); + } } } @@ -194,6 +200,62 @@ TEST_CASE("Throw int or double") { } } +TEST_CASE("eval_error derives from std::nested_exception") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + static_assert(std::is_base_of_v, + "eval_error must derive from std::nested_exception"); + + try { + chai.eval("throw(runtime_error(\"inner error\"));"); + REQUIRE(false); + } catch (const chaiscript::exception::eval_error &ee) { + CHECK(ee.nested_ptr() != nullptr); + try { + ee.rethrow_nested(); + REQUIRE(false); + } catch (const chaiscript::Boxed_Value &bv) { + const std::exception &nested = chai.boxed_cast(bv); + CHECK(nested.what() == std::string("inner error")); + } + } +} + +TEST_CASE("eval_error wraps non-eval exceptions with nested exception") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + try { + chai.eval("throw(42);"); + REQUIRE(false); + } catch (const chaiscript::exception::eval_error &ee) { + CHECK(ee.nested_ptr() != nullptr); + try { + ee.rethrow_nested(); + REQUIRE(false); + } catch (const chaiscript::Boxed_Value &) { + CHECK(true); + } + } +} + +TEST_CASE("eval_error includes nested exception for script-thrown exceptions") { + chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser()); + + try { + chai.eval("def foo() { throw(runtime_error(\"from foo\")); } \n foo();"); + REQUIRE(false); + } catch (const chaiscript::exception::eval_error &ee) { + CHECK(ee.nested_ptr() != nullptr); + try { + ee.rethrow_nested(); + REQUIRE(false); + } catch (const chaiscript::Boxed_Value &bv) { + const std::exception &nested = chai.boxed_cast(bv); + CHECK(nested.what() == std::string("from foo")); + } + } +} + TEST_CASE("Deduction of pointer return types") { int val = 5; int *val_ptr = &val; @@ -259,10 +321,8 @@ TEST_CASE("Throw unhandled type") { REQUIRE(false); } catch (float) { REQUIRE(false); - } catch (const std::exception &) { - REQUIRE(false); - } catch (const chaiscript::Boxed_Value &) { - REQUIRE(true); + } catch (const chaiscript::exception::eval_error &ee) { + REQUIRE(ee.nested_ptr() != nullptr); } }