Skip to content

Commit

Permalink
Merge pull request #28878 from JuliaLang/cjf/exception-stack
Browse files Browse the repository at this point in the history
  • Loading branch information
c42f authored Oct 23, 2018
2 parents 8496fe9 + 3577e33 commit 24f1316
Show file tree
Hide file tree
Showing 39 changed files with 935 additions and 296 deletions.
3 changes: 3 additions & 0 deletions NEWS.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
----------------
Expand Down Expand Up @@ -36,5 +38,6 @@ Deprecated or removed

<!--- generated by NEWS-update.jl: -->
[#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
2 changes: 1 addition & 1 deletion base/compiler/ssair/ir.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
1 change: 1 addition & 0 deletions base/compiler/ssair/slot2ssa.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
3 changes: 2 additions & 1 deletion base/compiler/validation.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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
Expand Down
24 changes: 24 additions & 0 deletions base/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -679,6 +679,7 @@ export
# errors
backtrace,
catch_backtrace,
catch_stack,
error,
rethrow,
retry,
Expand Down
1 change: 1 addition & 0 deletions doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,7 @@ Core.throw
Base.rethrow
Base.backtrace
Base.catch_backtrace
Base.catch_stack
Base.@assert
Base.ArgumentError
Base.AssertionError
Expand Down
10 changes: 7 additions & 3 deletions doc/src/devdocs/ast.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
4 changes: 2 additions & 2 deletions doc/src/manual/control-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
47 changes: 47 additions & 0 deletions doc/src/manual/stacktraces.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 6 additions & 2 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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");
Expand Down Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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("<macrocall>");
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;
Expand Down
8 changes: 0 additions & 8 deletions src/cgutils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -2561,14 +2561,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<Value*>(offset), "jl_exception_in_transit");
}

static void emit_signal_fence(jl_codectx_t &ctx)
{
#if defined(_CPU_ARM_) || defined(_CPU_AARCH64_)
Expand Down
Loading

0 comments on commit 24f1316

Please sign in to comment.