diff --git a/src/callback.jl b/src/callback.jl index 55d84936..332ba9c0 100644 --- a/src/callback.jl +++ b/src/callback.jl @@ -39,7 +39,7 @@ function _pyjlwrap_call(f, args_::PyPtr, kw_::PyPtr) return pyreturn(ret) catch e - pyraise(e) + @pyraise e finally args.o = PyPtr_NULL # don't decref end diff --git a/src/exception.jl b/src/exception.jl index 8e706600..ffb7c51d 100644 --- a/src/exception.jl +++ b/src/exception.jl @@ -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) + _showerror_string(io, e, bt) return String(take!(io)) catch try @@ -163,15 +168,26 @@ function showerror_string(e::T) where {T} end end -function pyraise(e) +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 end + +""" + @pyraise e + +Throw the exception `e` to Python, attaching a backtrace. This macro should only be +used in a `catch` block so that `catch_backtrace()` is valid. +""" +macro pyraise(e) + :(pyraise($(esc(e)), catch_backtrace())) +end diff --git a/src/io.jl b/src/io.jl index 91c28625..117f3a2c 100644 --- a/src/io.jl +++ b/src/io.jl @@ -8,13 +8,13 @@ # IOBase methods: # IO objects should raise IOError for unsupported operations or failed IO -function ioraise(e) +function ioraise(e, bt = nothing) if isa(e, MethodError) || isa(e, ErrorException) ccall((@pysym :PyErr_SetString), Cvoid, (PyPtr, Cstring), (pyexc::Dict)[PyIOError], - string("Julia exception: ", e)) + showerror_string(e, bt)) else - pyraise(e) + pyraise(e, bt) end end @@ -22,7 +22,7 @@ macro with_ioraise(expr) :(try $(esc(expr)) catch e - ioraise(e) + ioraise(e, catch_backtrace()) end) end diff --git a/src/pyiterator.jl b/src/pyiterator.jl index 5b4e44d6..8cfbc1c9 100644 --- a/src/pyiterator.jl +++ b/src/pyiterator.jl @@ -134,7 +134,7 @@ const jlWrapIteratorType = PyTypeObject() return pyreturn(item) end catch e - pyraise(e) + @pyraise e end return PyPtr_NULL end @@ -149,7 +149,7 @@ else return pyreturn(item) end catch e - pyraise(e) + @pyraise e end return PyPtr_NULL end @@ -162,7 +162,7 @@ function pyjlwrap_getiter(self_::PyPtr) self = unsafe_pyjlwrap_to_objref(self_) return pystealref!(jlwrap_iterator(self)) catch e - pyraise(e) + @pyraise e end return PyPtr_NULL end diff --git a/src/pytype.jl b/src/pytype.jl index 387bc56f..f5fdeaa1 100644 --- a/src/pytype.jl +++ b/src/pytype.jl @@ -348,7 +348,7 @@ function pyjlwrap_repr(o::PyPtr) return pyreturn(o != C_NULL ? string("") : "") catch e - pyraise(e) + @pyraise e return PyPtr_NULL end end @@ -395,7 +395,7 @@ function pyjlwrap_getattr(self_::PyPtr, attr__::PyPtr) end end catch e - pyraise(e) + @pyraise e finally attr_.o = PyPtr_NULL # don't decref end