Author: Armin Rigo <[email protected]>
Branch: ffi-backend
Changeset: r55933:2540f3ab37b1
Date: 2012-07-06 14:41 +0200
http://bitbucket.org/pypy/pypy/changeset/2540f3ab37b1/

Log:    Fix: new("struct foo") returns a pointer, but both that pointer 'p'
        and the structure 'p[0]' keep alive the structure data.

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
@@ -7,13 +7,13 @@
 from pypy.rlib.objectmodel import compute_unique_id, keepalive_until_here
 from pypy.rlib import clibffi, rweakref, rgc
 
-from pypy.module._cffi_backend.cdataobj import W_CData, W_CDataOwn
+from pypy.module._cffi_backend.cdataobj import W_CData, W_CDataApplevelOwning
 from pypy.module._cffi_backend.ctypefunc import SIZE_OF_FFI_ARG
 
 # ____________________________________________________________
 
 
-class W_CDataCallback(W_CDataOwn):
+class W_CDataCallback(W_CDataApplevelOwning):
     ll_error = lltype.nullptr(rffi.CCHARP.TO)
 
     def __init__(self, space, ctype, w_callable, w_error):
diff --git a/pypy/module/_cffi_backend/cdataobj.py 
b/pypy/module/_cffi_backend/cdataobj.py
--- a/pypy/module/_cffi_backend/cdataobj.py
+++ b/pypy/module/_cffi_backend/cdataobj.py
@@ -11,8 +11,9 @@
 
 
 class W_CData(Wrappable):
+    _attrs_ = ['space', '_cdata', 'ctype']
     _immutable_ = True
-    cdata = lltype.nullptr(rffi.CCHARP.TO)
+    _cdata = lltype.nullptr(rffi.CCHARP.TO)
 
     def __init__(self, space, cdata, ctype):
         from pypy.module._cffi_backend import ctypeprim
@@ -82,12 +83,15 @@
         space = self.space
         i = space.getindex_w(w_index, space.w_IndexError)
         self.ctype._check_subscript_index(self, i)
-        ctitem = self.ctype.ctitem
-        w_o = ctitem.convert_to_object(
-            rffi.ptradd(self._cdata, i * ctitem.size))
+        w_o = self._do_getitem(i)
         keepalive_until_here(self)
         return w_o
 
+    def _do_getitem(self, i):
+        ctitem = self.ctype.ctitem
+        return ctitem.convert_to_object(
+            rffi.ptradd(self._cdata, i * ctitem.size))
+
     def setitem(self, w_index, w_value):
         space = self.space
         i = space.getindex_w(w_index, space.w_IndexError)
@@ -181,7 +185,72 @@
         return length
 
 
-class W_CDataOwnFromCasted(W_CData):
+class W_CDataApplevelOwning(W_CData):
+    """This is the abstract base class for classes that are of the app-level
+    type '_cffi_backend.CDataOwn'.  These are weakrefable."""
+    _attrs_ = []
+
+    def _owning_num_bytes(self):
+        return self.ctype.size
+
+    def repr(self):
+        return self.space.wrap("<cdata '%s' owning %d bytes>" % (
+            self.ctype.name, self._owning_num_bytes()))
+
+
+class W_CDataNewOwning(W_CDataApplevelOwning):
+    """This is the class used for the app-level type
+    '_cffi_backend.CDataOwn' created by newp()."""
+    _attrs_ = []
+
+    def __init__(self, space, size, ctype):
+        cdata = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw', zero=True)
+        W_CDataApplevelOwning.__init__(self, space, cdata, ctype)
+
+    @rgc.must_be_light_finalizer
+    def __del__(self):
+        lltype.free(self._cdata, flavor='raw')
+
+
+class W_CDataNewOwningLength(W_CDataNewOwning):
+    """Subclass with an explicit length, for allocated instances of
+    the C type 'foo[]'."""
+    _attrs_ = ['length']
+
+    def __init__(self, space, size, ctype, length):
+        W_CDataNewOwning.__init__(self, space, size, ctype)
+        self.length = length
+
+    def get_array_length(self):
+        return self.length
+
+
+class W_CDataPtrToStructOrUnion(W_CDataApplevelOwning):
+    """This subclass is used for the pointer returned by new('struct foo').
+    It has a strong reference to a W_CDataNewOwning that really owns the
+    struct, which is the object returned by the app-level expression 'p[0]'."""
+    _attrs_ = ['structobj']
+
+    def __init__(self, space, cdata, ctype, structobj):
+        W_CDataApplevelOwning.__init__(self, space, cdata, ctype)
+        self.structobj = structobj
+
+    def _owning_num_bytes(self):
+        from pypy.module._cffi_backend.ctypeptr import W_CTypePtrBase
+        ctype = self.ctype
+        assert isinstance(ctype, W_CTypePtrBase)
+        return ctype.ctitem.size
+
+    def _do_getitem(self, i):
+        return self.structobj
+
+
+class W_CDataCasted(W_CData):
+    """This subclass is used by the results of cffi.cast('int', x)
+    or other primitive explicitly-casted types.  Relies on malloc'ing
+    small bits of memory (e.g. just an 'int').  Its point is to not be
+    a subclass of W_CDataApplevelOwning."""
+    _attrs_ = []
 
     def __init__(self, space, size, ctype):
         cdata = lltype.malloc(rffi.CCHARP.TO, size, flavor='raw', zero=True)
