Skip to content
Prev Previous commit
Next Next commit
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>
  • Loading branch information
leftibot and claude committed Apr 15, 2026
commit 5cebd2cd2213f0a45dd212bd608affa4a6c4b306
9 changes: 8 additions & 1 deletion include/chaiscript/language/chaiscript_common.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -341,8 +341,15 @@ 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)) {
}

Expand Down
18 changes: 17 additions & 1 deletion include/chaiscript/language/chaiscript_engine.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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);
}
}
Expand All @@ -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);
}
}
Expand Down Expand Up @@ -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);
}
}

Expand Down
10 changes: 9 additions & 1 deletion include/chaiscript/language/chaiscript_eval.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}

Expand Down Expand Up @@ -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) {
Expand Down
37 changes: 37 additions & 0 deletions unittests/compiled_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading