[pypy-commit] cffi default: Exception to the no-keepalive rule: add one keepalive for a case

arigo noreply at buildbot.pypy.org
Fri Jun 29 10:49:59 CEST 2012


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r560:6b874784716c
Date: 2012-06-29 10:24 +0200
http://bitbucket.org/cffi/cffi/changeset/6b874784716c/

Log:	Exception to the no-keepalive rule: add one keepalive for a case
	that is occasionally hard to work around.

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -41,7 +41,8 @@
 #define CT_PRIMITIVE_FITS_LONG   2048
 #define CT_IS_OPAQUE             4096
 #define CT_IS_ENUM               8192
-#define CT_CUSTOM_FIELD_POS     16384
+#define CT_IS_PTR_TO_OWNED      16384
+#define CT_CUSTOM_FIELD_POS     32768
 #define CT_PRIMITIVE_ANY  (CT_PRIMITIVE_SIGNED |        \
                            CT_PRIMITIVE_UNSIGNED |      \
                            CT_PRIMITIVE_CHAR |          \
@@ -126,6 +127,11 @@
 } CDataObject_own_length;
 
 typedef struct {
+    CDataObject_own_base head;
+    PyObject *structobj;
+} CDataObject_own_structptr;
+
+typedef struct {
     ffi_cif cif;
     /* the following information is used when doing the call:
        - a buffer of size 'exchange_size' is malloced
@@ -1007,7 +1013,10 @@
     if (cdb->weakreflist != NULL)
         PyObject_ClearWeakRefs((PyObject *) cdb);
 
-    if (cdb->head.c_type->ct_flags & CT_FUNCTIONPTR) {
+    if (cdb->head.c_type->ct_flags & CT_IS_PTR_TO_OWNED) {
+        Py_DECREF(((CDataObject_own_structptr *)cdb)->structobj);
+    }
+    else if (cdb->head.c_type->ct_flags & CT_FUNCTIONPTR) {
         /* a callback */
         ffi_closure *closure = (ffi_closure *)cdb->head.c_data;
         PyObject *args = (PyObject *)(closure->user_data);
@@ -1275,15 +1284,33 @@
 }
 
 static PyObject *
-cdata_subscript(CDataObject *cd, PyObject *key)
+cdataowning_subscript(CDataObject *cd, PyObject *key)
 {
     char *c = _cdata_get_indexed_ptr(cd, key);
-    CTypeDescrObject *ctitem = cd->c_type->ct_itemdescr;
     /* use 'mp_subscript' instead of 'sq_item' because we don't want
        negative indexes to be corrected automatically */
     if (c == NULL)
         return NULL;
-    return convert_to_object(c, ctitem);
+
+    if (cd->c_type->ct_flags & CT_IS_PTR_TO_OWNED) {
+        PyObject *res = ((CDataObject_own_structptr *)cd)->structobj;
+        Py_INCREF(res);
+        return res;
+    }
+    else {
+        return convert_to_object(c, cd->c_type->ct_itemdescr);
+    }
+}
+
+static PyObject *
+cdata_subscript(CDataObject *cd, PyObject *key)
+{
+    char *c = _cdata_get_indexed_ptr(cd, key);
+    /* use 'mp_subscript' instead of 'sq_item' because we don't want
+       negative indexes to be corrected automatically */
+    if (c == NULL)
+        return NULL;
+    return convert_to_object(c, cd->c_type->ct_itemdescr);
 }
 
 static int
@@ -1603,6 +1630,12 @@
     (objobjargproc)cdata_ass_sub, /*mp_ass_subscript*/
 };
 
