[pypy-commit] pypy missing-tp_new: merge default into branch

mattip pypy.commits at gmail.com
Tue Dec 27 16:33:52 EST 2016


Author: Matti Picus <matti.picus at gmail.com>
Branch: missing-tp_new
Changeset: r89256:d8febc18447e
Date: 2016-12-27 23:09 +0200
http://bitbucket.org/pypy/pypy/changeset/d8febc18447e/

Log:	merge default into branch

diff --git a/pypy/doc/whatsnew-head.rst b/pypy/doc/whatsnew-head.rst
--- a/pypy/doc/whatsnew-head.rst
+++ b/pypy/doc/whatsnew-head.rst
@@ -68,3 +68,11 @@
 .. branch: stdlib-2.7.13
 
 Updated the implementation to match CPython 2.7.13 instead of 2.7.13.
+
+.. branch: issue2444
+
+Fix ``PyObject_GetBuffer`` and ``PyMemoryView_GET_BUFFER``, which leaked
+memory and held references. Add a finalizer to CPyBuffer, add a
+PyMemoryViewObject with a PyBuffer attached so that the call to 
+``PyMemoryView_GET_BUFFER`` does not leak a PyBuffer-sized piece of memory.
+Properly call ``bf_releasebuffer`` when not ``NULL``.
diff --git a/pypy/module/cpyext/api.py b/pypy/module/cpyext/api.py
--- a/pypy/module/cpyext/api.py
+++ b/pypy/module/cpyext/api.py
@@ -637,18 +637,18 @@
         ('len', Py_ssize_t),
         ('itemsize', Py_ssize_t),
 
-        ('readonly', lltype.Signed),
-        ('ndim', lltype.Signed),
+        ('readonly', rffi.INT_real),
+        ('ndim', rffi.INT_real),
         ('format', rffi.CCHARP),
         ('shape', Py_ssize_tP),
         ('strides', Py_ssize_tP),
+        ('suboffsets', Py_ssize_tP),
         ('_format', rffi.CFixedArray(rffi.UCHAR, Py_MAX_FMT)),
         ('_shape', rffi.CFixedArray(Py_ssize_t, Py_MAX_NDIMS)),
         ('_strides', rffi.CFixedArray(Py_ssize_t, Py_MAX_NDIMS)),
-        ('suboffsets', Py_ssize_tP),
         #('smalltable', rffi.CFixedArray(Py_ssize_t, 2)),
-        ('internal', rffi.VOIDP)
-        ))
+        ('internal', rffi.VOIDP),
+))
 Py_bufferP = lltype.Ptr(Py_buffer)
 
 @specialize.memo()
diff --git a/pypy/module/cpyext/classobject.py b/pypy/module/cpyext/classobject.py
--- a/pypy/module/cpyext/classobject.py
+++ b/pypy/module/cpyext/classobject.py
@@ -1,7 +1,7 @@
 from rpython.rtyper.lltypesystem import rffi, lltype
 from pypy.module.cpyext.api import (
     PyObjectFields, CANNOT_FAIL,
-    cpython_api, bootstrap_function, cpython_struct, build_type_checkers)
+    cpython_api, bootstrap_function, build_type_checkers)
 from pypy.module.cpyext.pyobject import PyObject, make_ref, from_ref, Py_DecRef, make_typedescr
 from pypy.module.cpyext.pyerrors import PyErr_BadInternalCall
 from pypy.module.__builtin__.interp_classobj import W_ClassObject, W_InstanceObject
