From 118c13d18470df5cd572d0fd41cd70ed867b951e Mon Sep 17 00:00:00 2001 From: Aymeric Wibo Date: Sat, 11 Jul 2026 16:25:05 +0200 Subject: [PATCH] call: Properly unwind when failure This would cause problems because we'd set flamingo->env to the function's env, but then return without ever setting it back. This meant that if we failed and destroyed the flamingo object: - First, all values are dereferenced and destroyed, which means the function's env would be destroyed. - Then, we'd try to destroy flamingo->env, which would fail because it is one of the function environments we've already freed. - The previous/inital flamingo->env would never be freed. Unwind properly now and be a little less dense. Still, Flamingo error handling needs some TLC. --- flamingo/call.h | 48 ++++++++++++++++++++++++++++++------------------ 1 file changed, 30 insertions(+), 18 deletions(-) diff --git a/flamingo/call.h b/flamingo/call.h index e1e1eb2..da804aa 100644 --- a/flamingo/call.h +++ b/flamingo/call.h @@ -1,5 +1,5 @@ // This Source Form is subject to the terms of the AQUA Software License, v. 1.0. -// Copyright (c) 2024 Aymeric Wibo +// Copyright (c) 2024-2026 Aymeric Wibo /* * Function calling. @@ -124,33 +124,36 @@ static int call( flamingo->env = callable->fn.env; } + // A couple more declarations before we start goto'ing... + + int err = -1; + TSNode* const body = callable->fn.body; + bool const is_expr = body != NULL && strcmp(ts_node_type(*body), "expression") == 0; + flamingo_scope_t* inner_scope; + // Create a new scope for the function for the argument assignments. - // It's important to set 'scope->class_scope' to false for functions as new scopes will copy the 'class_scope' property from their parents otherwise. + // It's important to set 'arg_scope->class_scope' to false for functions as new scopes will copy the 'class_scope' property from their parents otherwise. - flamingo_scope_t* const scope = env_push_scope(flamingo->env); - scope->class_scope = is_class; + flamingo_scope_t* const arg_scope = env_push_scope(flamingo->env); + arg_scope->class_scope = is_class; if (is_ptm) { if (setup_args_no_param(flamingo, args) < 0) { - return -1; + goto unwind; } } else if (setup_args(flamingo, callable->fn.params, args) < 0) { - return -1; + goto unwind; } // If external function or primitive type member: call the function's callback. // If function or class: actually parse the function's body. - TSNode* const body = callable->fn.body; - bool const is_expr = body != NULL && strcmp(ts_node_type(*body), "expression") == 0; - - flamingo_scope_t* inner_scope; - if (is_extern || is_ptm) { if (is_extern && flamingo->external_fn_cb == NULL) { - return error(flamingo, "cannot call external function without a external function callback being set"); + error(flamingo, "cannot call external function without a external function callback being set"); + goto unwind; } // Create arg list. @@ -175,11 +178,11 @@ static int call( assert(flamingo->cur_fn_rv == NULL); if (is_extern && flamingo->external_fn_cb(flamingo, callable, flamingo->external_fn_cb_data, &arg_list, &flamingo->cur_fn_rv) < 0) { - return -1; + goto unwind; } else if (is_ptm && callable->fn.ptm_cb(flamingo, accessed_val, &arg_list, &flamingo->cur_fn_rv)) { - return -1; + goto unwind; } free(args); @@ -189,15 +192,20 @@ static int call( assert(callable->fn.kind == FLAMINGO_FN_KIND_FUNCTION); // The only kind of callable that can have an expression body. if (parse_expr(flamingo, *body, rv, NULL) < 0) { - return -1; + goto unwind; } } else if (parse_block(flamingo, *body, is_class ? &inner_scope : NULL) < 0) { - return -1; + goto unwind; } - // Unwind the scope stack and switch back to previous source, current function body context, and environment.. + err = 0; + + // Unwind the scope stack and switch back to previous source, current function body context, and environment. + // If there was an error, stop here. + +unwind: env_pop_scope(flamingo->env); @@ -207,6 +215,10 @@ static int call( flamingo->cur_fn_body = prev_fn_body; flamingo->env = prev_env; + if (err != 0) { + goto done; + } + // If the function body was an expression, we're done (no return value, call value was already set). if (is_expr) { @@ -269,5 +281,5 @@ static int call( done: flamingo->cur_fn_rv = NULL; - return 0; + return err; }