Skip to content

Fix current_exceptions when called with backtrace=false (#757). #758

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Sep 24, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 25 additions & 5 deletions src/Compat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1101,17 +1101,37 @@ if VERSION < v"1.7.0-DEV.1106"

if VERSION >= v"1.1"
function current_exceptions(task=current_task(); backtrace=true)
stack = Base.catch_stack(task, include_bt=backtrace)
ExceptionStack(Any[(exception=x[1],backtrace=x[2]) for x in stack])
old_stack = Base.catch_stack(task, include_bt=backtrace)
# If include_bt=true, Base.catch_stack yields a Vector of two-tuples,
# where the first element of each tuple is an exception and the second
# element is the corresponding backtrace. If instead include_bt=false,
# Base.catch_stack yields a Vector of exceptions.
#
# Independent of its backtrace keyword argument, Base.current_exceptions
# yields an ExceptionStack that wraps a Vector of two-element
# NamedTuples, where the first element of each named tuple is an exception
# and the second element is either a correpsonding backtrace or `nothing`.
#
# The following constructs the ExceptionStack-wrapped Vector appropriately.
new_stack = backtrace ?
Any[(exception=exc_and_bt[1], backtrace=exc_and_bt[2]) for exc_and_bt in old_stack] :
Any[(exception=exc_only, backtrace=nothing) for exc_only in old_stack]
return ExceptionStack(new_stack)
end
else
# There's no exception stack in 1.0, but we can fall back to returning
# the (single) current exception and backtrace instead.
@eval function current_exceptions(task=current_task(); backtrace=true)
bt = catch_backtrace()
# `exc = Expr(:the_exception)` is the lowering for `catch exc`
exc = isempty(bt) ? nothing : $(Expr(:the_exception))
ExceptionStack(isempty(bt) ? Any[] : Any[(exception=exc, backtrace=bt)])
stack = if isempty(bt)
Any[]
else
# Note that `exc = Expr(:the_exception)` is the lowering for `catch exc`,
# and please see the comment in the implementation for >v1.1 regarding
# the `backtrace ? bt : nothing`.
Any[(exception=$(Expr(:the_exception)), backtrace = backtrace ? bt : nothing)]
end
return ExceptionStack(stack)
end
@eval function the_stack()
$(Expr(:the_exception)), catch_backtrace()
Expand Down
65 changes: 53 additions & 12 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1112,36 +1112,77 @@ end

# https://github.com/JuliaLang/julia/pull/29901
@testset "current_exceptions" begin
# Display of errors which cause more than one entry on the exception stack
excs = try
try
__not_a_binding__
# Helper method to retrieve an ExceptionStack that should contain two exceptions,
# each of which accompanied by a backtrace or `nothing` according to `with_backtraces`.
function _retrieve_exception_stack(;with_backtraces::Bool)
exception_stack = try
try
# Generate the first exception:
__not_a_binding__
catch
# Catch the first exception, and generate a second exception
# during what would be handling of the first exception:
1 ÷ 0
end
catch
1 ÷ 0 # Generate error while handling error
# Retrieve an ExceptionStack with both exceptions,
# and bind `exception_stack` (at the top of this block) thereto:
current_exceptions(;backtrace=with_backtraces)
end
catch
current_exceptions()
return exception_stack
end

excs_with_bts = _retrieve_exception_stack(with_backtraces = true)
excs_sans_bts = _retrieve_exception_stack(with_backtraces = false)

# Check that the ExceptionStack with backtraces contains backtraces:
BACKTRACE_TYPE = Vector{Union{Ptr{Nothing}, Base.InterpreterIP}}
@test all(exc_with_bt[2] isa BACKTRACE_TYPE for exc_with_bt in excs_with_bts)

# Check that the ExceptionStack without backtraces contains `nothing`s:
@test all(exc_sans_bt[2] isa Nothing for exc_sans_bt in excs_sans_bts)

if VERSION >= v"1.1"
@test typeof.(first.(excs)) == [UndefVarError, DivideError]
# Check that the ExceptionStacks contain the expected exception types:
@test typeof.(first.(excs_with_bts)) == [UndefVarError, DivideError]
@test typeof.(first.(excs_sans_bts)) == [UndefVarError, DivideError]

# Check that the ExceptionStack with backtraces `show`s correctly:
@test occursin(r"""
2-element ExceptionStack:
DivideError: integer division error
Stacktrace:.*

caused by: UndefVarError: __not_a_binding__ not defined
Stacktrace:.*
"""s, sprint(show, excs))
"""s, sprint(show, excs_with_bts))

# Check that the ExceptionStack without backtraces `show`s correctly:
@test occursin(r"""
2-element ExceptionStack:
DivideError: integer division error

caused by: UndefVarError: __not_a_binding__ not defined"""s,
sprint(show, excs_sans_bts))
else
# Due to runtime limitations, julia-1.0 only retains the last exception
@test typeof.(first.(excs)) == [DivideError]
# Due to runtime limitations, julia-1.0 only retains the last exception.

# Check that the ExceptionStacks contain the expected last exception type:
@test typeof.(first.(excs_with_bts)) == [DivideError]
@test typeof.(first.(excs_sans_bts)) == [DivideError]

# Check that the ExceptionStack with backtraces `show`s correctly:
@test occursin(r"""
1-element ExceptionStack:
DivideError: integer division error
Stacktrace:.*
""", sprint(show, excs))
""", sprint(show, excs_with_bts))

# Check that the ExceptionStack without backtraces `show`s correctly:
@test occursin(r"""
1-element ExceptionStack:
DivideError: integer division error""",
sprint(show, excs_sans_bts))
end
end

Expand Down