[pypy-commit] cffi default: Implement ffi.from_buffer("foo[]", x)

arigo pypy.commits at gmail.com
Mon Jan 7 18:04:51 EST 2019


Author: Armin Rigo <arigo at tunes.org>
Branch: 
Changeset: r3179:097f3540b5aa
Date: 2019-01-07 23:23 +0100
http://bitbucket.org/cffi/cffi/changeset/097f3540b5aa/

Log:	Implement ffi.from_buffer("foo[]", x)

	Also contains some improvements to the documentation about other
	recent additions

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -175,7 +175,7 @@
 #define CT_IS_FILE             0x00100000
 #define CT_IS_VOID_PTR         0x00200000
 #define CT_WITH_VAR_ARRAY      0x00400000
-#define CT_IS_UNSIZED_CHAR_A   0x00800000
+/* unused                      0x00800000 */
 #define CT_LAZY_FIELD_LIST     0x01000000
 #define CT_WITH_PACKED_CHANGE  0x02000000
 #define CT_IS_SIGNED_WCHAR     0x04000000
@@ -1870,7 +1870,7 @@
         cffi_closure_free(closure);
 #endif
     }
-    else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) {  /* from_buffer */
+    else if (cd->c_type->ct_flags & CT_ARRAY) {         /* from_buffer */
         Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
         PyBuffer_Release(view);
         PyObject_Free(view);
@@ -1889,7 +1889,7 @@
         PyObject *args = (PyObject *)(closure->user_data);
         Py_VISIT(args);
     }
-    else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) {  /* from_buffer */
+    else if (cd->c_type->ct_flags & CT_ARRAY) {         /* from_buffer */
         Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
         Py_VISIT(view->obj);
     }
@@ -1911,7 +1911,7 @@
         closure->user_data = NULL;
         Py_XDECREF(args);
     }
-    else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) {  /* from_buffer */
+    else if (cd->c_type->ct_flags & CT_ARRAY) {         /* from_buffer */
         Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
         PyBuffer_Release(view);
     }
@@ -2125,7 +2125,7 @@
         else
             return _cdata_repr2(cd, "calling", PyTuple_GET_ITEM(args, 1));
     }
