Author: Armin Rigo <[email protected]>
Branch: 
Changeset: r2869:d05a37bd9b1f
Date: 2017-01-23 20:22 +0100
http://bitbucket.org/cffi/cffi/changeset/d05a37bd9b1f/

Log:    issue300: return _Bool as Python booleans; related fixes

diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c
--- a/c/_cffi_backend.c
+++ b/c/_cffi_backend.c
@@ -970,8 +970,23 @@
         /*READ(data, ct->ct_size)*/
         value = read_raw_unsigned_data(data, ct->ct_size);
 
-        if (ct->ct_flags & CT_PRIMITIVE_FITS_LONG)
+        if (ct->ct_flags & CT_PRIMITIVE_FITS_LONG) {
+            if (ct->ct_flags & CT_IS_BOOL) {
+                PyObject *x;
+                switch ((int)value) {
+                case 0: x = Py_False; break;
+                case 1: x = Py_True; break;
+                default:
+                    PyErr_Format(PyExc_ValueError,
+                                 "got a _Bool of value %d, expected 0 or 1",
+                                 (int)value);
+                    return NULL;
+                }
+                Py_INCREF(x);
+                return x;
+            }
             return PyInt_FromLong((long)value);
+        }
         else
             return PyLong_FromUnsignedLongLong(value);
     }
@@ -1208,6 +1223,20 @@
 }
 
 static int
+must_be_array_of_zero_or_one(const char *data, Py_ssize_t n)
+{
+    Py_ssize_t i;
+    for (i = 0; i < n; i++) {
+        if (((unsigned char)data[i]) > 1) {
+            PyErr_SetString(PyExc_ValueError,
+                "an array of _Bool can only contain \\x00 or \\x01");
+            return -1;
+        }
+    }
+    return 0;
+}
+
+static int
 convert_array_from_object(char *data, CTypeDescrObject *ct, PyObject *init)
 {
     /* used by convert_from_object(), and also to decode lists/tuples/unicodes
@@ -1254,6 +1283,9 @@
             if (n != ct->ct_length)
                 n++;
             srcdata = PyBytes_AS_STRING(init);
+            if (ctitem->ct_flags & CT_IS_BOOL)
+                if (must_be_array_of_zero_or_one(srcdata, n) < 0)
+                    return -1;
             memcpy(data, srcdata, n);
             return 0;
         }
@@ -1424,12 +1456,15 @@
         unsigned PY_LONG_LONG value = _my_PyLong_AsUnsignedLongLong(init, 1);
         if (value == (unsigned PY_LONG_LONG)-1 && PyErr_Occurred())
             return -1;
-        if (ct->ct_flags & CT_IS_BOOL)
-            if (value & ~1)      /* value != 0 && value != 1 */
+        if (ct->ct_flags & CT_IS_BOOL) {
+            if (value > 1ULL)      /* value != 0 && value != 1 */
                 goto overflow;
-        write_raw_integer_data(buf, value, ct->ct_size);
-        if (value != read_raw_unsigned_data(buf, ct->ct_size))
-            goto overflow;
+        }
+        else {
+            write_raw_integer_data(buf, value, ct->ct_size);
+            if (value != read_raw_unsigned_data(buf, ct->ct_size))
+                goto overflow;
+        }
         write_raw_integer_data(data, value, ct->ct_size);
         return 0;
     }
@@ -2544,6 +2579,10 @@
             length = PyBytes_GET_SIZE(init) + 1;
 #else
             *output_data = PyBytes_AS_STRING(init);
+            if (ctitem->ct_flags & CT_IS_BOOL)
+                if (must_be_array_of_zero_or_one(*output_data,
+                                                 PyBytes_GET_SIZE(init)) < 0)
+                    return -1;
             return 0;
 #endif
         }
