Author: Wim Lavrijsen <[email protected]>
Branch: cppyy-packaging
Changeset: r94431:dd967ce1da92
Date: 2018-04-23 15:52 -0700
http://bitbucket.org/pypy/pypy/changeset/dd967ce1da92/

Log:    initial support for exception handling from wrapped functions

diff --git a/pypy/module/_cppyy/include/capi.h 
b/pypy/module/_cppyy/include/capi.h
--- a/pypy/module/_cppyy/include/capi.h
+++ b/pypy/module/_cppyy/include/capi.h
@@ -16,6 +16,8 @@
     typedef long          cppyy_index_t;
     typedef void*         cppyy_funcaddr_t;
 
+    typedef unsigned long cppyy_exctype_t;
+
     /* name to opaque C++ scope representation 
-------------------------------- */
     RPY_EXTERN
     char* cppyy_resolve_name(const char* cppitem_name);
diff --git a/pypy/module/_cppyy/interp_cppyy.py 
b/pypy/module/_cppyy/interp_cppyy.py
--- a/pypy/module/_cppyy/interp_cppyy.py
+++ b/pypy/module/_cppyy/interp_cppyy.py
@@ -19,6 +19,9 @@
 INSTANCE_FLAGS_IS_REF      = 0x0002
 INSTANCE_FLAGS_IS_R_VALUE  = 0x0004
 
+OVERLOAD_FLAGS_USE_FFI     = 0x0001
+
+
 class FastCallNotPossible(Exception):
     pass
 
@@ -186,7 +189,7 @@
         return rffi.cast(rffi.VOIDP, loc_idx)
 
     @jit.unroll_safe
-    def call(self, cppthis, args_w):
+    def call(self, cppthis, args_w, useffi):
         jit.promote(self)
 
         assert lltype.typeOf(cppthis) == capi.C_OBJECT
@@ -218,16 +221,25 @@
 
         try:
             # attempt to call directly through ffi chain
-            if self._funcaddr:
+            if useffi and self._funcaddr:
                 try:
                     return self.do_fast_call(cppthis, args_w, call_local)
                 except FastCallNotPossible:
                     pass      # can happen if converters or executor does not 
implement ffi
 
             # ffi chain must have failed; using stub functions instead
-            args = self.prepare_arguments(args_w, call_local)
+            args, stat = self.prepare_arguments(args_w, call_local)
             try:
-                return self.executor.execute(self.space, self.cppmethod, 
cppthis, len(args_w), args)
+                result = self.executor.execute(
+                    self.space, self.cppmethod, cppthis, len(args_w), args)
+                if stat[0] != rffi.cast(rffi.ULONG, 0):
+                    what = rffi.cast(rffi.CCHARP, stat[1])
+                    pywhat = rffi.charp2str(what)
+                    capi.c_free(self.space, rffi.cast(rffi.VOIDP, what))
+                    if hasattr(self.space, "fake"):
+                        raise OperationError(self.space.w_Exception, 
self.space.newtext("C++ exception"))
+                    raise oefmt(self.space.w_Exception, pywhat)
+                return result
             finally:
                 self.finalize_call(args, args_w, call_local)
         finally:
@@ -373,7 +385,10 @@
                     conv.free_argument(self.space, rffi.cast(capi.C_OBJECT, 
arg_j), loc_j)
                 capi.c_deallocate_function_args(self.space, args)
                 raise
-        return args
+        stat = rffi.cast(rffi.ULONGP,
+            lltype.direct_ptradd(rffi.cast(rffi.CCHARP, args), 
int(len(args_w))*stride))
+        stat[0] = rffi.cast(rffi.ULONG, 0)
+        return args, stat
 
     @jit.unroll_safe
     def finalize_call(self, args, args_w, call_local):
@@ -435,7 +450,7 @@
         # TODO: might have to specialize for CPPTemplatedCall on 
CPPMethod/CPPFunction here
         CPPMethod.__init__(self, space, declaring_scope, method_index, 
arg_defs, args_required)
 
-    def call(self, cppthis, args_w):
+    def call(self, cppthis, args_w, useffi):
         assert lltype.typeOf(cppthis) == capi.C_OBJECT
         for i in range(len(args_w)):
             try:
@@ -447,10 +462,10 @@
                 raise oefmt(self.space.w_TypeError,
                             "non-matching template (got %s where %s expected)",
                             s, self.templ_args[i])
-        return W_CPPBoundMethod(cppthis, self)
+        return W_CPPBoundMethod(cppthis, self, useffi)
 