-    else if (cd->c_type->ct_flags & CT_IS_UNSIZED_CHAR_A) {  /* from_buffer */
+    else if (cd->c_type->ct_flags & CT_ARRAY) {         /* from_buffer */
         Py_buffer *view = ((CDataObject_owngc_frombuf *)cd)->bufferview;
         Py_ssize_t buflen = get_array_length(cd);
         return PyText_FromFormat(
@@ -2369,7 +2369,7 @@
     else if (cd->c_type->ct_flags & CT_ARRAY) {
         if (i < 0) {
             PyErr_SetString(PyExc_IndexError,
-                            "negative index not supported");
+                            "negative index");
             return NULL;
         }
         if (i >= get_array_length(cd)) {
@@ -2422,7 +2422,7 @@
     if (ct->ct_flags & CT_ARRAY) {
         if (start < 0) {
             PyErr_SetString(PyExc_IndexError,
-                            "negative index not supported");
+                            "negative index");
             return NULL;
         }
         if (stop > get_array_length(cd)) {
@@ -3140,7 +3140,7 @@
             return 0;
     }
     else if (Py_TYPE(cd) == &CDataOwningGC_Type) {
-        if (ct->ct_flags & CT_IS_UNSIZED_CHAR_A)   /* ffi.from_buffer() */
+        if (ct->ct_flags & CT_ARRAY)      /* ffi.from_buffer() */
             return 1;
     }
     else if (Py_TYPE(cd) == &CDataGCP_Type) {
@@ -3309,24 +3309,24 @@
     0,                                          /* tp_setattr */
     0,                                          /* tp_compare */
     (reprfunc)cdataowning_repr,                 /* tp_repr */
-    0,                                          /* tp_as_number */
+    0,  /* inherited */                         /* tp_as_number */
     0,                                          /* tp_as_sequence */
     &CDataOwn_as_mapping,                       /* tp_as_mapping */
-    0,                                          /* tp_hash */
-    0,                                          /* tp_call */
+    0,  /* inherited */                         /* tp_hash */
+    0,  /* inherited */                         /* tp_call */
     0,                                          /* tp_str */
-    0,                                          /* tp_getattro */
-    0,                                          /* tp_setattro */
+    0,  /* inherited */                         /* tp_getattro */
+    0,  /* inherited */                         /* tp_setattro */
     0,                                          /* tp_as_buffer */
     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES, /* tp_flags */
     0,                                          /* tp_doc */
     0,                                          /* tp_traverse */
     0,                                          /* tp_clear */
-    0,                                          /* tp_richcompare */
-    0,                                          /* tp_weaklistoffset */
-    0,                                          /* tp_iter */
+    0,  /* inherited */                         /* tp_richcompare */
+    0,  /* inherited */                         /* tp_weaklistoffset */
+    0,  /* inherited */                         /* tp_iter */
     0,                                          /* tp_iternext */
-    0,                                          /* tp_methods */
+    0,  /* inherited */                         /* tp_methods */
     0,                                          /* tp_members */
     0,                                          /* tp_getset */
     &CData_Type,                                /* tp_base */
@@ -3351,25 +3351,25 @@
     0,                                          /* tp_setattr */
     0,                                          /* tp_compare */
     (reprfunc)cdataowninggc_repr,               /* tp_repr */
-    0,                                          /* tp_as_number */
+    0,  /* inherited */                         /* tp_as_number */
     0,                                          /* tp_as_sequence */
-    0,                                          /* tp_as_mapping */
-    0,                                          /* tp_hash */
-    0,                                          /* tp_call */
+    0,  /* inherited */                         /* tp_as_mapping */
+    0,  /* inherited */                         /* tp_hash */
+    0,  /* inherited */                         /* tp_call */
     0,                                          /* tp_str */
-    0,                                          /* tp_getattro */
-    0,                                          /* tp_setattro */
+    0,  /* inherited */                         /* tp_getattro */
+    0,  /* inherited */                         /* tp_setattro */
     0,                                          /* tp_as_buffer */
     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES  /* tp_flags */
                        | Py_TPFLAGS_HAVE_GC,
     0,                                          /* tp_doc */
     (traverseproc)cdataowninggc_traverse,       /* tp_traverse */
     (inquiry)cdataowninggc_clear,               /* tp_clear */
-    0,                                          /* tp_richcompare */
-    0,                                          /* tp_weaklistoffset */
-    0,                                          /* tp_iter */
+    0,  /* inherited */                         /* tp_richcompare */
+    0,  /* inherited */                         /* tp_weaklistoffset */
+    0,  /* inherited */                         /* tp_iter */
     0,                                          /* tp_iternext */
-    0,                                          /* tp_methods */
+    0,  /* inherited */                         /* tp_methods */
     0,                                          /* tp_members */
     0,                                          /* tp_getset */
     &CDataOwning_Type,                          /* tp_base */
@@ -3393,15 +3393,15 @@
     0,                                          /* tp_getattr */
     0,                                          /* tp_setattr */
     0,                                          /* tp_compare */
-    0,                                          /* tp_repr */
-    0,                                          /* tp_as_number */
+    0,  /* inherited */                         /* tp_repr */
+    0,  /* inherited */                         /* tp_as_number */
     0,                                          /* tp_as_sequence */
-    0,                                          /* tp_as_mapping */
-    0,                                          /* tp_hash */
-    0,                                          /* tp_call */
+    0,  /* inherited */                         /* tp_as_mapping */
+    0,  /* inherited */                         /* tp_hash */
+    0,  /* inherited */                         /* tp_call */
     0,                                          /* tp_str */
-    0,                                          /* tp_getattro */
-    0,                                          /* tp_setattro */
+    0,  /* inherited */                         /* tp_getattro */
+    0,  /* inherited */                         /* tp_setattro */
     0,                                          /* tp_as_buffer */
     Py_TPFLAGS_DEFAULT | Py_TPFLAGS_CHECKTYPES  /* tp_flags */
 #ifdef Py_TPFLAGS_HAVE_FINALIZE
@@ -3411,11 +3411,11 @@
     0,                                          /* tp_doc */
     (traverseproc)cdatagcp_traverse,            /* tp_traverse */
     0,                                          /* tp_clear */
-    0,                                          /* tp_richcompare */
-    0,                                          /* tp_weaklistoffset */
-    0,                                          /* tp_iter */
+    0,  /* inherited */                         /* tp_richcompare */
+    0,  /* inherited */                         /* tp_weaklistoffset */
+    0,  /* inherited */                         /* tp_iter */
     0,                                          /* tp_iternext */
-    0,                                          /* tp_methods */
+    0,  /* inherited */                         /* tp_methods */
     0,                                          /* tp_members */
     0,                                          /* tp_getset */
     &CData_Type,                                /* tp_base */
@@ -3427,7 +3427,7 @@
     0,                                          /* tp_init */
     0,                                          /* tp_alloc */
     0,                                          /* tp_new */
-    0,                                          /* tp_free */
+    0,  /* inherited */                         /* tp_free */
     0,                                          /* tp_is_gc */
     0,                                          /* tp_bases */
     0,                                          /* tp_mro */
@@ -3527,6 +3527,8 @@
                                            CTypeDescrObject *ct,
                                            int dont_clear)
 {
+    /* note: objects with &CDataOwning_Type are always allocated with
+       either a plain malloc() or calloc(), and freed with free(). */
     CDataObject *cd;
     if (dont_clear)
         cd = malloc(size);
@@ -4688,9 +4690,6 @@
         sprintf(extra_text, "[]");
         length = -1;
         arraysize = -1;
-        if ((ctitem->ct_flags & CT_PRIMITIVE_CHAR) &&
-                ctitem->ct_size == sizeof(char))
-            flags |= CT_IS_UNSIZED_CHAR_A;
     }
     else {
         sprintf(extra_text, "[%llu]", (unsigned PY_LONG_LONG)length);
@@ -6815,6 +6814,13 @@
 {
     CDataObject *cd;
     Py_buffer *view;
+    Py_ssize_t arraylength;
+
+    if (!(ct->ct_flags & CT_ARRAY)) {
+        PyErr_Format(PyExc_TypeError, "expected an array ctype, got '%s'",
+                     ct->ct_name);
+        return NULL;
+    }
 
     /* PyPy 5.7 can obtain buffers for string (python 2)
        or bytes (python 3). from_buffer(u"foo") is disallowed.
@@ -6834,6 +6840,41 @@
     if (_my_PyObject_GetContiguousBuffer(x, view, require_writable) < 0)
         goto error1;
 
+    if (ct->ct_length >= 0) {
+        /* it's an array with a fixed length; make sure that the
+           buffer contains enough bytes. */
+        if (view->len < ct->ct_size) {
+            PyErr_Format(PyExc_ValueError,
+                "buffer is too small (%zd bytes) for '%s' (%zd bytes)",
+                view->len, ct->ct_name, ct->ct_size);
+            goto error1;
+        }
+        arraylength = ct->ct_length;
+    }
+    else {
+        /* it's an open 'array[]' */
+        if (ct->ct_itemdescr->ct_size == 1) {
+            /* fast path, performance only */
+            arraylength = view->len;
+        }
+        else if (ct->ct_itemdescr->ct_size > 0) {
+            /* give it as many items as fit the buffer.  Ignore a
+               partial last element. */
+            arraylength = view->len / ct->ct_itemdescr->ct_size;
+        }
+        else {
+            /* it's an array 'empty[]'.  Unsupported obscure case:
+               the problem is that setting the length of the result
+               to anything large (like SSIZE_T_MAX) is dangerous,
+               because if someone tries to loop over it, it will
+               turn effectively into an infinite loop. */
+            PyErr_Format(PyExc_ZeroDivisionError,
+                "from_buffer('%s', ..): the actual length of the array "
+                "cannot be computed", ct->ct_name);
+            goto error1;
+        }
+    }
+
     cd = (CDataObject *)PyObject_GC_New(CDataObject_owngc_frombuf,
                                         &CDataOwningGC_Type);
     if (cd == NULL)
@@ -6843,7 +6884,7 @@
     cd->c_type = ct;
     cd->c_data = view->buf;
     cd->c_weakreflist = NULL;
-    ((CDataObject_owngc_frombuf *)cd)->length = view->len;
+    ((CDataObject_owngc_frombuf *)cd)->length = arraylength;
     ((CDataObject_owngc_frombuf *)cd)->bufferview = view;
     PyObject_GC_Track(cd);
     return (PyObject *)cd;
@@ -6865,10 +6906,6 @@
                           &require_writable))
         return NULL;
 
-    if (!(ct->ct_flags & CT_IS_UNSIZED_CHAR_A)) {
-        PyErr_Format(PyExc_TypeError, "needs 'char[]', got '%s'", ct->ct_name);
-        return NULL;
-    }
     return direct_from_buffer(ct, x, require_writable);
 }
 
diff --git a/c/ffi_obj.c b/c/ffi_obj.c
--- a/c/ffi_obj.c
+++ b/c/ffi_obj.c
@@ -697,16 +697,29 @@
 "containing large quantities of raw data in some other format, like\n"
 "'array.array' or numpy arrays.");
 
-static PyObject *ffi_from_buffer(PyObject *self, PyObject *args, PyObject *kwds)
+static PyObject *ffi_from_buffer(FFIObject *self, PyObject *args,
+                                 PyObject *kwds)
 {
-    PyObject *arg;
+    PyObject *cdecl, *python_buf = NULL;
+    CTypeDescrObject *ct;
     int require_writable = 0;
-    static char *keywords[] = {"python_buffer", "require_writable", NULL};
+    static char *keywords[] = {"cdecl", "python_buffer",
+                               "require_writable", NULL};
 
-    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|i:from_buffer", keywords,
-                                     &arg, &require_writable))
+    if (!PyArg_ParseTupleAndKeywords(args, kwds, "O|Oi:from_buffer", keywords,
+                                     &cdecl, &python_buf, &require_writable))
         return NULL;
