Commits

Lenard Lindstrom committed 440c63e

Let PgObject_GetBuffer check for an array struct after a bad array interface

The PgObject_GetBuffer C api function would return with an exception if
an object had a __array_struct__ property that failed to return a wrapped
PyArrayInterface struct. Now it clears any exceptions and continues on to check
for an array interface.

As a side benefit, PgObject_GetBuffer no longer uses PyObject_HasAttrString,
which calls PyObject_GetAttrString and discards the returned object. This
meant PgObject_GetBuffer could retrieve a __array_struct__ or
__array_interface__ attribute twice per call, executing related property code
twice as well. Now __array_struct__ and __array_interface__ are retrieved at
most once per PgObject_GetBuffer call.

Comments (0)

Files changed (2)

 static void atexit_quit (void);
 static int PyGame_Video_AutoInit (void);
 static void PyGame_Video_AutoQuit (void);
-static int GetArrayInterface (PyObject*, PyObject**, PyArrayInterface**);
+static int GetArrayStruct (PyObject*, PyObject**, PyArrayInterface**);
 static PyObject* ArrayStructAsDict (PyArrayInterface*);
 static PyObject* PgBuffer_AsArrayInterface (Py_buffer*);
 static PyObject* PgBuffer_AsArrayStruct (Py_buffer*);
 static int _buffer_is_byteswapped (Py_buffer*);
 static void PgBuffer_Release (Pg_buffer*);
 static int PgObject_GetBuffer (PyObject*, Pg_buffer*, int);
-static int ArrayInterface_AsView (Pg_buffer*, PyObject*, int);
+static int GetArrayInterface (PyObject**, PyObject*);
 static int PgDict_AsBuffer (Pg_buffer*, PyObject*, int);
 static int _shape_arg_convert (PyObject *, Py_buffer*);
 static int _typestr_arg_convert (PyObject *, Py_buffer*);
 /*array interface*/
 
 static int
