Skip to content

Commit

Permalink
Port Base.current_exceptions() to older Julia versions (#746)
Browse files Browse the repository at this point in the history
* Port Base.current_exceptions() to older Julia versions

See JuliaLang/julia#29901

This is limited to julia-1.1 and above because earlier versions don't
have the necessary runtime library support (Base.catch_stack() etc).

* Also implement current_exceptions on julia-1.0

* Tweak readme to mention Julia-1.0 limitations
  • Loading branch information
c42f authored Aug 19, 2021
1 parent ad9a668 commit d79d0fc
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 1 deletion.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
name = "Compat"
uuid = "34da2185-b29b-5c13-b0c7-acf172513d20"
version = "3.33.0"
version = "3.34.0"

[deps]
Base64 = "2a0f44e3-6c83-55bd-87e4-b1978d98bd5f"
Expand Down
5 changes: 5 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,11 @@ changes in `julia`.

## Supported features

* The function `current_exceptions()` has been added to get the current
exception stack. Julia-1.0 lacks runtime support for full execption stacks,
so we return only the most recent exception in that case. ([#29901]) (since
Compat 3.34)

* The methods `Base.include(::Function, ::Module, path)` and
`Base.include_string(::Function, ::Module, code[, filename])` have been added. ([#34595]) (since Compat 3.33)

Expand Down
53 changes: 53 additions & 0 deletions src/Compat.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1049,6 +1049,59 @@ if VERSION < v"1.5.0-DEV.263"
end
end

# https://github.com/JuliaLang/julia/pull/29901
if VERSION < v"1.7.0-DEV.1106"
struct ExceptionStack <: AbstractArray{Any,1}
stack
end

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])
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)])
end
@eval function the_stack()
$(Expr(:the_exception)), catch_backtrace()
end
end

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

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.
nexc = length(stack)
for i = nexc:-1:1
if nexc != i
printstyled(io, "\ncaused by: ", color=Base.error_color())
end
exc, bt = stack[i]
showerror(io, exc, bt, backtrace = bt!==nothing)
i == 1 || println(io)
end
end

function Base.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
Base.show(io::IO, stack::ExceptionStack) = show(io, MIME("text/plain"), stack)

export current_exceptions
end

include("iterators.jl")
include("deprecated.jl")

Expand Down
35 changes: 35 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -1109,3 +1109,38 @@ end
@test m.y === 3
@test !isdefined(m, :x)
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__
catch
1 ÷ 0 # Generate error while handling error
end
catch
current_exceptions()
end

if VERSION >= v"1.1"
@test typeof.(first.(excs)) == [UndefVarError, DivideError]

@test occursin(r"""
2-element ExceptionStack:
DivideError: integer division error
Stacktrace:.*
caused by: UndefVarError: __not_a_binding__ not defined
Stacktrace:.*
"""s, sprint(show, excs))
else
# Due to runtime limitations, julia-1.0 only retains the last exception
@test typeof.(first.(excs)) == [DivideError]
@test occursin(r"""
1-element ExceptionStack:
DivideError: integer division error
Stacktrace:.*
""", sprint(show, excs))
end
end

2 comments on commit d79d0fc

@martinholters
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@JuliaRegistrator
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Registration pull request created: JuliaRegistries/General/43136

After the above pull request is merged, it is recommended that a tag is created on this repository for the registered package version.

This will be done automatically if the Julia TagBot GitHub Action is installed, or can be done manually through the github interface, or via:

git tag -a v3.34.0 -m "<description of version>" d79d0fc9881e68dd65448c0d2892a5495c18e380
git push origin v3.34.0

Please sign in to comment.