Author: Armin Rigo <[email protected]>
Branch: cffi-callback-onerror
Changeset: r78433:77365fd8018a
Date: 2015-07-04 23:30 +0200
http://bitbucket.org/pypy/pypy/changeset/77365fd8018a/
Log: Implement 'onerror' in exactly the same way as cffi on CPython
diff --git a/pypy/interpreter/error.py b/pypy/interpreter/error.py
--- a/pypy/interpreter/error.py
+++ b/pypy/interpreter/error.py
@@ -252,7 +252,8 @@
w_t, w_v, w_tb],
"""(where, objrepr, extra_line, t, v, tb):
import sys, traceback
- sys.stderr.write('From %s%s:\\n' % (where, objrepr))
+ if where or objrepr:
+ sys.stderr.write('From %s%s:\\n' % (where, objrepr))
if extra_line:
sys.stderr.write(extra_line)
traceback.print_exception(t, v, tb)
diff --git a/pypy/module/_cffi_backend/ccallback.py
b/pypy/module/_cffi_backend/ccallback.py
--- a/pypy/module/_cffi_backend/ccallback.py
+++ b/pypy/module/_cffi_backend/ccallback.py
@@ -93,14 +93,14 @@
return ctype
@jit.unroll_safe
- def prepare_invoke_args(self, ll_args):
+ def invoke(self, ll_args):
space = self.space
ctype = self.getfunctype()
args_w = []
for i, farg in enumerate(ctype.fargs):
ll_arg = rffi.cast(rffi.CCHARP, ll_args[i])
args_w.append(farg.convert_to_object(ll_arg))
- return space.newtuple(args_w)
+ return space.call(self.w_callable, space.newtuple(args_w))
def convert_result(self, ll_res, w_res):
fresult = self.getfunctype().ctitem
@@ -168,6 +168,29 @@
STDERR = 2
[email protected]_look_inside
+def _handle_applevel_exception(space, callback, e, ll_res, extra_line):
+ callback.write_error_return_value(ll_res)
+ if callback.w_onerror is None:
+ callback.print_error(e, extra_line)
+ else:
+ try:
+ e.normalize_exception(space)
+ w_t = e.w_type
+ w_v = e.get_w_value(space)
+ w_tb = space.wrap(e.get_traceback())
+ w_res = space.call_function(callback.w_onerror,
+ w_t, w_v, w_tb)
+ if not space.is_none(w_res):
+ callback.convert_result(ll_res, w_res)
+ except OperationError, e2:
+ # double exception! print a double-traceback...
+ callback.print_error(e, extra_line) # original traceback
+ e2.write_unraisable(space, '', with_traceback=True,
+ extra_line="\nDuring the call to 'onerror', "
+ "another exception occurred:\n\n")
+
+
@jit.jit_callback("CFFI")
def _invoke_callback(ffi_cif, ll_res, ll_args, ll_userdata):
""" Callback specification.
@@ -185,7 +208,7 @@
try:
os.write(STDERR, "SystemError: invoking a callback "
"that was already freed\n")
- except OSError:
+ except:
pass
# In this case, we don't even know how big ll_res is. Let's assume
# it is just a 'ffi_arg', and store 0 there.
@@ -198,24 +221,11 @@
must_leave = space.threadlocals.try_enter_thread(space)
extra_line = ''
try:
- w_args = callback.prepare_invoke_args(ll_args)
- try:
- w_res = space.call(callback.w_callable, w_args)
- extra_line = "Trying to convert the result back to C:\n"
- callback.convert_result(ll_res, w_res)
- except OperationError, e:
- # got an app-level exception
- if callback.w_onerror is None:
- raise
- e.normalize_exception(space)
- w_t = e.w_type
- w_v = e.get_w_value(space)
- w_tb = space.wrap(e.get_traceback())
- space.call_function(callback.w_onerror, w_t, w_v, w_tb, w_args)
- callback.write_error_return_value(ll_res)
+ w_res = callback.invoke(ll_args)
+ extra_line = "Trying to convert the result back to C:\n"
+ callback.convert_result(ll_res, w_res)
except OperationError, e:
- callback.print_error(e, extra_line)
- callback.write_error_return_value(ll_res)
+ _handle_applevel_exception(space, callback, e, ll_res, extra_line)
#
except Exception, e:
# oups! last-level attempt to recover.
diff --git a/pypy/module/_cffi_backend/test/_backend_test_c.py
b/pypy/module/_cffi_backend/test/_backend_test_c.py
--- a/pypy/module/_cffi_backend/test/_backend_test_c.py
+++ b/pypy/module/_cffi_backend/test/_backend_test_c.py
@@ -1170,6 +1170,14 @@
BShort = new_primitive_type("short")
BFunc = new_function_type((BShort,), BShort, False)
f = callback(BFunc, Zcb1, -42)
+ #
+ seen = []
+ oops_result = None
+ def oops(*args):
+ seen.append(args)
+ return oops_result
+ ff = callback(BFunc, Zcb1, -42, oops)
+ #
orig_stderr = sys.stderr
orig_getline = linecache.getline
try:
@@ -1195,6 +1203,59 @@
Trying to convert the result back to C:
OverflowError: integer 60000 does not fit 'short'
""")
+ sys.stderr = cStringIO.StringIO()
+ bigvalue = 20000
+ assert len(seen) == 0
+ assert ff(bigvalue) == -42
+ assert sys.stderr.getvalue() == ""
+ assert len(seen) == 1
+ exc, val, tb = seen[0]
+ assert exc is OverflowError
+ assert str(val) == "integer 60000 does not fit 'short'"
+ #
+ sys.stderr = cStringIO.StringIO()
+ bigvalue = 20000
+ del seen[:]
+ oops_result = 81
+ assert ff(bigvalue) == 81
+ oops_result = None
+ assert sys.stderr.getvalue() == ""
+ assert len(seen) == 1
+ exc, val, tb = seen[0]
+ assert exc is OverflowError
+ assert str(val) == "integer 60000 does not fit 'short'"
+ #
+ sys.stderr = cStringIO.StringIO()
+ bigvalue = 20000
+ del seen[:]
+ oops_result = "xy" # not None and not an int!
+ assert ff(bigvalue) == -42
+ oops_result = None
+ assert matches(sys.stderr.getvalue(), """\
+From cffi callback <function$Zcb1 at 0x$>:
+Trying to convert the result back to C:
+OverflowError: integer 60000 does not fit 'short'
+
+During the call to 'onerror', another exception occurred:
+
+TypeError: $integer$
+""")
+ #
+ sys.stderr = cStringIO.StringIO()
+ seen = "not a list" # this makes the oops() function crash
+ assert ff(bigvalue) == -42
+ assert matches(sys.stderr.getvalue(), """\
+From cffi callback <function$Zcb1 at 0x$>:
+Trying to convert the result back to C:
+OverflowError: integer 60000 does not fit 'short'
+
+During the call to 'onerror', another exception occurred:
+
+Traceback (most recent call last):
+ File "$", line $, in oops
+ $
+AttributeError: 'str' object has no attribute 'append'
+""")
finally:
sys.stderr = orig_stderr
linecache.getline = orig_getline
diff --git a/pypy/module/_cffi_backend/test/test_ffi_obj.py
b/pypy/module/_cffi_backend/test/test_ffi_obj.py
--- a/pypy/module/_cffi_backend/test/test_ffi_obj.py
+++ b/pypy/module/_cffi_backend/test/test_ffi_obj.py
@@ -120,13 +120,13 @@
import _cffi_backend as _cffi1_backend
ffi = _cffi1_backend.FFI()
seen = []
- def myerror(exc, val, tb, args):
- seen.append((exc, args))
+ def myerror(exc, val, tb):
+ seen.append(exc)
cb = ffi.callback("int(int)", lambda x: x + "", onerror=myerror)
assert cb(10) == 0
cb = ffi.callback("int(int)", lambda x:int(1E100), -66,
onerror=myerror)
- assert cb(12) == -66
- assert seen == [(TypeError, (10,)), (OverflowError, (12,))]
+ assert cb(10) == -66
+ assert seen == [TypeError, OverflowError]
def test_ffi_callback_decorator(self):
import _cffi_backend as _cffi1_backend
@@ -136,6 +136,37 @@
assert deco(lambda x: x + "")(10) == -66
assert deco(lambda x: x + 42)(10) == 52
+ def test_ffi_callback_onerror(self):
+ import _cffi_backend as _cffi1_backend
+ ffi = _cffi1_backend.FFI()
+ seen = []
+ def oops(*args):
+ seen.append(args)
+
+ @ffi.callback("int(int)", onerror=oops)
+ def fn1(x):
+ return x + ""
+ assert fn1(10) == 0
+
+ @ffi.callback("int(int)", onerror=oops, error=-66)
+ def fn2(x):
+ return x + ""
+ assert fn2(10) == -66
+
+ assert len(seen) == 2
+ exc, val, tb = seen[0]
+ assert exc is TypeError
+ assert isinstance(val, TypeError)
+ assert tb.tb_frame.f_code.co_name == "fn1"
+ exc, val, tb = seen[1]
+ assert exc is TypeError
+ assert isinstance(val, TypeError)
+ assert tb.tb_frame.f_code.co_name == "fn2"
+ del seen[:]
+ #
+ raises(TypeError, ffi.callback, "int(int)",
+ lambda x: x, onerror=42) # <- not callable
+
def test_ffi_getctype(self):
import _cffi_backend as _cffi1_backend
ffi = _cffi1_backend.FFI()
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit