Commit eb83111f02

Andrew Kelley <superjoe30@gmail.com>
2016-05-08 04:58:02
add debug safety for division
See #149
1 parent 9d29674
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);
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));
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<const char *> 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();
             }
src/os.cpp
@@ -48,7 +48,23 @@
 
 
 #if defined(ZIG_OS_POSIX)
-static void os_spawn_process_posix(const char *exe, ZigList<const char *> &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<const char *> &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<const char *> &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<const char *> &args, int *return_code) {
+static void os_spawn_process_windows(const char *exe, ZigList<const char *> &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<const char *> &args, int *return_code) {
+void os_spawn_process(const char *exe, ZigList<const char *> &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<const char *> &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<const char *> &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<const char *> &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<const char *> &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<const char *> &args,
 #endif
 
 int os_exec_process(const char *exe, ZigList<const char *> &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
src/os.hpp
@@ -13,10 +13,23 @@
 
 #include <stdio.h>
 
+enum TerminationId {
+    TerminationIdClean,
+    TerminationIdSignaled,
+    TerminationIdStopped,
+    TerminationIdUnknown,
+};
+
+struct Termination {
+    TerminationId how;
+    int code;
+};
+
+
 void os_init(void);
-void os_spawn_process(const char *exe, ZigList<const char *> &args, int *return_code);
+void os_spawn_process(const char *exe, ZigList<const char *> &args, Termination *term);
 int os_exec_process(const char *exe, ZigList<const char *> &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);
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<const char *> 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));
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
+}