-    def bound_call(self, cppthis, args_w):
-        return CPPMethod.call(self, cppthis, args_w)
+    def bound_call(self, cppthis, args_w, useffi):
+        return CPPMethod.call(self, cppthis, args_w, useffi)
 
     def __repr__(self):
         return "CPPTemplatedCall: %s" % self.prototype()
@@ -468,11 +483,11 @@
     def unpack_cppthis(space, w_cppinstance, declaring_scope):
         return rffi.cast(capi.C_OBJECT, declaring_scope.handle)
 
-    def call(self, cppthis, args_w):
+    def call(self, cppthis, args_w, useffi):
         # Note: this does not return a wrapped instance, just a pointer to the
         # new instance; the overload must still wrap it before returning. Also,
         # cppthis is declaring_scope.handle (as per unpack_cppthis(), above).
-        return CPPMethod.call(self, cppthis, args_w)
+        return CPPMethod.call(self, cppthis, args_w, useffi)
 
     def __repr__(self):
         return "CPPConstructor: %s" % self.prototype()
@@ -485,7 +500,7 @@
 
     _immutable_ = True
 
-    def call(self, cppthis, args_w):
+    def call(self, cppthis, args_w, useffi):
         end = len(args_w)-1
         if 0 <= end:
             w_item = args_w[end]
@@ -493,7 +508,7 @@
             if self.converters is None:
                 self._setup(cppthis)
             self.executor.set_item(self.space, w_item) # TODO: what about 
threads?
-        CPPMethod.call(self, cppthis, args_w)
+        CPPMethod.call(self, cppthis, args_w, useffi)
 
 
 class W_CPPOverload(W_Root):
