From eb83111f0258ee41af021091bb78b3b5e5f6f3d3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 7 May 2016 19:58:02 -0700 Subject: [PATCH] add debug safety for division See #149 --- src/codegen.cpp | 49 +++++++++++++++++++++++++++++++++---------- src/link.cpp | 14 +++++++++---- src/main.cpp | 8 +++---- src/os.cpp | 50 +++++++++++++++++++++++++++++++------------- src/os.hpp | 17 +++++++++++++-- test/run_tests.cpp | 46 ++++++++++++++++++++++++++++------------ test/self_hosted.zig | 9 ++++++++ 7 files changed, 144 insertions(+), 49 deletions(-) diff --git a/src/codegen.cpp b/src/codegen.cpp index 4d86e85df3..79601a3865 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -1565,6 +1565,43 @@ static LLVMValueRef gen_prefix_op_expr(CodeGen *g, AstNode *node) { zig_unreachable(); } +static LLVMValueRef gen_div(CodeGen *g, AstNode *source_node, LLVMValueRef val1, LLVMValueRef val2, + TypeTableEntry *type_entry) +{ + set_debug_source_node(g, source_node); + + if (want_debug_safety(g, source_node)) { + LLVMValueRef zero = LLVMConstNull(type_entry->type_ref); + LLVMValueRef is_zero_bit; + if (type_entry->id == TypeTableEntryIdInt) { + is_zero_bit = LLVMBuildICmp(g->builder, LLVMIntEQ, val2, zero, ""); + } else if (type_entry->id == TypeTableEntryIdFloat) { + is_zero_bit = LLVMBuildFCmp(g->builder, LLVMRealOEQ, val2, zero, ""); + } else { + zig_unreachable(); + } + LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "DivZeroOk"); + LLVMBasicBlockRef fail_block = LLVMAppendBasicBlock(g->cur_fn->fn_value, "DivZeroFail"); + LLVMBuildCondBr(g->builder, is_zero_bit, fail_block, ok_block); + + LLVMPositionBuilderAtEnd(g->builder, fail_block); + gen_debug_safety_crash(g); + + LLVMPositionBuilderAtEnd(g->builder, ok_block); + } + + if (type_entry->id == TypeTableEntryIdFloat) { + return LLVMBuildFDiv(g->builder, val1, val2, ""); + } else { + assert(type_entry->id == TypeTableEntryIdInt); + if (type_entry->data.integral.is_signed) { + return LLVMBuildSDiv(g->builder, val1, val2, ""); + } else { + return LLVMBuildUDiv(g->builder, val1, val2, ""); + } + } +} + static LLVMValueRef gen_arithmetic_bin_op(CodeGen *g, AstNode *source_node, LLVMValueRef val1, LLVMValueRef val2, TypeTableEntry *op1_type, TypeTableEntry *op2_type, @@ -1665,17 +1702,7 @@ static LLVMValueRef gen_arithmetic_bin_op(CodeGen *g, AstNode *source_node, } case BinOpTypeDiv: case BinOpTypeAssignDiv: - set_debug_source_node(g, source_node); - if (op1_type->id == TypeTableEntryIdFloat) { - return LLVMBuildFDiv(g->builder, val1, val2, ""); - } else { - assert(op1_type->id == TypeTableEntryIdInt); - if (op1_type->data.integral.is_signed) { - return LLVMBuildSDiv(g->builder, val1, val2, ""); - } else { - return LLVMBuildUDiv(g->builder, val1, val2, ""); - } - } + return gen_div(g, source_node, val1, val2, op1_type); case BinOpTypeMod: case BinOpTypeAssignMod: set_debug_source_node(g, source_node); diff --git a/src/link.cpp b/src/link.cpp index c7c4b2bb68..a2fab50373 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -821,17 +821,23 @@ void codegen_link(CodeGen *g, const char *out_file) { fprintf(stderr, "\n"); } - int return_code; Buf ld_stderr = BUF_INIT; Buf ld_stdout = BUF_INIT; - int err = os_exec_process(buf_ptr(g->linker_path), lj.args, &return_code, &ld_stderr, &ld_stdout); + Termination term; + int err = os_exec_process(buf_ptr(g->linker_path), lj.args, &term, &ld_stderr, &ld_stdout); if (err) { fprintf(stderr, "linker not found: '%s'\n", buf_ptr(g->linker_path)); exit(1); } - if (return_code != 0) { - fprintf(stderr, "linker failed with return code %d\n", return_code); + if (term.how != TerminationIdClean || term.code != 0) { + if (term.how == TerminationIdClean) { + fprintf(stderr, "linker failed with return code %d\n", term.code); + } else if (term.how == TerminationIdSignaled) { + fprintf(stderr, "linker failed with signal %d\n", term.code); + } else { + fprintf(stderr, "linker failed\n"); + } fprintf(stderr, "%s ", buf_ptr(g->linker_path)); for (int i = 0; i < lj.args.length; i += 1) { fprintf(stderr, "%s ", lj.args.at(i)); diff --git a/src/main.cpp b/src/main.cpp index 564cad1758..798ef5af52 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -395,13 +395,13 @@ int main(int argc, char **argv) { codegen_add_root_code(g, &root_source_dir, &root_source_name, &root_source_code); codegen_link(g, "./test"); ZigList args = {0}; - int return_code; - os_spawn_process("./test", args, &return_code); - if (return_code != 0) { + Termination term; + os_spawn_process("./test", args, &term); + if (term.how != TerminationIdClean || term.code != 0) { fprintf(stderr, "\nTests failed. Use the following command to reproduce the failure:\n"); fprintf(stderr, "./test\n"); } - return return_code; + return (term.how == TerminationIdClean) ? term.code : -1; } else { zig_unreachable(); } diff --git a/src/os.cpp b/src/os.cpp index 4b5d898aa7..3e9ab27f8b 100644 --- a/src/os.cpp +++ b/src/os.cpp @@ -48,7 +48,23 @@ #if defined(ZIG_OS_POSIX) -static void os_spawn_process_posix(const char *exe, ZigList &args, int *return_code) { +static void populate_termination(Termination *term, int status) { + if (WIFEXITED(status)) { + term->how = TerminationIdClean; + term->code = WEXITSTATUS(status); + } else if (WIFSIGNALED(status)) { + term->how = TerminationIdSignaled; + term->code = WTERMSIG(status); + } else if (WIFSTOPPED(status)) { + term->how = TerminationIdStopped; + term->code = WSTOPSIG(status); + } else { + term->how = TerminationIdUnknown; + term->code = status; + } +} + +static void os_spawn_process_posix(const char *exe, ZigList &args, Termination *term) { pid_t pid = fork(); if (pid == -1) zig_panic("fork failed"); @@ -64,28 +80,30 @@ static void os_spawn_process_posix(const char *exe, ZigList &args, zig_panic("execvp failed: %s", strerror(errno)); } else { // parent - waitpid(pid, return_code, 0); + int status; + waitpid(pid, &status, 0); + populate_termination(term, status); } } #endif #if defined(ZIG_OS_WINDOWS) -static void os_spawn_process_windows(const char *exe, ZigList &args, int *return_code) { +static void os_spawn_process_windows(const char *exe, ZigList &args, Termination *term) { Buf stderr_buf = BUF_INIT; Buf stdout_buf = BUF_INIT; // TODO this is supposed to inherit stdout/stderr instead of capturing it - os_exec_process(exe, args, return_code, &stderr_buf, &stdout_buf); + os_exec_process(exe, args, term, &stderr_buf, &stdout_buf); fwrite(buf_ptr(&stderr_buf), 1, buf_len(&stderr_buf), stderr); fwrite(buf_ptr(&stdout_buf), 1, buf_len(&stdout_buf), stdout); } #endif -void os_spawn_process(const char *exe, ZigList &args, int *return_code) { +void os_spawn_process(const char *exe, ZigList &args, Termination *term) { #if defined(ZIG_OS_WINDOWS) - os_spawn_process_windows(exe, args, return_code); + os_spawn_process_windows(exe, args, term); #elif defined(ZIG_OS_POSIX) - os_spawn_process_posix(exe, args, return_code); + os_spawn_process_posix(exe, args, term); #else #error "missing os_spawn_process implementation" #endif @@ -195,10 +213,9 @@ int os_fetch_file(FILE *f, Buf *out_buf) { zig_unreachable(); } - #if defined(ZIG_OS_POSIX) static int os_exec_process_posix(const char *exe, ZigList &args, - int *return_code, Buf *out_stderr, Buf *out_stdout) + Termination *term, Buf *out_stderr, Buf *out_stdout) { int stdin_pipe[2]; int stdout_pipe[2]; @@ -244,7 +261,9 @@ static int os_exec_process_posix(const char *exe, ZigList &args, close(stdout_pipe[1]); close(stderr_pipe[1]); - waitpid(pid, return_code, 0); + int status; + waitpid(pid, &status, 0); + populate_termination(term, status); os_fetch_file(fdopen(stdout_pipe[0], "rb"), out_stdout); os_fetch_file(fdopen(stderr_pipe[0], "rb"), out_stderr); @@ -269,7 +288,7 @@ static void win32_panic(const char *str) { */ static int os_exec_process_windows(const char *exe, ZigList &args, - int *return_code, Buf *out_stderr, Buf *out_stdout) + Termination *term, Buf *out_stderr, Buf *out_stdout) { Buf command_line = BUF_INIT; buf_resize(&command_line, 0); @@ -391,7 +410,8 @@ static int os_exec_process_windows(const char *exe, ZigList &args, if (!GetExitCodeProcess(piProcInfo.hProcess, &exit_code)) { zig_panic("GetExitCodeProcess failed"); } - *return_code = exit_code; + term->how == TerminationIdClean; + term->code = exit_code; CloseHandle(piProcInfo.hProcess); CloseHandle(piProcInfo.hThread); @@ -401,12 +421,12 @@ static int os_exec_process_windows(const char *exe, ZigList &args, #endif int os_exec_process(const char *exe, ZigList &args, - int *return_code, Buf *out_stderr, Buf *out_stdout) + Termination *term, Buf *out_stderr, Buf *out_stdout) { #if defined(ZIG_OS_WINDOWS) - return os_exec_process_windows(exe, args, return_code, out_stderr, out_stdout); + return os_exec_process_windows(exe, args, term, out_stderr, out_stdout); #elif defined(ZIG_OS_POSIX) - return os_exec_process_posix(exe, args, return_code, out_stderr, out_stdout); + return os_exec_process_posix(exe, args, term, out_stderr, out_stdout); #else #error "missing os_exec_process implementation" #endif diff --git a/src/os.hpp b/src/os.hpp index f545170138..70456aac18 100644 --- a/src/os.hpp +++ b/src/os.hpp @@ -13,10 +13,23 @@ #include +enum TerminationId { + TerminationIdClean, + TerminationIdSignaled, + TerminationIdStopped, + TerminationIdUnknown, +}; + +struct Termination { + TerminationId how; + int code; +}; + + void os_init(void); -void os_spawn_process(const char *exe, ZigList &args, int *return_code); +void os_spawn_process(const char *exe, ZigList &args, Termination *term); int os_exec_process(const char *exe, ZigList &args, - int *return_code, Buf *out_stderr, Buf *out_stdout); + Termination *term, Buf *out_stderr, Buf *out_stdout); void os_path_dirname(Buf *full_path, Buf *out_dirname); void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename); diff --git a/test/run_tests.cpp b/test/run_tests.cpp index b37147597a..44b2c93e83 100644 --- a/test/run_tests.cpp +++ b/test/run_tests.cpp @@ -1446,6 +1446,16 @@ fn shl(a: u16, b: u16) -> u16 { } )SOURCE"); + add_debug_safety_case("integer division by zero", R"SOURCE( +pub fn main(args: [][]u8) -> %void { + div0(999, 0); +} +#static_eval_enable(false) +fn div0(a: i32, b: i32) -> i32 { + a / b +} + )SOURCE"); + } ////////////////////////////////////////////////////////////////////////////// @@ -1627,16 +1637,16 @@ struct type { static void run_self_hosted_test(bool is_release_mode) { Buf zig_stderr = BUF_INIT; Buf zig_stdout = BUF_INIT; - int return_code; ZigList args = {0}; args.append("test"); args.append("../test/self_hosted.zig"); if (is_release_mode) { args.append("--release"); } - os_exec_process(zig_exe, args, &return_code, &zig_stderr, &zig_stdout); + Termination term; + os_exec_process(zig_exe, args, &term, &zig_stderr, &zig_stdout); - if (return_code) { + if (term.how != TerminationIdClean) { printf("\nSelf-hosted tests failed:\n"); printf("./zig"); for (int i = 0; i < args.length; i += 1) { @@ -1694,14 +1704,14 @@ static void run_test(TestCase *test_case) { Buf zig_stderr = BUF_INIT; Buf zig_stdout = BUF_INIT; - int return_code; int err; - if ((err = os_exec_process(zig_exe, test_case->compiler_args, &return_code, &zig_stderr, &zig_stdout))) { + Termination term; + if ((err = os_exec_process(zig_exe, test_case->compiler_args, &term, &zig_stderr, &zig_stdout))) { fprintf(stderr, "Unable to exec %s: %s\n", zig_exe, err_str(err)); } if (!test_case->is_parseh && test_case->compile_errors.length) { - if (return_code) { + if (term.how != TerminationIdClean || term.code != 0) { for (int i = 0; i < test_case->compile_errors.length; i += 1) { const char *err_text = test_case->compile_errors.at(i); if (!strstr(buf_ptr(&zig_stderr), err_text)) { @@ -1723,8 +1733,8 @@ static void run_test(TestCase *test_case) { } } - if (return_code != 0) { - printf("\nCompile failed with return code %d:\n", return_code); + if (term.how != TerminationIdClean || term.code != 0) { + printf("\nCompile failed:\n"); print_compiler_invocation(test_case); printf("%s\n", buf_ptr(&zig_stderr)); exit(1); @@ -1754,18 +1764,28 @@ static void run_test(TestCase *test_case) { } else { Buf program_stderr = BUF_INIT; Buf program_stdout = BUF_INIT; - os_exec_process(tmp_exe_path, test_case->program_args, &return_code, &program_stderr, &program_stdout); + os_exec_process(tmp_exe_path, test_case->program_args, &term, &program_stderr, &program_stdout); if (test_case->is_debug_safety) { - if (return_code == 0) { - printf("\nProgram expected to hit debug trap but exited with return code 0\n"); + int debug_trap_signal = 5; + if (term.how != TerminationIdSignaled || term.code != debug_trap_signal) { + if (term.how == TerminationIdClean) { + printf("\nProgram expected to hit debug trap (signal %d) but exited with return code %d\n", + debug_trap_signal, term.code); + } else if (term.how == TerminationIdSignaled) { + printf("\nProgram expected to hit debug trap (signal %d) but signaled with code %d\n", + debug_trap_signal, term.code); + } else { + printf("\nProgram expected to hit debug trap (signal %d) exited in an unexpected way\n", + debug_trap_signal); + } print_compiler_invocation(test_case); print_exe_invocation(test_case); exit(1); } } else { - if (return_code != 0) { - printf("\nProgram exited with return code %d:\n", return_code); + if (term.how != TerminationIdClean || term.code != 0) { + printf("\nProgram exited with error\n"); print_compiler_invocation(test_case); print_exe_invocation(test_case); printf("%s\n", buf_ptr(&program_stderr)); diff --git a/test/self_hosted.zig b/test/self_hosted.zig index ac8ef41917..34577e570e 100644 --- a/test/self_hosted.zig +++ b/test/self_hosted.zig @@ -1594,3 +1594,12 @@ fn cast_slice_to_u8_slice() { bytes[7] = 0; assert(big_thing_slice[1] == 0); } + +#attribute("test") +fn float_division() { + assert(fdiv32(12.0, 3.0) == 4.0); +} +#static_eval_enable(false) +fn fdiv32(a: f32, b: f32) -> f32 { + a / b +}