Skip to content
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

Display exception stacks #30900

Merged
merged 4 commits into from
Feb 2, 2019
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
61 changes: 43 additions & 18 deletions base/client.jl
Original file line number Diff line number Diff line change
Expand Up @@ -99,19 +99,44 @@ function display_error(io::IO, er, bt)
showerror(IOContext(io, :limit => true), er, bt)
println(io)
end
function display_error(io::IO, stack::Vector)
nexc = length(stack)
printstyled(io, "ERROR: "; bold=true, color=Base.error_color())
# 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.
for i = nexc:-1:1
if nexc != i
printstyled(io, "caused by [exception ", i, "]\n", color=:light_black)
end
exc,bt = stack[i]
if bt != nothing
# remove REPL-related (or other) frames
eval_ind = findlast(addr->ip_matches_func(addr, :eval), bt)
if eval_ind !== nothing
bt = bt[1:eval_ind-1]
end
showerror(io, exc, bt, backtrace=bt!==nothing)
println(io)
end
end
end
display_error(stack::Vector) = display_error(stderr, stack)
display_error(er, bt) = display_error(stderr, er, bt)
display_error(er) = display_error(er, [])

function eval_user_input(@nospecialize(ast), show_value::Bool)
errcount, lasterr, bt = 0, (), nothing
function eval_user_input(errio, @nospecialize(ast), show_value::Bool)
errcount = 0
lasterr = nothing
while true
try
if have_color
print(color_normal)
end
if errcount > 0
invokelatest(display_error, lasterr, bt)
errcount, lasterr = 0, ()
if lasterr !== nothing
invokelatest(display_error, errio, lasterr)
errcount = 0
lasterr = nothing
else
ast = Meta.lower(Main, ast)
value = Core.eval(Main, ast)
Expand All @@ -123,23 +148,23 @@ function eval_user_input(@nospecialize(ast), show_value::Bool)
try
invokelatest(display, value)
catch
println(stderr, "Evaluation succeeded, but an error occurred while showing value of type ", typeof(value), ":")
@error "Evaluation succeeded, but an error occurred while displaying the value" typeof(value)
rethrow()
end
println()
end
end
break
catch err
catch
if errcount > 0
println(stderr, "SYSTEM: show(lasterr) caused an error")
@error "SYSTEM: display_error(errio, lasterr) caused an error"
end
errcount, lasterr = errcount+1, err
errcount += 1
lasterr = catch_stack()
if errcount > 2
println(stderr, "WARNING: it is likely that something important is broken, and Julia will not be able to continue normally")
@error "It is likely that something important is broken, and Julia will not be able to continue normally" errcount
break
end
bt = catch_backtrace()
end
end
isa(stdin, TTY) && println()
Expand Down Expand Up @@ -280,8 +305,8 @@ function exec_options(opts)
end
try
include(Main, PROGRAM_FILE)
catch err
invokelatest(display_error, err, catch_backtrace())
catch
invokelatest(display_error, catch_stack())
if !is_interactive
exit(1)
end
Expand Down Expand Up @@ -390,11 +415,11 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_fil
# if we get back a list of statements, eval them sequentially
# as if we had parsed them sequentially
for stmt in ex.args
eval_user_input(stmt, true)
eval_user_input(stderr, stmt, true)
end
body = ex.args
else
eval_user_input(ex, true)
eval_user_input(stderr, ex, true)
end
else
while isopen(input) || !eof(input)
Expand All @@ -403,7 +428,7 @@ function run_main_repl(interactive::Bool, quiet::Bool, banner::Bool, history_fil
flush(stdout)
end
try
eval_user_input(parse_input_line(input), true)
eval_user_input(stderr, parse_input_line(input), true)
catch err
isa(err, InterruptException) ? print("\n\n") : rethrow()
end
Expand Down Expand Up @@ -449,8 +474,8 @@ function _start()
@eval Main import Base.MainInclude: eval, include
try
exec_options(JLOptions())
catch err
invokelatest(display_error, err, catch_backtrace())
catch
invokelatest(display_error, catch_stack())
exit(1)
end
if is_interactive && have_color
Expand Down
11 changes: 2 additions & 9 deletions base/errorshow.jl
Original file line number Diff line number Diff line change
Expand Up @@ -85,18 +85,11 @@ function showerror(io::IO, ex, bt; backtrace=true)
end

function showerror(io::IO, ex::LoadError, bt; backtrace=true)
print(io, "LoadError: ")
showerror(io, ex.error, bt, backtrace=backtrace)
print(io, "\nin expression starting at $(ex.file):$(ex.line)")
print(io, "Error while loading expression starting at ", ex.file, ":", ex.line)
end
showerror(io::IO, ex::LoadError) = showerror(io, ex, [])

function showerror(io::IO, ex::InitError, bt; backtrace=true)
print(io, "InitError: ")
showerror(io, ex.error, bt, backtrace=backtrace)
print(io, "\nduring initialization of module ", ex.mod)
end
showerror(io::IO, ex::InitError) = showerror(io, ex, [])
showerror(io::IO, ex::InitError) = print(io, "InitError during initialization of module ", ex.mod)

function showerror(io::IO, ex::DomainError, bt; backtrace=true)
if isa(ex.val, AbstractArray)
Expand Down
8 changes: 4 additions & 4 deletions src/ast.c
Original file line number Diff line number Diff line change
Expand Up @@ -888,8 +888,8 @@ jl_value_t *jl_parse_eval_all(const char *fname,
if (jl_loaderror_type == NULL)
jl_rethrow();
else
jl_rethrow_other(jl_new_struct(jl_loaderror_type, form, result,
jl_current_exception()));
jl_throw(jl_new_struct(jl_loaderror_type, form, result,
jl_current_exception()));
}
JL_GC_POP();
return result;
Expand Down Expand Up @@ -1046,8 +1046,8 @@ static jl_value_t *jl_invoke_julia_macro(jl_array_t *args, jl_module_t *inmodule
else
margs[0] = jl_cstr_to_string("<macrocall>");
margs[1] = jl_fieldref(lno, 0); // extract and allocate line number
jl_rethrow_other(jl_new_struct(jl_loaderror_type, margs[0], margs[1],
jl_current_exception()));
jl_throw(jl_new_struct(jl_loaderror_type, margs[0], margs[1],
jl_current_exception()));
}
}
ptls->world_age = last_age;
Expand Down
4 changes: 2 additions & 2 deletions src/toplevel.c
Original file line number Diff line number Diff line change
Expand Up @@ -78,8 +78,8 @@ void jl_module_run_initializer(jl_module_t *m)
jl_rethrow();
}
else {
jl_rethrow_other(jl_new_struct(jl_initerror_type, m->name,
jl_current_exception()));
jl_throw(jl_new_struct(jl_initerror_type, m->name,
jl_current_exception()));
}
}
}
Expand Down
92 changes: 39 additions & 53 deletions stdlib/REPL/src/REPL.jl
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import Base:
display,
show,
AnyDict,
==
==,
catch_stack


