From f789b9ee2b93ef0cdf80d9ef281a6ed13767fa4e Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Wed, 28 Apr 2021 00:11:24 -0400 Subject: [PATCH 01/28] Add global to store and re-display errors in the REPL --- base/client.jl | 7 +++++++ stdlib/REPL/src/REPL.jl | 1 + 2 files changed, 8 insertions(+) diff --git a/base/client.jl b/base/client.jl index 375e41d3ee38f..e15cad1b64465 100644 --- a/base/client.jl +++ b/base/client.jl @@ -82,6 +82,12 @@ function ip_matches_func(ip, func::Symbol) return false end +struct ExceptionInfo + errors::Vector{Tuple{Any, Vector{Union{Ptr{Nothing}, Base.InterpreterIP}}}} +end + +show(io::IO, exs::ExceptionInfo) = display_error(io, exs.errors) + function scrub_repl_backtrace(bt) if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types bt = stacktrace(bt) @@ -117,6 +123,7 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) print(color_normal) end if lasterr !== nothing + ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :errs, ExceptionInfo(lasterr)) invokelatest(display_error, errio, lasterr) errcount = 0 lasterr = nothing diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 9e67ad9c2d8ab..03cb6457b1e41 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -284,6 +284,7 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, try Base.sigatomic_end() if iserr + ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :errs, ExceptionInfo(val)) Base.invokelatest(Base.display_error, errio, val) else if val !== nothing && show_value From 1a6ebe7a30cb2cfefebc76828ba8ba16f8f609eb Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Wed, 28 Apr 2021 00:34:43 -0400 Subject: [PATCH 02/28] Add test --- stdlib/REPL/test/repl.jl | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 3fbf6d8825bba..5599e2386f922 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1366,3 +1366,19 @@ end @test isempty(mods) end end + +# errs should reprint error +fake_repl() do stdin_write, stdout_read, repl + repltask = @async begin + REPL.run_repl(repl) + end + write(stdin_write, "foobar\n") + readline(stdout_read) + @test readline(stdout_read) == "\e[0mERROR: UndefVarError: foobar not defined" + @test readline(stdout_read) == "" + readuntil(stdout_read, "julia> ", keep=true) + write(stdin_write, "errs\n") + readline(stdout_read) + @test readline(stdout_read) == "\e[0mERROR: UndefVarError: foobar not defined" + @test readline(stdout_read) == "" +end From e1e0beb4bcd9e166624672a7e8065f3d1493c0a6 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Wed, 28 Apr 2021 08:19:25 -0400 Subject: [PATCH 03/28] Add MIME type --- base/client.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/client.jl b/base/client.jl index e15cad1b64465..b8c75e78cd694 100644 --- a/base/client.jl +++ b/base/client.jl @@ -86,7 +86,7 @@ struct ExceptionInfo errors::Vector{Tuple{Any, Vector{Union{Ptr{Nothing}, Base.InterpreterIP}}}} end -show(io::IO, exs::ExceptionInfo) = display_error(io, exs.errors) +show(io::IO, ::MIME"text/plain", exs::ExceptionInfo) = display_error(io, exs.errors) function scrub_repl_backtrace(bt) if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types From d2768a0183b545308f3874ed85a0c6600002c6f4 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 2 May 2021 21:52:24 -0400 Subject: [PATCH 04/28] Changed to ExceptionStack --- base/client.jl | 8 ++------ base/error.jl | 4 ++++ stdlib/REPL/src/REPL.jl | 2 +- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/base/client.jl b/base/client.jl index b8c75e78cd694..28278e6b1e7d4 100644 --- a/base/client.jl +++ b/base/client.jl @@ -82,11 +82,7 @@ function ip_matches_func(ip, func::Symbol) return false end -struct ExceptionInfo - errors::Vector{Tuple{Any, Vector{Union{Ptr{Nothing}, Base.InterpreterIP}}}} -end - -show(io::IO, ::MIME"text/plain", exs::ExceptionInfo) = display_error(io, exs.errors) +show(io::IO, ::MIME"text/plain", exs::ExceptionStack) = display_error(io, exs.errors) function scrub_repl_backtrace(bt) if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types @@ -123,7 +119,7 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) print(color_normal) end if lasterr !== nothing - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :errs, ExceptionInfo(lasterr)) + ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :errs, ExceptionStack(lasterr)) invokelatest(display_error, errio, lasterr) errcount = 0 lasterr = nothing diff --git a/base/error.jl b/base/error.jl index a1a7d1817d4c6..ad72975049e73 100644 --- a/base/error.jl +++ b/base/error.jl @@ -65,6 +65,10 @@ struct InterpreterIP mod::Union{Module,Nothing} end +struct ExceptionStack + stack::Array{Tuple{Any, Array{Union{Ptr{Nothing}, InterpreterIP}, 1}}, 1} +end + # convert dual arrays (raw bt buffer, array of GC managed values) to a single # array of locations function _reformat_bt(bt::Array{Ptr{Cvoid},1}, bt2::Array{Any,1}) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 03cb6457b1e41..44e7352ea51a2 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -284,7 +284,7 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, try Base.sigatomic_end() if iserr - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :errs, ExceptionInfo(val)) + ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :errs, ExceptionStack(val)) Base.invokelatest(Base.display_error, errio, val) else if val !== nothing && show_value From a5ccf7d55f982ff2dbf9e70b329482761c1d62a6 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 2 May 2021 22:02:21 -0400 Subject: [PATCH 05/28] Fix --- base/client.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/client.jl b/base/client.jl index 28278e6b1e7d4..e7bcb1f8c6d00 100644 --- a/base/client.jl +++ b/base/client.jl @@ -82,7 +82,7 @@ function ip_matches_func(ip, func::Symbol) return false end -show(io::IO, ::MIME"text/plain", exs::ExceptionStack) = display_error(io, exs.errors) +show(io::IO, ::MIME"text/plain", exs::ExceptionStack) = display_error(io, exs.stack) function scrub_repl_backtrace(bt) if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types From 06122027cccec83c0c3e9d49b778eb0f4c54664b Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 2 May 2021 22:02:53 -0400 Subject: [PATCH 06/28] errs => err --- base/client.jl | 2 +- stdlib/REPL/src/REPL.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/base/client.jl b/base/client.jl index e7bcb1f8c6d00..b82549295914c 100644 --- a/base/client.jl +++ b/base/client.jl @@ -119,7 +119,7 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) print(color_normal) end if lasterr !== nothing - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :errs, ExceptionStack(lasterr)) + ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, ExceptionStack(lasterr)) invokelatest(display_error, errio, lasterr) errcount = 0 lasterr = nothing diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 44e7352ea51a2..522ad79facb83 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -284,7 +284,7 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, try Base.sigatomic_end() if iserr - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :errs, ExceptionStack(val)) + ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, ExceptionStack(val)) Base.invokelatest(Base.display_error, errio, val) else if val !== nothing && show_value From dbb106009940aa28e622315188df5920ebafb665 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 2 May 2021 22:03:31 -0400 Subject: [PATCH 07/28] update test --- stdlib/REPL/test/repl.jl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 5599e2386f922..cc2abc81e3268 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1367,7 +1367,7 @@ end end end -# errs should reprint error +# err should reprint error fake_repl() do stdin_write, stdout_read, repl repltask = @async begin REPL.run_repl(repl) @@ -1377,7 +1377,7 @@ fake_repl() do stdin_write, stdout_read, repl @test readline(stdout_read) == "\e[0mERROR: UndefVarError: foobar not defined" @test readline(stdout_read) == "" readuntil(stdout_read, "julia> ", keep=true) - write(stdin_write, "errs\n") + write(stdin_write, "err\n") readline(stdout_read) @test readline(stdout_read) == "\e[0mERROR: UndefVarError: foobar not defined" @test readline(stdout_read) == "" From dcd610b64b4944106472be52046afdec61198525 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Mon, 3 May 2021 00:11:21 -0400 Subject: [PATCH 08/28] Added NEWS --- NEWS.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/NEWS.md b/NEWS.md index 9fca99a785f3a..2fd9d73214bcf 100644 --- a/NEWS.md +++ b/NEWS.md @@ -103,6 +103,8 @@ Standard library changes argument have a type more specific than `Any`; use SHIFT-TAB instead of TAB to allow any compatible methods. +* New `err` global variable in `Main` that is set when an expression throws an exception, akin to `ans`. Typing `err` reprints the exception information. + #### SparseArrays #### Dates From 5eb85818cc65d628ae20b78930027c25924bdda2 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Mon, 3 May 2021 14:16:18 -0400 Subject: [PATCH 09/28] bump --- NEWS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 2fd9d73214bcf..8e69c8ce3f878 100644 --- a/NEWS.md +++ b/NEWS.md @@ -103,7 +103,7 @@ Standard library changes argument have a type more specific than `Any`; use SHIFT-TAB instead of TAB to allow any compatible methods. -* New `err` global variable in `Main` that is set when an expression throws an exception, akin to `ans`. Typing `err` reprints the exception information. +* New `err` global variable in `Main` set when an expression throws an exception, akin to `ans`. Typing `err` reprints the exception information. #### SparseArrays From 6a3e5f49726a745c076dfe666b6de9dc6cc01e3c Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Thu, 20 May 2021 15:40:14 -0400 Subject: [PATCH 10/28] Removed unnecessary extra ExceptionStack --- base/error.jl | 4 ---- 1 file changed, 4 deletions(-) diff --git a/base/error.jl b/base/error.jl index ad72975049e73..a1a7d1817d4c6 100644 --- a/base/error.jl +++ b/base/error.jl @@ -65,10 +65,6 @@ struct InterpreterIP mod::Union{Module,Nothing} end -struct ExceptionStack - stack::Array{Tuple{Any, Array{Union{Ptr{Nothing}, InterpreterIP}, 1}}, 1} -end - # convert dual arrays (raw bt buffer, array of GC managed values) to a single # array of locations function _reformat_bt(bt::Array{Ptr{Cvoid},1}, bt2::Array{Any,1}) From 370ad13289d3ffbf92b64e38a56d363eb58b80b0 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 6 Jun 2021 00:30:59 -0400 Subject: [PATCH 11/28] Fix client, which was wrapping an ExceptionStack in itself. --- base/client.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/client.jl b/base/client.jl index b82549295914c..3ac23f911e564 100644 --- a/base/client.jl +++ b/base/client.jl @@ -119,7 +119,7 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) print(color_normal) end if lasterr !== nothing - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, ExceptionStack(lasterr)) + ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr) invokelatest(display_error, errio, lasterr) errcount = 0 lasterr = nothing From dbf0bcc17f17ddc8781cafb00a4c4f6dd43e09bf Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 6 Jun 2021 03:57:11 -0400 Subject: [PATCH 12/28] Fix REPL to remove unnecessary Exception Stack wrapping --- stdlib/REPL/src/REPL.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 522ad79facb83..f38ae9bd39ade 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -284,7 +284,7 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, try Base.sigatomic_end() if iserr - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, ExceptionStack(val)) + ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, val) Base.invokelatest(Base.display_error, errio, val) else if val !== nothing && show_value From c83f65a8998f0b6536d3d28d1a9dc9f4641f1834 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Sun, 6 Jun 2021 04:00:41 -0400 Subject: [PATCH 13/28] Remove unnecessary unwrapping of ExceptionStack --- base/client.jl | 2 -- 1 file changed, 2 deletions(-) diff --git a/base/client.jl b/base/client.jl index 3ac23f911e564..def745ce697cf 100644 --- a/base/client.jl +++ b/base/client.jl @@ -82,8 +82,6 @@ function ip_matches_func(ip, func::Symbol) return false end -show(io::IO, ::MIME"text/plain", exs::ExceptionStack) = display_error(io, exs.stack) - function scrub_repl_backtrace(bt) if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types bt = stacktrace(bt) From 33299b35f5b2df57907c01a4c6b7ea4f2f72414d Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Mon, 7 Jun 2021 11:43:55 -0400 Subject: [PATCH 14/28] Corrected test --- stdlib/REPL/test/repl.jl | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index cc2abc81e3268..ebd8890374cf7 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1379,6 +1379,7 @@ fake_repl() do stdin_write, stdout_read, repl readuntil(stdout_read, "julia> ", keep=true) write(stdin_write, "err\n") readline(stdout_read) - @test readline(stdout_read) == "\e[0mERROR: UndefVarError: foobar not defined" + @test readline(stdout_read) == "\e[0m1-element ExceptionStack:" + @test readline(stdout_read) == "ERROR: UndefVarError: foobar not defined" @test readline(stdout_read) == "" end From 47ce6e0a8be47bdffea32394f04568a1d9ba5715 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Mon, 7 Jun 2021 17:00:01 -0400 Subject: [PATCH 15/28] test fix --- stdlib/REPL/test/repl.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index ebd8890374cf7..f5d62ef83bcbf 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1381,5 +1381,5 @@ fake_repl() do stdin_write, stdout_read, repl readline(stdout_read) @test readline(stdout_read) == "\e[0m1-element ExceptionStack:" @test readline(stdout_read) == "ERROR: UndefVarError: foobar not defined" - @test readline(stdout_read) == "" + @test readline(stdout_read) == "Stacktrace:" end From 6000b8dae5cfded96aac4801427608b8fb75edb2 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Mon, 7 Jun 2021 20:00:34 -0400 Subject: [PATCH 16/28] Testing out limiting storage of an error --- base/client.jl | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/base/client.jl b/base/client.jl index def745ce697cf..c4c3e9488643a 100644 --- a/base/client.jl +++ b/base/client.jl @@ -95,12 +95,16 @@ end function display_error(io::IO, er, bt) printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) bt = scrub_repl_backtrace(bt) + istrivial = length(stack) == 1 && length(bt) ≤ 1 # frame 1 = top level + !istrivial && ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, stack) showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing) println(io) end 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 ] + istrivial = length(stack) == 1 && length(stack[1].backtrace) ≤ 1 # frame 1 = top level + !istrivial && ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, stack) show_exception_stack(IOContext(io, :limit => true), bt) println(io) end @@ -117,7 +121,6 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) print(color_normal) end if lasterr !== nothing - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr) invokelatest(display_error, errio, lasterr) errcount = 0 lasterr = nothing From cfab0ef944b55330fc1a9118cf3838e3162b15ac Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Mon, 7 Jun 2021 20:01:42 -0400 Subject: [PATCH 17/28] Moved global-setting to `display_error` --- stdlib/REPL/src/REPL.jl | 1 - 1 file changed, 1 deletion(-) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index f38ae9bd39ade..9e67ad9c2d8ab 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -284,7 +284,6 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, try Base.sigatomic_end() if iserr - ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, val) Base.invokelatest(Base.display_error, errio, val) else if val !== nothing && show_value From b33665740cdc1b1a89112dde6d2b6c4276b3ec02 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Mon, 7 Jun 2021 20:39:55 -0400 Subject: [PATCH 18/28] Test to conform with limit to storing err --- stdlib/REPL/test/repl.jl | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index f5d62ef83bcbf..5b6771ebe218f 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1367,7 +1367,7 @@ end end end -# err should reprint error +# err should reprint error if deeper than top-level fake_repl() do stdin_write, stdout_read, repl repltask = @async begin REPL.run_repl(repl) @@ -1379,6 +1379,11 @@ fake_repl() do stdin_write, stdout_read, repl readuntil(stdout_read, "julia> ", keep=true) write(stdin_write, "err\n") readline(stdout_read) + @test readline(stdout_read) == "\e[0mERROR: UndefVarError: err not defined" + readuntil(stdout_read, "julia> ", keep=true) + write(stdin_write, "foo() = foobar\n") + write(stdin_write, "foo()\n") + readline(stdout_read) @test readline(stdout_read) == "\e[0m1-element ExceptionStack:" @test readline(stdout_read) == "ERROR: UndefVarError: foobar not defined" @test readline(stdout_read) == "Stacktrace:" From 292bc37259bc745141126886a45c379537c21de5 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Thu, 10 Jun 2021 13:22:48 -0400 Subject: [PATCH 19/28] Fix error --- base/client.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/base/client.jl b/base/client.jl index c4c3e9488643a..cc332746b4482 100644 --- a/base/client.jl +++ b/base/client.jl @@ -95,6 +95,7 @@ end function display_error(io::IO, er, bt) printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) bt = scrub_repl_backtrace(bt) + stack = ExceptionStack([(exception = er, backtrace = bt)]) istrivial = length(stack) == 1 && length(bt) ≤ 1 # frame 1 = top level !istrivial && ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, stack) showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing) From 00d5f517f3473653a965a91736d198f7622bf47c Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Thu, 10 Jun 2021 15:44:39 -0400 Subject: [PATCH 20/28] Fix for saving logic and test --- base/client.jl | 6 +++--- stdlib/REPL/test/repl.jl | 2 ++ 2 files changed, 5 insertions(+), 3 deletions(-) diff --git a/base/client.jl b/base/client.jl index cc332746b4482..b3aac21b2ef7a 100644 --- a/base/client.jl +++ b/base/client.jl @@ -96,17 +96,17 @@ function display_error(io::IO, er, bt) printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) bt = scrub_repl_backtrace(bt) stack = ExceptionStack([(exception = er, backtrace = bt)]) - istrivial = length(stack) == 1 && length(bt) ≤ 1 # frame 1 = top level + istrivial = length(bt) ≤ 1 # frame 1 = top level !istrivial && ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, stack) showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing) println(io) end 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 ] + stack = ExceptionStack([(exception = x[1], backtrace = scrub_repl_backtrace(x[2])) for x in stack ]) istrivial = length(stack) == 1 && length(stack[1].backtrace) ≤ 1 # frame 1 = top level !istrivial && ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, stack) - show_exception_stack(IOContext(io, :limit => true), bt) + show_exception_stack(IOContext(io, :limit => true), stack) println(io) end display_error(stack::ExceptionStack) = display_error(stderr, stack) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 5b6771ebe218f..bcc6f928b0394 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1382,6 +1382,8 @@ fake_repl() do stdin_write, stdout_read, repl @test readline(stdout_read) == "\e[0mERROR: UndefVarError: err not defined" readuntil(stdout_read, "julia> ", keep=true) write(stdin_write, "foo() = foobar\n") + readline(stdout_read) + readuntil(stdout_read, "julia> ", keep=true) write(stdin_write, "foo()\n") readline(stdout_read) @test readline(stdout_read) == "\e[0m1-element ExceptionStack:" From 3cae5ee816df771caaf63dce956b8bbaba94c208 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Thu, 10 Jun 2021 17:04:36 -0400 Subject: [PATCH 21/28] test fixed --- stdlib/REPL/test/repl.jl | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index bcc6f928b0394..db138e55cbb4b 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -1372,21 +1372,34 @@ fake_repl() do stdin_write, stdout_read, repl repltask = @async begin REPL.run_repl(repl) end + # initialize `err` to `nothing` + write(stdin_write, "global err = nothing\n") + readline(stdout_read) + readline(stdout_read) == "\e[0m" + readuntil(stdout_read, "julia> ", keep=true) + # generate top-level error write(stdin_write, "foobar\n") readline(stdout_read) @test readline(stdout_read) == "\e[0mERROR: UndefVarError: foobar not defined" @test readline(stdout_read) == "" readuntil(stdout_read, "julia> ", keep=true) + # check that top-level error did not change `err` write(stdin_write, "err\n") readline(stdout_read) - @test readline(stdout_read) == "\e[0mERROR: UndefVarError: err not defined" + @test readline(stdout_read) == "\e[0m" readuntil(stdout_read, "julia> ", keep=true) + # generate deeper error write(stdin_write, "foo() = foobar\n") readline(stdout_read) readuntil(stdout_read, "julia> ", keep=true) write(stdin_write, "foo()\n") readline(stdout_read) + @test readline(stdout_read) == "\e[0mERROR: UndefVarError: foobar not defined" + readuntil(stdout_read, "julia> ", keep=true) + # check that deeper error did set `err` + write(stdin_write, "err\n") + readline(stdout_read) @test readline(stdout_read) == "\e[0m1-element ExceptionStack:" - @test readline(stdout_read) == "ERROR: UndefVarError: foobar not defined" + @test readline(stdout_read) == "UndefVarError: foobar not defined" @test readline(stdout_read) == "Stacktrace:" end From a46df33663726206149344846a198c87b1ea8487 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Tue, 12 Oct 2021 20:18:22 -0400 Subject: [PATCH 22/28] bump --- NEWS.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/NEWS.md b/NEWS.md index 8e69c8ce3f878..e097d86d3289d 100644 --- a/NEWS.md +++ b/NEWS.md @@ -103,7 +103,8 @@ Standard library changes argument have a type more specific than `Any`; use SHIFT-TAB instead of TAB to allow any compatible methods. -* New `err` global variable in `Main` set when an expression throws an exception, akin to `ans`. Typing `err` reprints the exception information. +* New `err` global variable in `Main` set when an expression throws an exception, akin to `ans`. Typing `err` reprints + the exception information. #### SparseArrays From 31d4bf46aa527df81eb62332004968477e824ac8 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Fri, 29 Oct 2021 01:18:30 -0400 Subject: [PATCH 23/28] Refinements --- base/client.jl | 24 ++++++++++-------------- contrib/generate_precompile.jl | 4 +--- stdlib/REPL/src/REPL.jl | 6 +++++- stdlib/REPL/test/repl.jl | 6 +++--- 4 files changed, 19 insertions(+), 21 deletions(-) diff --git a/base/client.jl b/base/client.jl index b3aac21b2ef7a..166a748bffce7 100644 --- a/base/client.jl +++ b/base/client.jl @@ -84,33 +84,26 @@ end function scrub_repl_backtrace(bt) if bt !== nothing && !(bt isa Vector{Any}) # ignore our sentinel value types - bt = stacktrace(bt) + bt = bt isa Vector{StackFrame} ? copy(bt) : stacktrace(bt) # remove REPL-related frames from interactive printing eval_ind = findlast(frame -> !frame.from_c && frame.func === :eval, bt) eval_ind === nothing || deleteat!(bt, eval_ind:length(bt)) end return bt end +scrub_repl_backtrace(stack::ExceptionStack) = + ExceptionStack([(exception = x[1], backtrace = scrub_repl_backtrace(x[2])) for x in stack]) + +istrivialerror(stack::ExceptionStack) = + length(stack) == 1 && length(stack[1].backtrace) ≤ 1 + # frame 1 = top level; assumes already went through scrub_repl_backtrace -function display_error(io::IO, er, bt) - printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) - bt = scrub_repl_backtrace(bt) - stack = ExceptionStack([(exception = er, backtrace = bt)]) - istrivial = length(bt) ≤ 1 # frame 1 = top level - !istrivial && ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, stack) - showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing) - println(io) -end function display_error(io::IO, stack::ExceptionStack) printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) - stack = ExceptionStack([(exception = x[1], backtrace = scrub_repl_backtrace(x[2])) for x in stack ]) - istrivial = length(stack) == 1 && length(stack[1].backtrace) ≤ 1 # frame 1 = top level - !istrivial && ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, stack) show_exception_stack(IOContext(io, :limit => true), stack) println(io) end 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) errcount = 0 @@ -122,6 +115,8 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) print(color_normal) end if lasterr !== nothing + lasterr = scrub_repl_backtrace(lasterr) + istrivialerror(lasterr) || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr) invokelatest(display_error, errio, lasterr) errcount = 0 lasterr = nothing @@ -149,6 +144,7 @@ function eval_user_input(errio, @nospecialize(ast), show_value::Bool) end errcount += 1 lasterr = current_exceptions() + ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr) if errcount > 2 @error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount break diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index fbc4f837ccae4..262bf564d84e2 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -32,9 +32,7 @@ precompile(Tuple{typeof(Base.recursive_prefs_merge), Base.Dict{String, Any}}) precompile(Tuple{typeof(isassigned), Core.SimpleVector, Int}) precompile(Tuple{typeof(getindex), Core.SimpleVector, Int}) precompile(Tuple{typeof(Base.Experimental.register_error_hint), Any, Type}) -precompile(Tuple{typeof(Base.display_error), MethodError, Vector{Union{Ptr{Nothing}, Base.InterpreterIP}}}) -precompile(Tuple{typeof(Base.display_error), ErrorException}) -precompile(Tuple{typeof(Base.display_error), BoundsError}) +precompile(Tuple{typeof(Base.display_error), ExceptionStack}) precompile(Tuple{Core.kwftype(typeof(Type)), NamedTuple{(:sizehint,), Tuple{Int}}, Type{IOBuffer}}) precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, String, Module)) precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, Symbol, Module)) diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 9e67ad9c2d8ab..7818104a35f74 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -284,6 +284,8 @@ function print_response(errio::IO, response, show_value::Bool, have_color::Bool, try Base.sigatomic_end() if iserr + val = Base.scrub_repl_backtrace(val) + Base.istrivialerror(val) || ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, val) Base.invokelatest(Base.display_error, errio, val) else if val !== nothing && show_value @@ -305,7 +307,9 @@ 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, current_exceptions()) + excs = current_exceptions() + ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, excs) + Base.invokelatest(Base.display_error, errio, excs) catch e # at this point, only print the name of the type as a Symbol to # minimize the possibility of further errors. diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index db138e55cbb4b..9014aad40a215 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -885,13 +885,13 @@ end # Test containers in error messages are limited #18726 let io = IOBuffer() - Base.display_error(io, - try + Base.display_error(io, Base.ExceptionStack([(exception = + (try [][trues(6000)] @assert false catch e e - end, []) + end), backtrace = [])])) @test length(String(take!(io))) < 1500 end From 4fcab0ec706649548a794dd0deb123266fd972a6 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Fri, 29 Oct 2021 01:21:59 -0400 Subject: [PATCH 24/28] restore error-error scrubbing --- base/client.jl | 2 +- stdlib/REPL/src/REPL.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/base/client.jl b/base/client.jl index 166a748bffce7..3c628675339b2 100644 --- a/base/client.jl +++ b/base/client.jl @@ -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 = current_exceptions() + lasterr = scrub_repl_backtrace(current_exceptions()) ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, lasterr) if errcount > 2 @error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount diff --git a/stdlib/REPL/src/REPL.jl b/stdlib/REPL/src/REPL.jl index 7818104a35f74..a450e2eb7c1bd 100644 --- a/stdlib/REPL/src/REPL.jl +++ b/stdlib/REPL/src/REPL.jl @@ -307,7 +307,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 - excs = current_exceptions() + excs = Base.scrub_repl_backtrace(current_exceptions()) ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :err, excs) Base.invokelatest(Base.display_error, errio, excs) catch e From fb8474c29841b059389906f0ec521777b96d4891 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Fri, 29 Oct 2021 14:26:17 -0400 Subject: [PATCH 25/28] More scrubbing, fixed precompile --- base/client.jl | 6 +++--- base/task.jl | 4 ++-- contrib/generate_precompile.jl | 2 +- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/base/client.jl b/base/client.jl index 3c628675339b2..0174ef1a23ca0 100644 --- a/base/client.jl +++ b/base/client.jl @@ -261,7 +261,7 @@ function exec_options(opts) try load_julia_startup() catch - invokelatest(display_error, current_exceptions()) + invokelatest(display_error, scrub_repl_backtrace(current_exceptions())) !(repl || is_interactive::Bool) && exit(1) end end @@ -295,7 +295,7 @@ function exec_options(opts) try include(Main, PROGRAM_FILE) catch - invokelatest(display_error, current_exceptions()) + invokelatest(display_error, scrub_repl_backtrace(current_exceptions())) if !is_interactive::Bool exit(1) end @@ -497,7 +497,7 @@ function _start() try exec_options(JLOptions()) catch - invokelatest(display_error, current_exceptions()) + invokelatest(display_error, scrub_repl_backtrace(current_exceptions())) exit(1) end if is_interactive && get(stdout, :color, false) diff --git a/base/task.jl b/base/task.jl index b25197e0aadcc..74643fb916902 100644 --- a/base/task.jl +++ b/base/task.jl @@ -446,13 +446,13 @@ function errormonitor(t::Task) try # try to display the failure atomically errio = IOContext(PipeBuffer(), errs::IO) emphasize(errio, "Unhandled Task ") - display_error(errio, current_exceptions(t)) + display_error(errio, scrub_repl_backtrace(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, current_exceptions()) + display_error(errio, scrub_repl_backtrace(current_exceptions())) write(errs, errio) flush(errs) # and then the actual error, as best we can diff --git a/contrib/generate_precompile.jl b/contrib/generate_precompile.jl index 262bf564d84e2..3c31c4c118c00 100644 --- a/contrib/generate_precompile.jl +++ b/contrib/generate_precompile.jl @@ -32,7 +32,7 @@ precompile(Tuple{typeof(Base.recursive_prefs_merge), Base.Dict{String, Any}}) precompile(Tuple{typeof(isassigned), Core.SimpleVector, Int}) precompile(Tuple{typeof(getindex), Core.SimpleVector, Int}) precompile(Tuple{typeof(Base.Experimental.register_error_hint), Any, Type}) -precompile(Tuple{typeof(Base.display_error), ExceptionStack}) +precompile(Tuple{typeof(Base.display_error), Base.ExceptionStack}) precompile(Tuple{Core.kwftype(typeof(Type)), NamedTuple{(:sizehint,), Tuple{Int}}, Type{IOBuffer}}) precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, String, Module)) precompile(Base.CoreLogging.current_logger_for_env, (Base.CoreLogging.LogLevel, Symbol, Module)) From bac0089cca68c0c1c43d5bd4ef1f42bd1b288cde Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Fri, 5 Nov 2021 16:45:40 -0400 Subject: [PATCH 26/28] Restore `display_error` with note --- base/client.jl | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/base/client.jl b/base/client.jl index 0174ef1a23ca0..4a5379f507fb7 100644 --- a/base/client.jl +++ b/base/client.jl @@ -105,6 +105,14 @@ function display_error(io::IO, stack::ExceptionStack) end display_error(stack::ExceptionStack) = display_error(stderr, stack) +# these forms are depended on by packages outside Julia +function display_error(io::IO, exception, backtrace) + printstyled(io, "ERROR: "; bold=true, color=Base.error_color()) + showerror(IOContext(io, :limit => true), er, bt, backtrace = bt!==nothing) + println(io) +end +display_error(er, bt=nothing) = display_error(stderr, er, bt) + function eval_user_input(errio, @nospecialize(ast), show_value::Bool) errcount = 0 lasterr = nothing From f54c85ce99b7fa31ab8d100b2d3e7068e0cc5dc8 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Fri, 5 Nov 2021 16:47:29 -0400 Subject: [PATCH 27/28] it's a named tuple --- base/client.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/base/client.jl b/base/client.jl index 4a5379f507fb7..226f3fa00a61b 100644 --- a/base/client.jl +++ b/base/client.jl @@ -92,7 +92,7 @@ function scrub_repl_backtrace(bt) return bt end scrub_repl_backtrace(stack::ExceptionStack) = - ExceptionStack([(exception = x[1], backtrace = scrub_repl_backtrace(x[2])) for x in stack]) + ExceptionStack([(;x.exception, backtrace = scrub_repl_backtrace(x.backtrace)) for x in stack]) istrivialerror(stack::ExceptionStack) = length(stack) == 1 && length(stack[1].backtrace) ≤ 1 From 1e47f958be1e25bad03134664f985c0d4dc86229 Mon Sep 17 00:00:00 2001 From: Nicholas Bauer Date: Fri, 5 Nov 2021 16:57:36 -0400 Subject: [PATCH 28/28] Ensure uses Vector{Any} Co-authored-by: Jameson Nash --- base/client.jl | 2 +- stdlib/REPL/test/repl.jl | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/base/client.jl b/base/client.jl index 226f3fa00a61b..67264f96fae0e 100644 --- a/base/client.jl +++ b/base/client.jl @@ -92,7 +92,7 @@ function scrub_repl_backtrace(bt) return bt end scrub_repl_backtrace(stack::ExceptionStack) = - ExceptionStack([(;x.exception, backtrace = scrub_repl_backtrace(x.backtrace)) for x in stack]) + ExceptionStack(Any[(;x.exception, backtrace = scrub_repl_backtrace(x.backtrace)) for x in stack]) istrivialerror(stack::ExceptionStack) = length(stack) == 1 && length(stack[1].backtrace) ≤ 1 diff --git a/stdlib/REPL/test/repl.jl b/stdlib/REPL/test/repl.jl index 9014aad40a215..e81b48f69f705 100644 --- a/stdlib/REPL/test/repl.jl +++ b/stdlib/REPL/test/repl.jl @@ -885,7 +885,7 @@ end # Test containers in error messages are limited #18726 let io = IOBuffer() - Base.display_error(io, Base.ExceptionStack([(exception = + Base.display_error(io, Base.ExceptionStack(Any[(exception = (try [][trues(6000)] @assert false