+static PyMappingMethods CDataOwn_as_mapping = {
+    (lenfunc)cdata_length, /*mp_length*/
+    (binaryfunc)cdataowning_subscript, /*mp_subscript*/
+    (objobjargproc)cdata_ass_sub, /*mp_ass_subscript*/
+};
+
 static PyTypeObject CData_Type = {
     PyVarObject_HEAD_INIT(NULL, 0)
     "_cffi_backend.CData",
@@ -1645,7 +1678,7 @@
     (reprfunc)cdataowning_repr,                 /* tp_repr */
     0,                                          /* tp_as_number */
     0,                                          /* tp_as_sequence */
-    0,                                          /* tp_as_mapping */
+    &CDataOwn_as_mapping,                       /* tp_as_mapping */
     0,                                          /* tp_hash */
     0,                                          /* tp_call */
     0,                                          /* tp_str */
@@ -1752,6 +1785,7 @@
 static PyObject *b_newp(PyObject *self, PyObject *args)
 {
     CTypeDescrObject *ct, *ctitem;
+    CDataObject *cd;
     CDataObject_own_base *cdb;
     PyObject *init = Py_None;
     Py_ssize_t dataoffset, datasize, explicitlength;
@@ -1812,23 +1846,48 @@
     cdb = (CDataObject_own_base *)PyObject_Malloc(dataoffset + datasize);
     if (PyObject_Init((PyObject *)cdb, &CDataOwning_Type) == NULL)
         return NULL;
-
-    Py_INCREF(ct);
-    cdb->head.c_type = ct;
+    cdb->weakreflist = NULL;
     cdb->head.c_data = ((char *)cdb) + dataoffset;
-    cdb->weakreflist = NULL;
     if (explicitlength >= 0)
         ((CDataObject_own_length*)cdb)->length = explicitlength;
 
-    memset(cdb->head.c_data, 0, datasize);
-    if (init != Py_None) {
-        if (convert_from_object(cdb->head.c_data,
-              (ct->ct_flags & CT_POINTER) ? ct->ct_itemdescr : ct, init) < 0) {
+    if (ct->ct_flags & CT_IS_PTR_TO_OWNED) {
+        /* common case of ptr-to-struct (or ptr-to-union): for this case
+           we build two objects instead of one, with the memory-owning
+           one being really the struct (or union) and the returned one
+           having a strong reference to it */
+        CDataObject_own_structptr *cdp;
+
+        ctitem = ct->ct_itemdescr;
+        Py_INCREF(ctitem);
+        cdb->head.c_type = ctitem;
+
+        cdp = (CDataObject_own_structptr *)
+            PyObject_Malloc(sizeof(CDataObject_own_structptr));
+        if (PyObject_Init((PyObject *)cdp, &CDataOwning_Type) == NULL) {
             Py_DECREF(cdb);
             return NULL;
         }
+        cdp->head.weakreflist = NULL;
+        cdp->head.head.c_data = cdb->head.c_data;
+        cdp->structobj = (PyObject *)cdb;   /* store away the only reference */
+        cd = &cdp->head.head;
     }
-    return (PyObject *)cdb;
+    else {
+        cd = &cdb->head;
+    }
+    Py_INCREF(ct);
+    cd->c_type = ct;
+
+    memset(cd->c_data, 0, datasize);
+    if (init != Py_None) {
+        if (convert_from_object(cd->c_data,
+              (ct->ct_flags & CT_POINTER) ? ct->ct_itemdescr : ct, init) < 0) {
+            Py_DECREF(cd);
+            return NULL;
+        }
+    }
+    return (PyObject *)cd;
 }
 
 static CDataObject *_new_casted_primitive(CTypeDescrObject *ct)
@@ -2294,6 +2353,8 @@
 
     td->ct_size = sizeof(void *);
     td->ct_flags = CT_POINTER;
+    if (ctitem->ct_flags & (CT_STRUCT|CT_UNION))
+        td->ct_flags |= CT_IS_PTR_TO_OWNED;
     return (PyObject *)td;
 }
 
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -1164,3 +1164,33 @@
     newp(BStructPtr, [cast(BCharP, 0)])
     py.test.raises(TypeError, newp, BStructPtr, [cast(BIntP, 0)])
     py.test.raises(TypeError, newp, BStructPtr, [cast(BFunc2, 0)])
+
+def test_keepalive_struct():
+    # exception to the no-keepalive rule: p=newp(BStructPtr) returns a
+    # pointer owning the memory, and p[0] returns a pointer to the
+    # struct that *also* owns the memory
+    BStruct = new_struct_type("foo")
+    BStructPtr = new_pointer_type(BStruct)
+    complete_struct_or_union(BStruct, [('a1', new_primitive_type("int"), -1)])
+    p = newp(BStructPtr)
+    assert repr(p) == "<cdata 'struct foo *' owning 4 bytes>"
+    q = p[0]
+    assert repr(q) == "<cdata 'struct foo' owning 4 bytes>"
+    q.a1 = 123456
+    assert p.a1 == 123456
+    del p
+    import gc; gc.collect()
+    assert q.a1 == 123456
+    assert repr(q) == "<cdata 'struct foo' owning 4 bytes>"
+    assert q.a1 == 123456
+
+def test_nokeepalive_struct():
+    BStruct = new_struct_type("foo")
+    BStructPtr = new_pointer_type(BStruct)
+    BStructPtrPtr = new_pointer_type(BStructPtr)
+    complete_struct_or_union(BStruct, [('a1', new_primitive_type("int"), -1)])
+    p = newp(BStructPtr)
+    pp = newp(BStructPtrPtr)
+    pp[0] = p
+    s = pp[0][0]
+    assert repr(s).startswith("<cdata 'struct foo' 0x")
diff --git a/doc/source/index.rst b/doc/source/index.rst
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -393,6 +393,14 @@
 ownership, so you must keep it alive.  As soon as you forget it, then
 the casted pointer will point to garbage.)
 
+.. versionchanged:: 0.2
+   Allocating a new struct also returns an owning pointer object.
+   But --- as an exception to the above rule --- dereferencing this
+   particular pointer object also returns an *owning* struct object.
+   This is done for cases where you really want to have a struct
+   object but don't have any convenient place to keep the original
+   pointer object alive.
+
 The cdata objects support mostly the same operations as in C: you can
 read or write from pointers, arrays and structures.  Dereferencing a
 pointer is done usually in C with the syntax ``*p``, which is not valid


More information about the pypy-commit mailing list