From 7ec783876a565662223268a70ba984e0a132b94a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 11 Jan 2018 23:04:08 -0500 Subject: functions which can return errors have secret stack trace param See #651 --- src/analyze.cpp | 39 ++++++++++++++++++++++++++++++++++++--- 1 file changed, 36 insertions(+), 3 deletions(-) (limited to 'src/analyze.cpp') diff --git a/src/analyze.cpp b/src/analyze.cpp index 7e4a861f0f..2e51e5151c 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -869,6 +869,16 @@ static const char *calling_convention_fn_type_str(CallingConvention cc) { zig_unreachable(); } +static TypeTableEntry *get_ptr_to_stack_trace_type(CodeGen *g) { + if (g->stack_trace_type == nullptr) { + ConstExprValue *stack_trace_type_val = get_builtin_value(g, "StackTrace"); + assert(stack_trace_type_val->type->id == TypeTableEntryIdMetaType); + g->stack_trace_type = stack_trace_type_val->data.x_type; + g->ptr_to_stack_trace_type = get_pointer_to_type(g, g->stack_trace_type, false); + } + return g->ptr_to_stack_trace_type; +} + TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { auto table_entry = g->fn_type_table.maybe_get(fn_type_id); if (table_entry) { @@ -915,10 +925,15 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { if (!skip_debug_info) { bool first_arg_return = calling_convention_does_first_arg_return(fn_type_id->cc) && handle_is_ptr(fn_type_id->return_type); + bool last_arg_error_return_trace = fn_type_id->return_type->id == TypeTableEntryIdErrorUnion || + fn_type_id->return_type->id == TypeTableEntryIdPureError; // +1 for maybe making the first argument the return value - LLVMTypeRef *gen_param_types = allocate(1 + fn_type_id->param_count); - // +1 because 0 is the return type and +1 for maybe making first arg ret val - ZigLLVMDIType **param_di_types = allocate(2 + fn_type_id->param_count); + // +1 for maybe last argument the error return trace + LLVMTypeRef *gen_param_types = allocate(2 + fn_type_id->param_count); + // +1 because 0 is the return type and + // +1 for maybe making first arg ret val and + // +1 for maybe last argument the error return trace + ZigLLVMDIType **param_di_types = allocate(3 + fn_type_id->param_count); param_di_types[0] = fn_type_id->return_type->di_type; size_t gen_param_index = 0; TypeTableEntry *gen_return_type; @@ -965,6 +980,14 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { } } + if (last_arg_error_return_trace) { + TypeTableEntry *gen_type = get_ptr_to_stack_trace_type(g); + gen_param_types[gen_param_index] = gen_type->type_ref; + gen_param_index += 1; + // after the gen_param_index += 1 because 0 is the return type + param_di_types[gen_param_index] = gen_type->di_type; + } + fn_type->data.fn.gen_param_count = gen_param_index; fn_type->data.fn.raw_type_ref = LLVMFunctionType(gen_return_type->type_ref, @@ -5527,3 +5550,13 @@ bool type_ptr_eql(const TypeTableEntry *a, const TypeTableEntry *b) { return a == b; } +ConstExprValue *get_builtin_value(CodeGen *codegen, const char *name) { + Tld *tld = codegen->compile_var_import->decls_scope->decl_table.get(buf_create_from_str(name)); + resolve_top_level_decl(codegen, tld, false, nullptr); + assert(tld->id == TldIdVar); + TldVar *tld_var = (TldVar *)tld; + ConstExprValue *var_value = tld_var->var->value; + assert(var_value != nullptr); + return var_value; +} + -- cgit v1.2.3 From 32ea6f54e5f05c4173828c4f4c8ab9965a929120 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 12 Jan 2018 02:12:11 -0500 Subject: *WIP* proof of concept error return traces --- build.zig | 12 ++--- src/all_types.hpp | 2 + src/analyze.cpp | 6 ++- src/analyze.hpp | 1 + src/codegen.cpp | 106 +++++++++++++++++++++++++++++++----- src/ir.cpp | 12 +++++ std/build.zig | 6 +-- std/debug/index.zig | 110 ++++++++++++++++++++++++++++++++++++-- std/special/build_runner.zig | 4 +- std/special/builtin.zig | 2 +- std/special/compiler_rt/index.zig | 2 +- std/special/panic.zig | 8 ++- 12 files changed, 238 insertions(+), 33 deletions(-) (limited to 'src/analyze.cpp') diff --git a/build.zig b/build.zig index 7c1563c706..1fa984dcff 100644 --- a/build.zig +++ b/build.zig @@ -10,7 +10,7 @@ const ArrayList = std.ArrayList; const Buffer = std.Buffer; const io = std.io; -pub fn build(b: &Builder) { +pub fn build(b: &Builder) -> %void { const mode = b.standardReleaseOptions(); var docgen_exe = b.addExecutable("docgen", "doc/docgen.zig"); @@ -36,7 +36,7 @@ pub fn build(b: &Builder) { const test_step = b.step("test", "Run all the tests"); // find the stage0 build artifacts because we're going to re-use config.h and zig_cpp library - const build_info = b.exec([][]const u8{b.zig_exe, "BUILD_INFO"}); + const build_info = try b.exec([][]const u8{b.zig_exe, "BUILD_INFO"}); var index: usize = 0; const cmake_binary_dir = nextValue(&index, build_info); const cxx_compiler = nextValue(&index, build_info); @@ -68,7 +68,7 @@ pub fn build(b: &Builder) { dependOnLib(exe, llvm); if (exe.target.getOs() == builtin.Os.linux) { - const libstdcxx_path_padded = b.exec([][]const u8{cxx_compiler, "-print-file-name=libstdc++.a"}); + const libstdcxx_path_padded = try b.exec([][]const u8{cxx_compiler, "-print-file-name=libstdc++.a"}); const libstdcxx_path = ??mem.split(libstdcxx_path_padded, "\r\n").next(); exe.addObjectFile(libstdcxx_path); @@ -155,9 +155,9 @@ const LibraryDep = struct { }; fn findLLVM(b: &Builder, llvm_config_exe: []const u8) -> %LibraryDep { - const libs_output = b.exec([][]const u8{llvm_config_exe, "--libs", "--system-libs"}); - const includes_output = b.exec([][]const u8{llvm_config_exe, "--includedir"}); - const libdir_output = b.exec([][]const u8{llvm_config_exe, "--libdir"}); + const libs_output = try b.exec([][]const u8{llvm_config_exe, "--libs", "--system-libs"}); + const includes_output = try b.exec([][]const u8{llvm_config_exe, "--includedir"}); + const libdir_output = try b.exec([][]const u8{llvm_config_exe, "--libdir"}); var result = LibraryDep { .libs = ArrayList([]const u8).init(b.allocator), diff --git a/src/all_types.hpp b/src/all_types.hpp index caae13d20d..6476c08625 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1205,6 +1205,7 @@ struct FnTableEntry { uint32_t alignstack_value; ZigList export_list; + bool calls_errorable_function; }; uint32_t fn_table_entry_hash(FnTableEntry*); @@ -1530,6 +1531,7 @@ struct CodeGen { FnTableEntry *panic_fn; LLVMValueRef cur_ret_ptr; LLVMValueRef cur_fn_val; + LLVMValueRef cur_err_ret_trace_val; bool c_want_stdint; bool c_want_stdbool; AstNode *root_export_decl; diff --git a/src/analyze.cpp b/src/analyze.cpp index 2e51e5151c..61d86b5eb2 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -869,7 +869,7 @@ static const char *calling_convention_fn_type_str(CallingConvention cc) { zig_unreachable(); } -static TypeTableEntry *get_ptr_to_stack_trace_type(CodeGen *g) { +TypeTableEntry *get_ptr_to_stack_trace_type(CodeGen *g) { if (g->stack_trace_type == nullptr) { ConstExprValue *stack_trace_type_val = get_builtin_value(g, "StackTrace"); assert(stack_trace_type_val->type->id == TypeTableEntryIdMetaType); @@ -1191,6 +1191,9 @@ static TypeTableEntry *analyze_fn_type(CodeGen *g, AstNode *proto_node, Scope *c } TypeTableEntry *type_entry = analyze_type_expr(g, child_scope, param_node->data.param_decl.type); + if (type_is_invalid(type_entry)) { + return g->builtin_types.entry_invalid; + } if (fn_type_id.cc != CallingConventionUnspecified) { type_ensure_zero_bits_known(g, type_entry); if (!type_has_bits(type_entry)) { @@ -2586,6 +2589,7 @@ static void wrong_panic_prototype(CodeGen *g, AstNode *proto_node, TypeTableEntr } static void typecheck_panic_fn(CodeGen *g, FnTableEntry *panic_fn) { + return; // TODO AstNode *proto_node = panic_fn->proto_node; assert(proto_node->type == NodeTypeFnProto); TypeTableEntry *fn_type = panic_fn->type_entry; diff --git a/src/analyze.hpp b/src/analyze.hpp index 050630bc07..3992cefdfc 100644 --- a/src/analyze.hpp +++ b/src/analyze.hpp @@ -187,6 +187,7 @@ void add_fn_export(CodeGen *g, FnTableEntry *fn_table_entry, Buf *symbol_name, G ConstExprValue *get_builtin_value(CodeGen *codegen, const char *name); +TypeTableEntry *get_ptr_to_stack_trace_type(CodeGen *g); #endif diff --git a/src/codegen.cpp b/src/codegen.cpp index 4de0fcec51..97cab9a69f 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -22,6 +22,8 @@ #include #include +static const size_t stack_trace_ptr_count = 31; + static void init_darwin_native(CodeGen *g) { char *osx_target = getenv("MACOSX_DEPLOYMENT_TARGET"); char *ios_target = getenv("IPHONEOS_DEPLOYMENT_TARGET"); @@ -867,16 +869,24 @@ static LLVMValueRef get_panic_msg_ptr_val(CodeGen *g, PanicMsgId msg_id) { return LLVMConstBitCast(val->global_refs->llvm_global, LLVMPointerType(str_type->type_ref, 0)); } -static void gen_panic(CodeGen *g, LLVMValueRef msg_arg) { +static void gen_panic(CodeGen *g, LLVMValueRef msg_arg, LLVMValueRef stack_trace_arg) { assert(g->panic_fn != nullptr); LLVMValueRef fn_val = fn_llvm_value(g, g->panic_fn); LLVMCallConv llvm_cc = get_llvm_cc(g, g->panic_fn->type_entry->data.fn.fn_type_id.cc); - ZigLLVMBuildCall(g->builder, fn_val, &msg_arg, 1, llvm_cc, ZigLLVM_FnInlineAuto, ""); + if (stack_trace_arg == nullptr) { + TypeTableEntry *ptr_to_stack_trace_type = get_ptr_to_stack_trace_type(g); + stack_trace_arg = LLVMConstNull(ptr_to_stack_trace_type->type_ref); + } + LLVMValueRef args[] = { + msg_arg, + stack_trace_arg, + }; + ZigLLVMBuildCall(g->builder, fn_val, args, 2, llvm_cc, ZigLLVM_FnInlineAuto, ""); LLVMBuildUnreachable(g->builder); } static void gen_debug_safety_crash(CodeGen *g, PanicMsgId msg_id) { - gen_panic(g, get_panic_msg_ptr_val(g, msg_id)); + gen_panic(g, get_panic_msg_ptr_val(g, msg_id), nullptr); } static LLVMValueRef get_memcpy_fn_val(CodeGen *g) { @@ -956,7 +966,11 @@ static LLVMValueRef get_safety_crash_err_fn(CodeGen *g) { LLVMValueRef offset_buf_ptr = LLVMConstInBoundsGEP(global_array, offset_ptr_indices, 2); Buf *fn_name = get_mangled_name(g, buf_create_from_str("__zig_fail_unwrap"), false); - LLVMTypeRef fn_type_ref = LLVMFunctionType(LLVMVoidType(), &g->err_tag_type->type_ref, 1, false); + LLVMTypeRef arg_types[] = { + g->err_tag_type->type_ref, + g->ptr_to_stack_trace_type->type_ref, + }; + LLVMTypeRef fn_type_ref = LLVMFunctionType(LLVMVoidType(), arg_types, 2, false); LLVMValueRef fn_val = LLVMAddFunction(g->module, buf_ptr(fn_name), fn_type_ref); addLLVMFnAttr(fn_val, "noreturn"); addLLVMFnAttr(fn_val, "cold"); @@ -1008,7 +1022,7 @@ static LLVMValueRef get_safety_crash_err_fn(CodeGen *g) { LLVMValueRef global_slice_len_field_ptr = LLVMBuildStructGEP(g->builder, global_slice, slice_len_index, ""); gen_store(g, full_buf_len, global_slice_len_field_ptr, u8_ptr_type); - gen_panic(g, global_slice); + gen_panic(g, global_slice, LLVMGetParam(fn_val, 1)); LLVMPositionBuilderAtEnd(g->builder, prev_block); LLVMSetCurrentDebugLocation(g->builder, prev_debug_location); @@ -1019,7 +1033,16 @@ static LLVMValueRef get_safety_crash_err_fn(CodeGen *g) { static void gen_debug_safety_crash_for_err(CodeGen *g, LLVMValueRef err_val) { LLVMValueRef safety_crash_err_fn = get_safety_crash_err_fn(g); - ZigLLVMBuildCall(g->builder, safety_crash_err_fn, &err_val, 1, get_llvm_cc(g, CallingConventionUnspecified), + LLVMValueRef err_ret_trace_val = g->cur_err_ret_trace_val; + if (err_ret_trace_val == nullptr) { + TypeTableEntry *ptr_to_stack_trace_type = get_ptr_to_stack_trace_type(g); + err_ret_trace_val = LLVMConstNull(ptr_to_stack_trace_type->type_ref); + } + LLVMValueRef args[] = { + err_val, + err_ret_trace_val, + }; + ZigLLVMBuildCall(g->builder, safety_crash_err_fn, args, 2, get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, ""); LLVMBuildUnreachable(g->builder); } @@ -1299,6 +1322,50 @@ static LLVMValueRef ir_llvm_value(CodeGen *g, IrInstruction *instruction) { static LLVMValueRef ir_render_return(CodeGen *g, IrExecutable *executable, IrInstructionReturn *return_instruction) { LLVMValueRef value = ir_llvm_value(g, return_instruction->value); TypeTableEntry *return_type = return_instruction->value->value.type; + + bool is_err_return = false; + if (return_type->id == TypeTableEntryIdErrorUnion) { + if (return_instruction->value->value.special == ConstValSpecialStatic) { + is_err_return = return_instruction->value->value.data.x_err_union.err != nullptr; + } else if (return_instruction->value->value.special == ConstValSpecialRuntime) { + is_err_return = return_instruction->value->value.data.rh_error_union == RuntimeHintErrorUnionError; + // TODO: emit a branch to check if the return value is an error + } + } else if (return_type->id == TypeTableEntryIdPureError) { + is_err_return = true; + } + if (is_err_return) { + LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->type_ref; + + // stack_trace.instruction_addresses[stack_trace.index % stack_trace_ptr_count] = @instructionPointer(); + // stack_trace.index += 1; + + LLVMBasicBlockRef return_block = LLVMAppendBasicBlock(g->cur_fn_val, "ReturnError"); + + LLVMValueRef block_address = LLVMBlockAddress(g->cur_fn_val, return_block); + size_t index_field_index = g->stack_trace_type->data.structure.fields[0].gen_index; + LLVMValueRef index_field_ptr = LLVMBuildStructGEP(g->builder, g->cur_err_ret_trace_val, (unsigned)index_field_index, ""); + size_t addresses_field_index = g->stack_trace_type->data.structure.fields[1].gen_index; + LLVMValueRef addresses_field_ptr = LLVMBuildStructGEP(g->builder, g->cur_err_ret_trace_val, (unsigned)addresses_field_index, ""); + + // stack_trace.instruction_addresses[stack_trace.index % stack_trace_ptr_count] = @instructionPointer(); + LLVMValueRef index_val = gen_load_untyped(g, index_field_ptr, 0, false, ""); + LLVMValueRef modded_val = LLVMBuildURem(g->builder, index_val, LLVMConstInt(usize_type_ref, stack_trace_ptr_count, false), ""); + LLVMValueRef address_indices[] = { + LLVMConstNull(usize_type_ref), + modded_val, + }; + LLVMValueRef address_slot = LLVMBuildInBoundsGEP(g->builder, addresses_field_ptr, address_indices, 2, ""); + LLVMValueRef address_value = LLVMBuildPtrToInt(g->builder, block_address, usize_type_ref, ""); + gen_store_untyped(g, address_value, address_slot, 0, false); + + // stack_trace.index += 1; + LLVMValueRef index_plus_one_val = LLVMBuildAdd(g->builder, index_val, LLVMConstInt(usize_type_ref, 1, false), ""); + gen_store_untyped(g, index_plus_one_val, index_field_ptr, 0, false); + + LLVMBuildBr(g->builder, return_block); + LLVMPositionBuilderAtEnd(g->builder, return_block); + } if (handle_is_ptr(return_type)) { if (calling_convention_does_first_arg_return(g->cur_fn->type_entry->data.fn.fn_type_id.cc)) { assert(g->cur_ret_ptr); @@ -2353,7 +2420,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr } } if (last_arg_err_ret_stack) { - gen_param_values[gen_param_index] = LLVMGetUndef(g->ptr_to_stack_trace_type->type_ref); + gen_param_values[gen_param_index] = g->cur_err_ret_trace_val; gen_param_index += 1; } @@ -3482,7 +3549,7 @@ static LLVMValueRef ir_render_container_init_list(CodeGen *g, IrExecutable *exec } static LLVMValueRef ir_render_panic(CodeGen *g, IrExecutable *executable, IrInstructionPanic *instruction) { - gen_panic(g, ir_llvm_value(g, instruction->msg)); + gen_panic(g, ir_llvm_value(g, instruction->msg), nullptr); return nullptr; } @@ -4501,7 +4568,8 @@ static void do_code_gen(CodeGen *g) { LLVMValueRef fn = fn_llvm_value(g, fn_table_entry); g->cur_fn = fn_table_entry; g->cur_fn_val = fn; - if (handle_is_ptr(fn_table_entry->type_entry->data.fn.fn_type_id.return_type)) { + TypeTableEntry *return_type = fn_table_entry->type_entry->data.fn.fn_type_id.return_type; + if (handle_is_ptr(return_type)) { g->cur_ret_ptr = LLVMGetParam(fn, 0); } else { g->cur_ret_ptr = nullptr; @@ -4510,6 +4578,18 @@ static void do_code_gen(CodeGen *g) { build_all_basic_blocks(g, fn_table_entry); clear_debug_source_node(g); + if (return_type->id == TypeTableEntryIdPureError || return_type->id == TypeTableEntryIdErrorUnion) { + g->cur_err_ret_trace_val = LLVMGetParam(fn, LLVMCountParamTypes(fn_table_entry->type_entry->data.fn.raw_type_ref) - 1); + } else if (fn_table_entry->calls_errorable_function) { + g->cur_err_ret_trace_val = build_alloca(g, g->stack_trace_type, "error_return_trace", get_abi_alignment(g, g->stack_trace_type)); + size_t index_field_index = g->stack_trace_type->data.structure.fields[0].gen_index; + LLVMValueRef index_field_ptr = LLVMBuildStructGEP(g->builder, g->cur_err_ret_trace_val, (unsigned)index_field_index, ""); + TypeTableEntry *usize = g->builtin_types.entry_usize; + gen_store_untyped(g, LLVMConstNull(usize->type_ref), index_field_ptr, 0, false); + } else { + g->cur_err_ret_trace_val = nullptr; + } + // allocate temporary stack data for (size_t alloca_i = 0; alloca_i < fn_table_entry->alloca_list.length; alloca_i += 1) { IrInstruction *instruction = fn_table_entry->alloca_list.at(alloca_i); @@ -5096,12 +5176,11 @@ static void define_builtin_compile_vars(CodeGen *g) { os_path_join(g->cache_dir, buf_create_from_str(builtin_zig_basename), builtin_zig_path); Buf *contents = buf_alloc(); - buf_append_str(contents, + buf_appendf(contents, "pub const StackTrace = struct {\n" " index: usize,\n" - " instruction_addresses: [31]usize,\n" - "};\n\n" - ); + " instruction_addresses: [%" ZIG_PRI_usize "]usize,\n" + "};\n\n", stack_trace_ptr_count); const char *cur_os = nullptr; { @@ -5266,6 +5345,7 @@ static void define_builtin_compile_vars(CodeGen *g) { g->root_package->package_table.put(buf_create_from_str("builtin"), g->compile_var_package); g->std_package->package_table.put(buf_create_from_str("builtin"), g->compile_var_package); g->compile_var_import = add_source_file(g, g->compile_var_package, abs_full_path, contents); + scan_import(g, g->compile_var_import); } static void init(CodeGen *g) { diff --git a/src/ir.cpp b/src/ir.cpp index 340e4fbeda..5dd608083f 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -10043,9 +10043,21 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal TypeTableEntry *return_type = impl_fn->type_entry->data.fn.fn_type_id.return_type; ir_add_alloca(ira, new_call_instruction, return_type); + if (return_type->id == TypeTableEntryIdPureError || return_type->id == TypeTableEntryIdErrorUnion) { + parent_fn_entry->calls_errorable_function = true; + } + return ir_finish_anal(ira, return_type); } + FnTableEntry *parent_fn_entry = exec_fn_entry(ira->new_irb.exec); + assert(fn_type_id->return_type != nullptr); + assert(parent_fn_entry != nullptr); + if (fn_type_id->return_type->id == TypeTableEntryIdPureError || fn_type_id->return_type->id == TypeTableEntryIdErrorUnion) { + parent_fn_entry->calls_errorable_function = true; + } + + IrInstruction **casted_args = allocate(call_param_count); size_t next_arg_index = 0; if (first_arg_ptr) { diff --git a/std/build.zig b/std/build.zig index cd6b3811ea..6984715466 100644 --- a/std/build.zig +++ b/std/build.zig @@ -721,11 +721,9 @@ pub const Builder = struct { return error.FileNotFound; } - pub fn exec(self: &Builder, argv: []const []const u8) -> []u8 { + pub fn exec(self: &Builder, argv: []const []const u8) -> %[]u8 { const max_output_size = 100 * 1024; - const result = os.ChildProcess.exec(self.allocator, argv, null, null, max_output_size) catch |err| { - std.debug.panic("Unable to spawn {}: {}", argv[0], @errorName(err)); - }; + const result = try os.ChildProcess.exec(self.allocator, argv, null, null, max_output_size); switch (result.term) { os.ChildProcess.Term.Exited => |code| { if (code != 0) { diff --git a/std/debug/index.zig b/std/debug/index.zig index 464974b7de..fa23040794 100644 --- a/std/debug/index.zig +++ b/std/debug/index.zig @@ -37,10 +37,16 @@ fn getStderrStream() -> %&io.OutStream { } } +/// Tries to print the current stack trace to stderr, unbuffered, and ignores any error returned. +pub fn dumpCurrentStackTrace() { + const stderr = getStderrStream() catch return; + writeCurrentStackTrace(stderr, global_allocator, stderr_file.isTty(), 1) catch return; +} + /// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned. -pub fn dumpStackTrace() { +pub fn dumpStackTrace(stack_trace: &builtin.StackTrace) { const stderr = getStderrStream() catch return; - writeStackTrace(stderr, global_allocator, stderr_file.isTty(), 1) catch return; + writeStackTrace(stack_trace, stderr, global_allocator, stderr_file.isTty()) catch return; } /// This function invokes undefined behavior when `ok` is `false`. @@ -88,7 +94,7 @@ pub fn panic(comptime format: []const u8, args: ...) -> noreturn { const stderr = getStderrStream() catch os.abort(); stderr.print(format ++ "\n", args) catch os.abort(); - writeStackTrace(stderr, global_allocator, stderr_file.isTty(), 1) catch os.abort(); + writeCurrentStackTrace(stderr, global_allocator, stderr_file.isTty(), 1) catch os.abort(); os.abort(); } @@ -101,7 +107,103 @@ const RESET = "\x1b[0m"; error PathNotFound; error InvalidDebugInfo; -pub fn writeStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty_color: bool, +pub fn writeStackTrace(st_addrs: &builtin.StackTrace, out_stream: &io.OutStream, allocator: &mem.Allocator, tty_color: bool) -> %void { + switch (builtin.object_format) { + builtin.ObjectFormat.elf => { + var stack_trace = ElfStackTrace { + .self_exe_file = undefined, + .elf = undefined, + .debug_info = undefined, + .debug_abbrev = undefined, + .debug_str = undefined, + .debug_line = undefined, + .debug_ranges = null, + .abbrev_table_list = ArrayList(AbbrevTableHeader).init(allocator), + .compile_unit_list = ArrayList(CompileUnit).init(allocator), + }; + const st = &stack_trace; + st.self_exe_file = try os.openSelfExe(); + defer st.self_exe_file.close(); + + try st.elf.openFile(allocator, &st.self_exe_file); + defer st.elf.close(); + + st.debug_info = (try st.elf.findSection(".debug_info")) ?? return error.MissingDebugInfo; + st.debug_abbrev = (try st.elf.findSection(".debug_abbrev")) ?? return error.MissingDebugInfo; + st.debug_str = (try st.elf.findSection(".debug_str")) ?? return error.MissingDebugInfo; + st.debug_line = (try st.elf.findSection(".debug_line")) ?? return error.MissingDebugInfo; + st.debug_ranges = (try st.elf.findSection(".debug_ranges")); + try scanAllCompileUnits(st); + + var ignored_count: usize = 0; + + var frame_index: usize = undefined; + var frames_left: usize = undefined; + if (st_addrs.index < st_addrs.instruction_addresses.len) { + frame_index = 0; + frames_left = st_addrs.index; + } else { + frame_index = (st_addrs.index + 1) % st_addrs.instruction_addresses.len; + frames_left = st_addrs.instruction_addresses.len; + } + + while (frames_left != 0) : ({frames_left -= 1; frame_index = (frame_index + 1) % st_addrs.instruction_addresses.len;}) { + const return_address = st_addrs.instruction_addresses[frame_index]; + + // TODO we really should be able to convert @sizeOf(usize) * 2 to a string literal + // at compile time. I'll call it issue #313 + const ptr_hex = if (@sizeOf(usize) == 4) "0x{x8}" else "0x{x16}"; + + const compile_unit = findCompileUnit(st, return_address) catch { + try out_stream.print("???:?:?: " ++ DIM ++ ptr_hex ++ " in ??? (???)" ++ RESET ++ "\n ???\n\n", + return_address); + continue; + }; + const compile_unit_name = try compile_unit.die.getAttrString(st, DW.AT_name); + if (getLineNumberInfo(st, compile_unit, usize(return_address) - 1)) |line_info| { + defer line_info.deinit(); + try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++ + DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n", + line_info.file_name, line_info.line, line_info.column, + return_address, compile_unit_name); + if (printLineFromFile(st.allocator(), out_stream, line_info)) { + if (line_info.column == 0) { + try out_stream.write("\n"); + } else { + {var col_i: usize = 1; while (col_i < line_info.column) : (col_i += 1) { + try out_stream.writeByte(' '); + }} + try out_stream.write(GREEN ++ "^" ++ RESET ++ "\n"); + } + } else |err| switch (err) { + error.EndOfFile, error.PathNotFound => {}, + else => return err, + } + } else |err| switch (err) { + error.MissingDebugInfo, error.InvalidDebugInfo => { + try out_stream.print(ptr_hex ++ " in ??? ({})\n", + return_address, compile_unit_name); + }, + else => return err, + } + } + }, + builtin.ObjectFormat.coff => { + try out_stream.write("(stack trace unavailable for COFF object format)\n"); + }, + builtin.ObjectFormat.macho => { + try out_stream.write("(stack trace unavailable for Mach-O object format)\n"); + }, + builtin.ObjectFormat.wasm => { + try out_stream.write("(stack trace unavailable for WASM object format)\n"); + }, + builtin.ObjectFormat.unknown => { + try out_stream.write("(stack trace unavailable for unknown object format)\n"); + }, + } +} + +pub fn writeCurrentStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocator, tty_color: bool, ignore_frame_count: usize) -> %void { switch (builtin.object_format) { diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig index 24d9c756e7..b293a1e64d 100644 --- a/std/special/build_runner.zig +++ b/std/special/build_runner.zig @@ -112,7 +112,7 @@ pub fn main() -> %void { } builder.setInstallPrefix(prefix); - root.build(&builder); + root.build(&builder) catch unreachable; if (builder.validateUserInputDidItFail()) return usageAndErr(&builder, true, try stderr_stream); @@ -129,7 +129,7 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) // run the build script to collect the options if (!already_ran_build) { builder.setInstallPrefix(null); - root.build(builder); + root.build(builder) catch unreachable; } // This usage text has to be synchronized with src/main.cpp diff --git a/std/special/builtin.zig b/std/special/builtin.zig index e6c09863ca..dd77ba9c75 100644 --- a/std/special/builtin.zig +++ b/std/special/builtin.zig @@ -5,7 +5,7 @@ const builtin = @import("builtin"); // Avoid dragging in the debug safety mechanisms into this .o file, // unless we're trying to test this file. -pub coldcc fn panic(msg: []const u8) -> noreturn { +pub coldcc fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) -> noreturn { if (builtin.is_test) { @import("std").debug.panic("{}", msg); } else { diff --git a/std/special/compiler_rt/index.zig b/std/special/compiler_rt/index.zig index 5034a6fd90..96f34e56ff 100644 --- a/std/special/compiler_rt/index.zig +++ b/std/special/compiler_rt/index.zig @@ -74,7 +74,7 @@ const __udivmoddi4 = @import("udivmoddi4.zig").__udivmoddi4; // Avoid dragging in the debug safety mechanisms into this .o file, // unless we're trying to test this file. -pub coldcc fn panic(msg: []const u8) -> noreturn { +pub coldcc fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) -> noreturn { if (is_test) { @import("std").debug.panic("{}", msg); } else { diff --git a/std/special/panic.zig b/std/special/panic.zig index 03c2586739..690c4afa1c 100644 --- a/std/special/panic.zig +++ b/std/special/panic.zig @@ -4,14 +4,20 @@ // have to be added in the compiler. const builtin = @import("builtin"); +const std = @import("std"); -pub coldcc fn panic(msg: []const u8) -> noreturn { +pub coldcc fn panic(msg: []const u8, error_return_trace: ?&builtin.StackTrace) -> noreturn { switch (builtin.os) { // TODO: fix panic in zen. builtin.Os.freestanding, builtin.Os.zen => { while (true) {} }, else => { + if (error_return_trace) |trace| { + std.debug.warn("{}\n", msg); + std.debug.dumpStackTrace(trace); + @import("std").debug.panic(""); + } @import("std").debug.panic("{}", msg); }, } -- cgit v1.2.3 From 4551489b924fad262eb3343b68fc4c0e18ed6a97 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 13 Jan 2018 01:00:50 -0500 Subject: typecheck the panic function --- src/analyze.cpp | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) (limited to 'src/analyze.cpp') diff --git a/src/analyze.cpp b/src/analyze.cpp index 61d86b5eb2..b52cb019d3 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2584,17 +2584,16 @@ static bool scope_is_root_decls(Scope *scope) { static void wrong_panic_prototype(CodeGen *g, AstNode *proto_node, TypeTableEntry *fn_type) { add_node_error(g, proto_node, - buf_sprintf("expected 'fn([]const u8) -> unreachable', found '%s'", + buf_sprintf("expected 'fn([]const u8, ?&builtin.StackTrace) -> unreachable', found '%s'", buf_ptr(&fn_type->name))); } static void typecheck_panic_fn(CodeGen *g, FnTableEntry *panic_fn) { - return; // TODO AstNode *proto_node = panic_fn->proto_node; assert(proto_node->type == NodeTypeFnProto); TypeTableEntry *fn_type = panic_fn->type_entry; FnTypeId *fn_type_id = &fn_type->data.fn.fn_type_id; - if (fn_type_id->param_count != 1) { + if (fn_type_id->param_count != 2) { return wrong_panic_prototype(g, proto_node, fn_type); } TypeTableEntry *const_u8_ptr = get_pointer_to_type(g, g->builtin_types.entry_u8, true); @@ -2603,6 +2602,11 @@ static void typecheck_panic_fn(CodeGen *g, FnTableEntry *panic_fn) { return wrong_panic_prototype(g, proto_node, fn_type); } + TypeTableEntry *nullable_ptr_to_stack_trace_type = get_maybe_type(g, get_ptr_to_stack_trace_type(g)); + if (fn_type_id->param_info[1].type != nullable_ptr_to_stack_trace_type) { + return wrong_panic_prototype(g, proto_node, fn_type); + } + TypeTableEntry *actual_return_type = fn_type_id->return_type; if (actual_return_type != g->builtin_types.entry_unreachable) { return wrong_panic_prototype(g, proto_node, fn_type); -- cgit v1.2.3 From fa024f80924d35e4aac4b4edcac88193def03175 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 14 Jan 2018 14:35:43 -0500 Subject: error return trace pointer prefixes other params instead of being last. This increases the chances that it can remain in the same register between calls. --- src/analyze.cpp | 18 +++++++++--------- src/codegen.cpp | 34 +++++++++++++++++++++++----------- 2 files changed, 32 insertions(+), 20 deletions(-) (limited to 'src/analyze.cpp') diff --git a/src/analyze.cpp b/src/analyze.cpp index b52cb019d3..f7c0f832a2 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -925,7 +925,7 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { if (!skip_debug_info) { bool first_arg_return = calling_convention_does_first_arg_return(fn_type_id->cc) && handle_is_ptr(fn_type_id->return_type); - bool last_arg_error_return_trace = fn_type_id->return_type->id == TypeTableEntryIdErrorUnion || + bool prefix_arg_error_return_trace = fn_type_id->return_type->id == TypeTableEntryIdErrorUnion || fn_type_id->return_type->id == TypeTableEntryIdPureError; // +1 for maybe making the first argument the return value // +1 for maybe last argument the error return trace @@ -951,6 +951,14 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { } fn_type->data.fn.gen_return_type = gen_return_type; + if (prefix_arg_error_return_trace) { + TypeTableEntry *gen_type = get_ptr_to_stack_trace_type(g); + gen_param_types[gen_param_index] = gen_type->type_ref; + gen_param_index += 1; + // after the gen_param_index += 1 because 0 is the return type + param_di_types[gen_param_index] = gen_type->di_type; + } + fn_type->data.fn.gen_param_info = allocate(fn_type_id->param_count); for (size_t i = 0; i < fn_type_id->param_count; i += 1) { FnTypeParamInfo *src_param_info = &fn_type->data.fn.fn_type_id.param_info[i]; @@ -980,14 +988,6 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { } } - if (last_arg_error_return_trace) { - TypeTableEntry *gen_type = get_ptr_to_stack_trace_type(g); - gen_param_types[gen_param_index] = gen_type->type_ref; - gen_param_index += 1; - // after the gen_param_index += 1 because 0 is the return type - param_di_types[gen_param_index] = gen_type->di_type; - } - fn_type->data.fn.gen_param_count = gen_param_index; fn_type->data.fn.raw_type_ref = LLVMFunctionType(gen_return_type->type_ref, diff --git a/src/codegen.cpp b/src/codegen.cpp index 97cab9a69f..8b2cf2bcd4 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -406,6 +406,16 @@ static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) { zig_unreachable(); } +static uint32_t get_err_ret_trace_arg_index(FnTableEntry *fn_table_entry) { + TypeTableEntry *fn_type = fn_table_entry->type_entry; + TypeTableEntry *return_type = fn_type->data.fn.fn_type_id.return_type; + if (return_type->id != TypeTableEntryIdErrorUnion && return_type->id != TypeTableEntryIdPureError) { + return UINT32_MAX; + } + bool first_arg_ret = type_has_bits(return_type) && handle_is_ptr(return_type); + return first_arg_ret ? 1 : 0; +} + static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) { if (fn_table_entry->llvm_value) return fn_table_entry->llvm_value; @@ -563,9 +573,10 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) { addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)gen_index, "byval"); } } - if (return_type->id == TypeTableEntryIdErrorUnion || return_type->id == TypeTableEntryIdPureError) { - unsigned gen_index = LLVMCountParamTypes(fn_llvm_type) - 1; - addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)gen_index, "nonnull"); + + uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(fn_table_entry); + if (err_ret_trace_arg_index != UINT32_MAX) { + addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)err_ret_trace_arg_index, "nonnull"); } return fn_table_entry->llvm_value; @@ -2400,8 +2411,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr TypeTableEntry *src_return_type = fn_type_id->return_type; bool ret_has_bits = type_has_bits(src_return_type); bool first_arg_ret = ret_has_bits && handle_is_ptr(src_return_type); - bool last_arg_err_ret_stack = src_return_type->id == TypeTableEntryIdErrorUnion || src_return_type->id == TypeTableEntryIdPureError; - size_t actual_param_count = instruction->arg_count + (first_arg_ret ? 1 : 0) + (last_arg_err_ret_stack ? 1 : 0); + bool prefix_arg_err_ret_stack = src_return_type->id == TypeTableEntryIdErrorUnion || src_return_type->id == TypeTableEntryIdPureError; + size_t actual_param_count = instruction->arg_count + (first_arg_ret ? 1 : 0) + (prefix_arg_err_ret_stack ? 1 : 0); bool is_var_args = fn_type_id->is_var_args; LLVMValueRef *gen_param_values = allocate(actual_param_count); size_t gen_param_index = 0; @@ -2409,6 +2420,10 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr gen_param_values[gen_param_index] = instruction->tmp_ptr; gen_param_index += 1; } + if (prefix_arg_err_ret_stack) { + gen_param_values[gen_param_index] = g->cur_err_ret_trace_val; + gen_param_index += 1; + } for (size_t call_i = 0; call_i < instruction->arg_count; call_i += 1) { IrInstruction *param_instruction = instruction->args[call_i]; TypeTableEntry *param_type = param_instruction->value.type; @@ -2419,10 +2434,6 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr gen_param_index += 1; } } - if (last_arg_err_ret_stack) { - gen_param_values[gen_param_index] = g->cur_err_ret_trace_val; - gen_param_index += 1; - } ZigLLVM_FnInline fn_inline; switch (instruction->fn_inline) { @@ -4578,8 +4589,9 @@ static void do_code_gen(CodeGen *g) { build_all_basic_blocks(g, fn_table_entry); clear_debug_source_node(g); - if (return_type->id == TypeTableEntryIdPureError || return_type->id == TypeTableEntryIdErrorUnion) { - g->cur_err_ret_trace_val = LLVMGetParam(fn, LLVMCountParamTypes(fn_table_entry->type_entry->data.fn.raw_type_ref) - 1); + uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(fn_table_entry); + if (err_ret_trace_arg_index != UINT32_MAX) { + g->cur_err_ret_trace_val = LLVMGetParam(fn, err_ret_trace_arg_index); } else if (fn_table_entry->calls_errorable_function) { g->cur_err_ret_trace_val = build_alloca(g, g->stack_trace_type, "error_return_trace", get_abi_alignment(g, g->stack_trace_type)); size_t index_field_index = g->stack_trace_type->data.structure.fields[0].gen_index; -- cgit v1.2.3 From 7b57454cc11371b71097967656e19f0a1736d733 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 15 Jan 2018 00:01:02 -0500 Subject: clean up error return tracing * error return tracing is disabled in release-fast mode * add @errorReturnTrace * zig build API changes build return type from `void` to `%void` * allow `void`, `noreturn`, and `u8` from main. closes #535 --- doc/langref.html.in | 8 ++++ example/mix_o_files/build.zig | 2 +- example/shared_library/build.zig | 2 +- src/all_types.hpp | 7 ++++ src/analyze.cpp | 12 ++---- src/codegen.cpp | 78 ++++++++++++++++++++++-------------- src/ir.cpp | 35 ++++++++++++++++ src/ir_print.cpp | 7 ++++ std/build.zig | 4 +- std/debug/index.zig | 9 +++-- std/io.zig | 2 +- std/os/index.zig | 11 +---- std/special/bootstrap.zig | 45 ++++++++++++++++----- std/special/build_runner.zig | 8 ++-- std/special/test_runner.zig | 9 +---- test/compile_errors.zig | 18 ++++----- test/debug_safety.zig | 40 +++++++++--------- test/standalone/issue_339/build.zig | 2 +- test/standalone/issue_339/test.zig | 3 +- test/standalone/pkg_import/build.zig | 2 +- test/standalone/use_alias/build.zig | 2 +- test/tests.zig | 4 +- 22 files changed, 198 insertions(+), 112 deletions(-) (limited to 'src/analyze.cpp') diff --git a/doc/langref.html.in b/doc/langref.html.in index 2476790105..94bc780959 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -142,6 +142,7 @@
  • @TagType
  • @EnumTagType
  • @errorName
  • +
  • @errorReturnTrace
  • @fence
  • @fieldParentPtr
  • @frameAddress
  • @@ -4412,6 +4413,13 @@ test.zig:6:2: error: found compile log statement or all calls have a compile-time known value for err, then no error name table will be generated.

    +

    @errorReturnTrace

    +
    @errorReturnTrace() -> ?&builtin.StackTrace
    +

    + If the binary is built with error return tracing, and this function is invoked in a + function that calls a function with an error or error union return type, returns a + stack trace object. Otherwise returns `null`. +

    @fence

    @fence(order: AtomicOrder)

    diff --git a/example/mix_o_files/build.zig b/example/mix_o_files/build.zig index 391af9924a..4bba69b091 100644 --- a/example/mix_o_files/build.zig +++ b/example/mix_o_files/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) { +pub fn build(b: &Builder) -> %void { const obj = b.addObject("base64", "base64.zig"); const exe = b.addCExecutable("test"); diff --git a/example/shared_library/build.zig b/example/shared_library/build.zig index 9b7f3793c6..147b54401c 100644 --- a/example/shared_library/build.zig +++ b/example/shared_library/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) { +pub fn build(b: &Builder) -> %void { const lib = b.addSharedLibrary("mathtest", "mathtest.zig", b.version(1, 0, 0)); const exe = b.addCExecutable("test"); diff --git a/src/all_types.hpp b/src/all_types.hpp index 7a3f1a9b79..b401097647 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1274,6 +1274,7 @@ enum BuiltinFnId { BuiltinFnIdSetAlignStack, BuiltinFnIdArgType, BuiltinFnIdExport, + BuiltinFnIdErrorReturnTrace, }; struct BuiltinFnEntry { @@ -1499,6 +1500,7 @@ struct CodeGen { Buf triple_str; BuildMode build_mode; bool is_test_build; + bool have_err_ret_tracing; uint32_t target_os_index; uint32_t target_arch_index; uint32_t target_environ_index; @@ -1902,6 +1904,7 @@ enum IrInstructionId { IrInstructionIdSetAlignStack, IrInstructionIdArgType, IrInstructionIdExport, + IrInstructionIdErrorReturnTrace, }; struct IrInstruction { @@ -2723,6 +2726,10 @@ struct IrInstructionExport { IrInstruction *target; }; +struct IrInstructionErrorReturnTrace { + IrInstruction base; +}; + static const size_t slice_ptr_index = 0; static const size_t slice_len_index = 1; diff --git a/src/analyze.cpp b/src/analyze.cpp index f7c0f832a2..8028ace289 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -925,8 +925,9 @@ TypeTableEntry *get_fn_type(CodeGen *g, FnTypeId *fn_type_id) { if (!skip_debug_info) { bool first_arg_return = calling_convention_does_first_arg_return(fn_type_id->cc) && handle_is_ptr(fn_type_id->return_type); - bool prefix_arg_error_return_trace = fn_type_id->return_type->id == TypeTableEntryIdErrorUnion || - fn_type_id->return_type->id == TypeTableEntryIdPureError; + bool prefix_arg_error_return_trace = g->have_err_ret_tracing && + (fn_type_id->return_type->id == TypeTableEntryIdErrorUnion || + fn_type_id->return_type->id == TypeTableEntryIdPureError); // +1 for maybe making the first argument the return value // +1 for maybe last argument the error return trace LLVMTypeRef *gen_param_types = allocate(2 + fn_type_id->param_count); @@ -2711,13 +2712,6 @@ static void resolve_decl_fn(CodeGen *g, TldFn *tld_fn) { { if (g->have_pub_main && buf_eql_str(&fn_table_entry->symbol_name, "main")) { g->main_fn = fn_table_entry; - TypeTableEntry *err_void = get_error_type(g, g->builtin_types.entry_void); - TypeTableEntry *actual_return_type = fn_table_entry->type_entry->data.fn.fn_type_id.return_type; - if (actual_return_type != err_void) { - add_node_error(g, fn_proto->return_type, - buf_sprintf("expected return type of main to be '%%void', instead is '%s'", - buf_ptr(&actual_return_type->name))); - } } else if ((import->package == g->panic_package || g->have_pub_panic) && buf_eql_str(&fn_table_entry->symbol_name, "panic")) { diff --git a/src/codegen.cpp b/src/codegen.cpp index 96c0879615..5113aa7275 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -404,7 +404,10 @@ static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) { zig_unreachable(); } -static uint32_t get_err_ret_trace_arg_index(FnTableEntry *fn_table_entry) { +static uint32_t get_err_ret_trace_arg_index(CodeGen *g, FnTableEntry *fn_table_entry) { + if (!g->have_err_ret_tracing) { + return UINT32_MAX; + } TypeTableEntry *fn_type = fn_table_entry->type_entry; TypeTableEntry *return_type = fn_type->data.fn.fn_type_id.return_type; if (return_type->id != TypeTableEntryIdErrorUnion && return_type->id != TypeTableEntryIdPureError) { @@ -572,7 +575,7 @@ static LLVMValueRef fn_llvm_value(CodeGen *g, FnTableEntry *fn_table_entry) { } } - uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(fn_table_entry); + uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(g, fn_table_entry); if (err_ret_trace_arg_index != UINT32_MAX) { addLLVMArgAttr(fn_table_entry->llvm_value, (unsigned)err_ret_trace_arg_index, "nonnull"); } @@ -1415,31 +1418,33 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutable *executable, IrIns LLVMValueRef value = ir_llvm_value(g, return_instruction->value); TypeTableEntry *return_type = return_instruction->value->value.type; - bool is_err_return = false; - if (return_type->id == TypeTableEntryIdErrorUnion) { - if (return_instruction->value->value.special == ConstValSpecialStatic) { - is_err_return = return_instruction->value->value.data.x_err_union.err != nullptr; - } else if (return_instruction->value->value.special == ConstValSpecialRuntime) { - is_err_return = return_instruction->value->value.data.rh_error_union == RuntimeHintErrorUnionError; - // TODO: emit a branch to check if the return value is an error + if (g->have_err_ret_tracing) { + bool is_err_return = false; + if (return_type->id == TypeTableEntryIdErrorUnion) { + if (return_instruction->value->value.special == ConstValSpecialStatic) { + is_err_return = return_instruction->value->value.data.x_err_union.err != nullptr; + } else if (return_instruction->value->value.special == ConstValSpecialRuntime) { + is_err_return = return_instruction->value->value.data.rh_error_union == RuntimeHintErrorUnionError; + // TODO: emit a branch to check if the return value is an error + } + } else if (return_type->id == TypeTableEntryIdPureError) { + is_err_return = true; + } + if (is_err_return) { + LLVMBasicBlockRef return_block = LLVMAppendBasicBlock(g->cur_fn_val, "ReturnError"); + LLVMValueRef block_address = LLVMBlockAddress(g->cur_fn_val, return_block); + + LLVMValueRef return_err_fn = get_return_err_fn(g); + LLVMValueRef args[] = { + g->cur_err_ret_trace_val, + block_address, + }; + LLVMBuildBr(g->builder, return_block); + LLVMPositionBuilderAtEnd(g->builder, return_block); + LLVMValueRef call_instruction = ZigLLVMBuildCall(g->builder, return_err_fn, args, 2, + get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, ""); + LLVMSetTailCall(call_instruction, true); } - } else if (return_type->id == TypeTableEntryIdPureError) { - is_err_return = true; - } - if (is_err_return) { - LLVMBasicBlockRef return_block = LLVMAppendBasicBlock(g->cur_fn_val, "ReturnError"); - LLVMValueRef block_address = LLVMBlockAddress(g->cur_fn_val, return_block); - - LLVMValueRef return_err_fn = get_return_err_fn(g); - LLVMValueRef args[] = { - g->cur_err_ret_trace_val, - block_address, - }; - LLVMBuildBr(g->builder, return_block); - LLVMPositionBuilderAtEnd(g->builder, return_block); - LLVMValueRef call_instruction = ZigLLVMBuildCall(g->builder, return_err_fn, args, 2, - get_llvm_cc(g, CallingConventionUnspecified), ZigLLVM_FnInlineAuto, ""); - LLVMSetTailCall(call_instruction, true); } if (handle_is_ptr(return_type)) { if (calling_convention_does_first_arg_return(g->cur_fn->type_entry->data.fn.fn_type_id.cc)) { @@ -2475,7 +2480,7 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutable *executable, IrInstr TypeTableEntry *src_return_type = fn_type_id->return_type; bool ret_has_bits = type_has_bits(src_return_type); bool first_arg_ret = ret_has_bits && handle_is_ptr(src_return_type); - bool prefix_arg_err_ret_stack = src_return_type->id == TypeTableEntryIdErrorUnion || src_return_type->id == TypeTableEntryIdPureError; + bool prefix_arg_err_ret_stack = g->have_err_ret_tracing && (src_return_type->id == TypeTableEntryIdErrorUnion || src_return_type->id == TypeTableEntryIdPureError); size_t actual_param_count = instruction->arg_count + (first_arg_ret ? 1 : 0) + (prefix_arg_err_ret_stack ? 1 : 0); bool is_var_args = fn_type_id->is_var_args; LLVMValueRef *gen_param_values = allocate(actual_param_count); @@ -3031,6 +3036,16 @@ static LLVMValueRef ir_render_align_cast(CodeGen *g, IrExecutable *executable, I return target_val; } +static LLVMValueRef ir_render_error_return_trace(CodeGen *g, IrExecutable *executable, + IrInstructionErrorReturnTrace *instruction) +{ + TypeTableEntry *ptr_to_stack_trace_type = get_ptr_to_stack_trace_type(g); + if (g->cur_err_ret_trace_val == nullptr) { + return LLVMConstNull(ptr_to_stack_trace_type->type_ref); + } + return g->cur_err_ret_trace_val; +} + static LLVMAtomicOrdering to_LLVMAtomicOrdering(AtomicOrder atomic_order) { switch (atomic_order) { case AtomicOrderUnordered: return LLVMAtomicOrderingUnordered; @@ -3804,6 +3819,8 @@ static LLVMValueRef ir_render_instruction(CodeGen *g, IrExecutable *executable, return ir_render_field_parent_ptr(g, executable, (IrInstructionFieldParentPtr *)instruction); case IrInstructionIdAlignCast: return ir_render_align_cast(g, executable, (IrInstructionAlignCast *)instruction); + case IrInstructionIdErrorReturnTrace: + return ir_render_error_return_trace(g, executable, (IrInstructionErrorReturnTrace *)instruction); } zig_unreachable(); } @@ -4653,10 +4670,10 @@ static void do_code_gen(CodeGen *g) { build_all_basic_blocks(g, fn_table_entry); clear_debug_source_node(g); - uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(fn_table_entry); + uint32_t err_ret_trace_arg_index = get_err_ret_trace_arg_index(g, fn_table_entry); if (err_ret_trace_arg_index != UINT32_MAX) { g->cur_err_ret_trace_val = LLVMGetParam(fn, err_ret_trace_arg_index); - } else if (fn_table_entry->calls_errorable_function) { + } else if (g->have_err_ret_tracing && fn_table_entry->calls_errorable_function) { // TODO call graph analysis to find out what this number needs to be for every function static const size_t stack_trace_ptr_count = 30; @@ -5251,6 +5268,7 @@ static void define_builtin_fns(CodeGen *g) { create_builtin_fn(g, BuiltinFnIdSetAlignStack, "setAlignStack", 1); create_builtin_fn(g, BuiltinFnIdArgType, "ArgType", 2); create_builtin_fn(g, BuiltinFnIdExport, "export", 3); + create_builtin_fn(g, BuiltinFnIdErrorReturnTrace, "errorReturnTrace", 0); } static const char *bool_to_str(bool b) { @@ -5553,6 +5571,8 @@ static void init(CodeGen *g) { } } + g->have_err_ret_tracing = g->build_mode != BuildModeFastRelease; + define_builtin_fns(g); define_builtin_compile_vars(g); } diff --git a/src/ir.cpp b/src/ir.cpp index 5dd608083f..d6844f32ba 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -572,6 +572,10 @@ static constexpr IrInstructionId ir_instruction_id(IrInstructionArgType *) { return IrInstructionIdArgType; } +static constexpr IrInstructionId ir_instruction_id(IrInstructionErrorReturnTrace *) { + return IrInstructionIdErrorReturnTrace; +} + template static T *ir_create_instruction(IrBuilder *irb, Scope *scope, AstNode *source_node) { T *special_instruction = allocate(1); @@ -2305,6 +2309,12 @@ static IrInstruction *ir_build_arg_type(IrBuilder *irb, Scope *scope, AstNode *s return &instruction->base; } +static IrInstruction *ir_build_error_return_trace(IrBuilder *irb, Scope *scope, AstNode *source_node) { + IrInstructionErrorReturnTrace *instruction = ir_build_instruction(irb, scope, source_node); + + return &instruction->base; +} + static void ir_count_defers(IrBuilder *irb, Scope *inner_scope, Scope *outer_scope, size_t *results) { results[ReturnKindUnconditional] = 0; results[ReturnKindError] = 0; @@ -3731,6 +3741,10 @@ static IrInstruction *ir_gen_builtin_fn_call(IrBuilder *irb, Scope *scope, AstNo return ir_build_export(irb, scope, node, arg0_value, arg1_value, arg2_value); } + case BuiltinFnIdErrorReturnTrace: + { + return ir_build_error_return_trace(irb, scope, node); + } } zig_unreachable(); } @@ -9568,6 +9582,24 @@ static TypeTableEntry *ir_analyze_instruction_export(IrAnalyze *ira, IrInstructi return ira->codegen->builtin_types.entry_void; } +static TypeTableEntry *ir_analyze_instruction_error_return_trace(IrAnalyze *ira, + IrInstructionErrorReturnTrace *instruction) +{ + FnTableEntry *fn_entry = exec_fn_entry(ira->new_irb.exec); + TypeTableEntry *ptr_to_stack_trace_type = get_ptr_to_stack_trace_type(ira->codegen); + TypeTableEntry *nullable_type = get_maybe_type(ira->codegen, ptr_to_stack_trace_type); + if (fn_entry == nullptr || !fn_entry->calls_errorable_function || !ira->codegen->have_err_ret_tracing) { + ConstExprValue *out_val = ir_build_const_from(ira, &instruction->base); + out_val->data.x_maybe = nullptr; + return nullable_type; + } + + IrInstruction *new_instruction = ir_build_error_return_trace(&ira->new_irb, instruction->base.scope, + instruction->base.source_node); + ir_link_new_instruction(new_instruction, &instruction->base); + return nullable_type; +} + static bool ir_analyze_fn_call_inline_arg(IrAnalyze *ira, AstNode *fn_proto_node, IrInstruction *arg, Scope **exec_scope, size_t *next_proto_i) { @@ -15324,6 +15356,8 @@ static TypeTableEntry *ir_analyze_instruction_nocast(IrAnalyze *ira, IrInstructi return ir_analyze_instruction_tag_type(ira, (IrInstructionTagType *)instruction); case IrInstructionIdExport: return ir_analyze_instruction_export(ira, (IrInstructionExport *)instruction); + case IrInstructionIdErrorReturnTrace: + return ir_analyze_instruction_error_return_trace(ira, (IrInstructionErrorReturnTrace *)instruction); } zig_unreachable(); } @@ -15507,6 +15541,7 @@ bool ir_has_side_effects(IrInstruction *instruction) { case IrInstructionIdOpaqueType: case IrInstructionIdArgType: case IrInstructionIdTagType: + case IrInstructionIdErrorReturnTrace: return false; case IrInstructionIdAsm: { diff --git a/src/ir_print.cpp b/src/ir_print.cpp index f5aba2a45d..930d22f21a 100644 --- a/src/ir_print.cpp +++ b/src/ir_print.cpp @@ -996,6 +996,10 @@ static void ir_print_export(IrPrint *irp, IrInstructionExport *instruction) { } } +static void ir_print_error_return_trace(IrPrint *irp, IrInstructionErrorReturnTrace *instruction) { + fprintf(irp->f, "@errorReturnTrace()"); +} + static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { ir_print_prefix(irp, instruction); @@ -1308,6 +1312,9 @@ static void ir_print_instruction(IrPrint *irp, IrInstruction *instruction) { case IrInstructionIdExport: ir_print_export(irp, (IrInstructionExport *)instruction); break; + case IrInstructionIdErrorReturnTrace: + ir_print_error_return_trace(irp, (IrInstructionErrorReturnTrace *)instruction); + break; } fprintf(irp->f, "\n"); } diff --git a/std/build.zig b/std/build.zig index 6984715466..5d79b00c4f 100644 --- a/std/build.zig +++ b/std/build.zig @@ -247,11 +247,11 @@ pub const Builder = struct { defer wanted_steps.deinit(); if (step_names.len == 0) { - wanted_steps.append(&self.default_step) catch unreachable; + try wanted_steps.append(&self.default_step); } else { for (step_names) |step_name| { const s = try self.getTopLevelStepByName(step_name); - wanted_steps.append(s) catch unreachable; + try wanted_steps.append(s); } } diff --git a/std/debug/index.zig b/std/debug/index.zig index f7f6ffa323..5dc7b0dbfa 100644 --- a/std/debug/index.zig +++ b/std/debug/index.zig @@ -56,7 +56,7 @@ pub fn dumpCurrentStackTrace() { } /// Tries to print a stack trace to stderr, unbuffered, and ignores any error returned. -pub fn dumpStackTrace(stack_trace: &builtin.StackTrace) { +pub fn dumpStackTrace(stack_trace: &const builtin.StackTrace) { const stderr = getStderrStream() catch return; const debug_info = openSelfDebugInfo(global_allocator) catch |err| { stderr.print("Unable to open debug info: {}\n", @errorName(err)) catch return; @@ -127,7 +127,7 @@ const RESET = "\x1b[0m"; error PathNotFound; error InvalidDebugInfo; -pub fn writeStackTrace(stack_trace: &builtin.StackTrace, out_stream: &io.OutStream, allocator: &mem.Allocator, +pub fn writeStackTrace(stack_trace: &const builtin.StackTrace, out_stream: &io.OutStream, allocator: &mem.Allocator, debug_info: &ElfStackTrace, tty_color: bool) -> %void { var frame_index: usize = undefined; @@ -167,6 +167,9 @@ pub fn writeCurrentStackTrace(out_stream: &io.OutStream, allocator: &mem.Allocat } fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: &io.OutStream, address: usize) -> %void { + if (builtin.os == builtin.Os.windows) { + return error.UnsupportedDebugInfo; + } // TODO we really should be able to convert @sizeOf(usize) * 2 to a string literal // at compile time. I'll call it issue #313 const ptr_hex = if (@sizeOf(usize) == 4) "0x{x8}" else "0x{x16}"; @@ -177,7 +180,7 @@ fn printSourceAtAddress(debug_info: &ElfStackTrace, out_stream: &io.OutStream, a return; }; const compile_unit_name = try compile_unit.die.getAttrString(debug_info, DW.AT_name); - if (getLineNumberInfo(debug_info, compile_unit, usize(address) - 1)) |line_info| { + if (getLineNumberInfo(debug_info, compile_unit, address - 1)) |line_info| { defer line_info.deinit(); try out_stream.print(WHITE ++ "{}:{}:{}" ++ RESET ++ ": " ++ DIM ++ ptr_hex ++ " in ??? ({})" ++ RESET ++ "\n", diff --git a/std/io.zig b/std/io.zig index c6fd5502a2..605553b0ea 100644 --- a/std/io.zig +++ b/std/io.zig @@ -224,7 +224,7 @@ pub const File = struct { }; } }, - else => @compileError("unsupported OS"), + else => @compileError("unsupported OS: " ++ @tagName(builtin.os)), } } diff --git a/std/os/index.zig b/std/os/index.zig index e338359522..fd8eb84ab4 100644 --- a/std/os/index.zig +++ b/std/os/index.zig @@ -148,7 +148,7 @@ pub coldcc fn abort() -> noreturn { } /// Exits the program cleanly with the specified status code. -pub coldcc fn exit(status: i32) -> noreturn { +pub coldcc fn exit(status: u8) -> noreturn { if (builtin.link_libc) { c.exit(status); } @@ -157,14 +157,7 @@ pub coldcc fn exit(status: i32) -> noreturn { posix.exit(status); }, Os.windows => { - // Map a possibly negative status code to a non-negative status for the systems default - // integer width. - const p_status = if (@sizeOf(c_uint) < @sizeOf(u32)) - @truncate(c_uint, @bitCast(u32, status)) - else - c_uint(@bitCast(u32, status)); - - windows.ExitProcess(p_status); + windows.ExitProcess(status); }, else => @compileError("Unsupported OS"), } diff --git a/std/special/bootstrap.zig b/std/special/bootstrap.zig index 55d28a2d0e..77bb7316a9 100644 --- a/std/special/bootstrap.zig +++ b/std/special/bootstrap.zig @@ -21,8 +21,7 @@ comptime { } extern fn zenMain() -> noreturn { - root.main() catch std.os.posix.exit(1); - std.os.posix.exit(0); + std.os.posix.exit(callMain()); } nakedcc fn _start() -> noreturn { @@ -43,29 +42,55 @@ nakedcc fn _start() -> noreturn { extern fn WinMainCRTStartup() -> noreturn { @setAlignStack(16); - root.main() catch std.os.windows.ExitProcess(1); - std.os.windows.ExitProcess(0); + std.os.windows.ExitProcess(callMain()); } fn posixCallMainAndExit() -> noreturn { const argc = *argc_ptr; const argv = @ptrCast(&&u8, &argc_ptr[1]); const envp = @ptrCast(&?&u8, &argv[argc + 1]); - callMain(argc, argv, envp) catch std.os.posix.exit(1); - std.os.posix.exit(0); + std.os.posix.exit(callMainWithArgs(argc, argv, envp)); } -fn callMain(argc: usize, argv: &&u8, envp: &?&u8) -> %void { +fn callMainWithArgs(argc: usize, argv: &&u8, envp: &?&u8) -> u8 { std.os.ArgIteratorPosix.raw = argv[0..argc]; var env_count: usize = 0; while (envp[env_count] != null) : (env_count += 1) {} std.os.posix_environ_raw = @ptrCast(&&u8, envp)[0..env_count]; - return root.main(); + return callMain(); } extern fn main(c_argc: i32, c_argv: &&u8, c_envp: &?&u8) -> i32 { - callMain(usize(c_argc), c_argv, c_envp) catch return 1; - return 0; + return callMainWithArgs(usize(c_argc), c_argv, c_envp); +} + +fn callMain() -> u8 { + switch (@typeId(@typeOf(root.main).ReturnType)) { + builtin.TypeId.NoReturn => { + root.main(); + }, + builtin.TypeId.Void => { + root.main(); + return 0; + }, + builtin.TypeId.Int => { + if (@typeOf(root.main).ReturnType.bit_count != 8) { + @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '%void'"); + } + return root.main(); + }, + builtin.TypeId.ErrorUnion => { + root.main() catch |err| { + std.debug.warn("error: {}\n", @errorName(err)); + if (@errorReturnTrace()) |trace| { + std.debug.dumpStackTrace(trace); + } + return 1; + }; + return 0; + }, + else => @compileError("expected return type of main to be 'u8', 'noreturn', 'void', or '%void'"), + } } diff --git a/std/special/build_runner.zig b/std/special/build_runner.zig index b293a1e64d..4722aff07a 100644 --- a/std/special/build_runner.zig +++ b/std/special/build_runner.zig @@ -14,7 +14,7 @@ pub fn main() -> %void { var arg_it = os.args(); // TODO use a more general purpose allocator here - var inc_allocator = std.heap.IncrementingAllocator.init(40 * 1024 * 1024) catch unreachable; + var inc_allocator = try std.heap.IncrementingAllocator.init(40 * 1024 * 1024); defer inc_allocator.deinit(); const allocator = &inc_allocator.allocator; @@ -107,12 +107,12 @@ pub fn main() -> %void { return usageAndErr(&builder, false, try stderr_stream); } } else { - targets.append(arg) catch unreachable; + try targets.append(arg); } } builder.setInstallPrefix(prefix); - root.build(&builder) catch unreachable; + try root.build(&builder); if (builder.validateUserInputDidItFail()) return usageAndErr(&builder, true, try stderr_stream); @@ -129,7 +129,7 @@ fn usage(builder: &Builder, already_ran_build: bool, out_stream: &io.OutStream) // run the build script to collect the options if (!already_ran_build) { builder.setInstallPrefix(null); - root.build(builder) catch unreachable; + try root.build(builder); } // This usage text has to be synchronized with src/main.cpp diff --git a/std/special/test_runner.zig b/std/special/test_runner.zig index 027251c52c..c5c0bad40c 100644 --- a/std/special/test_runner.zig +++ b/std/special/test_runner.zig @@ -8,14 +8,7 @@ pub fn main() -> %void { for (test_fn_list) |test_fn, i| { warn("Test {}/{} {}...", i + 1, test_fn_list.len, test_fn.name); - if (builtin.is_test) { - test_fn.func() catch unreachable; - } else { - test_fn.func() catch |err| { - warn("{}\n", err); - return err; - }; - } + try test_fn.func(); warn("OK\n"); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 005fe55e41..6b2ea545ed 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -1,6 +1,15 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompileErrorContext) { + cases.add("wrong return type for main", + \\pub fn main() -> f32 { } + , "error: expected return type of main to be 'u8', 'noreturn', 'void', or '%void'"); + + cases.add("double ?? on main return value", + \\pub fn main() -> ??void { + \\} + , "error: expected return type of main to be 'u8', 'noreturn', 'void', or '%void'"); + cases.add("bad identifier in function with struct defined inside function which references local const", \\export fn entry() { \\ const BlockKind = u32; @@ -1059,15 +1068,6 @@ pub fn addCases(cases: &tests.CompileErrorContext) { , ".tmp_source.zig:2:5: error: expected type 'void', found 'error'"); - cases.add("wrong return type for main", - \\pub fn main() { } - , ".tmp_source.zig:1:15: error: expected return type of main to be '%void', instead is 'void'"); - - cases.add("double ?? on main return value", - \\pub fn main() -> ??void { - \\} - , ".tmp_source.zig:1:18: error: expected return type of main to be '%void', instead is '??void'"); - cases.add("invalid pointer for var type", \\extern fn ext() -> usize; \\var bytes: [ext()]u8 = undefined; diff --git a/test/debug_safety.zig b/test/debug_safety.zig index fde5b061ee..b32ffb34f0 100644 --- a/test/debug_safety.zig +++ b/test/debug_safety.zig @@ -2,7 +2,7 @@ const tests = @import("tests.zig"); pub fn addCases(cases: &tests.CompareOutputContext) { cases.addDebugSafety("calling panic", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\pub fn main() -> %void { @@ -11,7 +11,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("out of bounds slice access", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\pub fn main() -> %void { @@ -25,7 +25,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("integer addition overflow", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -39,7 +39,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("integer subtraction overflow", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -53,7 +53,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("integer multiplication overflow", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -67,7 +67,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("integer negation overflow", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -81,7 +81,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("signed integer division overflow", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -95,7 +95,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("signed shift left overflow", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -109,7 +109,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("unsigned shift left overflow", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -123,7 +123,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("signed shift right overflow", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -137,7 +137,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("unsigned shift right overflow", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -151,7 +151,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("integer division by zero", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -164,7 +164,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("exact division failure", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -178,7 +178,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("cast []u8 to bigger slice of wrong size", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -192,7 +192,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("value does not fit in shortening cast", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -206,7 +206,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("signed integer not fitting in cast to unsigned integer", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Whatever; @@ -220,7 +220,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("unwrap error", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ if (@import("std").mem.eql(u8, message, "attempt to unwrap error: Whatever")) { \\ @import("std").os.exit(126); // good \\ } @@ -236,7 +236,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("cast integer to error and no code matches", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\pub fn main() -> %void { @@ -248,7 +248,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("@alignCast misaligned", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\error Wrong; @@ -265,7 +265,7 @@ pub fn addCases(cases: &tests.CompareOutputContext) { ); cases.addDebugSafety("bad union field access", - \\pub fn panic(message: []const u8) -> noreturn { + \\pub fn panic(message: []const u8, stack_trace: ?&@import("builtin").StackTrace) -> noreturn { \\ @import("std").os.exit(126); \\} \\ diff --git a/test/standalone/issue_339/build.zig b/test/standalone/issue_339/build.zig index 50fa10c593..b7b288f7a1 100644 --- a/test/standalone/issue_339/build.zig +++ b/test/standalone/issue_339/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) { +pub fn build(b: &Builder) -> %void { const obj = b.addObject("test", "test.zig"); const test_step = b.step("test", "Test the program"); diff --git a/test/standalone/issue_339/test.zig b/test/standalone/issue_339/test.zig index e0d6c3f3e5..dd97ffa9b1 100644 --- a/test/standalone/issue_339/test.zig +++ b/test/standalone/issue_339/test.zig @@ -1,4 +1,5 @@ -pub fn panic(msg: []const u8) -> noreturn { @breakpoint(); while (true) {} } +const StackTrace = @import("builtin").StackTrace; +pub fn panic(msg: []const u8, stack_trace: ?&StackTrace) -> noreturn { @breakpoint(); while (true) {} } fn bar() -> %void {} diff --git a/test/standalone/pkg_import/build.zig b/test/standalone/pkg_import/build.zig index 044787dd4d..f40011d834 100644 --- a/test/standalone/pkg_import/build.zig +++ b/test/standalone/pkg_import/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) { +pub fn build(b: &Builder) -> %void { const exe = b.addExecutable("test", "test.zig"); exe.addPackagePath("my_pkg", "pkg.zig"); diff --git a/test/standalone/use_alias/build.zig b/test/standalone/use_alias/build.zig index 03398f1a41..beca6bc304 100644 --- a/test/standalone/use_alias/build.zig +++ b/test/standalone/use_alias/build.zig @@ -1,6 +1,6 @@ const Builder = @import("std").build.Builder; -pub fn build(b: &Builder) { +pub fn build(b: &Builder) -> %void { b.addCIncludePath("."); const main = b.addTest("main.zig"); diff --git a/test/tests.zig b/test/tests.zig index 20e1e94459..5fd995f034 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -50,6 +50,7 @@ const test_targets = []TestTarget { }; error TestFailed; +error CompilationIncorrectlySucceeded; const max_stdout_size = 1 * 1024 * 1024; // 1 MB @@ -607,8 +608,7 @@ pub const CompileErrorContext = struct { switch (term) { Term.Exited => |code| { if (code == 0) { - warn("Compilation incorrectly succeeded\n"); - return error.TestFailed; + return error.CompilationIncorrectlySucceeded; } }, else => { -- cgit v1.2.3 From 92fc5947fc18ea077f0e02bc4758e9050270e4bb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 15 Jan 2018 20:44:21 -0500 Subject: fix compiler crash related to @alignOf --- src/analyze.cpp | 1 + src/ir.cpp | 4 +- test/build_examples.zig | 1 + test/standalone/brace_expansion/build.zig | 9 ++ test/standalone/brace_expansion/main.zig | 254 ++++++++++++++++++++++++++++++ 5 files changed, 267 insertions(+), 2 deletions(-) create mode 100644 test/standalone/brace_expansion/build.zig create mode 100644 test/standalone/brace_expansion/main.zig (limited to 'src/analyze.cpp') diff --git a/src/analyze.cpp b/src/analyze.cpp index 8028ace289..9da8485014 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -2231,6 +2231,7 @@ static void resolve_union_zero_bits(CodeGen *g, TypeTableEntry *union_type) { // is a pointer to this very struct, or a function pointer with parameters that // reference such a type. union_type->data.unionation.zero_bits_known = true; + union_type->data.unionation.zero_bits_loop_flag = false; if (union_type->data.unionation.abi_alignment == 0) { if (union_type->data.unionation.layout == ContainerLayoutPacked) { union_type->data.unionation.abi_alignment = 1; diff --git a/src/ir.cpp b/src/ir.cpp index d6844f32ba..4dd022c09f 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -9858,7 +9858,7 @@ static TypeTableEntry *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCall *cal if (fn_proto_node->data.fn_proto.is_var_args) { ir_add_error(ira, &call_instruction->base, - buf_sprintf("compiler bug: unable to call var args function at compile time. https://github.com/andrewrk/zig/issues/313")); + buf_sprintf("compiler bug: unable to call var args function at compile time. https://github.com/zig-lang/zig/issues/313")); return ira->codegen->builtin_types.entry_invalid; } @@ -14011,7 +14011,7 @@ static TypeTableEntry *ir_analyze_instruction_align_of(IrAnalyze *ira, IrInstruc return ira->codegen->builtin_types.entry_invalid; TypeTableEntry *type_entry = ir_resolve_type(ira, type_value); - ensure_complete_type(ira->codegen, type_entry); + type_ensure_zero_bits_known(ira->codegen, type_entry); if (type_is_invalid(type_entry)) return ira->codegen->builtin_types.entry_invalid; diff --git a/test/build_examples.zig b/test/build_examples.zig index 29a15fd515..666dac8df1 100644 --- a/test/build_examples.zig +++ b/test/build_examples.zig @@ -16,4 +16,5 @@ pub fn addCases(cases: &tests.BuildExamplesContext) { cases.addBuildFile("test/standalone/issue_339/build.zig"); cases.addBuildFile("test/standalone/pkg_import/build.zig"); cases.addBuildFile("test/standalone/use_alias/build.zig"); + cases.addBuildFile("test/standalone/brace_expansion/build.zig"); } diff --git a/test/standalone/brace_expansion/build.zig b/test/standalone/brace_expansion/build.zig new file mode 100644 index 0000000000..d170b50fe0 --- /dev/null +++ b/test/standalone/brace_expansion/build.zig @@ -0,0 +1,9 @@ +const Builder = @import("std").build.Builder; + +pub fn build(b: &Builder) -> %void { + const main = b.addTest("main.zig"); + main.setBuildMode(b.standardReleaseOptions()); + + const test_step = b.step("test", "Test it"); + test_step.dependOn(&main.step); +} diff --git a/test/standalone/brace_expansion/main.zig b/test/standalone/brace_expansion/main.zig new file mode 100644 index 0000000000..7b03e230f7 --- /dev/null +++ b/test/standalone/brace_expansion/main.zig @@ -0,0 +1,254 @@ +const std = @import("std"); +const io = std.io; +const mem = std.mem; +const debug = std.debug; +const assert = debug.assert; +const Buffer = std.Buffer; +const ArrayList = std.ArrayList; + +error InvalidInput; +error OutOfMem; + +const Token = union(enum) { + Word: []const u8, + OpenBrace, + CloseBrace, + Comma, + Eof, +}; + +var global_allocator: &mem.Allocator = undefined; + +fn tokenize(input:[] const u8) -> %ArrayList(Token) { + const State = enum { + Start, + Word, + }; + + var token_list = ArrayList(Token).init(global_allocator); + var tok_begin: usize = undefined; + var state = State.Start; + + for (input) |b, i| { + switch (state) { + State.Start => switch (b) { + 'a'...'z', 'A'...'Z' => { + state = State.Word; + tok_begin = i; + }, + '{' => try token_list.append(Token.OpenBrace), + '}' => try token_list.append(Token.CloseBrace), + ',' => try token_list.append(Token.Comma), + else => return error.InvalidInput, + }, + State.Word => switch (b) { + 'a'...'z', 'A'...'Z' => {}, + '{', '}', ',' => { + try token_list.append(Token { .Word = input[tok_begin..i] }); + switch (b) { + '{' => try token_list.append(Token.OpenBrace), + '}' => try token_list.append(Token.CloseBrace), + ',' => try token_list.append(Token.Comma), + else => unreachable, + } + state = State.Start; + }, + else => return error.InvalidInput, + }, + } + } + switch (state) { + State.Start => {}, + State.Word => try token_list.append(Token {.Word = input[tok_begin..] }), + } + try token_list.append(Token.Eof); + return token_list; +} + +const Node = union(enum) { + Scalar: []const u8, + List: ArrayList(Node), + Combine: []Node, +}; + +fn parse(tokens: &const ArrayList(Token), token_index: &usize) -> %Node { + const first_token = tokens.items[*token_index]; + *token_index += 1; + + const result_node = switch (first_token) { + Token.Word => |word| Node { .Scalar = word }, + Token.OpenBrace => blk: { + var list = ArrayList(Node).init(global_allocator); + while (true) { + try list.append(try parse(tokens, token_index)); + + const token = tokens.items[*token_index]; + *token_index += 1; + + switch (token) { + Token.CloseBrace => break, + Token.Comma => continue, + else => return error.InvalidInput, + } + } + break :blk Node { .List = list }; + }, + else => return error.InvalidInput, + }; + + switch (tokens.items[*token_index]) { + Token.Word, Token.OpenBrace => { + const pair = try global_allocator.alloc(Node, 2); + pair[0] = result_node; + pair[1] = try parse(tokens, token_index); + return Node { .Combine = pair }; + }, + else => return result_node, + } +} + +fn expandString(input: []const u8, output: &Buffer) -> %void { + const tokens = try tokenize(input); + if (tokens.len == 1) { + return output.resize(0); + } + + var token_index: usize = 0; + const root = try parse(tokens, &token_index); + const last_token = tokens.items[token_index]; + switch (last_token) { + Token.Eof => {}, + else => return error.InvalidInput, + } + + var result_list = ArrayList(Buffer).init(global_allocator); + defer result_list.deinit(); + + try expandNode(root, &result_list); + + try output.resize(0); + for (result_list.toSliceConst()) |buf, i| { + if (i != 0) { + try output.appendByte(' '); + } + try output.append(buf.toSliceConst()); + } +} + +const ListOfBuffer0 = ArrayList(Buffer); // TODO this is working around a compiler bug, fix and delete this + +fn expandNode(node: &const Node, output: &ListOfBuffer0) -> %void { + assert(output.len == 0); + switch (*node) { + Node.Scalar => |scalar| { + try output.append(try Buffer.init(global_allocator, scalar)); + }, + Node.Combine => |pair| { + const a_node = pair[0]; + const b_node = pair[1]; + + var child_list_a = ArrayList(Buffer).init(global_allocator); + try expandNode(a_node, &child_list_a); + + var child_list_b = ArrayList(Buffer).init(global_allocator); + try expandNode(b_node, &child_list_b); + + for (child_list_a.toSliceConst()) |buf_a| { + for (child_list_b.toSliceConst()) |buf_b| { + var combined_buf = try Buffer.initFromBuffer(buf_a); + try combined_buf.append(buf_b.toSliceConst()); + try output.append(combined_buf); + } + } + }, + Node.List => |list| { + for (list.toSliceConst()) |child_node| { + var child_list = ArrayList(Buffer).init(global_allocator); + try expandNode(child_node, &child_list); + + for (child_list.toSliceConst()) |buf| { + try output.append(buf); + } + } + }, + } +} + +pub fn main() -> %void { + var stdin_file = try io.getStdIn(); + var stdout_file = try io.getStdOut(); + + var inc_allocator = try std.heap.IncrementingAllocator.init(2 * 1024 * 1024); + defer inc_allocator.deinit(); + + global_allocator = &inc_allocator.allocator; + + var stdin_buf = try Buffer.initSize(global_allocator, 0); + defer stdin_buf.deinit(); + + var stdin_adapter = io.FileInStream.init(&stdin_file); + try stdin_adapter.stream.readAllBuffer(&stdin_buf, @maxValue(usize)); + + var result_buf = try Buffer.initSize(global_allocator, 0); + defer result_buf.deinit(); + + try expandString(stdin_buf.toSlice(), &result_buf); + try stdout_file.write(result_buf.toSliceConst()); +} + +test "invalid inputs" { + global_allocator = std.debug.global_allocator; + + expectError("}ABC", error.InvalidInput); + expectError("{ABC", error.InvalidInput); + expectError("}{", error.InvalidInput); + expectError("{}", error.InvalidInput); + expectError("A,B,C", error.InvalidInput); + expectError("{A{B,C}", error.InvalidInput); + expectError("{A,}", error.InvalidInput); + + expectError("\n", error.InvalidInput); +} + +fn expectError(test_input: []const u8, expected_err: error) { + var output_buf = Buffer.initSize(global_allocator, 0) catch unreachable; + defer output_buf.deinit(); + + if (expandString("}ABC", &output_buf)) { + unreachable; + } else |err| { + assert(expected_err == err); + } +} + +test "valid inputs" { + global_allocator = std.debug.global_allocator; + + expectExpansion("{x,y,z}", "x y z"); + expectExpansion("{A,B}{x,y}", "Ax Ay Bx By"); + expectExpansion("{A,B{x,y}}", "A Bx By"); + + expectExpansion("{ABC}", "ABC"); + expectExpansion("{A,B,C}", "A B C"); + expectExpansion("ABC", "ABC"); + + expectExpansion("", ""); + expectExpansion("{A,B}{C,{x,y}}{g,h}", "ACg ACh Axg Axh Ayg Ayh BCg BCh Bxg Bxh Byg Byh"); + expectExpansion("{A,B}{C,C{x,y}}{g,h}", "ACg ACh ACxg ACxh ACyg ACyh BCg BCh BCxg BCxh BCyg BCyh"); + expectExpansion("{A,B}a", "Aa Ba"); + expectExpansion("{C,{x,y}}", "C x y"); + expectExpansion("z{C,{x,y}}", "zC zx zy"); + expectExpansion("a{b,c{d,e{f,g}}}", "ab acd acef aceg"); + expectExpansion("a{x,y}b", "axb ayb"); + expectExpansion("z{{a,b}}", "za zb"); + expectExpansion("a{b}", "ab"); +} + +fn expectExpansion(test_input: []const u8, expected_result: []const u8) { + var result = Buffer.initSize(global_allocator, 0) catch unreachable; + defer result.deinit(); + + expandString(test_input, &result) catch unreachable; + + assert(mem.eql(u8, result.toSlice(), expected_result)); +} -- cgit v1.2.3