mirror of
https://github.com/ChaiScript/ChaiScript.git
synced 2026-04-30 19:09:26 +08:00
When all typed catch blocks failed to match a thrown exception's type,
handle_exception() would silently discard the exception and return a
default-constructed Boxed_Value instead of propagating it. This meant
code like `try { throw(42) } catch(string e) { }` would swallow the
int exception rather than letting it propagate to an outer handler.
The fix adds an explicit re-throw when no catch block matches, and
restructures eval_internal() with a nested try/catch to ensure the
finally block still executes before the unhandled exception propagates.
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:
parent
362e93fb29
commit
d4c5bdb3e4
@ -1305,6 +1305,7 @@ namespace chaiscript {
|
||||
|
||||
Boxed_Value handle_exception(const chaiscript::detail::Dispatch_State &t_ss, const Boxed_Value &t_except) const {
|
||||
Boxed_Value retval;
|
||||
bool handled = false;
|
||||
|
||||
size_t end_point = this->children.size();
|
||||
if (this->children.back()->identifier == AST_Node_Type::Finally) {
|
||||
@ -1318,6 +1319,7 @@ namespace chaiscript {
|
||||
if (catch_block.children.size() == 1) {
|
||||
// No variable capture
|
||||
retval = catch_block.children[0]->eval(t_ss);
|
||||
handled = true;
|
||||
break;
|
||||
} else if (catch_block.children.size() == 2 || catch_block.children.size() == 3) {
|
||||
const auto name = Arg_List_AST_Node<T>::get_arg_name(*catch_block.children[0]);
|
||||
@ -1331,17 +1333,19 @@ namespace chaiscript {
|
||||
if (catch_block.children.size() == 2) {
|
||||
// Variable capture
|
||||
retval = catch_block.children[1]->eval(t_ss);
|
||||
handled = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (this->children.back()->identifier == AST_Node_Type::Finally) {
|
||||
this->children.back()->children[0]->eval(t_ss);
|
||||
}
|
||||
throw exception::eval_error("Internal error: catch block size unrecognized");
|
||||
}
|
||||
}
|
||||
|
||||
if (!handled) {
|
||||
throw;
|
||||
}
|
||||
|
||||
return retval;
|
||||
}
|
||||
|
||||
@ -1351,17 +1355,19 @@ namespace chaiscript {
|
||||
chaiscript::eval::detail::Scope_Push_Pop spp(t_ss);
|
||||
|
||||
try {
|
||||
retval = this->children[0]->eval(t_ss);
|
||||
} catch (const exception::eval_error &e) {
|
||||
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) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (const std::exception &e) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (Boxed_Value &e) {
|
||||
retval = handle_exception(t_ss, e);
|
||||
try {
|
||||
retval = this->children[0]->eval(t_ss);
|
||||
} catch (const exception::eval_error &e) {
|
||||
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) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (const std::exception &e) {
|
||||
retval = handle_exception(t_ss, Boxed_Value(std::ref(e)));
|
||||
} catch (Boxed_Value &e) {
|
||||
retval = handle_exception(t_ss, e);
|
||||
}
|
||||
} catch (...) {
|
||||
if (this->children.back()->identifier == AST_Node_Type::Finally) {
|
||||
this->children.back()->children[0]->eval(t_ss);
|
||||
|
||||
@ -1782,3 +1782,191 @@ TEST_CASE("eval_error with AST_Node_Trace call stack compiles in C++20") {
|
||||
(void)stack;
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("C++ runtime_error thrown from registered function is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.add(chaiscript::fun([]() -> int { throw std::runtime_error("cpp_runtime_error"); }), "cpp_throw_runtime");
|
||||
|
||||
CHECK(chai.eval<bool>(R"(
|
||||
var caught = false
|
||||
try {
|
||||
cpp_throw_runtime()
|
||||
}
|
||||
catch(e) {
|
||||
caught = true
|
||||
}
|
||||
caught
|
||||
)") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("C++ out_of_range thrown from registered function is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.add(chaiscript::fun([]() -> int { throw std::out_of_range("cpp_out_of_range"); }), "cpp_throw_oor");
|
||||
|
||||
CHECK(chai.eval<bool>(R"(
|
||||
var caught = false
|
||||
try {
|
||||
cpp_throw_oor()
|
||||
}
|
||||
catch(e) {
|
||||
caught = true
|
||||
}
|
||||
caught
|
||||
)") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("C++ logic_error thrown from registered function is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.add(chaiscript::fun([]() -> int { throw std::logic_error("cpp_logic_error"); }), "cpp_throw_logic");
|
||||
|
||||
CHECK(chai.eval<bool>(R"(
|
||||
var caught = false
|
||||
try {
|
||||
cpp_throw_logic()
|
||||
}
|
||||
catch(e) {
|
||||
caught = true
|
||||
}
|
||||
caught
|
||||
)") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript throw(int) propagates as Boxed_Value to C++") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
try {
|
||||
chai.eval("throw(42)");
|
||||
REQUIRE(false);
|
||||
} catch (chaiscript::Boxed_Value &bv) {
|
||||
CHECK(chaiscript::boxed_cast<int>(bv) == 42);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("ChaiScript throw(string) propagates as Boxed_Value to C++") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
try {
|
||||
chai.eval(R"(throw("error msg"))");
|
||||
REQUIRE(false);
|
||||
} catch (chaiscript::Boxed_Value &bv) {
|
||||
CHECK(chaiscript::boxed_cast<std::string>(bv) == "error msg");
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE("Typed catch with no match propagates exception") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
CHECK_THROWS_AS(chai.eval(R"(
|
||||
try {
|
||||
throw(42)
|
||||
}
|
||||
catch(string e) {
|
||||
// wrong type, should not match
|
||||
}
|
||||
)"), chaiscript::Boxed_Value);
|
||||
}
|
||||
|
||||
TEST_CASE("Typed catch with no match still runs finally block") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
CHECK_THROWS_AS(chai.eval(R"(
|
||||
var finally_ran = false
|
||||
try {
|
||||
throw(42)
|
||||
}
|
||||
catch(string e) {
|
||||
// wrong type
|
||||
}
|
||||
finally {
|
||||
finally_ran = true
|
||||
}
|
||||
)"), chaiscript::Boxed_Value);
|
||||
|
||||
CHECK(chai.eval<bool>("finally_ran") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("Multiple C++ exception types from registered functions") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
chai.add(chaiscript::fun([](int which) -> int {
|
||||
switch (which) {
|
||||
case 0: throw std::runtime_error("runtime");
|
||||
case 1: throw std::out_of_range("range");
|
||||
case 2: throw std::logic_error("logic");
|
||||
default: return which;
|
||||
}
|
||||
}), "cpp_multi_throw");
|
||||
|
||||
CHECK(chai.eval<int>(R"(
|
||||
var catch_count = 0
|
||||
for (var i = 0; i < 3; ++i) {
|
||||
try {
|
||||
cpp_multi_throw(i)
|
||||
}
|
||||
catch(e) {
|
||||
catch_count = catch_count + 1
|
||||
}
|
||||
}
|
||||
catch_count
|
||||
)") == 3);
|
||||
|
||||
CHECK(chai.eval<int>("cpp_multi_throw(5)") == 5);
|
||||
}
|
||||
|
||||
TEST_CASE("Exception from C++ binary operator is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
struct ThrowingType {
|
||||
int value;
|
||||
};
|
||||
|
||||
chai.add(chaiscript::user_type<ThrowingType>(), "ThrowingType");
|
||||
chai.add(chaiscript::constructor<ThrowingType(int)>(), "ThrowingType");
|
||||
chai.add(chaiscript::fun([](const ThrowingType &, const ThrowingType &) -> ThrowingType {
|
||||
throw std::runtime_error("cpp operator+ threw");
|
||||
}), "+");
|
||||
|
||||
CHECK(chai.eval<bool>(R"(
|
||||
var caught = false
|
||||
try {
|
||||
var a = ThrowingType(1)
|
||||
var b = ThrowingType(2)
|
||||
var c = a + b
|
||||
}
|
||||
catch(e) {
|
||||
caught = true
|
||||
}
|
||||
caught
|
||||
)") == true);
|
||||
}
|
||||
|
||||
TEST_CASE("Exception from C++ [] operator is catchable in ChaiScript") {
|
||||
chaiscript::ChaiScript_Basic chai(create_chaiscript_stdlib(), create_chaiscript_parser());
|
||||
|
||||
struct IndexableType {
|
||||
int value;
|
||||
};
|
||||
|
||||
chai.add(chaiscript::user_type<IndexableType>(), "IndexableType");
|
||||
chai.add(chaiscript::constructor<IndexableType(int)>(), "IndexableType");
|
||||
chai.add(chaiscript::fun([](const IndexableType &, int idx) -> int {
|
||||
if (idx < 0) { throw std::out_of_range("negative index"); }
|
||||
return idx;
|
||||
}), "[]");
|
||||
|
||||
CHECK(chai.eval<int>("var obj = IndexableType(0); obj[5]") == 5);
|
||||
|
||||
CHECK(chai.eval<bool>(R"(
|
||||
var caught = false
|
||||
try {
|
||||
var x = obj[-1]
|
||||
}
|
||||
catch(e) {
|
||||
caught = true
|
||||
}
|
||||
caught
|
||||
)") == true);
|
||||
}
|
||||
|
||||
862
unittests/exception_comprehensive.chai
Normal file
862
unittests/exception_comprehensive.chai
Normal file
@ -0,0 +1,862 @@
|
||||
// Comprehensive exception throwing and catching tests
|
||||
// Tests throw/catch from various contexts: operators, functions, lambdas,
|
||||
// methods, [] operator, nested scopes, and with various value types.
|
||||
|
||||
// ============================================================
|
||||
// Section 1: Throwing and catching different value types
|
||||
// ============================================================
|
||||
|
||||
// Throw int
|
||||
try {
|
||||
throw(42)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal(42, e)
|
||||
}
|
||||
|
||||
// Throw string
|
||||
try {
|
||||
throw("error message")
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("error message", e)
|
||||
}
|
||||
|
||||
// Throw double
|
||||
try {
|
||||
throw(3.14)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal(3.14, e)
|
||||
}
|
||||
|
||||
// Throw bool
|
||||
try {
|
||||
throw(true)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal(true, e)
|
||||
}
|
||||
|
||||
// Throw a Vector (via named variable to preserve mutability)
|
||||
auto thrown_vec = [1, 2, 3]
|
||||
try {
|
||||
throw(thrown_vec)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal(3, e.size())
|
||||
assert_equal(1, e[0])
|
||||
assert_equal(2, e[1])
|
||||
assert_equal(3, e[2])
|
||||
}
|
||||
|
||||
// Throw a Map (via named variable to preserve mutability)
|
||||
auto thrown_map = ["key": 42]
|
||||
try {
|
||||
throw(thrown_map)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal(42, e["key"])
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 2: Typed catch blocks
|
||||
// ============================================================
|
||||
|
||||
// Typed catch matching int
|
||||
auto typed_result = 0
|
||||
try {
|
||||
throw(10)
|
||||
}
|
||||
catch(int e) {
|
||||
typed_result = e + 1
|
||||
}
|
||||
assert_equal(11, typed_result)
|
||||
|
||||
// Typed catch matching string
|
||||
typed_result = 0
|
||||
try {
|
||||
throw("hello")
|
||||
}
|
||||
catch(string e) {
|
||||
typed_result = 1
|
||||
}
|
||||
assert_equal(1, typed_result)
|
||||
|
||||
// Typed catch mismatch falls through to untyped
|
||||
typed_result = 0
|
||||
try {
|
||||
throw(42)
|
||||
}
|
||||
catch(string e) {
|
||||
typed_result = 1
|
||||
}
|
||||
catch(e) {
|
||||
typed_result = 2
|
||||
}
|
||||
assert_equal(2, typed_result)
|
||||
|
||||
// Multiple typed catches - first match wins
|
||||
typed_result = 0
|
||||
try {
|
||||
throw("test")
|
||||
}
|
||||
catch(int e) {
|
||||
typed_result = 1
|
||||
}
|
||||
catch(string e) {
|
||||
typed_result = 2
|
||||
}
|
||||
catch(e) {
|
||||
typed_result = 3
|
||||
}
|
||||
assert_equal(2, typed_result)
|
||||
|
||||
// Typed catch with int, multiple typed blocks, none match except untyped
|
||||
typed_result = 0
|
||||
try {
|
||||
throw(3.14)
|
||||
}
|
||||
catch(int e) {
|
||||
typed_result = 1
|
||||
}
|
||||
catch(string e) {
|
||||
typed_result = 2
|
||||
}
|
||||
catch(e) {
|
||||
typed_result = 3
|
||||
}
|
||||
assert_equal(3, typed_result)
|
||||
|
||||
// ============================================================
|
||||
// Section 3: Catch-all (no variable) catch block
|
||||
// ============================================================
|
||||
|
||||
auto catch_all_reached = false
|
||||
try {
|
||||
throw(99)
|
||||
}
|
||||
catch {
|
||||
catch_all_reached = true
|
||||
}
|
||||
assert_true(catch_all_reached)
|
||||
|
||||
// ============================================================
|
||||
// Section 4: Finally block semantics
|
||||
// ============================================================
|
||||
|
||||
// Finally runs after exception
|
||||
auto finally_ran = false
|
||||
try {
|
||||
throw(1)
|
||||
}
|
||||
catch(e) {
|
||||
// caught
|
||||
}
|
||||
finally {
|
||||
finally_ran = true
|
||||
}
|
||||
assert_true(finally_ran)
|
||||
|
||||
// Finally runs without exception
|
||||
finally_ran = false
|
||||
try {
|
||||
auto x = 1
|
||||
}
|
||||
catch(e) {
|
||||
// not reached
|
||||
}
|
||||
finally {
|
||||
finally_ran = true
|
||||
}
|
||||
assert_true(finally_ran)
|
||||
|
||||
// Finally runs even with typed catch that matches
|
||||
finally_ran = false
|
||||
try {
|
||||
throw(42)
|
||||
}
|
||||
catch(int e) {
|
||||
assert_equal(42, e)
|
||||
}
|
||||
finally {
|
||||
finally_ran = true
|
||||
}
|
||||
assert_true(finally_ran)
|
||||
|
||||
// Finally runs when typed catch does NOT match (exception not caught)
|
||||
finally_ran = false
|
||||
auto outer_caught = false
|
||||
try {
|
||||
try {
|
||||
throw(42)
|
||||
}
|
||||
catch(string e) {
|
||||
// wrong type, won't match
|
||||
}
|
||||
finally {
|
||||
finally_ran = true
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
outer_caught = true
|
||||
}
|
||||
assert_true(finally_ran)
|
||||
assert_true(outer_caught)
|
||||
|
||||
// ============================================================
|
||||
// Section 5: Throwing from functions
|
||||
// ============================================================
|
||||
|
||||
def throwing_function() {
|
||||
throw("from function")
|
||||
}
|
||||
|
||||
try {
|
||||
throwing_function()
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("from function", e)
|
||||
}
|
||||
|
||||
// Throwing from nested function calls
|
||||
def inner_throw() {
|
||||
throw("inner")
|
||||
}
|
||||
|
||||
def outer_call() {
|
||||
inner_throw()
|
||||
}
|
||||
|
||||
try {
|
||||
outer_call()
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("inner", e)
|
||||
}
|
||||
|
||||
// Function that throws conditionally
|
||||
def conditional_throw(should_throw) {
|
||||
if (should_throw) {
|
||||
throw("conditional")
|
||||
}
|
||||
return "no throw"
|
||||
}
|
||||
|
||||
assert_equal("no throw", conditional_throw(false))
|
||||
|
||||
try {
|
||||
conditional_throw(true)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("conditional", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 6: Throwing from lambdas
|
||||
// ============================================================
|
||||
|
||||
auto throwing_lambda = fun() { throw("from lambda") }
|
||||
|
||||
try {
|
||||
throwing_lambda()
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("from lambda", e)
|
||||
}
|
||||
|
||||
// Lambda that captures and throws
|
||||
auto captured_val = "captured"
|
||||
auto capture_lambda = fun[captured_val]() { throw(captured_val) }
|
||||
|
||||
try {
|
||||
capture_lambda()
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("captured", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 7: Throwing from binary operators
|
||||
// ============================================================
|
||||
|
||||
// Define a type and an operator that throws
|
||||
attr ThrowOnAdd::val
|
||||
def ThrowOnAdd::ThrowOnAdd(v) { this.val = v }
|
||||
|
||||
def `+`(ThrowOnAdd x, ThrowOnAdd y) {
|
||||
throw("add not supported")
|
||||
}
|
||||
|
||||
try {
|
||||
auto a = ThrowOnAdd(1)
|
||||
auto b = ThrowOnAdd(2)
|
||||
auto c = a + b
|
||||
assert_true(false) // should not reach here
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("add not supported", e)
|
||||
}
|
||||
|
||||
// Operator that throws for specific values
|
||||
def `-`(ThrowOnAdd x, ThrowOnAdd y) {
|
||||
if (x.val == y.val) {
|
||||
throw("cannot subtract equal values")
|
||||
}
|
||||
return ThrowOnAdd(x.val - y.val)
|
||||
}
|
||||
|
||||
try {
|
||||
auto a = ThrowOnAdd(5)
|
||||
auto b = ThrowOnAdd(5)
|
||||
auto c = a - b
|
||||
assert_true(false)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("cannot subtract equal values", e)
|
||||
}
|
||||
|
||||
// Multiplication operator that throws (not pre-defined for Dynamic_Object)
|
||||
def `*`(ThrowOnAdd x, ThrowOnAdd y) {
|
||||
throw("multiply not supported")
|
||||
}
|
||||
|
||||
try {
|
||||
auto a = ThrowOnAdd(1)
|
||||
auto b = ThrowOnAdd(2)
|
||||
auto c = a * b
|
||||
assert_true(false)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("multiply not supported", e)
|
||||
}
|
||||
|
||||
// Subtraction works for non-equal values
|
||||
auto sub_result = ThrowOnAdd(10) - ThrowOnAdd(3)
|
||||
assert_equal(7, sub_result.val)
|
||||
|
||||
// ============================================================
|
||||
// Section 8: Throwing from unary/prefix operators
|
||||
// ============================================================
|
||||
|
||||
def `++`(ThrowOnAdd x) {
|
||||
throw("increment not supported")
|
||||
}
|
||||
|
||||
try {
|
||||
auto a = ThrowOnAdd(1)
|
||||
++a
|
||||
assert_true(false)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("increment not supported", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 9: Throwing from [] operator
|
||||
// ============================================================
|
||||
|
||||
attr ThrowOnIndex::data
|
||||
def ThrowOnIndex::ThrowOnIndex() { this.data = [1, 2, 3] }
|
||||
|
||||
def `[]`(ThrowOnIndex obj, int idx) {
|
||||
if (idx < 0) {
|
||||
throw("negative index not allowed")
|
||||
}
|
||||
return obj.data[idx]
|
||||
}
|
||||
|
||||
auto toi = ThrowOnIndex()
|
||||
assert_equal(1, toi[0])
|
||||
assert_equal(2, toi[1])
|
||||
|
||||
try {
|
||||
auto val = toi[-1]
|
||||
assert_true(false)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("negative index not allowed", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 10: Throwing from member functions
|
||||
// ============================================================
|
||||
|
||||
attr Validatable::value
|
||||
def Validatable::Validatable(v) { this.value = v }
|
||||
|
||||
def Validatable::validate() {
|
||||
if (this.value < 0) {
|
||||
throw("validation failed: negative value")
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
auto valid_obj = Validatable(10)
|
||||
assert_true(valid_obj.validate())
|
||||
|
||||
auto invalid_obj = Validatable(-1)
|
||||
try {
|
||||
invalid_obj.validate()
|
||||
assert_true(false)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("validation failed: negative value", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 11: Nested try/catch
|
||||
// ============================================================
|
||||
|
||||
auto inner_caught = false
|
||||
auto outer_caught_val = 0
|
||||
try {
|
||||
try {
|
||||
throw(1)
|
||||
}
|
||||
catch(e) {
|
||||
inner_caught = true
|
||||
throw(e + 10)
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
outer_caught_val = e
|
||||
}
|
||||
assert_true(inner_caught)
|
||||
assert_equal(11, outer_caught_val)
|
||||
|
||||
// Deeply nested try/catch
|
||||
auto depth = 0
|
||||
try {
|
||||
try {
|
||||
try {
|
||||
throw("deep")
|
||||
}
|
||||
catch(e) {
|
||||
depth = 1
|
||||
throw(e + "er")
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
depth = 2
|
||||
throw(e + "est")
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
depth = 3
|
||||
assert_equal("deeperest", e)
|
||||
}
|
||||
assert_equal(3, depth)
|
||||
|
||||
// ============================================================
|
||||
// Section 12: Rethrow from catch block
|
||||
// ============================================================
|
||||
|
||||
auto rethrow_caught = false
|
||||
try {
|
||||
try {
|
||||
throw("rethrown")
|
||||
}
|
||||
catch(e) {
|
||||
throw(e)
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
rethrow_caught = true
|
||||
assert_equal("rethrown", e)
|
||||
}
|
||||
assert_true(rethrow_caught)
|
||||
|
||||
// ============================================================
|
||||
// Section 13: Exception in for loop
|
||||
// ============================================================
|
||||
|
||||
auto loop_exception_val = 0
|
||||
try {
|
||||
for (auto i = 0; i < 10; ++i) {
|
||||
if (i == 5) {
|
||||
throw(i)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
loop_exception_val = e
|
||||
}
|
||||
assert_equal(5, loop_exception_val)
|
||||
|
||||
// ============================================================
|
||||
// Section 14: Exception in while loop
|
||||
// ============================================================
|
||||
|
||||
auto while_exc_val = 0
|
||||
auto counter = 0
|
||||
try {
|
||||
while (true) {
|
||||
++counter
|
||||
if (counter == 3) {
|
||||
throw(counter)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
while_exc_val = e
|
||||
}
|
||||
assert_equal(3, while_exc_val)
|
||||
|
||||
// ============================================================
|
||||
// Section 15: Exception preserves value through nested calls
|
||||
// ============================================================
|
||||
|
||||
def deep_throw(val) {
|
||||
throw(val)
|
||||
}
|
||||
|
||||
def middle_call(val) {
|
||||
deep_throw(val)
|
||||
}
|
||||
|
||||
def top_call(val) {
|
||||
middle_call(val)
|
||||
}
|
||||
|
||||
auto nested_map = ["key": "value"]
|
||||
try {
|
||||
top_call(nested_map)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("value", e["key"])
|
||||
}
|
||||
|
||||
auto nested_vec = [10, 20, 30]
|
||||
try {
|
||||
top_call(nested_vec)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal(3, e.size())
|
||||
assert_equal(20, e[1])
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 16: Code after throw is not executed
|
||||
// ============================================================
|
||||
|
||||
auto after_throw = false
|
||||
try {
|
||||
throw(1)
|
||||
after_throw = true
|
||||
}
|
||||
catch(e) {
|
||||
// caught
|
||||
}
|
||||
assert_false(after_throw)
|
||||
|
||||
// ============================================================
|
||||
// Section 17: Exception value is usable in catch block arithmetic
|
||||
// ============================================================
|
||||
|
||||
auto catch_computed = 0
|
||||
try {
|
||||
throw(1)
|
||||
}
|
||||
catch(e) {
|
||||
catch_computed = e + 100
|
||||
}
|
||||
assert_equal(101, catch_computed)
|
||||
|
||||
// ============================================================
|
||||
// Section 18: No exception means catch is skipped
|
||||
// ============================================================
|
||||
|
||||
auto catch_skipped = true
|
||||
try {
|
||||
auto x = 42
|
||||
}
|
||||
catch(e) {
|
||||
catch_skipped = false
|
||||
}
|
||||
assert_true(catch_skipped)
|
||||
|
||||
// ============================================================
|
||||
// Section 19: Exception from dynamic object method chaining
|
||||
// ============================================================
|
||||
|
||||
attr Chain::val
|
||||
def Chain::Chain(v) { this.val = v }
|
||||
|
||||
def Chain::add(n) {
|
||||
if (this.val + n > 100) {
|
||||
throw("overflow: " + to_string(this.val + n))
|
||||
}
|
||||
this.val = this.val + n
|
||||
return this
|
||||
}
|
||||
|
||||
auto chain = Chain(50)
|
||||
try {
|
||||
chain.add(30).add(30)
|
||||
assert_true(false)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("overflow: 110", e)
|
||||
}
|
||||
assert_equal(80, chain.val)
|
||||
|
||||
// ============================================================
|
||||
// Section 20: Exception thrown during map construction
|
||||
// ============================================================
|
||||
|
||||
def exploding_value() {
|
||||
throw("boom during construction")
|
||||
}
|
||||
|
||||
try {
|
||||
auto m = ["ok": 1, "bad": exploding_value()]
|
||||
assert_true(false)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("boom during construction", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 21: Exception thrown during vector construction
|
||||
// ============================================================
|
||||
|
||||
try {
|
||||
auto v = [1, 2, exploding_value(), 4]
|
||||
assert_true(false)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("boom during construction", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 22: Exception in if-condition
|
||||
// ============================================================
|
||||
|
||||
def exploding_condition() {
|
||||
throw("condition exploded")
|
||||
}
|
||||
|
||||
try {
|
||||
if (exploding_condition()) {
|
||||
assert_true(false)
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("condition exploded", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 23: Multiple catch blocks - only first matching runs
|
||||
// ============================================================
|
||||
|
||||
auto catch_count = 0
|
||||
try {
|
||||
throw(42)
|
||||
}
|
||||
catch(int e) {
|
||||
++catch_count
|
||||
}
|
||||
catch(e) {
|
||||
++catch_count
|
||||
}
|
||||
assert_equal(1, catch_count)
|
||||
|
||||
// ============================================================
|
||||
// Section 24: Throwing from within catch, with finally
|
||||
// ============================================================
|
||||
|
||||
auto s24_finally = false
|
||||
auto s24_outer = false
|
||||
try {
|
||||
try {
|
||||
throw("original")
|
||||
}
|
||||
catch(e) {
|
||||
throw("replaced: " + e)
|
||||
}
|
||||
finally {
|
||||
s24_finally = true
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
s24_outer = true
|
||||
assert_equal("replaced: original", e)
|
||||
}
|
||||
assert_true(s24_finally)
|
||||
assert_true(s24_outer)
|
||||
|
||||
// ============================================================
|
||||
// Section 25: Unhandled typed catch propagates exception
|
||||
// ============================================================
|
||||
|
||||
auto s25_caught = false
|
||||
try {
|
||||
try {
|
||||
throw(3.14)
|
||||
}
|
||||
catch(int e) {
|
||||
assert_true(false) // should not match double
|
||||
}
|
||||
catch(string e) {
|
||||
assert_true(false) // should not match double
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
s25_caught = true
|
||||
assert_equal(3.14, e)
|
||||
}
|
||||
assert_true(s25_caught)
|
||||
|
||||
// ============================================================
|
||||
// Section 26: Throw from range-based for
|
||||
// ============================================================
|
||||
|
||||
auto s26_val = 0
|
||||
try {
|
||||
for (x : [10, 20, 30, 40]) {
|
||||
if (x == 30) {
|
||||
throw(x)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch(e) {
|
||||
s26_val = e
|
||||
}
|
||||
assert_equal(30, s26_val)
|
||||
|
||||
// ============================================================
|
||||
// Section 27: Throw from eval
|
||||
// ============================================================
|
||||
|
||||
try {
|
||||
eval("throw(\"from eval\")")
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("from eval", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 28: Exception from built-in operations (out of range)
|
||||
// ============================================================
|
||||
|
||||
auto s28_caught = false
|
||||
try {
|
||||
auto v = [1, 2, 3]
|
||||
auto x = v[10]
|
||||
}
|
||||
catch(e) {
|
||||
s28_caught = true
|
||||
}
|
||||
assert_true(s28_caught)
|
||||
|
||||
// ============================================================
|
||||
// Section 29: Throw zero and empty string (falsy values)
|
||||
// ============================================================
|
||||
|
||||
try {
|
||||
throw(0)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal(0, e)
|
||||
}
|
||||
|
||||
try {
|
||||
throw("")
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 30: Throw from ternary-style inline_if
|
||||
// ============================================================
|
||||
|
||||
def maybe_throw(do_it) {
|
||||
if (do_it) { throw("inline threw") } else { "ok" }
|
||||
}
|
||||
|
||||
try {
|
||||
maybe_throw(true)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("inline threw", e)
|
||||
}
|
||||
assert_equal("ok", maybe_throw(false))
|
||||
|
||||
// ============================================================
|
||||
// Section 31: Verify catch variable scope isolation
|
||||
// ============================================================
|
||||
|
||||
auto outer_e = "untouched"
|
||||
try {
|
||||
throw("caught_value")
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("caught_value", e)
|
||||
}
|
||||
assert_equal("untouched", outer_e)
|
||||
|
||||
// ============================================================
|
||||
// Section 32: Exception from recursive function
|
||||
// ============================================================
|
||||
|
||||
def recursive_throw(n) {
|
||||
if (n == 0) {
|
||||
throw("bottom")
|
||||
}
|
||||
recursive_throw(n - 1)
|
||||
}
|
||||
|
||||
try {
|
||||
recursive_throw(5)
|
||||
}
|
||||
catch(e) {
|
||||
assert_equal("bottom", e)
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// Section 33: Try/catch in a function body
|
||||
// ============================================================
|
||||
|
||||
def safe_divide(a, b) {
|
||||
try {
|
||||
if (b == 0) {
|
||||
throw("division by zero")
|
||||
}
|
||||
return a / b
|
||||
}
|
||||
catch(e) {
|
||||
return e
|
||||
}
|
||||
}
|
||||
|
||||
assert_equal(5, safe_divide(10, 2))
|
||||
assert_equal("division by zero", safe_divide(10, 0))
|
||||
|
||||
// ============================================================
|
||||
// Section 34: Throw from [] on a Map with missing key
|
||||
// ============================================================
|
||||
|
||||
auto s34_caught = false
|
||||
try {
|
||||
auto m = ["a": 1]
|
||||
auto x = m["nonexistent"]
|
||||
}
|
||||
catch(e) {
|
||||
s34_caught = true
|
||||
}
|
||||
assert_true(s34_caught)
|
||||
|
||||
// ============================================================
|
||||
// Section 35: Arithmetic exception (divide by zero)
|
||||
// ============================================================
|
||||
|
||||
auto s35_caught = false
|
||||
try {
|
||||
auto x = 1 / 0
|
||||
}
|
||||
catch(e) {
|
||||
s35_caught = true
|
||||
}
|
||||
assert_true(s35_caught)
|
||||
Loading…
x
Reference in New Issue
Block a user