Skip to content

Commit

Permalink
Exception stack API refinements
Browse files Browse the repository at this point in the history
* Rename `catch_stack()` to the more descriptive name `current_exceptions()`.
* Introduce an ExceptionStack as the return type for the function.

Having ExceptionStack gives us a place to integrate exception printing
in a natural way. In the same way this should be useful for dispatch in
other areas of the ecosystem which want to dispatch on exception stacks.
  • Loading branch information
c42f committed Feb 6, 2019
1 parent c396179 commit 23b064d
Show file tree
Hide file tree
Showing 12 changed files with 96 additions and 68 deletions.
10 changes: 5 additions & 5 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -99,7 +99,7 @@ function display_error(io::IO, er, bt)
showerror(IOContext(io, :limit => true), er, bt)
println(io)
end
function display_error(io::IO, stack::Vector)
function display_error(io::IO, stack::ExceptionStack)
nexc = length(stack)
printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
# Display exception stack with the top of the stack first. This ordering
Expand All @@ -121,7 +121,7 @@ function display_error(io::IO, stack::Vector)
end
end
end
display_error(stack::Vector) = display_error(stderr, stack)
display_error(stack::ExceptionStack) = display_error(stderr, stack)
display_error(er, bt) = display_error(stderr, er, bt)
display_error(er) = display_error(er, [])

Expand Down Expand Up @@ -160,7 +160,7 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
@error "SYSTEM: display_error(errio, lasterr) caused an error"
end
errcount += 1
lasterr = catch_stack()
lasterr = current_exceptions()
if errcount > 2
@error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount
break
Expand Down Expand Up @@ -306,7 +306,7 @@ function exec_options(opts)
try
include(Main, PROGRAM_FILE)
catch
invokelatest(display_error, catch_stack())
invokelatest(display_error, current_exceptions())
if !is_interactive
exit(1)
end
Expand Down Expand Up @@ -475,7 +475,7 @@ function _start()
try
exec_options(JLOptions())
catch
invokelatest(display_error, catch_stack())
invokelatest(display_error, current_exceptions())
exit(1)
end
if is_interactive && have_color
Expand Down
26 changes: 16 additions & 10 deletions base/error.jl
Original file line number Diff line number Diff line change
Expand Up @@ -91,14 +91,19 @@ function catch_backtrace()
return _reformat_bt(bt[], bt2[])
end

struct ExceptionStack <: AbstractArray{Any,1}
stack
end

"""
catch_stack(task=current_task(); [inclue_bt=true])
current_exceptions(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.
thrown exception is last in the stack. The stack is returned as an
`ExceptionStack` which is an AbstractVector of named tuples
`(exception,backtrace)`. If `backtrace` is false, the backtrace in each pair
will be set to `nothing`.
Explicitly passing `task` will return the current exception stack on an
arbitrary task. This is useful for inspecting tasks which have failed due to
Expand All @@ -108,15 +113,16 @@ uncaught exceptions.
This function is experimental in Julia 1.1 and will likely be renamed in a
future release (see https://github.com/JuliaLang/julia/pull/29901).
"""
function catch_stack(task=current_task(); include_bt=true)
raw = ccall(:jl_get_excstack, Any, (Any,Cint,Cint), task, include_bt, typemax(Cint))
function current_exceptions(task=current_task(); backtrace=true)
raw = ccall(:jl_get_excstack, Any, (Any,Cint,Cint), task, backtrace, typemax(Cint))
formatted = Any[]
stride = include_bt ? 3 : 1
stride = backtrace ? 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)
exc = raw[i]
bt = backtrace ? Base._reformat_bt(raw[i+1],raw[i+2]) : nothing
push!(formatted, (exception=exc,backtrace=bt))
end
formatted
ExceptionStack(formatted)
end

## keyword arg lowering generates calls to this ##
Expand Down
20 changes: 20 additions & 0 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -633,6 +633,26 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true)
return ret
end

size(s::ExceptionStack) = size(s.stack)
getindex(s::ExceptionStack, i) = s.stack[i]

function show(io::IO, ::MIME"text/plain", stack::ExceptionStack)
nexc = length(stack)
printstyled(io, nexc, "-element ExceptionStack", nexc == 0 ? "" : ":\n")
for i = nexc:-1:1
if nexc != 1
printstyled(io, "caused by [exception ", i, "]\n", color=:light_black)
end
exc,bt = stack[i]
show(io, exc)
if !isnothing(bt)
show_backtrace(io, bt)
end
println(io)
end
end
show(io::IO, stack::ExceptionStack) = show(io, MIME"text/plain"(), stack)