diff --git a/pypy/module/cpyext/include/memoryobject.h b/pypy/module/cpyext/include/memoryobject.h
--- a/pypy/module/cpyext/include/memoryobject.h
+++ b/pypy/module/cpyext/include/memoryobject.h
@@ -5,6 +5,14 @@
 extern "C" {
 #endif
 
+/* The struct is declared here but it shouldn't
+   be considered public. Don't access those fields directly,
+   use the functions instead! */
+typedef struct {
+    PyObject_HEAD
+    Py_buffer view;
+} PyMemoryViewObject;
+
 
 
 
diff --git a/pypy/module/cpyext/memoryobject.py b/pypy/module/cpyext/memoryobject.py
--- a/pypy/module/cpyext/memoryobject.py
+++ b/pypy/module/cpyext/memoryobject.py
@@ -1,14 +1,59 @@
 from pypy.module.cpyext.api import (cpython_api, Py_buffer, CANNOT_FAIL,
-                         Py_MAX_FMT, Py_MAX_NDIMS, build_type_checkers, Py_ssize_tP)
-from pypy.module.cpyext.pyobject import PyObject, make_ref, incref, from_ref
+                         Py_MAX_FMT, Py_MAX_NDIMS, build_type_checkers,
+                         Py_ssize_tP, PyObjectFields, cpython_struct,
+                         bootstrap_function, Py_bufferP)
+from pypy.module.cpyext.pyobject import (PyObject, make_ref, as_pyobj, incref,
+             decref, from_ref, make_typedescr)
 from rpython.rtyper.lltypesystem import lltype, rffi
 from rpython.rlib.rarithmetic import widen
 from pypy.objspace.std.memoryobject import W_MemoryView
+from pypy.module.cpyext.object import _dealloc
 from pypy.module.cpyext.import_ import PyImport_Import
 
-PyMemoryView_Check, PyMemoryView_CheckExact = build_type_checkers("MemoryView", "w_memoryview")
+PyMemoryView_Check, PyMemoryView_CheckExact = build_type_checkers("MemoryView")
 
