From 2c80c2a55af259abe79e9c531ca964d1f41c8634 Mon Sep 17 00:00:00 2001 From: leftibot Date: Mon, 13 Apr 2026 19:02:32 -0600 Subject: [PATCH] Fix #63: Derive eval_error from std::nested_exception and wrap thrown exceptions eval_error now inherits from both std::runtime_error and std::nested_exception, enabling users to access the original exception via rethrow_nested() or nested_ptr(). The engine's eval() method wraps uncaught Boxed_Value exceptions in eval_error, nesting the original Boxed_Value so it can be recovered. exception_specification continues to work for typed exception unboxing. Co-Authored-By: Claude Opus 4.6 (1M context) --- .../chaiscript/language/chaiscript_common.hpp | 3 +- .../chaiscript/language/chaiscript_engine.hpp | 2 +- unittests/compiled_tests.cpp | 74 +++++++++++++++++-- 3 files changed, 70 insertions(+), 9 deletions(-) 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); } }