@noinline function throw_eachindex_mismatch(::IndexLinear, A...)
throw(DimensionMismatch("all inputs to eachindex must have the same indices, got $(join(LinearIndices.(A), ", ", " and "))"))
end
Expand Down
1 change: 1 addition & 0 deletions base/exports.jl
Original file line number Diff line number Diff line change
Expand Up @@ -683,6 +683,7 @@ export
# errors
backtrace,
catch_backtrace,
current_exceptions,
error,
rethrow,
retry,
Expand Down
2 changes: 1 addition & 1 deletion doc/src/base/base.md
Original file line number Diff line number Diff line change
Expand Up @@ -314,7 +314,7 @@ Core.throw
Base.rethrow
Base.backtrace
Base.catch_backtrace
Base.catch_stack
Base.current_exceptions
Base.@assert
Base.ArgumentError
Base.AssertionError
Expand Down
2 changes: 1 addition & 1 deletion doc/src/manual/control-flow.md
Original file line number Diff line number Diff line change
Expand Up @@ -791,7 +791,7 @@ The power of the `try/catch` construct lies in the ability to unwind a deeply ne
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), [`catch_backtrace`](@ref)
and [`Base.catch_stack`](@ref) functions for more advanced error handling.
and [`current_exceptions`](@ref) functions for more advanced error handling.

### `finally` Clauses

Expand Down
8 changes: 4 additions & 4 deletions doc/src/manual/stacktraces.md
Original file line number Diff line number Diff line change
Expand Up @@ -187,7 +187,7 @@ ERROR: Whoops!
[...]
```

## Exception stacks and `catch_stack`
## Exception stacks and [`current_exceptions`](@ref)

!!! compat "Julia 1.1"
Exception stacks requires at least Julia 1.1.
Expand All @@ -197,7 +197,7 @@ identify the root cause of a problem. The julia runtime supports this by pushing
*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 experimental [`Base.catch_stack`](@ref) function. For example,
The stack of current exceptions can be accessed using the [`current_exceptions`](@ref) function. For example,

```julia-repl
julia> try
Expand All @@ -206,7 +206,7 @@ julia> try
try
error("(B) An exception while handling the exception")
catch
for (exc, bt) in Base.catch_stack()
for (exc, bt) in current_exceptions()
showerror(stdout, exc, bt)
println()
end
Expand Down Expand Up @@ -235,7 +235,7 @@ exiting both catch blocks normally (i.e., without throwing a further exception)
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.
`current_exceptions(task)` may be used to inspect the exception stack for that task.

## Comparison with [`backtrace`](@ref)

Expand Down
2 changes: 1 addition & 1 deletion src/stackwalk.c
Original file line number Diff line number Diff line change
Expand Up @@ -183,7 +183,7 @@ JL_DLLEXPORT void jl_get_backtrace(jl_array_t **btout, jl_array_t **bt2out)
// interleaved.
JL_DLLEXPORT jl_value_t *jl_get_excstack(jl_task_t* task, int include_bt, int max_entries)
{
JL_TYPECHK(catch_stack, task, (jl_value_t*)task);
JL_TYPECHK(current_exceptions, task, (jl_value_t*)task);
jl_ptls_t ptls = jl_get_ptls_states();
if (task != ptls->current_task &&
task->state != failed_sym && task->state != done_sym) {
Expand Down
13 changes: 6 additions & 7 deletions stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ import Base:
display,
show,
AnyDict,
==,
catch_stack
==


include("Terminals.jl")
Expand Down Expand Up @@ -95,7 +94,7 @@ function eval_user_input(@nospecialize(ast), backend::REPLBackend)
println("SYSTEM ERROR: Failed to report error to REPL frontend")
println(err)
end
lasterr = catch_stack()
lasterr = current_exceptions()
end
end
Base.sigatomic_end()
Expand Down Expand Up @@ -167,10 +166,10 @@ function print_response(errio::IO, @nospecialize(response), show_value::Bool, ha
catch
if iserr
println(errio, "SYSTEM (REPL): showing an error caused an error")
println(errio, catch_stack())
println(errio, current_exceptions())
break
end
val = catch_stack()
val = current_exceptions()
iserr = true
end
end
Expand Down Expand Up @@ -696,7 +695,7 @@ function respond(f, repl, main; pass_empty = false)
ast = Base.invokelatest(f, line)
response = eval_with_backend(ast, backend(repl))
catch
response = (catch_stack(), true)
response = (current_exceptions(), true)
end
print_response(repl, response, !ends_with_semicolon(line), Base.have_color)
end
Expand Down Expand Up @@ -839,7 +838,7 @@ function setup_interface(
end
hist_from_file(hp, f, hist_path)
catch
print_response(repl, (catch_stack(),true), true, Base.have_color)
print_response(repl, (current_exceptions(),true), true, Base.have_color)
println(outstream(repl))
@info "Disabling history file for this session"
repl.history_file = false
Expand Down
2 changes: 1 addition & 1 deletion stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,7 @@ mutable struct Error19864 <: Exception; end
function test19864()
@eval Base.showerror(io::IO, e::Error19864) = print(io, "correct19864")
buf = IOBuffer()
fake_response = (Any[(Error19864(),[])],true)
fake_response = (Base.ExceptionStack([(exception=Error19864(),backtrace=[])]),true)
REPL.print_response(buf, fake_response, false, false, nothing)
return String(take!(buf))
end
Expand Down
2 changes: 1 addition & 1 deletion test/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ nested_error_pattern = r"""
err_str = try
eval(nested_error_expr)
catch
excs = Base.catch_stack()
excs = Base.current_exceptions()
@test typeof.(first.(excs)) == [UndefVarError, DivideError]
sprint(Base.display_error, excs)
end
Expand Down
Loading

0 comments on commit 23b064d

Please sign in to comment.