- at cpython_api([PyObject, lltype.Ptr(Py_buffer), rffi.INT_real],
+
+PyMemoryViewObjectStruct = lltype.ForwardReference()
+PyMemoryViewObject = lltype.Ptr(PyMemoryViewObjectStruct)
+PyMemoryViewObjectFields = PyObjectFields + \
+    (("view", Py_buffer),)
+cpython_struct(
+    "PyMemoryViewObject", PyMemoryViewObjectFields, PyMemoryViewObjectStruct,
+    level=2)
+
+ at bootstrap_function
+def init_memoryobject(space):
+    "Type description of PyDictObject"
+    make_typedescr(W_MemoryView.typedef,
+                   basestruct=PyMemoryViewObject.TO,
+                   attach=memory_attach,
+                   dealloc=memory_dealloc,
+                   #realize=memory_realize,
+                  )
+
+def memory_attach(space, py_obj, w_obj, w_userdata=None):
+    """
+    Fills a newly allocated PyMemoryViewObject with the given W_MemoryView object.
+    """
+    py_obj = rffi.cast(PyMemoryViewObject, py_obj)
+    py_obj.c_view.c_obj = rffi.cast(PyObject, 0)
+
+def memory_realize(space, py_obj):
+    """
+    Creates the memory object in the interpreter
+    """
+    raise oefmt(space.w_NotImplementedError, "cannot call this yet")
+
+ at cpython_api([PyObject], lltype.Void, header=None)
+def memory_dealloc(space, py_obj):
+    mem_obj = rffi.cast(PyMemoryViewObject, py_obj)
+    if mem_obj.c_view.c_obj:
+        decref(space, mem_obj.c_view.c_obj)
+    mem_obj.c_view.c_obj = rffi.cast(PyObject, 0)
+    _dealloc(space, py_obj)
+
+
+ at cpython_api([PyObject, Py_bufferP, rffi.INT_real],
              rffi.INT_real, error=-1)
 def PyObject_GetBuffer(space, w_obj, view, flags):
     """Export obj into a Py_buffer, view.  These arguments must
@@ -33,11 +78,40 @@
     try:
         view.c_buf = rffi.cast(rffi.VOIDP, buf.get_raw_address())
     except ValueError:
-        raise BufferError("could not create buffer from object")
+        if not space.isinstance_w(w_obj, space.w_str):
+            # XXX Python 3?
+            raise BufferError("could not create buffer from object")
+        view.c_buf = rffi.cast(rffi.VOIDP, rffi.str2charp(space.str_w(w_obj), track_allocation=False))
+        rffi.setintfield(view, 'c_readonly', 1)
     ret = fill_Py_buffer(space, buf, view)
     view.c_obj = make_ref(space, w_obj)
     return ret
 
+ at cpython_api([PyObject], Py_bufferP, error=CANNOT_FAIL)
+def PyMemoryView_GET_BUFFER(space, w_obj):
+    """Return a pointer to the buffer-info structure wrapped by the given
+    object.  The object must be a memoryview instance; this macro doesn't
+    check its type, you must do it yourself or you will risk crashes."""
+    if not isinstance(w_obj, W_MemoryView):
+        return lltype.nullptr(Py_buffer)
+    py_memobj = rffi.cast(PyMemoryViewObject, as_pyobj(space, w_obj)) # no inc_ref
+    view = py_memobj.c_view
+    ndim = w_obj.buf.getndim()
+    if ndim >= Py_MAX_NDIMS:
+        # XXX warn?
+        return view
+    fill_Py_buffer(space, w_obj.buf, view)
+    try:
+        view.c_buf = rffi.cast(rffi.VOIDP, w_obj.buf.get_raw_address())
+        #view.c_obj = make_ref(space, w_obj) # NO - this creates a ref cycle!
+        rffi.setintfield(view, 'c_readonly', w_obj.buf.readonly)
+    except ValueError:
+        w_s = w_obj.descr_tobytes(space)
+        view.c_obj = make_ref(space, w_s)
+        view.c_buf = rffi.cast(rffi.VOIDP, rffi.str2charp(space.str_w(w_s), track_allocation=False))
+        rffi.setintfield(view, 'c_readonly', 1)
+    return view
+
 def fill_Py_buffer(space, buf, view):
     # c_buf, c_obj have been filled in
     ndim = buf.getndim()
@@ -111,7 +185,7 @@
         sd *= dim
     return 1
 
- at cpython_api([lltype.Ptr(Py_buffer), lltype.Char], rffi.INT_real, error=CANNOT_FAIL)
+ at cpython_api([Py_bufferP, lltype.Char], rffi.INT_real, error=CANNOT_FAIL)
 def PyBuffer_IsContiguous(space, view, fort):
     """Return 1 if the memory defined by the view is C-style (fort is
     'C') or Fortran-style (fort is 'F') contiguous or either one
@@ -132,7 +206,7 @@
 def PyMemoryView_FromObject(space, w_obj):
     return space.call_method(space.builtin, "memoryview", w_obj)
 
- at cpython_api([lltype.Ptr(Py_buffer)], PyObject)
+ at cpython_api([Py_bufferP], PyObject)
 def PyMemoryView_FromBuffer(space, view):
     """Create a memoryview object wrapping the given buffer-info structure view.
     The memoryview object then owns the buffer, which means you shouldn't
@@ -149,29 +223,3 @@
     # XXX needed for numpy on py3k
     raise NotImplementedError('PyMemoryView_GET_BASE')
 
- at cpython_api([PyObject], lltype.Ptr(Py_buffer), error=CANNOT_FAIL)
-def PyMemoryView_GET_BUFFER(space, w_obj):
-    """Return a pointer to the buffer-info structure wrapped by the given
-    object.  The object must be a memoryview instance; this macro doesn't
-    check its type, you must do it yourself or you will risk crashes."""
-    view = lltype.malloc(Py_buffer, flavor='raw', zero=True)
-    if not isinstance(w_obj, W_MemoryView):
-        return view
-    ndim = w_obj.buf.getndim()
-    if ndim >= Py_MAX_NDIMS:
-        # XXX warn?
-        return view
-    fill_Py_buffer(space, w_obj.buf, view)
-    try:
-        view.c_buf = rffi.cast(rffi.VOIDP, w_obj.buf.get_raw_address())
-        view.c_obj = make_ref(space, w_obj)
-        rffi.setintfield(view, 'c_readonly', w_obj.buf.readonly)
-        isstr = False
-    except ValueError:
-        w_s = w_obj.descr_tobytes(space)
-        view.c_obj = make_ref(space, w_s)
-        view.c_buf = rffi.cast(rffi.VOIDP, rffi.str2charp(space.str_w(w_s), track_allocation=False))
-        rffi.setintfield(view, 'c_readonly', 1)
-        isstr = True
-    return view
-
diff --git a/pypy/module/cpyext/slotdefs.py b/pypy/module/cpyext/slotdefs.py
--- a/pypy/module/cpyext/slotdefs.py
+++ b/pypy/module/cpyext/slotdefs.py
@@ -4,6 +4,7 @@
 
 from rpython.rtyper.lltypesystem import rffi, lltype
 from rpython.rlib.rarithmetic import widen
+from rpython.rlib import rgc # Force registration of gc.collect
 from pypy.module.cpyext.api import (
     cpython_api, generic_cpy_call, PyObject, Py_ssize_t, Py_TPFLAGS_CHECKTYPES,
     mangle_name, pypy_decl, Py_buffer, Py_bufferP)
@@ -12,8 +13,8 @@
     getattrfunc, getattrofunc, setattrofunc, lenfunc, ssizeargfunc, inquiry,
     ssizessizeargfunc, ssizeobjargproc, iternextfunc, initproc, richcmpfunc,
     cmpfunc, hashfunc, descrgetfunc, descrsetfunc, objobjproc, objobjargproc,
-    readbufferproc, getbufferproc, ssizessizeobjargproc)
-from pypy.module.cpyext.pyobject import make_ref, Py_DecRef
+    readbufferproc, getbufferproc, releasebufferproc, ssizessizeobjargproc)
+from pypy.module.cpyext.pyobject import make_ref, decref
 from pypy.module.cpyext.pyerrors import PyErr_Occurred
 from pypy.module.cpyext.memoryobject import fill_Py_buffer
 from pypy.module.cpyext.state import State
@@ -99,7 +100,7 @@
     if (not ref.c_ob_type.c_tp_flags & Py_TPFLAGS_CHECKTYPES and
         not space.issubtype_w(space.type(args_w[0]), space.type(w_self))):
         return space.w_NotImplemented
-    Py_DecRef(space, ref)
+    decref(space, ref)
     return generic_cpy_call(space, func_binary, w_self, args_w[0])
 
 def wrap_binaryfunc_r(space, w_self, w_args, func):
@@ -110,7 +111,7 @@
     if (not ref.c_ob_type.c_tp_flags & Py_TPFLAGS_CHECKTYPES and
         not space.issubtype_w(space.type(args_w[0]), space.type(w_self))):
         return space.w_NotImplemented
-    Py_DecRef(space, ref)
+    decref(space, ref)
     return generic_cpy_call(space, func_binary, args_w[0], w_self)
 
 def wrap_ternaryfunc(space, w_self, w_args, func):
@@ -132,7 +133,7 @@
     if (not ref.c_ob_type.c_tp_flags & Py_TPFLAGS_CHECKTYPES and
         not space.issubtype_w(space.type(args_w[0]), space.type(w_self))):
         return space.w_NotImplemented
-    Py_DecRef(space, ref)
+    decref(space, ref)
     arg3 = space.w_None
     if len(args_w) > 1:
         arg3 = args_w[1]
@@ -322,11 +323,14 @@
     # Similar to Py_buffer
     _immutable_ = True
 
-    def __init__(self, ptr, size, w_obj, format='B', shape=None,
-                strides=None, ndim=1, itemsize=1, readonly=True):
+    def __init__(self, space, ptr, size, w_obj, format='B', shape=None,
+                strides=None, ndim=1, itemsize=1, readonly=True,
+                releasebuffer=None):
+        self.space = space
         self.ptr = ptr
         self.size = size
         self.w_obj = w_obj # kept alive
+        self.pyobj = make_ref(space, w_obj)
         self.format = format
         if not shape:
             self.shape = [size]
@@ -339,6 +343,27 @@
         self.ndim = ndim
         self.itemsize = itemsize
         self.readonly = readonly
+        self.releasebufferproc = releasebuffer
+
+    def releasebuffer(self):
+        if self.pyobj:
+            decref(self.space, self.pyobj)
+            self.pyobj = lltype.nullptr(PyObject.TO)
+        else:
+            #do not call twice
+            return
+        if self.releasebufferproc:        
+            func_target = rffi.cast(releasebufferproc, self.releasebufferproc)
+            with lltype.scoped_alloc(Py_buffer) as pybuf:
+                pybuf.c_buf = self.ptr
+                pybuf.c_len = self.size
+                pybuf.c_ndim = rffi.cast(rffi.INT_real, self.ndim)
+                for i in range(self.ndim):
+                    pybuf.c_shape[i] = self.shape[i]
+                    pybuf.c_strides[i] = self.strides[i]
+                pybuf.c_format = rffi.str2charp(self.format)
+                generic_cpy_call(self.space, func_target, self.pyobj, pybuf)
+            self.releasebufferproc = rffi.cast(rffi.VOIDP, 0)
 
     def getlength(self):
         return self.size
@@ -368,26 +393,61 @@
         # absolutely no safety checks, what could go wrong?
         self.ptr[index] = char
 
+class FQ(rgc.FinalizerQueue):
+    Class = CPyBuffer
+    def finalizer_trigger(self):
+        while 1:
+            buf  = self.next_dead()
+            if not buf:
+                break
+            buf.releasebuffer()
+
+fq = FQ()
+
 def wrap_getreadbuffer(space, w_self, w_args, func):
     func_target = rffi.cast(readbufferproc, func)
+    py_obj = make_ref(space, w_self)
+    py_type = py_obj.c_ob_type
+    releasebuffer = rffi.cast(rffi.VOIDP, 0)
+    if py_type.c_tp_as_buffer:
+        releasebuffer = rffi.cast(rffi.VOIDP, py_type.c_tp_as_buffer.c_bf_releasebuffer)
+    decref(space, py_obj)
     with lltype.scoped_alloc(rffi.VOIDPP.TO, 1) as ptr:
         index = rffi.cast(Py_ssize_t, 0)
         size = generic_cpy_call(space, func_target, w_self, index, ptr)
         if size < 0:
             space.fromcache(State).check_and_raise_exception(always=True)
-        return space.newbuffer(CPyBuffer(ptr[0], size, w_self))
+        buf = CPyBuffer(space, ptr[0], size, w_self, 
+                               releasebuffer=releasebuffer)
+        fq.register_finalizer(buf)
+        return space.newbuffer(buf)
 
 def wrap_getwritebuffer(space, w_self, w_args, func):
     func_target = rffi.cast(readbufferproc, func)
+    py_obj = make_ref(space, w_self)
+    py_type = py_obj.c_ob_type
+    decref(space, py_obj)
+    releasebuffer = rffi.cast(rffi.VOIDP, 0)
+    if py_type.c_tp_as_buffer:
+        releasebuffer = rffi.cast(rffi.VOIDP, py_type.c_tp_as_buffer.c_bf_releasebuffer)
     with lltype.scoped_alloc(rffi.VOIDPP.TO, 1) as ptr:
         index = rffi.cast(Py_ssize_t, 0)
         size = generic_cpy_call(space, func_target, w_self, index, ptr)
         if size < 0:
             space.fromcache(State).check_and_raise_exception(always=True)
-        return space.newbuffer(CPyBuffer(ptr[0], size, w_self, readonly=False))
+        buf = CPyBuffer(space, ptr[0], size, w_self, readonly=False,
+                               releasebuffer=releasebuffer)
+        fq.register_finalizer(buf)
+        return space.newbuffer(buf)
 
 def wrap_getbuffer(space, w_self, w_args, func):
     func_target = rffi.cast(getbufferproc, func)
+    py_obj = make_ref(space, w_self)
+    py_type = py_obj.c_ob_type
+    releasebuffer = rffi.cast(rffi.VOIDP, 0)
+    if py_type.c_tp_as_buffer:
+        releasebuffer = rffi.cast(rffi.VOIDP, py_type.c_tp_as_buffer.c_bf_releasebuffer)
+    decref(space, py_obj)
     with lltype.scoped_alloc(Py_buffer) as pybuf:
         _flags = 0
         if space.len_w(w_args) > 0:
@@ -408,10 +468,13 @@
             format = rffi.charp2str(pybuf.c_format)
         else:
             format = 'B'
-        return space.newbuffer(CPyBuffer(ptr, size, w_self, format=format,
+        buf = CPyBuffer(space, ptr, size, w_self, format=format,
                             ndim=ndim, shape=shape, strides=strides,
                             itemsize=pybuf.c_itemsize,
-                            readonly=widen(pybuf.c_readonly)))
+                            readonly=widen(pybuf.c_readonly),
+                            releasebuffer = releasebuffer)
+        fq.register_finalizer(buf)
+        return space.newbuffer(buf)
 
 def get_richcmp_func(OP_CONST):
     def inner(space, w_self, w_args, func):
diff --git a/pypy/module/cpyext/stubs.py b/pypy/module/cpyext/stubs.py
--- a/pypy/module/cpyext/stubs.py
+++ b/pypy/module/cpyext/stubs.py
@@ -10,11 +10,7 @@
 PyMethodDef = rffi.VOIDP
 PyGetSetDef = rffi.VOIDP
 PyMemberDef = rffi.VOIDP
-Py_buffer = rffi.VOIDP
 va_list = rffi.VOIDP
-PyDateTime_Date = rffi.VOIDP
-PyDateTime_DateTime = rffi.VOIDP
-PyDateTime_Time = rffi.VOIDP
 wrapperbase = rffi.VOIDP
 FILE = rffi.VOIDP
 PyFileObject = rffi.VOIDP
diff --git a/pypy/module/cpyext/test/buffer_test.c b/pypy/module/cpyext/test/buffer_test.c
--- a/pypy/module/cpyext/test/buffer_test.c
+++ b/pypy/module/cpyext/test/buffer_test.c
@@ -185,19 +185,6 @@
     (initproc)PyMyArray_init,     /* tp_init */
 };
 
-static PyObject*
-test_buffer(PyObject* self, PyObject* args)
-{
-    Py_buffer* view = NULL;
-    PyObject* obj = PyTuple_GetItem(args, 0);
-    PyObject* memoryview = PyMemoryView_FromObject(obj);
-    if (memoryview == NULL)
-        return PyInt_FromLong(-1);
-    view = PyMemoryView_GET_BUFFER(memoryview);
-    Py_DECREF(memoryview);
-    return PyInt_FromLong(view->len);
-}
-
 /* Copied from numpy tests */
 /*
  * Create python string from a FLAG and or the corresponding PyBuf flag
@@ -308,7 +295,6 @@
 
 
 static PyMethodDef buffer_functions[] = {
-    {"test_buffer",   (PyCFunction)test_buffer, METH_VARARGS, NULL},
     {"get_buffer_info",   (PyCFunction)get_buffer_info, METH_VARARGS, NULL},
     {NULL,        NULL}    /* Sentinel */
 };
diff --git a/pypy/module/cpyext/test/test_bufferobject.py b/pypy/module/cpyext/test/test_bufferobject.py
--- a/pypy/module/cpyext/test/test_bufferobject.py
+++ b/pypy/module/cpyext/test/test_bufferobject.py
@@ -64,3 +64,58 @@
         b = buffer(a)
         assert module.roundtrip(b) == 'text'
         
+    def test_releasebuffer(self):
+        module = self.import_extension('foo', [
+            ("create_test", "METH_NOARGS",
+             """
+                PyObject *obj;
+                obj = PyObject_New(PyObject, (PyTypeObject*)type);
+                return obj;
+             """),
+            ("get_cnt", "METH_NOARGS",
+             'return PyLong_FromLong(cnt);')], prologue="""
+                static float test_data = 42.f;
+                static int cnt=0;
+                static PyHeapTypeObject * type=NULL;
+
+                int getbuffer(PyObject *obj, Py_buffer *view, int flags) {
+
+                    cnt ++;
+                    memset(view, 0, sizeof(Py_buffer));
+                    view->obj = obj;
+                    view->ndim = 0;
+                    view->buf = (void *) &test_data;
+                    view->itemsize = sizeof(float);
+                    view->len = 1;
+                    view->strides = NULL;
+                    view->shape = NULL;
+                    view->format = "f";
+                    return 0;
+                }
+
+                void releasebuffer(PyObject *obj, Py_buffer *view) { 
+                    cnt --;
+                }
+            """, more_init="""
+                type = (PyHeapTypeObject *) PyType_Type.tp_alloc(&PyType_Type, 0);
+
+                type->ht_type.tp_name = "Test";
+                type->ht_type.tp_basicsize = sizeof(PyObject);
+                type->ht_name = PyString_FromString("Test");
+                type->ht_type.tp_flags |= Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE |
+                                          Py_TPFLAGS_HEAPTYPE | Py_TPFLAGS_HAVE_NEWBUFFER;
+                type->ht_type.tp_flags &= ~Py_TPFLAGS_HAVE_GC;
+
+                type->ht_type.tp_as_buffer = &type->as_buffer;
+                type->as_buffer.bf_getbuffer = getbuffer;
+                type->as_buffer.bf_releasebuffer = releasebuffer;
+
+                if (PyType_Ready(&type->ht_type) < 0) INITERROR;
+            """, )
+        import gc
+        assert module.get_cnt() == 0
+        a = memoryview(module.create_test())
+        assert module.get_cnt() == 1
+        del a
+        gc.collect(); gc.collect(); gc.collect()
+        assert module.get_cnt() == 0
diff --git a/pypy/module/cpyext/test/test_memoryobject.py b/pypy/module/cpyext/test/test_memoryobject.py
--- a/pypy/module/cpyext/test/test_memoryobject.py
+++ b/pypy/module/cpyext/test/test_memoryobject.py
@@ -20,16 +20,16 @@
     def test_frombuffer(self, space, api):
         w_buf = space.newbuffer(StringBuffer("hello"))
         w_memoryview = api.PyMemoryView_FromObject(w_buf)
-        w_view = api.PyMemoryView_GET_BUFFER(w_memoryview)
-        assert w_view.c_ndim == 1
-        f = rffi.charp2str(w_view.c_format)
+        view = api.PyMemoryView_GET_BUFFER(w_memoryview)
+        assert view.c_ndim == 1
+        f = rffi.charp2str(view.c_format)
         assert f == 'B'
-        assert w_view.c_shape[0] == 5
-        assert w_view.c_strides[0] == 1
-        assert w_view.c_len == 5
-        o = rffi.charp2str(w_view.c_buf)
+        assert view.c_shape[0] == 5
+        assert view.c_strides[0] == 1
+        assert view.c_len == 5
+        o = rffi.charp2str(view.c_buf)
         assert o == 'hello'
-        w_mv = api.PyMemoryView_FromBuffer(w_view)
+        w_mv = api.PyMemoryView_FromBuffer(view)
         for f in ('format', 'itemsize', 'ndim', 'readonly', 
                   'shape', 'strides', 'suboffsets'):
             w_f = space.wrap(f)
@@ -37,7 +37,7 @@
                               space.getattr(w_memoryview, w_f))
 
 class AppTestBufferProtocol(AppTestCpythonExtensionBase):
-    def test_buffer_protocol(self):
+    def test_buffer_protocol_app(self):
         import struct
         module = self.import_module(name='buffer_test')
         arr = module.PyMyArray(10)
@@ -48,8 +48,41 @@
         s = y[3]
         assert len(s) == struct.calcsize('i')
         assert s == struct.pack('i', 3)
-        viewlen = module.test_buffer(arr)
-        assert viewlen == y.itemsize * len(y)
+
+    def test_buffer_protocol_capi(self):
+        foo = self.import_extension('foo', [
+            ("get_len", "METH_VARARGS",
+             """
+                Py_buffer view;
+                PyObject* obj = PyTuple_GetItem(args, 0);
+                long ret, vlen;
+                memset(&view, 0, sizeof(Py_buffer));
+                ret = PyObject_GetBuffer(obj, &view, PyBUF_FULL_RO);
+                if (ret != 0)
+                    return NULL;
+                vlen = view.len / view.itemsize;
+                PyBuffer_Release(&view);
+                return PyInt_FromLong(vlen);
+             """),
+            ("test_buffer", "METH_VARARGS",
+             """
+                Py_buffer* view = NULL;
+                PyObject* obj = PyTuple_GetItem(args, 0);
+                PyObject* memoryview = PyMemoryView_FromObject(obj);
+                if (memoryview == NULL)
+                    return PyInt_FromLong(-1);
+                view = PyMemoryView_GET_BUFFER(memoryview);
+                Py_DECREF(memoryview);
+                return PyInt_FromLong(view->len / view->itemsize);
+            """)])
+        module = self.import_module(name='buffer_test')
+        arr = module.PyMyArray(10)
+        ten = foo.get_len(arr)
+        assert ten == 10
+        ten = foo.get_len('1234567890')
+        assert ten == 10
+        ten = foo.test_buffer(arr)
+        assert ten == 10
 
     @pytest.mark.skipif(only_pypy, reason='pypy only test')
     def test_buffer_info(self):
diff --git a/pypy/module/cpyext/typeobjectdefs.py b/pypy/module/cpyext/typeobjectdefs.py
--- a/pypy/module/cpyext/typeobjectdefs.py
+++ b/pypy/module/cpyext/typeobjectdefs.py
@@ -60,7 +60,7 @@
 segcountproc = P(FT([PyO, Py_ssize_tP], Py_ssize_t))
 charbufferproc = P(FT([PyO, Py_ssize_t, rffi.CCHARPP], Py_ssize_t))
 getbufferproc = P(FT([PyO, Py_bufferP, rffi.INT_real], rffi.INT_real))
-releasebufferproc = rffi.VOIDP
+releasebufferproc = P(FT([PyO, Py_bufferP], Void))
 
 
 PyGetSetDef = cpython_struct("PyGetSetDef", (
diff --git a/pypy/objspace/std/bufferobject.py b/pypy/objspace/std/bufferobject.py
--- a/pypy/objspace/std/bufferobject.py
+++ b/pypy/objspace/std/bufferobject.py
@@ -17,6 +17,9 @@
         assert isinstance(buf, Buffer)
         self.buf = buf
 
+    def _finalize_(self):
+        return self.buf.releasebuffer()
+
     def buffer_w(self, space, flags):
         space.check_buf_flags(flags, self.buf.readonly)
         return self.buf
diff --git a/pypy/objspace/std/objspace.py b/pypy/objspace/std/objspace.py
--- a/pypy/objspace/std/objspace.py
+++ b/pypy/objspace/std/objspace.py
@@ -321,8 +321,9 @@
     def newseqiter(self, w_obj):
         return W_SeqIterObject(w_obj)
 
-    def newbuffer(self, w_obj):
-        return W_Buffer(w_obj)
+    def newbuffer(self, obj):
+        ret = W_Buffer(obj)
+        return ret
 
     def newbytes(self, s):
         return W_BytesObject(s)
diff --git a/rpython/rlib/buffer.py b/rpython/rlib/buffer.py
--- a/rpython/rlib/buffer.py
+++ b/rpython/rlib/buffer.py
@@ -6,7 +6,7 @@
 
 class Buffer(object):
     """Abstract base class for buffers."""
-    __slots__ = ['readonly']
+    _attrs_ = ['readonly']
     _immutable_ = True
 
     def getlength(self):
@@ -75,8 +75,11 @@
     def getstrides(self):
         return [1]
 
+    def releasebuffer(self):
+        pass
+
 class StringBuffer(Buffer):
-    __slots__ = ['value']
+    _attrs_ = ['readonly', 'value']
     _immutable_ = True
 
     def __init__(self, value):
@@ -107,7 +110,7 @@
 
 
 class SubBuffer(Buffer):
-    __slots__ = ['buffer', 'offset', 'size']
+    _attrs_ = ['buffer', 'offset', 'size', 'readonly']
     _immutable_ = True
 
     def __init__(self, buffer, offset, size):


More information about the pypy-commit mailing list