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