diff --git a/NEWS.md b/NEWS.md index 4009a4a387e5f..d650733551474 100644 --- a/NEWS.md +++ b/NEWS.md @@ -4,6 +4,8 @@ Julia v1.1.0 Release Notes New language features --------------------- + * An *exception stack* is maintained on each task to make exception handling more robust and enable root cause analysis using `catch_stack` ([#28878]). + Language changes ---------------- @@ -36,5 +38,6 @@ Deprecated or removed [#28156]: https://github.com/JuliaLang/julia/issues/28156 +[#28878]: https://github.com/JuliaLang/julia/issues/28878 [#29440]: https://github.com/JuliaLang/julia/issues/29440 [#29442]: https://github.com/JuliaLang/julia/issues/29442 diff --git a/base/compiler/ssair/ir.jl b/base/compiler/ssair/ir.jl index b341ad7734d18..0317d05e8e32f 100644 --- a/base/compiler/ssair/ir.jl +++ b/base/compiler/ssair/ir.jl @@ -318,7 +318,7 @@ function is_relevant_expr(e::Expr) :gc_preserve_begin, :gc_preserve_end, :foreigncall, :isdefined, :copyast, :undefcheck, :throw_undef_if_not, - :cfunction, :method, + :cfunction, :method, :pop_exception, #=legacy IR format support=# :gotoifnot, :return) end diff --git a/base/compiler/ssair/slot2ssa.jl b/base/compiler/ssair/slot2ssa.jl index b88b88d20f8d2..c1dd86e1fa20f 100644 --- a/base/compiler/ssair/slot2ssa.jl +++ b/base/compiler/ssair/slot2ssa.jl @@ -802,6 +802,7 @@ function construct_ssa!(ci::CodeInfo, code::Vector{Any}, ir::IRCode, domtree::Do end elseif isexpr(stmt, :enter) new_code[idx] = Expr(:enter, block_for_inst(cfg, stmt.args[1])) + ssavalmap[idx] = SSAValue(idx) # Slot to store token for pop_exception elseif isexpr(stmt, :leave) || isexpr(stmt, :(=)) || isexpr(stmt, :return) || isexpr(stmt, :meta) || isa(stmt, NewvarNode) new_code[idx] = stmt diff --git a/base/compiler/validation.jl b/base/compiler/validation.jl index 03f3d89038e0d..567d06861ff1f 100644 --- a/base/compiler/validation.jl +++ b/base/compiler/validation.jl @@ -16,6 +16,7 @@ const VALID_EXPR_HEADS = IdDict{Any,Any}( :the_exception => 0:0, :enter => 1:1, :leave => 1:1, + :pop_exception => 1:1, :inbounds => 1:1, :boundscheck => 0:0, :copyast => 1:1, @@ -139,7 +140,7 @@ function validate_code!(errors::Vector{>:InvalidCodeError}, c::CodeInfo, is_top_ validate_val!(x.args[1]) elseif head === :call || head === :invoke || head == :gc_preserve_end || head === :meta || head === :inbounds || head === :foreigncall || head === :cfunction || - head === :const || head === :enter || head === :leave || + head === :const || head === :enter || head === :leave || head == :pop_exception || head === :method || head === :global || head === :static_parameter || head === :new || head === :thunk || head === :simdloop || head === :throw_undef_if_not || head === :unreachable diff --git a/base/error.jl b/base/error.jl index 3282323d0be50..3693129600736 100644 --- a/base/error.jl +++ b/base/error.jl @@ -91,6 +91,30 @@ function catch_backtrace() return _reformat_bt(bt[], bt2[]) end +""" + catch_stack(task=current_task(); [inclue_bt=true]) + +Get the stack of exceptions currently being handled. For nested catch blocks +there may be more than one current exception in which case the most recently +thrown exception is last in the stack. The stack is returned as a Vector of +`(exception,backtrace)` pairs, or a Vector of exceptions if `include_bt` is +false. + +Explicitly passing `task` will return the current exception stack on an +arbitrary task. This is useful for inspecting tasks which have failed due to +uncaught exceptions. +""" +function catch_stack(task=current_task(); include_bt=true) + raw = ccall(:jl_get_excstack, Any, (Any,Cint,Cint), task, include_bt, typemax(Cint)) + formatted = Any[] + stride = include_bt ? 3 : 1 + for i = reverse(1:stride:length(raw)) + e = raw[i] + push!(formatted, include_bt ? (e,Base._reformat_bt(raw[i+1],raw[i+2])) : e) + end + formatted +end + ## keyword arg lowering generates calls to this ## function kwerr(kw, args::Vararg{Any,N}) where {N} @_noinline_meta diff --git a/base/exports.jl b/base/exports.jl index 5da9a06207a62..de508c7998642 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -679,6 +679,7 @@ export # errors backtrace, catch_backtrace, + catch_stack, error, rethrow, retry, diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 1e7455a383cbc..4eebbd76f49aa 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -304,6 +304,7 @@ Core.throw Base.rethrow Base.backtrace Base.catch_backtrace +Base.catch_stack Base.@assert Base.ArgumentError Base.AssertionError diff --git a/doc/src/devdocs/ast.md b/doc/src/devdocs/ast.md index 8669b0fba3ff2..699a2b00d8d17 100644 --- a/doc/src/devdocs/ast.md +++ b/doc/src/devdocs/ast.md @@ -142,18 +142,22 @@ These symbols appear in the `head` field of `Expr`s in lowered form. * `the_exception` - Yields the caught exception inside a `catch` block. This is the value of the run time system variable - `jl_exception_in_transit`. + Yields the caught exception inside a `catch` block, as returned by `jl_current_exception()`. * `enter` Enters an exception handler (`setjmp`). `args[1]` is the label of the catch block to jump to on - error. + error. Yields a token which is consumed by `pop_exception`. * `leave` Pop exception handlers. `args[1]` is the number of handlers to pop. + * `pop_exception` + + Pop the stack of current exceptions back to the state at the associated `enter` when leaving a + catch block. `args[1]` contains the token from the associated `enter`. + * `inbounds` Controls turning bounds checks on or off. A stack is maintained; if the first argument of this diff --git a/doc/src/manual/control-flow.md b/doc/src/manual/control-flow.md index f73e523c35343..9de3b8206c8a7 100644 --- a/doc/src/manual/control-flow.md +++ b/doc/src/manual/control-flow.md @@ -793,8 +793,8 @@ end The power of the `try/catch` construct lies in the ability to unwind a deeply nested computation immediately to a much higher level in the stack of calling functions. There are situations where no error has occurred, but the ability to unwind the stack and pass a value to a higher level -is desirable. Julia provides the [`rethrow`](@ref), [`backtrace`](@ref) and [`catch_backtrace`](@ref) -functions for more advanced error handling. +is desirable. Julia provides the [`rethrow`](@ref), [`backtrace`](@ref), [`catch_backtrace`](@ref) +and [`catch_stack`](@ref) functions for more advanced error handling. ### `finally` Clauses diff --git a/doc/src/manual/stacktraces.md b/doc/src/manual/stacktraces.md index 776570d6b0c26..a7da7a6464cd7 100644 --- a/doc/src/manual/stacktraces.md +++ b/doc/src/manual/stacktraces.md @@ -187,6 +187,53 @@ ERROR: Whoops! [...] ``` +## Exception stacks and [`catch_stack`](@ref) + +While handling an exception further exceptions may be thrown. It can be useful to inspect all these exceptions to +identify the root cause of a problem. The julia runtime supports this by pushing each exception onto an internal +*exception stack* as it occurs. When the code exits a `catch` normally, any exceptions which were pushed onto the stack +in the associated `try` are considered to be successfully handled and are removed from the stack. + +The stack of current exceptions can be accessed using the [`catch_stack`](@ref) function. For example, + +```julia-repl +julia> try + error("(A) The root cause") + catch + try + error("(B) An exception while handling the exception") + catch + for (exc, bt) in catch_stack() + showerror(stdout, exc, bt) + println() + end + end + end +(A) The root cause +Stacktrace: + [1] error(::String) at error.jl:33 + [2] top-level scope at REPL[7]:2 + [3] eval(::Module, ::Any) at boot.jl:319 + [4] eval_user_input(::Any, ::REPL.REPLBackend) at REPL.jl:85 + [5] macro expansion at REPL.jl:117 [inlined] + [6] (::getfield(REPL, Symbol("##26#27")){REPL.REPLBackend})() at task.jl:259 +(B) An exception while handling the exception +Stacktrace: + [1] error(::String) at error.jl:33 + [2] top-level scope at REPL[7]:5 + [3] eval(::Module, ::Any) at boot.jl:319 + [4] eval_user_input(::Any, ::REPL.REPLBackend) at REPL.jl:85 + [5] macro expansion at REPL.jl:117 [inlined] + [6] (::getfield(REPL, Symbol("##26#27")){REPL.REPLBackend})() at task.jl:259 +``` + +In this example the root cause exception (A) is first on the stack, with a further exception (B) following it. After +exiting both catch blocks normally (i.e., without throwing a further exception) all exceptions are removed from the stack +and are no longer accessible. + +The exception stack is stored on the `Task` where the exceptions occurred. When a task fails with uncaught exceptions, +`catch_stack(task)` may be used to inspect the exception stack for that task. + ## Comparison with [`backtrace`](@ref) A call to [`backtrace`](@ref) returns a vector of `Union{Ptr{Nothing}, Base.InterpreterIP}`, which may then be passed into diff --git a/src/ast.c b/src/ast.c index d758892961c86..a84bc2f47b9cb 100644 --- a/src/ast.c +++ b/src/ast.c @@ -38,6 +38,7 @@ jl_sym_t *lambda_sym; jl_sym_t *assign_sym; jl_sym_t *globalref_sym; jl_sym_t *do_sym; jl_sym_t *method_sym; jl_sym_t *core_sym; jl_sym_t *enter_sym; jl_sym_t *leave_sym; +jl_sym_t *pop_exception_sym; jl_sym_t *exc_sym; jl_sym_t *error_sym; jl_sym_t *new_sym; jl_sym_t *using_sym; jl_sym_t *const_sym; jl_sym_t *thunk_sym; @@ -342,6 +343,7 @@ void jl_init_frontend(void) exc_sym = jl_symbol("the_exception"); enter_sym = jl_symbol("enter"); leave_sym = jl_symbol("leave"); + pop_exception_sym = jl_symbol("pop_exception"); new_sym = jl_symbol("new"); const_sym = jl_symbol("const"); global_sym = jl_symbol("global"); @@ -887,7 +889,9 @@ jl_value_t *jl_parse_eval_all(const char *fname, form = jl_pchar_to_string(fname, len); result = jl_box_long(jl_lineno); err = 1; + goto finally; // skip jl_restore_excstack } +finally: jl_get_ptls_states()->world_age = last_age; jl_lineno = last_lineno; jl_filename = last_filename; @@ -899,7 +903,7 @@ jl_value_t *jl_parse_eval_all(const char *fname, jl_rethrow(); else jl_rethrow_other(jl_new_struct(jl_loaderror_type, form, result, - ptls->exception_in_transit)); + jl_current_exception())); } JL_GC_POP(); return result; @@ -1042,7 +1046,7 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule margs[0] = jl_cstr_to_string(""); margs[1] = jl_fieldref(lno, 0); // extract and allocate line number jl_rethrow_other(jl_new_struct(jl_loaderror_type, margs[0], margs[1], - ptls->exception_in_transit)); + jl_current_exception())); } } ptls->world_age = last_age; diff --git a/src/cgutils.cpp b/src/cgutils.cpp index 248cbe4c902e2..83a1673eea9f9 100644 --- a/src/cgutils.cpp +++ b/src/cgutils.cpp @@ -2568,14 +2568,6 @@ static jl_cgval_t emit_new_struct(jl_codectx_t &ctx, jl_value_t *ty, size_t narg } } -static Value *emit_exc_in_transit(jl_codectx_t &ctx) -{ - Value *pexc_in_transit = emit_bitcast(ctx, ctx.ptlsStates, T_pprjlvalue); - Constant *offset = ConstantInt::getSigned(T_int32, - offsetof(jl_tls_states_t, exception_in_transit) / sizeof(void*)); - return ctx.builder.CreateInBoundsGEP(pexc_in_transit, ArrayRef(offset), "jl_exception_in_transit"); -} - static void emit_signal_fence(jl_codectx_t &ctx) { #if defined(_CPU_ARM_) || defined(_CPU_AARCH64_) diff --git a/src/codegen.cpp b/src/codegen.cpp index 4146fd2fd5ad5..3c2c6252bca63 100644 --- a/src/codegen.cpp +++ b/src/codegen.cpp @@ -287,7 +287,10 @@ static Function *jlgetfield_func; static Function *jlmethod_func; static Function *jlgenericfunction_func; static Function *jlenter_func; +static Function *jl_current_exception_func; static Function *jlleave_func; +static Function *jl_restore_excstack_func; +static Function *jl_excstack_state_func; static Function *jlegal_func; static Function *jl_alloc_obj_func; static Function *jl_newbits_func; @@ -320,7 +323,6 @@ static Function *jlgetnthfieldchecked_func; //static Function *jlsetnthfield_func; static Function *jlgetcfunctiontrampoline_func; #ifdef _OS_WINDOWS_ -static Function *resetstkoflw_func; #if defined(_CPU_X86_64_) Function *juliapersonality_func; #endif @@ -783,9 +785,8 @@ static void emit_write_barrier(jl_codectx_t&, Value*, Value*); static void jl_rethrow_with_add(const char *fmt, ...) { - jl_ptls_t ptls = jl_get_ptls_states(); - if (jl_typeis(ptls->exception_in_transit, jl_errorexception_type)) { - char *str = jl_string_data(jl_fieldref(ptls->exception_in_transit,0)); + if (jl_typeis(jl_current_exception(), jl_errorexception_type)) { + char *str = jl_string_data(jl_fieldref(jl_current_exception(),0)); char buf[1024]; va_list args; va_start(args, fmt); @@ -1776,7 +1777,13 @@ static jl_value_t *static_apply_type(jl_codectx_t &ctx, const jl_cgval_t *args, size_t last_age = jl_get_ptls_states()->world_age; // call apply_type, but ignore errors. we know that will work in world 1. jl_get_ptls_states()->world_age = 1; - jl_value_t *result = jl_apply_with_saved_exception_state(v, nargs, 1); + jl_value_t *result; + JL_TRY { + result = jl_apply(v, nargs); + } + JL_CATCH { + result = NULL; + } jl_get_ptls_states()->world_age = last_age; return result; } @@ -1858,7 +1865,13 @@ static jl_value_t *static_eval(jl_codectx_t &ctx, jl_value_t *ex, int sparams=tr size_t last_age = jl_get_ptls_states()->world_age; // here we know we're calling specific builtin functions that work in world 1. jl_get_ptls_states()->world_age = 1; - jl_value_t *result = jl_apply_with_saved_exception_state(v, n+1, 1); + jl_value_t *result; + JL_TRY { + result = jl_apply(v, n+1); + } + JL_CATCH { + result = NULL; + } jl_get_ptls_states()->world_age = last_age; JL_GC_POP(); return result; @@ -3776,6 +3789,12 @@ static void emit_stmtpos(jl_codectx_t &ctx, jl_value_t *expr, int ssaval_result) ctx.builder.CreateCall(prepare_call(jlleave_func), ConstantInt::get(T_int32, jl_unbox_long(args[0]))); } + else if (head == pop_exception_sym) { + jl_cgval_t excstack_state = emit_expr(ctx, jl_exprarg(expr, 0)); + assert(excstack_state.V && excstack_state.V->getType() == T_size); + ctx.builder.CreateCall(prepare_call(jl_restore_excstack_func), excstack_state.V); + return; + } else { if (!jl_is_method(ctx.linfo->def.method)) { // TODO: inference is invalid if this has an effect @@ -3930,8 +3949,7 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) bnd = jl_get_binding_for_method_def(mod, (jl_sym_t*)mn); } JL_CATCH { - jl_ptls_t ptls = jl_get_ptls_states(); - jl_value_t *e = ptls->exception_in_transit; + jl_value_t *e = jl_current_exception(); // errors. boo. root it somehow :( bnd = jl_get_binding_wr(ctx.module, (jl_sym_t*)jl_gensym(), 1); bnd->value = e; @@ -4000,9 +4018,9 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) Value *val = emit_jlcall(ctx, jlnew_func, typ, &argv[1], nargs - 1); return mark_julia_type(ctx, val, true, ty); } - else if (head == exc_sym) { // *ptls->exception_in_transit + else if (head == exc_sym) { return mark_julia_type(ctx, - ctx.builder.CreateLoad(emit_exc_in_transit(ctx), /*isvolatile*/true), + ctx.builder.CreateCall(prepare_call(jl_current_exception_func)), true, jl_any_type); } else if (head == copyast_sym) { @@ -4031,6 +4049,9 @@ static jl_cgval_t emit_expr(jl_codectx_t &ctx, jl_value_t *expr, ssize_t ssaval) else if (head == leave_sym) { jl_error("Expr(:leave) in value position"); } + else if (head == pop_exception_sym) { + jl_error("Expr(:pop_exception) in value position"); + } else if (head == enter_sym) { jl_error("Expr(:enter) in value position"); } @@ -6191,6 +6212,13 @@ static std::unique_ptr emit_function( assert(jl_is_long(args[0])); int lname = jl_unbox_long(args[0]); + // Save exception stack depth at enter for use in pop_exception + Value *excstack_state = + ctx.builder.CreateCall(prepare_call(jl_excstack_state_func)); + assert(!ctx.ssavalue_assigned.at(cursor)); + ctx.SAvalues.at(cursor) = jl_cgval_t(excstack_state, NULL, false, + (jl_value_t*)jl_ulong_type, NULL); + ctx.ssavalue_assigned.at(cursor) = true; CallInst *sj = ctx.builder.CreateCall(prepare_call(except_enter_func)); // We need to mark this on the call site as well. See issue #6757 sj->setCanReturnTwice(); @@ -6200,21 +6228,7 @@ static std::unique_ptr emit_function( handlr = BB[lname]; workstack.push_back(lname - 1); come_from_bb[cursor + 1] = ctx.builder.GetInsertBlock(); -#ifdef _OS_WINDOWS_ - BasicBlock *cond_resetstkoflw_blk = BasicBlock::Create(jl_LLVMContext, "cond_resetstkoflw", f); - BasicBlock *resetstkoflw_blk = BasicBlock::Create(jl_LLVMContext, "resetstkoflw", f); - ctx.builder.CreateCondBr(isz, tryblk, cond_resetstkoflw_blk); - ctx.builder.SetInsertPoint(cond_resetstkoflw_blk); - ctx.builder.CreateCondBr(ctx.builder.CreateICmpEQ( - maybe_decay_untracked(literal_pointer_val(ctx, jl_stackovf_exception)), - ctx.builder.CreateLoad(emit_exc_in_transit(ctx), /*isvolatile*/true)), - resetstkoflw_blk, handlr); - ctx.builder.SetInsertPoint(resetstkoflw_blk); - ctx.builder.CreateCall(prepare_call(resetstkoflw_func), {}); - ctx.builder.CreateBr(handlr); -#else ctx.builder.CreateCondBr(isz, tryblk, handlr); -#endif ctx.builder.SetInsertPoint(tryblk); } else { @@ -6359,6 +6373,7 @@ static std::unique_ptr emit_function( } } assert(found); + (void)found; } else { terminator->removeFromParent(); @@ -7105,10 +7120,13 @@ static void init_julia_llvm_env(Module *m) "jl_enter_handler", m); add_named_global(jlenter_func, &jl_enter_handler); + jl_current_exception_func = + Function::Create(FunctionType::get(T_prjlvalue, false), + Function::ExternalLinkage, + "jl_current_exception", m); + add_named_global(jl_current_exception_func, &jl_current_exception); + #ifdef _OS_WINDOWS_ - resetstkoflw_func = Function::Create(FunctionType::get(T_int32, false), - Function::ExternalLinkage, "_resetstkoflw", m); - add_named_global(resetstkoflw_func, &_resetstkoflw); #if defined(_CPU_X86_64_) juliapersonality_func = Function::Create(FunctionType::get(T_int32, true), Function::ExternalLinkage, "__julia_personality", m); @@ -7147,6 +7165,18 @@ static void init_julia_llvm_env(Module *m) "jl_pop_handler", m); add_named_global(jlleave_func, &jl_pop_handler); + jl_restore_excstack_func = + Function::Create(FunctionType::get(T_void, T_size, false), + Function::ExternalLinkage, + "jl_restore_excstack", m); + add_named_global(jl_restore_excstack_func, &jl_restore_excstack); + + jl_excstack_state_func = + Function::Create(FunctionType::get(T_size, false), + Function::ExternalLinkage, + "jl_excstack_state", m); + add_named_global(jl_excstack_state_func, &jl_excstack_state); + std::vector args_2vals_callee_rooted(0); args_2vals_callee_rooted.push_back(PointerType::get(T_jlvalue, AddressSpace::CalleeRooted)); args_2vals_callee_rooted.push_back(PointerType::get(T_jlvalue, AddressSpace::CalleeRooted)); diff --git a/src/dump.c b/src/dump.c index 773f943b43c70..197748dd05f44 100644 --- a/src/dump.c +++ b/src/dump.c @@ -2337,7 +2337,6 @@ static void jl_finalize_serializer(jl_serializer_state *s) void jl_typemap_rehash(jl_typemap_t *ml, int8_t offs); static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list) { - jl_ptls_t ptls = jl_get_ptls_states(); JL_TRY { switch (how) { case 1: { // rehash IdDict @@ -2390,7 +2389,7 @@ static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list) jl_printf(JL_STDERR, "WARNING: error while reinitializing value "); jl_static_show(JL_STDERR, v); jl_printf(JL_STDERR, ":\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); } } diff --git a/src/gc.c b/src/gc.c index 9c20d8a1e5f3e..e5fb03cf95cc0 100644 --- a/src/gc.c +++ b/src/gc.c @@ -234,7 +234,7 @@ static void run_finalizer(jl_ptls_t ptls, jl_value_t *o, jl_value_t *ff) } JL_CATCH { jl_printf(JL_STDERR, "error in running finalizer: "); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); } } @@ -313,27 +313,11 @@ static void jl_gc_run_finalizers_in_list(jl_ptls_t ptls, arraylist_t *list) jl_value_t **items = (jl_value_t**)list->items; size_t len = list->len; JL_UNLOCK_NOGC(&finalizers_lock); - // from jl_apply_with_saved_exception_state; to hoist state saving out of the loop - jl_value_t *exc = ptls->exception_in_transit; - jl_array_t *bt = NULL; - jl_array_t *bt2 = NULL; - JL_GC_PUSH3(&exc, &bt, &bt2); - if (ptls->bt_size > 0) { - jl_get_backtrace(&bt, &bt2); - ptls->bt_size = 0; - } // run finalizers in reverse order they were added, so lower-level finalizers run last for (size_t i = len-4; i >= 2; i -= 2) run_finalizer(ptls, items[i], items[i + 1]); // first entries were moved last to make room for GC frame metadata run_finalizer(ptls, items[len-2], items[len-1]); - ptls->exception_in_transit = exc; - if (bt != NULL) { - // This is sufficient because bt2 roots the managed values - memcpy(ptls->bt_data, bt->data, jl_array_len(bt) * sizeof(void*)); - ptls->bt_size = jl_array_len(bt); - } - JL_GC_POP(); // matches the jl_gc_push_arraylist above JL_GC_POP(); } @@ -1844,6 +1828,8 @@ STATIC_INLINE int gc_mark_scan_obj32(jl_ptls_t ptls, jl_gc_mark_sp_t *sp, gc_mar goto obj32; \ case GC_MARK_L_stack: \ goto stack; \ + case GC_MARK_L_excstack: \ + goto excstack; \ case GC_MARK_L_module_binding: \ goto module_binding; \ default: \ @@ -1929,6 +1915,7 @@ JL_EXTENSION NOINLINE void gc_mark_loop(jl_ptls_t ptls, jl_gc_mark_sp_t sp) gc_mark_label_addrs[GC_MARK_L_obj16] = gc_mark_laddr(obj16); gc_mark_label_addrs[GC_MARK_L_obj32] = gc_mark_laddr(obj32); gc_mark_label_addrs[GC_MARK_L_stack] = gc_mark_laddr(stack); + gc_mark_label_addrs[GC_MARK_L_excstack] = gc_mark_laddr(excstack); gc_mark_label_addrs[GC_MARK_L_module_binding] = gc_mark_laddr(module_binding); return; } @@ -2081,6 +2068,46 @@ stack: { } } +excstack: { + // Scan an exception stack + gc_mark_excstack_t *stackitr = gc_pop_markdata(&sp, gc_mark_excstack_t); + jl_excstack_t *excstack = stackitr->s; + size_t itr = stackitr->itr; + size_t i = stackitr->i; + while (itr > 0) { + size_t bt_size = jl_excstack_bt_size(excstack, itr); + uintptr_t *bt_data = jl_excstack_bt_data(excstack, itr); + while (i+2 < bt_size) { + if (bt_data[i] != JL_BT_INTERP_FRAME) { + i++; + continue; + } + // found an interpreter frame to mark + new_obj = (jl_value_t*)bt_data[i+1]; + uintptr_t nptr = 0; + i += 3; + if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { + stackitr->i = i; + stackitr->itr = itr; + gc_repush_markdata(&sp, gc_mark_excstack_t); + goto mark; + } + } + // mark the exception + new_obj = jl_excstack_exception(excstack, itr); + itr = jl_excstack_next(excstack, itr); + i = 0; + uintptr_t nptr = 0; + if (gc_try_setmark(new_obj, &nptr, &tag, &bits)) { + stackitr->i = i; + stackitr->itr = itr; + gc_repush_markdata(&sp, gc_mark_excstack_t); + goto mark; + } + } + goto pop; + } + module_binding: { // Scan a module. see `gc_mark_binding_t` // Other fields of the module will be scanned after the bindings are scanned @@ -2340,13 +2367,22 @@ mark: { gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(stack), &stackdata, sizeof(stackdata), 1); } + if (ta->excstack) { + gc_setmark_buf_(ptls, ta->excstack, bits, sizeof(jl_excstack_t) + + sizeof(uintptr_t)*ta->excstack->reserved_size); + gc_mark_excstack_t stackdata = {ta->excstack, ta->excstack->top, 0}; + gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(excstack), + &stackdata, sizeof(stackdata), 1); + } const jl_datatype_layout_t *layout = jl_task_type->layout; assert(layout->fielddesc_type == 0); size_t nfields = layout->nfields; assert(nfields > 0); obj8_begin = (jl_fielddesc8_t*)jl_dt_layout_fields(layout); obj8_end = obj8_begin + nfields; - gc_mark_obj8_t markdata = {new_obj, obj8_begin, obj8_end, (9 << 2) | 1 | bits}; + // assume tasks always reference young objects: set lowest bit + uintptr_t nptr = (9 << 2) | 1 | bits; + gc_mark_obj8_t markdata = {new_obj, obj8_begin, obj8_end, nptr}; gc_mark_stack_push(&ptls->gc_cache, &sp, gc_mark_laddr(obj8), &markdata, sizeof(markdata), 0); obj8 = (gc_mark_obj8_t*)sp.data; @@ -2441,7 +2477,8 @@ static void jl_gc_queue_thread_local(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp { gc_mark_queue_obj(gc_cache, sp, ptls2->current_task); gc_mark_queue_obj(gc_cache, sp, ptls2->root_task); - gc_mark_queue_obj(gc_cache, sp, ptls2->exception_in_transit); + if (ptls2->previous_exception) + gc_mark_queue_obj(gc_cache, sp, ptls2->previous_exception); } // mark the initial root set @@ -2623,7 +2660,7 @@ static void jl_gc_queue_bt_buf(jl_gc_mark_cache_t *gc_cache, jl_gc_mark_sp_t *sp { size_t n = 0; while (n+2 < ptls2->bt_size) { - if (ptls2->bt_data[n] == (uintptr_t)-1) { + if (ptls2->bt_data[n] == JL_BT_INTERP_FRAME) { gc_mark_queue_obj(gc_cache, sp, (jl_value_t*)ptls2->bt_data[n+1]); n += 2; } diff --git a/src/gc.h b/src/gc.h index b3d5af52d581b..e99e6b9b8cfd6 100644 --- a/src/gc.h +++ b/src/gc.h @@ -84,19 +84,21 @@ enum { GC_MARK_L_obj16, GC_MARK_L_obj32, GC_MARK_L_stack, + GC_MARK_L_excstack, GC_MARK_L_module_binding, _GC_MARK_L_MAX }; -/** - * The `nptr` member of marking data records the number of pointers slots referenced by - * an object to be used in the full collection heuristics as well as whether the object - * references young objects. - * `nptr >> 2` is the number of pointers fields referenced by the object. - * The lowest bit of `nptr` is set if the object references young object. - * The 2nd lowest bit of `nptr` is the GC old bits of the object after marking. - * A `0x3` in the low bits means that the object needs to be in the remset. - */ +// The following structs (`gc_mark_*_t`) contain iterator state used for the +// scanning of various object types. +// +// The `nptr` member records the number of pointers slots referenced by +// an object to be used in the full collection heuristics as well as whether the object +// references young objects. +// `nptr >> 2` is the number of pointers fields referenced by the object. +// The lowest bit of `nptr` is set if the object references young object. +// The 2nd lowest bit of `nptr` is the GC old bits of the object after marking. +// A `0x3` in the low bits means that the object needs to be in the remset. // An generic object that's marked and needs to be scanned // The metadata might need update too (depend on the PC) @@ -149,6 +151,13 @@ typedef struct { uintptr_t ub; } gc_mark_stackframe_t; +// Exception stack data +typedef struct { + jl_excstack_t *s; // Stack of exceptions + size_t itr; // Iterator into exception stack + size_t i; // Iterator into backtrace data for exception +} gc_mark_excstack_t; + // Module bindings. This is also the beginning of module scanning. // The loop will start marking other references in a module after the bindings are marked typedef struct { @@ -176,6 +185,7 @@ union _jl_gc_mark_data { gc_mark_obj16_t obj16; gc_mark_obj32_t obj32; gc_mark_stackframe_t stackframe; + gc_mark_excstack_t excstackframe; gc_mark_binding_t binding; gc_mark_finlist_t finlist; }; diff --git a/src/gf.c b/src/gf.c index aa597ecbaa116..ae4a1413b7849 100644 --- a/src/gf.c +++ b/src/gf.c @@ -119,7 +119,7 @@ void jl_call_tracer(tracer_cb callback, jl_value_t *tracee) JL_CATCH { ptls->in_pure_callback = last_in; jl_printf(JL_STDERR, "WARNING: tracer callback function threw an error:\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); jlbacktrace(); } @@ -272,17 +272,28 @@ jl_code_info_t *jl_type_infer(jl_method_instance_t **pli JL_ROOTS_TEMPORARILY, s ptls->world_age = jl_typeinf_world; li->inInference = 1; in_inference++; - jl_svec_t *linfo_src = (jl_svec_t*)jl_apply_with_saved_exception_state(fargs, 3, 0); + jl_svec_t *linfo_src; + JL_TRY { + linfo_src = (jl_svec_t*)jl_apply(fargs, 3); + } + JL_CATCH { + jl_printf(JL_STDERR, "Internal error: encountered unexpected error in runtime:\n"); + jl_static_show(JL_STDERR, jl_current_exception()); + jl_printf(JL_STDERR, "\n"); + jlbacktrace(); // written to STDERR_FILENO + linfo_src = NULL; + } ptls->world_age = last_age; in_inference--; li->inInference = 0; - if (linfo_src && - jl_is_svec(linfo_src) && jl_svec_len(linfo_src) == 2 && - jl_is_method_instance(jl_svecref(linfo_src, 0)) && - jl_is_code_info(jl_svecref(linfo_src, 1))) { - *pli = (jl_method_instance_t*)jl_svecref(linfo_src, 0); - src = (jl_code_info_t*)jl_svecref(linfo_src, 1); + if (linfo_src && jl_is_svec(linfo_src) && jl_svec_len(linfo_src) == 2) { + jl_value_t *mi = jl_svecref(linfo_src, 0); + jl_value_t *ci = jl_svecref(linfo_src, 1); + if (jl_is_method_instance(mi) && jl_is_code_info(ci)) { + *pli = (jl_method_instance_t*)mi; + src = (jl_code_info_t*)ci; + } } JL_GC_POP(); #endif diff --git a/src/init.c b/src/init.c index 18c1feeea6237..cf3a420d76d98 100644 --- a/src/init.c +++ b/src/init.c @@ -236,7 +236,7 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) } JL_CATCH { jl_printf(JL_STDERR, "\natexit hook threw an error: "); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); } } } @@ -270,7 +270,7 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) assert(item); uv_unref(item->h); jl_printf(JL_STDERR, "error during exit cleanup: close: "); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); item = next_shutdown_queue_item(item); } } @@ -286,6 +286,8 @@ JL_DLLEXPORT void jl_atexit_hook(int exitcode) loop->stop_flag = 0; while (uv_run(loop, UV_RUN_DEFAULT)) { } + // TODO: Destroy threads + jl_destroy_timing(); #ifdef ENABLE_TIMINGS jl_print_timings(); @@ -745,7 +747,7 @@ void _julia_init(JL_IMAGE_SEARCH rel) } JL_CATCH { jl_printf(JL_STDERR, "error during init:\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); jl_exit(1); } diff --git a/src/interpreter-stacktrace.c b/src/interpreter-stacktrace.c index c717624ffc1ae..c15fbbc6f4ef9 100644 --- a/src/interpreter-stacktrace.c +++ b/src/interpreter-stacktrace.c @@ -400,7 +400,7 @@ JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintp if (space_remaining <= 1) return 0; // Sentinel value to indicate an interpreter frame - data[0] = (uintptr_t)-1; + data[0] = JL_BT_INTERP_FRAME; data[1] = s->mi ? (uintptr_t)s->mi : s->src ? (uintptr_t)s->src : (uintptr_t)jl_nothing; data[2] = (uintptr_t)s->ip; return 2; diff --git a/src/interpreter.c b/src/interpreter.c index bdb2a396d46a5..e9f1bb3acd67c 100644 --- a/src/interpreter.c +++ b/src/interpreter.c @@ -483,7 +483,7 @@ SECT_INTERP static jl_value_t *eval_value(jl_value_t *e, interpreter_state *s) return jl_copy_ast(eval_value(args[0], s)); } else if (head == exc_sym) { - return jl_get_ptls_states()->exception_in_transit; + return jl_current_exception(); } else if (head == boundscheck_sym) { return jl_true; @@ -681,6 +681,8 @@ SECT_INTERP static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s s->locals[jl_source_nslots(s->src) + catch_ip] = NULL; catch_ip += 1; } + // store current top of exception stack for restore in pop_exception. + s->locals[jl_source_nslots(s->src) + ip] = jl_box_ulong(jl_excstack_state()); if (!jl_setjmp(__eh.eh_ctx, 1)) { return eval_body(stmts, s, next_ip, toplevel); } @@ -690,10 +692,6 @@ SECT_INTERP static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s continue; } else { // a real exeception -#ifdef _OS_WINDOWS_ - if (jl_get_ptls_states()->exception_in_transit == jl_stackovf_exception) - _resetstkoflw(); -#endif ip = catch_ip; continue; } @@ -701,16 +699,21 @@ SECT_INTERP static jl_value_t *eval_body(jl_array_t *stmts, interpreter_state *s else if (head == leave_sym) { int hand_n_leave = jl_unbox_long(jl_exprarg(stmt, 0)); assert(hand_n_leave > 0); - // equivalent to jl_pop_handler(hand_n_leave) : + // equivalent to jl_pop_handler(hand_n_leave), but retaining eh for longjmp: jl_ptls_t ptls = jl_get_ptls_states(); jl_handler_t *eh = ptls->current_task->eh; while (--hand_n_leave > 0) eh = eh->prev; jl_eh_restore_state(eh); - // pop jmp_bufs from stack + // leave happens during normal control flow, but we must + // longjmp to pop the eval_body call for each enter. s->continue_at = next_ip; jl_longjmp(eh->eh_ctx, 1); } + else if (head == pop_exception_sym) { + size_t prev_state = jl_unbox_ulong(eval_value(jl_exprarg(stmt, 0), s)); + jl_restore_excstack(prev_state); + } else if (head == const_sym) { jl_sym_t *sym = (jl_sym_t*)jl_exprarg(stmt, 0); jl_module_t *modu = s->module; diff --git a/src/jlapi.c b/src/jlapi.c index e20e0e3ae45ba..478c8884b334d 100644 --- a/src/jlapi.c +++ b/src/jlapi.c @@ -13,6 +13,7 @@ #include "julia.h" #include "options.h" #include "julia_assert.h" +#include "julia_internal.h" #ifdef __cplusplus #include @@ -95,22 +96,26 @@ JL_DLLEXPORT jl_value_t *jl_eval_string(const char *str) jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); r = NULL; } return r; } +JL_DLLEXPORT jl_value_t *jl_current_exception(void) JL_GLOBALLY_ROOTED +{ + jl_excstack_t *s = jl_get_ptls_states()->current_task->excstack; + return s && s->top != 0 ? jl_excstack_exception(s, s->top) : jl_nothing; +} + JL_DLLEXPORT jl_value_t *jl_exception_occurred(void) { - jl_ptls_t ptls = jl_get_ptls_states(); - return ptls->exception_in_transit == jl_nothing ? NULL : - ptls->exception_in_transit; + return jl_get_ptls_states()->previous_exception; } JL_DLLEXPORT void jl_exception_clear(void) { - jl_ptls_t ptls = jl_get_ptls_states(); - ptls->exception_in_transit = jl_nothing; + jl_get_ptls_states()->previous_exception = NULL; } // get the name of a type as a string @@ -164,6 +169,7 @@ JL_DLLEXPORT jl_value_t *jl_call(jl_function_t *f, jl_value_t **args, int32_t na jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -182,6 +188,7 @@ JL_DLLEXPORT jl_value_t *jl_call0(jl_function_t *f) jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -202,6 +209,7 @@ JL_DLLEXPORT jl_value_t *jl_call1(jl_function_t *f, jl_value_t *a) jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -222,6 +230,7 @@ JL_DLLEXPORT jl_value_t *jl_call2(jl_function_t *f, jl_value_t *a, jl_value_t *b jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -243,6 +252,7 @@ JL_DLLEXPORT jl_value_t *jl_call3(jl_function_t *f, jl_value_t *a, jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; @@ -267,6 +277,7 @@ JL_DLLEXPORT jl_value_t *jl_get_field(jl_value_t *o, const char *fld) jl_exception_clear(); } JL_CATCH { + jl_get_ptls_states()->previous_exception = jl_current_exception(); v = NULL; } return v; diff --git a/src/julia-syntax.scm b/src/julia-syntax.scm index 03abf6012896e..080b75f8973a0 100644 --- a/src/julia-syntax.scm +++ b/src/julia-syntax.scm @@ -3397,13 +3397,14 @@ f(x) = yt(x) (arg-map #f) ;; map arguments to new names if they are assigned (label-counter 0) ;; counter for generating label addresses (label-map (table)) ;; maps label names to generated addresses - (label-level (table)) ;; exception handler level of each label + (label-nesting (table)) ;; exception handler and catch block nesting of each label (finally-handler #f) ;; `(var label map level)` where `map` is a list of `(tag . action)`. ;; to exit the current finally block, set `var` to integer `tag`, ;; jump to `label`, and put `(tag . action)` in the map, where `action` ;; is `(return x)`, `(break x)`, or a call to rethrow. (handler-goto-fixups '()) ;; `goto`s that might need `leave` exprs added - (handler-level 0)) ;; exception handler nesting depth + (handler-level 0) ;; exception handler nesting depth + (catch-token-stack '())) ;; tokens identifying handler enter for current catch blocks (define (emit c) (set! code (cons c code))) (define (make-label) @@ -3426,6 +3427,16 @@ f(x) = yt(x) (begin (emit `(leave ,(+ 1 (- handler-level (cadddr finally-handler))))) (emit `(goto ,(cadr finally-handler))))) tag)) + (define (pop-exc-expr src-tokens dest-tokens) + (if (eq? src-tokens dest-tokens) + #f + (let ((restore-token (let loop ((s src-tokens)) + (if (not (pair? s)) + (error "Attempt to jump into catch block")) + (if (eq? (cdr s) dest-tokens) + (car s) + (loop (cdr s)))))) + `(pop_exception ,restore-token)))) (define (emit-return x) (define (actually-return x) (let* ((x (if rett @@ -3433,6 +3444,8 @@ f(x) = yt(x) x)) (tmp (if (valid-ir-return? x) #f (make-ssavalue)))) (if tmp (emit `(= ,tmp ,x))) + (let ((pexc (pop-exc-expr catch-token-stack '()))) + (if pexc (emit pexc))) (emit `(return ,(or tmp x))))) (if x (if (> handler-level 0) @@ -3447,10 +3460,13 @@ f(x) = yt(x) (or tmp x)) (actually-return x)))) (define (emit-break labl) - (let ((lvl (caddr labl))) + (let ((lvl (caddr labl)) + (dest-tokens (cadddr labl))) (if (and finally-handler (> (cadddr finally-handler) lvl)) (leave-finally-block `(break ,labl)) (begin + (let ((pexc (pop-exc-expr catch-token-stack dest-tokens))) + (if pexc (emit pexc))) (if (> handler-level lvl) (emit `(leave ,(- handler-level lvl)))) (emit `(goto ,(cadr labl))))))) @@ -3685,7 +3701,7 @@ f(x) = yt(x) ((break-block) (let ((endl (make-label))) (compile (caddr e) - (cons (list (cadr e) endl handler-level) + (cons (list (cadr e) endl handler-level catch-token-stack) break-labels) #f #f) (mark-label endl)) @@ -3697,9 +3713,9 @@ f(x) = yt(x) (emit-break labl)))) ((label symboliclabel) (if (eq? (car e) 'symboliclabel) - (if (has? label-level (cadr e)) + (if (has? label-nesting (cadr e)) (error (string "label \"" (cadr e) "\" defined multiple times")) - (put! label-level (cadr e) handler-level))) + (put! label-nesting (cadr e) (list handler-level catch-token-stack)))) (let ((m (get label-map (cadr e) #f))) (if m (emit `(label ,m)) @@ -3715,27 +3731,31 @@ f(x) = yt(x) (emit `(null)) ;; save space for `leave` that might be needed (emit `(goto ,m)) (set! handler-goto-fixups - (cons (list code handler-level (cadr e)) handler-goto-fixups)) + (cons (list code handler-level catch-token-stack (cadr e)) handler-goto-fixups)) #f)) ;; exception handlers are lowered using - ;; (enter L) - push handler with catch block at label L + ;; (= tok (enter L)) - push handler with catch block at label L, yielding token ;; (leave n) - pop N exception handlers + ;; (pop_exception tok) - pop exception stack back to state of associated enter ((trycatch tryfinally) - (let ((catch (make-label)) + (let ((handler-token (make-ssavalue)) + (catch (make-label)) (endl (make-label)) (last-finally-handler finally-handler) (finally (if (eq? (car e) 'tryfinally) (new-mutable-var) #f)) (finally-exception (if (eq? (car e) 'tryfinally) (new-mutable-var) #f)) (my-finally-handler #f)) - (emit `(enter ,catch)) + ;; handler block entry + (emit `(= ,handler-token (enter ,catch))) (set! handler-level (+ handler-level 1)) (if finally (begin (set! my-finally-handler (list finally endl '() handler-level)) (set! finally-handler my-finally-handler) (emit `(= ,finally -1)))) - (let* ((v1 (compile (cadr e) break-labels value #f)) + (let* ((v1 (compile (cadr e) break-labels value #f)) ;; emit try block code (val (if (and value (not tail)) (new-mutable-var) #f))) + ;; handler block postfix (if (and val v1) (emit-assignment val v1)) (if tail (begin (if v1 (emit-return v1)) @@ -3743,19 +3763,15 @@ f(x) = yt(x) (begin (emit '(leave 1)) (emit `(goto ,endl)))) (set! handler-level (- handler-level 1)) + ;; emit either catch or finally block (mark-label catch) (emit `(leave 1)) (if finally - (begin (emit `(= ,finally-exception (the_exception))) - (leave-finally-block `(foreigncall 'jl_rethrow_other (top Bottom) (call (core svec) Any) - 'ccall 1 ,finally-exception) - #f)) - (let ((v2 (compile (caddr e) break-labels value tail))) - (if val (emit-assignment val v2)))) - (if endl (mark-label endl)) - (if finally - (begin (set! finally-handler last-finally-handler) + (begin (leave-finally-block '(call rethrow) #f) + (if endl (mark-label endl)) + (set! finally-handler last-finally-handler) (compile (caddr e) break-labels #f #f) + ;; emit actions to be taken at exit of finally block (let loop ((actions (caddr my-finally-handler))) (if (pair? actions) (let ((skip (if (and tail (null? (cdr actions)) @@ -3772,7 +3788,14 @@ f(x) = yt(x) (else ;; assumed to be a rethrow (emit ac)))) (if skip (mark-label skip)) - (loop (cdr actions))))))) + (loop (cdr actions)))))) + (begin (set! catch-token-stack (cons handler-token catch-token-stack)) + (let ((v2 (compile (caddr e) break-labels value tail))) + (if val (emit-assignment val v2)) + (if (not tail) (emit `(pop_exception ,handler-token))) + ;; else done in emit-return from compile + (if endl (mark-label endl))) + (set! catch-token-stack (cdr catch-token-stack)))) val))) ((newvar) @@ -3910,16 +3933,20 @@ f(x) = yt(x) (for-each (lambda (x) (let ((point (car x)) (hl (cadr x)) - (lab (caddr x))) - (let ((target-level (get label-level lab #f))) - (cond ((not target-level) - (error (string "label \"" lab "\" referenced but not defined"))) - ((> target-level hl) - (error (string "cannot goto label \"" lab "\" inside try/catch block"))) - ((= target-level hl) - (set-cdr! point (cddr point))) ;; remove empty slot - (else - (set-car! (cdr point) `(leave ,(- hl target-level)))))))) + (src-tokens (caddr x)) + (lab (cadddr x))) + (let ((target-nesting (get label-nesting lab #f))) + (if (not target-nesting) + (error (string "label \"" lab "\" referenced but not defined"))) + (let ((target-level (car target-nesting))) + (cond ((> target-level hl) + (error (string "cannot goto label \"" lab "\" inside try/catch block"))) + ((= target-level hl) + (set-cdr! point (cddr point))) ;; remove empty slot + (else + (set-car! (cdr point) `(leave ,(- hl target-level)))))) + (let ((pexc (pop-exc-expr src-tokens (cadr target-nesting)))) + (if pexc (set-cdr! point (cons pexc (cdr point)))))))) handler-goto-fixups) (if global-const-error (error (string "`global const` delcaration not allowed inside function" (format-loc global-const-error)))) @@ -3967,12 +3994,14 @@ f(x) = yt(x) (let ((vinf (var-info-for (cadr e) vi))) (if (and vinf (not (vinfo:capt vinf))) (put! vars (cadr e) #t)))) + ((and (pair? e) (or (memq (car e) '(goto gotoifnot)) + (and (eq? (car e) '=) (pair? (caddr e)) + (eq? (car (caddr e)) 'enter)))) + (set! vars (table))) ((and (pair? e) (eq? (car e) '=)) (if (has? vars (cadr e)) (begin (del! vars (cadr e)) - (put! di (cadr e) #t)))) - ((and (pair? e) (memq (car e) '(goto gotoifnot enter))) - (set! vars (table))))) + (put! di (cadr e) #t)))))) (loop (cdr stmts))))))) ;; pass 6: renumber slots and labels diff --git a/src/julia.h b/src/julia.h index 04f00c363ce62..fbc1eb2461b66 100644 --- a/src/julia.h +++ b/src/julia.h @@ -31,7 +31,6 @@ #else # include "win32_ucontext.h" # define jl_jmp_buf jmp_buf -# include //for _resetstkoflw # define MAX_ALIGN 8 #endif @@ -448,6 +447,7 @@ typedef struct _jl_module_t { JL_DATA_TYPE jl_sym_t *name; struct _jl_module_t *parent; + // hidden fields: htable_t bindings; arraylist_t usings; // modules with all bindings potentially imported uint64_t build_id; @@ -1206,6 +1206,7 @@ JL_DLLEXPORT int jl_get_size(jl_value_t *val, size_t *pnt); #define jl_box_long(x) jl_box_int64(x) #define jl_box_ulong(x) jl_box_uint64(x) #define jl_unbox_long(x) jl_unbox_int64(x) +#define jl_unbox_ulong(x) jl_unbox_uint64(x) #define jl_is_long(x) jl_is_int64(x) #define jl_long_type jl_int64_type #define jl_ulong_type jl_uint64_type @@ -1213,6 +1214,7 @@ JL_DLLEXPORT int jl_get_size(jl_value_t *val, size_t *pnt); #define jl_box_long(x) jl_box_int32(x) #define jl_box_ulong(x) jl_box_uint32(x) #define jl_unbox_long(x) jl_unbox_int32(x) +#define jl_unbox_ulong(x) jl_unbox_uint32(x) #define jl_is_long(x) jl_is_int32(x) #define jl_long_type jl_int32_type #define jl_ulong_type jl_uint32_type @@ -1423,6 +1425,15 @@ JL_DLLEXPORT void JL_NORETURN jl_bounds_error_tuple_int(jl_value_t **v, JL_DLLEXPORT void JL_NORETURN jl_bounds_error_unboxed_int(void *v, jl_value_t *vt, size_t i); JL_DLLEXPORT void JL_NORETURN jl_bounds_error_ints(jl_value_t *v, size_t *idxs, size_t nidxs); JL_DLLEXPORT void JL_NORETURN jl_eof_error(void); + +// Return the exception currently being handled, or `jl_nothing`. +// +// The catch scope is determined dynamically so this works in functions called +// from a catch block. The returned value is gc rooted until we exit the +// enclosing JL_CATCH. +// FIXME: Teach the static analyzer about this rather than using +// JL_GLOBALLY_ROOTED which is far too optimistic. +JL_DLLEXPORT jl_value_t *jl_current_exception(void) JL_GLOBALLY_ROOTED; JL_DLLEXPORT jl_value_t *jl_exception_occurred(void); JL_DLLEXPORT void jl_exception_clear(void) JL_NOTSAFEPOINT; @@ -1617,6 +1628,8 @@ typedef struct _jl_task_t { jl_handler_t *eh; // saved gc stack top for context switches jl_gcframe_t *gcstack; + // saved exception stack + jl_excstack_t *excstack; // current world age size_t world_age; @@ -1634,44 +1647,17 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, size_t ssize); JL_DLLEXPORT void jl_switchto(jl_task_t **pt); JL_DLLEXPORT void JL_NORETURN jl_throw(jl_value_t *e JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_rethrow(void); +JL_DLLEXPORT void JL_NORETURN jl_sig_throw(void); JL_DLLEXPORT void JL_NORETURN jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED); JL_DLLEXPORT void JL_NORETURN jl_no_exc_handler(jl_value_t *e); #include "locks.h" // requires jl_task_t definition -STATIC_INLINE void jl_eh_restore_state(jl_handler_t *eh) -{ - jl_ptls_t ptls = jl_get_ptls_states(); - jl_task_t *current_task = ptls->current_task; - // `eh` may not be `ptls->current_task->eh`. See `jl_pop_handler` - // This function should **NOT** have any safepoint before the ones at the - // end. - sig_atomic_t old_defer_signal = ptls->defer_signal; - int8_t old_gc_state = ptls->gc_state; - current_task->eh = eh->prev; - ptls->pgcstack = eh->gcstack; -#ifdef JULIA_ENABLE_THREADING - arraylist_t *locks = ¤t_task->locks; - if (locks->len > eh->locks_len) { - for (size_t i = locks->len;i > eh->locks_len;i--) - jl_mutex_unlock_nogc((jl_mutex_t*)locks->items[i - 1]); - locks->len = eh->locks_len; - } -#endif - ptls->world_age = eh->world_age; - ptls->defer_signal = eh->defer_signal; - ptls->gc_state = eh->gc_state; - ptls->finalizers_inhibited = eh->finalizers_inhibited; - if (old_gc_state && !eh->gc_state) { - jl_gc_safepoint_(ptls); - } - if (old_defer_signal && !eh->defer_signal) { - jl_sigint_safepoint(ptls); - } -} - JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh); +JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh); JL_DLLEXPORT void jl_pop_handler(int n); +JL_DLLEXPORT size_t jl_excstack_state(void); +JL_DLLEXPORT void jl_restore_excstack(size_t state); #if defined(_OS_WINDOWS_) #if defined(_COMPILER_MINGW_) @@ -1711,22 +1697,14 @@ extern int had_exception; #define JL_TRY \ int i__tr, i__ca; jl_handler_t __eh; \ + size_t __excstack_state = jl_excstack_state(); \ jl_enter_handler(&__eh); \ if (!jl_setjmp(__eh.eh_ctx,0)) \ for (i__tr=1; i__tr; i__tr=0, jl_eh_restore_state(&__eh)) -#define JL_EH_POP() jl_eh_restore_state(&__eh) - -#ifdef _OS_WINDOWS_ #define JL_CATCH \ else \ - for (i__ca=1, jl_eh_restore_state(&__eh); i__ca; i__ca=0) \ - if (((jl_get_ptls_states()->exception_in_transit==jl_stackovf_exception) && _resetstkoflw()) || 1) -#else -#define JL_CATCH \ - else \ - for (i__ca=1, jl_eh_restore_state(&__eh); i__ca; i__ca=0) -#endif + for (i__ca=1, jl_eh_restore_state(&__eh); i__ca; i__ca=0, jl_restore_excstack(__excstack_state)) #endif @@ -1929,8 +1907,6 @@ typedef struct { #define jl_current_task (jl_get_ptls_states()->current_task) #define jl_root_task (jl_get_ptls_states()->root_task) -#define jl_exception_in_transit (jl_get_ptls_states()->exception_in_transit) - // codegen interface ---------------------------------------------------------- diff --git a/src/julia_internal.h b/src/julia_internal.h index f39727895a679..dba4610fbb54e 100644 --- a/src/julia_internal.h +++ b/src/julia_internal.h @@ -630,13 +630,14 @@ typedef unw_cursor_t bt_cursor_t; typedef int bt_context_t; typedef int bt_cursor_t; #endif +// Special marker in backtrace data for encoding interpreter frames +#define JL_BT_INTERP_FRAME (((uintptr_t)0)-1) size_t rec_backtrace(uintptr_t *data, size_t maxsize) JL_NOTSAFEPOINT; size_t rec_backtrace_ctx(uintptr_t *data, size_t maxsize, bt_context_t *ctx) JL_NOTSAFEPOINT; #ifdef LIBOSXUNWIND size_t rec_backtrace_ctx_dwarf(uintptr_t *data, size_t maxsize, bt_context_t *ctx); #endif JL_DLLEXPORT void jl_get_backtrace(jl_array_t **bt, jl_array_t **bt2); -JL_DLLEXPORT jl_value_t *jl_apply_with_saved_exception_state(jl_value_t **args, uint32_t nargs, int drop_exceptions); void jl_critical_error(int sig, bt_context_t *context, uintptr_t *bt_data, size_t *bt_size); JL_DLLEXPORT void jl_raise_debugger(void); int jl_getFunctionInfo(jl_frame_t **frames, uintptr_t pointer, int skipC, int noInline); @@ -658,6 +659,49 @@ JL_DLLEXPORT int jl_is_interpreter_frame(uintptr_t ip); JL_DLLEXPORT int jl_is_enter_interpreter_frame(uintptr_t ip); JL_DLLEXPORT size_t jl_capture_interp_frame(uintptr_t *data, uintptr_t sp, uintptr_t fp, size_t space_remaining); +// Exception stack: a stack of pairs of (exception,raw_backtrace). +// The stack may be traversed and accessed with the functions below. +typedef struct _jl_excstack_t { + size_t top; + size_t reserved_size; + // Pack all stack entries into a growable buffer to amortize allocation + // across repeated exception handling. + // Layout: [bt_data1... bt_size1 exc1 bt_data2... bt_size2 exc2 ..] + // uintptr_t data[]; // Access with jl_excstack_raw +} jl_excstack_t; + +STATIC_INLINE uintptr_t *jl_excstack_raw(jl_excstack_t* stack) JL_NOTSAFEPOINT +{ + return (uintptr_t*)(stack + 1); +} + +// Exception stack access +STATIC_INLINE jl_value_t *jl_excstack_exception(jl_excstack_t *stack JL_PROPAGATES_ROOT, + size_t itr) JL_NOTSAFEPOINT +{ + return (jl_value_t*)(jl_excstack_raw(stack)[itr-1]); +} +STATIC_INLINE size_t jl_excstack_bt_size(jl_excstack_t *stack, size_t itr) JL_NOTSAFEPOINT +{ + return jl_excstack_raw(stack)[itr-2]; +} +STATIC_INLINE uintptr_t *jl_excstack_bt_data(jl_excstack_t *stack, size_t itr) JL_NOTSAFEPOINT +{ + return jl_excstack_raw(stack) + itr-2 - jl_excstack_bt_size(stack, itr); +} +// Exception stack iteration (start at itr=stack->top, stop at itr=0) +STATIC_INLINE size_t jl_excstack_next(jl_excstack_t *stack, size_t itr) JL_NOTSAFEPOINT +{ + return itr-2 - jl_excstack_bt_size(stack, itr); +} +// Exception stack manipulation +void jl_reserve_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT, + size_t reserved_size); +void jl_push_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, + jl_value_t *exception JL_ROOTED_ARGUMENT, + uintptr_t *bt_data, size_t bt_size); +void jl_copy_excstack(jl_excstack_t *dest, jl_excstack_t *src) JL_NOTSAFEPOINT; + // timers // Returns time in nanosec JL_DLLEXPORT uint64_t jl_hrtime(void); @@ -947,6 +991,7 @@ extern jl_sym_t *method_sym; extern jl_sym_t *core_sym; extern jl_sym_t *enter_sym; extern jl_sym_t *leave_sym; extern jl_sym_t *exc_sym; extern jl_sym_t *error_sym; extern jl_sym_t *new_sym; extern jl_sym_t *using_sym; +extern jl_sym_t *pop_exception_sym; extern jl_sym_t *const_sym; extern jl_sym_t *thunk_sym; extern jl_sym_t *abstracttype_sym; extern jl_sym_t *primtype_sym; extern jl_sym_t *structtype_sym; extern jl_sym_t *foreigncall_sym; diff --git a/src/julia_threads.h b/src/julia_threads.h index 58bf2e629da3a..eedab742ef6b1 100644 --- a/src/julia_threads.h +++ b/src/julia_threads.h @@ -132,12 +132,13 @@ typedef struct { jl_gc_mark_data_t *data_stack; } jl_gc_mark_cache_t; +typedef struct _jl_excstack_t jl_excstack_t; // This includes all the thread local states we care about for a thread. +// Changes to TLS field types must be reflected in codegen. #define JL_MAX_BT_SIZE 80000 struct _jl_tls_states_t { struct _jl_gcframe_t *pgcstack; size_t world_age; - struct _jl_value_t *exception_in_transit; volatile size_t *safepoint; // Whether it is safe to execute GC at the same time. #define JL_GC_STATE_WAITING 1 @@ -157,9 +158,11 @@ struct _jl_tls_states_t { jl_ucontext_t base_ctx; // base context of stack jl_jmp_buf *safe_restore; int16_t tid; - size_t bt_size; - // JL_MAX_BT_SIZE + 1 elements long - uintptr_t *bt_data; + // Temp storage for exception thrown in signal handler. Not rooted. + struct _jl_value_t *sig_exception; + // Temporary backtrace buffer. Scanned for gc roots when bt_size > 0. + uintptr_t *bt_data; // JL_MAX_BT_SIZE + 1 elements long + size_t bt_size; // Size for backtrace in transit in bt_data // Atomically set by the sender, reset by the handler. volatile sig_atomic_t signal_request; // Allow the sigint to be raised asynchronously @@ -171,6 +174,9 @@ struct _jl_tls_states_t { // These are only used on unix now pthread_t system_id; void *signal_stack; +#endif +#ifdef _OS_WINDOWS_ + int needs_resetstkoflw; #endif // execution of certain certain impure // statements is prohibited from certain @@ -183,6 +189,9 @@ struct _jl_tls_states_t { jl_gc_mark_cache_t gc_cache; arraylist_t sweep_objs; jl_gc_mark_sp_t gc_mark_sp; + // Saved exception for previous external API call or NULL if cleared. + // Access via jl_exception_occurred(). + struct _jl_value_t *previous_exception; }; // Update codegen version in `ccall.cpp` after changing either `pause` or `wake` diff --git a/src/method.c b/src/method.c index 175f6a24ebfa1..83cc4fabd1783 100644 --- a/src/method.c +++ b/src/method.c @@ -60,7 +60,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve rt = jl_interpret_toplevel_expr_in(module, rt, NULL, sparam_vals); } JL_CATCH { - if (jl_typeis(jl_exception_in_transit, jl_errorexception_type)) + if (jl_typeis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate cfunction return type (it might depend on a local variable)"); else jl_rethrow(); @@ -72,7 +72,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve at = jl_interpret_toplevel_expr_in(module, at, NULL, sparam_vals); } JL_CATCH { - if (jl_typeis(jl_exception_in_transit, jl_errorexception_type)) + if (jl_typeis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate cfunction argument type (it might depend on a local variable)"); else jl_rethrow(); @@ -96,7 +96,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve rt = jl_interpret_toplevel_expr_in(module, rt, NULL, sparam_vals); } JL_CATCH { - if (jl_typeis(jl_exception_in_transit, jl_errorexception_type)) + if (jl_typeis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate ccall return type (it might depend on a local variable)"); else jl_rethrow(); @@ -108,7 +108,7 @@ static jl_value_t *resolve_globals(jl_value_t *expr, jl_module_t *module, jl_sve at = jl_interpret_toplevel_expr_in(module, at, NULL, sparam_vals); } JL_CATCH { - if (jl_typeis(jl_exception_in_transit, jl_errorexception_type)) + if (jl_typeis(jl_current_exception(), jl_errorexception_type)) jl_error("could not evaluate ccall argument type (it might depend on a local variable)"); else jl_rethrow(); diff --git a/src/rtutils.c b/src/rtutils.c index 21e11e59facd0..b57785ec31fc1 100644 --- a/src/rtutils.c +++ b/src/rtutils.c @@ -205,6 +205,8 @@ JL_DLLEXPORT void jl_typeassert(jl_value_t *x, jl_value_t *t) jl_type_error("typeassert", t, x); } +// exceptions ----------------------------------------------------------------- + JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh) { jl_ptls_t ptls = jl_get_ptls_states(); @@ -225,6 +227,48 @@ JL_DLLEXPORT void jl_enter_handler(jl_handler_t *eh) #endif } +// Restore thread local state to saved state in error handler `eh`. +// This is executed in two circumstances: +// * We leave a try block through normal control flow +// * An exception causes a nonlocal jump to the catch block. In this case +// there's additional cleanup required, eg pushing the exception stack. +JL_DLLEXPORT void jl_eh_restore_state(jl_handler_t *eh) +{ + jl_ptls_t ptls = jl_get_ptls_states(); +#ifdef _OS_WINDOWS_ + if (ptls->needs_resetstkoflw) { + _resetstkoflw(); + ptls->needs_resetstkoflw = 0; + } +#endif + jl_task_t *current_task = ptls->current_task; + // `eh` may be not equal to `ptls->current_task->eh`. See `jl_pop_handler` + // This function should **NOT** have any safepoint before the ones at the + // end. + sig_atomic_t old_defer_signal = ptls->defer_signal; + int8_t old_gc_state = ptls->gc_state; + current_task->eh = eh->prev; + ptls->pgcstack = eh->gcstack; +#ifdef JULIA_ENABLE_THREADING + arraylist_t *locks = ¤t_task->locks; + if (locks->len > eh->locks_len) { + for (size_t i = locks->len;i > eh->locks_len;i--) + jl_mutex_unlock_nogc((jl_mutex_t*)locks->items[i - 1]); + locks->len = eh->locks_len; + } +#endif + ptls->world_age = eh->world_age; + ptls->defer_signal = eh->defer_signal; + ptls->gc_state = eh->gc_state; + ptls->finalizers_inhibited = eh->finalizers_inhibited; + if (old_gc_state && !eh->gc_state) { + jl_gc_safepoint_(ptls); + } + if (old_defer_signal && !eh->defer_signal) { + jl_sigint_safepoint(ptls); + } +} + JL_DLLEXPORT void jl_pop_handler(int n) { jl_ptls_t ptls = jl_get_ptls_states(); @@ -236,38 +280,55 @@ JL_DLLEXPORT void jl_pop_handler(int n) jl_eh_restore_state(eh); } -JL_DLLEXPORT jl_value_t *jl_apply_with_saved_exception_state(jl_value_t **args, uint32_t nargs, int drop_exceptions) +JL_DLLEXPORT size_t jl_excstack_state(void) { jl_ptls_t ptls = jl_get_ptls_states(); - jl_value_t *exc = ptls->exception_in_transit; - jl_array_t *bt = NULL; - jl_array_t *bt2 = NULL; - JL_GC_PUSH3(&exc, &bt, &bt2); - if (ptls->bt_size > 0) { - jl_get_backtrace(&bt, &bt2); - ptls->bt_size = 0; - } - jl_value_t *v; - JL_TRY { - v = jl_apply(args, nargs); - } - JL_CATCH { - if (!drop_exceptions) { - jl_printf(JL_STDERR, "Internal error: encountered unexpected error in runtime:\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); - jl_printf(JL_STDERR, "\n"); - jlbacktrace(); // written to STDERR_FILENO - } - v = NULL; - } - ptls->exception_in_transit = exc; - if (bt != NULL) { - // This is sufficient because bt2 roots the gc-managed values - memcpy(ptls->bt_data, bt->data, jl_array_len(bt) * sizeof(void*)); - ptls->bt_size = jl_array_len(bt); + jl_excstack_t *s = ptls->current_task->excstack; + return s ? s->top : 0; +} + +JL_DLLEXPORT void jl_restore_excstack(size_t state) +{ + jl_ptls_t ptls = jl_get_ptls_states(); + jl_excstack_t *s = ptls->current_task->excstack; + if (s) { + assert(s->top >= state); + s->top = state; } - JL_GC_POP(); - return v; +} + +void jl_copy_excstack(jl_excstack_t *dest, jl_excstack_t *src) JL_NOTSAFEPOINT +{ + assert(dest->reserved_size >= src->top); + memcpy(jl_excstack_raw(dest), jl_excstack_raw(src), sizeof(uintptr_t)*src->top); + dest->top = src->top; +} + +void jl_reserve_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT, + size_t reserved_size) +{ + jl_excstack_t *s = *stack; + if (s && s->reserved_size >= reserved_size) + return; + size_t bufsz = sizeof(jl_excstack_t) + sizeof(uintptr_t)*reserved_size; + jl_excstack_t *new_s = (jl_excstack_t*)jl_gc_alloc_buf(jl_get_ptls_states(), bufsz); + new_s->top = 0; + new_s->reserved_size = reserved_size; + if (s) + jl_copy_excstack(new_s, s); + *stack = new_s; +} + +void jl_push_excstack(jl_excstack_t **stack JL_REQUIRE_ROOTED_SLOT JL_ROOTING_ARGUMENT, + jl_value_t *exception JL_ROOTED_ARGUMENT, + uintptr_t *bt_data, size_t bt_size) +{ + jl_reserve_excstack(stack, (*stack ? (*stack)->top : 0) + bt_size + 2); + jl_excstack_t *s = *stack; + memcpy(jl_excstack_raw(s) + s->top, bt_data, sizeof(uintptr_t)*bt_size); + s->top += bt_size + 2; + jl_excstack_raw(s)[s->top-2] = bt_size; + jl_excstack_raw(s)[s->top-1] = (uintptr_t)exception; } // misc ----------------------------------------------------------------------- diff --git a/src/signals-mach.c b/src/signals-mach.c index ef38b25b01ff3..c1add709a3e6b 100644 --- a/src/signals-mach.c +++ b/src/signals-mach.c @@ -147,9 +147,9 @@ static void jl_throw_in_thread(int tid, mach_port_t thread, jl_value_t *exceptio assert(exception); ptls2->bt_size = rec_backtrace_ctx(ptls2->bt_data, JL_MAX_BT_SIZE, (bt_context_t*)&state); - ptls2->exception_in_transit = exception; + ptls2->sig_exception = exception; } - jl_call_in_state(ptls2, &state, &jl_rethrow); + jl_call_in_state(ptls2, &state, &jl_sig_throw); ret = thread_set_state(thread, x86_THREAD_STATE64, (thread_state_t)&state, count); HANDLE_MACH_ERROR("thread_set_state",ret); diff --git a/src/signals-unix.c b/src/signals-unix.c index 85f947edfb2a0..274277d32c845 100644 --- a/src/signals-unix.c +++ b/src/signals-unix.c @@ -80,6 +80,8 @@ static inline __attribute__((unused)) uintptr_t jl_get_rsp_from_ctx(const void * #endif } +// Modify signal context `_ctx` so that `fptr` will execute when the signal +// returns. `fptr` will execute on the signal stack, and must not return. static void jl_call_in_ctx(jl_ptls_t ptls, void (*fptr)(void), int sig, void *_ctx) { // Modifying the ucontext should work but there is concern that @@ -174,8 +176,8 @@ static void jl_throw_in_ctx(jl_ptls_t ptls, jl_value_t *e, int sig, void *sigctx if (!ptls->safe_restore) ptls->bt_size = rec_backtrace_ctx(ptls->bt_data, JL_MAX_BT_SIZE, jl_to_bt_context(sigctx)); - ptls->exception_in_transit = e; - jl_call_in_ctx(ptls, &jl_rethrow, sig, sigctx); + ptls->sig_exception = e; + jl_call_in_ctx(ptls, &jl_sig_throw, sig, sigctx); } static pthread_t signals_thread; diff --git a/src/signals-win.c b/src/signals-win.c index 2af450725c951..25d4a54e9ede3 100644 --- a/src/signals-win.c +++ b/src/signals-win.c @@ -136,16 +136,16 @@ void jl_throw_in_ctx(jl_value_t *excpt, PCONTEXT ctxThread) error_ctx = ctxThread; jl_swapcontext(&error_return_fiber, &collect_backtrace_fiber); } - jl_exception_in_transit = excpt; + ptls->sig_exception = excpt; } #if defined(_CPU_X86_64_) *(DWORD64*)Rsp = 0; ctxThread->Rsp = Rsp; - ctxThread->Rip = (DWORD64)&jl_rethrow; + ctxThread->Rip = (DWORD64)&jl_sig_throw; #elif defined(_CPU_X86_) *(DWORD32*)Esp = 0; ctxThread->Esp = Esp; - ctxThread->Eip = (DWORD)&jl_rethrow; + ctxThread->Eip = (DWORD)&jl_sig_throw; #endif } @@ -220,6 +220,7 @@ LONG WINAPI jl_exception_handler(struct _EXCEPTION_POINTERS *ExceptionInfo) jl_throw_in_ctx(jl_diverror_exception, ExceptionInfo->ContextRecord); return EXCEPTION_CONTINUE_EXECUTION; case EXCEPTION_STACK_OVERFLOW: + ptls->needs_resetstkoflw = 1; jl_throw_in_ctx(jl_stackovf_exception, ExceptionInfo->ContextRecord); return EXCEPTION_CONTINUE_EXECUTION; case EXCEPTION_ACCESS_VIOLATION: diff --git a/src/stackwalk.c b/src/stackwalk.c index dce26784dfb1f..3c9f463ced994 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -51,12 +51,12 @@ size_t jl_unw_stepn(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, size_t ma if (!jl_unw_step(cursor, &ip[n], &thesp, &thefp)) break; if (sp) - sp[n] = thesp; - if (add_interp_frames && jl_is_enter_interpreter_frame(ip[n])) { - n += jl_capture_interp_frame(&ip[n], thesp, thefp, maxsize-n-1) + 1; - } else { - n++; - } + sp[n] = thesp; + if (add_interp_frames && jl_is_enter_interpreter_frame(ip[n])) { + n += jl_capture_interp_frame(&ip[n], thesp, thefp, maxsize-n-1) + 1; + } else { + n++; + } } n++; #if !defined(_OS_WINDOWS_) @@ -127,7 +127,7 @@ JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp) n = 0; while (n < jl_array_len(ip)) { - if ((uintptr_t)jl_array_ptr_ref(ip, n) == (uintptr_t)-1) { + if ((uintptr_t)jl_array_ptr_ref(ip, n) == JL_BT_INTERP_FRAME) { jl_array_ptr_1d_push(bt2, jl_array_ptr_ref(ip, n+1)); n += 2; } @@ -139,23 +139,23 @@ JL_DLLEXPORT jl_value_t *jl_backtrace_from_here(int returnsp) return bt; } -JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) +void decode_backtrace(uintptr_t *bt_data, size_t bt_size, + jl_array_t **btout, jl_array_t **bt2out) { - jl_ptls_t ptls = jl_get_ptls_states(); jl_array_t *bt = NULL; jl_array_t *bt2 = NULL; JL_GC_PUSH2(&bt, &bt2); if (array_ptr_void_type == NULL) { array_ptr_void_type = jl_apply_type2((jl_value_t*)jl_array_type, (jl_value_t*)jl_voidpointer_type, jl_box_long(1)); } - bt = jl_alloc_array_1d(array_ptr_void_type, ptls->bt_size); - memcpy(bt->data, ptls->bt_data, ptls->bt_size * sizeof(void*)); + bt = jl_alloc_array_1d(array_ptr_void_type, bt_size); + memcpy(bt->data, bt_data, bt_size * sizeof(void*)); bt2 = jl_alloc_array_1d(jl_array_any_type, 0); // Scan the stack for any interpreter frames size_t n = 0; - while (n < ptls->bt_size) { - if (ptls->bt_data[n] == (uintptr_t)-1) { - jl_array_ptr_1d_push(bt2, (jl_value_t*)ptls->bt_data[n+1]); + while (n < bt_size) { + if (bt_data[n] == JL_BT_INTERP_FRAME) { + jl_array_ptr_1d_push(bt2, (jl_value_t*)bt_data[n+1]); n += 2; } n++; @@ -165,6 +165,48 @@ JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) JL_GC_POP(); } +JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out) +{ + jl_excstack_t *s = jl_get_ptls_states()->current_task->excstack; + uintptr_t *bt_data = NULL; + size_t bt_size = 0; + if (s && s->top) { + bt_data = jl_excstack_bt_data(s, s->top); + bt_size = jl_excstack_bt_size(s, s->top); + } + decode_backtrace(bt_data, bt_size, btout, bt2out); +} + +// Return data from the exception stack for `task` as an array of Any, starting +// with the top of the stack and returning up to `max_entries`. If requested by +// setting the `include_bt` flag, backtrace data in bt,bt2 format is +// interleaved. +JL_DLLEXPORT jl_value_t *jl_get_excstack(jl_value_t* task, int include_bt, int max_entries) +{ + JL_TYPECHK(catch_stack, task, task); + jl_array_t *stack = NULL; + jl_array_t *bt = NULL; + jl_array_t *bt2 = NULL; + JL_GC_PUSH3(&stack, &bt, &bt2); + stack = jl_alloc_array_1d(jl_array_any_type, 0); + jl_excstack_t *excstack = ((jl_task_t*)task)->excstack; + size_t itr = excstack ? excstack->top : 0; + int i = 0; + while (itr > 0 && i < max_entries) { + jl_array_ptr_1d_push(stack, jl_excstack_exception(excstack, itr)); + if (include_bt) { + decode_backtrace(jl_excstack_bt_data(excstack, itr), + jl_excstack_bt_size(excstack, itr), + &bt, &bt2); + jl_array_ptr_1d_push(stack, (jl_value_t*)bt); + jl_array_ptr_1d_push(stack, (jl_value_t*)bt2); + } + itr = jl_excstack_next(excstack, itr); + i++; + } + JL_GC_POP(); + return (jl_value_t*)stack; +} #if defined(_OS_WINDOWS_) #ifdef _CPU_X86_64_ @@ -283,9 +325,8 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt *sp = (uintptr_t)cursor->stackframe.AddrStack.Offset; if (fp) *fp = (uintptr_t)cursor->stackframe.AddrFrame.Offset; - if (*ip == 0 || *ip == ((uintptr_t)0)-1) { - // -1 is a special marker in the backtrace, - // don't leave it in there since it can corrupt the GC. + if (*ip == 0 || *ip == JL_BT_INTERP_FRAME) { + // don't leave special marker in the bt data as it can corrupt the GC. *ip = 0; if (!readable_pointer((LPCVOID)*sp)) return 0; @@ -302,9 +343,8 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt *sp = (uintptr_t)cursor->Rsp; if (fp) *fp = (uintptr_t)cursor->Rbp; - if (*ip == 0 || *ip == ((uintptr_t)0)-1) { - // -1 is a special marker in the backtrace, - // don't leave it in there since it can corrupt the GC. + if (*ip == 0 || *ip == JL_BT_INTERP_FRAME) { + // don't leave special marker in the bt data as it can corrupt the GC. *ip = 0; if (!readable_pointer((LPCVOID)*sp)) return 0; @@ -358,9 +398,8 @@ static int jl_unw_step(bt_cursor_t *cursor, uintptr_t *ip, uintptr_t *sp, uintpt unw_word_t reg; if (unw_get_reg(cursor, UNW_REG_IP, ®) < 0) return 0; - // -1 is a special marker in the backtrace, - // don't leave it in there since it can corrupt the GC. - *ip = reg == (uintptr_t)-1 ? 0 : reg; + // don't leave special marker in the bt data as it can corrupt the GC. + *ip = reg == JL_BT_INTERP_FRAME ? 0 : reg; if (unw_get_reg(cursor, UNW_REG_SP, ®) < 0) return 0; *sp = reg; @@ -474,10 +513,21 @@ JL_DLLEXPORT void jl_gdblookup(uintptr_t ip) JL_DLLEXPORT void jlbacktrace(void) { - jl_ptls_t ptls = jl_get_ptls_states(); - size_t i, n = ptls->bt_size; // ptls->bt_size > 400 ? 400 : ptls->bt_size; - for (i = 0; i < n; i++) - jl_gdblookup(ptls->bt_data[i] - 1); + jl_excstack_t *s = jl_get_ptls_states()->current_task->excstack; + if (!s) + return; + size_t bt_size = jl_excstack_bt_size(s, s->top); + uintptr_t *bt_data = jl_excstack_bt_data(s, s->top); + for (size_t i = 0; i < bt_size; ) { + if (bt_data[i] == JL_BT_INTERP_FRAME) { + jl_safe_printf("Interpreter frame (ip: %d)\n", (int)bt_data[i+2]); + jl_static_show(JL_STDERR, (jl_value_t*)bt_data[i+1]); + i += 3; + } else { + jl_gdblookup(bt_data[i] - 1); + i += 1; + } + } } #ifdef __cplusplus diff --git a/src/staticdata.c b/src/staticdata.c index 621284dce8b05..8747401f08065 100644 --- a/src/staticdata.c +++ b/src/staticdata.c @@ -1100,7 +1100,6 @@ static void jl_finalize_serializer(jl_serializer_state *s) void jl_typemap_rehash(jl_typemap_t *ml, int8_t offs); static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list) { - jl_ptls_t ptls = jl_get_ptls_states(); JL_TRY { switch (how) { case 1: { // rehash IdDict @@ -1173,7 +1172,7 @@ static void jl_reinit_item(jl_value_t *v, int how, arraylist_t *tracee_list) jl_printf(JL_STDERR, "WARNING: error while reinitializing value "); jl_static_show(JL_STDERR, v); jl_printf(JL_STDERR, ":\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); } } diff --git a/src/task.c b/src/task.c index a72b43b5660d8..3dce377a01a89 100644 --- a/src/task.c +++ b/src/task.c @@ -174,7 +174,7 @@ static void JL_NORETURN finish_task(jl_task_t *t, jl_value_t *resultval JL_MAYBE jl_apply(args, 2); } JL_CATCH { - jl_no_exc_handler(jl_exception_in_transit); + jl_no_exc_handler(jl_current_exception()); } } gc_debug_critical_error(); @@ -211,13 +211,12 @@ JL_DLLEXPORT void *jl_task_stack_buffer(jl_task_t *task, size_t *size, int *tid) return (void *)((char *)task->stkbuf + off); } -static void record_backtrace(void) JL_NOTSAFEPOINT +static void record_backtrace(jl_ptls_t ptls) JL_NOTSAFEPOINT { - jl_ptls_t ptls = jl_get_ptls_states(); + // storing bt_size in ptls ensures roots in bt_data will be found ptls->bt_size = rec_backtrace(ptls->bt_data, JL_MAX_BT_SIZE); } - JL_DLLEXPORT void julia_init(JL_IMAGE_SEARCH rel) { _julia_init(rel); @@ -235,8 +234,6 @@ static void ctx_switch(jl_ptls_t ptls, jl_task_t **pt) if (blk) jl_timing_block_stop(blk); #endif - // backtraces don't survive task switches, see e.g. issue #12485 - ptls->bt_size = 0; #ifdef JULIA_ENABLE_THREADING // If the current task is not holding any locks, free the locks list // so that it can be GC'd without leaking memory @@ -375,15 +372,24 @@ JL_DLLEXPORT JL_NORETURN void jl_no_exc_handler(jl_value_t *e) JL_NOTSAFEPOINT jl_timing_block_t *jl_pop_timing_block(jl_timing_block_t *cur_block); // yield to exception handler -void JL_NORETURN throw_internal(jl_value_t *e JL_MAYBE_UNROOTED) +void JL_NORETURN throw_internal(jl_value_t *exception JL_MAYBE_UNROOTED) { jl_ptls_t ptls = jl_get_ptls_states(); ptls->io_wait = 0; if (ptls->safe_restore) jl_longjmp(*ptls->safe_restore, 1); - assert(e != NULL); - ptls->exception_in_transit = e; + JL_GC_PUSH1(&exception); jl_gc_unsafe_enter(ptls); + if (exception) { + // The temporary ptls->bt_data is rooted by special purpose code in the + // GC. This exists only for the purpose of preserving bt_data until we + // set ptls->bt_size=0 below. + assert(ptls->current_task); + jl_push_excstack(&ptls->current_task->excstack, exception, + ptls->bt_data, ptls->bt_size); + ptls->bt_size = 0; + } + assert(ptls->current_task->excstack && ptls->current_task->excstack->top); jl_handler_t *eh = ptls->current_task->eh; if (eh != NULL) { #ifdef ENABLE_TIMINGS @@ -396,32 +402,56 @@ void JL_NORETURN throw_internal(jl_value_t *e JL_MAYBE_UNROOTED) jl_longjmp(eh->eh_ctx, 1); } else { - jl_no_exc_handler(e); + jl_no_exc_handler(exception); } assert(0); } // record backtrace and raise an error -JL_DLLEXPORT void jl_throw(jl_value_t *e) +JL_DLLEXPORT void jl_throw(jl_value_t *e JL_MAYBE_UNROOTED) { jl_ptls_t ptls = jl_get_ptls_states(); assert(e != NULL); - if (!ptls->safe_restore) - record_backtrace(); + if (ptls->safe_restore) + throw_internal(NULL); + record_backtrace(ptls); throw_internal(e); } +// rethrow with current excstack state JL_DLLEXPORT void jl_rethrow(void) { - jl_ptls_t ptls = jl_get_ptls_states(); - throw_internal(ptls->exception_in_transit); + jl_excstack_t *excstack = jl_get_ptls_states()->current_task->excstack; + if (!excstack || excstack->top == 0) + jl_error("rethrow() not allowed outside a catch block"); + throw_internal(NULL); } -JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e) +// Special case throw for errors detected inside signal handlers. This is not +// (cannot be) called directly in the signal handler itself, but is returned to +// after the signal handler exits. +JL_DLLEXPORT void jl_sig_throw(void) { + jl_ptls_t ptls = jl_get_ptls_states(); + jl_value_t *e = ptls->sig_exception; + assert(e && ptls->bt_size != 0); + ptls->sig_exception = NULL; throw_internal(e); } +JL_DLLEXPORT void jl_rethrow_other(jl_value_t *e JL_MAYBE_UNROOTED) +{ + // TODO: Should uses of `rethrow(exc)` be replaced with a normal throw, now + // that exception stacks allow root cause analysis? + jl_excstack_t *excstack = jl_get_ptls_states()->current_task->excstack; + if (!excstack || excstack->top == 0) + jl_error("rethrow(exc) not allowed outside a catch block"); + // overwrite exception on top of stack. see jl_excstack_exception + jl_excstack_raw(excstack)[excstack->top-1] = (uintptr_t)e; + JL_GC_PROMISE_ROOTED(e); + throw_internal(NULL); +} + JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, size_t ssize) { jl_ptls_t ptls = jl_get_ptls_states(); @@ -458,6 +488,7 @@ JL_DLLEXPORT jl_task_t *jl_new_task(jl_function_t *start, size_t ssize) t->eh = NULL; t->tid = 0; t->gcstack = NULL; + t->excstack = NULL; t->stkbuf = NULL; t->tid = 0; t->started = 0; @@ -525,7 +556,9 @@ static void NOINLINE JL_NORETURN start_task(void) jl_value_t *res; t->started = 1; if (t->exception != jl_nothing) { - record_backtrace(); + record_backtrace(ptls); + jl_push_excstack(&t->excstack, t->exception, + ptls->bt_data, ptls->bt_size); res = t->exception; } else { @@ -539,10 +572,12 @@ static void NOINLINE JL_NORETURN start_task(void) res = jl_apply(&t->start, 1); } JL_CATCH { - res = jl_exception_in_transit; + res = jl_current_exception(); t->exception = res; jl_gc_wb(t, res); + goto skip_pop_exception; } +skip_pop_exception:; } finish_task(t, res); gc_debug_critical_error(); @@ -873,12 +908,12 @@ void jl_init_root_task(void *stack_lo, void *stack_hi) ptls->current_task->logstate = jl_nothing; ptls->current_task->eh = NULL; ptls->current_task->gcstack = NULL; + ptls->current_task->excstack = NULL; ptls->current_task->tid = ptls->tid; #ifdef JULIA_ENABLE_THREADING arraylist_new(&ptls->current_task->locks, 0); #endif - ptls->exception_in_transit = (jl_value_t*)jl_nothing; ptls->root_task = ptls->current_task; jl_init_basefiber(JL_STACK_SIZE); diff --git a/src/threading.c b/src/threading.c index 445d17b485695..0a68e2025aede 100644 --- a/src/threading.c +++ b/src/threading.c @@ -275,13 +275,17 @@ static void ti_initthread(int16_t tid) } ptls->defer_signal = 0; void *bt_data = malloc(sizeof(uintptr_t) * (JL_MAX_BT_SIZE + 1)); - memset(bt_data, 0, sizeof(uintptr_t) * (JL_MAX_BT_SIZE + 1)); if (bt_data == NULL) { jl_printf(JL_STDERR, "could not allocate backtrace buffer\n"); gc_debug_critical_error(); abort(); } + memset(bt_data, 0, sizeof(uintptr_t) * (JL_MAX_BT_SIZE + 1)); ptls->bt_data = (uintptr_t*)bt_data; + ptls->sig_exception = NULL; +#ifdef _OS_WINDOWS_ + ptls->needs_resetstkoflw = 0; +#endif jl_init_thread_heap(ptls); jl_install_thread_signal_handler(ptls); @@ -320,7 +324,7 @@ static jl_value_t *ti_run_fun(jl_callptr_t fptr, jl_method_instance_t *mfunc, ptls->safe_restore = &buf; jl_printf(JL_STDERR, "\nError thrown in threaded loop on thread %d: ", (int)ptls->tid); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); } ptls->safe_restore = old_buf; JL_UNLOCK_NOGC(&lock); diff --git a/src/toplevel.c b/src/toplevel.c index 4a940c0ab4d4d..ecbeb7fe1dfd4 100644 --- a/src/toplevel.c +++ b/src/toplevel.c @@ -78,7 +78,7 @@ void jl_module_run_initializer(jl_module_t *m) } else { jl_rethrow_other(jl_new_struct(jl_initerror_type, m->name, - jl_exception_in_transit)); + jl_current_exception())); } } } diff --git a/test/channels.jl b/test/channels.jl index b750902606d78..a2dcf2c4ea2cf 100644 --- a/test/channels.jl +++ b/test/channels.jl @@ -217,7 +217,10 @@ using Dates end @testset "yield/wait/event failures" begin - @noinline garbage_finalizer(f) = finalizer(f, "gar" * "bage") + # garbage_finalizer returns `nothing` rather than the garbage object so + # that the interpreter doesn't accidentally root the garbage when + # interpreting the calling function. + @noinline garbage_finalizer(f) = (finalizer(f, "gar" * "bage"); nothing) run = Ref(0) GC.enable(false) # test for finalizers trying to yield leading to failed attempts to context switch diff --git a/test/choosetests.jl b/test/choosetests.jl index 1ce2591aff61b..2f9c5cd000bf2 100644 --- a/test/choosetests.jl +++ b/test/choosetests.jl @@ -41,7 +41,7 @@ function choosetests(choices = []) "intfuncs", "simdloop", "vecelement", "rational", "bitarray", "copy", "math", "fastmath", "functional", "iterators", "operators", "path", "ccall", "parse", "loading", "bigint", - "sorting", "spawn", "backtrace", + "sorting", "spawn", "backtrace", "exceptions", "file", "read", "version", "namedtuple", "mpfr", "broadcast", "complex", "floatapprox", "stdlib", "reflection", "regex", "float16", diff --git a/test/exceptions.jl b/test/exceptions.jl new file mode 100644 index 0000000000000..6bbeebd88e4c5 --- /dev/null +++ b/test/exceptions.jl @@ -0,0 +1,260 @@ +using Test + +@testset "Basic exception stack handling" begin + # Exiting the catch block normally pops the exception + try + error("A") + catch + @test length(catch_stack()) == 1 + end + @test length(catch_stack()) == 0 + # Exiting via a finally block does not pop the exception + try + try + error("A") + finally + @test length(catch_stack()) == 1 + end + catch + @test length(catch_stack()) == 1 + end + # The combined try-catch-finally form obeys the same rules as above + try + error("A") + catch + @test length(catch_stack()) == 1 + finally + @test length(catch_stack()) == 0 + end + @test length(catch_stack()) == 0 + # Errors are pushed onto the stack according to catch block nesting + try + error("RootCause") + catch + @test length(catch_stack()) == 1 + try + error("B") + catch + stack = catch_stack() + @test length(stack) == 2 + @test stack[1][1].msg == "RootCause" + @test stack[2][1].msg == "B" + end + # Stack pops correctly + stack = catch_stack() + @test length(stack) == 1 + @test stack[1][1].msg == "RootCause" + end +end + +@testset "Exception stack lowering special cases" begin + # try block in value position + val = try + error("A") + catch + @test length(catch_stack()) == 1 + 1 + end + @test val == 1 + function test_exc_stack_tailpos() + # try block in tail position + try + error("A") + catch + length(catch_stack()) + end + end + @test test_exc_stack_tailpos() == 1 + @test length(catch_stack()) == 0 +end + +@testset "Exception stacks - early exit from try or catch" begin + # Exiting a catch block early with normal control flow — break, continue, + # return, goto — will result in popping of the exception stack. + function test_exc_stack_catch_return() + try + error("A") + catch + @test length(catch_stack()) == 1 + return + end + end + test_exc_stack_catch_return() + for i=1:1 + try + error("A") + catch + @test length(catch_stack()) == 1 + break + end + end + for i=1:1 + try + error("A") + catch + @test length(catch_stack()) == 1 + continue + end + end + try + error("A") + catch + @test length(catch_stack()) == 1 + @goto outofcatch + end + @label outofcatch + @test length(catch_stack()) == 0 + + # Exiting from a try block in various ways should not affect the exception + # stack state. + try + error("ExceptionInOuterTry") + catch + @test length(catch_stack()) == 1 + function test_exc_stack_try_return() + try + return + catch + end + end + test_exc_stack_try_return() + for i=1:1 + try + break + catch + end + end + for i=1:1 + try + continue + catch + end + end + try + @goto outoftry + catch + end + @label outoftry + @test length(catch_stack()) == 1 + @test catch_stack()[1][1] == ErrorException("ExceptionInOuterTry") + end +end + +@testset "Deep exception stacks" begin + # Generate deep exception stack with recursive handlers Note that if you + # let this overflow the program stack (not the exception stack) julia will + # crash. See #28577 + function test_exc_stack_deep(n) + n != 1 || error("RootCause") + try + test_exc_stack_deep(n-1) + catch + error("n==$n") + end + end + @test try + test_exc_stack_deep(100) + catch + @test catch_stack()[1][1] == ErrorException("RootCause") + length(catch_stack()) + end == 100 + @test length(catch_stack()) == 0 +end + +@testset "Exception stacks and Tasks" begin + # Task switching should not affect exception state. See #12485. + try + error("A") + catch + t = @task try + error("B") + catch exc + exc + end + yield(t) + @test t.state == :done + @test t.result == ErrorException("B") + # Task exception state is preserved around task switches + @test length(catch_stack()) == 1 + @test catch_stack()[1][1] == ErrorException("A") + end + @test length(catch_stack()) == 0 + # test rethrow() rethrows correct state + bt = [] + try + try + error("A") + catch + bt = catch_backtrace() + t = @task try + error("B") + catch exc + exc + end + yield(t) + @test t.state == :done + @test t.result == ErrorException("B") + @test bt == catch_backtrace() + rethrow() + end + catch exc + @test exc == ErrorException("A") + @test bt == catch_backtrace() + end + @test length(catch_stack()) == 0 + # test rethrow with argument + bt = [] + try + try + error("A") + catch + t = @task try + error("B") + catch exc + exc + end + yield(t) + @test t.state == :done + @test t.result == ErrorException("B") + bt = catch_backtrace() + rethrow(ErrorException("C")) + end + catch exc + @test exc == ErrorException("C") + @test bt == catch_backtrace() + end + @test length(catch_stack()) == 0 + # Exception stacks on other tasks + t = @task try + error("A") + catch + error("B") + end + yield(t) + @test t.state == :failed + @test t.result == ErrorException("B") + @test catch_stack(t, include_bt=false) == [ErrorException("A"), ErrorException("B")] + # Exception stacks for tasks which never get the chance to start + t = @task nothing + @test try + @async Base.throwto(t, ErrorException("expected")) + wait(t) + catch e + e + end == ErrorException("expected") + @test length(catch_stack(t)) == 1 + @test length(catch_stack(t)[1][2]) > 0 # backtrace is nonempty +end + +@testset "rethrow" begin + @test try + rethrow() + catch exc + exc + end == ErrorException("rethrow() not allowed outside a catch block") + @test try + rethrow(ErrorException("A")) + catch exc + exc + end == ErrorException("rethrow(exc) not allowed outside a catch block") +end diff --git a/ui/repl.c b/ui/repl.c index fe5ecf53fbdd9..a33f81ed2ab0c 100644 --- a/ui/repl.c +++ b/ui/repl.c @@ -31,28 +31,18 @@ extern "C" { static int exec_program(char *program) { - jl_ptls_t ptls = jl_get_ptls_states(); JL_TRY { jl_load(jl_main_module, program); } JL_CATCH { jl_value_t *errs = jl_stderr_obj(); - jl_value_t *e = ptls->exception_in_transit; - // Manually save and restore the backtrace so that we print the original - // one instead of the one caused by `show`. - // We can't use safe_restore since that will cause any error - // (including the ones that would have been caught) to abort. - uintptr_t *volatile bt_data = NULL; - size_t bt_size = ptls->bt_size; volatile int shown_err = 0; jl_printf(JL_STDERR, "error during bootstrap:\n"); JL_TRY { if (errs) { - bt_data = (uintptr_t*)malloc(bt_size * sizeof(void*)); - memcpy(bt_data, ptls->bt_data, bt_size * sizeof(void*)); jl_value_t *showf = jl_get_function(jl_base_module, "show"); if (showf != NULL) { - jl_call2(showf, errs, e); + jl_call2(showf, errs, jl_current_exception()); jl_printf(JL_STDERR, "\n"); shown_err = 1; } @@ -60,13 +50,8 @@ static int exec_program(char *program) } JL_CATCH { } - if (bt_data) { - ptls->bt_size = bt_size; - memcpy(ptls->bt_data, bt_data, bt_size * sizeof(void*)); - free(bt_data); - } if (!shown_err) { - jl_static_show(JL_STDERR, e); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); } jlbacktrace(); @@ -99,7 +84,6 @@ static void print_profile(void) static NOINLINE int true_main(int argc, char *argv[]) { - jl_ptls_t ptls = jl_get_ptls_states(); jl_set_ARGS(argc, argv); jl_function_t *start_client = jl_base_module ? @@ -113,7 +97,7 @@ static NOINLINE int true_main(int argc, char *argv[]) jl_get_ptls_states()->world_age = last_age; } JL_CATCH { - jl_no_exc_handler(jl_exception_in_transit); + jl_no_exc_handler(jl_current_exception()); } return 0; } @@ -138,7 +122,7 @@ static NOINLINE int true_main(int argc, char *argv[]) jl_value_t *val = (jl_value_t*)jl_eval_string(line); if (jl_exception_occurred()) { jl_printf(JL_STDERR, "error during run:\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_exception_occurred()); jl_exception_clear(); } else if (val) { @@ -155,7 +139,7 @@ static NOINLINE int true_main(int argc, char *argv[]) line = NULL; } jl_printf(JL_STDERR, "\nparser error:\n"); - jl_static_show(JL_STDERR, ptls->exception_in_transit); + jl_static_show(JL_STDERR, jl_current_exception()); jl_printf(JL_STDERR, "\n"); jlbacktrace(); }