Author: Armin Rigo <ar...@tunes.org> 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 +@jit.dont_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 pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit