Skip to content

Commit

Permalink
Allow pyraise to include backtrace from Julia
Browse files Browse the repository at this point in the history
Instead of using `catch_backtrace` in `showerror_string`, which may not always
be called from a catch block with a current exception set, I have added an
optional argument to `pyraise` which can be used to pass backtraces to
`showerror_string`.

Backtraces are only used if exception is not a PyError exception. By default, no
backtrace is shown.

All try-catch blocks in PyCall attempt to pass the backtrace for the current
exception to `pyraise`.
  • Loading branch information
galenlynch committed Nov 4, 2018
1 parent 4169c63 commit 2040dd9
Show file tree
Hide file tree
Showing 4 changed files with 18 additions and 11 deletions.
2 changes: 1 addition & 1 deletion src/callback.jl
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ function _pyjlwrap_call(f, args_::PyPtr, kw_::PyPtr)

return pyreturn(ret)
catch e
pyraise(e)
pyraise(e, catch_backtrace())
finally
args.o = PyPtr_NULL # don't decref
end
Expand Down
17 changes: 12 additions & 5 deletions src/exception.jl
Original file line number Diff line number Diff line change
Expand Up @@ -123,16 +123,21 @@ function pyexc_initialize()
pyexc[PyIOError] = @pyglobalobjptr :PyExc_IOError
end

_showerror_string(io::IO, e, ::Nothing) = showerror(io, e)
_showerror_string(io::IO, e, bt) = showerror(io, e, bt)

# bt argument defaults to nothing, to delay dispatching on the presence of a
# backtrace until after the try-catch block
"""
showerror_string(e) :: String
Convert output of `showerror` to a `String`. Since this function may
be called via Python C-API, it tries to not throw at all cost.
"""
function showerror_string(e::T) where {T}
function showerror_string(e::T, bt = nothing) where {T}
try
io = IOBuffer()
showerror(io, e, catch_backtrace())
_showerror_string(io, e, bt)
return String(take!(io))
catch
try
Expand Down Expand Up @@ -163,14 +168,16 @@ function showerror_string(e::T) where {T}
end
end

function pyraise(e)
# If no backtrace is passed pyraise,
function pyraise(e, bt = nothing)
eT = typeof(e)
pyeT = haskey(pyexc::Dict, eT) ? pyexc[eT] : pyexc[Exception]
ccall((@pysym :PyErr_SetString), Cvoid, (PyPtr, Cstring),
pyeT, string("Julia exception: ", showerror_string(e)))
pyeT, string("Julia exception: ", showerror_string(e, bt)))
end

function pyraise(e::PyError)
# Second argument allows for backtraces passed to `pyraise` to be ignored.
function pyraise(e::PyError, ::Vector = [])
ccall((@pysym :PyErr_Restore), Cvoid, (PyPtr, PyPtr, PyPtr),
e.T, e.val, e.traceback)
e.T.o = e.val.o = e.traceback.o = C_NULL # refs were stolen
Expand Down
6 changes: 3 additions & 3 deletions src/pyiterator.jl
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ const jlWrapIteratorType = PyTypeObject()
return pyreturn(item)
end
catch e
pyraise(e)
pyraise(e, catch_backtrace())
end
return PyPtr_NULL
end
Expand All @@ -83,7 +83,7 @@ else
return pyreturn(item)
end
catch e
pyraise(e)
pyraise(e, catch_backtrace())
end
return PyPtr_NULL
end
Expand All @@ -96,7 +96,7 @@ function pyjlwrap_getiter(self_::PyPtr)
self = unsafe_pyjlwrap_to_objref(self_)
return pystealref!(jlwrap_iterator(self))
catch e
pyraise(e)
pyraise(e, catch_backtrace())
end
return PyPtr_NULL
end
Expand Down
4 changes: 2 additions & 2 deletions src/pytype.jl
Original file line number Diff line number Diff line change
Expand Up @@ -342,7 +342,7 @@ function pyjlwrap_repr(o::PyPtr)
return pyreturn(o != C_NULL ? string("<PyCall.jlwrap ",unsafe_pyjlwrap_to_objref(o),">")
: "<PyCall.jlwrap NULL>")
catch e
pyraise(e)
pyraise(e, catch_backtrace())
return PyPtr_NULL
end
end
Expand Down Expand Up @@ -389,7 +389,7 @@ function pyjlwrap_getattr(self_::PyPtr, attr__::PyPtr)
end
end
catch e
pyraise(e)
pyraise(e, catch_backtrace())
finally
attr_.o = PyPtr_NULL # don't decref
end
Expand Down

0 comments on commit 2040dd9

Please sign in to comment.