diff --git a/releasenotes.md b/releasenotes.md index b8421948c..32782b68e 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -54,6 +54,7 @@ - Fixes to `x += { 1, 1 }` for enum and pointer vectors #2222. - Linking fails on operator method imported as `@public` #2224. - Lambda C-style vaargs were not properly rejected, leading to crash #2229. +- Incorrect handling of constant null fault causing compiler crash #2232. ### Stdlib changes - Deprecate `String.is_zstr` and `String.quick_zstr` #2188. diff --git a/src/compiler/copying.c b/src/compiler/copying.c index 56421ffef..b06b17148 100644 --- a/src/compiler/copying.c +++ b/src/compiler/copying.c @@ -46,7 +46,9 @@ static inline void *fixup(CopyStruct *c, void *original) INLINE void fixup_decl(CopyStruct *c, Decl **decl_ref) { - Decl *new_decl = fixup(c, *decl_ref); + Decl *old = *decl_ref; + if (!old) return; + Decl *new_decl = fixup(c, old); if (new_decl) *decl_ref = new_decl; } diff --git a/src/compiler/expr.c b/src/compiler/expr.c index 201a17cc9..5af16fbbf 100644 --- a/src/compiler/expr.c +++ b/src/compiler/expr.c @@ -659,11 +659,15 @@ void expr_rewrite_to_const_zero(Expr *expr, Type *type) case TYPE_POINTER: case TYPE_ANY: case TYPE_INTERFACE: - case TYPE_ANYFAULT: case TYPE_TYPEID: case TYPE_FUNC_PTR: expr_rewrite_const_null(expr, type); return; + case TYPE_ANYFAULT: + expr->const_expr.const_kind = CONST_FAULT; + expr->const_expr.fault = NULL; + expr->resolve_status = RESOLVE_DONE; + break; case TYPE_ENUM: expr->const_expr.const_kind = CONST_ENUM; ASSERT(canonical->decl->resolve_status == RESOLVE_DONE); diff --git a/src/compiler/llvm_codegen_expr.c b/src/compiler/llvm_codegen_expr.c index 28cb8ded0..92215d9fc 100644 --- a/src/compiler/llvm_codegen_expr.c +++ b/src/compiler/llvm_codegen_expr.c @@ -3076,6 +3076,7 @@ void llvm_emit_int_comp_raw(GenContext *c, BEValue *result, Type *lhs_type, Type } } } + ASSERT(LLVMTypeOf(lhs_value) == LLVMTypeOf(rhs_value)); if (lhs_signed && !rhs_signed && !vector_type && llvm_is_const(lhs_value) && type_size(lhs_type) <= 8) @@ -4765,8 +4766,7 @@ static void llvm_emit_const_expr(GenContext *c, BEValue *be_value, Expr *expr) case CONST_FAULT: { Decl *decl = expr->const_expr.fault; - ASSERT(decl); - LLVMValueRef value = LLVMBuildPtrToInt(c->builder, llvm_get_ref(c, decl), llvm_get_type(c, type_fault), ""); + LLVMValueRef value = decl ? LLVMBuildPtrToInt(c->builder, llvm_get_ref(c, decl), llvm_get_type(c, type_fault), "") : llvm_get_zero(c, type_fault); llvm_value_set(be_value, value, type_fault); return; } diff --git a/src/compiler/number.c b/src/compiler/number.c index 811e06fb4..13a7b33f0 100644 --- a/src/compiler/number.c +++ b/src/compiler/number.c @@ -137,7 +137,8 @@ bool expr_const_compare(const ExprConst *left, const ExprConst *right, BinaryOp return int_comp(left->ixx, right->ixx, op); case CONST_FAULT: ASSERT(right->const_kind == CONST_FAULT); - return decl_flatten(right->fault) == decl_flatten(left->fault); + if (right->fault == left->fault) return true; + return right->fault && left->fault && decl_flatten(right->fault) == decl_flatten(left->fault); case CONST_REF: ASSERT(right->const_kind == CONST_POINTER || right->const_kind == CONST_REF); if (right->const_kind == CONST_POINTER) return false; @@ -382,7 +383,7 @@ void expr_const_to_scratch_buffer(const ExprConst *expr) scratch_buffer_append(expr->global_ref->name); return; case CONST_FAULT: - scratch_buffer_append(expr->fault->name); + scratch_buffer_append(expr->fault ? expr->fault->name : "null"); return; case CONST_ENUM: scratch_buffer_append(expr->enum_val->name); @@ -443,7 +444,7 @@ const char *expr_const_to_error_string(const ExprConst *expr) case CONST_REF: return expr->global_ref->name; case CONST_FAULT: - return expr->fault->name; + return expr->fault ? expr->fault->name : "null"; case CONST_ENUM: return expr->enum_val->name; case CONST_TYPEID: diff --git a/src/compiler/sema_decls.c b/src/compiler/sema_decls.c index e6d8856af..8b404f5ac 100755 --- a/src/compiler/sema_decls.c +++ b/src/compiler/sema_decls.c @@ -4840,7 +4840,15 @@ static bool sema_generate_parameterized_name_to_scratch(SemaContext *context, Mo else if (type->type_kind == TYPE_ANYFAULT) { Decl *fault = param->const_expr.fault; - type_mangle_introspect_name_to_buffer(fault->type->canonical); + if (fault) + { + type_mangle_introspect_name_to_buffer(fault->type->canonical); + } + else + { + scratch_buffer_append("null"); + } + scratch_buffer_append(mangled ? "_" : ":"); scratch_buffer_append(fault->name); } diff --git a/src/compiler/sema_expr.c b/src/compiler/sema_expr.c index 566ec791d..7eda1dca9 100644 --- a/src/compiler/sema_expr.c +++ b/src/compiler/sema_expr.c @@ -5778,7 +5778,7 @@ CHECK_DEEPER: { if (sema_cast_const(current_parent)) { - expr_rewrite_const_string(expr, current_parent->const_expr.fault->name); + expr_rewrite_const_string(expr, current_parent->const_expr.fault ? current_parent->const_expr.fault->name : "null"); return true; } expr_rewrite_to_builtin_access(expr, current_parent, ACCESS_FAULTNAME, type_string); diff --git a/src/compiler/sema_passes.c b/src/compiler/sema_passes.c index 6ac52d524..caf8e8221 100644 --- a/src/compiler/sema_passes.c +++ b/src/compiler/sema_passes.c @@ -201,7 +201,7 @@ static bool exec_arg_append_to_scratch(Expr *arg) scratch_buffer_append(arg->const_expr.global_ref->name); return true; case CONST_FAULT: - scratch_buffer_append(arg->const_expr.fault->name); + scratch_buffer_append(arg->const_expr.fault ? arg->const_expr.fault->name : "null"); return true; case CONST_ENUM: scratch_buffer_append(arg->const_expr.enum_val->name); diff --git a/src/compiler/sema_stmts.c b/src/compiler/sema_stmts.c index 99d337630..ac5841879 100644 --- a/src/compiler/sema_stmts.c +++ b/src/compiler/sema_stmts.c @@ -3166,6 +3166,7 @@ static bool sema_analyse_optional_returns(SemaContext *context, Ast *directive) if (!sema_analyse_expr(context, expr)) return false; if (!expr_is_const_fault(expr)) RETURN_SEMA_ERROR(expr, "A fault is required."); Decl *decl = expr->const_expr.fault; + if (!decl) RETURN_SEMA_ERROR(expr, "A non-null fault is required."); ret->contract_fault.decl = decl; ret->contract_fault.resolved = true; vec_add(context->call_env.opt_returns, decl); diff --git a/test/test_suite/errors/fault_zero_switch.c3t b/test/test_suite/errors/fault_zero_switch.c3t new file mode 100644 index 000000000..1b293822d --- /dev/null +++ b/test/test_suite/errors/fault_zero_switch.c3t @@ -0,0 +1,170 @@ +// #target: macos-x64 +module test; +import std; +fn int main(String[] args) +{ + int? tok; + switch (fault err = @catch(tok)) + { + case {}: + io::printn("got valid token"); + case io::EOF: + return 0; + default: + io::printfn("error: %s, %d", err, (usz)err); + } + return 0; +} + +/* #expect: test.ll + +; Function Attrs: nounwind uwtable +define i32 @test.main(ptr %0, i64 %1) #0 { +entry: + %args = alloca %"char[][]", align 8 + %tok = alloca i32, align 4 + %tok.f = alloca i64, align 8 + %err = alloca i64, align 8 + %blockret = alloca i64, align 8 + %f = alloca i64, align 8 + %switch = alloca i64, align 8 + %len = alloca i64, align 8 + %error_var = alloca i64, align 8 + %retparam = alloca i64, align 8 + %error_var6 = alloca i64, align 8 + %error_var12 = alloca i64, align 8 + %varargslots = alloca [2 x %any], align 16 + %taddr = alloca i64, align 8 + %retparam22 = alloca i64, align 8 + store ptr %0, ptr %args, align 8 + %ptradd = getelementptr inbounds i8, ptr %args, i64 8 + store i64 %1, ptr %ptradd, align 8 + store i64 0, ptr %tok.f, align 8 + store i32 0, ptr %tok, align 4 + br label %testblock + +testblock: ; preds = %entry + %optval = load i64, ptr %tok.f, align 8 + %not_err = icmp eq i64 %optval, 0 + %2 = call i1 @llvm.expect.i1(i1 %not_err, i1 true) + br i1 %2, label %after_check, label %assign_optional + +assign_optional: ; preds = %testblock + store i64 %optval, ptr %f, align 8 + br label %end_block + +after_check: ; preds = %testblock + store i64 0, ptr %f, align 8 + br label %end_block + +end_block: ; preds = %after_check, %assign_optional + %3 = load i64, ptr %f, align 8 + %i2b = icmp ne i64 %3, 0 + br i1 %i2b, label %if.then, label %if.exit + +if.then: ; preds = %end_block + %4 = load i64, ptr %f, align 8 + store i64 %4, ptr %blockret, align 8 + br label %expr_block.exit + +if.exit: ; preds = %end_block + store i64 0, ptr %blockret, align 8 + br label %expr_block.exit + +expr_block.exit: ; preds = %if.exit, %if.then + %5 = load i64, ptr %blockret, align 8 + store i64 %5, ptr %err, align 8 + %6 = load i64, ptr %err, align 8 + store i64 %6, ptr %switch, align 8 + br label %switch.entry + +switch.entry: ; preds = %expr_block.exit + %7 = load i64, ptr %switch, align 8 + %eq = icmp eq i64 0, %7 + br i1 %eq, label %switch.case, label %next_if + +switch.case: ; preds = %switch.entry + %8 = call ptr @std.io.stdout() + %9 = call i64 @std.io.File.write(ptr %retparam, ptr %8, ptr @.str, i64 15) + %not_err3 = icmp eq i64 %9, 0 + %10 = call i1 @llvm.expect.i1(i1 %not_err3, i1 true) + br i1 %10, label %after_check5, label %assign_optional4 + +assign_optional4: ; preds = %switch.case + store i64 %9, ptr %error_var, align 8 + br label %guard_block + +after_check5: ; preds = %switch.case + br label %noerr_block + +guard_block: ; preds = %assign_optional4 + br label %voiderr + +noerr_block: ; preds = %after_check5 + %11 = load i64, ptr %retparam, align 8 + store i64 %11, ptr %len, align 8 + %12 = call i64 @std.io.File.write_byte(ptr %8, i8 zeroext 10) + %not_err7 = icmp eq i64 %12, 0 + %13 = call i1 @llvm.expect.i1(i1 %not_err7, i1 true) + br i1 %13, label %after_check9, label %assign_optional8 + +assign_optional8: ; preds = %noerr_block + store i64 %12, ptr %error_var6, align 8 + br label %guard_block10 + +after_check9: ; preds = %noerr_block + br label %noerr_block11 + +guard_block10: ; preds = %assign_optional8 + br label %voiderr + +noerr_block11: ; preds = %after_check9 + %14 = call i64 @std.io.File.flush(ptr %8) + %not_err13 = icmp eq i64 %14, 0 + %15 = call i1 @llvm.expect.i1(i1 %not_err13, i1 true) + br i1 %15, label %after_check15, label %assign_optional14 + +assign_optional14: ; preds = %noerr_block11 + store i64 %14, ptr %error_var12, align 8 + br label %guard_block16 + +after_check15: ; preds = %noerr_block11 + br label %noerr_block17 + +guard_block16: ; preds = %assign_optional14 + br label %voiderr + +noerr_block17: ; preds = %after_check15 + %16 = load i64, ptr %len, align 8 + %add = add i64 %16, 1 + br label %voiderr + +voiderr: ; preds = %noerr_block17, %guard_block16, %guard_block10, %guard_block + br label %switch.exit + +next_if: ; preds = %switch.entry + %eq18 = icmp eq i64 ptrtoint (ptr @std.io.EOF to i64), %7 + br i1 %eq18, label %switch.case19, label %next_if20 + +switch.case19: ; preds = %next_if + ret i32 0 + +next_if20: ; preds = %next_if + br label %switch.default + +switch.default: ; preds = %next_if20 + %17 = insertvalue %any undef, ptr %err, 0 + %18 = insertvalue %any %17, i64 ptrtoint (ptr @"$ct.fault" to i64), 1 + store %any %18, ptr %varargslots, align 16 + %19 = load i64, ptr %err, align 8 + store i64 %19, ptr %taddr, align 8 + %20 = insertvalue %any undef, ptr %taddr, 0 + %21 = insertvalue %any %20, i64 ptrtoint (ptr @"$ct.ulong" to i64), 1 + %ptradd21 = getelementptr inbounds i8, ptr %varargslots, i64 16 + store %any %21, ptr %ptradd21, align 16 + %22 = call i64 @std.io.printfn(ptr %retparam22, ptr @.str.1, i64 13, ptr %varargslots, i64 2) + br label %switch.exit + +switch.exit: ; preds = %switch.default, %voiderr + ret i32 0 +}