Skip to content

Commit

Permalink
Safer pyjlwrap (#583)
Browse files Browse the repository at this point in the history
* Make pyjlwrap_repr safe

Without this change, following script using PyJulia kills the whole
process with "fatal: error thrown and no exception handler available."
printed from libjulia runtime:

    from julia import Main
    Spam = Main.eval("""
    struct Spam end
    function Base.show(io::IO, ::Spam)
         error("show(::IO, ::Spam) called")
    end
    Spam
    """)
    print(Spam())

* Make pyraise (hence pyjlwrap_call) safe
  • Loading branch information
tkf authored and stevengj committed Oct 11, 2018
1 parent fb88f4d commit 0bdcc10
Show file tree
Hide file tree
Showing 3 changed files with 79 additions and 4 deletions.
42 changes: 41 additions & 1 deletion src/exception.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
12 changes: 9 additions & 3 deletions src/pytype.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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("<PyCall.jlwrap ",unsafe_pyjlwrap_to_objref(o),">")
: "<PyCall.jlwrap NULL>"))
function pyjlwrap_repr(o::PyPtr)
try
return pyreturn(o != C_NULL ? string("<PyCall.jlwrap ",unsafe_pyjlwrap_to_objref(o),">")
: "<PyCall.jlwrap NULL>")
catch e
pyraise(e)
return PyPtr_NULL
end
end

function pyjlwrap_hash(o::PyPtr)
h = hash(unsafe_pyjlwrap_to_objref(o))
Expand Down
29 changes: 29 additions & 0 deletions test/runtests.jl
Original file line number Diff line number Diff line change
Expand Up @@ -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")

0 comments on commit 0bdcc10

Please sign in to comment.