@@ -5740,7 +5779,8 @@
     if (cd->c_type->ct_itemdescr != NULL &&
         cd->c_type->ct_itemdescr->ct_flags & (CT_PRIMITIVE_CHAR |
                                               CT_PRIMITIVE_SIGNED |
-                                              CT_PRIMITIVE_UNSIGNED)) {
+                                              CT_PRIMITIVE_UNSIGNED) &&
+        !(cd->c_type->ct_itemdescr->ct_flags & CT_IS_BOOL)) {
         Py_ssize_t length = maxlen;
         if (cd->c_data == NULL) {
             PyObject *s = cdata_repr(cd);
@@ -5904,7 +5944,8 @@
             else if (itemsize == sizeof(short))       casenum = 1;
             else if (itemsize == sizeof(signed char)) casenum = 0;
         }
-        else if (ctitem->ct_flags & CT_PRIMITIVE_UNSIGNED) {
+        else if ((ctitem->ct_flags & (CT_PRIMITIVE_UNSIGNED | CT_IS_BOOL))
+                 == CT_PRIMITIVE_UNSIGNED) {
             /* Note: we never pick case 6 if sizeof(int) == sizeof(long),
                so that case 6 below can assume that the 'unsigned int' result
                would always fit in a 'signed long'. */
diff --git a/c/test_c.py b/c/test_c.py
--- a/c/test_c.py
+++ b/c/test_c.py
@@ -896,6 +896,15 @@
     py.test.raises(OverflowError, f, 128, 0)
     py.test.raises(OverflowError, f, 0, 128)
 
+def test_call_function_0_pretend_bool_result():
+    BSignedChar = new_primitive_type("signed char")
+    BBool = new_primitive_type("_Bool")
+    BFunc0 = new_function_type((BSignedChar, BSignedChar), BBool, False)
+    f = cast(BFunc0, _testfunc(0))
+    assert f(40, -39) is True
+    assert f(40, -40) is False
+    py.test.raises(ValueError, f, 40, 2)
+
 def test_call_function_1():
     BInt = new_primitive_type("int")
     BLong = new_primitive_type("long")
@@ -1058,6 +1067,17 @@
     res = f(b"foo")
     assert res == 1000 * ord(b'f')
 
+def test_call_function_23_bool_array():
+    # declaring the function as int(_Bool*)
+    BBool = new_primitive_type("_Bool")
+    BBoolP = new_pointer_type(BBool)
+    BInt = new_primitive_type("int")
+    BFunc23 = new_function_type((BBoolP,), BInt, False)
+    f = cast(BFunc23, _testfunc(23))
+    res = f(b"\x01\x01")
+    assert res == 1000
+    py.test.raises(ValueError, f, b"\x02\x02")
+
 def test_cannot_pass_struct_with_array_of_length_0():
     BInt = new_primitive_type("int")
     BArray0 = new_array_type(new_pointer_type(BInt), 0)
@@ -2628,13 +2648,38 @@
     py.test.raises(OverflowError, newp, BBoolP, 2)
     py.test.raises(OverflowError, newp, BBoolP, -1)
     BCharP = new_pointer_type(new_primitive_type("char"))
-    p = newp(BCharP, b'X')
+    p = newp(BCharP, b'\x01')
     q = cast(BBoolP, p)
-    assert q[0] == ord(b'X')
+    assert q[0] is True
+    p = newp(BCharP, b'\x00')
+    q = cast(BBoolP, p)
+    assert q[0] is False
     py.test.raises(TypeError, string, cast(BBool, False))
     BDouble = new_primitive_type("double")
     assert int(cast(BBool, cast(BDouble, 0.1))) == 1
     assert int(cast(BBool, cast(BDouble, 0.0))) == 0
+    BBoolA = new_array_type(BBoolP, None)
+    p = newp(BBoolA, b'\x01\x00')
+    assert p[0] is True
+    assert p[1] is False
+
+def test_bool_forbidden_cases():
+    BBool = new_primitive_type("_Bool")
+    BBoolP = new_pointer_type(BBool)
+    BBoolA = new_array_type(BBoolP, None)
+    BCharP = new_pointer_type(new_primitive_type("char"))
+    p = newp(BCharP, b'X')
+    q = cast(BBoolP, p)
+    py.test.raises(ValueError, "q[0]")
+    py.test.raises(TypeError, newp, BBoolP, b'\x00')
+    assert newp(BBoolP, 0)[0] is False
+    assert newp(BBoolP, 1)[0] is True
+    py.test.raises(OverflowError, newp, BBoolP, 2)
+    py.test.raises(OverflowError, newp, BBoolP, -1)
+    py.test.raises(ValueError, newp, BBoolA, b'\x00\x01\x02')
+    py.test.raises(OverflowError, newp, BBoolA, [0, 1, 2])
+    py.test.raises(TypeError, string, newp(BBoolP, 1))
+    py.test.raises(TypeError, string, newp(BBoolA, [1]))
 
 def test_typeoffsetof():
     BChar = new_primitive_type("char")
@@ -3674,7 +3719,7 @@
             ("int16_t", [-2**15, 2**15-1]),
             ("int32_t", [-2**31, 2**31-1]),
             ("int64_t", [-2**63, 2**63-1]),
-            ("_Bool", [0, 1]),
+            ("_Bool", [False, True]),
             ("float", [0.0, 10.5]),
             ("double", [12.34, 56.78]),
             ]:
diff --git a/doc/source/ref.rst b/doc/source/ref.rst
--- a/doc/source/ref.rst
+++ b/doc/source/ref.rst
@@ -597,7 +597,8 @@
 |   integers    | an integer or anything | a Python int or  | int(), bool()  |
 |   and enums   | on which int() works   | long, depending  | `(******)`     |
 |   `(*****)`   | (but not a float!).    | on the type      |                |
-|               | Must be within range.  |                  |                |
+|               | Must be within range.  | (ver. 1.10: or a |                |
+|               |                        | bool)            |                |
 +---------------+------------------------+------------------+----------------+
 |   ``char``    | a string of length 1   | a string of      | int(), bool()  |
 |               | or another <cdata char>| length 1         |                |
@@ -637,12 +638,13 @@
 |               | items                  |                  |``[]`` `(****)`,|
 |               |                        |                  |``+``, ``-``    |
 +---------------+------------------------+                  +----------------+
-|  ``char[]``   | same as arrays, or a   |                  | len(), iter(), |
-|               | Python string          |                  | ``[]``, ``+``, |
-|               |                        |                  | ``-``          |
+| ``char[]``,   | same as arrays, or a   |                  | len(), iter(), |
+| ``un/signed`` | Python byte string     |                  | ``[]``, ``+``, |
+| ``char[]``,   |                        |                  | ``-``          |
+| ``_Bool[]``   |                        |                  |                |
 +---------------+------------------------+                  +----------------+
 | ``wchar_t[]`` | same as arrays, or a   |                  | len(), iter(), |
-|               | Python unicode         |                  | ``[]``,        |
+|               | Python unicode string  |                  | ``[]``,        |
 |               |                        |                  | ``+``, ``-``   |
 |               |                        |                  |                |
 +---------------+------------------------+------------------+----------------+
@@ -727,6 +729,14 @@
    *New in version 1.7.*  In previous versions, it only worked on
    pointers; for primitives it always returned True.
 
+   *New in version 1.10:*  The C type ``_Bool`` or ``bool`` converts to
+   Python booleans now.  You get an exception if a C ``_Bool`` happens
+   to contain a value different from 0 and 1 (this case triggers
+   undefined behavior in C; if you really have to interface with a
+   library relying on this, don't use ``_Bool`` in the CFFI side).
+   Also, when converting from a byte string to a ``_Bool[]``, only the
+   bytes ``\x00`` and ``\x01`` are accepted.
+
 .. _file:
 
 Support for FILE
diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst
--- a/doc/source/whatsnew.rst
+++ b/doc/source/whatsnew.rst
@@ -31,6 +31,19 @@
   been fixed and the unicode strings don't support the memoryview
   interface any more.)
 
+* The C type ``_Bool`` or ``bool`` now converts to a Python boolean
+  when reading, instead of the content of the byte as an integer.  The
+  change here is mostly what occurs if the byte happens to contain a
+  value different from 0 and 1.  Previously, it would just return it;
+  with this change, CFFI raises an exception in this case.  But this
+  case means "undefined behavior" in C; if you really have to interface
+  with a library relying on this, don't use ``_Bool`` in the CFFI side.
+  Also, it is still valid to use a byte string as initializer for a
+  ``_Bool[]``, but now it must only contain ``\x00`` or ``\x01``.  As an
+  aside, ``ffi.string()`` no longer works on ``_Bool[]`` (but it never
+  made much sense, as this function stops on the first zero).
+
+
 v1.9
 ====
 
_______________________________________________
pypy-commit mailing list
[email protected]
https://mail.python.org/mailman/listinfo/pypy-commit

Reply via email to