add debug safety for division

See #149
master
Andrew Kelley 2016-05-07 19:58:02 -07:00
parent 9d29674711
commit eb83111f02
7 changed files with 144 additions and 49 deletions

View File

@ -1565,6 +1565,43 @@ static LLVMValueRef gen_prefix_op_expr(CodeGen *g, AstNode *node) {
zig_unreachable(); 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, static LLVMValueRef gen_arithmetic_bin_op(CodeGen *g, AstNode *source_node,
LLVMValueRef val1, LLVMValueRef val2, LLVMValueRef val1, LLVMValueRef val2,
TypeTableEntry *op1_type, TypeTableEntry *op2_type, TypeTableEntry *op1_type, TypeTableEntry *op2_type,
@ -1665,17 +1702,7 @@ static LLVMValueRef gen_arithmetic_bin_op(CodeGen *g, AstNode *source_node,
} }
case BinOpTypeDiv: case BinOpTypeDiv:
case BinOpTypeAssignDiv: case BinOpTypeAssignDiv:
set_debug_source_node(g, source_node); return gen_div(g, source_node, val1, val2, op1_type);
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, "");
}
}
case BinOpTypeMod: case BinOpTypeMod:
case BinOpTypeAssignMod: case BinOpTypeAssignMod:
set_debug_source_node(g, source_node); set_debug_source_node(g, source_node);

View File

@ -821,17 +821,23 @@ void codegen_link(CodeGen *g, const char *out_file) {
fprintf(stderr, "\n"); fprintf(stderr, "\n");
} }
int return_code;
Buf ld_stderr = BUF_INIT; Buf ld_stderr = BUF_INIT;
Buf ld_stdout = 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) { if (err) {
fprintf(stderr, "linker not found: '%s'\n", buf_ptr(g->linker_path)); fprintf(stderr, "linker not found: '%s'\n", buf_ptr(g->linker_path));
exit(1); exit(1);
} }
if (return_code != 0) { if (term.how != TerminationIdClean || term.code != 0) {
fprintf(stderr, "linker failed with return code %d\n", return_code); 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)); fprintf(stderr, "%s ", buf_ptr(g->linker_path));
for (int i = 0; i < lj.args.length; i += 1) { for (int i = 0; i < lj.args.length; i += 1) {
fprintf(stderr, "%s ", lj.args.at(i)); fprintf(stderr, "%s ", lj.args.at(i));

View File

@ -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_add_root_code(g, &root_source_dir, &root_source_name, &root_source_code);
codegen_link(g, "./test"); codegen_link(g, "./test");
ZigList<const char *> args = {0}; ZigList<const char *> args = {0};
int return_code; Termination term;
os_spawn_process("./test", args, &return_code); os_spawn_process("./test", args, &term);
if (return_code != 0) { if (term.how != TerminationIdClean || term.code != 0) {
fprintf(stderr, "\nTests failed. Use the following command to reproduce the failure:\n"); fprintf(stderr, "\nTests failed. Use the following command to reproduce the failure:\n");
fprintf(stderr, "./test\n"); fprintf(stderr, "./test\n");
} }
return return_code; return (term.how == TerminationIdClean) ? term.code : -1;
} else { } else {
zig_unreachable(); zig_unreachable();
} }

View File

