From 07eee04e94ac85275ad3600336b18f9f244ededf Mon Sep 17 00:00:00 2001 From: Christoffer Lerno Date: Sun, 15 Jun 2025 21:58:39 +0200 Subject: [PATCH] In some cases, the compiler would dereference a compile time null. #2215 --- releasenotes.md | 3 +- src/compiler/compiler_internal.h | 2 +- src/compiler/expr.c | 28 ---------------- src/compiler/sema_expr.c | 55 +++++++++++++++++++++++++------- src/compiler/sema_stmts.c | 6 ++-- test/test_suite/safe/deref_ct.c3 | 12 +++++++ 6 files changed, 62 insertions(+), 44 deletions(-) create mode 100644 test/test_suite/safe/deref_ct.c3 diff --git a/releasenotes.md b/releasenotes.md index cf2e11f47..967337a03 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -38,7 +38,8 @@ - `$echo` would suppress warning about unreachable code. #2205 - Correctly format '%c' when given a width. #2199 - Fix to `is_array_or_slice_of_char` #2214. -- Method on array slice caused segfault #2211. +- Method on array slice caused segfault #2211. +- In some cases, the compiler would dereference a compile time null. #2215 ### Stdlib changes - Deprecate `String.is_zstr` and `String.quick_zstr` #2188. diff --git a/src/compiler/compiler_internal.h b/src/compiler/compiler_internal.h index 2237e4086..6cf95168a 100644 --- a/src/compiler/compiler_internal.h +++ b/src/compiler/compiler_internal.h @@ -2234,7 +2234,7 @@ Expr *expr_generate_decl(Decl *decl, Expr *assign); Expr *expr_new_two(Expr *first, Expr *second); void expr_rewrite_two(Expr *original, Expr *first, Expr *second); void expr_insert_addr(Expr *original); -void expr_rewrite_insert_deref(Expr *original); +bool sema_expr_rewrite_insert_deref(SemaContext *context, Expr *original); Expr *expr_generate_decl(Decl *decl, Expr *assign); Expr *expr_variable(Decl *decl); Expr *expr_negate_expr(Expr *expr); diff --git a/src/compiler/expr.c b/src/compiler/expr.c index 6794104ca..baac38ed7 100644 --- a/src/compiler/expr.c +++ b/src/compiler/expr.c @@ -1115,34 +1115,6 @@ Expr *expr_variable(Decl *decl) return expr; } -void expr_rewrite_insert_deref(Expr *original) -{ - // Assume *(&x) => x - if (original->expr_kind == EXPR_UNARY && original->unary_expr.operator == UNARYOP_ADDR) - { - *original = *original->unary_expr.expr; - return; - } - - // Allocate our new and create our new inner, and overwrite the original. - Expr *inner = expr_copy(original); - original->expr_kind = EXPR_UNARY; - original->type = NULL; - original->unary_expr.operator = UNARYOP_DEREF; - original->unary_expr.expr = inner; - - // In the case the original is already resolved, we want to resolve the deref as well. - if (original->resolve_status == RESOLVE_DONE) - { - Type *no_fail = type_no_optional(inner->type); - ASSERT(no_fail->canonical->type_kind == TYPE_POINTER); - - // Only fold to the canonical type if it wasn't a pointer. - Type *pointee = no_fail->type_kind == TYPE_POINTER ? no_fail->pointer : no_fail->canonical->pointer; - original->type = type_add_optional(pointee, IS_OPTIONAL(inner)); - } -} - void expr_rewrite_const_ref(Expr *expr_to_rewrite, Decl *decl) { expr_to_rewrite->const_expr = (ExprConst) { diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 661973acc..6f76bb04e 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -5422,6 +5422,39 @@ static bool sema_expr_rewrite_to_type_property(SemaContext *context, Expr *expr, } +bool sema_expr_rewrite_insert_deref(SemaContext *context, Expr *original) +{ + if (expr_is_const_pointer(original) && !original->const_expr.ptr) + { + RETURN_SEMA_ERROR(original, "This value is known to be null so you cannot dereference it."); + } + // Assume *(&x) => x + if (original->expr_kind == EXPR_UNARY && original->unary_expr.operator == UNARYOP_ADDR) + { + *original = *original->unary_expr.expr; + return true; + } + + // Allocate our new and create our new inner, and overwrite the original. + Expr *inner = expr_copy(original); + original->expr_kind = EXPR_UNARY; + original->type = NULL; + original->unary_expr.operator = UNARYOP_DEREF; + original->unary_expr.expr = inner; + + // In the case the original is already resolved, we want to resolve the deref as well. + if (original->resolve_status == RESOLVE_DONE) + { + Type *no_fail = type_no_optional(inner->type); + ASSERT(no_fail->canonical->type_kind == TYPE_POINTER); + + // Only fold to the canonical type if it wasn't a pointer. + Type *pointee = no_fail->type_kind == TYPE_POINTER ? no_fail->pointer : no_fail->canonical->pointer; + original->type = type_add_optional(pointee, IS_OPTIONAL(inner)); + } + return true; +} + static inline bool sema_expr_analyse_swizzle(SemaContext *context, Expr *expr, Expr *parent, Type *flat_type, const char *kw, unsigned len, CheckType check, bool is_lvalue) { @@ -5487,7 +5520,7 @@ static inline bool sema_expr_analyse_swizzle(SemaContext *context, Expr *expr, E expr->resolve_status = RESOLVE_DONE; if (check == CHECK_ADDRESS) { - expr_rewrite_insert_deref(expr); + if (!sema_expr_rewrite_insert_deref(context, expr)) return false; } return true; } @@ -5638,7 +5671,7 @@ static inline bool sema_expr_analyse_access(SemaContext *context, Expr *expr, bo if (underlying_type->type_kind == TYPE_POINTER && underlying_type != type_voidptr) { if (!sema_cast_rvalue(context, parent, true)) return false; - expr_rewrite_insert_deref(expr->access_unresolved_expr.parent); + if (!sema_expr_rewrite_insert_deref(context, expr->access_unresolved_expr.parent)) return false; parent = expr->access_unresolved_expr.parent; } @@ -6561,7 +6594,7 @@ INLINE bool sema_rewrite_op_assign(SemaContext *context, Expr *expr, Expr *left, // Now, create a lhs of the binary add: Expr *lhs = expr_new_expr(EXPR_SUBSCRIPT, left); Expr *parent_by_variable = expr_variable(parent_val); - expr_rewrite_insert_deref(parent_by_variable); + if (!sema_expr_rewrite_insert_deref(context, parent_by_variable)) return false; lhs->subscript_expr = (ExprSubscript) { .expr = exprid(parent_by_variable), .index.expr = exprid(expr_variable(index_val)) }; // Now create the binary expression Expr *binary = expr_new_expr(EXPR_BINARY, expr); @@ -6571,7 +6604,7 @@ INLINE bool sema_rewrite_op_assign(SemaContext *context, Expr *expr, Expr *left, assign->binary_expr = (ExprBinary) { .left = exprid(left), .right = exprid(binary), .operator = BINARYOP_ASSIGN }; // Now we need to patch the values in `left`: parent_by_variable = expr_variable(parent_val); - expr_rewrite_insert_deref(parent_by_variable); + if (!sema_expr_rewrite_insert_deref(context, parent_by_variable)) return false; left->subscript_assign_expr.expr = exprid(parent_by_variable); left->subscript_assign_expr.index = exprid(expr_variable(index_val)); // We add the assign @@ -6594,8 +6627,8 @@ AFTER_ADDR:; Expr *left_lvalue = expr_variable(variable); // lvalue = *temp, rvalue = *temp - expr_rewrite_insert_deref(left_lvalue); - expr_rewrite_insert_deref(left_rvalue); + if (!sema_expr_rewrite_insert_deref(context, left_lvalue)) return false; + if (!sema_expr_rewrite_insert_deref(context, left_rvalue)) return false; // init, expr -> lvalue = rvalue + a expr->expr_kind = EXPR_BINARY; @@ -8350,7 +8383,7 @@ static bool sema_analyse_assign_mutate_overloaded_subscript(SemaContext *context { vec_add(args, exprptr(subscript_expr->subscript_assign_expr.index)); if (!sema_insert_method_call(context, subscript_expr, operator, exprptr(subscript_expr->subscript_assign_expr.expr), args, false)) return false; - expr_rewrite_insert_deref(subscript_expr); + if (!sema_expr_rewrite_insert_deref(context, subscript_expr)) return false; main->type = subscript_expr->type; return true; } @@ -8399,7 +8432,7 @@ static bool sema_analyse_assign_mutate_overloaded_subscript(SemaContext *context Expr *get_expr = expr_new(EXPR_ACCESS_RESOLVED, increased->span); vec_add(args, expr_variable(index_val)); Expr *temp_val_1 = expr_variable(temp_val); - expr_rewrite_insert_deref(temp_val_1); + if (!sema_expr_rewrite_insert_deref(context, temp_val_1)) return false; if (!sema_insert_method_call(context, get_expr, operator, temp_val_1, args, false)) return false; Expr *value_val_expr = expr_generate_decl(value_val, get_expr); // temp_value = func(temp, temp_index) @@ -8411,7 +8444,7 @@ static bool sema_analyse_assign_mutate_overloaded_subscript(SemaContext *context vec_add(args, expr_variable(index_val)); vec_add(args, expr_variable(value_val)); Expr *temp_val_2 = expr_variable(temp_val); - expr_rewrite_insert_deref(temp_val_2); + if (!sema_expr_rewrite_insert_deref(context, temp_val_2)) return false; if (!sema_insert_method_call(context, subscript_expr, declptr(subscript_expr->subscript_assign_expr.method), temp_val_2, args, false)) return false; ASSERT(subscript_expr->expr_kind == EXPR_CALL); subscript_expr->call_expr.has_optional_arg = false; @@ -10617,7 +10650,7 @@ bool sema_analyse_expr_rhs(SemaContext *context, Type *to, Expr *expr, bool allo // Given x[3..7] -> (int[5]*)x[3..7] cast_no_check(expr, type_get_ptr(type_get_array(element, len)), IS_OPTIONAL(expr)); // Deref - expr_rewrite_insert_deref(expr); + if (!sema_expr_rewrite_insert_deref(context, expr)) return false; cast_no_check(expr, to, IS_OPTIONAL(expr)); return true; } @@ -11293,7 +11326,7 @@ bool sema_insert_method_call(SemaContext *context, Expr *method_call, Decl *meth } else if (type->type_kind == TYPE_POINTER && type->pointer == first) { - expr_rewrite_insert_deref(parent); + if (!sema_expr_rewrite_insert_deref(context, parent)) return false; } } ASSERT_SPAN(method_call, parent && parent->type && first == parent->type->canonical); diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index cf6622cb0..99d337630 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -1507,7 +1507,7 @@ static inline bool sema_analyse_foreach_stmt(SemaContext *context, Ast *statemen { RETURN_SEMA_ERROR(enumerator, "It is not possible to enumerate an expression of type %s.", type_quoted_error_string(enumerator->type)); } - expr_rewrite_insert_deref(enumerator); + if (!sema_expr_rewrite_insert_deref(context, enumerator)) return false; } // At this point we should have dereferenced any pointer or bailed. @@ -1665,7 +1665,7 @@ SKIP_OVERLOAD:; // Create @__enum$.len() or @(*__enum$).len() Expr *enum_val = expr_variable(temp); enum_val->span = enumerator->span; - if (is_addr) expr_rewrite_insert_deref(enum_val); + if (is_addr && !sema_expr_rewrite_insert_deref(context, enum_val)) return false; Type *enumerator_type = type_flatten(enum_val->type); Expr *len_call; ArraySize array_len = 0; @@ -1837,7 +1837,7 @@ SKIP_OVERLOAD:; Expr *subscript = expr_new(EXPR_SUBSCRIPT, var->span); enum_val = expr_variable(temp); enum_val->span = enumerator->span; - if (is_addr) expr_rewrite_insert_deref(enum_val); + if (is_addr && !sema_expr_rewrite_insert_deref(context, enum_val)) return false; subscript->subscript_expr.expr = exprid(enum_val); Expr *index_expr = array_len == 1 ? expr_new_const_int(var->span, idx_decl->type, 0) : expr_variable(idx_decl); if (is_enum_iterator) diff --git a/test/test_suite/safe/deref_ct.c3 b/test/test_suite/safe/deref_ct.c3 new file mode 100644 index 000000000..621cdd21e --- /dev/null +++ b/test/test_suite/safe/deref_ct.c3 @@ -0,0 +1,12 @@ +module test; +macro void test() +{ + any *$value; + typeid t = $value.type; // #error: This value is known to be null so you +} + +fn int main() +{ + test(); + return 0; +} \ No newline at end of file