-    return direct_from_buffer(g_ct_chararray, arg, require_writable);
+
+    if (python_buf == NULL) {
+        python_buf = cdecl;
+        ct = g_ct_chararray;
+    }
+    else {
+        ct = _ffi_type(self, cdecl, ACCEPT_STRING|ACCEPT_CTYPE);
+        if (ct == NULL)
+            return NULL;
+    }
+    return direct_from_buffer(ct, python_buf, require_writable);
 }
 
 PyDoc_STRVAR(ffi_gc_doc,
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -3753,6 +3753,64 @@
     p1[0] = b"g"
     assert ba == b"goo"
 
+def test_from_buffer_types():
+    BInt = new_primitive_type("int")
+    BIntP = new_pointer_type(BInt)
+    BIntA = new_array_type(BIntP, None)
+    lst = [-12345678, 87654321, 489148]
+    bytestring = buffer(newp(BIntA, lst))[:] + b'XYZ'
+    #
+    p1 = from_buffer(BIntA, bytestring)      # int[]
+    assert typeof(p1) is BIntA
+    assert len(p1) == 3
+    assert p1[0] == lst[0]
+    assert p1[1] == lst[1]
+    assert p1[2] == lst[2]
+    py.test.raises(IndexError, "p1[3]")
+    py.test.raises(IndexError, "p1[-1]")
+    #
+    py.test.raises(TypeError, from_buffer, BInt, bytestring)
+    py.test.raises(TypeError, from_buffer, BIntP, bytestring)
+    #
+    BIntA2 = new_array_type(BIntP, 2)
+    p2 = from_buffer(BIntA2, bytestring)     # int[2]
+    assert typeof(p2) is BIntA2
+    assert len(p2) == 2
+    assert p2[0] == lst[0]
+    assert p2[1] == lst[1]
+    py.test.raises(IndexError, "p2[2]")
+    py.test.raises(IndexError, "p2[-1]")
+    assert p2 == p1
+    #
+    BIntA4 = new_array_type(BIntP, 4)        # int[4]: too big
+    py.test.raises(ValueError, from_buffer, BIntA4, bytestring)
+    #
+    BStruct = new_struct_type("foo")
+    complete_struct_or_union(BStruct, [('a1', BInt, -1),
+                                       ('a2', BInt, -1)])
+    BStructP = new_pointer_type(BStruct)
+    BStructA = new_array_type(BStructP, None)
+    p1 = from_buffer(BStructA, bytestring)   # struct[]
+    assert len(p1) == 1
+    assert typeof(p1) is BStructA
+    assert p1[0].a1 == lst[0]
+    assert p1[0].a2 == lst[1]
+    py.test.raises(IndexError, "p1[1]")
+    #
+    BEmptyStruct = new_struct_type("empty")
+    complete_struct_or_union(BEmptyStruct, [], Ellipsis, 0)
+    assert sizeof(BEmptyStruct) == 0
+    BEmptyStructP = new_pointer_type(BEmptyStruct)
+    BEmptyStructA = new_array_type(BEmptyStructP, None)
+    py.test.raises(ZeroDivisionError, from_buffer,      # empty[]
+                                      BEmptyStructA, bytestring)
+    #
+    BEmptyStructA5 = new_array_type(BEmptyStructP, 5)
+    p1 = from_buffer(BEmptyStructA5, bytestring)   # struct empty[5]
+    assert typeof(p1) is BEmptyStructA5
+    assert len(p1) == 5
+    assert cast(BIntP, p1) == from_buffer(BIntA, bytestring)
+
 def test_memmove():
     Short = new_primitive_type("short")
     ShortA = new_array_type(new_pointer_type(Short), None)
diff --git a/cffi/api.py b/cffi/api.py
--- a/cffi/api.py
+++ b/cffi/api.py
@@ -16,6 +16,8 @@
     # Python 3.x
     basestring = str
 
+_unspecified = object()
+
 
 
 class FFI(object):
@@ -341,15 +343,22 @@
    #    """
    #    note that 'buffer' is a type, set on this instance by __init__
 
-    def from_buffer(self, python_buffer, require_writable=False):
-        """Return a <cdata 'char[]'> that points to the data of the
+    def from_buffer(self, cdecl, python_buffer=_unspecified,
+                    require_writable=False):
+        """Return a cdata of the given type pointing to the data of the
         given Python object, which must support the buffer interface.
         Note that this is not meant to be used on the built-in types
         str or unicode (you can build 'char[]' arrays explicitly)
         but only on objects containing large quantities of raw data
         in some other format, like 'array.array' or numpy arrays.
+
+        The first argument is optional and default to 'char[]'.
         """
-        return self._backend.from_buffer(self.BCharA, python_buffer,
+        if python_buffer is _unspecified:
+            cdecl, python_buffer = self.BCharA, cdecl
+        elif isinstance(cdecl, basestring):
+            cdecl = self._typeof(cdecl)
+        return self._backend.from_buffer(cdecl, python_buffer,
                                          require_writable)
 
     def memmove(self, dest, src, n):
diff --git a/doc/source/ref.rst b/doc/source/ref.rst
--- a/doc/source/ref.rst
+++ b/doc/source/ref.rst
@@ -60,7 +60,7 @@
 `ffi.new_allocator()`_ for a way to allocate non-zero-initialized
 memory.
 
-*New in version 1.12:* see ``ffi.release()``.
+*New in version 1.12:* see also ``ffi.release()``.
 
 
 ffi.cast()
@@ -190,8 +190,8 @@
 *New in version 1.10:* ``ffi.buffer`` is now the type of the returned
 buffer objects; ``ffi.buffer()`` actually calls the constructor.
 
-**ffi.from_buffer(python_buffer, require_writable=False)**:
-return a ``<cdata 'char[]'>`` that
+**ffi.from_buffer([cdecl,] python_buffer, require_writable=False)**:
+return an array cdata (by default a ``<cdata 'char[]'>``) that
 points to the data of the given Python object, which must support the
 buffer interface.  This is the opposite of ``ffi.buffer()``.  It gives
 a reference to the existing data, not a copy.
@@ -219,19 +219,33 @@
 resize the bytearray, the ``<cdata>`` object will point to freed
 memory); and byte strings were supported in version 1.8 onwards.
 
-*New in version 1.12:* added the ``require_writable`` argument.  If set to
-True, the function fails if the buffer obtained from ``python_buffer`` is
-read-only (e.g. if ``python_buffer`` is a byte string).  The exact exception is
-raised by the object itself, and for things like bytes it varies with the
-Python version, so don't rely on it.  (Before version 1.12, the same effect can
-be achieved with a hack: call ``ffi.memmove(python_buffer, b"", 0)``.  This has
-no effect if the object is writable, but fails if it is read-only.)
+*New in version 1.12:* added the optional *first* argument ``cdecl``, and
+the keyword argument ``require_writable``:
 
-Please keep in mind that CFFI does not implement the C keyword ``const``: even
-if you set ``require_writable`` to False explicitly, you still get a regular
-read-write cdata pointer.
+* ``cdecl`` defaults to ``"char[]"``, but a different array type can be
+  specified for the result.  A value like ``"int[]"`` will return an array of
+  ints instead of chars, and its length will be set to the number of ints
+  that fit in the buffer (rounded down if the division is not exact).  Values
+  like ``"int[42]"`` or ``"int[2][3]"`` will return an array of exactly 42
+  (resp. 2-by-3) ints, raising a ValueError if the buffer is too small.  The
+  difference between specifying ``"int[]"`` and using the older code ``p1 =
+  ffi.from_buffer(x); p2 = ffi.cast("int *", p1)`` is that the older code
+  needs to keep ``p1`` alive as long as ``p2`` is in use, because only ``p1``
+  keeps the underlying Python object alive and locked.  (In addition,
+  ``ffi.from_buffer("int[]", x)`` gives better array bound checking.)
 
-*New in version 1.12:* see ``ffi.release()``.
+* if ``require_writable`` is set to True, the function fails if the buffer
+  obtained from ``python_buffer`` is read-only (e.g. if ``python_buffer`` is
+  a byte string).  The exact exception is raised by the object itself, and
+  for things like bytes it varies with the Python version, so don't rely on
+  it.  (Before version 1.12, the same effect can be achieved with a hack:
+  call ``ffi.memmove(python_buffer, b"", 0)``.  This has no effect if the
+  object is writable, but fails if it is read-only.)  Please keep in mind
+  that CFFI does not implement the C keyword ``const``: even if you set
+  ``require_writable`` to False explicitly, you still get a regular
+  read-write cdata pointer.
+
+*New in version 1.12:* see also ``ffi.release()``.
 
 
 ffi.memmove()
@@ -387,7 +401,7 @@
 which means the destructor is called as soon as *this* exact returned
 object is garbage-collected.
 
-*New in version 1.12:* see ``ffi.release()``.
+*New in version 1.12:* see also ``ffi.release()``.
 
 **ffi.gc(ptr, None, size=0)**:
 removes the ownership on a object returned by a
@@ -569,21 +583,34 @@
 
 and then call these two functions manually::
 
-    p = lib.malloc(bigsize)
+    p = lib.malloc(n * ffi.sizeof("int"))
     try:
-        my_array = ffi.cast("some_other_type_than_void*", p)
+        my_array = ffi.cast("int *", p)
         ...
     finally:
         lib.free(p)
 
+In cffi version 1.12 you can indeed use ``ffi.new_allocator()`` but use the
+``with`` statement (see ``ffi.release()``) to force the free function to be
+called at a known point.  The above is equivalent to this code::
 
-ffi.release()
-+++++++++++++
+    my_new = ffi.new_allocator(lib.malloc, lib.free)  # at global level
+    ...
+    with my_new("int[]", n) as my_array:
+        ...
 
-**ffi.release(cdata)**: release now the resources held by a cdata object from
-  ``ffi.new()``, ``ffi.gc()``, ``ffi.from_buffer()`` or
-  ``ffi.new_allocator()()``.  The cdata object must not be used afterwards.
-  *New in version 1.12.*
+
+.. _ffi-release:
+
+ffi.release() and the context manager
++++++++++++++++++++++++++++++++++++++
+
+**ffi.release(cdata)**: release the resources held by a cdata object from
+``ffi.new()``, ``ffi.gc()``, ``ffi.from_buffer()`` or
+``ffi.new_allocator()()``.  The cdata object must not be used afterwards.
+The regular destructor of the cdata object releases the same resources,
+but this allows the operation to occur at a known time.
+*New in version 1.12.*
 
 ``ffi.release(cdata)`` is equivalent to ``cdata.__exit__()``, which means that
 you can use the ``with`` statement to ensure that the cdata is released at the
@@ -592,6 +619,8 @@
     with ffi.from_buffer(...) as p:
         do something with p
 
+The effect is more precisely as follows:
+
 * on an object returned from ``ffi.gc(destructor)``, ``ffi.release()`` will
   cause the ``destructor`` to be called immediately.
 
@@ -599,8 +628,8 @@
   is called immediately.
 
 * on CPython, ``ffi.from_buffer(buf)`` locks the buffer, so ``ffi.release()``
-  unlocks it at a deterministic point.  On PyPy, there is no locking (so far)
-  so this has no effect.
+  can be used to unlock it at a known time.  On PyPy, there is no locking
+  (so far) so this has no effect.
 
 * on CPython this method has no effect (so far) on objects returned by
   ``ffi.new()``, because the memory is allocated inline with the cdata object
@@ -615,6 +644,11 @@
   anyway; but calling ``ffi.release()`` explicitly should improve performance
   by reducing the frequency of GC runs.
 
+After ``ffi.release(x)``, do not use anything pointed to by ``x`` any longer.
+As an exception to this rule, you can call ``ffi.release(x)`` several times
+for the exact same cdata object ``x``; the calls after the first one are
+ignored.
+
 
 ffi.init_once()
 +++++++++++++++
diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst
--- a/doc/source/whatsnew.rst
+++ b/doc/source/whatsnew.rst
@@ -23,8 +23,9 @@
 
 * CPython 2.x: ``ffi.dlopen()`` failed with non-ascii file names on Posix
 
-* ``ffi.from_buffer()`` takes a new keyword argument ``require_writable``.
-  When set to True, it asks the object passed in to raise an exception if
+* ``ffi.from_buffer()`` takes two new arguments: an optional *first* argument
+  gives the array type of the result; and the keyword argument
+  ``require_writable`` can ask the object passed in to raise an exception if
   it is read-only.
 
 * ``ffi.new()``, ``ffi.gc()`` or ``ffi.from_buffer()`` cdata objects
diff --git a/testing/cffi0/test_ffi_backend.py b/testing/cffi0/test_ffi_backend.py
--- a/testing/cffi0/test_ffi_backend.py
+++ b/testing/cffi0/test_ffi_backend.py
@@ -324,16 +324,22 @@
         a = array.array('H', [10000, 20000, 30000])
         c = ffi.from_buffer(a)
         assert ffi.typeof(c) is ffi.typeof("char[]")
+        assert len(c) == 6
         ffi.cast("unsigned short *", c)[1] += 500
         assert list(a) == [10000, 20500, 30000]
-        assert c == ffi.from_buffer(a, True)
+        assert c == ffi.from_buffer("char[]", a, True)
         assert c == ffi.from_buffer(a, require_writable=True)
         #
+        c = ffi.from_buffer("unsigned short[]", a)
+        assert len(c) == 3
+        assert c[1] == 20500
+        #
         p = ffi.from_buffer(b"abcd")
         assert p[2] == b"c"
         #
-        assert p == ffi.from_buffer(b"abcd", False)
-        py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd", True)
+        assert p == ffi.from_buffer(b"abcd", require_writable=False)
+        py.test.raises((TypeError, BufferError), ffi.from_buffer,
+                                                 "char[]", b"abcd", True)
         py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd",
                                                  require_writable=True)
 
diff --git a/testing/cffi1/test_ffi_obj.py b/testing/cffi1/test_ffi_obj.py
--- a/testing/cffi1/test_ffi_obj.py
+++ b/testing/cffi1/test_ffi_obj.py
@@ -238,19 +238,31 @@
 def test_ffi_from_buffer():
     import array
     ffi = _cffi1_backend.FFI()
-    a = array.array('H', [10000, 20000, 30000])
+    a = array.array('H', [10000, 20000, 30000, 40000])
     c = ffi.from_buffer(a)
     assert ffi.typeof(c) is ffi.typeof("char[]")
+    assert len(c) == 8
     ffi.cast("unsigned short *", c)[1] += 500
-    assert list(a) == [10000, 20500, 30000]
-    assert c == ffi.from_buffer(a, True)
+    assert list(a) == [10000, 20500, 30000, 40000]
+    py.test.raises(TypeError, ffi.from_buffer, a, True)
+    assert c == ffi.from_buffer("char[]", a, True)
     assert c == ffi.from_buffer(a, require_writable=True)
     #
+    c = ffi.from_buffer("unsigned short[]", a)
+    assert len(c) == 4
+    assert c[1] == 20500
+    #
+    c = ffi.from_buffer("unsigned short[2][2]", a)
+    assert len(c) == 2
+    assert len(c[0]) == 2
+    assert c[0][1] == 20500
+    #
     p = ffi.from_buffer(b"abcd")
     assert p[2] == b"c"
     #
-    assert p == ffi.from_buffer(b"abcd", False)
-    py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd", True)
+    assert p == ffi.from_buffer(b"abcd", require_writable=False)
+    py.test.raises((TypeError, BufferError), ffi.from_buffer,
+                                             "char[]", b"abcd", True)
     py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd",
                                              require_writable=True)
 
diff --git a/testing/cffi1/test_new_ffi_1.py b/testing/cffi1/test_new_ffi_1.py
--- a/testing/cffi1/test_new_ffi_1.py
+++ b/testing/cffi1/test_new_ffi_1.py
@@ -1675,24 +1675,6 @@
         py.test.raises(TypeError, len, q.a)
         py.test.raises(TypeError, list, q.a)
 
-    def test_from_buffer(self):
-        import array
-        a = array.array('H', [10000, 20000, 30000])
-        c = ffi.from_buffer(a)
-        assert ffi.typeof(c) is ffi.typeof("char[]")
-        ffi.cast("unsigned short *", c)[1] += 500
-        assert list(a) == [10000, 20500, 30000]
-        assert c == ffi.from_buffer(a, True)
-        assert c == ffi.from_buffer(a, require_writable=True)
-        #
-        p = ffi.from_buffer(b"abcd")
-        assert p[2] == b"c"
-        #
-        assert p == ffi.from_buffer(b"abcd", False)
-        py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd", True)
-        py.test.raises((TypeError, BufferError), ffi.from_buffer, b"abcd",
-                                                 require_writable=True)
-
     def test_all_primitives(self):
         assert set(PRIMITIVE_TO_INDEX) == set([
             "char",


More information about the pypy-commit mailing list