Commit ea197e78 authored by Armin Rigo's avatar Armin Rigo
Browse files

issue300: return _Bool as Python booleans; related fixes

parent b0204d9d8b96
......@@ -970,8 +970,23 @@ convert_to_object(char *data, CTypeDescrObject *ct)
/*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);
}
......@@ -1207,6 +1222,20 @@ convert_vfield_from_object(char *data, CFieldObject *cf, PyObject *value,
return 0;
}
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)
{
......@@ -1254,6 +1283,9 @@ convert_array_from_object(char *data, CTypeDescrObject *ct, PyObject *init)
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 @@ convert_from_object(char *data, CTypeDescrObject *ct, PyObject *init)
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 @@ _prepare_pointer_call_argument(CTypeDescrObject *ctptr, PyObject *init,
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 @@ static PyObject *b_string(PyObject *self, PyObject *args, PyObject *kwds)
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 @@ static PyObject *b_unpack(PyObject *self, PyObject *args, PyObject *kwds)
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'. */
......
......@@ -896,6 +896,15 @@ def test_call_function_0():
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 @@ def test_call_function_23_bis():
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 @@ def test_bool():
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 @@ def test_unpack():
("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]),
]:
......
......@@ -597,7 +597,8 @@ allowed.
| 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 @@ allowed.
| | 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 @@ allowed.
*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
......
......@@ -31,6 +31,19 @@ v1.10
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
====
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment