Author: Tom Krauss <thomas.p.kra...@gmail.com> Branch: sirtom67/float_complex Changeset: r2905:ab7c2855f10f Date: 2017-03-12 19:43 -0500 http://bitbucket.org/cffi/cffi/changeset/ab7c2855f10f/
Log: Merge default in. diff too long, truncating to 2000 out of 2939 lines diff --git a/README.md b/README.md --- a/README.md +++ b/README.md @@ -15,6 +15,16 @@ [Mailing list](https://groups.google.com/forum/#!forum/python-cffi) -To run tests under CPython, run: +Testing/development tips +------------------------ -python setup.py build_ext -i +To run tests under CPython, run:: + + pip install pytest # if you don't have py.test already + pip install pycparser + python setup.py build_ext -f -i + py.test c/ testing/ + +If you run in another directory (either the tests or another program), +you should use the environment variable ``PYTHONPATH=/path`` to point +to the location that contains the ``_cffi_backend.so`` just compiled. diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c --- a/c/_cffi_backend.c +++ b/c/_cffi_backend.c @@ -2,7 +2,7 @@ #include <Python.h> #include "structmember.h" -#define CFFI_VERSION "1.9.2" +#define CFFI_VERSION "1.10.0" #ifdef MS_WIN32 #include <windows.h> @@ -460,6 +460,8 @@ static PyObject * get_field_name(CTypeDescrObject *ct, CFieldObject *cf); /* forward */ +/* returns 0 if the struct ctype is opaque, 1 if it is not, or -1 if + an exception occurs */ #define force_lazy_struct(ct) \ ((ct)->ct_stuff != NULL ? 1 : do_realize_lazy_struct(ct)) @@ -1014,8 +1016,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); } @@ -1256,6 +1273,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 @@ -1302,6 +1333,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; } @@ -1472,12 +1506,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; } @@ -2047,47 +2084,97 @@ static PyObject *cdata_richcompare(PyObject *v, PyObject *w, int op) { - int res; + int v_is_ptr, w_is_ptr; PyObject *pyres; - char *v_cdata, *w_cdata; assert(CData_Check(v)); - if (!CData_Check(w)) { + + /* Comparisons involving a primitive cdata work differently than + * comparisons involving a struct/array/pointer. + * + * If v or w is a struct/array/pointer, then the other must be too + * (otherwise we return NotImplemented and leave the case to + * Python). If both are, then we compare the addresses. + * + * If v and/or w is a primitive cdata, then we convert the cdata(s) + * to regular Python objects and redo the comparison there. + */ + + v_is_ptr = !(((CDataObject *)v)->c_type->ct_flags & CT_PRIMITIVE_ANY); + w_is_ptr = CData_Check(w) && + !(((CDataObject *)w)->c_type->ct_flags & CT_PRIMITIVE_ANY); + + if (v_is_ptr && w_is_ptr) { + int res; + char *v_cdata = ((CDataObject *)v)->c_data; + char *w_cdata = ((CDataObject *)w)->c_data; + + switch (op) { + case Py_EQ: res = (v_cdata == w_cdata); break; + case Py_NE: res = (v_cdata != w_cdata); break; + case Py_LT: res = (v_cdata < w_cdata); break; + case Py_LE: res = (v_cdata <= w_cdata); break; + case Py_GT: res = (v_cdata > w_cdata); break; + case Py_GE: res = (v_cdata >= w_cdata); break; + default: res = -1; + } + pyres = res ? Py_True : Py_False; + } + else if (v_is_ptr || w_is_ptr) { pyres = Py_NotImplemented; - goto done; - } - - if ((op != Py_EQ && op != Py_NE) && - ((((CDataObject *)v)->c_type->ct_flags & CT_PRIMITIVE_ANY) || - (((CDataObject *)w)->c_type->ct_flags & CT_PRIMITIVE_ANY))) - goto Error; - - v_cdata = ((CDataObject *)v)->c_data; - w_cdata = ((CDataObject *)w)->c_data; - - switch (op) { - case Py_EQ: res = (v_cdata == w_cdata); break; - case Py_NE: res = (v_cdata != w_cdata); break; - case Py_LT: res = (v_cdata < w_cdata); break; - case Py_LE: res = (v_cdata <= w_cdata); break; - case Py_GT: res = (v_cdata > w_cdata); break; - case Py_GE: res = (v_cdata >= w_cdata); break; - default: res = -1; - } - pyres = res ? Py_True : Py_False; - done: + } + else { + PyObject *aa[2]; + int i; + + aa[0] = v; Py_INCREF(v); + aa[1] = w; Py_INCREF(w); + pyres = NULL; + + for (i = 0; i < 2; i++) { + v = aa[i]; + if (!CData_Check(v)) + continue; + w = convert_to_object(((CDataObject *)v)->c_data, + ((CDataObject *)v)->c_type); + if (w == NULL) + goto error; + if (CData_Check(w)) { + Py_DECREF(w); + PyErr_Format(PyExc_NotImplementedError, + "cannot use <cdata '%s'> in a comparison", + ((CDataObject *)v)->c_type->ct_name); + goto error; + } + aa[i] = w; + Py_DECREF(v); + } + pyres = PyObject_RichCompare(aa[0], aa[1], op); + error: + Py_DECREF(aa[1]); + Py_DECREF(aa[0]); + return pyres; + } + Py_INCREF(pyres); return pyres; - - Error: - PyErr_SetString(PyExc_TypeError, - "cannot do comparison on a primitive cdata"); - return NULL; -} - -static long cdata_hash(CDataObject *cd) -{ - return _Py_HashPointer(cd->c_data); +} + +static long cdata_hash(CDataObject *v) +{ + if (((CDataObject *)v)->c_type->ct_flags & CT_PRIMITIVE_ANY) { + PyObject *vv = convert_to_object(((CDataObject *)v)->c_data, + ((CDataObject *)v)->c_type); + if (vv == NULL) + return -1; + if (!CData_Check(vv)) { + long hash = PyObject_Hash(vv); + Py_DECREF(vv); + return hash; + } + Py_DECREF(vv); + } + return _Py_HashPointer(v->c_data); } static Py_ssize_t @@ -2470,11 +2557,26 @@ return _cdata_add_or_sub(v, w, -1); } +static void +_cdata_attr_errmsg(char *errmsg, CDataObject *cd, PyObject *attr) +{ + char *text; + if (!PyErr_ExceptionMatches(PyExc_AttributeError)) + return; + PyErr_Clear(); + text = PyText_AsUTF8(attr); + if (text == NULL) + return; + PyErr_Format(PyExc_AttributeError, errmsg, cd->c_type->ct_name, text); +} + static PyObject * cdata_getattro(CDataObject *cd, PyObject *attr) { CFieldObject *cf; CTypeDescrObject *ct = cd->c_type; + char *errmsg = "cdata '%s' has no attribute '%s'"; + PyObject *x; if (ct->ct_flags & CT_POINTER) ct = ct->ct_itemdescr; @@ -2506,14 +2608,19 @@ return new_simple_cdata(data, (CTypeDescrObject *)cf->cf_type->ct_stuff); } + errmsg = "cdata '%s' has no field '%s'"; break; case -1: return NULL; default: + errmsg = "cdata '%s' points to an opaque type: cannot read fields"; break; } } - return PyObject_GenericGetAttr((PyObject *)cd, attr); + x = PyObject_GenericGetAttr((PyObject *)cd, attr); + if (x == NULL) + _cdata_attr_errmsg(errmsg, cd, attr); + return x; } static int @@ -2521,6 +2628,8 @@ { CFieldObject *cf; CTypeDescrObject *ct = cd->c_type; + char *errmsg = "cdata '%s' has no attribute '%s'"; + int x; if (ct->ct_flags & CT_POINTER) ct = ct->ct_itemdescr; @@ -2540,14 +2649,19 @@ return -1; } } + errmsg = "cdata '%s' has no field '%s'"; break; case -1: return -1; default: + errmsg = "cdata '%s' points to an opaque type: cannot write fields"; break; } } - return PyObject_GenericSetAttr((PyObject *)cd, attr, value); + x = PyObject_GenericSetAttr((PyObject *)cd, attr, value); + if (x < 0) + _cdata_attr_errmsg(errmsg, cd, attr); + return x; } static PyObject * @@ -2597,6 +2711,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 } @@ -3761,19 +3879,14 @@ CTypeDescrObject *ct; char *funcname; void *funcptr; - int ok; if (!PyArg_ParseTuple(args, "O!s:load_function", &CTypeDescr_Type, &ct, &funcname)) return NULL; - ok = 0; - if (ct->ct_flags & CT_FUNCTIONPTR) - ok = 1; - if ((ct->ct_flags & CT_POINTER) && (ct->ct_itemdescr->ct_flags & CT_VOID)) - ok = 1; - if (!ok) { - PyErr_Format(PyExc_TypeError, "function cdata expected, got '%s'", + if (!(ct->ct_flags & (CT_FUNCTIONPTR | CT_POINTER | CT_ARRAY))) { + PyErr_Format(PyExc_TypeError, + "function or pointer or array cdata expected, got '%s'", ct->ct_name); return NULL; } @@ -3781,12 +3894,15 @@ funcptr = dlsym(dlobj->dl_handle, funcname); if (funcptr == NULL) { const char *error = dlerror(); - PyErr_Format(PyExc_KeyError, - "function '%s' not found in library '%s': %s", + PyErr_Format(PyExc_AttributeError, + "function/symbol '%s' not found in library '%s': %s", funcname, dlobj->dl_name, error); return NULL; } + if ((ct->ct_flags & CT_ARRAY) && ct->ct_length < 0) { + ct = (CTypeDescrObject *)ct->ct_stuff; + } return new_simple_cdata(funcptr, ct); } @@ -5890,7 +6006,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); @@ -6058,7 +6175,8 @@ /* 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'. */ - if (itemsize == sizeof(unsigned long)) casenum = 7; + if (ctitem->ct_flags & CT_IS_BOOL) casenum = 11; + else if (itemsize == sizeof(unsigned long)) casenum = 7; else if (itemsize == sizeof(unsigned int)) casenum = 6; else if (itemsize == sizeof(unsigned short)) casenum = 5; else if (itemsize == sizeof(unsigned char)) casenum = 4; @@ -6091,6 +6209,13 @@ case 8: x = PyFloat_FromDouble(*(float *)src); break; case 9: x = PyFloat_FromDouble(*(double *)src); break; case 10: x = new_simple_cdata(*(char **)src, ctitem); break; + case 11: + switch (*(unsigned char *)src) { + case 0: x = Py_False; Py_INCREF(x); break; + case 1: x = Py_True; Py_INCREF(x); break; + default: x = convert_to_object(src, ctitem); /* error */ + } + break; } if (x == NULL) { Py_DECREF(result); @@ -6102,8 +6227,10 @@ return result; } -static PyObject *b_buffer(PyObject *self, PyObject *args, PyObject *kwds) -{ +static PyObject * +b_buffer_new(PyTypeObject *type, PyObject *args, PyObject *kwds) +{ + /* this is the constructor of the type implemented in minibuffer.h */ CDataObject *cd; Py_ssize_t size = -1; static char *keywords[] = {"cdata", "size", NULL}; @@ -6738,7 +6865,6 @@ {"getcname", b_getcname, METH_VARARGS}, {"string", (PyCFunction)b_string, METH_VARARGS | METH_KEYWORDS}, {"unpack", (PyCFunction)b_unpack, METH_VARARGS | METH_KEYWORDS}, - {"buffer", (PyCFunction)b_buffer, METH_VARARGS | METH_KEYWORDS}, {"get_errno", b_get_errno, METH_NOARGS}, {"set_errno", b_set_errno, METH_O}, {"newp_handle", b_newp_handle, METH_VARARGS}, @@ -7042,6 +7168,10 @@ INITERROR; } + Py_INCREF(&MiniBuffer_Type); + if (PyModule_AddObject(m, "buffer", (PyObject *)&MiniBuffer_Type) < 0) + INITERROR; + init_cffi_tls(); if (PyErr_Occurred()) INITERROR; diff --git a/c/call_python.c b/c/call_python.c --- a/c/call_python.c +++ b/c/call_python.c @@ -177,7 +177,7 @@ #if (defined(WITH_THREAD) && !defined(_MSC_VER) && \ !defined(__amd64__) && !defined(__x86_64__) && \ !defined(__i386__) && !defined(__i386)) -# if defined(__GNUC__) +# if defined(HAVE_SYNC_SYNCHRONIZE) # define read_barrier() __sync_synchronize() # elif defined(_AIX) # define read_barrier() __lwsync() diff --git a/c/cffi1_module.c b/c/cffi1_module.c --- a/c/cffi1_module.c +++ b/c/cffi1_module.c @@ -45,6 +45,9 @@ if (PyDict_SetItemString(FFI_Type.tp_dict, "CData", (PyObject *)&CData_Type) < 0) return -1; + if (PyDict_SetItemString(FFI_Type.tp_dict, "buffer", + (PyObject *)&MiniBuffer_Type) < 0) + return -1; for (i = 0; all_dlopen_flags[i].name != NULL; i++) { x = PyInt_FromLong(all_dlopen_flags[i].value); diff --git a/c/ffi_obj.c b/c/ffi_obj.c --- a/c/ffi_obj.c +++ b/c/ffi_obj.c @@ -475,19 +475,6 @@ #define ffi_unpack b_unpack /* ffi_unpack() => b_unpack() from _cffi_backend.c */ -PyDoc_STRVAR(ffi_buffer_doc, -"Return a read-write buffer object that references the raw C data\n" -"pointed to by the given 'cdata'. The 'cdata' must be a pointer or an\n" -"array. Can be passed to functions expecting a buffer, or directly\n" -"manipulated with:\n" -"\n" -" buf[:] get a copy of it in a regular string, or\n" -" buf[idx] as a single character\n" -" buf[:] = ...\n" -" buf[idx] = ... change the content"); - -#define ffi_buffer b_buffer /* ffi_buffer() => b_buffer() - from _cffi_backend.c */ PyDoc_STRVAR(ffi_offsetof_doc, "Return the offset of the named field inside the given structure or\n" @@ -1085,7 +1072,6 @@ static PyMethodDef ffi_methods[] = { {"addressof", (PyCFunction)ffi_addressof, METH_VARARGS, ffi_addressof_doc}, {"alignof", (PyCFunction)ffi_alignof, METH_O, ffi_alignof_doc}, - {"buffer", (PyCFunction)ffi_buffer, METH_VKW, ffi_buffer_doc}, {"def_extern", (PyCFunction)ffi_def_extern, METH_VKW, ffi_def_extern_doc}, {"callback", (PyCFunction)ffi_callback, METH_VKW, ffi_callback_doc}, {"cast", (PyCFunction)ffi_cast, METH_VARARGS, ffi_cast_doc}, diff --git a/c/minibuffer.h b/c/minibuffer.h --- a/c/minibuffer.h +++ b/c/minibuffer.h @@ -155,6 +155,81 @@ return 0; } +static PyObject * +mb_richcompare(PyObject *self, PyObject *other, int op) +{ + Py_ssize_t self_size, other_size; + Py_buffer self_bytes, other_bytes; + PyObject *res; + Py_ssize_t minsize; + int cmp, rc; + + /* Bytes can be compared to anything that supports the (binary) + buffer API. Except that a comparison with Unicode is always an + error, even if the comparison is for equality. */ + rc = PyObject_IsInstance(self, (PyObject*)&PyUnicode_Type); + if (!rc) + rc = PyObject_IsInstance(other, (PyObject*)&PyUnicode_Type); + if (rc < 0) + return NULL; + if (rc) { + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + } + + if (PyObject_GetBuffer(self, &self_bytes, PyBUF_SIMPLE) != 0) { + PyErr_Clear(); + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + + } + self_size = self_bytes.len; + + if (PyObject_GetBuffer(other, &other_bytes, PyBUF_SIMPLE) != 0) { + PyErr_Clear(); + PyBuffer_Release(&self_bytes); + Py_INCREF(Py_NotImplemented); + return Py_NotImplemented; + + } + other_size = other_bytes.len; + + if (self_size != other_size && (op == Py_EQ || op == Py_NE)) { + /* Shortcut: if the lengths differ, the objects differ */ + cmp = (op == Py_NE); + } + else { + minsize = self_size; + if (other_size < minsize) + minsize = other_size; + + cmp = memcmp(self_bytes.buf, other_bytes.buf, minsize); + /* In ISO C, memcmp() guarantees to use unsigned bytes! */ + + if (cmp == 0) { + if (self_size < other_size) + cmp = -1; + else if (self_size > other_size) + cmp = 1; + } + + switch (op) { + case Py_LT: cmp = cmp < 0; break; + case Py_LE: cmp = cmp <= 0; break; + case Py_EQ: cmp = cmp == 0; break; + case Py_NE: cmp = cmp != 0; break; + case Py_GT: cmp = cmp > 0; break; + case Py_GE: cmp = cmp >= 0; break; + } + } + + res = cmp ? Py_True : Py_False; + PyBuffer_Release(&self_bytes); + PyBuffer_Release(&other_bytes); + Py_INCREF(res); + return res; +} + #if PY_MAJOR_VERSION >= 3 /* pfffffffffffff pages of copy-paste from listobject.c */ static PyObject *mb_subscript(MiniBufferObj *self, PyObject *item) @@ -238,6 +313,22 @@ # define MINIBUF_TPFLAGS (Py_TPFLAGS_HAVE_GETCHARBUFFER | Py_TPFLAGS_HAVE_NEWBUFFER) #endif +PyDoc_STRVAR(ffi_buffer_doc, +"ffi.buffer(cdata[, byte_size]):\n" +"Return a read-write buffer object that references the raw C data\n" +"pointed to by the given 'cdata'. The 'cdata' must be a pointer or an\n" +"array. Can be passed to functions expecting a buffer, or directly\n" +"manipulated with:\n" +"\n" +" buf[:] get a copy of it in a regular string, or\n" +" buf[idx] as a single character\n" +" buf[:] = ...\n" +" buf[idx] = ... change the content"); + +static PyObject * /* forward, implemented in _cffi_backend.c */ +b_buffer_new(PyTypeObject *type, PyObject *args, PyObject *kwds); + + static PyTypeObject MiniBuffer_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_cffi_backend.buffer", @@ -268,11 +359,25 @@ &mb_as_buffer, /* tp_as_buffer */ (Py_TPFLAGS_DEFAULT | Py_TPFLAGS_HAVE_GC | MINIBUF_TPFLAGS), /* tp_flags */ - 0, /* tp_doc */ + ffi_buffer_doc, /* tp_doc */ (traverseproc)mb_traverse, /* tp_traverse */ (inquiry)mb_clear, /* tp_clear */ - 0, /* tp_richcompare */ + (richcmpfunc)mb_richcompare, /* tp_richcompare */ offsetof(MiniBufferObj, mb_weakreflist), /* tp_weaklistoffset */ + 0, /* tp_iter */ + 0, /* tp_iternext */ + 0, /* tp_methods */ + 0, /* tp_members */ + 0, /* tp_getset */ + 0, /* tp_base */ + 0, /* tp_dict */ + 0, /* tp_descr_get */ + 0, /* tp_descr_set */ + 0, /* tp_dictoffset */ + 0, /* tp_init */ + 0, /* tp_alloc */ + b_buffer_new, /* tp_new */ + 0, /* tp_free */ }; static PyObject *minibuffer_new(char *data, Py_ssize_t size, diff --git a/c/test_c.py b/c/test_c.py --- a/c/test_c.py +++ b/c/test_c.py @@ -12,9 +12,9 @@ # ____________________________________________________________ import sys -assert __version__ == "1.9.2", ("This test_c.py file is for testing a version" - " of cffi that differs from the one that we" - " get from 'import _cffi_backend'") +assert __version__ == "1.10.0", ("This test_c.py file is for testing a version" + " of cffi that differs from the one that we" + " get from 'import _cffi_backend'") if sys.version_info < (3,): type_or_class = "type" mandatory_b_prefix = '' @@ -27,6 +27,7 @@ .replace(r'\\U', r'\U')) u = U() str2bytes = str + strict_compare = False else: type_or_class = "class" long = int @@ -38,6 +39,7 @@ bitem2bchr = bytechr u = "" str2bytes = lambda s: bytes(s, "ascii") + strict_compare = True def size_of_int(): BInt = new_primitive_type("int") @@ -106,11 +108,11 @@ x = cast(p, -66 + (1<<199)*256) assert repr(x) == "<cdata 'signed char' -66>" assert int(x) == -66 - assert (x == cast(p, -66)) is False - assert (x != cast(p, -66)) is True + assert (x == cast(p, -66)) is True + assert (x != cast(p, -66)) is False q = new_primitive_type("short") - assert (x == cast(q, -66)) is False - assert (x != cast(q, -66)) is True + assert (x == cast(q, -66)) is True + assert (x != cast(q, -66)) is False def test_sizeof_type(): py.test.raises(TypeError, sizeof, 42.5) @@ -175,7 +177,7 @@ assert float(cast(p, 1.1)) != 1.1 # rounding error assert float(cast(p, 1E200)) == INF # limited range - assert cast(p, -1.1) != cast(p, -1.1) + assert cast(p, -1.1) == cast(p, -1.1) assert repr(float(cast(p, -0.0))) == '-0.0' assert float(cast(p, b'\x09')) == 9.0 assert float(cast(p, u+'\x09')) == 9.0 @@ -219,7 +221,7 @@ p = new_primitive_type("char") assert bool(cast(p, 'A')) is True assert bool(cast(p, '\x00')) is False # since 1.7 - assert cast(p, '\x00') != cast(p, -17*256) + assert cast(p, '\x00') == cast(p, -17*256) assert int(cast(p, 'A')) == 65 assert long(cast(p, 'A')) == 65 assert type(int(cast(p, 'A'))) is int @@ -376,29 +378,12 @@ x = find_and_load_library(None) BVoidP = new_pointer_type(new_void_type()) assert x.load_function(BVoidP, 'strcpy') - py.test.raises(KeyError, x.load_function, + py.test.raises(AttributeError, x.load_function, BVoidP, 'xxx_this_function_does_not_exist') # the next one is from 'libm', not 'libc', but we assume # that it is already loaded too, so it should work assert x.load_function(BVoidP, 'sqrt') -def test_hash_differences(): - BChar = new_primitive_type("char") - BInt = new_primitive_type("int") - BFloat = new_primitive_type("float") - for i in range(1, 20): - x1 = cast(BChar, chr(i)) - x2 = cast(BInt, i) - if hash(x1) != hash(x2): - break - else: - raise AssertionError("hashes are equal") - for i in range(1, 20): - if hash(cast(BFloat, i)) != hash(float(i)): - break - else: - raise AssertionError("hashes are equal") - def test_no_len_on_nonarray(): p = new_primitive_type("int") py.test.raises(TypeError, len, cast(p, 42)) @@ -748,8 +733,14 @@ BInt = new_primitive_type("int") BStruct = new_struct_type("struct foo") BStructPtr = new_pointer_type(BStruct) - p = cast(BStructPtr, 0) - py.test.raises(AttributeError, "p.a1") # opaque + p = cast(BStructPtr, 42) + e = py.test.raises(AttributeError, "p.a1") # opaque + assert str(e.value) == ("cdata 'struct foo *' points to an opaque type: " + "cannot read fields") + e = py.test.raises(AttributeError, "p.a1 = 10") # opaque + assert str(e.value) == ("cdata 'struct foo *' points to an opaque type: " + "cannot write fields") + complete_struct_or_union(BStruct, [('a1', BInt, -1), ('a2', BInt, -1)]) p = newp(BStructPtr, None) @@ -760,8 +751,29 @@ assert s.a2 == 123 py.test.raises(OverflowError, "s.a1 = sys.maxsize+1") assert s.a1 == 0 - py.test.raises(AttributeError, "p.foobar") - py.test.raises(AttributeError, "s.foobar") + e = py.test.raises(AttributeError, "p.foobar") + assert str(e.value) == "cdata 'struct foo *' has no field 'foobar'" + e = py.test.raises(AttributeError, "p.foobar = 42") + assert str(e.value) == "cdata 'struct foo *' has no field 'foobar'" + e = py.test.raises(AttributeError, "s.foobar") + assert str(e.value) == "cdata 'struct foo' has no field 'foobar'" + e = py.test.raises(AttributeError, "s.foobar = 42") + assert str(e.value) == "cdata 'struct foo' has no field 'foobar'" + j = cast(BInt, 42) + e = py.test.raises(AttributeError, "j.foobar") + assert str(e.value) == "cdata 'int' has no attribute 'foobar'" + e = py.test.raises(AttributeError, "j.foobar = 42") + assert str(e.value) == "cdata 'int' has no attribute 'foobar'" + j = cast(new_pointer_type(BInt), 42) + e = py.test.raises(AttributeError, "j.foobar") + assert str(e.value) == "cdata 'int *' has no attribute 'foobar'" + e = py.test.raises(AttributeError, "j.foobar = 42") + assert str(e.value) == "cdata 'int *' has no attribute 'foobar'" + pp = newp(new_pointer_type(BStructPtr), p) + e = py.test.raises(AttributeError, "pp.a1") + assert str(e.value) == "cdata 'struct foo * *' has no attribute 'a1'" + e = py.test.raises(AttributeError, "pp.a1 = 42") + assert str(e.value) == "cdata 'struct foo * *' has no attribute 'a1'" def test_union_instance(): BInt = new_primitive_type("int") @@ -896,6 +908,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 +1079,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) @@ -2237,12 +2269,17 @@ BVoidP = new_pointer_type(new_void_type()) p = newp(BIntP, 123) q = cast(BInt, 124) - py.test.raises(TypeError, "p < q") - py.test.raises(TypeError, "p <= q") assert (p == q) is False assert (p != q) is True - py.test.raises(TypeError, "p > q") - py.test.raises(TypeError, "p >= q") + assert (q == p) is False + assert (q != p) is True + if strict_compare: + py.test.raises(TypeError, "p < q") + py.test.raises(TypeError, "p <= q") + py.test.raises(TypeError, "q < p") + py.test.raises(TypeError, "q <= p") + py.test.raises(TypeError, "p > q") + py.test.raises(TypeError, "p >= q") r = cast(BVoidP, p) assert (p < r) is False assert (p <= r) is True @@ -2275,6 +2312,7 @@ buf = buffer(c) assert repr(buf).startswith('<_cffi_backend.buffer object at 0x') assert bytes(buf) == b"hi there\x00" + assert type(buf) is buffer if sys.version_info < (3,): assert str(buf) == "hi there\x00" assert unicode(buf) == u+"hi there\x00" @@ -2651,13 +2689,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") @@ -3697,7 +3760,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]), ]: @@ -3767,7 +3830,7 @@ def test_char_pointer_conversion(): import warnings - assert __version__.startswith(("1.8", "1.9")), ( + assert __version__.startswith(("1.8", "1.9", "1.10")), ( "consider turning the warning into an error") BCharP = new_pointer_type(new_primitive_type("char")) BIntP = new_pointer_type(new_primitive_type("int")) @@ -3790,3 +3853,87 @@ assert len(w) == 2 # check that the warnings are associated with lines in this file assert w[1].lineno == w[0].lineno + 4 + +def test_primitive_comparison(): + def assert_eq(a, b): + assert (a == b) is True + assert (b == a) is True + assert (a != b) is False + assert (b != a) is False + assert (a < b) is False + assert (a <= b) is True + assert (a > b) is False + assert (a >= b) is True + assert (b < a) is False + assert (b <= a) is True + assert (b > a) is False + assert (b >= a) is True + assert hash(a) == hash(b) + def assert_lt(a, b, check_hash=True): + assert (a == b) is False + assert (b == a) is False + assert (a != b) is True + assert (b != a) is True + assert (a < b) is True + assert (a <= b) is True + assert (a > b) is False + assert (a >= b) is False + assert (b < a) is False + assert (b <= a) is False + assert (b > a) is True + assert (b >= a) is True + if check_hash: + assert hash(a) != hash(b) # (or at least, it is unlikely) + def assert_gt(a, b, check_hash=True): + assert_lt(b, a, check_hash) + def assert_ne(a, b): + assert (a == b) is False + assert (b == a) is False + assert (a != b) is True + assert (b != a) is True + if strict_compare: + py.test.raises(TypeError, "a < b") + py.test.raises(TypeError, "a <= b") + py.test.raises(TypeError, "a > b") + py.test.raises(TypeError, "a >= b") + py.test.raises(TypeError, "b < a") + py.test.raises(TypeError, "b <= a") + py.test.raises(TypeError, "b > a") + py.test.raises(TypeError, "b >= a") + elif a < b: + assert_lt(a, b) + else: + assert_lt(b, a) + assert_eq(5, 5) + assert_lt(3, 5) + assert_ne('5', 5) + # + t1 = new_primitive_type("char") + t2 = new_primitive_type("int") + t3 = new_primitive_type("unsigned char") + t4 = new_primitive_type("unsigned int") + t5 = new_primitive_type("float") + t6 = new_primitive_type("double") + assert_eq(cast(t1, 65), b'A') + assert_lt(cast(t1, 64), b'\x99') + assert_gt(cast(t1, 200), b'A') + assert_ne(cast(t1, 65), 65) + assert_eq(cast(t2, -25), -25) + assert_lt(cast(t2, -25), -24) + assert_gt(cast(t2, -25), -26) + assert_eq(cast(t3, 65), 65) + assert_ne(cast(t3, 65), b'A') + assert_ne(cast(t3, 65), cast(t1, 65)) + assert_gt(cast(t4, -1), -1, check_hash=False) + assert_gt(cast(t4, -1), cast(t2, -1), check_hash=False) + assert_gt(cast(t4, -1), 99999) + assert_eq(cast(t4, -1), 256 ** size_of_int() - 1) + assert_eq(cast(t5, 3.0), 3) + assert_eq(cast(t5, 3.5), 3.5) + assert_lt(cast(t5, 3.3), 3.3) # imperfect rounding + assert_eq(cast(t6, 3.3), 3.3) + assert_eq(cast(t5, 3.5), cast(t6, 3.5)) + assert_lt(cast(t5, 3.1), cast(t6, 3.1)) # imperfect rounding + assert_eq(cast(t5, 7.0), cast(t3, 7)) + assert_lt(cast(t5, 3.1), 3.101) + assert_gt(cast(t5, 3.1), 3) diff --git a/cffi/__init__.py b/cffi/__init__.py --- a/cffi/__init__.py +++ b/cffi/__init__.py @@ -1,11 +1,11 @@ __all__ = ['FFI', 'VerificationError', 'VerificationMissing', 'CDefError', 'FFIError'] -from .api import FFI, CDefError, FFIError -from .ffiplatform import VerificationError, VerificationMissing +from .api import FFI +from .error import CDefError, FFIError, VerificationError, VerificationMissing -__version__ = "1.9.2" -__version_info__ = (1, 9, 2) +__version__ = "1.10.0" +__version_info__ = (1, 10, 0) # The verifier module file names are based on the CRC32 of a string that # contains the following version number. It may be older than __version__ diff --git a/cffi/_embedding.h b/cffi/_embedding.h --- a/cffi/_embedding.h +++ b/cffi/_embedding.h @@ -233,7 +233,7 @@ f = PySys_GetObject((char *)"stderr"); if (f != NULL && f != Py_None) { PyFile_WriteString("\nFrom: " _CFFI_MODULE_NAME - "\ncompiled with cffi version: 1.9.2" + "\ncompiled with cffi version: 1.10.0" "\n_cffi_backend module: ", f); modules = PyImport_GetModuleDict(); mod = PyDict_GetItemString(modules, "_cffi_backend"); diff --git a/cffi/api.py b/cffi/api.py --- a/cffi/api.py +++ b/cffi/api.py @@ -1,5 +1,7 @@ import sys, types from .lock import allocate_lock +from .error import CDefError +from . import model try: callable @@ -15,17 +17,6 @@ basestring = str -class FFIError(Exception): - pass - -class CDefError(Exception): - def __str__(self): - try: - line = 'line %d: ' % (self.args[1].coord.line,) - except (AttributeError, TypeError, IndexError): - line = '' - return '%s%s' % (line, self.args[0]) - class FFI(object): r''' @@ -49,7 +40,6 @@ """Create an FFI instance. The 'backend' argument is used to select a non-default backend, mostly for tests. """ - from . import cparser, model if backend is None: # You need PyPy (>= 2.0 beta), or a CPython (>= 2.6) with # _cffi_backend.so compiled. @@ -70,6 +60,7 @@ # 'backend=backend_ctypes.CTypesBackend()', but don't # rely on it! It's probably not going to work well.) + from . import cparser self._backend = backend self._lock = allocate_lock() self._parser = cparser.Parser() @@ -102,6 +93,7 @@ # ctypes backend: attach these constants to the instance self.NULL = self.cast(self.BVoidP, 0) self.CData, self.CType = backend._get_types() + self.buffer = backend.buffer def cdef(self, csource, override=False, packed=False): """Parse the given C source. This registers all declared functions, @@ -221,7 +213,7 @@ def offsetof(self, cdecl, *fields_or_indexes): """Return the offset of the named field inside the given - structure or array, which must be given as a C type name. + structure or array, which must be given as a C type name. You can give several field names in case of nested structures. You can also give numeric values which correspond to array items, in case of an array type. @@ -309,7 +301,7 @@ return self._backend.string(cdata, maxlen) def unpack(self, cdata, length): - """Unpack an array of C data of the given length, + """Unpack an array of C data of the given length, returning a Python string/unicode/list. If 'cdata' is a pointer to 'char', returns a byte string. @@ -325,18 +317,18 @@ """ return self._backend.unpack(cdata, length) - def buffer(self, cdata, size=-1): - """Return a read-write buffer object that references the raw C data - pointed to by the given 'cdata'. The 'cdata' must be a pointer or - an array. Can be passed to functions expecting a buffer, or directly - manipulated with: - - buf[:] get a copy of it in a regular string, or - buf[idx] as a single character - buf[:] = ... - buf[idx] = ... change the content - """ - return self._backend.buffer(cdata, size) + #def buffer(self, cdata, size=-1): + # """Return a read-write buffer object that references the raw C data + # pointed to by the given 'cdata'. The 'cdata' must be a pointer or + # an array. Can be passed to functions expecting a buffer, or directly + # manipulated with: + # + # buf[:] get a copy of it in a regular string, or + # buf[idx] as a single character + # buf[:] = ... + # buf[idx] = ... change the content + # """ + # note that 'buffer' is a type, set on this instance by __init__ def from_buffer(self, python_buffer): """Return a <cdata 'char[]'> that points to the data of the @@ -461,7 +453,6 @@ return self._backend.getwinerror(code) def _pointer_to(self, ctype): - from . import model with self._lock: return model.pointer_cache(self, ctype) @@ -471,7 +462,12 @@ field or array item in the structure or array, recursively in case of nested structures. """ - ctype = self._backend.typeof(cdata) + try: + ctype = self._backend.typeof(cdata) + except TypeError: + if '__addressof__' in type(cdata).__dict__: + return type(cdata).__addressof__(cdata, *fields_or_indexes) + raise if fields_or_indexes: ctype, offset = self._typeoffsetof(ctype, *fields_or_indexes) else: @@ -574,7 +570,10 @@ # we need 'libpypy-c.{so,dylib}', which should be by # default located in 'sys.prefix/bin' for installed # systems. - pythonlib = "pypy-c" + if sys.version_info < (3,): + pythonlib = "pypy-c" + else: + pythonlib = "pypy3-c" if hasattr(sys, 'prefix'): ensure('library_dirs', os.path.join(sys.prefix, 'bin')) # On uninstalled pypy's, the libpypy-c is typically found in @@ -603,11 +602,15 @@ ensure('extra_link_args', '/MANIFEST') def set_source(self, module_name, source, source_extension='.c', **kwds): + import os if hasattr(self, '_assigned_source'): raise ValueError("set_source() cannot be called several times " "per ffi object") if not isinstance(module_name, basestring): raise TypeError("'module_name' must be a string") + if os.sep in module_name or (os.altsep and os.altsep in module_name): + raise ValueError("'module_name' must not contain '/': use a dotted " + "name to make a 'package.module' location") self._assigned_source = (str(module_name), source, source_extension, kwds) @@ -756,24 +759,29 @@ def _load_backend_lib(backend, name, flags): + import os if name is None: if sys.platform != "win32": return backend.load_library(None, flags) name = "c" # Windows: load_library(None) fails, but this works # (backward compatibility hack only) - try: - if '.' not in name and '/' not in name: - raise OSError("library not found: %r" % (name,)) - return backend.load_library(name, flags) - except OSError: - import ctypes.util - path = ctypes.util.find_library(name) - if path is None: - raise # propagate the original OSError - return backend.load_library(path, flags) + first_error = None + if '.' in name or '/' in name or os.sep in name: + try: + return backend.load_library(name, flags) + except OSError as e: + first_error = e + import ctypes.util + path = ctypes.util.find_library(name) + if path is None: + msg = ("ctypes.util.find_library() did not manage " + "to locate a library called %r" % (name,)) + if first_error is not None: + msg = "%s. Additionally, %s" % (first_error, msg) + raise OSError(msg) + return backend.load_library(path, flags) def _make_ffi_library(ffi, libname, flags): - import os backend = ffi._backend backendlib = _load_backend_lib(backend, libname, flags) # @@ -781,10 +789,7 @@ key = 'function ' + name tp, _ = ffi._parser._declarations[key] BType = ffi._get_cached_btype(tp) - try: - value = backendlib.load_function(BType, name) - except KeyError as e: - raise AttributeError('%s: %s' % (name, e)) + value = backendlib.load_function(BType, name) library.__dict__[name] = value # def accessor_variable(name): @@ -797,6 +802,21 @@ lambda self: read_variable(BType, name), lambda self, value: write_variable(BType, name, value))) # + def addressof_var(name): + try: + return addr_variables[name] + except KeyError: + with ffi._lock: + if name not in addr_variables: + key = 'variable ' + name + tp, _ = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + if BType.kind != 'array': + BType = model.pointer_cache(ffi, BType) + p = backendlib.load_function(BType, name) + addr_variables[name] = p + return addr_variables[name] + # def accessor_constant(name): raise NotImplementedError("non-integer constant '%s' cannot be " "accessed from a dlopen() library" % (name,)) @@ -806,12 +826,12 @@ # accessors = {} accessors_version = [False] + addr_variables = {} # def update_accessors(): if accessors_version[0] is ffi._cdef_version: return # - from . import model for key, (tp, _) in ffi._parser._declarations.items(): if not isinstance(tp, model.EnumType): tag, name = key.split(' ', 1) @@ -857,6 +877,18 @@ with ffi._lock: update_accessors() return accessors.keys() + def __addressof__(self, name): + if name in library.__dict__: + return library.__dict__[name] + if name in FFILibrary.__dict__: + return addressof_var(name) + make_accessor(name) + if name in library.__dict__: + return library.__dict__[name] + if name in FFILibrary.__dict__: + return addressof_var(name) + raise AttributeError("cffi library has no function or " + "global variable named '%s'" % (name,)) # if libname is not None: try: diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py --- a/cffi/backend_ctypes.py +++ b/cffi/backend_ctypes.py @@ -112,11 +112,20 @@ def _make_cmp(name): cmpfunc = getattr(operator, name) def cmp(self, other): - if isinstance(other, CTypesData): + v_is_ptr = not isinstance(self, CTypesGenericPrimitive) + w_is_ptr = (isinstance(other, CTypesData) and + not isinstance(other, CTypesGenericPrimitive)) + if v_is_ptr and w_is_ptr: return cmpfunc(self._convert_to_address(None), other._convert_to_address(None)) + elif v_is_ptr or w_is_ptr: + return NotImplemented else: - return NotImplemented + if isinstance(self, CTypesGenericPrimitive): + self = self._value + if isinstance(other, CTypesGenericPrimitive): + other = other._value + return cmpfunc(self, other) cmp.func_name = name return cmp @@ -128,7 +137,7 @@ __ge__ = _make_cmp('__ge__') def __hash__(self): - return hash(type(self)) ^ hash(self._convert_to_address(None)) + return hash(self._convert_to_address(None)) def _to_string(self, maxlen): raise TypeError("string(): %r" % (self,)) @@ -137,14 +146,8 @@ class CTypesGenericPrimitive(CTypesData): __slots__ = [] - def __eq__(self, other): - return self is other - - def __ne__(self, other): - return self is not other - def __hash__(self): - return object.__hash__(self) + return hash(self._value) def _get_own_repr(self): return repr(self._from_ctypes(self._value)) diff --git a/cffi/cffi_opcode.py b/cffi/cffi_opcode.py --- a/cffi/cffi_opcode.py +++ b/cffi/cffi_opcode.py @@ -1,3 +1,4 @@ +from .error import VerificationError class CffiOp(object): def __init__(self, op, arg): @@ -19,7 +20,6 @@ % (self.arg,)) return format_four_bytes(value) if isinstance(self.arg, str): - from .ffiplatform import VerificationError raise VerificationError("cannot emit to Python: %r" % (self.arg,)) return format_four_bytes((self.arg << 8) | self.op) diff --git a/cffi/commontypes.py b/cffi/commontypes.py --- a/cffi/commontypes.py +++ b/cffi/commontypes.py @@ -1,5 +1,6 @@ import sys -from . import api, model +from . import model +from .error import FFIError COMMON_TYPES = {} @@ -31,11 +32,11 @@ elif cdecl in model.PrimitiveType.ALL_PRIMITIVE_TYPES: result, quals = model.PrimitiveType(cdecl), 0 elif cdecl == 'set-unicode-needed': - raise api.FFIError("The Windows type %r is only available after " - "you call ffi.set_unicode()" % (commontype,)) + raise FFIError("The Windows type %r is only available after " + "you call ffi.set_unicode()" % (commontype,)) else: if commontype == cdecl: - raise api.FFIError( + raise FFIError( "Unsupported type: %r. Please look at " "http://cffi.readthedocs.io/en/latest/cdef.html#ffi-cdef-limitations " "and file an issue if you think this type should really " diff --git a/cffi/cparser.py b/cffi/cparser.py --- a/cffi/cparser.py +++ b/cffi/cparser.py @@ -1,5 +1,6 @@ -from . import api, model +from . import model from .commontypes import COMMON_TYPES, resolve_common_type +from .error import FFIError, CDefError try: from . import _pycparser as pycparser except ImportError: @@ -33,6 +34,9 @@ r'(Python|Python\s*\+\s*C|C\s*\+\s*Python)"\s*.') _r_star_const_space = re.compile( # matches "* const " r"[*]\s*((const|volatile|restrict)\b\s*)+") +_r_int_dotdotdot = re.compile(r"(\b(int|long|short|signed|unsigned|char)\s*)+" + r"\.\.\.") +_r_float_dotdotdot = re.compile(r"\b(double|float)\s*\.\.\.") def _get_parser(): global _parser_cache @@ -113,7 +117,7 @@ # grouping variant closing = csource.find('}', endpos) if closing < 0: - raise api.CDefError("'extern \"Python\" {': no '}' found") + raise CDefError("'extern \"Python\" {': no '}' found") if csource.find('{', endpos + 1, closing) >= 0: raise NotImplementedError("cannot use { } inside a block " "'extern \"Python\" { ... }'") @@ -123,7 +127,7 @@ # non-grouping variant semicolon = csource.find(';', endpos) if semicolon < 0: - raise api.CDefError("'extern \"Python\": no ';' found") + raise CDefError("'extern \"Python\": no ';' found") parts.append(csource[endpos:semicolon+1]) csource = csource[semicolon+1:] parts.append(' void __cffi_extern_python_stop;') @@ -179,6 +183,10 @@ assert csource[p:p+3] == '...' csource = '%s __dotdotdot%d__ %s' % (csource[:p], number, csource[p+3:]) + # Replace "int ..." or "unsigned long int..." with "__dotdotdotint__" + csource = _r_int_dotdotdot.sub(' __dotdotdotint__ ', csource) + # Replace "float ..." or "double..." with "__dotdotdotfloat__" + csource = _r_float_dotdotdot.sub(' __dotdotdotfloat__ ', csource) # Replace all remaining "..." with the same name, "__dotdotdot__", # which is declared with a typedef for the purpose of C parsing. return csource.replace('...', ' __dotdotdot__ '), macros @@ -251,7 +259,8 @@ typenames += sorted(ctn) # csourcelines = ['typedef int %s;' % typename for typename in typenames] - csourcelines.append('typedef int __dotdotdot__;') + csourcelines.append('typedef int __dotdotdotint__, __dotdotdotfloat__,' + ' __dotdotdot__;') csourcelines.append(csource) csource = '\n'.join(csourcelines) if lock is not None: @@ -288,7 +297,7 @@ msg = 'cannot parse "%s"\n%s' % (line.strip(), msg) else: msg = 'parse error\n%s' % (msg,) - raise api.CDefError(msg) + raise CDefError(msg) def parse(self, csource, override=False, packed=False, dllexport=False): prev_options = self._options @@ -310,6 +319,8 @@ for decl in iterator: if decl.name == '__dotdotdot__': break + else: + assert 0 # try: self._inside_extern_python = '__cffi_extern_python_stop' @@ -318,18 +329,18 @@ self._parse_decl(decl) elif isinstance(decl, pycparser.c_ast.Typedef): if not decl.name: - raise api.CDefError("typedef does not declare any name", - decl) + raise CDefError("typedef does not declare any name", + decl) quals = 0 - if (isinstance(decl.type.type, pycparser.c_ast.IdentifierType) - and decl.type.type.names[-1] == '__dotdotdot__'): + if (isinstance(decl.type.type, pycparser.c_ast.IdentifierType) and + decl.type.type.names[-1].startswith('__dotdotdot')): realtype = self._get_unknown_type(decl) elif (isinstance(decl.type, pycparser.c_ast.PtrDecl) and isinstance(decl.type.type, pycparser.c_ast.TypeDecl) and isinstance(decl.type.type.type, pycparser.c_ast.IdentifierType) and - decl.type.type.type.names == ['__dotdotdot__']): - realtype = model.unknown_ptr_type(decl.name) + decl.type.type.type.names[-1].startswith('__dotdotdot')): + realtype = self._get_unknown_ptr_type(decl) else: realtype, quals = self._get_type_and_quals( decl.type, name=decl.name, partial_length_ok=True) @@ -337,8 +348,8 @@ elif decl.__class__.__name__ == 'Pragma': pass # skip pragma, only in pycparser 2.15 else: - raise api.CDefError("unrecognized construct", decl) - except api.FFIError as e: + raise CDefError("unrecognized construct", decl) + except FFIError as e: msg = self._convert_pycparser_error(e, csource) if msg: e.args = (e.args[0] + "\n *** Err: %s" % msg,) @@ -348,7 +359,7 @@ if key in self._int_constants: if self._int_constants[key] == val: return # ignore identical double declarations - raise api.FFIError( + raise FFIError( "multiple declarations of constant: %s" % (key,)) self._int_constants[key] = val @@ -375,7 +386,7 @@ elif value == '...': self._declare('macro ' + key, value) else: - raise api.CDefError( + raise CDefError( 'only supports one of the following syntax:\n' ' #define %s ... (literally dot-dot-dot)\n' ' #define %s NUMBER (with NUMBER an integer' @@ -410,8 +421,8 @@ elif isinstance(node, pycparser.c_ast.Enum): self._get_struct_union_enum_type('enum', node) elif not decl.name: - raise api.CDefError("construct does not declare any variable", - decl) + raise CDefError("construct does not declare any variable", + decl) # if decl.name: tp, quals = self._get_type_and_quals(node, @@ -438,7 +449,7 @@ self._inside_extern_python = decl.name else: if self._inside_extern_python !='__cffi_extern_python_stop': - raise api.CDefError( + raise CDefError( "cannot declare constants or " "variables with 'extern \"Python\"'") if (quals & model.Q_CONST) and not tp.is_array_type: @@ -454,7 +465,7 @@ assert not macros exprnode = ast.ext[-1].type.args.params[0] if isinstance(exprnode, pycparser.c_ast.ID): - raise api.CDefError("unknown identifier '%s'" % (exprnode.name,)) + raise CDefError("unknown identifier '%s'" % (exprnode.name,)) return self._get_type_and_quals(exprnode.type) def _declare(self, name, obj, included=False, quals=0): @@ -463,7 +474,7 @@ if prevobj is obj and prevquals == quals: return if not self._options.get('override'): - raise api.FFIError( + raise FFIError( "multiple declarations of %s (for interactive usage, " "try cdef(xx, override=True))" % (name,)) assert '__dotdotdot__' not in name.split() @@ -551,7 +562,7 @@ if ident == 'void': return model.void_type, quals if ident == '__dotdotdot__': - raise api.FFIError(':%d: bad usage of "..."' % + raise FFIError(':%d: bad usage of "..."' % typenode.coord.line) tp0, quals0 = resolve_common_type(self, ident) return tp0, (quals | quals0) @@ -583,14 +594,14 @@ return self._get_struct_union_enum_type('union', typenode, name, nested=True), 0 # - raise api.FFIError(":%d: bad or unsupported type declaration" % + raise FFIError(":%d: bad or unsupported type declaration" % typenode.coord.line) def _parse_function_type(self, typenode, funcname=None): params = list(getattr(typenode.args, 'params', [])) for i, arg in enumerate(params): if not hasattr(arg, 'type'): - raise api.CDefError("%s arg %d: unknown type '%s'" + raise CDefError("%s arg %d: unknown type '%s'" " (if you meant to use the old C syntax of giving" " untyped arguments, it is not supported)" % (funcname or 'in expression', i + 1, @@ -604,7 +615,7 @@ if ellipsis: params.pop() if not params: - raise api.CDefError( + raise CDefError( "%s: a function with only '(...)' as argument" " is not correct C" % (funcname or 'in expression')) args = [self._as_func_arg(*self._get_type_and_quals(argdeclnode.type)) @@ -705,7 +716,7 @@ return tp # if tp.fldnames is not None: - raise api.CDefError("duplicate declaration of struct %s" % name) + raise CDefError("duplicate declaration of struct %s" % name) fldnames = [] fldtypes = [] fldbitsize = [] @@ -749,7 +760,7 @@ def _make_partial(self, tp, nested): if not isinstance(tp, model.StructOrUnion): - raise api.CDefError("%s cannot be partial" % (tp,)) + raise CDefError("%s cannot be partial" % (tp,)) if not tp.has_c_name() and not nested: raise NotImplementedError("%s is partial but has no C name" %(tp,)) tp.partial = True @@ -769,7 +780,7 @@ len(s) == 3 or (len(s) == 4 and s[1] == "\\")): return ord(s[-2]) else: - raise api.CDefError("invalid constant %r" % (s,)) + raise CDefError("invalid constant %r" % (s,)) # if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and exprnode.op == '+'): @@ -788,12 +799,12 @@ if partial_length_ok: self._partial_length = True return '...' - raise api.FFIError(":%d: unsupported '[...]' here, cannot derive " - "the actual array length in this context" - % exprnode.coord.line) + raise FFIError(":%d: unsupported '[...]' here, cannot derive " + "the actual array length in this context" + % exprnode.coord.line) # - raise api.FFIError(":%d: unsupported expression: expected a " - "simple numeric constant" % exprnode.coord.line) + raise FFIError(":%d: unsupported expression: expected a " + "simple numeric constant" % exprnode.coord.line) def _build_enum_type(self, explicit_name, decls): if decls is not None: @@ -831,24 +842,25 @@ def _get_unknown_type(self, decl): typenames = decl.type.type.names - assert typenames[-1] == '__dotdotdot__' - if len(typenames) == 1: + if typenames == ['__dotdotdot__']: return model.unknown_type(decl.name) - if (typenames[:-1] == ['float'] or - typenames[:-1] == ['double']): - # not for 'long double' so far - result = model.UnknownFloatType(decl.name) - else: - for t in typenames[:-1]: - if t not in ['int', 'short', 'long', 'signed', - 'unsigned', 'char']: - raise api.FFIError(':%d: bad usage of "..."' % - decl.coord.line) - result = model.UnknownIntegerType(decl.name) + if typenames == ['__dotdotdotint__']: + if self._uses_new_feature is None: + self._uses_new_feature = "'typedef int... %s'" % decl.name + return model.UnknownIntegerType(decl.name) - if self._uses_new_feature is None: - self._uses_new_feature = "'typedef %s... %s'" % ( - ' '.join(typenames[:-1]), decl.name) + if typenames == ['__dotdotdotfloat__']: + # note: not for 'long double' so far + if self._uses_new_feature is None: + self._uses_new_feature = "'typedef float... %s'" % decl.name + return model.UnknownFloatType(decl.name) - return result + raise FFIError(':%d: unsupported usage of "..." in typedef' + % decl.coord.line) + + def _get_unknown_ptr_type(self, decl): + if decl.type.type.type.names == ['__dotdotdot__']: + return model.unknown_ptr_type(decl.name) + raise FFIError(':%d: unsupported usage of "..." in typedef' + % decl.coord.line) diff --git a/cffi/error.py b/cffi/error.py new file mode 100644 --- /dev/null +++ b/cffi/error.py @@ -0,0 +1,20 @@ + +class FFIError(Exception): + pass + +class CDefError(Exception): + def __str__(self): + try: + line = 'line %d: ' % (self.args[1].coord.line,) + except (AttributeError, TypeError, IndexError): + line = '' + return '%s%s' % (line, self.args[0]) + +class VerificationError(Exception): + """ An error raised when verification fails + """ + +class VerificationMissing(Exception): + """ An error raised when incomplete structures are passed into + cdef, but no verification has been done + """ diff --git a/cffi/ffiplatform.py b/cffi/ffiplatform.py --- a/cffi/ffiplatform.py +++ b/cffi/ffiplatform.py @@ -1,14 +1,5 @@ import sys, os - - -class VerificationError(Exception): - """ An error raised when verification fails - """ - -class VerificationMissing(Exception): - """ An error raised when incomplete structures are passed into - cdef, but no verification has been done - """ +from .error import VerificationError LIST_OF_FILE_NAMES = ['sources', 'include_dirs', 'library_dirs', diff --git a/cffi/model.py b/cffi/model.py --- a/cffi/model.py +++ b/cffi/model.py @@ -1,8 +1,8 @@ -import types, sys +import types import weakref from .lock import allocate_lock - +from .error import CDefError, VerificationError, VerificationMissing # type qualifiers Q_CONST = 0x01 @@ -39,7 +39,6 @@ replace_with = qualify(quals, replace_with) result = result.replace('&', replace_with) if '$' in result: - from .ffiplatform import VerificationError raise VerificationError( "cannot generate '%s' in %s: unknown type name" % (self._get_c_name(), context)) @@ -225,9 +224,8 @@ is_raw_function = True def build_backend_type(self, ffi, finishlist): - from . import api - raise api.CDefError("cannot render the type %r: it is a function " - "type, not a pointer-to-function type" % (self,)) + raise CDefError("cannot render the type %r: it is a function " + "type, not a pointer-to-function type" % (self,)) def as_function_pointer(self): return FunctionPtrType(self.args, self.result, self.ellipsis, self.abi) @@ -309,9 +307,8 @@ def build_backend_type(self, ffi, finishlist): if self.length == '...': - from . import api - raise api.CDefError("cannot render the type %r: unknown length" % - (self,)) + raise CDefError("cannot render the type %r: unknown length" % + (self,)) self.item.get_cached_btype(ffi, finishlist) # force the item BType BPtrItem = PointerType(self.item).get_cached_btype(ffi, finishlist) return global_cache(self, ffi, 'new_array_type', BPtrItem, self.length) @@ -457,13 +454,11 @@ self.completed = 2 def _verification_error(self, msg): - from .ffiplatform import VerificationError raise VerificationError(msg) def check_not_partial(self): if self.partial and self.fixedlayout is None: - from . import ffiplatform - raise ffiplatform.VerificationMissing(self._get_c_name()) + raise VerificationMissing(self._get_c_name()) def build_backend_type(self, ffi, finishlist): self.check_not_partial() @@ -501,8 +496,7 @@ def check_not_partial(self): if self.partial and not self.partial_resolved: - from . import ffiplatform - raise ffiplatform.VerificationMissing(self._get_c_name()) + raise VerificationMissing(self._get_c_name()) def build_backend_type(self, ffi, finishlist): self.check_not_partial() @@ -516,7 +510,6 @@ if self.baseinttype is not None: return self.baseinttype.get_cached_btype(ffi, finishlist) # - from . import api if self.enumvalues: smallest_value = min(self.enumvalues) largest_value = max(self.enumvalues) @@ -551,8 +544,8 @@ if (smallest_value >= ((-1) << (8*size2-1)) and largest_value < (1 << (8*size2-sign))): return btype2 - raise api.CDefError("%s values don't all fit into either 'long' " - "or 'unsigned long'" % self._get_c_name()) + raise CDefError("%s values don't all fit into either 'long' " + "or 'unsigned long'" % self._get_c_name()) def unknown_type(name, structname=None): if structname is None: diff --git a/cffi/recompiler.py b/cffi/recompiler.py --- a/cffi/recompiler.py +++ b/cffi/recompiler.py @@ -1,5 +1,6 @@ import os, sys, io from . import ffiplatform, model +from .error import VerificationError from .cffi_opcode import * VERSION = "0x2601" @@ -211,7 +212,7 @@ method = getattr(self, '_generate_cpy_%s_%s' % (kind, step_name)) except AttributeError: - raise ffiplatform.VerificationError( + raise VerificationError( "not implemented in recompile(): %r" % name) try: self._current_quals = quals @@ -354,12 +355,12 @@ included_module_name, included_source = ( ffi_to_include._assigned_source[:2]) except AttributeError: - raise ffiplatform.VerificationError( + raise VerificationError( "ffi object %r includes %r, but the latter has not " "been prepared with set_source()" % ( self.ffi, ffi_to_include,)) if included_source is None: - raise ffiplatform.VerificationError( + raise VerificationError( "not implemented yet: ffi.include() of a Python-based " "ffi inside a C-based ffi") prnt(' "%s",' % (included_module_name,)) @@ -391,6 +392,10 @@ prnt() # # the init function + prnt('#ifdef __GNUC__') + prnt('# pragma GCC visibility push(default) /* for -fvisibility= */') + prnt('#endif') + prnt() prnt('#ifdef PYPY_VERSION') prnt('PyMODINIT_FUNC') prnt('_cffi_pypyinit_%s(const void *p[])' % (base_module_name,)) @@ -429,6 +434,10 @@ self.module_name, version)) prnt('}') prnt('#endif') + prnt() + prnt('#ifdef __GNUC__') + prnt('# pragma GCC visibility pop') + prnt('#endif') def _to_py(self, x): if isinstance(x, str): @@ -456,12 +465,12 @@ included_module_name, included_source = ( ffi_to_include._assigned_source[:2]) except AttributeError: - raise ffiplatform.VerificationError( + raise VerificationError( "ffi object %r includes %r, but the latter has not " "been prepared with set_source()" % ( self.ffi, ffi_to_include,)) if included_source is not None: - raise ffiplatform.VerificationError( + raise VerificationError( "not implemented yet: ffi.include() of a C-based " "ffi inside a Python-based ffi") prnt('from %s import ffi as _ffi%d' % (included_module_name, i)) @@ -831,7 +840,7 @@ prnt(' { %s = &p->%s; (void)tmp; }' % ( ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), fname)) - except ffiplatform.VerificationError as e: + except VerificationError as e: prnt(' /* %s */' % str(e)) # cannot verify it, ignore prnt('}') prnt('struct _cffi_align_%s { char x; %s y; };' % (approxname, cname)) @@ -994,7 +1003,7 @@ def _generate_cpy_const(self, is_int, name, tp=None, category='const', check_value=None): if (category, name) in self._seen_constants: - raise ffiplatform.VerificationError( + raise VerificationError( "duplicate declaration of %s '%s'" % (category, name)) self._seen_constants.add((category, name)) # @@ -1093,7 +1102,7 @@ def _generate_cpy_macro_ctx(self, tp, name): if tp == '...': if self.target_is_python: - raise ffiplatform.VerificationError( + raise VerificationError( "cannot use the syntax '...' in '#define %s ...' when " "using the ABI mode" % (name,)) check_value = None @@ -1226,7 +1235,7 @@ def _generate_cpy_extern_python_ctx(self, tp, name): if self.target_is_python: - raise ffiplatform.VerificationError( + raise VerificationError( "cannot use 'extern \"Python\"' in the ABI mode") if tp.ellipsis: raise NotImplementedError("a vararg function is extern \"Python\"") @@ -1307,7 +1316,7 @@ if tp.length is None: self.cffi_types[index] = CffiOp(OP_OPEN_ARRAY, item_index) elif tp.length == '...': - raise ffiplatform.VerificationError( + raise VerificationError( "type %s badly placed: the '...' array length can only be " "used on global arrays or on fields of structures" % ( str(tp).replace('/*...*/', '...'),)) diff --git a/cffi/vengine_cpy.py b/cffi/vengine_cpy.py --- a/cffi/vengine_cpy.py +++ b/cffi/vengine_cpy.py @@ -2,7 +2,8 @@ # DEPRECATED: implementation for ffi.verify() # import sys, imp -from . import model, ffiplatform +from . import model +from .error import VerificationError class VCPythonEngine(object): @@ -155,7 +156,7 @@ self.verifier.modulefilename) except ImportError as e: error = "importing %r: %s" % (self.verifier.modulefilename, e) - raise ffiplatform.VerificationError(error) + raise VerificationError(error) finally: if hasattr(sys, "setdlopenflags"): sys.setdlopenflags(previous_flags) @@ -185,7 +186,7 @@ def __dir__(self): return FFILibrary._cffi_dir + list(self.__dict__) library = FFILibrary() - if module._cffi_setup(lst, ffiplatform.VerificationError, library): + if module._cffi_setup(lst, VerificationError, library): import warnings warnings.warn("reimporting %r might overwrite older definitions" % (self.verifier.get_module_name())) @@ -212,7 +213,7 @@ method = getattr(self, '_generate_cpy_%s_%s' % (kind, step_name)) except AttributeError: - raise ffiplatform.VerificationError( + raise VerificationError( "not implemented in verify(): %r" % name) try: method(tp, realname) @@ -485,7 +486,7 @@ prnt(' { %s = &p->%s; (void)tmp; }' % ( ftype.get_c_name('*tmp', 'field %r'%fname, quals=fqual), fname)) - except ffiplatform.VerificationError as e: + except VerificationError as e: prnt(' /* %s */' % str(e)) # cannot verify it, ignore prnt('}') prnt('static PyObject *') @@ -550,7 +551,7 @@ # check that the layout sizes and offsets match the real ones def check(realvalue, expectedvalue, msg): if realvalue != expectedvalue: - raise ffiplatform.VerificationError( + raise VerificationError( "%s (we have %d, but C compiler says %d)" % (msg, expectedvalue, realvalue)) ffi = self.ffi @@ -771,7 +772,7 @@ BItemType = self.ffi._get_cached_btype(tp.item) length, rest = divmod(size, self.ffi.sizeof(BItemType)) if rest != 0: - raise ffiplatform.VerificationError( + raise VerificationError( "bad size: %r does not seem to be an array of %s" % (name, tp.item)) tp = tp.resolve_length(length) diff --git a/cffi/vengine_gen.py b/cffi/vengine_gen.py --- a/cffi/vengine_gen.py +++ b/cffi/vengine_gen.py @@ -4,7 +4,8 @@ import sys, os import types -from . import model, ffiplatform _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit