Skip to content

Commit a836fa1

Browse files
c42fjohanmon
authored andcommitted
Exception stack API refinements (JuliaLang#29901)
* Rename the non-exported `catch_stack()` to the more descriptive name `current_exceptions()`. Keep the old name available but deprecated. * Introduce an ExceptionStack as the return type for the function, which (as an AbstractVector) is API-compatible with the previous type returned by `catch_stack()` 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.
1 parent 6675ef7 commit a836fa1

File tree

18 files changed

+138
-106
lines changed

18 files changed

+138
-106
lines changed

NEWS.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ Standard library changes
8787
```
8888
([#39322])
8989
* `@lock` is now exported from Base ([#39588]).
90+
* The experimental function `Base.catch_stack()` has been renamed to `current_exceptions()`, exported from Base and given a more specific return type ([#29901])
9091

9192
#### Package Manager
9293

base/client.jl

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -98,13 +98,13 @@ function display_error(io::IO, er, bt)
9898
showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing)
9999
println(io)
100100
end
101-
function display_error(io::IO, stack::Vector)
101+
function display_error(io::IO, stack::ExceptionStack)
102102
printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
103103
bt = Any[ (x[1], scrub_repl_backtrace(x[2])) for x in stack ]
104104
show_exception_stack(IOContext(io, :limit => true), bt)
105105
println(io)
106106
end
107-
display_error(stack::Vector) = display_error(stderr, stack)
107+
display_error(stack::ExceptionStack) = display_error(stderr, stack)
108108
display_error(er, bt=nothing) = display_error(stderr, er, bt)
109109

110110
function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
@@ -143,7 +143,7 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
143143
@error "SYSTEM: display_error(errio, lasterr) caused an error"
144144
end
145145
errcount += 1
146-
lasterr = catch_stack()
146+
lasterr = current_exceptions()
147147
if errcount > 2
148148
@error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount
149149
break
@@ -257,7 +257,7 @@ function exec_options(opts)
257257
try
258258
load_julia_startup()
259259
catch
260-
invokelatest(display_error, catch_stack())
260+
invokelatest(display_error, current_exceptions())
261261
!(repl || is_interactive) && exit(1)
262262
end
263263
end
@@ -291,7 +291,7 @@ function exec_options(opts)
291291
try
292292
include(Main, PROGRAM_FILE)
293293
catch
294-
invokelatest(display_error, catch_stack())
294+
invokelatest(display_error, current_exceptions())
295295
if !is_interactive::Bool
296296
exit(1)
297297
end
@@ -494,7 +494,7 @@ function _start()
494494
try
495495
exec_options(JLOptions())
496496
catch
497-
invokelatest(display_error, catch_stack())
497+
invokelatest(display_error, current_exceptions())
498498
exit(1)
499499
end
500500
if is_interactive && get(stdout, :color, false)

base/deprecated.jl

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -250,4 +250,7 @@ cat_shape(dims, shape::Tuple{}) = () # make sure `cat_shape(dims, ())` do not re
250250
return getfield(x, s)
251251
end
252252

253+
# This function was marked as experimental and not exported.
254+
@deprecate catch_stack(task=current_task(); include_bt=true) current_exceptions(task; backtrace=include_bt) false
255+
253256
# END 1.7 deprecations

base/error.jl

Lines changed: 20 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ exception will continue propagation as if it had not been caught.
5454
the program state at the time of the error so you're encouraged to instead
5555
throw a new exception using `throw(e)`. In Julia 1.1 and above, using
5656
`throw(e)` will preserve the root cause exception on the stack, as
57-
described in [`catch_stack`](@ref).
57+
described in [`current_exceptions`](@ref).
5858
"""
5959
rethrow() = ccall(:jl_rethrow, Bottom, ())
6060
rethrow(@nospecialize(e)) = ccall(:jl_rethrow_other, Bottom, (Any,), e)
@@ -123,32 +123,38 @@ function catch_backtrace()
123123
return _reformat_bt(bt::Vector{Ptr{Cvoid}}, bt2::Vector{Any})
124124
end
125125

126+
struct ExceptionStack <: AbstractArray{Any,1}
127+
stack
128+
end
129+
126130
"""
127-
catch_stack(task=current_task(); [inclue_bt=true])
131+
current_exceptions(task=current_task(); [inclue_bt=true])
128132
129133
Get the stack of exceptions currently being handled. For nested catch blocks
130134
there may be more than one current exception in which case the most recently
131-
thrown exception is last in the stack. The stack is returned as a Vector of
132-
`(exception,backtrace)` pairs, or a Vector of exceptions if `include_bt` is
133-
false.
135+
thrown exception is last in the stack. The stack is returned as an
136+
`ExceptionStack` which is an AbstractVector of named tuples
137+
`(exception,backtrace)`. If `backtrace` is false, the backtrace in each pair
138+
will be set to `nothing`.
134139
135140
Explicitly passing `task` will return the current exception stack on an
136141
arbitrary task. This is useful for inspecting tasks which have failed due to
137142
uncaught exceptions.
138143
139-
!!! compat "Julia 1.1"
140-
This function is experimental in Julia 1.1 and will likely be renamed in a
141-
future release (see https://github.com/JuliaLang/julia/pull/29901).
144+
!!! compat "Julia 1.7"
145+
This function went by the experiemental name `catch_stack()` in Julia
146+
1.1–1.6, and had a plain Vector-of-tuples as a return type.
142147
"""
143-
function catch_stack(task=current_task(); include_bt=true)
144-
raw = ccall(:jl_get_excstack, Any, (Any,Cint,Cint), task, include_bt, typemax(Cint))::Vector{Any}
148+
function current_exceptions(task=current_task(); backtrace=true)
149+
raw = ccall(:jl_get_excstack, Any, (Any,Cint,Cint), task, backtrace, typemax(Cint))::Vector{Any}
145150
formatted = Any[]
146-
stride = include_bt ? 3 : 1
151+
stride = backtrace ? 3 : 1
147152
for i = reverse(1:stride:length(raw))
148-
e = raw[i]
149-
push!(formatted, include_bt ? (e,Base._reformat_bt(raw[i+1],raw[i+2])) : e)
153+
exc = raw[i]
154+
bt = backtrace ? Base._reformat_bt(raw[i+1],raw[i+2]) : nothing
155+
push!(formatted, (exception=exc,backtrace=bt))
150156
end
151-
formatted
157+
ExceptionStack(formatted)
152158
end
153159

154160
## keyword arg lowering generates calls to this ##

base/errorshow.jl

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -849,7 +849,7 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true)
849849
return _simplify_include_frames(ret)
850850
end
851851

852-
function show_exception_stack(io::IO, stack::Vector)
852+
function show_exception_stack(io::IO, stack)
853853
# Display exception stack with the top of the stack first. This ordering
854854
# means that the user doesn't have to scroll up in the REPL to discover the
855855
# root cause.
@@ -886,3 +886,15 @@ function noncallable_number_hint_handler(io, ex, arg_types, kwargs)
886886
end
887887

888888
Experimental.register_error_hint(noncallable_number_hint_handler, MethodError)
889+
890+
# ExceptionStack implementation
891+
size(s::ExceptionStack) = size(s.stack)
892+
getindex(s::ExceptionStack, i::Int) = s.stack[i]
893+
894+
function show(io::IO, ::MIME"text/plain", stack::ExceptionStack)
895+
nexc = length(stack)
896+
printstyled(io, nexc, "-element ExceptionStack", nexc == 0 ? "" : ":\n")
897+
show_exception_stack(io, stack)
898+
end
899+
show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain"), stack)
900+

base/exports.jl

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -716,6 +716,7 @@ export
716716
# errors
717717
backtrace,
718718
catch_backtrace,
719+
current_exceptions,
719720
error,
720721
rethrow,
721722
retry,

base/task.jl

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ function showerror(io::IO, ex::TaskFailedException, bt = nothing; backtrace=true
7777
end
7878

7979
function show_task_exception(io::IO, t::Task; indent = true)
80-
stack = catch_stack(t)
80+
stack = current_exceptions(t)
8181
b = IOBuffer()
8282
if isempty(stack)
8383
# exception stack buffer not available; probably a serialized task
@@ -162,7 +162,7 @@ end
162162
end
163163
elseif field === :backtrace
164164
# TODO: this field name should be deprecated in 2.0
165-
return catch_stack(t)[end][2]
165+
return current_exceptions(t)[end][2]
166166
elseif field === :exception
167167
# TODO: this field name should be deprecated in 2.0
168168
return t._isexception ? t.result : nothing
@@ -434,18 +434,18 @@ function errormonitor(t::Task)
434434
try # try to display the failure atomically
435435
errio = IOContext(PipeBuffer(), errs::IO)
436436
emphasize(errio, "Unhandled Task ")
437-
display_error(errio, catch_stack(t))
437+
display_error(errio, current_exceptions(t))
438438
write(errs, errio)
439439
catch
440440
try # try to display the secondary error atomically
441441
errio = IOContext(PipeBuffer(), errs::IO)
442442
print(errio, "\nSYSTEM: caught exception while trying to print a failed Task notice: ")
443-
display_error(errio, catch_stack())
443+
display_error(errio, current_exceptions())
444444
write(errs, errio)
445445
flush(errs)
446446
# and then the actual error, as best we can
447447
Core.print(Core.stderr, "while handling: ")
448-
Core.println(Core.stderr, catch_stack(t)[end][1])
448+
Core.println(Core.stderr, current_exceptions(t)[end][1])
449449
catch e
450450
# give up
451451
Core.print(Core.stderr, "\nSYSTEM: caught exception of type ", typeof(e).name.name,

doc/src/base/base.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -354,7 +354,7 @@ Core.throw
354354
Base.rethrow
355355
Base.backtrace
356356
Base.catch_backtrace
357-
Base.catch_stack
357+
Base.current_exceptions
358358
Base.@assert
359359
Base.Experimental.register_error_hint
360360
Base.Experimental.show_error_hints

doc/src/manual/control-flow.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -817,7 +817,7 @@ The power of the `try/catch` construct lies in the ability to unwind a deeply ne
817817
immediately to a much higher level in the stack of calling functions. There are situations where
818818
no error has occurred, but the ability to unwind the stack and pass a value to a higher level
819819
is desirable. Julia provides the [`rethrow`](@ref), [`backtrace`](@ref), [`catch_backtrace`](@ref)
820-
and [`Base.catch_stack`](@ref) functions for more advanced error handling.
820+
and [`current_exceptions`](@ref) functions for more advanced error handling.
821821

822822
### `finally` Clauses
823823

doc/src/manual/stacktraces.md

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -185,7 +185,7 @@ ERROR: Whoops!
185185
[...]
186186
```
187187

188-
## Exception stacks and `catch_stack`
188+
## Exception stacks and [`current_exceptions`](@ref)
189189

190190
!!! compat "Julia 1.1"
191191
Exception stacks requires at least Julia 1.1.
@@ -195,7 +195,7 @@ identify the root cause of a problem. The julia runtime supports this by pushing
195195
*exception stack* as it occurs. When the code exits a `catch` normally, any exceptions which were pushed onto the stack
196196
in the associated `try` are considered to be successfully handled and are removed from the stack.
197197

198-
The stack of current exceptions can be accessed using the experimental [`Base.catch_stack`](@ref) function. For example,
198+
The stack of current exceptions can be accessed using the [`current_exceptions`](@ref) function. For example,
199199

200200
```julia-repl
201201
julia> try
@@ -204,7 +204,7 @@ julia> try
204204
try
205205
error("(B) An exception while handling the exception")
206206
catch
207-
for (exc, bt) in Base.catch_stack()
207+
for (exc, bt) in current_exceptions()
208208
showerror(stdout, exc, bt)
209209
println(stdout)
210210
end
@@ -233,7 +233,7 @@ exiting both catch blocks normally (i.e., without throwing a further exception)
233233
and are no longer accessible.
234234

235235
The exception stack is stored on the `Task` where the exceptions occurred. When a task fails with uncaught exceptions,
236-
`catch_stack(task)` may be used to inspect the exception stack for that task.
236+
`current_exceptions(task)` may be used to inspect the exception stack for that task.
237237

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

0 commit comments

Comments
 (0)