@@ -501,7 +516,7 @@
     collection of (possibly) overloaded methods or functions. It calls these
     in order and deals with error handling and reporting."""
 
-    _attrs_ = ['space', 'scope', 'functions']
+    _attrs_ = ['space', 'scope', 'functions', 'flags']
     _immutable_fields_ = ['scope', 'functions[*]']
 
     def __init__(self, space, declaring_scope, functions):
@@ -510,6 +525,19 @@
         assert len(functions)
         from rpython.rlib import debug
         self.functions = debug.make_sure_not_resized(functions)
+        self.flags = 0
+        self.flags |= OVERLOAD_FLAGS_USE_FFI
+
+    # allow user to determine ffi use rules per overload
+    def fget_useffi(self, space):
+        return space.newbool(bool(self.flags & OVERLOAD_FLAGS_USE_FFI))
+
+    @unwrap_spec(value=bool)
+    def fset_useffi(self, space, value):
+        if space.is_true(value):
+            self.flags |= OVERLOAD_FLAGS_USE_FFI
+        else:
+            self.flags &= ~OVERLOAD_FLAGS_USE_FFI
 
     @jit.elidable_promote()
     def is_static(self):
@@ -540,7 +568,7 @@
         for i in range(len(self.functions)):
             cppyyfunc = self.functions[i]
             try:
-                return cppyyfunc.call(cppthis, args_w)
+                return cppyyfunc.call(cppthis, args_w, self.flags & 
OVERLOAD_FLAGS_USE_FFI)
             except Exception:
                 pass
 
@@ -553,7 +581,7 @@
         for i in range(len(self.functions)):
             cppyyfunc = self.functions[i]
             try:
-                return cppyyfunc.call(cppthis, args_w)
+                return cppyyfunc.call(cppthis, args_w, self.flags & 
OVERLOAD_FLAGS_USE_FFI)
             except OperationError as e:
                 # special case if there's just one function, to prevent 
clogging the error message
                 if len(self.functions) == 1:
@@ -588,6 +616,7 @@
     'CPPOverload',
     is_static = interp2app(W_CPPOverload.is_static),
     call = interp2app(W_CPPOverload.call),
+    __useffi__ = GetSetProperty(W_CPPOverload.fget_useffi, 
W_CPPOverload.fset_useffi),
     prototype = interp2app(W_CPPOverload.prototype),
 )
 
@@ -642,17 +671,18 @@
 
 
 class W_CPPBoundMethod(W_Root):
-    _attrs_ = ['cppthis', 'method']
+    _attrs_ = ['cppthis', 'method', 'useffi']
 
-    def __init__(self, cppthis, method):
+    def __init__(self, cppthis, method, useffi):
         self.cppthis = cppthis
         self.method = method
+        self.useffi = useffi
 
     def __call__(self, args_w):
-        return self.method.bound_call(self.cppthis, args_w)
+        return self.method.bound_call(self.cppthis, args_w, self.useffi)
 
     def __repr__(self):
-        return "W_CPPBoundMethod(%s)" % [f.prototype() for f in self.functions]
+        return "W_CPPBoundMethod(%s)" % self.method.prototype()
 
 W_CPPBoundMethod.typedef = TypeDef(
     'CPPBoundMethod',
diff --git a/pypy/module/_cppyy/test/advancedcpp.cxx 
b/pypy/module/_cppyy/test/advancedcpp.cxx
--- a/pypy/module/_cppyy/test/advancedcpp.cxx
+++ b/pypy/module/_cppyy/test/advancedcpp.cxx
@@ -1,5 +1,7 @@
 #include "advancedcpp.h"
 
+#include <stdexcept>
+
 
 // for testing of default arguments
 #define IMPLEMENT_DEFAULTER_CLASS(type, tname)                               \
@@ -112,3 +114,13 @@
 
 std::string overload_the_other_way::gime() { return "aap"; }
 int overload_the_other_way::gime() const { return 1; }
+
+
+// exception handling testing
+void Thrower::throw_anything() {
+    throw 1;
+}
+
+void Thrower::throw_exception() {
+    throw std::runtime_error("C++ function failed");
+}
diff --git a/pypy/module/_cppyy/test/advancedcpp.h 
b/pypy/module/_cppyy/test/advancedcpp.h
--- a/pypy/module/_cppyy/test/advancedcpp.h
+++ b/pypy/module/_cppyy/test/advancedcpp.h
@@ -400,3 +400,11 @@
    std::string gime();
    int gime() const;
 };
+
+
+//===========================================================================
+class Thrower {                    // exception handling testing
+public:
+    void throw_anything();
+    void throw_exception();
+};
diff --git a/pypy/module/_cppyy/test/advancedcpp.xml 
b/pypy/module/_cppyy/test/advancedcpp.xml
--- a/pypy/module/_cppyy/test/advancedcpp.xml
+++ b/pypy/module/_cppyy/test/advancedcpp.xml
@@ -57,4 +57,6 @@
   <class name="overload_one_way" />
   <class name="overload_the_other_way" />
 
+  <class name="Thrower" />
+
 </lcgdict>
diff --git a/pypy/module/_cppyy/test/test_advancedcpp.py 
b/pypy/module/_cppyy/test/test_advancedcpp.py
--- a/pypy/module/_cppyy/test/test_advancedcpp.py
+++ b/pypy/module/_cppyy/test/test_advancedcpp.py
@@ -656,3 +656,22 @@
         # TODO: currently fails b/c double** not understood as &double*
         #assert cppyy.gbl.my_global_ptr[0] == 1234.
 
+    def test22_exceptions(self):
+        """Catching of C++ exceptions"""
+
+        import _cppyy as cppyy
+        Thrower = cppyy.gbl.Thrower
+
+        # TODO: clean up this interface:
+        Thrower.__cppdecl__.get_overload('throw_anything').__useffi__  = False
+        Thrower.__cppdecl__.get_overload('throw_exception').__useffi__ = False
+
+        t = Thrower()
+
+        assert raises(Exception, t.throw_anything)
+        assert raises(Exception, t.throw_exception)
+
+        try:
+            t.throw_exception()
+        except Exception, e:
+            "C++ function failed" in str(e)
diff --git a/pypy/module/_cppyy/test/test_overloads.py 
b/pypy/module/_cppyy/test/test_overloads.py
--- a/pypy/module/_cppyy/test/test_overloads.py
+++ b/pypy/module/_cppyy/test/test_overloads.py
@@ -15,7 +15,6 @@
     spaceconfig = dict(usemodules=['_cppyy', '_rawffi', 'itertools'])
 
     def setup_class(cls):
-        env = os.environ
         cls.w_test_dct  = cls.space.newtext(test_dct)
         cls.w_overloads = cls.space.appexec([], """():
             import ctypes
diff --git a/pypy/module/_cppyy/test/test_zjit.py 
b/pypy/module/_cppyy/test/test_zjit.py
--- a/pypy/module/_cppyy/test/test_zjit.py
+++ b/pypy/module/_cppyy/test/test_zjit.py
@@ -137,6 +137,7 @@
         executor.get_executor(self, 'int').__class__.c_stubcall = 
staticmethod(c_call_i)
 
         self.w_AttributeError      = FakeException(self, "AttributeError")
+        self.w_Exception           = FakeException(self, "Exception")
         self.w_KeyError            = FakeException(self, "KeyError")
         self.w_NotImplementedError = FakeException(self, "NotImplementedError")
         self.w_ReferenceError      = FakeException(self, "ReferenceError")
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to