From 35a501b67061bdb4d467276c9f88d455c093d84d Mon Sep 17 00:00:00 2001 From: Chris Foster Date: Fri, 14 May 2021 13:36:16 +1000 Subject: [PATCH] Exception stack API refinements * 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. --- NEWS.md | 1 + base/client.jl | 12 +-- base/deprecated.jl | 3 + base/error.jl | 34 ++++--- base/errorshow.jl | 14 ++- base/exports.jl | 1 + base/task.jl | 10 +- doc/src/base/base.md | 2 +- doc/src/manual/control-flow.md | 2 +- doc/src/manual/stacktraces.md | 8 +- src/stackwalk.c | 2 +- stdlib/REPL/src/REPL.jl | 13 ++- stdlib/REPL/test/repl.jl | 2 +- stdlib/Serialization/src/Serialization.jl | 4 +- stdlib/Test/src/Test.jl | 6 +- stdlib/Test/src/logging.jl | 2 +- test/client.jl | 18 +++- test/exceptions.jl | 110 +++++++++++----------- 18 files changed, 138 insertions(+), 106 deletions(-) diff --git a/NEWS.md b/NEWS.md index d93e4d2d5f968..7bacc44220b9e 100644 --- a/NEWS.md +++ b/NEWS.md @@ -87,6 +87,7 @@ Standard library changes ``` ([#39322]) * `@lock` is now exported from Base ([#39588]). +* The experimental function `Base.catch_stack()` has been renamed to `current_exceptions()`, exported from Base and given a more specific return type ([#29901]) #### Package Manager diff --git a/base/client.jl b/base/client.jl index d9df2f04922dd..7e5f1ab5c5d58 100644 --- a/base/client.jl +++ b/base/client.jl @@ -98,13 +98,13 @@ function display_error(io::IO, er, bt) showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing) println(io) end -function display_error(io::IO, stack::Vector) +function display_error(io::IO, stack::ExceptionStack) printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) bt = Any[ (x[1], scrub_repl_backtrace(x[2])) for x in stack ] show_exception_stack(IOContext(io, :limit => true), bt) println(io) end -display_error(stack::Vector) = display_error(stderr, stack) +display_error(stack::ExceptionStack) = display_error(stderr, stack) display_error(er, bt=nothing) = display_error(stderr, er, bt) function eval_user_input(errio, @nospecialize(ast), show_value::Bool) @@ -143,7 +143,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 @@ -257,7 +257,7 @@ function exec_options(opts) try load_julia_startup() catch - invokelatest(display_error, catch_stack()) + invokelatest(display_error, current_exceptions()) !(repl || is_interactive) && exit(1) end end @@ -291,7 +291,7 @@ function exec_options(opts) try include(Main, PROGRAM_FILE) catch - invokelatest(display_error, catch_stack()) + invokelatest(display_error, current_exceptions()) if !is_interactive::Bool exit(1) end @@ -494,7 +494,7 @@ function _start() try exec_options(JLOptions()) catch - invokelatest(display_error, catch_stack()) + invokelatest(display_error, current_exceptions()) exit(1) end if is_interactive && get(stdout, :color, false) diff --git a/base/deprecated.jl b/base/deprecated.jl index 5cd32a16e50e2..d60cc3393662c 100644 --- a/base/deprecated.jl +++ b/base/deprecated.jl @@ -250,4 +250,7 @@ cat_shape(dims, shape::Tuple{}) = () # make sure `cat_shape(dims, ())` do not re return getfield(x, s) end +# This function was marked as experimental and not exported. +@deprecate catch_stack(task=current_task(); include_bt=true) current_exceptions(task; backtrace=include_bt) false + # END 1.7 deprecations diff --git a/base/error.jl b/base/error.jl index 72c13396e3fc4..9116d00618900 100644 --- a/base/error.jl +++ b/base/error.jl @@ -54,7 +54,7 @@ exception will continue propagation as if it had not been caught. the program state at the time of the error so you're encouraged to instead throw a new exception using `throw(e)`. In Julia 1.1 and above, using `throw(e)` will preserve the root cause exception on the stack, as - described in [`catch_stack`](@ref). + described in [`current_exceptions`](@ref). """ rethrow() = ccall(:jl_rethrow, Bottom, ()) rethrow(@nospecialize(e)) = ccall(:jl_rethrow_other, Bottom, (Any,), e) @@ -123,32 +123,38 @@ function catch_backtrace() return _reformat_bt(bt::Vector{Ptr{Cvoid}}, bt2::Vector{Any}) 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 uncaught exceptions. -!!! compat "Julia 1.1" - 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). +!!! compat "Julia 1.7" + This function went by the experiemental name `catch_stack()` in Julia + 1.1–1.6, and had a plain Vector-of-tuples as a return type. """ -function catch_stack(task=current_task(); include_bt=true) - raw = ccall(:jl_get_excstack, Any, (Any,Cint,Cint), task, include_bt, typemax(Cint))::Vector{Any} +function current_exceptions(task=current_task(); backtrace=true) + raw = ccall(:jl_get_excstack, Any, (Any,Cint,Cint), task, backtrace, typemax(Cint))::Vector{Any} 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 ## diff --git a/base/errorshow.jl b/base/errorshow.jl index ceca8b268e42b..615b1ff4253df 100644 --- a/base/errorshow.jl +++ b/base/errorshow.jl @@ -849,7 +849,7 @@ function process_backtrace(t::Vector, limit::Int=typemax(Int); skipC = true) return _simplify_include_frames(ret) end -function show_exception_stack(io::IO, stack::Vector) +function show_exception_stack(io::IO, stack) # Display exception stack with the top of the stack first. This ordering # means that the user doesn't have to scroll up in the REPL to discover the # root cause. @@ -886,3 +886,15 @@ function noncallable_number_hint_handler(io, ex, arg_types, kwargs) end Experimental.register_error_hint(noncallable_number_hint_handler, MethodError) + +# ExceptionStack implementation +size(s::ExceptionStack) = size(s.stack) +getindex(s::ExceptionStack, i::Int) = s.stack[i] + +function show(io::IO, ::MIME"text/plain", stack::ExceptionStack) + nexc = length(stack) + printstyled(io, nexc, "-element ExceptionStack", nexc == 0 ? "" : ":\n") + show_exception_stack(io, stack) +end +show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain"), stack) + diff --git a/base/exports.jl b/base/exports.jl index 125e38930640d..7a2f25072b8cd 100644 --- a/base/exports.jl +++ b/base/exports.jl @@ -716,6 +716,7 @@ export # errors backtrace, catch_backtrace, + current_exceptions, error, rethrow, retry, diff --git a/base/task.jl b/base/task.jl index 87b354d052797..2c3e5c44e6d0b 100644 --- a/base/task.jl +++ b/base/task.jl @@ -77,7 +77,7 @@ function showerror(io::IO, ex::TaskFailedException, bt = nothing; backtrace=true end function show_task_exception(io::IO, t::Task; indent = true) - stack = catch_stack(t) + stack = current_exceptions(t) b = IOBuffer() if isempty(stack) # exception stack buffer not available; probably a serialized task @@ -162,7 +162,7 @@ end end elseif field === :backtrace # TODO: this field name should be deprecated in 2.0 - return catch_stack(t)[end][2] + return current_exceptions(t)[end][2] elseif field === :exception # TODO: this field name should be deprecated in 2.0 return t._isexception ? t.result : nothing @@ -434,18 +434,18 @@ function errormonitor(t::Task) try # try to display the failure atomically errio = IOContext(PipeBuffer(), errs::IO) emphasize(errio, "Unhandled Task ") - display_error(errio, catch_stack(t)) + display_error(errio, current_exceptions(t)) write(errs, errio) catch try # try to display the secondary error atomically errio = IOContext(PipeBuffer(), errs::IO) print(errio, "\nSYSTEM: caught exception while trying to print a failed Task notice: ") - display_error(errio, catch_stack()) + display_error(errio, current_exceptions()) write(errs, errio) flush(errs) # and then the actual error, as best we can Core.print(Core.stderr, "while handling: ") - Core.println(Core.stderr, catch_stack(t)[end][1]) + Core.println(Core.stderr, current_exceptions(t)[end][1]) catch e # give up Core.print(Core.stderr, "\nSYSTEM: caught exception of type ", typeof(e).name.name, diff --git a/doc/src/base/base.md b/doc/src/base/base.md index 7d1160dac918b..7364b0c3b50b5 100644 --- a/doc/src/base/base.md +++ b/doc/src/base/base.md @@ -354,7 +354,7 @@ Core.throw Base.rethrow Base.backtrace Base.catch_backtrace -Base.catch_stack +Base.current_exceptions Base.@assert Base.Experimental.register_error_hint Base.Experimental.show_error_hints diff --git a/doc/src/manual/control-flow.md b/doc/src/manual/control-flow.md index 1b785070c0e1a..63832cc4c90c9 100644 --- a/doc/src/manual/control-flow.md +++ b/doc/src/manual/control-flow.md @@ -817,7 +817,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 diff --git a/doc/src/manual/stacktraces.md b/doc/src/manual/stacktraces.md index 50cdfd5b1ed64..40130d9e7dd44 100644 --- a/doc/src/manual/stacktraces.md +++ b/doc/src/manual/stacktraces.md @@ -185,7 +185,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. @@ -195,7 +195,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 @@ -204,7 +204,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(stdout) end @@ -233,7 +233,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) diff --git a/src/stackwalk.c b/src/stackwalk.c index d117b7146fe77..790b7583cc587 100644 --- a/src/stackwalk.c +++ b/src/stackwalk.c @@ -335,7 +335,7 @@ JL_DLLEXPORT jl_value_t *jl_get_backtrace(void) // 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 == JL_TASK_STATE_RUNNABLE) { jl_error("Inspecting the exception stack of a task which might " diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index e74834e0bd2c3..7ac8c2906423f 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -29,8 +29,7 @@ import Base: display, show, AnyDict, - ==, - catch_stack + == _displaysize(io::IO) = displaysize(io)::Tuple{Int,Int} @@ -160,7 +159,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() @@ -301,7 +300,7 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, println(errio) # an error during printing is likely to leave us mid-line println(errio, "SYSTEM (REPL): showing an error caused an error") try - Base.invokelatest(Base.display_error, errio, catch_stack()) + Base.invokelatest(Base.display_error, errio, current_exceptions()) catch e # at this point, only print the name of the type as a Symbol to # minimize the possibility of further errors. @@ -311,7 +310,7 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, end break end - val = catch_stack() + val = current_exceptions() iserr = true end end @@ -835,7 +834,7 @@ function respond(f, repl, main; pass_empty::Bool = false, suppress_on_semicolon: ast = Base.invokelatest(f, line) response = eval_with_backend(ast, backend(repl)) catch - response = Pair{Any, Bool}(catch_stack(), true) + response = Pair{Any, Bool}(current_exceptions(), true) end hide_output = suppress_on_semicolon && ends_with_semicolon(line) print_response(repl, response, !hide_output, hascolor(repl)) @@ -987,7 +986,7 @@ function setup_interface( hist_from_file(hp, hist_path) catch # use REPL.hascolor to avoid using the local variable with the same name - print_response(repl, Pair{Any, Bool}(catch_stack(), true), true, REPL.hascolor(repl)) + print_response(repl, Pair{Any, Bool}(current_exceptions(), true), true, REPL.hascolor(repl)) println(outstream(repl)) @info "Disabling history file for this session" repl.history_file = false diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 34011569659af..d467c82eb4226 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -877,7 +877,7 @@ mutable struct Error19864 <: Exception; end function test19864() @eval Base.showerror(io::IO, e::Error19864) = print(io, "correct19864") buf = IOBuffer() - fake_response = (Any[(Error19864(), Ptr{Cvoid}[])], true) + fake_response = (Base.ExceptionStack([(exception=Error19864(),backtrace=Ptr{Cvoid}[])]),true) REPL.print_response(buf, fake_response, false, false, nothing) return String(take!(buf)) end diff --git a/stdlib/Serialization/src/Serialization.jl b/stdlib/Serialization/src/Serialization.jl index f644c73762c2f..060dbb2bc011f 100644 --- a/stdlib/Serialization/src/Serialization.jl +++ b/stdlib/Serialization/src/Serialization.jl @@ -462,11 +462,11 @@ function serialize(s::AbstractSerializer, t::Task) serialize(s, t.code) serialize(s, t.storage) serialize(s, t.state) - if t._isexception && (stk = Base.catch_stack(t); !isempty(stk)) + if t._isexception && (stk = Base.current_exceptions(t); !isempty(stk)) # the exception stack field is hidden inside the task, so if there # is any information there make a CapturedException from it instead. # TODO: Handle full exception chain, not just the first one. - serialize(s, CapturedException(stk[1][1], stk[1][2])) + serialize(s, CapturedException(stk[1].exception, stk[1].backtrace)) else serialize(s, t.result) end diff --git a/stdlib/Test/src/Test.jl b/stdlib/Test/src/Test.jl index d303b6a7c69aa..9a16d3d25c9be 100644 --- a/stdlib/Test/src/Test.jl +++ b/stdlib/Test/src/Test.jl @@ -589,7 +589,7 @@ function get_test_result(ex, source) $testret catch _e _e isa InterruptException && rethrow() - Threw(_e, Base.catch_stack(), $(QuoteNode(source))) + Threw(_e, Base.current_exceptions(), $(QuoteNode(source))) end end Base.remove_linenums!(result) @@ -1272,7 +1272,7 @@ function testset_beginend(args, tests, source) err isa InterruptException && rethrow() # something in the test block threw an error. Count that as an # error in this test set - record(ts, Error(:nontest_error, Expr(:tuple), err, Base.catch_stack(), $(QuoteNode(source)))) + record(ts, Error(:nontest_error, Expr(:tuple), err, Base.current_exceptions(), $(QuoteNode(source)))) finally copy!(RNG, oldrng) pop_testset() @@ -1346,7 +1346,7 @@ function testset_forloop(args, testloop, source) err isa InterruptException && rethrow() # Something in the test block threw an error. Count that as an # error in this test set - record(ts, Error(:nontest_error, Expr(:tuple), err, Base.catch_stack(), $(QuoteNode(source)))) + record(ts, Error(:nontest_error, Expr(:tuple), err, Base.current_exceptions(), $(QuoteNode(source)))) end end quote diff --git a/stdlib/Test/src/logging.jl b/stdlib/Test/src/logging.jl index f7ecf90e2bc7f..d333f40b11428 100644 --- a/stdlib/Test/src/logging.jl +++ b/stdlib/Test/src/logging.jl @@ -184,7 +184,7 @@ macro test_logs(exs...) $(QuoteNode(exs[1:end-1])), logs) end catch e - testres = Error(:test_error, $orig_expr, e, Base.catch_stack(), $sourceloc) + testres = Error(:test_error, $orig_expr, e, Base.current_exceptions(), $sourceloc) end Test.record(Test.get_testset(), testres) value diff --git a/test/client.jl b/test/client.jl index 497cc54b13534..f917e45fb412d 100644 --- a/test/client.jl +++ b/test/client.jl @@ -18,14 +18,22 @@ nested_error_pattern = r""" @testset "display_error" begin # Display of errors which cause more than one entry on the exception stack - err_str = try + excs = try eval(nested_error_expr) catch - excs = Base.catch_stack() - @test typeof.(first.(excs)) == [UndefVarError, DivideError] - sprint(Base.display_error, excs) + Base.current_exceptions() end - @test occursin(nested_error_pattern, err_str) + @test typeof.(first.(excs)) == [UndefVarError, DivideError] + @test occursin(nested_error_pattern, sprint(Base.display_error, excs)) + + @test occursin(r""" + 2-element ExceptionStack: + DivideError: integer division error + Stacktrace:.* + + caused by: UndefVarError: __not_a_binding__ not defined + Stacktrace:.* + """s, sprint(show, excs)) end @testset "Fallback REPL" begin diff --git a/test/exceptions.jl b/test/exceptions.jl index 7b8a54da2c6eb..d8d1e7b45b8b5 100644 --- a/test/exceptions.jl +++ b/test/exceptions.jl @@ -1,52 +1,51 @@ # This file is a part of Julia. License is MIT: https://julialang.org/license using Test -using Base: catch_stack @testset "Basic exception stack handling" begin # Exiting the catch block normally pops the exception try error("A") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 end - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 # Exiting via a finally block does not pop the exception try try error("A") finally - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 end catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 end # The combined try-catch-finally form obeys the same rules as above try error("A") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 finally - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 end - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 # Errors are pushed onto the stack according to catch block nesting try error("RootCause") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 try error("B") catch - stack = catch_stack() + stack = current_exceptions() @test length(stack) == 2 - @test stack[1][1].msg == "RootCause" - @test stack[2][1].msg == "B" + @test stack[1].exception.msg == "RootCause" + @test stack[2].exception.msg == "B" end # Stack pops correctly - stack = catch_stack() + stack = current_exceptions() @test length(stack) == 1 - @test stack[1][1].msg == "RootCause" + @test stack[1].exception.msg == "RootCause" end end @@ -55,7 +54,7 @@ end val = try error("A") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 1 end @test val == 1 @@ -64,11 +63,11 @@ end try error("A") catch - length(catch_stack()) + length(current_exceptions()) end end @test test_exc_stack_tailpos() == 1 - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 end @testset "Exception stacks - early exit from try or catch" begin @@ -78,7 +77,7 @@ end try error("A") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 return end end @@ -88,7 +87,7 @@ end try error("A") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 break end end @@ -97,19 +96,19 @@ end try error("A") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 break finally - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 end end - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 for i=1:1 try error("A") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 continue end end @@ -117,38 +116,38 @@ end try error("A") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 continue finally - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 end end - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 try error("A") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 @goto outofcatch end @label outofcatch try error("A") catch - @test length(catch_stack()) == 1 + @test length(current_exceptions()) == 1 @goto outofcatch2 finally - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 end @label outofcatch2 - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 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 + @test length(current_exceptions()) == 1 function test_exc_stack_try_return() try return @@ -173,8 +172,8 @@ end catch end @label outoftry - @test length(catch_stack()) == 1 - @test catch_stack()[1][1] == ErrorException("ExceptionInOuterTry") + @test length(current_exceptions()) == 1 + @test current_exceptions()[1].exception == ErrorException("ExceptionInOuterTry") end end @@ -195,10 +194,10 @@ end # Explicit return => exception should be popped before finally block return finally - @test length(Base.catch_stack()) == 0 + @test length(Base.current_exceptions()) == 0 end end)() - @test length(Base.catch_stack()) == 0 + @test length(Base.current_exceptions()) == 0 while true try @@ -209,11 +208,11 @@ end # exception should not be popped inside finally block break finally - @test length(Base.catch_stack()) == 1 + @test length(Base.current_exceptions()) == 1 end end end - @test length(Base.catch_stack()) == 0 + @test length(Base.current_exceptions()) == 0 # Nested finally handling with `return`: each finally block should observe # only the active exceptions as according to its nesting depth. @@ -232,16 +231,16 @@ end end finally # At this point err2 is dealt with - @test length(Base.catch_stack()) == 1 - @test Base.catch_stack()[1][1] == ErrorException("err1") + @test length(Base.current_exceptions()) == 1 + @test Base.current_exceptions()[1].exception == ErrorException("err1") end end finally # At this point err1 is dealt with - @test length(Base.catch_stack()) == 0 + @test length(Base.current_exceptions()) == 0 end end)() - @test length(Base.catch_stack()) == 0 + @test length(Base.current_exceptions()) == 0 end @testset "Deep exception stacks" begin @@ -260,10 +259,10 @@ end @test try test_exc_stack_deep(100) catch - @test catch_stack()[1][1] == ErrorException("RootCause") - length(catch_stack()) + @test current_exceptions()[1].exception == ErrorException("RootCause") + length(current_exceptions()) end == 100 - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 end @testset "Exception stacks and Tasks" begin @@ -280,10 +279,10 @@ end @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") + @test length(current_exceptions()) == 1 + @test current_exceptions()[1].exception == ErrorException("A") end - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 # test rethrow() rethrows correct state bt = [] try @@ -306,7 +305,7 @@ end @test exc == ErrorException("A") @test bt == catch_backtrace() end - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 # test rethrow with argument bt = [] try @@ -328,7 +327,7 @@ end @test exc == ErrorException("C") @test bt == catch_backtrace() end - @test length(catch_stack()) == 0 + @test length(current_exceptions()) == 0 # Exception stacks on other tasks t = @task try error("A") @@ -338,7 +337,10 @@ end yield(t) @test t.state == :failed @test t.result == ErrorException("B") - @test catch_stack(t, include_bt=false) == [ErrorException("A"), ErrorException("B")] + @test current_exceptions(t, backtrace=false) == [ + (exception=ErrorException("A"),backtrace=nothing), + (exception=ErrorException("B"),backtrace=nothing) + ] # Exception stacks for tasks which never get the chance to start t = @task nothing @test (try @@ -347,12 +349,12 @@ end catch e e end).task.exception == ErrorException("expected") - @test length(catch_stack(t)) == 1 - @test length(catch_stack(t)[1][2]) > 0 # backtrace is nonempty + @test length(current_exceptions(t)) == 1 + @test length(current_exceptions(t)[1].backtrace) > 0 # backtrace is nonempty # Exception stacks should not be accessed on concurrently running tasks t = @task ()->nothing @test_throws ErrorException("Inspecting the exception stack of a task which might "* - "be running concurrently isn't allowed.") catch_stack(t) + "be running concurrently isn't allowed.") current_exceptions(t) end @testset "rethrow" begin @@ -396,5 +398,5 @@ end undef_var_in_catch() [] catch - catch_stack() + current_exceptions() end) == 2