include("Terminals.jl")
Expand Down Expand Up @@ -61,7 +62,7 @@ const JULIA_PROMPT = "julia> "
mutable struct REPLBackend
"channel for AST"
repl_channel::Channel
"channel for results: (value, nothing) or (error, backtrace)"
"channel for results: (value, iserror)"
response_channel::Channel
"flag indicating the state of this backend"
in_eval::Bool
Expand All @@ -73,28 +74,28 @@ mutable struct REPLBackend
end

function eval_user_input(@nospecialize(ast), backend::REPLBackend)
iserr, lasterr = false, ((), nothing)
lasterr = nothing
Base.sigatomic_begin()
while true
try
Base.sigatomic_end()
if iserr
put!(backend.response_channel, lasterr)
if lasterr !== nothing
put!(backend.response_channel, (lasterr,true))
else
backend.in_eval = true
value = Core.eval(Main, ast)
backend.in_eval = false
# note: use jl_set_global to make sure value isn't passed through `expand`
ccall(:jl_set_global, Cvoid, (Any, Any, Any), Main, :ans, value)
put!(backend.response_channel, (value, nothing))
put!(backend.response_channel, (value,false))
end
break
catch err
if iserr
if lasterr !== nothing
println("SYSTEM ERROR: Failed to report error to REPL frontend")
println(err)
end
iserr, lasterr = true, (err, catch_backtrace())
lasterr = catch_stack()
end
end
Base.sigatomic_end()
Expand Down Expand Up @@ -134,20 +135,20 @@ function display(d::REPLDisplay, mime::MIME"text/plain", x)
end
display(d::REPLDisplay, x) = display(d, MIME("text/plain"), x)

