diff --git a/src/exception.jl b/src/exception.jl index 1b750fd3..8e706600 100644 --- a/src/exception.jl +++ b/src/exception.jl @@ -123,11 +123,51 @@ function pyexc_initialize() pyexc[PyIOError] = @pyglobalobjptr :PyExc_IOError end +""" + 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} + try + io = IOBuffer() + showerror(io, e) + return String(take!(io)) + catch + try + return """ + $e + ERROR: showerror(::IO, ::$T) failed!""" + catch + try + return """ + $T + ERROR: showerror(::IO, ::$T) failed! + ERROR: string(::$T) failed!""" + catch + name = try + io = IOBuffer() + Base.show_datatype(io, T) + String(take!(io)) + catch + "_UNKNOWN_TYPE_" + end + return """ + Unprintable error. + ERROR: showerror(::IO, ::$name) failed! + ERROR: string(::$name) failed! + ERROR: string($name) failed!""" + end + end + end +end + function pyraise(e) eT = typeof(e) pyeT = haskey(pyexc::Dict, eT) ? pyexc[eT] : pyexc[Exception] ccall((@pysym :PyErr_SetString), Cvoid, (PyPtr, Cstring), - pyeT, string("Julia exception: ", e)) + pyeT, string("Julia exception: ", showerror_string(e))) end function pyraise(e::PyError) diff --git a/src/pytype.jl b/src/pytype.jl index a05d5f8c..058cc713 100644 --- a/src/pytype.jl +++ b/src/pytype.jl @@ -337,9 +337,15 @@ end unsafe_pyjlwrap_to_objref(o::PyPtr) = unsafe_pointer_to_objref(unsafe_load(convert(Ptr{Ptr{Cvoid}}, o), 3)) -pyjlwrap_repr(o::PyPtr) = - pystealref!(PyObject(o != C_NULL ? string("") - : "")) +function pyjlwrap_repr(o::PyPtr) + try + return pyreturn(o != C_NULL ? string("") + : "") + catch e + pyraise(e) + return PyPtr_NULL + end +end function pyjlwrap_hash(o::PyPtr) h = hash(unsafe_pyjlwrap_to_objref(o)) diff --git a/test/runtests.jl b/test/runtests.jl index f5cf21d5..a1951214 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -637,4 +637,33 @@ end @test anonymous.sys != PyNULL() end +struct Unprintable end +Base.show(::IO, ::Unprintable) = error("show(::IO, ::Unprintable) called") +Base.show(::IO, ::Type{Unprintable}) = error("show(::IO, ::Type{Unprintable}) called") + +py""" +def try_repr(x): + try: + return repr(x) + except Exception as err: + return err +""" + +py""" +def try_call(f): + try: + return f() + except Exception as err: + return err +""" + +@testset "throwing show" begin + unp = Unprintable() + @test_throws Exception show(unp) + @test py"try_repr"("printable") isa String + @test pyisinstance(py"try_repr"(unp), pybuiltin("Exception")) + @test pyisinstance(py"try_call"(() -> throw(Unprintable())), + pybuiltin("Exception")) +end + include("test_pyfncall.jl")