authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-02-08 21:54:44-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2018-02-08 21:54:44-05:00
log54c06bf7158ce52c5de8d09f109215c467a3bf6a
tree1087306b7a45c59ad1072877f34f65efd6dc2a1c
parent8fc6e31567057ca39466a34b63917f6f22f2c288

error sets: runtime safety for int-to-err and err set cast


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
1717
18foo() catch |err| switch (err) {};18foo() catch |err| switch (err) {};
1919
20// TODO this is an explicit cast and should actually coerce the type
21 erorr set casting
22 // add a runtime safety check
23
2420
25test err should be comptime if error set has 0 members21test err should be comptime if error set has 0 members
2622
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}
19601960
1961static 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
1961static LLVMValueRef ir_render_cast(CodeGen *g, IrExecutable *executable,2009static 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 casting2133 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);
21552205
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 }
21842209
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);
85078507
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 }
85178511
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 }
85228552
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 @@
1const tests = @import("tests.zig");1const tests = @import("tests.zig");
22
3pub fn addCases(cases: &tests.CompileErrorContext) void {3pub 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 );
222222
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 );
234234
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);