@@ -192,23 +261,6 @@
         lltype.free(self._cdata, flavor='raw')
 
 
-class W_CDataOwn(W_CDataOwnFromCasted):
-
-    def repr(self):
-        return self.space.wrap("<cdata '%s' owning %d bytes>" % (
-            self.ctype.name, self.ctype.size))
-
-
-class W_CDataOwnLength(W_CDataOwn):
-
-    def __init__(self, space, size, ctype, length):
-        W_CDataOwn.__init__(self, space, size, ctype)
-        self.length = length
-
-    def get_array_length(self):
-        return self.length
-
-
 common_methods = dict(
     __nonzero__ = interp2app(W_CData.nonzero),
     __int__ = interp2app(W_CData.int),
@@ -235,11 +287,11 @@
     )
 W_CData.typedef.acceptable_as_base_class = False
 
-W_CDataOwn.typedef = TypeDef(
+W_CDataApplevelOwning.typedef = TypeDef(
     '_cffi_backend.CDataOwn',
     __base = W_CData.typedef,
-    __repr__ = interp2app(W_CDataOwn.repr),
-    __weakref__ = make_weakref_descr(W_CDataOwn),
+    __repr__ = interp2app(W_CDataApplevelOwning.repr),
+    __weakref__ = make_weakref_descr(W_CDataApplevelOwning),
     **common_methods
     )
-W_CDataOwn.typedef.acceptable_as_base_class = False
+W_CDataApplevelOwning.typedef.acceptable_as_base_class = False
diff --git a/pypy/module/_cffi_backend/ctypearray.py 
b/pypy/module/_cffi_backend/ctypearray.py
--- a/pypy/module/_cffi_backend/ctypearray.py
+++ b/pypy/module/_cffi_backend/ctypearray.py
@@ -55,10 +55,11 @@
                 raise OperationError(space.w_OverflowError,
                     space.wrap("array size would overflow a ssize_t"))
             #
-            cdata = cdataobj.W_CDataOwnLength(space, datasize, self, length)
+            cdata = cdataobj.W_CDataNewOwningLength(space, datasize,
+                                                    self, length)
         #
         else:
-            cdata = cdataobj.W_CDataOwn(space, datasize, self)
+            cdata = cdataobj.W_CDataNewOwning(space, datasize, self)
         #
         if not space.is_w(w_init, space.w_None):
             self.convert_from_object(cdata._cdata, w_init)
diff --git a/pypy/module/_cffi_backend/ctypeprim.py 
b/pypy/module/_cffi_backend/ctypeprim.py
--- a/pypy/module/_cffi_backend/ctypeprim.py
+++ b/pypy/module/_cffi_backend/ctypeprim.py
@@ -44,7 +44,7 @@
             value = self.cast_str(w_ob)
         else:
             value = misc.as_unsigned_long_long(space, w_ob, strict=False)
-        w_cdata = cdataobj.W_CDataOwnFromCasted(space, self.size, self)
+        w_cdata = cdataobj.W_CDataCasted(space, self.size, self)
         w_cdata.write_raw_integer_data(value)
         return w_cdata
 
@@ -163,7 +163,7 @@
             value = self.cast_str(w_ob)
         else:
             value = space.float_w(w_ob)
-        w_cdata = cdataobj.W_CDataOwnFromCasted(space, self.size, self)
+        w_cdata = cdataobj.W_CDataCasted(space, self.size, self)
         w_cdata.write_raw_float_data(value)
         return w_cdata
 
diff --git a/pypy/module/_cffi_backend/ctypeptr.py 
b/pypy/module/_cffi_backend/ctypeptr.py
--- a/pypy/module/_cffi_backend/ctypeptr.py
+++ b/pypy/module/_cffi_backend/ctypeptr.py
@@ -7,7 +7,6 @@
 from pypy.rlib.objectmodel import keepalive_until_here
 
 from pypy.module._cffi_backend.ctypeobj import W_CType
-from pypy.module._cffi_backend.ctypeprim import W_CTypePrimitiveChar
 from pypy.module._cffi_backend import cdataobj, misc
 
 
@@ -15,6 +14,8 @@
 
     def __init__(self, space, size, extra, extra_position, ctitem,
                  could_cast_anything=True):
+        from pypy.module._cffi_backend.ctypeprim import W_CTypePrimitiveChar
+        from pypy.module._cffi_backend.ctypestruct import W_CTypeStructOrUnion
         name, name_position = ctitem.insert_name(extra, extra_position)
         W_CType.__init__(self, space, size, name, name_position)
         # this is the "underlying type":
@@ -24,6 +25,7 @@
         self.ctitem = ctitem
         self.can_cast_anything = could_cast_anything and ctitem.cast_anything
         self.is_char_ptr_or_array = isinstance(ctitem, W_CTypePrimitiveChar)
+        self.is_struct_ptr = isinstance(ctitem, W_CTypeStructOrUnion)
 
 
 class W_CTypePtrBase(W_CTypePtrOrArray):
@@ -79,7 +81,7 @@
         W_CTypePtrBase.__init__(self, space, size, extra, 2, ctitem)
 
     def str(self, cdataobj):
-        if isinstance(self.ctitem, W_CTypePrimitiveChar):
+        if self.is_char_ptr_or_array:
             if not cdataobj._cdata:
                 space = self.space
                 raise operationerrfmt(space.w_RuntimeError,
@@ -99,16 +101,26 @@
             raise operationerrfmt(space.w_TypeError,
                 "cannot instantiate ctype '%s' of unknown size",
                                   self.name)
-        if isinstance(ctitem, W_CTypePrimitiveChar):
-            datasize *= 2       # forcefully add a null character
-        cdata = cdataobj.W_CDataOwn(space, datasize, self)
+        if self.is_struct_ptr:
+            # 'newp' on a struct-or-union pointer: in this case, we return
+            # a W_CDataPtrToStruct object which has a strong reference
+            # to a W_CDataNewOwning that really contains the structure.
+            cdatastruct = cdataobj.W_CDataNewOwning(space, datasize, ctitem)
+            cdata = cdataobj.W_CDataPtrToStructOrUnion(space,
+                                                       cdatastruct._cdata,
+                                                       self, cdatastruct)
+        else:
+            if self.is_char_ptr_or_array:
+                datasize *= 2       # forcefully add a null character
+            cdata = cdataobj.W_CDataNewOwning(space, datasize, self)
+        #
         if not space.is_w(w_init, space.w_None):
             ctitem.convert_from_object(cdata._cdata, w_init)
             keepalive_until_here(cdata)
         return cdata
 
     def _check_subscript_index(self, w_cdata, i):
-        if isinstance(w_cdata, cdataobj.W_CDataOwn) and i != 0:
+        if isinstance(w_cdata, cdataobj.W_CDataApplevelOwning) and i != 0:
             space = self.space
             raise operationerrfmt(space.w_IndexError,
                                   "cdata '%s' can only be indexed by 0",
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
@@ -885,7 +885,8 @@
     f = callback(BFunc, cb)
     s = f(10)
     assert typeof(s) is BStruct
-    assert repr(s).startswith("<cdata 'struct foo' owning ")
+    assert repr(s) in ["<cdata 'struct foo' owning 12 bytes>",
+                       "<cdata 'struct foo' owning 16 bytes>"]
     assert s.a == -10
     assert s.b == 1E-42
 
@@ -1266,17 +1267,19 @@
     # 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)])
+    complete_struct_or_union(BStruct, [('a1', new_primitive_type("int"), -1),
+                                       ('a2', new_primitive_type("int"), -1),
+                                       ('a3', new_primitive_type("int"), -1)])
     p = newp(BStructPtr)
-    assert repr(p) == "<cdata 'struct foo *' owning 4 bytes>"
+    assert repr(p) == "<cdata 'struct foo *' owning 12 bytes>"
     q = p[0]
-    assert repr(q) == "<cdata 'struct foo' owning 4 bytes>"
+    assert repr(q) == "<cdata 'struct foo' owning 12 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 repr(q) == "<cdata 'struct foo' owning 12 bytes>"
     assert q.a1 == 123456
 
 def test_nokeepalive_struct():
_______________________________________________
pypy-commit mailing list
[email protected]
http://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to