-GetArrayInterface (PyObject* obj,
+GetArrayStruct (PyObject* obj,
                    PyObject** cobj_p,
                    PyArrayInterface** inter_p)
 {
     PyArrayInterface *inter_p;
     PyObject *dictobj;
 
-    if (GetArrayInterface (arg, &cobj, &inter_p)) {
+    if (GetArrayStruct (arg, &cobj, &inter_p)) {
         return 0;
     }
     dictobj = ArrayStructAsDict (inter_p);
 {
     Py_buffer* view_p = (Py_buffer*)pg_view_p;
     PyObject* cobj = 0;
+    PyObject* dict = 0;
     PyArrayInterface* inter_p = 0;
     ViewInternals* internal_p;
     size_t sz;
     char *fchar_p;
     Py_ssize_t i;
+    int success = 0;
 
     pg_view_p->release_buffer = _release_buffer_generic;
     view_p->len = 0;
                              "Arrays of records are unsupported");
             return -1;
         }
+        success = 1;
     }
-    else
+
 #endif
-    if (PyObject_HasAttrString (obj, "__array_struct__")) {
-        if (GetArrayInterface (obj, &cobj, &inter_p)) {
-            return -1;
-        }
+    if (!success && GetArrayStruct (obj, &cobj, &inter_p) == 0) {
         sz = (sizeof (ViewInternals) + 
               (2 * inter_p->nd - 1) * sizeof (Py_ssize_t));
         internal_p = (ViewInternals*)PyMem_Malloc (sz);
             view_p->len *= view_p->shape[i];
         }
         pg_view_p->release_buffer = _release_buffer_array;
+        success = 1;
     }
-    else if (PyObject_HasAttrString (obj, "__array_interface__")) {
-        if (ArrayInterface_AsView (pg_view_p, obj, flags)) {
+    else if (!success) {
+        PyErr_Clear ();
+    }
+
+    if (!success && GetArrayInterface (&dict, obj) == 0) {
+        if (PgDict_AsBuffer (pg_view_p, dict, flags)) {
             return -1;
         }
+        Py_INCREF (obj);
+        view_p->obj = obj;
+        success = 1;
     }
-    else {
+    else if (!success) {
+        PyErr_Clear ();
+    }
+
+    if (!success) {
         PyErr_Format (PyExc_TypeError,
-                      "%s object does not export a C level array buffer",
+                      "%s object does not export an array buffer",
                       Py_TYPE (obj)->tp_name);
         return -1;
     }
+
     if (!view_p->len) {
         view_p->len = view_p->itemsize;
         for (i = 0; i < view_p->ndim; ++i) {
 }
 
 static int
-ArrayInterface_AsView (Pg_buffer* pg_view_p, PyObject* obj, int flags)
+GetArrayInterface (PyObject **dict, PyObject *obj)
 {
-    PyObject* dict = PyObject_GetAttrString (obj, "__array_interface__");
+    PyObject* inter = PyObject_GetAttrString (obj, "__array_interface__");
 
-    if (dict == NULL) {
+    if (inter == NULL) {
         if (PyErr_ExceptionMatches (PyExc_AttributeError)) {
                 PyErr_Clear ();
                 PyErr_SetString (PyExc_ValueError, "no array interface");
         }
         return -1;
     }
-    if (!PyDict_Check (dict)) {
+    if (!PyDict_Check (inter)) {
         PyErr_Format (PyExc_ValueError,
                       "expected __array_interface__ to return a dict: got a %s",
                       Py_TYPE (dict)->tp_name);
-        Py_DECREF (dict);
+        Py_DECREF (inter);
         return -1;
     }
-    if (PgDict_AsBuffer (pg_view_p, dict, flags)) {
-        Py_DECREF (dict);
-        return -1;
-    }
-    Py_INCREF (obj);
-    ((Py_buffer*)pg_view_p)->obj = obj;
-    Py_DECREF (dict);
+    *dict = inter;
     return 0;
 }
 
     c_api[12] = RGBAFromObj;
     c_api[13] = ArrayStructAsDict;
     c_api[14] = PgBuffer_AsArrayInterface;
-    c_api[15] = GetArrayInterface;
+    c_api[15] = GetArrayStruct;
     c_api[16] = PgBuffer_AsArrayStruct;
     c_api[17] = PgObject_GetBuffer;
     c_api[18] = PgBuffer_Release;
         MODINIT_ERROR;
     }
 
+    if (PyModule_AddIntConstant (module, "HAVE_NEWBUF", PG_ENABLE_NEWBUF)) {
+        Py_XDECREF (atexit_register);
+        Py_DECREF (PgExc_BufferError);
+        DECREF_MOD (module);
+        MODINIT_ERROR;
+    }
+
     if (!is_loaded) {
         /*some intialization*/
         quit = PyObject_GetAttrString (module, "quit");
 
         class Exporter(self.ExporterBase):
             def get__array_interface__(self):
-                return {'typestr': self.typestr,
+                return {'version': 3,
+                        'typestr': self.typestr,
                         'shape': self.shape,
                         'strides': self.strides,
                         'data': self.data}
             __array_interface__ = property(get__array_interface__)
+            # Should be ignored by PgObject_GetBuffer
+            __array_struct__ = property(lambda self: None)
 
         _shape = [2, 3, 5, 7, 11]  # Some prime numbers
         for ndim in range(1, len(_shape)):
             def get__array_struct__(self):
                 return self.view.__array_struct__
             __array_struct__ = property(get__array_struct__)
+            # Should not cause PgObject_GetBuffer to fail
+            __array_interface__ = property(lambda self: None)
 
         _shape = [2, 3, 5, 7, 11]  # Some prime numbers
         for ndim in range(1, len(_shape)):
             v = BufferProxy(o)
             self.assertSame(v, o)
 
-    def test_GetView_newbuf(self):
-        self.fail()
-    if sys.version_info < (3, 0):
-        del test_GetView_newbuf
-        
+    if (pygame.HAVE_NEWBUF):
+        def test_GetView_newbuf(self):
+            self.NEWBUF_test_GetView_newbuf()
+
+    def NEWBUF_test_GetView_newbuf(self):
+        from pygame.bufferproxy import BufferProxy
+
+        def exporter(shape, typechar, itemsize):
+            # Use a BufferProxy object as the new buffer exporter, as none
+            # of the Python standard types are multidimensional arrays.
+            e = self.ExporterBase(shape, typechar, itemsize)
+            bp = BufferProxy(e.__dict__)
+            bp.size = e.size
+            bp.typestr = e.typestr
+            bp.shape = e.shape
+            bp.strides = e.strides
+            bp.data = e.data
+            return bp
+
+        _shape = [2, 3, 5, 7, 11]  # Some prime numbers
+        for ndim in range(1, len(_shape)):
+            o = exporter(_shape[0:ndim], 'i', 2)
+            v = BufferProxy(o)
+            self.assertSame(v, o)
+        ndim = 2
+        shape = _shape[0:ndim]
+        for typechar in ('i', 'u'):
+            for itemsize in (1, 2, 4, 8):
+                o = exporter(shape, typechar, itemsize)
+                v = BufferProxy(o)
+                self.assertSame(v, o)
+        for itemsize in (4, 8):
+            o = exporter(shape, 'f', itemsize)
+            v = BufferProxy(o)
+            self.assertSame(v, o)
+
     def not_init_assertions(self):
         self.assert_(not pygame.display.get_init(),
                      "display shouldn't be initialized" )