function print_response(repl::AbstractREPL, @nospecialize(val), bt, show_value::Bool, have_color::Bool)
repl.waserror = bt !== nothing
function print_response(repl::AbstractREPL, @nospecialize(response), show_value::Bool, have_color::Bool)
repl.waserror = response[2]
io = IOContext(outstream(repl), :module => Main)
print_response(io, val, bt, show_value, have_color, specialdisplay(repl))
print_response(io, response, show_value, have_color, specialdisplay(repl))
nothing
end
function print_response(errio::IO, @nospecialize(val), bt, show_value::Bool, have_color::Bool, specialdisplay=nothing)
function print_response(errio::IO, @nospecialize(response), show_value::Bool, have_color::Bool, specialdisplay=nothing)
Base.sigatomic_begin()
val, iserr = response
while true
try
Base.sigatomic_end()
if bt !== nothing
Base.invokelatest(Base.display_error, errio, val, bt)
iserr, lasterr = false, ()
if iserr
Base.invokelatest(Base.display_error, errio, val)
else
if val !== nothing && show_value
try
Expand All @@ -163,15 +164,14 @@ function print_response(errio::IO, @nospecialize(val), bt, show_value::Bool, hav
end
end
break
catch err
if bt !== nothing
println(errio, "SYSTEM: show(lasterr) caused an error")
println(errio, err)
Base.show_backtrace(errio, bt)
catch
if iserr
println(errio, "SYSTEM (REPL): showing an error caused an error")
println(errio, catch_stack())
break
end
val = err
bt = catch_backtrace()
val = catch_stack()
iserr = true
end
end
Base.sigatomic_end()
Expand Down Expand Up @@ -207,7 +207,6 @@ function run_frontend(repl::BasicREPL, backend::REPLBackendRef)
d = REPLDisplay(repl)
dopushdisplay = !in(d,Base.Multimedia.displays)
dopushdisplay && pushdisplay(d)
repl_channel, response_channel = backend.repl_channel, backend.response_channel
hit_eof = false
while true
Base.reseteof(repl.terminal)
Expand Down Expand Up @@ -238,17 +237,14 @@ function run_frontend(repl::BasicREPL, backend::REPLBackendRef)
(isa(ast,Expr) && ast.head == :incomplete) || break
end
if !isempty(line)
put!(repl_channel, (ast, 1))
val, bt = take!(response_channel)
if !ends_with_semicolon(line)
print_response(repl, val, bt, true, false)
end
response = eval_with_backend(ast, backend)
print_response(repl, response, !ends_with_semicolon(line), false)
end
write(repl.terminal, '\n')
((!interrupted && isempty(line)) || hit_eof) && break
end
# terminate backend
put!(repl_channel, (nothing, -1))
put!(backend.repl_channel, (nothing, -1))
dopushdisplay && popdisplay(d)
nothing
end
Expand Down Expand Up @@ -682,12 +678,9 @@ find_hist_file() = get(ENV, "JULIA_HISTORY",

backend(r::AbstractREPL) = r.backendref

send_to_backend(ast, backend::REPLBackendRef) =
send_to_backend(ast, backend.repl_channel, backend.response_channel)

function send_to_backend(ast, req, rep)
put!(req, (ast, 1))
return take!(rep) # (val, bt)
function eval_with_backend(ast, backend::REPLBackendRef)
put!(backend.repl_channel, (ast, 1))
take!(backend.response_channel) # (val, iserr)
end

function respond(f, repl, main; pass_empty = false)
Expand All @@ -698,17 +691,14 @@ function respond(f, repl, main; pass_empty = false)
line = String(take!(buf))
if !isempty(line) || pass_empty
reset(repl)
local val, bt
local response
try
response = Base.invokelatest(f, line)
val, bt = send_to_backend(response, backend(repl))
catch err
val = err
bt = catch_backtrace()
end
if !ends_with_semicolon(line) || bt !== nothing
print_response(repl, val, bt, true, Base.have_color)
ast = Base.invokelatest(f, line)
response = eval_with_backend(ast, backend(repl))
catch
response = (catch_stack(), true)
end
print_response(repl, response, !ends_with_semicolon(line), Base.have_color)
end
prepare_next(repl)
reset_state(s)
Expand Down Expand Up @@ -848,8 +838,8 @@ function setup_interface(
close(f)
end
hist_from_file(hp, f, hist_path)
catch e
print_response(repl, e, catch_backtrace(), true, Base.have_color)
catch
print_response(repl, (catch_stack(),true), true, Base.have_color)
println(outstream(repl))
@info "Disabling history file for this session"
repl.history_file = false
Expand Down Expand Up @@ -1110,7 +1100,6 @@ function run_frontend(repl::StreamREPL, backend::REPLBackendRef)
d = REPLDisplay(repl)
dopushdisplay = !in(d,Base.Multimedia.displays)
dopushdisplay && pushdisplay(d)
repl_channel, response_channel = backend.repl_channel, backend.response_channel
while !eof(repl.stream)
if have_color
print(repl.stream,repl.prompt_color)
Expand All @@ -1125,15 +1114,12 @@ function run_frontend(repl::StreamREPL, backend::REPLBackendRef)
if have_color
print(repl.stream, Base.color_normal)
end
put!(repl_channel, (ast, 1))
val, bt = take!(response_channel)
if !ends_with_semicolon(line)
print_response(repl, val, bt, true, have_color)
end
response = eval_with_backend(ast, backend)
print_response(repl, response, !ends_with_semicolon(line), have_color)
end
end
# Terminate Backend
put!(repl_channel, (nothing, -1))
put!(backend.repl_channel, (nothing, -1))
dopushdisplay && popdisplay(d)
nothing
end
Expand Down
3 changes: 2 additions & 1 deletion stdlib/REPL/test/repl.jl
Original file line number Diff line number Diff line change
Expand Up @@ -759,7 +759,8 @@ mutable struct Error19864 <: Exception; end
function test19864()
@eval Base.showerror(io::IO, e::Error19864) = print(io, "correct19864")
buf = IOBuffer()
REPL.print_response(buf, Error19864(), [], false, false, nothing)
fake_response = (Any[(Error19864(),[])],true)
REPL.print_response(buf, fake_response, false, false, nothing)
return String(take!(buf))
end
@test occursin("correct19864", test19864())
Expand Down
Loading