Skip to content

Commit 0bdcc10

Browse files
tkfstevengj
authored andcommitted
Safer pyjlwrap (#583)
* 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
1 parent fb88f4d commit 0bdcc10

File tree

3 files changed

+79
-4
lines changed

3 files changed

+79
-4
lines changed

src/exception.jl

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,11 +123,51 @@ function pyexc_initialize()
123123
pyexc[PyIOError] = @pyglobalobjptr :PyExc_IOError
124124
end
125125

126+
"""
127+
showerror_string(e) :: String
128+
129+
Convert output of `showerror` to a `String`. Since this function may
130+
be called via Python C-API, it tries to not throw at all cost.
131+
"""
132+
function showerror_string(e::T) where {T}
133+
try
134+
io = IOBuffer()
135+
showerror(io, e)
136+
return String(take!(io))
137+
catch
138+
try
139+
return """
140+
$e
141+
ERROR: showerror(::IO, ::$T) failed!"""
142+
catch
143+
try
144+
return """
145+
$T
146+
ERROR: showerror(::IO, ::$T) failed!
147+
ERROR: string(::$T) failed!"""
148+
catch
149+
name = try
150+
io = IOBuffer()
151+
Base.show_datatype(io, T)
152+
String(take!(io))
153+
catch
154+
"_UNKNOWN_TYPE_"
155+
end
156+
return """
157+
Unprintable error.
158+
ERROR: showerror(::IO, ::$name) failed!
159+
ERROR: string(::$name) failed!
160+
ERROR: string($name) failed!"""
161+
end
162+
end
163+
end
164+
end
165+
126166
function pyraise(e)
127167
eT = typeof(e)
128168
pyeT = haskey(pyexc::Dict, eT) ? pyexc[eT] : pyexc[Exception]
129169
ccall((@pysym :PyErr_SetString), Cvoid, (PyPtr, Cstring),
130-
pyeT, string("Julia exception: ", e))
170+
pyeT, string("Julia exception: ", showerror_string(e)))
131171
end
132172

133173
function pyraise(e::PyError)

src/pytype.jl

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -337,9 +337,15 @@ end
337337
unsafe_pyjlwrap_to_objref(o::PyPtr) =
338338
unsafe_pointer_to_objref(unsafe_load(convert(Ptr{Ptr{Cvoid}}, o), 3))
339339

340-
pyjlwrap_repr(o::PyPtr) =
341-
pystealref!(PyObject(o != C_NULL ? string("<PyCall.jlwrap ",unsafe_pyjlwrap_to_objref(o),">")
342-
: "<PyCall.jlwrap NULL>"))
340+
function pyjlwrap_repr(o::PyPtr)
341+
try
342+
return pyreturn(o != C_NULL ? string("<PyCall.jlwrap ",unsafe_pyjlwrap_to_objref(o),">")
343+
: "<PyCall.jlwrap NULL>")
344+
catch e
345+
pyraise(e)
346+
return PyPtr_NULL
347+
end
348+
end
343349

344350
function pyjlwrap_hash(o::PyPtr)
345351
h = hash(unsafe_pyjlwrap_to_objref(o))

test/runtests.jl

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -637,4 +637,33 @@ end
637637
@test anonymous.sys != PyNULL()
638638
end
639639

640+
struct Unprintable end
641+
Base.show(::IO, ::Unprintable) = error("show(::IO, ::Unprintable) called")
642+
Base.show(::IO, ::Type{Unprintable}) = error("show(::IO, ::Type{Unprintable}) called")
643+
644+
py"""
645+
def try_repr(x):
646+
try:
647+
return repr(x)
648+
except Exception as err:
649+
return err
650+
"""
651+
652+
py"""
653+
def try_call(f):
654+
try:
655+
return f()
656+
except Exception as err:
657+
return err
658+
"""
659+
660+
@testset "throwing show" begin
661+
unp = Unprintable()
662+
@test_throws Exception show(unp)
663+
@test py"try_repr"("printable") isa String
664+
@test pyisinstance(py"try_repr"(unp), pybuiltin("Exception"))
665+
@test pyisinstance(py"try_call"(() -> throw(Unprintable())),
666+
pybuiltin("Exception"))
667+
end
668+
640669
include("test_pyfncall.jl")

0 commit comments

Comments
 (0)