authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2016-04-26 11:35:56-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2016-04-26 11:36:11-07:00
logd1fa5692c685b804181d4658afce1e53ca74ec19
tree18d0c57776cdc470fa918e3e5fa18fdca80aa09d
parent61e6c49bc537a1c8a8da1d8a0e777261eef74cef

add array bounds checking in debug mode

closes #27

5 files changed, 216 insertions(+), 55 deletions(-)

src/all_types.hpp+3
......@@ -1051,6 +1051,7 @@ struct FnTableEntry {
10511051 bool is_extern;
10521052 bool is_test;
10531053 bool is_pure;
1054 bool safety_off;
10541055 BlockContext *parent_block_context;
10551056 FnAnalState anal_state;
10561057
......@@ -1315,6 +1316,8 @@ struct BlockContext {
13151316
13161317 // if this is true, then this code will not be generated
13171318 bool codegen_excluded;
1319
1320 bool safety_off;
13181321};
13191322
13201323
src/analyze.cpp+14
......@@ -993,6 +993,18 @@ static void resolve_function_proto(CodeGen *g, AstNode *node, FnTableEntry *fn_t
993993 add_node_error(g, directive_node,
994994 buf_sprintf("invalid function attribute: '%s'", buf_ptr(name)));
995995 }
996 } else if (buf_eql_str(name, "debug_safety")) {
997 if (fn_table_entry->is_extern) {
998 add_node_error(g, directive_node,
999 buf_sprintf("#debug_safety invalid on extern functions"));
1000 } else {
1001 bool enable;
1002 bool ok = resolve_const_expr_bool(g, import, import->block_context,
1003 &directive_node->data.directive.expr, &enable);
1004 if (ok && !enable) {
1005 fn_table_entry->safety_off = true;
1006 }
1007 }
9961008 } else if (buf_eql_str(name, "condition")) {
9971009 if (fn_proto->top_level_decl.visib_mod == VisibModExport) {
9981010 bool include;
......@@ -2102,11 +2114,13 @@ BlockContext *new_block_context(AstNode *node, BlockContext *parent) {
21022114 context->parent_loop_node = parent->parent_loop_node;
21032115 context->c_import_buf = parent->c_import_buf;
21042116 context->codegen_excluded = parent->codegen_excluded;
2117 context->safety_off = parent->safety_off;
21052118 }
21062119
21072120 if (node && node->type == NodeTypeFnDef) {
21082121 AstNode *fn_proto_node = node->data.fn_def.fn_proto;
21092122 context->fn_entry = fn_proto_node->data.fn_proto.fn_table_entry;
2123 context->safety_off = context->fn_entry->safety_off;
21102124 } else if (parent) {
21112125 context->fn_entry = parent->fn_entry;
21122126 }
src/codegen.cpp+98-32
......@@ -330,6 +330,46 @@ static LLVMValueRef get_handle_value(CodeGen *g, AstNode *source_node, LLVMValue
330330 }
331331}
332332
333static bool want_debug_safety(CodeGen *g, AstNode *node) {
334 return !g->is_release_build && !node->block_context->safety_off;
335}
336
337static void add_bounds_check(CodeGen *g, AstNode *source_node, LLVMValueRef target_val,
338 LLVMIntPredicate lower_pred, LLVMValueRef lower_value,
339 LLVMIntPredicate upper_pred, LLVMValueRef upper_value)
340{
341 if (!lower_value && !upper_value) {
342 return;
343 }
344 if (upper_value && !lower_value) {
345 lower_value = upper_value;
346 lower_pred = upper_pred;
347 upper_value = nullptr;
348 }
349
350 add_debug_source_node(g, source_node);
351
352 LLVMBasicBlockRef bounds_check_fail_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "BoundsCheckFail");
353 LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "BoundsCheckOk");
354 LLVMBasicBlockRef lower_ok_block = upper_value ?
355 LLVMAppendBasicBlock(g->cur_fn->fn_value, "FirstBoundsCheckOk") : ok_block;
356
357 LLVMValueRef lower_ok_val = LLVMBuildICmp(g->builder, lower_pred, target_val, lower_value, "");
358 LLVMBuildCondBr(g->builder, lower_ok_val, lower_ok_block, bounds_check_fail_block);
359
360 LLVMPositionBuilderAtEnd(g->builder, bounds_check_fail_block);
361 LLVMBuildCall(g->builder, g->trap_fn_val, nullptr, 0, "");
362 LLVMBuildUnreachable(g->builder);
363
364 if (upper_value) {
365 LLVMPositionBuilderAtEnd(g->builder, lower_ok_block);
366 LLVMValueRef upper_ok_val = LLVMBuildICmp(g->builder, upper_pred, target_val, upper_value, "");
367 LLVMBuildCondBr(g->builder, upper_ok_val, ok_block, bounds_check_fail_block);
368 }
369
370 LLVMPositionBuilderAtEnd(g->builder, ok_block);
371}
372
333373static LLVMValueRef gen_err_name(CodeGen *g, AstNode *node) {
334374 assert(node->type == NodeTypeFnCallExpr);
335375 assert(g->generate_error_name_table);
......@@ -344,25 +384,10 @@ static LLVMValueRef gen_err_name(CodeGen *g, AstNode *node) {
344384 LLVMValueRef err_val = gen_expr(g, err_val_node);
345385 add_debug_source_node(g, node);
346386
347 if (!g->is_release_build) {
348 LLVMBasicBlockRef bounds_check_fail_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "BoundsCheckFail");
349 LLVMBasicBlockRef lower_ok_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "LowerBoundsCheckOk");
350 LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "BoundsCheckOk");
351
387 if (want_debug_safety(g, node)) {
352388 LLVMValueRef zero = LLVMConstNull(LLVMTypeOf(err_val));
353 LLVMValueRef is_zero_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, "");
354 LLVMBuildCondBr(g->builder, is_zero_val, bounds_check_fail_block, lower_ok_block);
355
356 LLVMPositionBuilderAtEnd(g->builder, bounds_check_fail_block);
357 LLVMBuildCall(g->builder, g->trap_fn_val, nullptr, 0, "");
358 LLVMBuildUnreachable(g->builder);
359
360 LLVMPositionBuilderAtEnd(g->builder, lower_ok_block);
361389 LLVMValueRef end_val = LLVMConstInt(LLVMTypeOf(err_val), g->error_decls.length, false);
362 LLVMValueRef is_too_big_val = LLVMBuildICmp(g->builder, LLVMIntUGE, err_val, end_val, "");
363 LLVMBuildCondBr(g->builder, is_too_big_val, bounds_check_fail_block, ok_block);
364
365 LLVMPositionBuilderAtEnd(g->builder, ok_block);
390 add_bounds_check(g, node, err_val, LLVMIntNE, zero, LLVMIntULT, end_val);
366391 }
367392
368393 LLVMValueRef indices[] = {
......@@ -869,6 +894,11 @@ static LLVMValueRef gen_array_elem_ptr(CodeGen *g, AstNode *source_node, LLVMVal
869894 }
870895
871896 if (array_type->id == TypeTableEntryIdArray) {
897 if (want_debug_safety(g, source_node)) {
898 LLVMValueRef end = LLVMConstInt(g->builtin_types.entry_isize->type_ref,
899 array_type->data.array.len, false);
900 add_bounds_check(g, source_node, subscript_value, LLVMIntEQ, nullptr, LLVMIntULT, end);
901 }
872902 LLVMValueRef indices[] = {
873903 LLVMConstNull(g->builtin_types.entry_isize->type_ref),
874904 subscript_value
......@@ -887,6 +917,15 @@ static LLVMValueRef gen_array_elem_ptr(CodeGen *g, AstNode *source_node, LLVMVal
887917 assert(LLVMGetTypeKind(LLVMTypeOf(array_ptr)) == LLVMPointerTypeKind);
888918 assert(LLVMGetTypeKind(LLVMGetElementType(LLVMTypeOf(array_ptr))) == LLVMStructTypeKind);
889919
920 if (want_debug_safety(g, source_node)) {
921 add_debug_source_node(g, source_node);
922 int len_index = array_type->data.structure.fields[1].gen_index;
923 assert(len_index >= 0);
924 LLVMValueRef len_ptr = LLVMBuildStructGEP(g->builder, array_ptr, len_index, "");
925 LLVMValueRef len = LLVMBuildLoad(g->builder, len_ptr, "");
926 add_bounds_check(g, source_node, subscript_value, LLVMIntEQ, nullptr, LLVMIntULT, len);
927 }
928
890929 add_debug_source_node(g, source_node);
891930 int ptr_index = array_type->data.structure.fields[0].gen_index;
892931 assert(ptr_index >= 0);
......@@ -907,7 +946,6 @@ static LLVMValueRef gen_array_ptr(CodeGen *g, AstNode *node) {
907946 LLVMValueRef array_ptr = gen_array_base_ptr(g, array_expr_node);
908947
909948 LLVMValueRef subscript_value = gen_expr(g, node->data.array_access_expr.subscript);
910
911949 return gen_array_elem_ptr(g, node, array_ptr, array_type, subscript_value);
912950}
913951
......@@ -969,6 +1007,15 @@ static LLVMValueRef gen_slice_expr(CodeGen *g, AstNode *node) {
9691007 end_val = LLVMConstInt(g->builtin_types.entry_isize->type_ref, array_type->data.array.len, false);
9701008 }
9711009
1010 if (want_debug_safety(g, node)) {
1011 add_bounds_check(g, node, start_val, LLVMIntEQ, nullptr, LLVMIntULE, end_val);
1012 if (node->data.slice_expr.end) {
1013 LLVMValueRef array_end = LLVMConstInt(g->builtin_types.entry_isize->type_ref,
1014 array_type->data.array.len, false);
1015 add_bounds_check(g, node, end_val, LLVMIntEQ, nullptr, LLVMIntULE, array_end);
1016 }
1017 }
1018
9721019 add_debug_source_node(g, node);
9731020 LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, 0, "");
9741021 LLVMValueRef indices[] = {
......@@ -987,6 +1034,10 @@ static LLVMValueRef gen_slice_expr(CodeGen *g, AstNode *node) {
9871034 LLVMValueRef start_val = gen_expr(g, node->data.slice_expr.start);
9881035 LLVMValueRef end_val = gen_expr(g, node->data.slice_expr.end);
9891036
1037 if (want_debug_safety(g, node)) {
1038 add_bounds_check(g, node, start_val, LLVMIntEQ, nullptr, LLVMIntULE, end_val);
1039 }
1040
9901041 add_debug_source_node(g, node);
9911042 LLVMValueRef ptr_field_ptr = LLVMBuildStructGEP(g->builder, tmp_struct_ptr, 0, "");
9921043 LLVMValueRef slice_start_ptr = LLVMBuildInBoundsGEP(g->builder, array_ptr, &start_val, 1, "");
......@@ -1002,22 +1053,33 @@ static LLVMValueRef gen_slice_expr(CodeGen *g, AstNode *node) {
10021053 assert(LLVMGetTypeKind(LLVMTypeOf(array_ptr)) == LLVMPointerTypeKind);
10031054 assert(LLVMGetTypeKind(LLVMGetElementType(LLVMTypeOf(array_ptr))) == LLVMStructTypeKind);
10041055
1056 int ptr_index = array_type->data.structure.fields[0].gen_index;
1057 assert(ptr_index >= 0);
1058 int len_index = array_type->data.structure.fields[1].gen_index;
1059 assert(len_index >= 0);
1060
1061 LLVMValueRef prev_end = nullptr;
1062 if (!node->data.slice_expr.end || want_debug_safety(g, node)) {
1063 add_debug_source_node(g, node);
1064 LLVMValueRef src_len_ptr = LLVMBuildStructGEP(g->builder, array_ptr, len_index, "");
1065 prev_end = LLVMBuildLoad(g->builder, src_len_ptr, "");
1066 }
1067
10051068 LLVMValueRef start_val = gen_expr(g, node->data.slice_expr.start);
10061069 LLVMValueRef end_val;
10071070 if (node->data.slice_expr.end) {
10081071 end_val = gen_expr(g, node->data.slice_expr.end);
10091072 } else {
1010 add_debug_source_node(g, node);
1011 int len_index = array_type->data.structure.fields[1].gen_index;
1012 assert(len_index >= 0);
1013 LLVMValueRef src_len_ptr = LLVMBuildStructGEP(g->builder, array_ptr, len_index, "");
1014 end_val = LLVMBuildLoad(g->builder, src_len_ptr, "");
1073 end_val = prev_end;
10151074 }
10161075
1017 int ptr_index = array_type->data.structure.fields[0].gen_index;
1018 assert(ptr_index >= 0);
1019 int len_index = array_type->data.structure.fields[1].gen_index;
1020 assert(len_index >= 0);
1076 if (want_debug_safety(g, node)) {
1077 assert(prev_end);
1078 add_bounds_check(g, node, start_val, LLVMIntEQ, nullptr, LLVMIntULE, end_val);
1079 if (node->data.slice_expr.end) {
1080 add_bounds_check(g, node, end_val, LLVMIntEQ, nullptr, LLVMIntULE, prev_end);
1081 }
1082 }
10211083
10221084 add_debug_source_node(g, node);
10231085 LLVMValueRef src_ptr_ptr = LLVMBuildStructGEP(g->builder, array_ptr, ptr_index, "");
......@@ -1225,7 +1287,7 @@ static LLVMValueRef gen_prefix_op_expr(CodeGen *g, AstNode *node) {
12251287 assert(expr_type->id == TypeTableEntryIdErrorUnion);
12261288 TypeTableEntry *child_type = expr_type->data.error.child_type;
12271289
1228 if (!g->is_release_build) {
1290 if (want_debug_safety(g, node)) {
12291291 LLVMValueRef err_val;
12301292 if (type_has_bits(child_type)) {
12311293 add_debug_source_node(g, node);
......@@ -1263,7 +1325,7 @@ static LLVMValueRef gen_prefix_op_expr(CodeGen *g, AstNode *node) {
12631325 assert(expr_type->id == TypeTableEntryIdMaybe);
12641326 TypeTableEntry *child_type = expr_type->data.maybe.child_type;
12651327
1266 if (!g->is_release_build) {
1328 if (want_debug_safety(g, node)) {
12671329 add_debug_source_node(g, node);
12681330 LLVMValueRef cond_val;
12691331 if (child_type->id == TypeTableEntryIdPointer ||
......@@ -2261,7 +2323,7 @@ static LLVMValueRef gen_container_init_expr(CodeGen *g, AstNode *node) {
22612323 } else if (type_entry->id == TypeTableEntryIdUnreachable) {
22622324 assert(node->data.container_init_expr.entries.length == 0);
22632325 add_debug_source_node(g, node);
2264 if (!g->is_release_build) {
2326 if (want_debug_safety(g, node)) {
22652327 LLVMBuildCall(g->builder, g->trap_fn_val, nullptr, 0, "");
22662328 }
22672329 LLVMBuildUnreachable(g->builder);
......@@ -2575,7 +2637,7 @@ static LLVMValueRef gen_var_decl_raw(CodeGen *g, AstNode *source_node, AstNodeVa
25752637 }
25762638 }
25772639 }
2578 if (!ignore_uninit && !g->is_release_build) {
2640 if (!ignore_uninit && want_debug_safety(g, source_node)) {
25792641 TypeTableEntry *isize = g->builtin_types.entry_isize;
25802642 uint64_t size_bytes = LLVMStoreSizeOfType(g->target_data_ref, variable->type->type_ref);
25812643 uint64_t align_bytes = get_memcpy_align(g, variable->type);
......@@ -2790,7 +2852,7 @@ static LLVMValueRef gen_switch_expr(CodeGen *g, AstNode *node) {
27902852 if (!else_prong) {
27912853 LLVMPositionBuilderAtEnd(g->builder, else_block);
27922854 add_debug_source_node(g, node);
2793 if (!g->is_release_build) {
2855 if (want_debug_safety(g, node)) {
27942856 LLVMBuildCall(g->builder, g->trap_fn_val, nullptr, 0, "");
27952857 }
27962858 LLVMBuildUnreachable(g->builder);
......@@ -3383,6 +3445,10 @@ static void do_code_gen(CodeGen *g) {
33833445
33843446 // Generate the list of test function pointers.
33853447 if (g->is_test_build) {
3448 if (g->test_fn_count == 0) {
3449 fprintf(stderr, "No tests to run.\n");
3450 exit(0);
3451 }
33863452 assert(g->test_fn_count > 0);
33873453 assert(next_test_index == g->test_fn_count);
33883454
std/builtin.zig+2
......@@ -1,6 +1,7 @@
11// These functions are provided when not linking against libc because LLVM
22// sometimes generates code that calls them.
33
4#debug_safety(false)
45export fn memset(dest: &u8, c: u8, n: isize) -> &u8 {
56 var index : @typeof(n) = 0;
67 while (index != n) {
......@@ -10,6 +11,7 @@ export fn memset(dest: &u8, c: u8, n: isize) -> &u8 {
1011 return dest;
1112}
1213
14#debug_safety(false)
1315export fn memcpy(noalias dest: &u8, noalias src: &const u8, n: isize) -> &u8 {
1416 var index : @typeof(n) = 0;
1517 while (index != n) {
test/run_tests.cpp+99-23
......@@ -27,6 +27,7 @@ struct TestCase {
2727 ZigList<const char *> program_args;
2828 bool is_parseh;
2929 bool is_self_hosted;
30 bool is_debug_safety;
3031};
3132
3233static ZigList<TestCase*> test_cases = {0};
......@@ -122,6 +123,55 @@ static TestCase *add_compile_fail_case(const char *case_name, const char *source
122123 return test_case;
123124}
124125
126static void add_debug_safety_case(const char *case_name, const char *source) {
127 {
128 TestCase *test_case = allocate<TestCase>(1);
129 test_case->is_debug_safety = true;
130 test_case->case_name = buf_ptr(buf_sprintf("%s (debug)", case_name));
131 test_case->source_files.resize(1);
132 test_case->source_files.at(0).relative_path = tmp_source_path;
133 test_case->source_files.at(0).source_code = source;
134
135 test_case->compiler_args.append("build");
136 test_case->compiler_args.append(tmp_source_path);
137
138 test_case->compiler_args.append("--name");
139 test_case->compiler_args.append("test");
140
141 test_case->compiler_args.append("--export");
142 test_case->compiler_args.append("exe");
143
144 test_case->compiler_args.append("--output");
145 test_case->compiler_args.append(tmp_exe_path);
146
147 test_cases.append(test_case);
148 }
149 {
150 TestCase *test_case = allocate<TestCase>(1);
151 test_case->case_name = buf_ptr(buf_sprintf("%s (release)", case_name));
152 test_case->source_files.resize(1);
153 test_case->source_files.at(0).relative_path = tmp_source_path;
154 test_case->source_files.at(0).source_code = source;
155 test_case->output = "";
156
157 test_case->compiler_args.append("build");
158 test_case->compiler_args.append(tmp_source_path);
159
160 test_case->compiler_args.append("--name");
161 test_case->compiler_args.append("test");
162
163 test_case->compiler_args.append("--export");
164 test_case->compiler_args.append("exe");
165
166 test_case->compiler_args.append("--output");
167 test_case->compiler_args.append(tmp_exe_path);
168
169 test_case->compiler_args.append("--release");
170
171 test_cases.append(test_case);
172 }
173}
174
125175static TestCase *add_parseh_case(const char *case_name, const char *source, int count, ...) {
126176 va_list ap;
127177 va_start(ap, count);
......@@ -1247,6 +1297,22 @@ fn bar() -> i32 { 2 }
12471297 )SOURCE", 1, ".tmp_source.zig:3:15: error: unable to infer expression type");
12481298}
12491299
1300static void add_debug_safety_test_cases(void) {
1301 add_debug_safety_case("out of bounds slice access", R"SOURCE(
1302pub fn main(args: [][]u8) -> %void {
1303 const a = []i32{1, 2, 3, 4};
1304 baz(bar(a));
1305}
1306#static_eval_enable(false)
1307fn bar(a: []i32) -> i32 {
1308 a[4]
1309}
1310#static_eval_enable(false)
1311fn baz(a: i32) {}
1312 )SOURCE");
1313
1314}
1315
12501316//////////////////////////////////////////////////////////////////////////////
12511317
12521318static void add_parseh_test_cases(void) {
......@@ -1455,6 +1521,14 @@ static void print_compiler_invocation(TestCase *test_case) {
14551521 printf("\n");
14561522}
14571523
1524static void print_exe_invocation(TestCase *test_case) {
1525 printf("%s", tmp_exe_path);
1526 for (int i = 0; i < test_case->program_args.length; i += 1) {
1527 printf(" %s", test_case->program_args.at(i));
1528 }
1529 printf("\n");
1530}
1531
14581532static void run_test(TestCase *test_case) {
14591533 if (test_case->is_self_hosted) {
14601534 return run_self_hosted_test();
......@@ -1531,32 +1605,33 @@ static void run_test(TestCase *test_case) {
15311605 Buf program_stdout = BUF_INIT;
15321606 os_exec_process(tmp_exe_path, test_case->program_args, &return_code, &program_stderr, &program_stdout);
15331607
1534 if (return_code != 0) {
1535 printf("\nProgram exited with return code %d:\n", return_code);
1536 print_compiler_invocation(test_case);
1537 printf("%s", tmp_exe_path);
1538 for (int i = 0; i < test_case->program_args.length; i += 1) {
1539 printf(" %s", test_case->program_args.at(i));
1608 if (test_case->is_debug_safety) {
1609 if (return_code == 0) {
1610 printf("\nProgram expected to hit debug trap but exited with return code 0\n");
1611 print_compiler_invocation(test_case);
1612 print_exe_invocation(test_case);
1613 exit(1);
1614 }
1615 } else {
1616 if (return_code != 0) {
1617 printf("\nProgram exited with return code %d:\n", return_code);
1618 print_compiler_invocation(test_case);
1619 print_exe_invocation(test_case);
1620 printf("%s\n", buf_ptr(&program_stderr));
1621 exit(1);
15401622 }
1541 printf("\n");
1542 printf("%s\n", buf_ptr(&program_stderr));
1543 exit(1);
1544 }
15451623
1546 if (!buf_eql_str(&program_stdout, test_case->output)) {
1547 printf("\n");
1548 print_compiler_invocation(test_case);
1549 printf("%s", tmp_exe_path);
1550 for (int i = 0; i < test_case->program_args.length; i += 1) {
1551 printf(" %s", test_case->program_args.at(i));
1624 if (!buf_eql_str(&program_stdout, test_case->output)) {
1625 printf("\n");
1626 print_compiler_invocation(test_case);
1627 print_exe_invocation(test_case);
1628 printf("==== Test failed. Expected output: ====\n");
1629 printf("%s\n", test_case->output);
1630 printf("========= Actual output: ==============\n");
1631 printf("%s\n", buf_ptr(&program_stdout));
1632 printf("=======================================\n");
1633 exit(1);
15521634 }
1553 printf("\n");
1554 printf("==== Test failed. Expected output: ====\n");
1555 printf("%s\n", test_case->output);
1556 printf("========= Actual output: ==============\n");
1557 printf("%s\n", buf_ptr(&program_stdout));
1558 printf("=======================================\n");
1559 exit(1);
15601635 }
15611636 }
15621637
......@@ -1606,6 +1681,7 @@ int main(int argc, char **argv) {
16061681 }
16071682 }
16081683 add_compiling_test_cases();
1684 add_debug_safety_test_cases();
16091685 add_compile_failure_test_cases();
16101686 add_parseh_test_cases();
16111687 add_self_hosted_tests();