| author | |
| committer | |
| log | 54c06bf7158ce52c5de8d09f109215c467a3bf6a |
| tree | 1087306b7a45c59ad1072877f34f65efd6dc2a1c |
| parent | 8fc6e31567057ca39466a34b63917f6f22f2c288 |
5 files changed, 139 insertions(+), 42 deletions(-)
TODO-4| ... | @@ -17,10 +17,6 @@ you can get the compiler to tell you the possible errors for an inferred error s | ... | @@ -17,10 +17,6 @@ you can get the compiler to tell you the possible errors for an inferred error s |
| 17 | 17 | ||
| 18 | foo() catch |err| switch (err) {}; | 18 | foo() catch |err| switch (err) {}; |
| 19 | 19 | ||
| 20 | // TODO this is an explicit cast and should actually coerce the type | ||
| 21 | erorr set casting | ||
| 22 | // add a runtime safety check | ||
| 23 | |||
| 24 | 20 | ||
| 25 | test err should be comptime if error set has 0 members | 21 | test err should be comptime if error set has 0 members |
| 26 | 22 |
src/codegen.cpp+52-27| ... | @@ -1958,6 +1958,54 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, | ... | @@ -1958,6 +1958,54 @@ static LLVMValueRef ir_render_bin_op(CodeGen *g, IrExecutable *executable, |
| 1958 | zig_unreachable(); | 1958 | zig_unreachable(); |
| 1959 | } | 1959 | } |
| 1960 | 1960 | ||
| 1961 | static void add_error_range_check(CodeGen *g, TypeTableEntry *err_set_type, TypeTableEntry *int_type, LLVMValueRef target_val) { | ||
| 1962 | assert(err_set_type->id == TypeTableEntryIdErrorSet); | ||
| 1963 | |||
| 1964 | if (type_is_global_error_set(err_set_type)) { | ||
| 1965 | LLVMValueRef zero = LLVMConstNull(int_type->type_ref); | ||
| 1966 | LLVMValueRef neq_zero_bit = LLVMBuildICmp(g->builder, LLVMIntNE, target_val, zero, ""); | ||
| 1967 | LLVMValueRef ok_bit; | ||
| 1968 | |||
| 1969 | BigInt biggest_possible_err_val = {0}; | ||
| 1970 | eval_min_max_value_int(g, int_type, &biggest_possible_err_val, true); | ||
| 1971 | |||
| 1972 | if (bigint_fits_in_bits(&biggest_possible_err_val, 64, false) && | ||
| 1973 | bigint_as_unsigned(&biggest_possible_err_val) < g->errors_by_index.length) | ||
| 1974 | { | ||
| 1975 | ok_bit = neq_zero_bit; | ||
| 1976 | } else { | ||
| 1977 | LLVMValueRef error_value_count = LLVMConstInt(int_type->type_ref, g->errors_by_index.length, false); | ||
| 1978 | LLVMValueRef in_bounds_bit = LLVMBuildICmp(g->builder, LLVMIntULT, target_val, error_value_count, ""); | ||
| 1979 | ok_bit = LLVMBuildAnd(g->builder, neq_zero_bit, in_bounds_bit, ""); | ||
| 1980 | } | ||
| 1981 | |||
| 1982 | LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrOk"); | ||
| 1983 | LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrFail"); | ||
| 1984 | |||
| 1985 | LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); | ||
| 1986 | |||
| 1987 | LLVMPositionBuilderAtEnd(g->builder, fail_block); | ||
| 1988 | gen_safety_crash(g, PanicMsgIdInvalidErrorCode); | ||
| 1989 | |||
| 1990 | LLVMPositionBuilderAtEnd(g->builder, ok_block); | ||
| 1991 | } else { | ||
| 1992 | LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrOk"); | ||
| 1993 | LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrFail"); | ||
| 1994 | |||
| 1995 | uint32_t err_count = err_set_type->data.error_set.err_count; | ||
| 1996 | LLVMValueRef switch_instr = LLVMBuildSwitch(g->builder, target_val, fail_block, err_count); | ||
| 1997 | for (uint32_t i = 0; i < err_count; i += 1) { | ||
| 1998 | LLVMValueRef case_value = LLVMConstInt(g->err_tag_type->type_ref, err_set_type->data.error_set.errors[i]->value, false); | ||
| 1999 | LLVMAddCase(switch_instr, case_value, ok_block); | ||
| 2000 | } | ||
| 2001 | |||
| 2002 | LLVMPositionBuilderAtEnd(g->builder, fail_block); | ||
| 2003 | gen_safety_crash(g, PanicMsgIdInvalidErrorCode); | ||
| 2004 | |||
| 2005 | LLVMPositionBuilderAtEnd(g->builder, ok_block); | ||
| 2006 | } | ||
| 2007 | } | ||
| 2008 | |||
| 1961 | static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, | 2009 | static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, |
| 1962 | IrInstructionCast *cast_instruction) | 2010 | IrInstructionCast *cast_instruction) |
| 1963 | { | 2011 | { |
| ... | @@ -2082,7 +2130,9 @@ static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, | ... | @@ -2082,7 +2130,9 @@ static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable, |
| 2082 | assert(actual_type->id == TypeTableEntryIdBool); | 2130 | assert(actual_type->id == TypeTableEntryIdBool); |
| 2083 | return LLVMBuildZExt(g->builder, expr_val, wanted_type->type_ref, ""); | 2131 | return LLVMBuildZExt(g->builder, expr_val, wanted_type->type_ref, ""); |
| 2084 | case CastOpErrSet: | 2132 | case CastOpErrSet: |
| 2085 | // TODO runtime safety for error casting | 2133 | if (ir_want_runtime_safety(g, &cast_instruction->base)) { |
| 2134 | add_error_range_check(g, wanted_type, g->err_tag_type, expr_val); | ||
| 2135 | } | ||
| 2086 | return expr_val; | 2136 | return expr_val; |
| 2087 | } | 2137 | } |
| 2088 | zig_unreachable(); | 2138 | zig_unreachable(); |
| ... | @@ -2154,32 +2204,7 @@ static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, I | ... | @@ -2154,32 +2204,7 @@ static LLVMValueRef ir_render_int_to_err(CodeGen *g, IrExecutable *executable, I |
| 2154 | LLVMValueRef target_val = ir_llvm_value(g, instruction->target); | 2204 | LLVMValueRef target_val = ir_llvm_value(g, instruction->target); |
| 2155 | 2205 | ||
| 2156 | if (ir_want_runtime_safety(g, &instruction->base)) { | 2206 | if (ir_want_runtime_safety(g, &instruction->base)) { |
| 2157 | LLVMValueRef zero = LLVMConstNull(actual_type->type_ref); | 2207 | add_error_range_check(g, wanted_type, actual_type, target_val); |
| 2158 | LLVMValueRef neq_zero_bit = LLVMBuildICmp(g->builder, LLVMIntNE, target_val, zero, ""); | ||
| 2159 | LLVMValueRef ok_bit; | ||
| 2160 | |||
| 2161 | BigInt biggest_possible_err_val = {0}; | ||
| 2162 | eval_min_max_value_int(g, actual_type, &biggest_possible_err_val, true); | ||
| 2163 | |||
| 2164 | if (bigint_fits_in_bits(&biggest_possible_err_val, 64, false) && | ||
| 2165 | bigint_as_unsigned(&biggest_possible_err_val) < g->errors_by_index.length) | ||
| 2166 | { | ||
| 2167 | ok_bit = neq_zero_bit; | ||
| 2168 | } else { | ||
| 2169 | LLVMValueRef error_value_count = LLVMConstInt(actual_type->type_ref, g->errors_by_index.length, false); | ||
| 2170 | LLVMValueRef in_bounds_bit = LLVMBuildICmp(g->builder, LLVMIntULT, target_val, error_value_count, ""); | ||
| 2171 | ok_bit = LLVMBuildAnd(g->builder, neq_zero_bit, in_bounds_bit, ""); | ||
| 2172 | } | ||
| 2173 | |||
| 2174 | LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrOk"); | ||
| 2175 | LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn_val, "IntToErrFail"); | ||
| 2176 | |||
| 2177 | LLVMBuildCondBr(g->builder, ok_bit, ok_block, fail_block); | ||
| 2178 | |||
| 2179 | LLVMPositionBuilderAtEnd(g->builder, fail_block); | ||
| 2180 | gen_safety_crash(g, PanicMsgIdInvalidErrorCode); | ||
| 2181 | |||
| 2182 | LLVMPositionBuilderAtEnd(g->builder, ok_block); | ||
| 2183 | } | 2208 | } |
| 2184 | 2209 | ||
| 2185 | return gen_widen_or_shorten(g, false, actual_type, g->err_tag_type, target_val); | 2210 | return gen_widen_or_shorten(g, false, actual_type, g->err_tag_type, target_val); |
src/ir.cpp+40-10| ... | @@ -8505,19 +8505,49 @@ static IrInstruction *ir_analyze_int_to_err(IrAnalyze *ira, IrInstruction *sourc | ... | @@ -8505,19 +8505,49 @@ static IrInstruction *ir_analyze_int_to_err(IrAnalyze *ira, IrInstruction *sourc |
| 8505 | IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, | 8505 | IrInstruction *result = ir_create_const(&ira->new_irb, source_instr->scope, |
| 8506 | source_instr->source_node, wanted_type); | 8506 | source_instr->source_node, wanted_type); |
| 8507 | 8507 | ||
| 8508 | BigInt err_count; | 8508 | if (!resolve_inferred_error_set(ira, wanted_type, source_instr->source_node)) { |
| 8509 | bigint_init_unsigned(&err_count, ira->codegen->errors_by_index.length); | ||
| 8510 | if (bigint_cmp_zero(&val->data.x_bigint) == CmpEQ || bigint_cmp(&val->data.x_bigint, &err_count) != CmpLT) { | ||
| 8511 | Buf *val_buf = buf_alloc(); | ||
| 8512 | bigint_append_buf(val_buf, &val->data.x_bigint, 10); | ||
| 8513 | ir_add_error(ira, source_instr, | ||
| 8514 | buf_sprintf("integer value %s represents no error", buf_ptr(val_buf))); | ||
| 8515 | return ira->codegen->invalid_instruction; | 8509 | return ira->codegen->invalid_instruction; |
| 8516 | } | 8510 | } |
| 8517 | 8511 | ||
| 8518 | size_t index = bigint_as_unsigned(&val->data.x_bigint); | 8512 | if (type_is_global_error_set(wanted_type)) { |
| 8519 | result->value.data.x_err_set = ira->codegen->errors_by_index.at(index); | 8513 | BigInt err_count; |
| 8520 | return result; | 8514 | bigint_init_unsigned(&err_count, ira->codegen->errors_by_index.length); |
| 8515 | |||
| 8516 | if (bigint_cmp_zero(&val->data.x_bigint) == CmpEQ || bigint_cmp(&val->data.x_bigint, &err_count) != CmpLT) { | ||
| 8517 | Buf *val_buf = buf_alloc(); | ||
| 8518 | bigint_append_buf(val_buf, &val->data.x_bigint, 10); | ||
| 8519 | ir_add_error(ira, source_instr, | ||
| 8520 | buf_sprintf("integer value %s represents no error", buf_ptr(val_buf))); | ||
| 8521 | return ira->codegen->invalid_instruction; | ||
| 8522 | } | ||
| 8523 | |||
| 8524 | size_t index = bigint_as_unsigned(&val->data.x_bigint); | ||
| 8525 | result->value.data.x_err_set = ira->codegen->errors_by_index.at(index); | ||
| 8526 | return result; | ||
| 8527 | } else { | ||
| 8528 | ErrorTableEntry *err = nullptr; | ||
| 8529 | BigInt err_int; | ||
| 8530 | |||
| 8531 | for (uint32_t i = 0, count = wanted_type->data.error_set.err_count; i < count; i += 1) { | ||
| 8532 | ErrorTableEntry *this_err = wanted_type->data.error_set.errors[i]; | ||
| 8533 | bigint_init_unsigned(&err_int, this_err->value); | ||
| 8534 | if (bigint_cmp(&val->data.x_bigint, &err_int) == CmpEQ) { | ||
| 8535 | err = this_err; | ||
| 8536 | break; | ||
| 8537 | } | ||
| 8538 | } | ||
| 8539 | |||
| 8540 | if (err == nullptr) { | ||
| 8541 | Buf *val_buf = buf_alloc(); | ||
| 8542 | bigint_append_buf(val_buf, &val->data.x_bigint, 10); | ||
| 8543 | ir_add_error(ira, source_instr, | ||
| 8544 | buf_sprintf("integer value %s represents no error in '%s'", buf_ptr(val_buf), buf_ptr(&wanted_type->name))); | ||
| 8545 | return ira->codegen->invalid_instruction; | ||
| 8546 | } | ||
| 8547 | |||
| 8548 | result->value.data.x_err_set = err; | ||
| 8549 | return result; | ||
| 8550 | } | ||
| 8521 | } | 8551 | } |
| 8522 | 8552 | ||
| 8523 | IrInstruction *result = ir_build_int_to_err(&ira->new_irb, source_instr->scope, source_instr->source_node, target); | 8553 | IrInstruction *result = ir_build_int_to_err(&ira->new_irb, source_instr->scope, source_instr->source_node, target); |
test/compile_errors.zig+32| ... | @@ -1,6 +1,38 @@ | ... | @@ -1,6 +1,38 @@ |
| 1 | const tests = @import("tests.zig"); | 1 | const tests = @import("tests.zig"); |
| 2 | 2 | ||
| 3 | pub fn addCases(cases: &tests.CompileErrorContext) void { | 3 | pub fn addCases(cases: &tests.CompileErrorContext) void { |
| 4 | cases.add("implicit cast of error set not a subset", | ||
| 5 | \\const Set1 = error{A, B}; | ||
| 6 | \\const Set2 = error{A, C}; | ||
| 7 | \\export fn entry() void { | ||
| 8 | \\ foo(Set1.B); | ||
| 9 | \\} | ||
| 10 | \\fn foo(set1: Set1) void { | ||
| 11 | \\ var x: Set2 = set1; | ||
| 12 | \\} | ||
| 13 | , | ||
| 14 | ".tmp_source.zig:7:19: error: expected 'Set2', found 'Set1'", | ||
| 15 | ".tmp_source.zig:1:23: note: 'error.B' not a member of destination error set"); | ||
| 16 | |||
| 17 | cases.add("int to err global invalid number", | ||
| 18 | \\const Set1 = error{A, B}; | ||
| 19 | \\comptime { | ||
| 20 | \\ var x: usize = 3; | ||
| 21 | \\ var y = error(x); | ||
| 22 | \\} | ||
| 23 | , | ||
| 24 | ".tmp_source.zig:4:18: error: integer value 3 represents no error"); | ||
| 25 | |||
| 26 | cases.add("int to err non global invalid number", | ||
| 27 | \\const Set1 = error{A, B}; | ||
| 28 | \\const Set2 = error{A, C}; | ||
| 29 | \\comptime { | ||
| 30 | \\ var x = usize(Set1.B); | ||
| 31 | \\ var y = Set2(x); | ||
| 32 | \\} | ||
| 33 | , | ||
| 34 | ".tmp_source.zig:5:17: error: integer value 2 represents no error in 'Set2'"); | ||
| 35 | |||
| 4 | cases.add("@memberCount of error", | 36 | cases.add("@memberCount of error", |
| 5 | \\comptime { | 37 | \\comptime { |
| 6 | \\ _ = @memberCount(error); | 38 | \\ _ = @memberCount(error); |
test/runtime_safety.zig+15-1| ... | @@ -220,7 +220,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { | ... | @@ -220,7 +220,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { |
| 220 | \\} | 220 | \\} |
| 221 | ); | 221 | ); |
| 222 | 222 | ||
| 223 | cases.addRuntimeSafety("cast integer to error and no code matches", | 223 | cases.addRuntimeSafety("cast integer to global error and no code matches", |
| 224 | \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { | 224 | \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { |
| 225 | \\ @import("std").os.exit(126); | 225 | \\ @import("std").os.exit(126); |
| 226 | \\} | 226 | \\} |
| ... | @@ -232,6 +232,20 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { | ... | @@ -232,6 +232,20 @@ pub fn addCases(cases: &tests.CompareOutputContext) void { |
| 232 | \\} | 232 | \\} |
| 233 | ); | 233 | ); |
| 234 | 234 | ||
| 235 | cases.addRuntimeSafety("cast integer to non-global error set and no match", | ||
| 236 | \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { | ||
| 237 | \\ @import("std").os.exit(126); | ||
| 238 | \\} | ||
| 239 | \\const Set1 = error{A, B}; | ||
| 240 | \\const Set2 = error{A, C}; | ||
| 241 | \\pub fn main() void { | ||
| 242 | \\ _ = foo(Set1.B); | ||
| 243 | \\} | ||
| 244 | \\fn foo(set1: Set1) Set2 { | ||
| 245 | \\ return Set2(set1); | ||
| 246 | \\} | ||
| 247 | ); | ||
| 248 | |||
| 235 | cases.addRuntimeSafety("@alignCast misaligned", | 249 | cases.addRuntimeSafety("@alignCast misaligned", |
| 236 | \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { | 250 | \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) noreturn { |
| 237 | \\ @import("std").os.exit(126); | 251 | \\ @import("std").os.exit(126); |