Address review: add call stack and filename to wrapped script exceptions

- Catch Boxed_Value in AST_Node_Impl::eval() and wrap in eval_error so
  call stack accumulates as the exception propagates through the AST chain
- Add eval_error constructor accepting filename + Boxed_Value
- Populate filename on wrapped eval_error in chaiscript_engine::eval()
- Extract original boxed value in Try_AST_Node for script try/catch semantics
- Unwrap boxed value in internal_eval/internal_eval_file to preserve
  script-level exception identity across nested eval() calls
- Add tests for call stack presence, filename population, and
  exception_specification backward compatibility

Requested by @lefticus in PR #682 review.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
leftibot 2026-04-15 15:14:32 -06:00
parent 754304314a
commit 5cebd2cd22
4 changed files with 71 additions and 3 deletions

View File

@ -341,11 +341,18 @@ namespace chaiscript {
}
eval_error(const std::string &t_why, Boxed_Value t_bv) noexcept
: std::runtime_error("Error: \"" + t_why + "\" ")
: std::runtime_error(format_why(t_why) + " " + format_filename("__EVAL__"))
, reason(t_why)
, m_boxed_value(std::move(t_bv)) {
}
eval_error(const std::string &t_why, const std::string &t_fname, Boxed_Value t_bv) noexcept
: std::runtime_error(format_why(t_why) + " " + format_filename(t_fname))
, reason(t_why)
, filename(t_fname)
, m_boxed_value(std::move(t_bv)) {
}
eval_error(const eval_error &) = default;
bool has_boxed_value() const noexcept { return !m_boxed_value.is_undef(); }

View File

@ -100,6 +100,9 @@ namespace chaiscript {
} catch (const exception::file_not_found_error &) {
// failed to load, try the next path
} catch (const exception::eval_error &t_ee) {
if (t_ee.has_boxed_value()) {
throw Boxed_Value(t_ee.boxed_value());
}
throw Boxed_Value(t_ee);
}
}
@ -113,6 +116,9 @@ namespace chaiscript {
try {
return do_eval(t_e, "__EVAL__", true);
} catch (const exception::eval_error &t_ee) {
if (t_ee.has_boxed_value()) {
throw Boxed_Value(t_ee.boxed_value());
}
throw Boxed_Value(t_ee);
}
}
@ -691,11 +697,21 @@ namespace chaiscript {
eval(const std::string &t_input, const Exception_Handler &t_handler = Exception_Handler(), const std::string &t_filename = "__EVAL__") {
try {
return do_eval(t_input, t_filename);
} catch (exception::eval_error &ee) {
if (ee.has_boxed_value()) {
if (ee.filename.empty()) {
ee.filename = t_filename;
}
if (t_handler) {
t_handler->handle(ee.boxed_value(), m_engine);
}
}
throw;
} catch (Boxed_Value &bv) {
if (t_handler) {
t_handler->handle(bv, m_engine);
}
throw exception::eval_error("Exception thrown during evaluation", bv);
throw exception::eval_error("Exception thrown during evaluation", t_filename, bv);
}
}

View File

@ -142,6 +142,10 @@ namespace chaiscript {
} catch (exception::eval_error &ee) {
ee.call_stack.push_back(*this);
throw;
} catch (const Boxed_Value &bv) {
exception::eval_error ee("Exception thrown during evaluation", bv);
ee.call_stack.push_back(*this);
throw ee;
}
}
@ -1358,7 +1362,11 @@ namespace chaiscript {
try {
retval = this->children[0]->eval(t_ss);
} catch (const exception::eval_error &e) {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
if (e.has_boxed_value()) {
retval = handle_exception(t_ss, e.boxed_value());
} else {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
}
} catch (const std::runtime_error &e) {
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
} catch (const std::out_of_range &e) {

View File

@ -320,6 +320,43 @@ TEST_CASE("eval_error without boxed_value has_boxed_value returns false") {
CHECK_FALSE(ee.has_boxed_value());
}
TEST_CASE("eval_error wrapping script throw includes call stack") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("def foo() { throw(42); } \n foo();");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.has_boxed_value());
CHECK_FALSE(ee.call_stack.empty());
const auto pretty = ee.pretty_print();
CHECK(pretty.find("foo") != std::string::npos);
}
}
TEST_CASE("eval_error wrapping script throw includes filename") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("throw(99);", chaiscript::Exception_Handler(), "test_script.chai");
REQUIRE(false);
} catch (const chaiscript::exception::eval_error &ee) {
REQUIRE(ee.has_boxed_value());
CHECK(ee.filename == "test_script.chai");
}
}
TEST_CASE("exception_specification still auto-unboxes for backward compatibility") {
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
try {
chai.eval("throw(1)", chaiscript::exception_specification<int>());
REQUIRE(false);
} catch (const int e) {
CHECK(e == 1);
}
}
TEST_CASE("Deduction of pointer return types") {
int val = 5;
int *val_ptr = &val;