@ -48,7 +48,23 @@
#if defined(ZIG_OS_POSIX) #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(); pid_t pid = fork();
if (pid == -1) if (pid == -1)
zig_panic("fork failed"); 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)); zig_panic("execvp failed: %s", strerror(errno));
} else { } else {
// parent // parent
waitpid(pid, return_code, 0); int status;
waitpid(pid, &status, 0);
populate_termination(term, status);
} }
} }
#endif #endif
#if defined(ZIG_OS_WINDOWS) #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 stderr_buf = BUF_INIT;
Buf stdout_buf = BUF_INIT; Buf stdout_buf = BUF_INIT;
// TODO this is supposed to inherit stdout/stderr instead of capturing it // 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(&stderr_buf), 1, buf_len(&stderr_buf), stderr);
fwrite(buf_ptr(&stdout_buf), 1, buf_len(&stdout_buf), stdout); fwrite(buf_ptr(&stdout_buf), 1, buf_len(&stdout_buf), stdout);
} }
#endif #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) #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) #elif defined(ZIG_OS_POSIX)
os_spawn_process_posix(exe, args, return_code); os_spawn_process_posix(exe, args, term);
#else #else
#error "missing os_spawn_process implementation" #error "missing os_spawn_process implementation"
#endif #endif
@ -195,10 +213,9 @@ int os_fetch_file(FILE *f, Buf *out_buf) {
zig_unreachable(); zig_unreachable();
} }
#if defined(ZIG_OS_POSIX) #if defined(ZIG_OS_POSIX)
static int os_exec_process_posix(const char *exe, ZigList<const char *> &args, 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 stdin_pipe[2];
int stdout_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(stdout_pipe[1]);
close(stderr_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(stdout_pipe[0], "rb"), out_stdout);
os_fetch_file(fdopen(stderr_pipe[0], "rb"), out_stderr); 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, 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 command_line = BUF_INIT;
buf_resize(&command_line, 0); 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)) { if (!GetExitCodeProcess(piProcInfo.hProcess, &exit_code)) {
zig_panic("GetExitCodeProcess failed"); zig_panic("GetExitCodeProcess failed");
} }
*return_code = exit_code; term->how == TerminationIdClean;
term->code = exit_code;
CloseHandle(piProcInfo.hProcess); CloseHandle(piProcInfo.hProcess);
CloseHandle(piProcInfo.hThread); CloseHandle(piProcInfo.hThread);
@ -401,12 +421,12 @@ static int os_exec_process_windows(const char *exe, ZigList<const char *> &args,
#endif #endif
int os_exec_process(const char *exe, ZigList<const char *> &args, 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) #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) #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 #else
#error "missing os_exec_process implementation" #error "missing os_exec_process implementation"
#endif #endif

View File

@ -13,10 +13,23 @@
#include <stdio.h> #include <stdio.h>
enum TerminationId {
TerminationIdClean,
TerminationIdSignaled,
TerminationIdStopped,
TerminationIdUnknown,
};
struct Termination {
TerminationId how;
int code;
};
void os_init(void); 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 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_dirname(Buf *full_path, Buf *out_dirname);
void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename); void os_path_split(Buf *full_path, Buf *out_dirname, Buf *out_basename);

View File

@ -1446,6 +1446,16 @@ fn shl(a: u16, b: u16) -> u16 {
} }
)SOURCE"); )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) { static void run_self_hosted_test(bool is_release_mode) {
Buf zig_stderr = BUF_INIT; Buf zig_stderr = BUF_INIT;
Buf zig_stdout = BUF_INIT; Buf zig_stdout = BUF_INIT;
int return_code;
ZigList<const char *> args = {0}; ZigList<const char *> args = {0};
args.append("test"); args.append("test");
args.append("../test/self_hosted.zig"); args.append("../test/self_hosted.zig");
if (is_release_mode) { if (is_release_mode) {
args.append("--release"); 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("\nSelf-hosted tests failed:\n");
printf("./zig"); printf("./zig");
for (int i = 0; i < args.length; i += 1) { 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_stderr = BUF_INIT;
Buf zig_stdout = BUF_INIT; Buf zig_stdout = BUF_INIT;
int return_code;
int err; 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)); fprintf(stderr, "Unable to exec %s: %s\n", zig_exe, err_str(err));
} }
if (!test_case->is_parseh && test_case->compile_errors.length) { 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) { for (int i = 0; i < test_case->compile_errors.length; i += 1) {
const char *err_text = test_case->compile_errors.at(i); const char *err_text = test_case->compile_errors.at(i);
if (!strstr(buf_ptr(&zig_stderr), err_text)) { if (!strstr(buf_ptr(&zig_stderr), err_text)) {
@ -1723,8 +1733,8 @@ static void run_test(TestCase *test_case) {
} }
} }
if (return_code != 0) { if (term.how != TerminationIdClean || term.code != 0) {
printf("\nCompile failed with return code %d:\n", return_code); printf("\nCompile failed:\n");
print_compiler_invocation(test_case); print_compiler_invocation(test_case);
printf("%s\n", buf_ptr(&zig_stderr)); printf("%s\n", buf_ptr(&zig_stderr));
exit(1); exit(1);
@ -1754,18 +1764,28 @@ static void run_test(TestCase *test_case) {
} else { } else {
Buf program_stderr = BUF_INIT; Buf program_stderr = BUF_INIT;
Buf program_stdout = 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 (test_case->is_debug_safety) {
if (return_code == 0) { int debug_trap_signal = 5;
printf("\nProgram expected to hit debug trap but exited with return code 0\n"); 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_compiler_invocation(test_case);
print_exe_invocation(test_case); print_exe_invocation(test_case);
exit(1); exit(1);
} }
} else { } else {
if (return_code != 0) { if (term.how != TerminationIdClean || term.code != 0) {
printf("\nProgram exited with return code %d:\n", return_code); printf("\nProgram exited with error\n");
print_compiler_invocation(test_case); print_compiler_invocation(test_case);
print_exe_invocation(test_case); print_exe_invocation(test_case);
printf("%s\n", buf_ptr(&program_stderr)); printf("%s\n", buf_ptr(&program_stderr));

View File

@ -1594,3 +1594,12 @@ fn cast_slice_to_u8_slice() {
bytes[7] = 0; bytes[7] = 0;
assert(big_thing_slice[1] == 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
}