From be3f9007c940b05ddf038986d3e0a7cd693368f2 Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Tue, 27 May 2025 23:03:32 +0200 Subject: [PATCH] Check pointer/slice/etc on `[out]` and `&` params. #2156. --- lib/std/atomic.c3 | 22 +++++------ releasenotes.md | 1 + src/compiler/sema_expr.c | 47 +++++++++++++++--------- test/test_suite/contracts/inout_macro.c3 | 12 ++++++ 4 files changed, 54 insertions(+), 28 deletions(-) create mode 100644 test/test_suite/contracts/inout_macro.c3 diff --git a/lib/std/atomic.c3 b/lib/std/atomic.c3 index 3e5748e47..5687da955 100644 --- a/lib/std/atomic.c3 +++ b/lib/std/atomic.c3 @@ -183,7 +183,7 @@ macro bool is_native_atomic_type($Type) } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param [in] y : "the value to be added to ptr." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" @@ -203,7 +203,7 @@ macro fetch_add(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param [in] y : "the value to be subtracted from ptr." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" @@ -223,7 +223,7 @@ macro fetch_sub(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param [in] y : "the value to be multiplied with ptr." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" @@ -263,7 +263,7 @@ macro fetch_mul(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT) } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param [in] y : "the value to divide ptr by." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" @@ -303,7 +303,7 @@ macro fetch_div(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT) } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param [in] y : "the value to perform a bitwise or with." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" @@ -320,7 +320,7 @@ macro fetch_or(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatile } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param [in] y : "the value to perform a bitwise xor with." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" @@ -337,7 +337,7 @@ macro fetch_xor(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param [in] y : "the value to perform a bitwise and with." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" @@ -354,7 +354,7 @@ macro fetch_and(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT, bool $volatil } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param [in] y : "the value to shift ptr by." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" @@ -396,7 +396,7 @@ macro fetch_shift_right(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT) } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param [in] y : "the value to shift ptr by." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" @@ -438,7 +438,7 @@ macro fetch_shift_left(ptr, y, AtomicOrdering $ordering = SEQ_CONSISTENT) } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" @@ -466,7 +466,7 @@ macro flag_set(ptr, AtomicOrdering $ordering = SEQ_CONSISTENT) } <* - @param [&in] ptr : "the variable or dereferenced pointer to the data." + @param [&inout] ptr : "the variable or dereferenced pointer to the data." @param $ordering : "atomic ordering of the load, defaults to SEQ_CONSISTENT" @return "returns the old value of ptr" diff --git a/releasenotes.md b/releasenotes.md index 560da72bb..fbdf7528c 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -47,6 +47,7 @@ - Distinct types could not be used with tagof #2152. - `$$sat_mul` was missing. - `for` with incorrect `var` declaration caused crash #2154. +- Check pointer/slice/etc on `[out]` and `&` params. #2156. ### Stdlib changes - Added `String.quick_ztr` and `String.is_zstr` diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 732a1a5e3..cc229c63b 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -2446,27 +2446,40 @@ bool sema_expr_analyse_macro_call(SemaContext *context, Expr *call_expr, Expr *s // Skip raw vararg if (!param) continue; if (!sema_add_local(¯o_context, param)) goto EXIT_FAIL; - if (param->var.init_expr && param->var.not_null) + if (param->var.init_expr) { - Expr *expr = expr_variable(param); - Expr *binary = expr_new_expr(EXPR_BINARY, expr); - binary->binary_expr.left = exprid(expr); - binary->binary_expr.right = exprid(expr_new_const_null(expr->span, type_voidptr)); - binary->binary_expr.operator = BINARYOP_NE; - if (!sema_analyse_expr_rhs(context, type_bool, binary, false, NULL, false)) goto EXIT_FAIL; - const char *string = struct_var && idx == 0 ? "Called a method on a null value." : "Passed null to a ref ('&') parameter."; - if (expr_is_const_bool(binary) && !binary->const_expr.b) + Type *param_type = param->type; + if (param_type && (param->var.out_param || param->var.not_null)) { - sema_error_at(context, param->var.init_expr->span, string); - goto EXIT_FAIL; + param_type = type_flatten(param_type); + if (param_type->type_kind != TYPE_POINTER && param_type->type_kind != TYPE_SLICE && param_type->type_kind != TYPE_INTERFACE && param_type->type_kind != TYPE_ANY) + { + SEMA_ERROR(param->var.init_expr, "Expected a pointer, slice or interface value for '%s'.", param->name); + goto EXIT_FAIL; + } } + if (param->var.not_null) + { + Expr *expr = expr_variable(param); + Expr *binary = expr_new_expr(EXPR_BINARY, expr); + binary->binary_expr.left = exprid(expr); + binary->binary_expr.right = exprid(expr_new_const_null(expr->span, type_voidptr)); + binary->binary_expr.operator = BINARYOP_NE; + if (!sema_analyse_expr_rhs(context, type_bool, binary, false, NULL, false)) goto EXIT_FAIL; + const char *string = struct_var && idx == 0 ? "Called a method on a null value." : "Passed null to a ref ('&') parameter."; + if (expr_is_const_bool(binary) && !binary->const_expr.b) + { + sema_error_at(context, param->var.init_expr->span, string); + goto EXIT_FAIL; + } - Ast *assert = new_ast(AST_ASSERT_STMT, param->var.init_expr->span); - assert->assert_stmt.is_ensure = true; - assert->assert_stmt.expr = exprid(binary); - Expr *comment_expr = expr_new_const_string(expr->span, string); - assert->assert_stmt.message = exprid(comment_expr); - ast_append(&next, assert); + Ast *assert = new_ast(AST_ASSERT_STMT, param->var.init_expr->span); + assert->assert_stmt.is_ensure = true; + assert->assert_stmt.expr = exprid(binary); + Expr *comment_expr = expr_new_const_string(expr->span, string); + assert->assert_stmt.message = exprid(comment_expr); + ast_append(&next, assert); + } } } diff --git a/test/test_suite/contracts/inout_macro.c3 b/test/test_suite/contracts/inout_macro.c3 new file mode 100644 index 000000000..9e3514186 --- /dev/null +++ b/test/test_suite/contracts/inout_macro.c3 @@ -0,0 +1,12 @@ +import std; +<* + @param [&out] x + @require $defined(*x) : "x must be a pointer" +*> +macro foo(x) => *x = 1; + +fn int main() +{ + foo(0); // #error: Expected a pointer + return 0; +} \ No newline at end of file