summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorCharles Harris <charlesr.harris@gmail.com>2016-02-19 08:01:20 -0700
committerCharles Harris <charlesr.harris@gmail.com>2016-02-19 08:01:20 -0700
commit2112e7d47ab401453230f6dc1b6923a468ad947d (patch)
treed802bb45442c33c3ded7dd387558dc7abc1e433e
parentdad36a3d86cb092b9bcfd5a9c03180597af82c07 (diff)
parentbf6017a42dae58f0354e7da70c24f3ded40d0410 (diff)
downloadnumpy-2112e7d47ab401453230f6dc1b6923a468ad947d.tar.gz
Merge pull request #7266 from ahaldane/segfault_invalidlenclass
BUG: Segfault for classes with deceptive __len__
-rw-r--r--doc/release/1.11.0-notes.rst9
-rw-r--r--numpy/core/src/multiarray/common.c56
-rw-r--r--numpy/core/src/multiarray/ctors.c5
-rw-r--r--numpy/core/tests/test_dtype.py4
-rw-r--r--numpy/core/tests/test_multiarray.py10
5 files changed, 31 insertions, 53 deletions
diff --git a/doc/release/1.11.0-notes.rst b/doc/release/1.11.0-notes.rst
index 8c3028b69..26406a874 100644
--- a/doc/release/1.11.0-notes.rst
+++ b/doc/release/1.11.0-notes.rst
@@ -149,6 +149,15 @@ it's unlikely that any third-party code is using them either, but we
mention it here for completeness.
+object dtype detection for old-style classes
+~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+
+In python 2, objects which are instances of old-style user-defined classes no
+longer automatically count as 'object' type in the dtype-detection handler.
+Instead, as in python 3, they may potentially count as sequences, but only if
+they define both a `__len__` and a `__getitem__` method. This fixes a segfault
+and inconsistency between python 2 and 3.
+
New Features
============
diff --git a/numpy/core/src/multiarray/common.c b/numpy/core/src/multiarray/common.c
index 1948b8b61..c216daa95 100644
--- a/numpy/core/src/multiarray/common.c
+++ b/numpy/core/src/multiarray/common.c
@@ -128,30 +128,6 @@ _array_find_python_scalar_type(PyObject *op)
return NULL;
}
-#if !defined(NPY_PY3K)
-static PyArray_Descr *
-_use_default_type(PyObject *op)
-{
- int typenum, l;
- PyObject *type;
-
- typenum = -1;
- l = 0;
- type = (PyObject *)Py_TYPE(op);
- while (l < NPY_NUMUSERTYPES) {
- if (type == (PyObject *)(userdescrs[l]->typeobj)) {
- typenum = l + NPY_USERDEF;
- break;
- }
- l++;
- }
- if (typenum == -1) {
- typenum = NPY_OBJECT;
- }
- return PyArray_DescrFromType(typenum);
-}
-#endif
-
/*
* These constants are used to signal that the recursive dtype determination in
* PyArray_DTypeFromObject encountered a string type, and that the recursive
@@ -490,24 +466,16 @@ PyArray_DTypeFromObjectHelper(PyObject *obj, int maxdims,
}
}
- /* Not exactly sure what this is about... */
-#if !defined(NPY_PY3K)
- if (PyInstance_Check(obj)) {
- dtype = _use_default_type(obj);
- if (dtype == NULL) {
- goto fail;
- }
- else {
- goto promote_types;
- }
- }
-#endif
-
/*
* If we reached the maximum recursion depth without hitting one
- * of the above cases, the output dtype should be OBJECT
+ * of the above cases, and obj isn't a sequence-like object, the output
+ * dtype should be either OBJECT or a user-defined type.
+ *
+ * Note that some libraries define sequence-like classes but want them to
+ * be treated as objects, and they expect numpy to treat it as an object if
+ * __len__ is not defined.
*/
- if (maxdims == 0 || !PySequence_Check(obj)) {
+ if (maxdims == 0 || !PySequence_Check(obj) || PySequence_Size(obj) < 0) {
if (*out_dtype == NULL || (*out_dtype)->type_num != NPY_OBJECT) {
Py_XDECREF(*out_dtype);
*out_dtype = PyArray_DescrFromType(NPY_OBJECT);
@@ -518,20 +486,12 @@ PyArray_DTypeFromObjectHelper(PyObject *obj, int maxdims,
return 0;
}
- /*
- * fails if convertable to list but no len is defined which some libraries
- * require to get object arrays
- */
- size = PySequence_Size(obj);
- if (size < 0) {
- goto fail;
- }
-
/* Recursive case, first check the sequence contains only one type */
seq = PySequence_Fast(obj, "Could not convert object to sequence");
if (seq == NULL) {
goto fail;
}
+ size = PySequence_Fast_GET_SIZE(seq);
objects = PySequence_Fast_ITEMS(seq);
common_type = size > 0 ? Py_TYPE(objects[0]) : NULL;
for (i = 1; i < size; ++i) {
diff --git a/numpy/core/src/multiarray/ctors.c b/numpy/core/src/multiarray/ctors.c
index 785b3073a..79c2b16b1 100644
--- a/numpy/core/src/multiarray/ctors.c
+++ b/numpy/core/src/multiarray/ctors.c
@@ -695,11 +695,6 @@ discover_dimensions(PyObject *obj, int *maxndim, npy_intp *d, int check_it,
/* obj is not a Sequence */
if (!PySequence_Check(obj) ||
-#if defined(NPY_PY3K)
- /* FIXME: XXX -- what is the correct thing to do here? */
-#else
- PyInstance_Check(obj) ||
-#endif
PySequence_Length(obj) < 0) {
*maxndim = 0;
PyErr_Clear();
diff --git a/numpy/core/tests/test_dtype.py b/numpy/core/tests/test_dtype.py
index 6d898eaa1..a6cb66b7d 100644
--- a/numpy/core/tests/test_dtype.py
+++ b/numpy/core/tests/test_dtype.py
@@ -590,6 +590,10 @@ def test_rational_dtype():
a = np.array([1111], dtype=rational).astype
assert_raises(OverflowError, a, 'int8')
+ # test that dtype detection finds user-defined types
+ x = rational(1)
+ assert_equal(np.array([x,x]).dtype, np.dtype(rational))
+
if __name__ == "__main__":
run_module_suite()
diff --git a/numpy/core/tests/test_multiarray.py b/numpy/core/tests/test_multiarray.py
index 3bbfed569..fedd33282 100644
--- a/numpy/core/tests/test_multiarray.py
+++ b/numpy/core/tests/test_multiarray.py
@@ -702,6 +702,16 @@ class TestCreation(TestCase):
d = np.array([Point2(), Point2(), Point2()])
assert_equal(d.dtype, np.dtype(object))
+ def test_false_len_sequence(self):
+ # gh-7264, segfault for this example
+ class C:
+ def __getitem__(self, i):
+ raise IndexError
+ def __len__(self):
+ return 42
+
+ assert_raises(ValueError, np.array, C()) # segfault?
+
class TestStructured(TestCase):
def test_subarray_field_access(self):