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

WIP/RFC: print stacktrace when eval fails #160

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from

Conversation

mortenpi
Copy link
Contributor

@mortenpi mortenpi commented Jul 9, 2021

Currently, suppose I have a file like

function foo(x)
  error("error on line $(@__LINE__)")
end

#-

foo(42)

I get an error which does not actually show a stacktrace of where the exception was raised

julia> Literate.notebook("stacktrace.jl")
[ Info: generating notebook from `~/Julia/Literate/stacktrace.jl`
[ Info: executing notebook `stacktrace.ipynb`
┌ Error: error when executing notebook based on input file: `~/Julia/Literate/stacktrace.jl`
└ @ Literate ~/Julia/Literate/src/Literate.jl:667
ERROR: LoadError: error on line 2
in expression starting at string:1
when executing the following code block in file `~/Julia/Literate/stacktrace.jl`

```julia
foo(42)
```

Stacktrace:
 [1] error(s::String)
   @ Base ./error.jl:33
 [2] execute_block(sb::Module, block::String; inputfile::String)
   @ Literate ~/Julia/Literate/src/Literate.jl:785
 [3] execute_notebook(nb::Dict{Any, Any}; inputfile::String)
   @ Literate ~/Julia/Literate/src/Literate.jl:683
 [4] (::Literate.var"#36#38"{Dict{String, Any}})()
   @ Literate ~/Julia/Literate/src/Literate.jl:664
 [5] cd(f::Literate.var"#36#38"{Dict{String, Any}}, dir::String)
   @ Base.Filesystem ./file.jl:106
 [6] jupyter_notebook(chunks::Vector{Literate.Chunk}, config::Dict{String, Any})
   @ Literate ~/Julia/Literate/src/Literate.jl:663
 [7] notebook(inputfile::String, outputdir::String; config::Dict{Any, Any}, kwargs::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ Literate ~/Julia/Literate/src/Literate.jl:600
 [8] notebook (repeats 2 times)
   @ ~/Julia/Literate/src/Literate.jl:596 [inlined]
 [9] top-level scope
   @ REPL[6]:1

The stacktrace traces back to this error call:

Literate.jl/src/Literate.jl

Lines 785 to 792 in 42f3941

error("""
$(sprint(showerror, c.value))
when executing the following code block in file `$(Base.contractuser(inputfile))`
```julia
$block
```
""")

With this patch you get something like the following (which could be much longer if the error thrown is deep in some dependency, which is the information you would want):

julia> Literate.notebook("stacktrace.jl")
[ Info: generating notebook from `~/Julia/Literate/stacktrace.jl`
[ Info: executing notebook `stacktrace.ipynb`
┌ Error: error when executing notebook based on input file: `~/Julia/Literate/stacktrace.jl`
└ @ Literate ~/Julia/Literate/src/Literate.jl:667
ERROR: Literate.EvalException: LoadError when executing code in: ~/Julia/Literate/stacktrace.jl
LoadError: error on line 2
Stacktrace:
  [1] error(s::String)
    @ Base ./error.jl:33
  [2] foo(x::Int64)
    @ Main.##257 ./string:2
  [3] top-level scope
    @ string:1
  [4] eval
    @ ./boot.jl:360 [inlined]
  [5] include_string(mapexpr::typeof(identity), mod::Module, code::String, filename::String)
    @ Base ./loading.jl:1094
  [6] include_string (repeats 2 times)
    @ ./loading.jl:1104 [inlined]
  [7] #42
    @ ~/Julia/Literate/src/Literate.jl:781 [inlined]
  [8] (::IOCapture.var"#3#5"{Core.TypeofBottom, Literate.var"#42#43"{Module, String}, Task, Pipe, Base.TTY, Base.TTY})()
    @ IOCapture ~/.julia/packages/IOCapture/g8FZl/src/IOCapture.jl:105
  [9] with_logstate(f::Function, logstate::Any)
    @ Base.CoreLogging ./logging.jl:491
 [10] with_logger
    @ ./logging.jl:603 [inlined]
 [11] capture(f::Literate.var"#42#43"{Module, String}; rethrow::Type, color::Bool)
    @ IOCapture ~/.julia/packages/IOCapture/g8FZl/src/IOCapture.jl:103
 [12] execute_block(sb::Module, block::String; inputfile::String)
    @ Literate ~/Julia/Literate/src/Literate.jl:780
in expression starting at string:1

Stacktrace:
 [1] execute_block(sb::Module, block::String; inputfile::String)
   @ Literate ~/Julia/Literate/src/Literate.jl:801
 [2] execute_notebook(nb::Dict{Any, Any}; inputfile::String)
   @ Literate ~/Julia/Literate/src/Literate.jl:683
 [3] (::Literate.var"#36#38"{Dict{String, Any}})()
   @ Literate ~/Julia/Literate/src/Literate.jl:664
 [4] cd(f::Literate.var"#36#38"{Dict{String, Any}}, dir::String)
   @ Base.Filesystem ./file.jl:106
 [5] jupyter_notebook(chunks::Vector{Literate.Chunk}, config::Dict{String, Any})
   @ Literate ~/Julia/Literate/src/Literate.jl:663
 [6] notebook(inputfile::String, outputdir::String; config::Dict{Any, Any}, kwargs::Base.Iterators.Pairs{Union{}, Union{}, Tuple{}, NamedTuple{(), Tuple{}}})
   @ Literate ~/Julia/Literate/src/Literate.jl:600
 [7] notebook (repeats 2 times)
   @ ~/Julia/Literate/src/Literate.jl:596 [inlined]
 [8] top-level scope
   @ REPL[2]:1

Now, I don't yet quite have a good idea of how to do this well, so opening this for discussion. This was just a side tangent when working on a notebook. A few notes though:

  • The simple option (least amount of code modified) would actually be to sprintf the backtrace into error. However, returning a custom exception object seems like the more correct thing to do.
  • The @error from the parent function seems actually redundant.
  • I noticed that by using include_string, any error I think gets wrapped in LoadError (but this does not seem to be documented). It also adds a bunch of things into the stacktrace which are hard to get rid of.
  • It's sorta confusing that we print two stacktraces.. but it's sorta the right thing to do. Unless we can somehow do some rethrow-type magic..

Comment on lines +785 to +800
# if c.value isa LoadError
# @info "..." c.value.file c.value.line
# bt = remove_common_backtrace(c.backtrace, backtrace())
# st = stacktrace(bt)
# @assert length(bt) >= length(st)
# idx = findlast(sf -> sf.func === :eval, st)
# display(st)
# @show idx st[idx]
# bt_ptr = Ptr{Nothing}(st[idx-1].pointer)
# idx = findfirst(isequal(bt_ptr), bt)
# @show idx
# display(stacktrace(bt[1:idx]))
# throw(EvalException(Base.contractuser(inputfile), block, c.value.error, bt[1:idx]))
# else
# throw(EvalException(Base.contractuser(inputfile), block, c.value, remove_common_backtrace(c.backtrace, backtrace())))
# end
Copy link
Contributor Author

Choose a reason for hiding this comment

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

This was a naive attempt the get rid of the stackframes introduced by include_string, but it didn't quite work right.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

1 participant