diff options
| author | Armin Rigo <arigo@tunes.org> | 2012-08-12 15:53:06 +0200 |
|---|---|---|
| committer | Armin Rigo <arigo@tunes.org> | 2012-08-12 15:53:06 +0200 |
| commit | 07613592b703a7cf887091820df0e3c7ea25eebe (patch) | |
| tree | e6ad090f78f0be1959a00f0cae8ba7a7b7befa7a | |
| parent | 2a3e5600e8a47d9f604bff219869d85bf46b0d33 (diff) | |
| parent | 6751d3980678045f9e35892fd8ddb454ce79f4ec (diff) | |
| download | cffi-07613592b703a7cf887091820df0e3c7ea25eebe.tar.gz | |
hg merge default, and try to resolve all conflicts.
Not tested so far.
| -rw-r--r-- | c/_cffi_backend.c | 779 | ||||
| -rw-r--r-- | c/misc_win32.h | 3 | ||||
| -rw-r--r-- | c/test_c.py | 298 | ||||
| -rw-r--r-- | cffi/__init__.py | 4 | ||||
| -rw-r--r-- | cffi/api.py | 96 | ||||
| -rw-r--r-- | cffi/backend_ctypes.py | 118 | ||||
| -rw-r--r-- | cffi/gc_weakref.py | 19 | ||||
| -rw-r--r-- | cffi/vengine_cpy.py | 776 | ||||
| -rw-r--r-- | cffi/vengine_gen.py | 458 | ||||
| -rw-r--r-- | cffi/verifier.py | 803 | ||||
| -rw-r--r-- | demo/_curses.py | 4 | ||||
| -rw-r--r-- | demo/bsdopendirtype.py | 2 | ||||
| -rw-r--r-- | demo/gmp.py | 2 | ||||
| -rw-r--r-- | demo/pwuid.py | 2 | ||||
| -rw-r--r-- | demo/readdir.py | 2 | ||||
| -rw-r--r-- | demo/readdir2.py | 2 | ||||
| -rw-r--r-- | demo/readdir_ctypes.py | 69 | ||||
| -rw-r--r-- | doc/source/conf.py | 4 | ||||
| -rw-r--r-- | doc/source/index.rst | 213 | ||||
| -rw-r--r-- | setup.py | 22 | ||||
| -rw-r--r-- | testing/backend_tests.py | 175 | ||||
| -rw-r--r-- | testing/test_ctypes.py | 11 | ||||
| -rw-r--r-- | testing/test_function.py | 39 | ||||
| -rw-r--r-- | testing/test_ownlib.py | 42 | ||||
| -rw-r--r-- | testing/test_verify.py | 103 | ||||
| -rw-r--r-- | testing/test_vgen.py | 12 | ||||
| -rw-r--r-- | testing/test_vgen2.py | 13 | ||||
| -rw-r--r-- | testing/test_zdistutils.py | 287 |
28 files changed, 2929 insertions, 1429 deletions
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c index 2e6cf8f..27cb91b 100644 --- a/c/_cffi_backend.c +++ b/c/_cffi_backend.c @@ -23,6 +23,7 @@ #endif #if PY_MAJOR_VERSION >= 3 +# define STR_OR_BYTES "bytes" # define PyText_Type PyUnicode_Type # define PyText_Check PyUnicode_Check # define PyText_FromFormat PyUnicode_FromFormat @@ -32,6 +33,7 @@ # define PyText_FromStringAndSize PyUnicode_FromStringAndSize # define PyText_InternInPlace PyUnicode_InternInPlace #else +# define STR_OR_BYTES "str" # define PyText_Type PyString_Type # define PyText_Check PyString_Check # define PyText_FromFormat PyString_FromFormat @@ -63,7 +65,7 @@ #define CT_PRIMITIVE_SIGNED 1 /* signed integer */ #define CT_PRIMITIVE_UNSIGNED 2 /* unsigned integer */ #define CT_PRIMITIVE_CHAR 4 /* char, wchar_t */ -#define CT_PRIMITIVE_FLOAT 8 /* float, double */ +#define CT_PRIMITIVE_FLOAT 8 /* float, double, long double */ #define CT_POINTER 16 /* pointer, excluding ptr-to-func */ #define CT_ARRAY 32 /* array */ #define CT_STRUCT 64 /* struct */ @@ -78,6 +80,7 @@ #define CT_IS_ENUM 8192 #define CT_IS_PTR_TO_OWNED 16384 #define CT_CUSTOM_FIELD_POS 32768 +#define CT_IS_LONGDOUBLE 65536 #define CT_PRIMITIVE_ANY (CT_PRIMITIVE_SIGNED | \ CT_PRIMITIVE_UNSIGNED | \ CT_PRIMITIVE_CHAR | \ @@ -99,7 +102,8 @@ typedef struct _ctypedescr { Py_ssize_t ct_size; /* size of instances, or -1 if unknown */ Py_ssize_t ct_length; /* length of arrays, or -1 if unknown; - or alignment of primitive and struct types */ + or alignment of primitive and struct types; + always -1 for pointers */ int ct_flags; /* CT_xxx flags */ int ct_name_position; /* index in ct_name of where to put a var name */ @@ -110,15 +114,19 @@ typedef struct { PyObject_HEAD CTypeDescrObject *c_type; char *c_data; + PyObject *c_weakreflist; } CDataObject; typedef struct cfieldobject_s { PyObject_HEAD CTypeDescrObject *cf_type; Py_ssize_t cf_offset; - short cf_bitshift, cf_bitsize; + short cf_bitshift; /* >= 0: bitshift; or BS_REGULAR or BS_EMPTY_ARRAY */ + short cf_bitsize; struct cfieldobject_s *cf_next; } CFieldObject; +#define BS_REGULAR (-1) /* a regular field, not with bitshift */ +#define BS_EMPTY_ARRAY (-2) /* a field which is an array 'type[0]' */ static PyTypeObject CTypeDescr_Type; static PyTypeObject CField_Type; @@ -138,6 +146,7 @@ typedef union { unsigned long long m_longlong; float m_float; double m_double; + long double m_longdouble; } union_alignment; typedef struct { @@ -147,22 +156,17 @@ typedef struct { typedef struct { CDataObject head; - PyObject *weakreflist; -} CDataObject_own_base; - -typedef struct { - CDataObject_own_base head; union_alignment alignment; } CDataObject_own_nolength; typedef struct { - CDataObject_own_base head; + CDataObject head; Py_ssize_t length; union_alignment alignment; } CDataObject_own_length; typedef struct { - CDataObject_own_base head; + CDataObject head; PyObject *structobj; } CDataObject_own_structptr; @@ -553,6 +557,12 @@ read_raw_float_data(char *target, int size) } } +static long double +read_raw_longdouble_data(char *target) +{ + return *((long double*)target); +} + static void write_raw_float_data(char *target, double source, int size) { @@ -564,6 +574,12 @@ write_raw_float_data(char *target, double source, int size) Py_FatalError("write_raw_float_data: bad float size"); } +static void +write_raw_longdouble_data(char *target, long double source) +{ + *((long double*)target) = source; +} + static PyObject * new_simple_cdata(char *data, CTypeDescrObject *ct) { @@ -573,15 +589,17 @@ new_simple_cdata(char *data, CTypeDescrObject *ct) Py_INCREF(ct); cd->c_data = data; cd->c_type = ct; + cd->c_weakreflist = NULL; return (PyObject *)cd; } static PyObject *convert_enum_string_to_int(CTypeDescrObject *ct, PyObject *ob) { PyObject *d_value; + char *p = PyText_AsUTF8(ob); - if (PyText_AsUTF8(ob)[0] == '#') { - char *number = PyText_AsUTF8(ob) + 1; /* strip initial '#' */ + if (p[0] == '#') { + char *number = p + 1; /* strip initial '#' */ PyObject *ob2 = PyText_FromString(number); if (ob2 == NULL) return NULL; @@ -602,6 +620,8 @@ static PyObject *convert_enum_string_to_int(CTypeDescrObject *ct, PyObject *ob) return d_value; } +static CDataObject *_new_casted_primitive(CTypeDescrObject *ct); /*forward*/ + static PyObject * convert_to_object(char *data, CTypeDescrObject *ct) { @@ -616,7 +636,16 @@ convert_to_object(char *data, CTypeDescrObject *ct) ct->ct_name); return NULL; } - else if (ct->ct_flags & (CT_ARRAY|CT_STRUCT|CT_UNION)) { + else if (ct->ct_flags & (CT_STRUCT|CT_UNION)) { + return new_simple_cdata(data, ct); + } + else if (ct->ct_flags & CT_ARRAY) { + if (ct->ct_length < 0) { + /* we can't return a <cdata 'int[]'> here, because we don't + know the length to give it. As a compromize, returns + <cdata 'int *'> in this case. */ + ct = (CTypeDescrObject *)ct->ct_stuff; + } return new_simple_cdata(data, ct); } } @@ -650,8 +679,17 @@ convert_to_object(char *data, CTypeDescrObject *ct) return PyLong_FromUnsignedLongLong(value); } else if (ct->ct_flags & CT_PRIMITIVE_FLOAT) { - double value = read_raw_float_data(data, ct->ct_size); - return PyFloat_FromDouble(value); + if (!(ct->ct_flags & CT_IS_LONGDOUBLE)) { + double value = read_raw_float_data(data, ct->ct_size); + return PyFloat_FromDouble(value); + } + else { + long double value = read_raw_longdouble_data(data); + CDataObject *cd = _new_casted_primitive(ct); + if (cd != NULL) + write_raw_longdouble_data(cd->c_data, value); + return (PyObject *)cd; + } } else if (ct->ct_flags & CT_PRIMITIVE_CHAR) { if (ct->ct_size == sizeof(char)) @@ -793,78 +831,91 @@ convert_field_from_object(char *data, CFieldObject *cf, PyObject *value) } static int -convert_from_object(char *data, CTypeDescrObject *ct, PyObject *init) +convert_array_from_object(char *data, CTypeDescrObject *ct, PyObject *init) { + /* used by convert_from_object(), and also to decode lists/tuples/unicodes + passed as function arguments. 'ct' is an CT_ARRAY in the first case + and a CT_POINTER in the second case. */ const char *expected; - char buf[sizeof(PY_LONG_LONG)]; + CTypeDescrObject *ctitem = ct->ct_itemdescr; - if (ct->ct_flags & CT_ARRAY) { - CTypeDescrObject *ctitem = ct->ct_itemdescr; - - if (PyList_Check(init) || PyTuple_Check(init)) { - PyObject **items; - Py_ssize_t i, n; - n = PySequence_Fast_GET_SIZE(init); + if (PyList_Check(init) || PyTuple_Check(init)) { + PyObject **items; + Py_ssize_t i, n; + n = PySequence_Fast_GET_SIZE(init); + if (ct->ct_length >= 0 && n > ct->ct_length) { + PyErr_Format(PyExc_IndexError, + "too many initializers for '%s' (got %zd)", + ct->ct_name, n); + return -1; + } + items = PySequence_Fast_ITEMS(init); + for (i=0; i<n; i++) { + if (convert_from_object(data, ctitem, items[i]) < 0) + return -1; + data += ctitem->ct_size; + } + return 0; + } + else if (ctitem->ct_flags & CT_PRIMITIVE_CHAR) { + if (ctitem->ct_size == sizeof(char)) { + char *srcdata; + Py_ssize_t n; + if (!PyBytes_Check(init)) { + expected = STR_OR_BYTES" or list or tuple"; + goto cannot_convert; + } + n = PyString_GET_SIZE(init); if (ct->ct_length >= 0 && n > ct->ct_length) { PyErr_Format(PyExc_IndexError, - "too many initializers for '%s' (got %zd)", - ct->ct_name, n); + "initializer "STR_OR_BYTES" is too long for '%s' " + "(got %zd characters)", ct->ct_name, n); return -1; } - items = PySequence_Fast_ITEMS(init); - for (i=0; i<n; i++) { - if (convert_from_object(data, ctitem, items[i]) < 0) - return -1; - data += ctitem->ct_size; - } + if (n != ct->ct_length) + n++; + srcdata = PyString_AS_STRING(init); + memcpy(data, srcdata, n); return 0; } - else if (ctitem->ct_flags & CT_PRIMITIVE_CHAR) { - if (ctitem->ct_size == sizeof(char)) { - char *srcdata; - Py_ssize_t n; - if (!PyBytes_Check(init)) { - expected = "bytes or list or tuple"; - goto cannot_convert; - } - n = PyBytes_GET_SIZE(init); - if (ct->ct_length >= 0 && n > ct->ct_length) { - PyErr_Format(PyExc_IndexError, - "initializer string is too long for '%s' " - "(got %zd characters)", ct->ct_name, n); - return -1; - } - if (n != ct->ct_length) - n++; - srcdata = PyBytes_AS_STRING(init); - memcpy(data, srcdata, n); - return 0; - } #ifdef HAVE_WCHAR_H - else { - Py_ssize_t n; - if (!PyUnicode_Check(init)) { - expected = "unicode or list or tuple"; - goto cannot_convert; - } - n = _my_PyUnicode_SizeAsWideChar(init); - if (ct->ct_length >= 0 && n > ct->ct_length) { - PyErr_Format(PyExc_IndexError, - "initializer unicode is too long for '%s' " - "(got %zd characters)", ct->ct_name, n); - return -1; - } - if (n != ct->ct_length) - n++; - _my_PyUnicode_AsWideChar(init, (wchar_t *)data, n); - return 0; - } -#endif - } else { - expected = "list or tuple"; - goto cannot_convert; + Py_ssize_t n; + if (!PyUnicode_Check(init)) { + expected = "unicode or list or tuple"; + goto cannot_convert; + } + n = _my_PyUnicode_SizeAsWideChar(init); + if (ct->ct_length >= 0 && n > ct->ct_length) { + PyErr_Format(PyExc_IndexError, + "initializer unicode is too long for '%s' " + "(got %zd characters)", ct->ct_name, n); + return -1; + } + if (n != ct->ct_length) + n++; + _my_PyUnicode_AsWideChar(init, (wchar_t *)data, n); + return 0; } +#endif + } + else { + expected = "list or tuple"; + goto cannot_convert; + } + + cannot_convert: + return _convert_error(init, ct->ct_name, expected); +} + +static int +convert_from_object(char *data, CTypeDescrObject *ct, PyObject *init) +{ + const char *expected; + char buf[sizeof(PY_LONG_LONG)]; + + if (ct->ct_flags & CT_ARRAY) { + return convert_array_from_object(data, ct, init); } if (ct->ct_flags & (CT_POINTER|CT_FUNCTIONPTR)) { char *ptrdata; @@ -937,10 +988,22 @@ convert_from_object(char *data, CTypeDescrObject *ct, PyObject *init) return 0; } if (ct->ct_flags & CT_PRIMITIVE_FLOAT) { - double value = PyFloat_AsDouble(init); + double value; + if ((ct->ct_flags & CT_IS_LONGDOUBLE) && + CData_Check(init) && + (((CDataObject *)init)->c_type->ct_flags & CT_IS_LONGDOUBLE)) { + long double lvalue; + lvalue = read_raw_longdouble_data(((CDataObject *)init)->c_data); + write_raw_longdouble_data(data, lvalue); + return 0; + } + value = PyFloat_AsDouble(init); if (value == -1.0 && PyErr_Occurred()) return -1; - write_raw_float_data(data, value, ct->ct_size); + if (!(ct->ct_flags & CT_IS_LONGDOUBLE)) + write_raw_float_data(data, value, ct->ct_size); + else + write_raw_longdouble_data(data, (long double)value); return 0; } if (ct->ct_flags & CT_PRIMITIVE_CHAR) { @@ -1128,26 +1191,26 @@ get_alignment(CTypeDescrObject *ct) static void cdata_dealloc(CDataObject *cd) { + if (cd->c_weakreflist != NULL) + PyObject_ClearWeakRefs((PyObject *) cd); + Py_DECREF(cd->c_type); PyObject_Del(cd); } -static void cdataowning_dealloc(CDataObject_own_base *cdb) +static void cdataowning_dealloc(CDataObject *cd) { - if (cdb->weakreflist != NULL) - PyObject_ClearWeakRefs((PyObject *) cdb); - - if (cdb->head.c_type->ct_flags & CT_IS_PTR_TO_OWNED) { - Py_DECREF(((CDataObject_own_structptr *)cdb)->structobj); + if (cd->c_type->ct_flags & CT_IS_PTR_TO_OWNED) { + Py_DECREF(((CDataObject_own_structptr *)cd)->structobj); } - else if (cdb->head.c_type->ct_flags & CT_FUNCTIONPTR) { + else if (cd->c_type->ct_flags & CT_FUNCTIONPTR) { /* a callback */ - ffi_closure *closure = (ffi_closure *)cdb->head.c_data; + ffi_closure *closure = (ffi_closure *)cd->c_data; PyObject *args = (PyObject *)(closure->user_data); Py_XDECREF(args); cffi_closure_free(closure); } - cdata_dealloc(&cdb->head); + cdata_dealloc(cd); } static int cdata_traverse(CDataObject *cd, visitproc visit, void *arg) @@ -1156,31 +1219,37 @@ static int cdata_traverse(CDataObject *cd, visitproc visit, void *arg) return 0; } +static PyObject *cdata_float(CDataObject *cd); /*forward*/ + static PyObject *cdata_repr(CDataObject *cd) { - char *p, *extra; - PyObject *result, *s = NULL; + char *extra; + PyObject *result, *s; if (cd->c_type->ct_flags & CT_PRIMITIVE_ANY) { - PyObject *o = convert_to_object(cd->c_data, cd->c_type); - if (o == NULL) - return NULL; - s = PyObject_Repr(o); - Py_DECREF(o); - if (s == NULL) - return NULL; - p = PyText_AsUTF8(s); + if (!(cd->c_type->ct_flags & CT_IS_LONGDOUBLE)) { + PyObject *o = convert_to_object(cd->c_data, cd->c_type); + if (o == NULL) + return NULL; + s = PyObject_Repr(o); + Py_DECREF(o); + } + else { + long double lvalue = read_raw_longdouble_data(cd->c_data); + char buffer[128]; /* big enough */ + sprintf(buffer, "%LE", lvalue); + s = PyText_FromString(buffer); + } } else { if (cd->c_data != NULL) { s = PyText_FromFormat("%p", cd->c_data); - if (s == NULL) - return NULL; - p = PyText_AsUTF8(s); } else - p = "NULL"; + s = PyText_FromString("NULL"); } + if (s == NULL) + return NULL; /* it's slightly confusing to get "<cdata 'struct foo' 0x...>" because the struct foo is not owned. Trying to make it clearer, write in this case "<cdata 'struct foo &' 0x...>". */ @@ -1188,95 +1257,12 @@ static PyObject *cdata_repr(CDataObject *cd) extra = " &"; else extra = ""; - result = PyText_FromFormat("<cdata '%s%s' %s>", - cd->c_type->ct_name, extra, p); - Py_XDECREF(s); + result = PyText_FromFormat("<cdata '%s%s' %S>", + cd->c_type->ct_name, extra, s); + Py_DECREF(s); return result; } -static PyObject *cdata_get_value(CDataObject *cd) -{ - if (cd->c_type->ct_flags & CT_PRIMITIVE_CHAR && - cd->c_type->ct_size == sizeof(char)) { - return PyBytes_FromStringAndSize(cd->c_data, 1); - } - else if (cd->c_type->ct_itemdescr != NULL && - cd->c_type->ct_itemdescr->ct_flags & CT_PRIMITIVE_CHAR && - cd->c_type->ct_itemdescr->ct_size == sizeof(char)) { - Py_ssize_t length; - - if (cd->c_type->ct_flags & CT_ARRAY) { - const char *start = cd->c_data; - const char *end; - length = get_array_length(cd); - end = (const char *)memchr(start, 0, length); - if (end != NULL) - length = end - start; - } - else { - if (cd->c_data == NULL) { - PyObject *s = cdata_repr(cd); - if (s != NULL) { - PyErr_Format(PyExc_RuntimeError, - "cannot use str() on %S", s); - Py_DECREF(s); - } - return NULL; - } - length = strlen(cd->c_data); - } - - return PyBytes_FromStringAndSize(cd->c_data, length); - } -#ifdef HAVE_WCHAR_H - else if (cd->c_type->ct_flags & CT_PRIMITIVE_CHAR && - cd->c_type->ct_size == sizeof(wchar_t)) { - return _my_PyUnicode_FromWideChar((wchar_t *)cd->c_data, 1); - } - else if (cd->c_type->ct_itemdescr != NULL && - cd->c_type->ct_itemdescr->ct_flags & CT_PRIMITIVE_CHAR && - cd->c_type->ct_itemdescr->ct_size == sizeof(wchar_t)) { - Py_ssize_t length; - const wchar_t *start = (wchar_t *)cd->c_data; - - if (cd->c_type->ct_flags & CT_ARRAY) { - const Py_ssize_t lenmax = get_array_length(cd); - length = 0; - while (length < lenmax && start[length]) - length++; - } - else { - if (cd->c_data == NULL) { - PyObject *s = cdata_repr(cd); - if (s != NULL) { - PyErr_Format(PyExc_RuntimeError, - "cannot use unicode() on %S", s); - Py_DECREF(s); - } - return NULL; - } - length = 0; - while (start[length]) - length++; - } - - return _my_PyUnicode_FromWideChar((wchar_t *)cd->c_data, length); - } -#endif - else { - Py_INCREF(cd); - return (PyObject *)cd; - } -} - -static PyObject *cdata_str(CDataObject *cd) -{ - if (cd->c_type->ct_flags & CT_IS_ENUM) - return convert_to_object(cd->c_data, cd->c_type); - else - return Py_TYPE(cd)->tp_repr((PyObject *)cd); -} - static PyObject *cdataowning_repr(CDataObject *cd) { Py_ssize_t size; @@ -1336,7 +1322,7 @@ static PyObject *cdata_int(CDataObject *cd) #endif } else if (cd->c_type->ct_flags & CT_PRIMITIVE_FLOAT) { - PyObject *o = convert_to_object(cd->c_data, cd->c_type); + PyObject *o = cdata_float(cd); #if PY_MAJOR_VERSION < 3 PyObject *r = o ? PyNumber_Int(o) : NULL; #else @@ -1366,7 +1352,14 @@ static PyObject *cdata_long(CDataObject *cd) static PyObject *cdata_float(CDataObject *cd) { if (cd->c_type->ct_flags & CT_PRIMITIVE_FLOAT) { - return convert_to_object(cd->c_data, cd->c_type); + double value; + if (!(cd->c_type->ct_flags & CT_IS_LONGDOUBLE)) { + value = read_raw_float_data(cd->c_data, cd->c_type->ct_size); + } + else { + value = (double)read_raw_longdouble_data(cd->c_data); + } + return PyFloat_FromDouble(value); } PyErr_Format(PyExc_TypeError, "float() not supported on cdata '%s'", cd->c_type->ct_name); @@ -1607,10 +1600,13 @@ cdata_getattro(CDataObject *cd, PyObject *attr) if (cf != NULL) { /* read the field 'cf' */ char *data = cd->c_data + cf->cf_offset; - if (cf->cf_bitshift >= 0) - return convert_to_object_bitfield(data, cf); - else + if (cf->cf_bitshift == BS_REGULAR) return convert_to_object(data, cf->cf_type); + else if (cf->cf_bitshift == BS_EMPTY_ARRAY) + return new_simple_cdata(data, + (CTypeDescrObject *)cf->cf_type->ct_stuff); + else + return convert_to_object_bitfield(data, cf); } } return PyObject_GenericGetAttr((PyObject *)cd, attr); @@ -1665,14 +1661,72 @@ static CTypeDescrObject *_get_ct_int(void) return ct_int; } +static PyObject * +_prepare_pointer_call_argument(CTypeDescrObject *ctptr, PyObject *init) +{ + /* 'ctptr' is here a pointer type 'ITEM *'. Accept as argument an + initializer for an array 'ITEM[]'. This includes the case of + passing a Python string to a 'char *' argument. */ + Py_ssize_t length, datasize; + CTypeDescrObject *ctitem = ctptr->ct_itemdescr; + PyObject *result; + char *data; + + /* XXX some code duplication, how to avoid it? */ + if (PyString_Check(init)) { + /* from a string: just returning the string here is fine. + We assume that the C code won't modify the 'char *' data. */ + if ((ctitem->ct_flags & CT_PRIMITIVE_CHAR) && + (ctitem->ct_size == sizeof(char))) { + Py_INCREF(init); + return init; + } + else + return Py_None; + } + else if (PyList_Check(init) || PyTuple_Check(init)) { + length = PySequence_Fast_GET_SIZE(init); + } + else if (PyUnicode_Check(init)) { + /* from a unicode, we add the null terminator */ + length = _my_PyUnicode_SizeAsWideChar(init) + 1; + } + else { + /* refuse to receive just an integer (and interpret it + as the array size) */ + return Py_None; + } + + if (ctitem->ct_size <= 0) + return Py_None; + datasize = length * ctitem->ct_size; + if ((datasize / ctitem->ct_size) != length) { + PyErr_SetString(PyExc_OverflowError, + "array size would overflow a Py_ssize_t"); + return NULL; + } + + result = PyString_FromStringAndSize(NULL, datasize); + if (result == NULL) + return NULL; + + data = PyString_AS_STRING(result); + memset(data, 0, datasize); + if (convert_array_from_object(data, ctptr, init) < 0) { + Py_DECREF(result); + return NULL; + } + return result; +} + static PyObject* cdata_call(CDataObject *cd, PyObject *args, PyObject *kwds) { char *buffer; void** buffer_array; cif_description_t *cif_descr; - Py_ssize_t i, nargs, nargs_declared; - PyObject *signature, *res, *fvarargs; + Py_ssize_t i, nargs, nargs_declared, free_me_until = 0; + PyObject *signature, *res = NULL, *fvarargs; CTypeDescrObject *fresult; char *resultdata; char *errormsg; @@ -1702,7 +1756,10 @@ cdata_call(CDataObject *cd, PyObject *args, PyObject *kwds) /* regular case: this function does not take '...' arguments */ if (nargs != nargs_declared) { errormsg = "'%s' expects %zd arguments, got %zd"; - goto bad_number_of_arguments; + bad_number_of_arguments: + PyErr_Format(PyExc_TypeError, errormsg, + cd->c_type->ct_name, nargs_declared, nargs); + goto error; } } else { @@ -1778,37 +1835,44 @@ cdata_call(CDataObject *cd, PyObject *args, PyObject *kwds) else argtype = (CTypeDescrObject *)PyTuple_GET_ITEM(fvarargs, i); - if ((argtype->ct_flags & CT_POINTER) && - (argtype->ct_itemdescr->ct_flags & CT_PRIMITIVE_CHAR)) { - if (argtype->ct_itemdescr->ct_size == sizeof(char)) { - if (PyText_Check(obj)) { - /* special case: Python string -> cdata 'char *' */ - *(char **)data = PyText_AsUTF8(obj); + if (argtype->ct_flags & CT_POINTER) { + PyObject *string; + if (!CData_Check(obj)) { + string = _prepare_pointer_call_argument(argtype, obj); + if (string != Py_None) { + if (string == NULL) + goto error; + ((char **)data)[0] = PyBytes_AS_STRING(string); + ((char **)data)[1] = (char *)string; + assert(i < nargs_declared); /* otherwise, obj is a CData */ + free_me_until = i + 1; continue; } } -#ifdef HAVE_WCHAR_H - else { - if (PyUnicode_Check(obj)) { - /* Python Unicode string -> cdata 'wchar_t *': - not supported yet */ - PyErr_SetString(PyExc_NotImplementedError, - "automatic unicode-to-'wchar_t *' conversion"); - goto error; - } - } -#endif + ((char **)data)[1] = NULL; } - if (convert_from_object(data, argtype, obj) < 0) + if (convert_from_object(data, argtype, obj) < 0) { + if (CData_Check(obj) && (argtype->ct_flags & CT_IS_PTR_TO_OWNED) && + argtype->ct_itemdescr == ((CDataObject *)obj)->c_type) { + /* special case to make the life of verifier.py easier: + if the formal argument type is 'struct foo *' but + we pass a 'struct foo', then get a pointer to it */ + PyErr_Clear(); + ((char **)data)[0] = ((CDataObject *)obj)->c_data; + continue; + } goto error; + } } resultdata = buffer + cif_descr->exchange_offset_arg[0]; + Py_BEGIN_ALLOW_THREADS restore_errno(); ffi_call(&cif_descr->cif, (void (*)(void))(cd->c_data), resultdata, buffer_array); save_errno(); + Py_END_ALLOW_THREADS if (fresult->ct_flags & (CT_PRIMITIVE_CHAR | CT_PRIMITIVE_SIGNED | CT_PRIMITIVE_UNSIGNED)) { @@ -1831,23 +1895,26 @@ cdata_call(CDataObject *cd, PyObject *args, PyObject *kwds) else { res = convert_to_object(resultdata, fresult); } - PyObject_Free(buffer); - done: + /* fall-through */ + + error: + for (i=0; i<free_me_until; i++) { + CTypeDescrObject *argtype; + argtype = (CTypeDescrObject *)PyTuple_GET_ITEM(signature, 2 + i); + if (argtype->ct_flags & CT_POINTER) { + char *data = buffer + cif_descr->exchange_offset_arg[1 + i]; + PyObject *string_or_null = (PyObject *)(((char **)data)[1]); + Py_XDECREF(string_or_null); + } + } + if (buffer) + PyObject_Free(buffer); if (fvarargs != NULL) { Py_DECREF(fvarargs); if (cif_descr != NULL) /* but only if fvarargs != NULL, if variadic */ PyObject_Free(cif_descr); } return res; - - bad_number_of_arguments: - PyErr_Format(PyExc_TypeError, errormsg, - cd->c_type->ct_name, nargs_declared, nargs); - error: - if (buffer) - PyObject_Free(buffer); - res = NULL; - goto done; } static PyObject *cdata_iter(CDataObject *); @@ -1894,15 +1961,6 @@ static PyMappingMethods CDataOwn_as_mapping = { (objobjargproc)cdata_ass_sub, /*mp_ass_subscript*/ }; -static PyMethodDef CData_methods[] = { - {NULL, NULL} /* sentinel */ -}; - -static PyGetSetDef CData_getset[] = { - {"value", (getter)cdata_get_value, NULL, NULL}, - {0} -}; - static PyTypeObject CData_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_cffi_backend.CData", @@ -1919,7 +1977,7 @@ static PyTypeObject CData_Type = { &CData_as_mapping, /* tp_as_mapping */ (hashfunc)cdata_hash, /* tp_hash */ (ternaryfunc)cdata_call, /* tp_call */ - (reprfunc)cdata_str, /* tp_str */ + 0, /* tp_str */ (getattrofunc)cdata_getattro, /* tp_getattro */ (setattrofunc)cdata_setattro, /* tp_setattro */ 0, /* tp_as_buffer */ @@ -1928,18 +1986,15 @@ static PyTypeObject CData_Type = { (traverseproc)cdata_traverse, /* tp_traverse */ 0, /* tp_clear */ cdata_richcompare, /* tp_richcompare */ - 0, /* tp_weaklistoffset */ + offsetof(CDataObject, c_weakreflist), /* tp_weaklistoffset */ (getiterfunc)cdata_iter, /* tp_iter */ 0, /* tp_iternext */ - CData_methods, /* tp_methods */ - 0, /* tp_members */ - CData_getset, /* tp_getset */ }; static PyTypeObject CDataOwning_Type = { PyVarObject_HEAD_INIT(NULL, 0) "_cffi_backend.CDataOwn", - sizeof(CDataObject_own_base), + sizeof(CDataObject), 0, (destructor)cdataowning_dealloc, /* tp_dealloc */ 0, /* tp_print */ @@ -1961,7 +2016,7 @@ static PyTypeObject CDataOwning_Type = { 0, /* tp_traverse */ 0, /* tp_clear */ 0, /* tp_richcompare */ - offsetof(CDataObject_own_base, weakreflist),/* tp_weaklistoffset */ + 0, /* tp_weaklistoffset */ 0, /* tp_iter */ 0, /* tp_iternext */ 0, /* tp_methods */ @@ -2053,24 +2108,24 @@ cdata_iter(CDataObject *cd) /************************************************************/ -static CDataObject_own_base *allocate_owning_object(Py_ssize_t size, - CTypeDescrObject *ct) +static CDataObject *allocate_owning_object(Py_ssize_t size, + CTypeDescrObject *ct) { - CDataObject_own_base *cdb; - cdb = (CDataObject_own_base *)PyObject_Malloc(size); - if (PyObject_Init((PyObject *)cdb, &CDataOwning_Type) == NULL) + CDataObject *cd; + cd = (CDataObject *)PyObject_Malloc(size); + if (PyObject_Init((PyObject *)cd, &CDataOwning_Type) == NULL) return NULL; Py_INCREF(ct); - cdb->head.c_type = ct; - cdb->weakreflist = NULL; - return cdb; + cd->c_type = ct; + cd->c_weakreflist = NULL; + return cd; } static PyObject * convert_struct_to_owning_object(char *data, CTypeDescrObject *ct) { - CDataObject_own_base *cdb; + CDataObject *cd; Py_ssize_t dataoffset = offsetof(CDataObject_own_nolength, alignment); Py_ssize_t datasize = ct->ct_size; @@ -2079,20 +2134,19 @@ convert_struct_to_owning_object(char *data, CTypeDescrObject *ct) "return type is not a struct or is opaque"); return NULL; } - cdb = allocate_owning_object(dataoffset + datasize, ct); - if (cdb == NULL) + cd = allocate_owning_object(dataoffset + datasize, ct); + if (cd == NULL) return NULL; - cdb->head.c_data = ((char *)cdb) + dataoffset; + cd->c_data = ((char *)cd) + dataoffset; - memcpy(cdb->head.c_data, data, datasize); - return (PyObject *)cdb; + memcpy(cd->c_data, data, datasize); + return (PyObject *)cd; } static PyObject *b_newp(PyObject *self, PyObject *args) { CTypeDescrObject *ct, *ctitem; CDataObject *cd; - CDataObject_own_base *cdb; PyObject *init = Py_None; Py_ssize_t dataoffset, datasize, explicitlength; if (!PyArg_ParseTuple(args, "O!|O:newp", &CTypeDescr_Type, &ct, &init)) @@ -2160,33 +2214,31 @@ static PyObject *b_newp(PyObject *self, PyObject *args) we build two objects instead of one, with the memory-owning one being really the struct (or union) and the returned one having a strong reference to it */ - CDataObject_own_base *cdp; + CDataObject *cds; - cdb = allocate_owning_object(dataoffset + datasize, ct->ct_itemdescr); - if (cdb == NULL) + cds = allocate_owning_object(dataoffset + datasize, ct->ct_itemdescr); + if (cds == NULL) return NULL; - cdp = allocate_owning_object(sizeof(CDataObject_own_structptr), ct); - if (cdp == NULL) { - Py_DECREF(cdb); + cd = allocate_owning_object(sizeof(CDataObject_own_structptr), ct); + if (cd == NULL) { + Py_DECREF(cds); return NULL; } - /* store the only reference to cdb into cdp */ - ((CDataObject_own_structptr *)cdp)->structobj = (PyObject *)cdb; + /* store the only reference to cds into cd */ + ((CDataObject_own_structptr *)cd)->structobj = (PyObject *)cds; assert(explicitlength < 0); - cdb->head.c_data = cdp->head.c_data = ((char *)cdb) + dataoffset; - cd = &cdp->head; + cds->c_data = cd->c_data = ((char *)cds) + dataoffset; } else { - cdb = allocate_owning_object(dataoffset + datasize, ct); - if (cdb == NULL) + cd = allocate_owning_object(dataoffset + datasize, ct); + if (cd == NULL) return NULL; - cdb->head.c_data = ((char *)cdb) + dataoffset; + cd->c_data = ((char *)cd) + dataoffset; if (explicitlength >= 0) - ((CDataObject_own_length*)cdb)->length = explicitlength; - cd = &cdb->head; + ((CDataObject_own_length*)cd)->length = explicitlength; } memset(cd->c_data, 0, datasize); @@ -2209,6 +2261,7 @@ static CDataObject *_new_casted_primitive(CTypeDescrObject *ct) Py_INCREF(ct); cd->c_type = ct; cd->c_data = ((char*)cd) + dataoffset; + cd->c_weakreflist = NULL; return cd; } @@ -2337,6 +2390,16 @@ static PyObject *b_cast(PyObject *self, PyObject *args) } value = (unsigned char)PyBytes_AS_STRING(io)[0]; } + else if ((ct->ct_flags & CT_IS_LONGDOUBLE) && + CData_Check(io) && + (((CDataObject *)io)->c_type->ct_flags & CT_IS_LONGDOUBLE)) { + long double lvalue; + lvalue = read_raw_longdouble_data(((CDataObject *)io)->c_data); + cd = _new_casted_primitive(ct); + if (cd != NULL) + write_raw_longdouble_data(cd->c_data, lvalue); + return (PyObject *)cd; + } else { value = PyFloat_AsDouble(io); } @@ -2345,8 +2408,12 @@ static PyObject *b_cast(PyObject *self, PyObject *args) return NULL; cd = _new_casted_primitive(ct); - if (cd != NULL) - write_raw_float_data(cd->c_data, value, ct->ct_size); + if (cd != NULL) { + if (!(ct->ct_flags & CT_IS_LONGDOUBLE)) + write_raw_float_data(cd->c_data, value, ct->ct_size); + else + write_raw_longdouble_data(cd->c_data, (long double)value); + } return (PyObject *)cd; } else { @@ -2500,19 +2567,26 @@ static PyTypeObject dl_type = { static PyObject *b_load_library(PyObject *self, PyObject *args) { - char *filename; + char *filename_or_null, *printable_filename; void *handle; DynLibObject *dlobj; int is_global = 0; - if (!PyArg_ParseTuple(args, "et|i:load_library", - Py_FileSystemDefaultEncoding, &filename, + if (PyTuple_GET_SIZE(args) == 0 || PyTuple_GET_ITEM(args, 0) == Py_None) { + filename_or_null = NULL; + is_global = 1; + } + else if (!PyArg_ParseTuple(args, "et|i:load_library", + Py_FileSystemDefaultEncoding, &filename_or_null, &is_global)) return NULL; - handle = dlopen(filename, RTLD_LAZY | (is_global?RTLD_GLOBAL:RTLD_LOCAL)); + printable_filename = filename_or_null ? filename_or_null : "<None>"; + handle = dlopen(filename_or_null, + RTLD_LAZY | (is_global?RTLD_GLOBAL:RTLD_LOCAL)); if (handle == NULL) { - PyErr_Format(PyExc_OSError, "cannot load library: %s", filename); + PyErr_Format(PyExc_OSError, "cannot load library: %s", + printable_filename); return NULL; } @@ -2522,7 +2596,7 @@ static PyObject *b_load_library(PyObject *self, PyObject *args) return NULL; } dlobj->dl_handle = handle; - dlobj->dl_name = strdup(filename); + dlobj->dl_name = strdup(printable_filename); return (PyObject *)dlobj; } @@ -2588,7 +2662,8 @@ static PyObject *b_new_primitive_type(PyObject *self, PyObject *args) EPTYPE(ul, unsigned long, CT_PRIMITIVE_UNSIGNED ) \ EPTYPE(ull, unsigned long long, CT_PRIMITIVE_UNSIGNED ) \ EPTYPE(f, float, CT_PRIMITIVE_FLOAT ) \ - EPTYPE(d, double, CT_PRIMITIVE_FLOAT ) + EPTYPE(d, double, CT_PRIMITIVE_FLOAT ) \ + EPTYPE(ld, long double, CT_PRIMITIVE_FLOAT | CT_IS_LONGDOUBLE ) #ifdef HAVE_WCHAR_H # define ENUM_PRIMITIVE_TYPES_WCHAR \ EPTYPE(wc, wchar_t, CT_PRIMITIVE_CHAR ) @@ -2654,6 +2729,8 @@ static PyObject *b_new_primitive_type(PyObject *self, PyObject *args) ffitype = &ffi_type_float; else if (strcmp(ptypes->name, "double") == 0) ffitype = &ffi_type_double; + else if (strcmp(ptypes->name, "long double") == 0) + ffitype = &ffi_type_longdouble; else goto bad_ffi_type; } @@ -2713,6 +2790,7 @@ static PyObject *b_new_pointer_type(PyObject *self, PyObject *args) return NULL; td->ct_size = sizeof(void *); + td->ct_length = -1; td->ct_flags = CT_POINTER; if (ctitem->ct_flags & (CT_STRUCT|CT_UNION)) td->ct_flags |= CT_IS_PTR_TO_OWNED; @@ -2908,7 +2986,10 @@ static PyObject *b_complete_struct_or_union(PyObject *self, PyObject *args) if (fbitsize < 0 || (fbitsize == 8 * ftype->ct_size && !(ftype->ct_flags & CT_PRIMITIVE_CHAR))) { fbitsize = -1; - bitshift = -1; + if (ftype->ct_flags & CT_ARRAY && ftype->ct_length == 0) + bitshift = BS_EMPTY_ARRAY; + else + bitshift = BS_REGULAR; prev_bit_position = 0; } else { @@ -3225,6 +3306,15 @@ static int fb_build(struct funcbuilder_s *fb, PyObject *fargs, exchange_offset = ALIGN_ARG(exchange_offset); cif_descr->exchange_offset_arg[1 + i] = exchange_offset; exchange_offset += atype->size; + /* if 'farg' is a pointer type 'ITEM *', then we might receive + as argument to the function call what is an initializer + for an array 'ITEM[]'. This includes the case of passing a + Python string to a 'char *' argument. In this case, we + convert the initializer to a cdata 'ITEM[]' that gets + temporarily stored here: */ + if (farg->ct_flags & CT_POINTER) { + exchange_offset += sizeof(PyObject *); + } } } @@ -3505,6 +3595,9 @@ static void invoke_callback(ffi_cif *cif, void *result, void **args, { save_errno(); { +#ifdef WITH_THREAD + PyGILState_STATE state = PyGILState_Ensure(); +#endif PyObject *cb_args = (PyObject *)userdata; CTypeDescrObject *ct = (CTypeDescrObject *)PyTuple_GET_ITEM(cb_args, 0); PyObject *signature = ct->ct_stuff; @@ -3539,6 +3632,9 @@ static void invoke_callback(ffi_cif *cif, void *result, void **args, Py_XDECREF(py_args); Py_XDECREF(py_res); Py_DECREF(cb_args); +#ifdef WITH_THREAD + PyGILState_Release(state); +#endif restore_errno(); return; @@ -3558,7 +3654,7 @@ static void invoke_callback(ffi_cif *cif, void *result, void **args, static PyObject *b_callback(PyObject *self, PyObject *args) { CTypeDescrObject *ct, *ctresult; - CDataObject_own_base *cdb; + CDataObject *cd; PyObject *ob, *error_ob = Py_None; PyObject *py_rawerr, *infotuple = NULL; cif_description_t *cif_descr; @@ -3602,13 +3698,13 @@ static PyObject *b_callback(PyObject *self, PyObject *args) closure = cffi_closure_alloc(); - cdb = PyObject_New(CDataObject_own_base, &CDataOwning_Type); - if (cdb == NULL) + cd = PyObject_New(CDataObject, &CDataOwning_Type); + if (cd == NULL) goto error; Py_INCREF(ct); - cdb->head.c_type = ct; - cdb->head.c_data = (char *)closure; - cdb->weakreflist = NULL; + cd->c_type = ct; + cd->c_data = (char *)closure; + cd->c_weakreflist = NULL; cif_descr = (cif_description_t *)ct->ct_extra; if (cif_descr == NULL) { @@ -3623,14 +3719,14 @@ static PyObject *b_callback(PyObject *self, PyObject *args) goto error; } assert(closure->user_data == infotuple); - return (PyObject *)cdb; + return (PyObject *)cd; error: closure->user_data = NULL; - if (cdb == NULL) + if (cd == NULL) cffi_closure_free(closure); else - Py_DECREF(cdb); + Py_DECREF(cd); Py_XDECREF(infotuple); return NULL; } @@ -3812,6 +3908,84 @@ static PyObject *b_getcname(PyObject *self, PyObject *args) return PyText_FromStringAndSize(s, namelen + replacelen); } +static PyObject *b_string(PyObject *self, PyObject *args) +{ + CDataObject *cd; + Py_ssize_t maxlen = -1; + if (!PyArg_ParseTuple(args, "O!|n:string", + &CData_Type, &cd, &maxlen)) + return NULL; + + if (cd->c_type->ct_itemdescr != NULL && + cd->c_type->ct_itemdescr->ct_flags & (CT_PRIMITIVE_CHAR | + CT_PRIMITIVE_SIGNED | + CT_PRIMITIVE_UNSIGNED)) { + Py_ssize_t length = maxlen; + if (cd->c_data == NULL) { + PyObject *s = cdata_repr(cd); + if (s != NULL) { + PyErr_Format(PyExc_RuntimeError, + "cannot use string() on %s", + PyString_AS_STRING(s)); + Py_DECREF(s); + } + return NULL; + } + if (length < 0 && cd->c_type->ct_flags & CT_ARRAY) { + length = get_array_length(cd); + } + if (cd->c_type->ct_itemdescr->ct_size == sizeof(char)) { + const char *start = cd->c_data; + if (length < 0) + length = strlen(start); + else { + const char *end; + end = (const char *)memchr(start, 0, length); + if (end != NULL) + length = end - start; + } + return PyString_FromStringAndSize(start, length); + } +#ifdef HAVE_WCHAR_H + else if (cd->c_type->ct_itemdescr->ct_flags & CT_PRIMITIVE_CHAR) { + const wchar_t *start = (wchar_t *)cd->c_data; + assert(cd->c_type->ct_itemdescr->ct_size == sizeof(wchar_t)); + if (length < 0) { + length = 0; + while (start[length]) + length++; + } + else { + maxlen = length; + length = 0; + while (length < maxlen && start[length]) + length++; + } + return _my_PyUnicode_FromWideChar(start, length); + } +#endif + } + else if (cd->c_type->ct_flags & CT_IS_ENUM) { + return convert_to_object(cd->c_data, cd->c_type); + } + else if (cd->c_type->ct_flags & (CT_PRIMITIVE_CHAR | + CT_PRIMITIVE_SIGNED | + CT_PRIMITIVE_UNSIGNED)) { + if (cd->c_type->ct_size == sizeof(char)) { + return PyString_FromStringAndSize(cd->c_data, 1); + } +#ifdef HAVE_WCHAR_H + else if (cd->c_type->ct_flags & CT_PRIMITIVE_CHAR) { + assert(cd->c_type->ct_size == sizeof(wchar_t)); + return _my_PyUnicode_FromWideChar((wchar_t *)cd->c_data, 1); + } +#endif + } + PyErr_Format(PyExc_TypeError, "string(): unexpected cdata '%s' argument", + cd->c_type->ct_name); + return NULL; +} + static PyObject *b_buffer(PyObject *self, PyObject *args) { CDataObject *cd; @@ -3996,6 +4170,24 @@ static struct _testfunc17_s _testfunc17(int n) return result; } +static int _testfunc18(struct _testfunc17_s *ptr) +{ + return ptr->a1 + (int)ptr->a2; +} + +static long double _testfunc19(long double x) +{ + int i; + for (i=0; i<28; i++) + x += x; + return x; +} + +static short _testfunc20(struct _testfunc7_s *ptr) +{ + return ptr->a1 + ptr->a2; +} + static PyObject *b__testfunc(PyObject *self, PyObject *args) { /* for testing only */ @@ -4022,6 +4214,9 @@ static PyObject *b__testfunc(PyObject *self, PyObject *args) case 15: f = &_testfunc15; break; case 16: f = &_testfunc16; break; case 17: f = &_testfunc17; break; + case 18: f = &_testfunc18; break; + case 19: f = &_testfunc19; break; + case 20: f = &_testfunc20; break; default: PyErr_SetNone(PyExc_ValueError); return NULL; @@ -4050,6 +4245,7 @@ static PyMethodDef FFIBackendMethods[] = { {"typeof", b_typeof, METH_O}, {"offsetof", b_offsetof, METH_VARARGS}, {"getcname", b_getcname, METH_VARARGS}, + {"string", b_string, METH_VARARGS}, {"buffer", b_buffer, METH_VARARGS}, {"get_errno", b_get_errno, METH_NOARGS}, {"set_errno", b_set_errno, METH_VARARGS}, @@ -4209,12 +4405,13 @@ static struct PyModuleDef FFIBackendModuleDef = { }; #define INITERROR return NULL -PyObject * +PyMODINIT_FUNC PyInit__cffi_backend(void) #else #define INITERROR return -void init_cffi_backend(void) +PyMODINIT_FUNC +init_cffi_backend(void) #endif { PyObject *m, *v; @@ -4253,7 +4450,7 @@ void init_cffi_backend(void) if (v == NULL || PyModule_AddObject(m, "_C_API", v) < 0) INITERROR; - v = PyText_FromString("0.2.1"); + v = PyText_FromString("0.3"); if (v == NULL || PyModule_AddObject(m, "__version__", v) < 0) INITERROR; diff --git a/c/misc_win32.h b/c/misc_win32.h index bcba9d2..718514d 100644 --- a/c/misc_win32.h +++ b/c/misc_win32.h @@ -21,7 +21,8 @@ static struct cffi_errno_s *_geterrno_object(void) LPVOID p = TlsGetValue(cffi_tls_index); if (p == NULL) { - p = PyMem_Malloc(sizeof(struct cffi_errno_s)); + /* XXX this malloc() leaks */ + p = malloc(sizeof(struct cffi_errno_s)); if (p == NULL) return NULL; memset(p, 0, sizeof(struct cffi_errno_s)); diff --git a/c/test_c.py b/c/test_c.py index a5e9ad7..2d2df6a 100644 --- a/c/test_c.py +++ b/c/test_c.py @@ -26,7 +26,10 @@ def size_of_ptr(): def find_and_load_library(name, is_global=0): import ctypes.util - path = ctypes.util.find_library(name) + if name is None: + path = None + else: + path = ctypes.util.find_library(name) return load_library(path, is_global) def test_load_library(): @@ -92,6 +95,7 @@ def test_integer_types(): def test_no_float_on_int_types(): p = new_primitive_type('long') py.test.raises(TypeError, float, cast(p, 42)) + py.test.raises(TypeError, complex, cast(p, 42)) def test_float_types(): INF = 1E200 * 1E200 @@ -122,6 +126,39 @@ def test_float_types(): assert float(cast(p, True)) == 1.0 py.test.raises(TypeError, cast, p, None) +def test_complex_types(): + py.test.skip("later") + INF = 1E200 * 1E200 + for name in ["float", "double"]: + p = new_primitive_type("_Complex " + name) + assert bool(cast(p, 0)) + assert bool(cast(p, INF)) + assert bool(cast(p, -INF)) + assert bool(cast(p, 0j)) + assert bool(cast(p, INF*1j)) + assert bool(cast(p, -INF*1j)) + py.test.raises(TypeError, int, cast(p, -150)) + py.test.raises(TypeError, long, cast(p, -150)) + py.test.raises(TypeError, float, cast(p, -150)) + assert complex(cast(p, 1.25)) == 1.25 + assert complex(cast(p, 1.25j)) == 1.25j + assert float(cast(p, INF*1j)) == INF*1j + assert float(cast(p, -INF)) == -INF + if name == "float": + assert complex(cast(p, 1.1j)) != 1.1j # rounding error + assert complex(cast(p, 1E200+3j)) == INF+3j # limited range + assert complex(cast(p, 3+1E200j)) == 3+INF*1j # limited range + + assert cast(p, -1.1j) != cast(p, -1.1j) + assert repr(complex(cast(p, -0.0)).real) == '-0.0' + assert repr(complex(cast(p, -0j))) == '-0j' + assert complex(cast(p, '\x09')) == 9.0 + assert complex(cast(p, True)) == 1.0 + py.test.raises(TypeError, cast, p, None) + # + py.test.raises(cast, new_primitive_type(name), 1+2j) + py.test.raises(cast, new_primitive_type("int"), 1+2j) + def test_character_type(): p = new_primitive_type("char") assert bool(cast(p, '\x00')) @@ -130,7 +167,7 @@ def test_character_type(): assert long(cast(p, 'A')) == 65L assert type(int(cast(p, 'A'))) is int assert type(long(cast(p, 'A'))) is long - assert str(cast(p, 'A')) == 'A' + assert str(cast(p, 'A')) == repr(cast(p, 'A')) assert repr(cast(p, 'A')) == "<cdata 'char' 'A'>" assert repr(cast(p, 255)) == r"<cdata 'char' '\xff'>" assert repr(cast(p, 0)) == r"<cdata 'char' '\x00'>" @@ -235,7 +272,9 @@ def test_reading_pointer_to_char(): assert p[0] == 'A' py.test.raises(TypeError, newp, BPtr, 65) py.test.raises(TypeError, newp, BPtr, "foo") - assert str(cast(BChar, 'A')) == 'A' + c = cast(BChar, 'A') + assert str(c) == repr(c) + assert int(c) == ord('A') py.test.raises(TypeError, cast, BChar, 'foo') def test_reading_pointer_to_pointer(): @@ -261,6 +300,16 @@ def test_reading_pointer_to_pointer(): p = newp(BIntPtrPtr, q) assert p[0][0] == 43 +def test_load_standard_library(): + if sys.platform == "win32": + py.test.raises(OSError, find_and_load_library, None) + return + 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, + BVoidP, 'xxx_this_function_does_not_exist') + def test_hash_differences(): BChar = new_primitive_type("char") BInt = new_primitive_type("int") @@ -295,6 +344,9 @@ def test_invalid_indexing(): py.test.raises(TypeError, "p[0]") def test_default_str(): + BChar = new_primitive_type("char") + x = cast(BChar, 42) + assert str(x) == repr(x) BInt = new_primitive_type("int") x = cast(BInt, 42) assert str(x) == repr(x) @@ -320,7 +372,7 @@ def test_cast_from_cdataint(): y = cast(BInt, x) assert int(y) == 42 y = cast(new_primitive_type("char"), x) - assert str(y) == chr(42) + assert int(y) == 42 y = cast(new_primitive_type("float"), x) assert float(y) == 42.0 # @@ -461,7 +513,7 @@ def test_cast_primitive_from_cdata(): # p = new_primitive_type("char") n = cast(p, cast(p, "A")) - assert str(n) == "A" + assert int(n) == ord("A") def test_new_primitive_from_cdata(): p = new_primitive_type("int") @@ -763,12 +815,22 @@ def test_call_function_6(): BFunc6bis = new_function_type((BIntArray,), BIntPtr, False) f = cast(BFunc6bis, _testfunc(6)) # - py.test.raises(TypeError, f, [142]) + res = f([142]) + assert typeof(res) is BIntPtr + assert res[0] == 142 - 1000 + # + res = f((143,)) + assert typeof(res) is BIntPtr + assert res[0] == 143 - 1000 # x = newp(BIntArray, [242]) res = f(x) assert typeof(res) is BIntPtr assert res[0] == 242 - 1000 + # + py.test.raises(TypeError, f, 123456) + py.test.raises(TypeError, f, "foo") + py.test.raises(TypeError, f, u"bar") def test_call_function_7(): BChar = new_primitive_type("char") @@ -786,6 +848,22 @@ def test_call_function_7(): res = f(x[0]) assert res == -4042 + ord('A') +def test_call_function_20(): + BChar = new_primitive_type("char") + BShort = new_primitive_type("short") + BStruct = new_struct_type("foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BChar, -1), + ('a2', BShort, -1)]) + BFunc18 = new_function_type((BStructPtr,), BShort, False) + f = cast(BFunc18, _testfunc(20)) + x = newp(BStructPtr, {'a1': 'A', 'a2': -4042}) + # test the exception that allows us to pass a 'struct foo' where the + # function really expects a 'struct foo *'. + res = f(x[0]) + assert res == -4042 + ord('A') + assert res == f(x) + def test_call_function_9(): BInt = new_primitive_type("int") BFunc9 = new_function_type((BInt,), BInt, True) # vararg @@ -949,14 +1027,14 @@ def test_cast_to_enum(): BEnum = new_enum_type("foo", ('def', 'c', 'ab'), (0, 1, -20)) e = cast(BEnum, 0) assert repr(e) == "<cdata 'enum foo' 'def'>" - assert str(e) == 'def' - assert str(cast(BEnum, -20)) == 'ab' - assert str(cast(BEnum, 'c')) == 'c' + assert string(e) == 'def' + assert string(cast(BEnum, -20)) == 'ab' + assert string(cast(BEnum, 'c')) == 'c' assert int(cast(BEnum, 'c')) == 1 assert int(cast(BEnum, 'def')) == 0 assert int(cast(BEnum, -242 + 2**128)) == -242 - assert str(cast(BEnum, -242 + 2**128)) == '#-242' - assert str(cast(BEnum, '#-20')) == 'ab' + assert string(cast(BEnum, -242 + 2**128)) == '#-242' + assert string(cast(BEnum, '#-20')) == 'ab' assert repr(cast(BEnum, '#-20')) == "<cdata 'enum foo' 'ab'>" assert repr(cast(BEnum, '#-21')) == "<cdata 'enum foo' '#-21'>" @@ -1082,8 +1160,8 @@ def test_weakref(): BPtr = new_pointer_type(BInt) weakref.ref(BInt) weakref.ref(newp(BPtr, 42)) - py.test.raises(TypeError, weakref.ref, cast(BPtr, 42)) - py.test.raises(TypeError, weakref.ref, cast(BInt, 42)) + weakref.ref(cast(BPtr, 42)) + weakref.ref(cast(BInt, 42)) def test_no_inheritance(): BInt = new_primitive_type("int") @@ -1106,11 +1184,12 @@ def test_assign_string(): BArray1 = new_array_type(new_pointer_type(BChar), 5) BArray2 = new_array_type(new_pointer_type(BArray1), 5) a = newp(BArray2, ["abc", "de", "ghij"]) - assert str(a[2]) == "ghij" + assert string(a[1]) == "de" + assert string(a[2]) == "ghij" a[2] = "." - assert str(a[2]) == "." + assert string(a[2]) == "." a[2] = "12345" - assert str(a[2]) == "12345" + assert string(a[2]) == "12345" e = py.test.raises(IndexError, 'a[2] = "123456"') assert 'char[5]' in str(e.value) assert 'got 6 characters' in str(e.value) @@ -1203,16 +1282,56 @@ def test_newp_copying(): p2 = newp(new_pointer_type(BFunc), p1) assert p2[0] == p1 -def test_str(): +def test_string(): BChar = new_primitive_type("char") + assert string(cast(BChar, 42)) == '*' + assert string(cast(BChar, 0)) == '\x00' BCharP = new_pointer_type(BChar) BArray = new_array_type(BCharP, 10) a = newp(BArray, "hello") assert len(a) == 10 - assert str(a) == "hello" + assert string(a) == "hello" p = a + 2 - assert str(p) == "llo" - py.test.raises(RuntimeError, str, cast(BCharP, 0)) + assert string(p) == "llo" + assert string(newp(new_array_type(BCharP, 4), "abcd")) == "abcd" + py.test.raises(RuntimeError, string, cast(BCharP, 0)) + assert string(a, 4) == "hell" + assert string(a, 5) == "hello" + assert string(a, 6) == "hello" + +def test_string_byte(): + BByte = new_primitive_type("signed char") + assert string(cast(BByte, 42)) == '*' + assert string(cast(BByte, 0)) == '\x00' + BArray = new_array_type(new_pointer_type(BByte), None) + a = newp(BArray, [65, 66, 67]) + assert type(string(a)) is str and string(a) == 'ABC' + # + BByte = new_primitive_type("unsigned char") + assert string(cast(BByte, 42)) == '*' + assert string(cast(BByte, 0)) == '\x00' + BArray = new_array_type(new_pointer_type(BByte), None) + a = newp(BArray, [65, 66, 67]) + assert type(string(a)) is str and string(a) == 'ABC' + if 'PY_DOT_PY' not in globals(): + assert string(a, 8).startswith('ABC') # may contain additional garbage + +def test_string_wchar(): + BWChar = new_primitive_type("wchar_t") + assert string(cast(BWChar, 42)) == u'*' + assert string(cast(BWChar, 0x4253)) == u'\u4253' + assert string(cast(BWChar, 0)) == u'\x00' + BArray = new_array_type(new_pointer_type(BWChar), None) + a = newp(BArray, [u'A', u'B', u'C']) + assert type(string(a)) is unicode and string(a) == u'ABC' + if 'PY_DOT_PY' not in globals(): + assert string(a, 8).startswith(u'ABC') # may contain additional garbage + +def test_string_typeerror(): + BShort = new_primitive_type("short") + BArray = new_array_type(new_pointer_type(BShort), None) + a = newp(BArray, [65, 66, 67]) + py.test.raises(TypeError, string, a) def test_bug_convert_to_ptr(): BChar = new_primitive_type("char") @@ -1229,12 +1348,12 @@ def test_set_struct_fields(): BStructPtr = new_pointer_type(BStruct) complete_struct_or_union(BStruct, [('a1', BCharArray10, -1)]) p = newp(BStructPtr, None) - assert str(p.a1) == '' + assert string(p.a1) == '' p.a1 = 'foo' - assert str(p.a1) == 'foo' + assert string(p.a1) == 'foo' assert list(p.a1) == ['f', 'o', 'o'] + ['\x00'] * 7 p.a1 = ['x', 'y'] - assert str(p.a1) == 'xyo' + assert string(p.a1) == 'xyo' def test_invalid_function_result_types(): BFunc = new_function_type((), new_void_type()) @@ -1332,6 +1451,14 @@ def test_struct_return_in_func(): assert repr(s) == "<cdata 'struct test17' owning 8 bytes>" assert s.a1 == 40 assert s.a2 == 40.0 * 40.0 + # + BStruct17Ptr = new_pointer_type(BStruct17) + BFunc18 = new_function_type((BStruct17Ptr,), BInt) + f = cast(BFunc18, _testfunc(18)) + x = f([[40, 2.5]]) + assert x == 42 + x = f([{'a2': 43.1}]) + assert x == 43 def test_cast_with_functionptr(): BFunc = new_function_type((), new_void_type()) @@ -1356,7 +1483,7 @@ def test_wchar(): if wchar4: x = cast(BWChar, 0x12345) assert str(x) == "<cdata 'wchar_t' u'\U00012345'>" - assert unicode(x) == u'\U00012345' + assert int(x) == 0x12345 else: assert not pyuni4 # @@ -1387,20 +1514,20 @@ def test_wchar(): BWCharArray = new_array_type(BWCharP, None) a = newp(BWCharArray, u'hello \u1234 world') assert len(a) == 14 # including the final null - assert unicode(a) == u'hello \u1234 world' + assert string(a) == u'hello \u1234 world' a[13] = u'!' - assert unicode(a) == u'hello \u1234 world!' + assert string(a) == u'hello \u1234 world!' assert str(a) == repr(a) assert a[6] == u'\u1234' a[6] = u'-' - assert unicode(a) == 'hello - world!' + assert string(a) == u'hello - world!' assert str(a) == repr(a) # if wchar4: u = u'\U00012345\U00012346\U00012347' a = newp(BWCharArray, u) assert len(a) == 4 - assert unicode(a) == u + assert string(a) == u assert len(list(a)) == 4 expected = [u'\U00012345', u'\U00012346', u'\U00012347', unichr(0)] assert list(a) == expected @@ -1411,17 +1538,17 @@ def test_wchar(): w = cast(BWChar, 'a') assert repr(w) == "<cdata 'wchar_t' u'a'>" assert str(w) == repr(w) - assert unicode(w) == u'a' + assert string(w) == u'a' assert int(w) == ord('a') w = cast(BWChar, 0x1234) assert repr(w) == "<cdata 'wchar_t' u'\u1234'>" assert str(w) == repr(w) - assert unicode(w) == u'\u1234' + assert string(w) == u'\u1234' assert int(w) == 0x1234 w = cast(BWChar, u'\u8234') assert repr(w) == "<cdata 'wchar_t' u'\u8234'>" assert str(w) == repr(w) - assert unicode(w) == u'\u8234' + assert string(w) == u'\u8234' assert int(w) == 0x8234 w = cast(BInt, u'\u1234') assert repr(w) == "<cdata 'int' 4660>" @@ -1429,7 +1556,7 @@ def test_wchar(): w = cast(BWChar, u'\U00012345') assert repr(w) == "<cdata 'wchar_t' u'\U00012345'>" assert str(w) == repr(w) - assert unicode(w) == u'\U00012345' + assert string(w) == u'\U00012345' assert int(w) == 0x12345 w = cast(BInt, u'\U00012345') assert repr(w) == "<cdata 'int' 74565>" @@ -1439,34 +1566,33 @@ def test_wchar(): # a = newp(BWCharArray, u'hello - world') p = cast(BWCharP, a) - assert unicode(p) == u'hello - world' + assert string(p) == u'hello - world' p[6] = u'\u2345' - assert unicode(p) == u'hello \u2345 world' + assert string(p) == u'hello \u2345 world' # s = newp(BStructPtr, [u'\u1234', p]) assert s.a1 == u'\u1234' assert s.a2 == p assert str(s.a2) == repr(s.a2) - assert unicode(s.a2) == u'hello \u2345 world' + assert string(s.a2) == u'hello \u2345 world' # q = cast(BWCharP, 0) assert str(q) == repr(q) - py.test.raises(RuntimeError, unicode, q) + py.test.raises(RuntimeError, string, q) # def cb(p): assert repr(p).startswith("<cdata 'wchar_t *' 0x") - return len(unicode(p)) + return len(string(p)) BFunc = new_function_type((BWCharP,), BInt, False) f = callback(BFunc, cb, -42) - #assert f(u'a\u1234b') == 3 -- not implemented - py.test.raises(NotImplementedError, f, u'a\u1234b') + assert f(u'a\u1234b') == 3 # if wchar4 and not pyuni4: # try out-of-range wchar_t values x = cast(BWChar, 1114112) - py.test.raises(ValueError, unicode, x) + py.test.raises(ValueError, string, x) x = cast(BWChar, -1) - py.test.raises(ValueError, unicode, x) + py.test.raises(ValueError, string, x) def test_keepalive_struct(): # exception to the no-keepalive rule: p=newp(BStructPtr) returns a @@ -1578,12 +1704,12 @@ def test_buffer(): assert c[2] == '-' assert str(buf) == "hi-there\x00" buf[:2] = 'HI' - assert str(c) == 'HI-there' + assert string(c) == 'HI-there' assert buf[:4:2] == 'H-' if '__pypy__' not in sys.builtin_module_names: # XXX pypy doesn't support the following assignment so far buf[:4:2] = 'XY' - assert str(c) == 'XIYthere' + assert string(c) == 'XIYthere' def test_getcname(): BUChar = new_primitive_type("unsigned char") @@ -1689,3 +1815,89 @@ def test_variable_length_struct(): assert x.a1 == 0 assert len(x.a2) == 2 assert list(x.a2) == [4, 5] + +def test_autocast_int(): + BInt = new_primitive_type("int") + BIntPtr = new_pointer_type(BInt) + BLongLong = new_primitive_type("long long") + BULongLong = new_primitive_type("unsigned long long") + BULongLongPtr = new_pointer_type(BULongLong) + x = newp(BIntPtr, cast(BInt, 42)) + assert x[0] == 42 + x = newp(BIntPtr, cast(BLongLong, 42)) + assert x[0] == 42 + x = newp(BIntPtr, cast(BULongLong, 42)) + assert x[0] == 42 + x = newp(BULongLongPtr, cast(BInt, 42)) + assert x[0] == 42 + py.test.raises(OverflowError, newp, BULongLongPtr, cast(BInt, -42)) + x = cast(BInt, cast(BInt, 42)) + assert int(x) == 42 + x = cast(BInt, cast(BLongLong, 42)) + assert int(x) == 42 + x = cast(BInt, cast(BULongLong, 42)) + assert int(x) == 42 + x = cast(BULongLong, cast(BInt, 42)) + assert int(x) == 42 + x = cast(BULongLong, cast(BInt, -42)) + assert int(x) == 2 ** 64 - 42 + x = cast(BIntPtr, cast(BInt, 42)) + assert int(cast(BInt, x)) == 42 + +def test_autocast_float(): + BFloat = new_primitive_type("float") + BDouble = new_primitive_type("float") + BFloatPtr = new_pointer_type(BFloat) + x = newp(BFloatPtr, cast(BDouble, 12.5)) + assert x[0] == 12.5 + x = cast(BFloat, cast(BDouble, 12.5)) + assert float(x) == 12.5 + +def test_longdouble(): + py_py = 'PY_DOT_PY' in globals() + BLongDouble = new_primitive_type("long double") + BLongDoublePtr = new_pointer_type(BLongDouble) + BLongDoubleArray = new_array_type(BLongDoublePtr, None) + a = newp(BLongDoubleArray, 1) + x = a[0] + if not py_py: + assert repr(x).startswith("<cdata 'long double' 0.0") + assert float(x) == 0.0 + assert int(x) == 0 + # + b = newp(BLongDoubleArray, [1.23]) + x = b[0] + if not py_py: + assert repr(x).startswith("<cdata 'long double' 1.23") + assert float(x) == 1.23 + assert int(x) == 1 + # + BFunc19 = new_function_type((BLongDouble,), BLongDouble) + f = cast(BFunc19, _testfunc(19)) + start = 8 + for i in range(107): + start = f(start) + if sizeof(BLongDouble) > sizeof(new_primitive_type("double")): + if not py_py: + assert repr(start).startswith("<cdata 'long double' 6.15") + assert repr(start).endswith("E+902>") + # + c = newp(BLongDoubleArray, [start]) + x = c[0] + if not py_py: + assert repr(x).endswith("E+902>") + assert float(x) == float("inf") + +def test_get_array_of_length_zero(): + for length in [0, 5, 10]: + BLong = new_primitive_type("long") + BLongP = new_pointer_type(BLong) + BArray0 = new_array_type(BLongP, length) + BStruct = new_struct_type("foo") + BStructPtr = new_pointer_type(BStruct) + complete_struct_or_union(BStruct, [('a1', BArray0, -1)]) + p = newp(BStructPtr, None) + if length == 0: + assert repr(p.a1).startswith("<cdata 'long *' 0x") + else: + assert repr(p.a1).startswith("<cdata 'long[%d]' 0x" % length) diff --git a/cffi/__init__.py b/cffi/__init__.py index 7c012d0..54149b5 100644 --- a/cffi/__init__.py +++ b/cffi/__init__.py @@ -4,5 +4,5 @@ __all__ = ['FFI', 'VerificationError', 'VerificationMissing', 'CDefError', from .api import FFI, CDefError, FFIError from .ffiplatform import VerificationError, VerificationMissing -__version__ = "0.2.1" -__version_info__ = (0, 2, 1) +__version__ = "0.3" +__version_info__ = (0, 3) diff --git a/cffi/api.py b/cffi/api.py index 7c79281..de6725b 100644 --- a/cffi/api.py +++ b/cffi/api.py @@ -180,6 +180,24 @@ class FFI(object): cdecl = self._typeof(cdecl) return self._backend.cast(cdecl, source) + def string(self, cdata, maxlen=-1): + """Return a Python string (or unicode string) from the 'cdata'. + If 'cdata' is a pointer or array of characters or bytes, returns + the null-terminated string. The returned string extends until + the first null character, or at most 'maxlen' characters. If + 'cdata' is an array then 'maxlen' defaults to its length. + + If 'cdata' is a pointer or array of wchar_t, returns a unicode + string following the same rules. + + If 'cdata' is a single character or byte or a wchar_t, returns + it as a string or unicode string. + + If 'cdata' is an enum, returns the value of the enumerator as a + string, or "#NUMBER" if the value is out of range. + """ + return self._backend.string(cdata, maxlen) + 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 @@ -216,6 +234,18 @@ class FFI(object): replace_with = ' ' + replace_with return self._backend.getcname(cdecl, replace_with) + def gc(self, cdata, destructor): + """Return a new cdata object that points to the same + data. Later, when this new cdata object is garbage-collected, + 'destructor(old_cdata_object)' will be called. + """ + try: + gc_weakrefs = self.gc_weakrefs + except AttributeError: + from .gc_weakref import GcWeakrefs + gc_weakrefs = self.gc_weakrefs = GcWeakrefs(self) + return gc_weakrefs.build(cdata, destructor) + def _get_cached_btype(self, type): try: BType = self._cached_btypes[type] @@ -259,41 +289,43 @@ def _make_ffi_library(ffi, libname): # backend = ffi._backend backendlib = backend.load_library(path) - function_cache = {} + # + def make_accessor(name): + key = 'function ' + name + if key in ffi._parser._declarations: + tp = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + value = backendlib.load_function(BType, name) + library.__dict__[name] = value + return + # + key = 'variable ' + name + if key in ffi._parser._declarations: + tp = ffi._parser._declarations[key] + BType = ffi._get_cached_btype(tp) + read_variable = backendlib.read_variable + write_variable = backendlib.write_variable + setattr(FFILibrary, name, property( + lambda self: read_variable(BType, name), + lambda self, value: write_variable(BType, name, value))) + return + # + raise AttributeError(name) # class FFILibrary(object): - def __getattribute__(self, name): - try: - return function_cache[name] - except KeyError: - pass - # - key = 'function ' + name - if key in ffi._parser._declarations: - tp = ffi._parser._declarations[key] - BType = ffi._get_cached_btype(tp) - value = backendlib.load_function(BType, name) - function_cache[name] = value - return value - # - key = 'variable ' + name - if key in ffi._parser._declarations: - tp = ffi._parser._declarations[key] - BType = ffi._get_cached_btype(tp) - return backendlib.read_variable(BType, name) - # - raise AttributeError(name) - + def __getattr__(self, name): + make_accessor(name) + return getattr(self, name) def __setattr__(self, name, value): - key = 'variable ' + name - if key in ffi._parser._declarations: - tp = ffi._parser._declarations[key] - BType = ffi._get_cached_btype(tp) - backendlib.write_variable(BType, name, value) - return - # - raise AttributeError(name) + try: + property = getattr(self.__class__, name) + except AttributeError: + make_accessor(name) + setattr(self, name, value) + else: + property.__set__(self, value) # if libname is not None: FFILibrary.__name__ = 'FFILibrary_%s' % libname - return FFILibrary(), function_cache + library = FFILibrary() + return library, library.__dict__ diff --git a/cffi/backend_ctypes.py b/cffi/backend_ctypes.py index 4d6d55d..5ee451d 100644 --- a/cffi/backend_ctypes.py +++ b/cffi/backend_ctypes.py @@ -1,8 +1,8 @@ -import ctypes, ctypes.util, operator +import ctypes, ctypes.util, operator, sys from . import model import sys -if sys.version < '3': +if sys.version_info < (3,): integer_types = (int, long) bytes = str else: @@ -10,7 +10,7 @@ else: xrange = range class CTypesData(object): - __slots__ = [] + __slots__ = ['__weakref__'] def __init__(self, *args): raise TypeError("cannot instantiate %r" % (self.__class__,)) @@ -125,6 +125,9 @@ class CTypesData(object): def __hash__(self): return hash(type(self)) ^ hash(self._convert_to_address(None)) + def _to_string(self, maxlen): + raise TypeError("string(): %r" % (self,)) + class CTypesGenericPrimitive(CTypesData): __slots__ = [] @@ -326,7 +329,10 @@ class CTypesBackend(object): elif name in ('float', 'double'): kind = 'float' else: - kind = 'int' + if name in ('signed char', 'unsigned char'): + kind = 'byte' + else: + kind = 'int' is_signed = (ctype(-1).value == -1) # def _cast_source_to_int(source): @@ -357,7 +363,7 @@ class CTypesBackend(object): return ctype() return ctype(CTypesPrimitive._to_ctypes(init)) - if kind == 'int': + if kind == 'int' or kind == 'byte': @classmethod def _cast_from(cls, source): source = _cast_source_to_int(source) @@ -374,9 +380,6 @@ class CTypesBackend(object): return cls(source) def __int__(self): return ord(self._value) - @property - def value(self): - return self._value if kind == 'float': @classmethod @@ -399,7 +402,7 @@ class CTypesBackend(object): _cast_to_integer = __int__ - if kind == 'int': + if kind == 'int' or kind == 'byte': @staticmethod def _to_ctypes(x): if not isinstance(x, integer_types): @@ -423,7 +426,7 @@ class CTypesBackend(object): return x if isinstance(x, CTypesPrimitive): # <CData <char>> return x._value - if sys.version >= '3' and isinstance(x, int): + if sys.version_info >= (3,) and isinstance(x, int): return x raise TypeError("character expected, got %s" % type(x).__name__) @@ -443,13 +446,24 @@ class CTypesBackend(object): @staticmethod def _initialize(blob, init): blob.value = CTypesPrimitive._to_ctypes(init) + + if kind == 'char': + def _to_string(self, maxlen): + return self._value + if kind == 'byte': + def _to_string(self, maxlen): + return chr(self._value & 0xff) # CTypesPrimitive._fix_class() return CTypesPrimitive def new_pointer_type(self, BItem): - if BItem is self.ffi._get_cached_btype(model.PrimitiveType('char')): + getbtype = self.ffi._get_cached_btype + if BItem is getbtype(model.PrimitiveType('char')): kind = 'charp' + elif BItem in (getbtype(model.PrimitiveType('signed char')), + getbtype(model.PrimitiveType('unsigned char'))): + kind = 'bytep' else: kind = 'generic' # @@ -498,13 +512,6 @@ class CTypesBackend(object): self._as_ctype_ptr[index] = BItem._to_ctypes(value) if kind == 'charp': - @property - def value(self): - n = 0 - while self._as_ctype_ptr[n] != b'\x00': - n += 1 - chars = [self._as_ctype_ptr[i] for i in range(n)] - return b''.join(chars) @classmethod def _arg_to_ctypes(cls, value): if isinstance(value, bytes): @@ -512,6 +519,17 @@ class CTypesBackend(object): else: return super(CTypesPtr, cls)._arg_to_ctypes(value) + if kind == 'charp' or kind == 'bytep': + def _to_string(self, maxlen): + if maxlen < 0: + maxlen = sys.maxint + p = ctypes.cast(self._as_ctype_ptr, + ctypes.POINTER(ctypes.c_char)) + n = 0 + while n < maxlen and p[n] != '\x00': + n += 1 + return ''.join([p[i] for i in range(n)]) + def _get_own_repr(self): if getattr(self, '_own', False): return 'owning %d bytes' % ( @@ -531,8 +549,12 @@ class CTypesBackend(object): else: brackets = ' &[%d]' % length BItem = CTypesPtr._BItem - if BItem is self.ffi._get_cached_btype(model.PrimitiveType('char')): + getbtype = self.ffi._get_cached_btype + if BItem is getbtype(model.PrimitiveType('char')): kind = 'char' + elif BItem in (getbtype(model.PrimitiveType('signed char')), + getbtype(model.PrimitiveType('unsigned char'))): + kind = 'byte' else: kind = 'generic' # @@ -543,6 +565,8 @@ class CTypesBackend(object): else: __slots__.append('_ctype') _reftypename = BItem._get_c_name(brackets) + _declared_length = length + _CTPtr = CTypesPtr def __init__(self, init): if length is None: @@ -584,20 +608,21 @@ class CTypesBackend(object): raise IndexError self._blob[index] = BItem._to_ctypes(value) - if kind == 'char': - @property - def value(self): - s = b''.join(self._blob) - try: - s = s[:s.index(b'\x00')] - except ValueError: - pass - return s + if kind == 'char' or kind == 'byte': + def _to_string(self, maxlen): + if maxlen < 0: + maxlen = len(self._blob) + p = ctypes.cast(self._blob, + ctypes.POINTER(ctypes.c_char)) + n = 0 + while n < maxlen and p[n] != '\x00': + n += 1 + return ''.join([p[i] for i in range(n)]) def _get_own_repr(self): if getattr(self, '_own', False): return 'owning %d bytes' % (ctypes.sizeof(self._blob),) - return super(CTypesPtr, self)._get_own_repr() + return super(CTypesArray, self)._get_own_repr() def _convert_to_address(self, BClass): if BClass in (CTypesPtr, None) or BClass._automatic_casts: @@ -622,6 +647,11 @@ class CTypesBackend(object): other * ctypes.sizeof(BItem._ctype)) else: return NotImplemented + + @classmethod + def _cast_from(cls, source): + raise NotImplementedError("casting to %r" % ( + cls._get_c_name(),)) # CTypesArray._fix_class() return CTypesArray @@ -647,7 +677,12 @@ class CTypesBackend(object): def new_union_type(self, name): return self._new_struct_or_union('union', name, ctypes.Union) - def complete_struct_or_union(self, CTypesStructOrUnion, fields, tp): + def complete_struct_or_union(self, CTypesStructOrUnion, fields, tp, + totalsize=-1, totalalignment=-1): + if totalsize >= 0 or totalalignment >= 0: + raise NotImplementedError("the ctypes backend of CFFI does not support " + "structures completed by verify(); please " + "compile and install the _cffi_backend module.") struct_or_union = CTypesStructOrUnion._ctype fnames = [fname for (fname, BField, bitsize) in fields] btypes = [BField for (fname, BField, bitsize) in fields] @@ -708,6 +743,17 @@ class CTypesBackend(object): return BField._from_ctypes(p.contents) def setter(self, value, fname=fname, BField=BField): setattr(self._blob, fname, BField._to_ctypes(value)) + # + if issubclass(BField, CTypesGenericArray): + setter = None + if BField._declared_length == 0: + def getter(self, fname=fname, BFieldPtr=BField._CTPtr, + offset=CTypesStructOrUnion._offsetof(fname), + PTR=ctypes.POINTER(BField._ctype)): + addr = ctypes.addressof(self._blob) + p = ctypes.cast(addr + offset, PTR) + return BFieldPtr._from_ctypes(p) + # else: def getter(self, fname=fname, BField=BField): return BField._from_ctypes(getattr(self._blob, fname)) @@ -859,7 +905,7 @@ class CTypesBackend(object): __slots__ = [] _reftypename = 'enum %s &' % name - def __str__(self): + def _to_string(self, maxlen): return str(CTypesEnum._from_ctypes(self._value)) @classmethod @@ -889,8 +935,11 @@ class CTypesBackend(object): def set_errno(self, value): ctypes.set_errno(value) + def string(self, b, maxlen=-1): + return b._to_string(maxlen) + def buffer(self, bptr, size=-1): - if sys.version >= '3': + if sys.version_info >= (3,): # buf = bptr._as_ctype_ptr # return memoryview(buf.contents) if isinstance(bptr, CTypesGenericPtr): @@ -972,7 +1021,10 @@ class CTypesLibrary(object): return funcobj def read_variable(self, BType, name): - ctypes_obj = BType._ctype.in_dll(self.cdll, name) + try: + ctypes_obj = BType._ctype.in_dll(self.cdll, name) + except AttributeError, e: + raise NotImplementedError(e) return BType._from_ctypes(ctypes_obj) def write_variable(self, BType, name, value): diff --git a/cffi/gc_weakref.py b/cffi/gc_weakref.py new file mode 100644 index 0000000..31621fd --- /dev/null +++ b/cffi/gc_weakref.py @@ -0,0 +1,19 @@ +from weakref import ref + + +class GcWeakrefs(object): + # code copied and adapted from WeakKeyDictionary. + + def __init__(self, ffi): + self.ffi = ffi + self.data = data = {} + def remove(k): + destructor, cdata = data.pop(k) + destructor(cdata) + self.remove = remove + + def build(self, cdata, destructor): + # make a new cdata of the same type as the original one + new_cdata = self.ffi.cast(self.ffi.typeof(cdata), cdata) + self.data[ref(new_cdata, self.remove)] = destructor, cdata + return new_cdata diff --git a/cffi/vengine_cpy.py b/cffi/vengine_cpy.py new file mode 100644 index 0000000..c12bddd --- /dev/null +++ b/cffi/vengine_cpy.py @@ -0,0 +1,776 @@ +import imp +from . import model, ffiplatform + + +class VCPythonEngine(object): + _class_key = 'x' + _gen_python_module = True + + def __init__(self, verifier): + self.verifier = verifier + self.ffi = verifier.ffi + + def patch_extension_kwds(self, kwds): + pass + + def collect_types(self): + self._typesdict = {} + self._generate("collecttype") + + def _prnt(self, what=''): + self._f.write(what + '\n') + + def _gettypenum(self, type): + # a KeyError here is a bug. please report it! :-) + return self._typesdict[type] + + def _do_collect_type(self, tp): + if (not isinstance(tp, model.PrimitiveType) and + tp not in self._typesdict): + num = len(self._typesdict) + self._typesdict[tp] = num + + def write_source_to_f(self): + self.collect_types() + # + # The new module will have a _cffi_setup() function that receives + # objects from the ffi world, and that calls some setup code in + # the module. This setup code is split in several independent + # functions, e.g. one per constant. The functions are "chained" + # by ending in a tail call to each other. + # + # This is further split in two chained lists, depending on if we + # can do it at import-time or if we must wait for _cffi_setup() to + # provide us with the <ctype> objects. This is needed because we + # need the values of the enum constants in order to build the + # <ctype 'enum'> that we may have to pass to _cffi_setup(). + # + # The following two 'chained_list_constants' items contains + # the head of these two chained lists, as a string that gives the + # call to do, if any. + self._chained_list_constants = ['0', '0'] + # + prnt = self._prnt + # first paste some standard set of lines that are mostly '#define' + prnt(cffimod_header) + prnt() + # then paste the C source given by the user, verbatim. + prnt(self.verifier.preamble) + prnt() + # + # call generate_cpy_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._generate("decl") + # + # implement the function _cffi_setup_custom() as calling the + # head of the chained list. + self._generate_setup_custom() + prnt() + # + # produce the method table, including the entries for the + # generated Python->C function wrappers, which are done + # by generate_cpy_function_method(). + prnt('static PyMethodDef _cffi_methods[] = {') + self._generate("method") + prnt(' {"_cffi_setup", _cffi_setup, METH_VARARGS},') + prnt(' {NULL, NULL} /* Sentinel */') + prnt('};') + prnt() + # + # standard init. + modname = self.verifier.get_module_name() + if sys.version_info >= (3,): + prnt('static struct PyModuleDef _cffi_module_def = {') + prnt(' PyModuleDef_HEAD_INIT,') + prnt(' "%s",' % modname) + prnt(' NULL,') + prnt(' -1,') + prnt(' _cffi_methods,') + prnt(' NULL, NULL, NULL, NULL') + prnt('};') + prnt() + initname = 'PyInit_%s' % modname + createmod = 'PyModule_Create(&_cffi_module_def)' + errorcase = 'return NULL' + finalreturn = 'return lib' + else: + initname = 'init%s' % modname + createmod = 'Py_InitModule("%s", _cffi_methods)' % modname + errorcase = 'return' + finalreturn = 'return' + prnt('PyMODINIT_FUNC') + prnt('%s(void)' % initname) + prnt('{') + prnt(' PyObject *lib;') + prnt(' lib = %s;' % createmod) + prnt(' if (lib == NULL || %s < 0)' % ( + self._chained_list_constants[False],)) + prnt(' %s;' % errorcase) + prnt(' _cffi_init();') + prnt(' %s;' % finalreturn) + prnt('}') + + def load_library(self): + # XXX review all usages of 'self' here! + # import it as a new extension module + try: + module = imp.load_dynamic(self.verifier.get_module_name(), + self.verifier.modulefilename) + except ImportError as e: + error = "importing %r: %s" % (self.verifier.modulefilename, e) + raise ffiplatform.VerificationError(error) + # + # call loading_cpy_struct() to get the struct layout inferred by + # the C compiler + self._load(module, 'loading') + # + # the C code will need the <ctype> objects. Collect them in + # order in a list. + revmapping = dict([(value, key) + for (key, value) in self._typesdict.items()]) + lst = [revmapping[i] for i in range(len(revmapping))] + lst = list(map(self.ffi._get_cached_btype, lst)) + # + # build the FFILibrary class and instance and call _cffi_setup(). + # this will set up some fields like '_cffi_types', and only then + # it will invoke the chained list of functions that will really + # build (notably) the constant objects, as <cdata> if they are + # pointers, and store them as attributes on the 'library' object. + class FFILibrary(object): + _cffi_python_module = module + library = FFILibrary() + module._cffi_setup(lst, ffiplatform.VerificationError, library) + # + # finally, call the loaded_cpy_xxx() functions. This will perform + # the final adjustments, like copying the Python->C wrapper + # functions from the module to the 'library' object, and setting + # up the FFILibrary class with properties for the global C variables. + self._load(module, 'loaded', library=library) + return library + + def _generate(self, step_name): + for name, tp in self.ffi._parser._declarations.items(): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_cpy_%s_%s' % (kind, + step_name)) + except AttributeError: + raise ffiplatform.VerificationError( + "not implemented in verify(): %r" % name) + method(tp, realname) + + def _load(self, module, step_name, **kwds): + for name, tp in self.ffi._parser._declarations.items(): + kind, realname = name.split(' ', 1) + method = getattr(self, '_%s_cpy_%s' % (step_name, kind)) + method(tp, realname, module, **kwds) + + def _generate_nothing(self, tp, name): + pass + + def _loaded_noop(self, tp, name, module, **kwds): + pass + + # ---------- + + def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode): + extraarg = '' + if isinstance(tp, model.PrimitiveType): + converter = '_cffi_to_c_%s' % (tp.name.replace(' ', '_'),) + errvalue = '-1' + # + elif isinstance(tp, model.PointerType): + if (isinstance(tp.totype, model.PrimitiveType) and + tp.totype.name == 'char'): + converter = '_cffi_to_c_char_p' + else: + converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') + extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) + errvalue = 'NULL' + # + elif isinstance(tp, (model.StructOrUnion, model.EnumType)): + # a struct (not a struct pointer) as a function argument + self._prnt(' if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)' + % (tovar, self._gettypenum(tp), fromvar)) + self._prnt(' %s;' % errcode) + return + # + elif isinstance(tp, model.FunctionPtrType): + converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') + extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) + errvalue = 'NULL' + # + else: + raise NotImplementedError(tp) + # + self._prnt(' %s = %s(%s%s);' % (tovar, converter, fromvar, extraarg)) + self._prnt(' if (%s == (%s)%s && PyErr_Occurred())' % ( + tovar, tp.get_c_name(''), errvalue)) + self._prnt(' %s;' % errcode) + + def _convert_expr_from_c(self, tp, var): + if isinstance(tp, model.PrimitiveType): + return '_cffi_from_c_%s(%s)' % (tp.name.replace(' ', '_'), var) + elif isinstance(tp, (model.PointerType, model.FunctionPtrType)): + return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.ArrayType): + return '_cffi_from_c_deref((char *)%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.StructType): + return '_cffi_from_c_struct((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + elif isinstance(tp, model.EnumType): + return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( + var, self._gettypenum(tp)) + else: + raise NotImplementedError(tp) + + # ---------- + # typedefs: generates no code so far + + _generate_cpy_typedef_collecttype = _generate_nothing + _generate_cpy_typedef_decl = _generate_nothing + _generate_cpy_typedef_method = _generate_nothing + _loading_cpy_typedef = _loaded_noop + _loaded_cpy_typedef = _loaded_noop + + # ---------- + # function declarations + + def _generate_cpy_function_collecttype(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + self._do_collect_type(tp) + else: + for type in tp.args: + self._do_collect_type(type) + self._do_collect_type(tp.result) + + def _generate_cpy_function_decl(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no CPython wrapper) + self._generate_cpy_const(False, name, tp) + return + prnt = self._prnt + numargs = len(tp.args) + if numargs == 0: + argname = 'no_arg' + elif numargs == 1: + argname = 'arg0' + else: + argname = 'args' + prnt('static PyObject *') + prnt('_cffi_f_%s(PyObject *self, PyObject *%s)' % (name, argname)) + prnt('{') + # + for i, type in enumerate(tp.args): + prnt(' %s;' % type.get_c_name(' x%d' % i)) + if not isinstance(tp.result, model.VoidType): + result_code = 'result = ' + prnt(' %s;' % tp.result.get_c_name(' result')) + else: + result_code = '' + # + if len(tp.args) > 1: + rng = range(len(tp.args)) + for i in rng: + prnt(' PyObject *arg%d;' % i) + prnt() + prnt(' if (!PyArg_ParseTuple(args, "%s:%s", %s))' % ( + 'O' * numargs, name, ', '.join(['&arg%d' % i for i in rng]))) + prnt(' return NULL;') + prnt() + # + for i, type in enumerate(tp.args): + self._convert_funcarg_to_c(type, 'arg%d' % i, 'x%d' % i, + 'return NULL') + prnt() + # + prnt(' Py_BEGIN_ALLOW_THREADS') + prnt(' _cffi_restore_errno();') + prnt(' { %s%s(%s); }' % ( + result_code, name, + ', '.join(['x%d' % i for i in range(len(tp.args))]))) + prnt(' _cffi_save_errno();') + prnt(' Py_END_ALLOW_THREADS') + prnt() + # + if result_code: + prnt(' return %s;' % + self._convert_expr_from_c(tp.result, 'result')) + else: + prnt(' Py_INCREF(Py_None);') + prnt(' return Py_None;') + prnt('}') + prnt() + + def _generate_cpy_function_method(self, tp, name): + if tp.ellipsis: + return + numargs = len(tp.args) + if numargs == 0: + meth = 'METH_NOARGS' + elif numargs == 1: + meth = 'METH_O' + else: + meth = 'METH_VARARGS' + self._prnt(' {"%s", _cffi_f_%s, %s},' % (name, name, meth)) + + _loading_cpy_function = _loaded_noop + + def _loaded_cpy_function(self, tp, name, module, library): + if tp.ellipsis: + return + setattr(library, name, getattr(module, name)) + + # ---------- + # named structs + + _generate_cpy_struct_collecttype = _generate_nothing + + def _generate_cpy_struct_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'struct', name) + + def _generate_cpy_struct_method(self, tp, name): + self._generate_struct_or_union_method(tp, 'struct', name) + + def _loading_cpy_struct(self, tp, name, module): + self._loading_struct_or_union(tp, 'struct', name, module) + + def _loaded_cpy_struct(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_struct_or_union_decl(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + checkfuncname = '_cffi_check_%s_%s' % (prefix, name) + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + prnt = self._prnt + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + for i in range(len(tp.fldnames)): + fname = tp.fldnames[i] + ftype = tp.fldtypes[i] + if (isinstance(ftype, model.PrimitiveType) + and ftype.is_integer_type()): + # accept all integers, but complain on float or double + prnt(' (void)((p->%s) << 1);' % fname) + else: + # only accept exactly the type declared. Note the parentheses + # around the '*tmp' below. In most cases they are not needed + # but don't hurt --- except test_struct_array_field. + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('(*tmp)'), fname)) + prnt('}') + prnt('static PyObject *') + prnt('%s(PyObject *self, PyObject *noarg)' % (layoutfuncname,)) + prnt('{') + prnt(' struct _cffi_aligncheck { char x; %s y; };' % cname) + if tp.partial: + prnt(' static Py_ssize_t nums[] = {') + prnt(' sizeof(%s),' % cname) + prnt(' offsetof(struct _cffi_aligncheck, y),') + for fname in tp.fldnames: + prnt(' offsetof(%s, %s),' % (cname, fname)) + prnt(' sizeof(((%s *)0)->%s),' % (cname, fname)) + prnt(' -1') + prnt(' };') + prnt(' return _cffi_get_struct_layout(nums);') + else: + ffi = self.ffi + BStruct = ffi._get_cached_btype(tp) + conditions = [ + 'sizeof(%s) != %d' % (cname, ffi.sizeof(BStruct)), + 'offsetof(struct _cffi_aligncheck, y) != %d' % ( + ffi.alignof(BStruct),)] + for fname, ftype in zip(tp.fldnames, tp.fldtypes): + BField = ffi._get_cached_btype(ftype) + conditions += [ + 'offsetof(%s, %s) != %d' % ( + cname, fname, ffi.offsetof(BStruct, fname)), + 'sizeof(((%s *)0)->%s) != %d' % ( + cname, fname, ffi.sizeof(BField))] + prnt(' if (%s ||' % conditions[0]) + for i in range(1, len(conditions)-1): + prnt(' %s ||' % conditions[i]) + prnt(' %s) {' % conditions[-1]) + prnt(' Py_INCREF(Py_False);') + prnt(' return Py_False;') + prnt(' }') + prnt(' else {') + prnt(' Py_INCREF(Py_True);') + prnt(' return Py_True;') + prnt(' }') + prnt(' /* the next line is not executed, but compiled */') + prnt(' %s(0);' % (checkfuncname,)) + prnt('}') + prnt() + + def _generate_struct_or_union_method(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + self._prnt(' {"%s", %s, METH_NOARGS},' % (layoutfuncname, + layoutfuncname)) + + def _loading_struct_or_union(self, tp, prefix, name, module): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + function = getattr(module, layoutfuncname) + layout = function() + if layout is False: + raise ffiplatform.VerificationError( + "incompatible layout for %s" % cname) + elif layout is True: + assert not tp.partial + else: + totalsize = layout[0] + totalalignment = layout[1] + fieldofs = layout[2::2] + fieldsize = layout[3::2] + assert len(fieldofs) == len(fieldsize) == len(tp.fldnames) + tp.fixedlayout = fieldofs, fieldsize, totalsize, totalalignment + + def _loaded_struct_or_union(self, tp): + if tp.fldnames is None: + return # nothing to do with opaque structs + self.ffi._get_cached_btype(tp) # force 'fixedlayout' to be considered + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + _generate_cpy_anonymous_collecttype = _generate_nothing + + def _generate_cpy_anonymous_decl(self, tp, name): + self._generate_struct_or_union_decl(tp, '', name) + + def _generate_cpy_anonymous_method(self, tp, name): + self._generate_struct_or_union_method(tp, '', name) + + def _loading_cpy_anonymous(self, tp, name, module): + self._loading_struct_or_union(tp, '', name, module) + + def _loaded_cpy_anonymous(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + # ---------- + # constants, likely declared with '#define' + + def _generate_cpy_const(self, is_int, name, tp=None, category='const', + vartp=None, delayed=True): + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + prnt('static int %s(PyObject *lib)' % funcname) + prnt('{') + prnt(' PyObject *o;') + prnt(' int res;') + if not is_int: + prnt(' %s;' % (vartp or tp).get_c_name(' i')) + else: + assert category == 'const' + # + if not is_int: + if category == 'var': + realexpr = '&' + name + else: + realexpr = name + prnt(' i = (%s);' % (realexpr,)) + prnt(' o = %s;' % (self._convert_expr_from_c(tp, 'i'),)) + assert delayed + else: + prnt(' if (LONG_MIN <= (%s) && (%s) <= LONG_MAX)' % (name, name)) + prnt(' o = PyInt_FromLong((long)(%s));' % (name,)) + prnt(' else if ((%s) <= 0)' % (name,)) + prnt(' o = PyLong_FromLongLong((long long)(%s));' % (name,)) + prnt(' else') + prnt(' o = PyLong_FromUnsignedLongLong(' + '(unsigned long long)(%s));' % (name,)) + prnt(' if (o == NULL)') + prnt(' return -1;') + prnt(' res = PyObject_SetAttrString(lib, "%s", o);' % name) + prnt(' Py_DECREF(o);') + prnt(' if (res < 0)') + prnt(' return -1;') + prnt(' return %s;' % self._chained_list_constants[delayed]) + self._chained_list_constants[delayed] = funcname + '(lib)' + prnt('}') + prnt() + + def _generate_cpy_constant_collecttype(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + if not is_int: + self._do_collect_type(tp) + + def _generate_cpy_constant_decl(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + self._generate_cpy_const(is_int, name, tp) + + _generate_cpy_constant_method = _generate_nothing + _loading_cpy_constant = _loaded_noop + _loaded_cpy_constant = _loaded_noop + + # ---------- + # enums + + def _generate_cpy_enum_decl(self, tp, name): + if tp.partial: + for enumerator in tp.enumerators: + self._generate_cpy_const(True, enumerator, delayed=False) + return + # + funcname = '_cffi_enum_%s' % name + prnt = self._prnt + prnt('static int %s(PyObject *lib)' % funcname) + prnt('{') + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + prnt(' if (%s != %d) {' % (enumerator, enumvalue)) + prnt(' PyErr_Format(_cffi_VerificationError,') + prnt(' "in enum %s: %s has the real value %d, ' + 'not %d",') + prnt(' "%s", "%s", (int)%s, %d);' % ( + name, enumerator, enumerator, enumvalue)) + prnt(' return -1;') + prnt(' }') + prnt(' return %s;' % self._chained_list_constants[True]) + self._chained_list_constants[True] = funcname + '(lib)' + prnt('}') + prnt() + + _generate_cpy_enum_collecttype = _generate_nothing + _generate_cpy_enum_method = _generate_nothing + _loading_cpy_enum = _loaded_noop + + def _loading_cpy_enum(self, tp, name, module): + if tp.partial: + enumvalues = [getattr(module, enumerator) + for enumerator in tp.enumerators] + tp.enumvalues = tuple(enumvalues) + tp.partial = False + + def _loaded_cpy_enum(self, tp, name, module, library): + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + setattr(library, enumerator, enumvalue) + + # ---------- + # macros: for now only for integers + + def _generate_cpy_macro_decl(self, tp, name): + assert tp == '...' + self._generate_cpy_const(True, name) + + _generate_cpy_macro_collecttype = _generate_nothing + _generate_cpy_macro_method = _generate_nothing + _loading_cpy_macro = _loaded_noop + _loaded_cpy_macro = _loaded_noop + + # ---------- + # global variables + + def _generate_cpy_variable_collecttype(self, tp, name): + if isinstance(tp, model.ArrayType): + self._do_collect_type(tp) + else: + tp_ptr = model.PointerType(tp) + self._do_collect_type(tp_ptr) + + def _generate_cpy_variable_decl(self, tp, name): + if isinstance(tp, model.ArrayType): + tp_ptr = model.PointerType(tp.item) + self._generate_cpy_const(False, name, tp, vartp=tp_ptr) + else: + tp_ptr = model.PointerType(tp) + self._generate_cpy_const(False, name, tp_ptr, category='var') + + _generate_cpy_variable_method = _generate_nothing + _loading_cpy_variable = _loaded_noop + + def _loaded_cpy_variable(self, tp, name, module, library): + if isinstance(tp, model.ArrayType): # int a[5] is "constant" in the + return # sense that "a=..." is forbidden + # remove ptr=<cdata 'int *'> from the library instance, and replace + # it by a property on the class, which reads/writes into ptr[0]. + ptr = getattr(library, name) + delattr(library, name) + def getter(library): + return ptr[0] + def setter(library, value): + ptr[0] = value + setattr(library.__class__, name, property(getter, setter)) + + # ---------- + + def _generate_setup_custom(self): + prnt = self._prnt + prnt('static PyObject *_cffi_setup_custom(PyObject *lib)') + prnt('{') + prnt(' if (%s < 0)' % self._chained_list_constants[True]) + prnt(' return NULL;') + prnt(' Py_INCREF(Py_None);') + prnt(' return Py_None;') + prnt('}') + +cffimod_header = r''' +#include <Python.h> +#include <stddef.h> + +#ifdef MS_WIN32 +typedef __int8 int8_t; +typedef __int16 int16_t; +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +#endif + +#if PY_MAJOR_VERSION < 3 +# undef PyCapsule_CheckExact +# undef PyCapsule_GetPointer +# define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule)) +# define PyCapsule_GetPointer(capsule, name) \ + (PyCObject_AsVoidPtr(capsule)) +#endif + +#if PY_MAJOR_VERSION >= 3 +# define PyInt_FromLong PyLong_FromLong +# define PyInt_AsLong PyLong_AsLong +#endif + +#define _cffi_from_c_double PyFloat_FromDouble +#define _cffi_from_c_float PyFloat_FromDouble +#define _cffi_from_c_signed_char PyInt_FromLong +#define _cffi_from_c_short PyInt_FromLong +#define _cffi_from_c_int PyInt_FromLong +#define _cffi_from_c_long PyInt_FromLong +#define _cffi_from_c_unsigned_char PyInt_FromLong +#define _cffi_from_c_unsigned_short PyInt_FromLong +#define _cffi_from_c_unsigned_long PyLong_FromUnsignedLong +#define _cffi_from_c_unsigned_long_long PyLong_FromUnsignedLongLong + +#if SIZEOF_INT < SIZEOF_LONG +# define _cffi_from_c_unsigned_int PyInt_FromLong +#else +# define _cffi_from_c_unsigned_int PyLong_FromUnsignedLong +#endif + +#if SIZEOF_LONG < SIZEOF_LONG_LONG +# define _cffi_from_c_long_long PyLong_FromLongLong +#else +# define _cffi_from_c_long_long PyInt_FromLong +#endif + +#define _cffi_to_c_long PyInt_AsLong +#define _cffi_to_c_double PyFloat_AsDouble +#define _cffi_to_c_float PyFloat_AsDouble + +#define _cffi_to_c_char_p \ + ((char *(*)(PyObject *))_cffi_exports[0]) +#define _cffi_to_c_signed_char \ + ((signed char(*)(PyObject *))_cffi_exports[1]) +#define _cffi_to_c_unsigned_char \ + ((unsigned char(*)(PyObject *))_cffi_exports[2]) +#define _cffi_to_c_short \ + ((short(*)(PyObject *))_cffi_exports[3]) +#define _cffi_to_c_unsigned_short \ + ((unsigned short(*)(PyObject *))_cffi_exports[4]) + +#if SIZEOF_INT < SIZEOF_LONG +# define _cffi_to_c_int \ + ((int(*)(PyObject *))_cffi_exports[5]) +# define _cffi_to_c_unsigned_int \ + ((unsigned int(*)(PyObject *))_cffi_exports[6]) +#else +# define _cffi_to_c_int _cffi_to_c_long +# define _cffi_to_c_unsigned_int _cffi_to_c_unsigned_long +#endif + +#define _cffi_to_c_unsigned_long \ + ((unsigned long(*)(PyObject *))_cffi_exports[7]) +#define _cffi_to_c_unsigned_long_long \ + ((unsigned long long(*)(PyObject *))_cffi_exports[8]) +#define _cffi_to_c_char \ + ((char(*)(PyObject *))_cffi_exports[9]) +#define _cffi_from_c_pointer \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[10]) +#define _cffi_to_c_pointer \ + ((char *(*)(PyObject *, CTypeDescrObject *))_cffi_exports[11]) +#define _cffi_get_struct_layout \ + ((PyObject *(*)(Py_ssize_t[]))_cffi_exports[12]) +#define _cffi_restore_errno \ + ((void(*)(void))_cffi_exports[13]) +#define _cffi_save_errno \ + ((void(*)(void))_cffi_exports[14]) +#define _cffi_from_c_char \ + ((PyObject *(*)(char))_cffi_exports[15]) +#define _cffi_from_c_deref \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[16]) +#define _cffi_to_c \ + ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[17]) +#define _cffi_from_c_struct \ + ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[18]) +#define _cffi_to_c_wchar_t \ + ((wchar_t(*)(PyObject *))_cffi_exports[19]) +#define _cffi_from_c_wchar_t \ + ((PyObject *(*)(wchar_t))_cffi_exports[20]) +#define _CFFI_NUM_EXPORTS 21 + +#if SIZEOF_LONG < SIZEOF_LONG_LONG +# define _cffi_to_c_long_long PyLong_AsLongLong +#else +# define _cffi_to_c_long_long _cffi_to_c_long +#endif + +typedef struct _ctypedescr CTypeDescrObject; + +static void *_cffi_exports[_CFFI_NUM_EXPORTS]; +static PyObject *_cffi_types, *_cffi_VerificationError; + +static PyObject *_cffi_setup_custom(PyObject *lib); /* forward */ + +static PyObject *_cffi_setup(PyObject *self, PyObject *args) +{ + PyObject *library; + if (!PyArg_ParseTuple(args, "OOO", &_cffi_types, &_cffi_VerificationError, + &library)) + return NULL; + Py_INCREF(_cffi_types); + Py_INCREF(_cffi_VerificationError); + return _cffi_setup_custom(library); +} + +static void _cffi_init(void) +{ + PyObject *module = PyImport_ImportModule("_cffi_backend"); + PyObject *c_api_object; + + if (module == NULL) + return; + + c_api_object = PyObject_GetAttrString(module, "_C_API"); + if (c_api_object == NULL) + return; + if (!PyCapsule_CheckExact(c_api_object)) { + PyErr_SetNone(PyExc_ImportError); + return; + } + memcpy(_cffi_exports, PyCapsule_GetPointer(c_api_object, "cffi"), + _CFFI_NUM_EXPORTS * sizeof(void *)); +} + +#define _cffi_type(num) ((CTypeDescrObject *)PyList_GET_ITEM(_cffi_types, num)) + +/**********/ +''' diff --git a/cffi/vengine_gen.py b/cffi/vengine_gen.py new file mode 100644 index 0000000..d8cc5a4 --- /dev/null +++ b/cffi/vengine_gen.py @@ -0,0 +1,458 @@ +import sys, os, binascii, imp, shutil +from . import model, ffiplatform + + +class VGenericEngine(object): + _class_key = 'g' + _gen_python_module = False + + def __init__(self, verifier): + self.verifier = verifier + self.ffi = verifier.ffi + self.export_symbols = [] + + def patch_extension_kwds(self, kwds): + # add 'export_symbols' to the dictionary. Note that we add the + # list before filling it. When we fill it, it will thus also show + # up in kwds['export_symbols']. + kwds.setdefault('export_symbols', self.export_symbols) + + def collect_types(self): + pass # not needed in the generic engine + + def _prnt(self, what=''): + print >> self._f, what + + def write_source_to_f(self): + prnt = self._prnt + # first paste some standard set of lines that are mostly '#include' + prnt(cffimod_header) + # then paste the C source given by the user, verbatim. + prnt(self.verifier.preamble) + # + # call generate_gen_xxx_decl(), for every xxx found from + # ffi._parser._declarations. This generates all the functions. + self._generate('decl') + # + # on Windows, distutils insists on putting init_cffi_xyz in + # 'export_symbols', so instead of fighting it, just give up and + # give it one + if sys.platform == 'win32': + prnt("void init%s(void) { }\n" % self.verifier.get_module_name()) + + def load_library(self): + # import it with the CFFI backend + backend = self.ffi._backend + module = backend.load_library(self.verifier.modulefilename) + # + # call loading_gen_struct() to get the struct layout inferred by + # the C compiler + self._load(module, 'loading') + # + # build the FFILibrary class and instance + class FFILibrary(object): + _cffi_generic_module = module + library = FFILibrary() + # + # finally, call the loaded_gen_xxx() functions. This will set + # up the 'library' object. + self._load(module, 'loaded', library=library) + return library + + def _generate(self, step_name): + for name, tp in self.ffi._parser._declarations.iteritems(): + kind, realname = name.split(' ', 1) + try: + method = getattr(self, '_generate_gen_%s_%s' % (kind, + step_name)) + except AttributeError: + raise ffiplatform.VerificationError( + "not implemented in verify(): %r" % name) + method(tp, realname) + + def _load(self, module, step_name, **kwds): + for name, tp in self.ffi._parser._declarations.iteritems(): + kind, realname = name.split(' ', 1) + method = getattr(self, '_%s_gen_%s' % (step_name, kind)) + method(tp, realname, module, **kwds) + + def _generate_nothing(self, tp, name): + pass + + def _loaded_noop(self, tp, name, module, **kwds): + pass + + # ---------- + # typedefs: generates no code so far + + _generate_gen_typedef_decl = _generate_nothing + _loading_gen_typedef = _loaded_noop + _loaded_gen_typedef = _loaded_noop + + # ---------- + # function declarations + + def _generate_gen_function_decl(self, tp, name): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + # cannot support vararg functions better than this: check for its + # exact type (including the fixed arguments), and build it as a + # constant function pointer (no _cffi_f_%s wrapper) + self._generate_gen_const(False, name, tp) + return + prnt = self._prnt + numargs = len(tp.args) + argnames = [] + for i, type in enumerate(tp.args): + indirection = '' + if isinstance(type, model.StructOrUnion): + indirection = '*' + argnames.append('%sx%d' % (indirection, i)) + arglist = [type.get_c_name(' %s' % arg) + for type, arg in zip(tp.args, argnames)] + arglist = ', '.join(arglist) or 'void' + wrappername = '_cffi_f_%s' % name + self.export_symbols.append(wrappername) + funcdecl = ' %s(%s)' % (wrappername, arglist) + prnt(tp.result.get_c_name(funcdecl)) + prnt('{') + # + if not isinstance(tp.result, model.VoidType): + result_code = 'return ' + else: + result_code = '' + prnt(' %s%s(%s);' % (result_code, name, ', '.join(argnames))) + prnt('}') + prnt() + + _loading_gen_function = _loaded_noop + + def _loaded_gen_function(self, tp, name, module, library): + assert isinstance(tp, model.FunctionPtrType) + if tp.ellipsis: + newfunction = self._load_constant(False, tp, name, module) + else: + indirections = [] + if any(isinstance(type, model.StructOrUnion) for type in tp.args): + indirect_args = [] + for i, type in enumerate(tp.args): + if isinstance(type, model.StructOrUnion): + type = model.PointerType(type) + indirections.append((i, type)) + indirect_args.append(type) + tp = model.FunctionPtrType(tuple(indirect_args), + tp.result, tp.ellipsis) + BFunc = self.ffi._get_cached_btype(tp) + wrappername = '_cffi_f_%s' % name + newfunction = module.load_function(BFunc, wrappername) + for i, type in indirections: + newfunction = self._make_struct_wrapper(newfunction, i, type) + setattr(library, name, newfunction) + + def _make_struct_wrapper(self, oldfunc, i, tp): + backend = self.ffi._backend + BType = self.ffi._get_cached_btype(tp) + def newfunc(*args): + args = args[:i] + (backend.newp(BType, args[i]),) + args[i+1:] + return oldfunc(*args) + return newfunc + + # ---------- + # named structs + + def _generate_gen_struct_decl(self, tp, name): + assert name == tp.name + self._generate_struct_or_union_decl(tp, 'struct', name) + + def _loading_gen_struct(self, tp, name, module): + self._loading_struct_or_union(tp, 'struct', name, module) + + def _loaded_gen_struct(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + def _generate_struct_or_union_decl(self, tp, prefix, name): + if tp.fldnames is None: + return # nothing to do with opaque structs + checkfuncname = '_cffi_check_%s_%s' % (prefix, name) + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + prnt = self._prnt + prnt('static void %s(%s *p)' % (checkfuncname, cname)) + prnt('{') + prnt(' /* only to generate compile-time warnings or errors */') + for i in range(len(tp.fldnames)): + fname = tp.fldnames[i] + ftype = tp.fldtypes[i] + if (isinstance(ftype, model.PrimitiveType) + and ftype.is_integer_type()): + # accept all integers, but complain on float or double + prnt(' (void)((p->%s) << 1);' % fname) + else: + # only accept exactly the type declared. Note the parentheses + # around the '*tmp' below. In most cases they are not needed + # but don't hurt --- except test_struct_array_field. + prnt(' { %s = &p->%s; (void)tmp; }' % ( + ftype.get_c_name('(*tmp)'), fname)) + prnt('}') + self.export_symbols.append(layoutfuncname) + prnt('ssize_t %s(ssize_t i)' % (layoutfuncname,)) + prnt('{') + prnt(' struct _cffi_aligncheck { char x; %s y; };' % cname) + if tp.partial: + prnt(' static ssize_t nums[] = {') + prnt(' 1, sizeof(%s),' % cname) + prnt(' offsetof(struct _cffi_aligncheck, y),') + for fname in tp.fldnames: + prnt(' offsetof(%s, %s),' % (cname, fname)) + prnt(' sizeof(((%s *)0)->%s),' % (cname, fname)) + prnt(' -1') + prnt(' };') + prnt(' return nums[i];') + else: + ffi = self.ffi + BStruct = ffi._get_cached_btype(tp) + conditions = [ + 'sizeof(%s) != %d' % (cname, ffi.sizeof(BStruct)), + 'offsetof(struct _cffi_aligncheck, y) != %d' % ( + ffi.alignof(BStruct),)] + for fname, ftype in zip(tp.fldnames, tp.fldtypes): + BField = ffi._get_cached_btype(ftype) + conditions += [ + 'offsetof(%s, %s) != %d' % ( + cname, fname, ffi.offsetof(BStruct, fname)), + 'sizeof(((%s *)0)->%s) != %d' % ( + cname, fname, ffi.sizeof(BField))] + prnt(' if (%s ||' % conditions[0]) + for i in range(1, len(conditions)-1): + prnt(' %s ||' % conditions[i]) + prnt(' %s) {' % conditions[-1]) + prnt(' return -1;') + prnt(' }') + prnt(' else {') + prnt(' return 0;') + prnt(' }') + prnt(' /* the next line is not executed, but compiled */') + prnt(' %s(0);' % (checkfuncname,)) + prnt('}') + prnt() + + def _loading_struct_or_union(self, tp, prefix, name, module): + if tp.fldnames is None: + return # nothing to do with opaque structs + layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) + cname = ('%s %s' % (prefix, name)).strip() + # + BFunc = self.ffi.typeof("ssize_t(*)(ssize_t)") + function = module.load_function(BFunc, layoutfuncname) + layout = function(0) + if layout < 0: + raise ffiplatform.VerificationError( + "incompatible layout for %s" % cname) + elif layout == 0: + assert not tp.partial + else: + totalsize = function(1) + totalalignment = function(2) + fieldofs = [] + fieldsize = [] + num = 3 + while True: + x = function(num) + if x < 0: break + fieldofs.append(x) + fieldsize.append(function(num+1)) + num += 2 + assert len(fieldofs) == len(fieldsize) == len(tp.fldnames) + tp.fixedlayout = fieldofs, fieldsize, totalsize, totalalignment + + def _loaded_struct_or_union(self, tp): + if tp.fldnames is None: + return # nothing to do with opaque structs + self.ffi._get_cached_btype(tp) # force 'fixedlayout' to be considered + + # ---------- + # 'anonymous' declarations. These are produced for anonymous structs + # or unions; the 'name' is obtained by a typedef. + + def _generate_gen_anonymous_decl(self, tp, name): + self._generate_struct_or_union_decl(tp, '', name) + + def _loading_gen_anonymous(self, tp, name, module): + self._loading_struct_or_union(tp, '', name, module) + + def _loaded_gen_anonymous(self, tp, name, module, **kwds): + self._loaded_struct_or_union(tp) + + # ---------- + # constants, likely declared with '#define' + + def _generate_gen_const(self, is_int, name, tp=None, category='const'): + prnt = self._prnt + funcname = '_cffi_%s_%s' % (category, name) + self.export_symbols.append(funcname) + if is_int: + assert category == 'const' + prnt('int %s(long long *out_value)' % funcname) + prnt('{') + prnt(' *out_value = (long long)(%s);' % (name,)) + prnt(' return (%s) <= 0;' % (name,)) + prnt('}') + else: + assert tp is not None + prnt(tp.get_c_name(' %s(void)' % funcname),) + prnt('{') + if category == 'var': + ampersand = '&' + else: + ampersand = '' + prnt(' return (%s%s);' % (ampersand, name)) + prnt('}') + prnt() + + def _generate_gen_constant_decl(self, tp, name): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + self._generate_gen_const(is_int, name, tp) + + _loading_gen_constant = _loaded_noop + + def _load_constant(self, is_int, tp, name, module): + funcname = '_cffi_const_%s' % name + if is_int: + BFunc = self.ffi.typeof("int(*)(long long*)") + function = module.load_function(BFunc, funcname) + p = self.ffi.new("long long*") + negative = function(p) + value = int(p[0]) + if value < 0 and not negative: + value += (1 << (8*self.ffi.sizeof("long long"))) + else: + BFunc = self.ffi.typeof(tp.get_c_name('(*)(void)')) + function = module.load_function(BFunc, funcname) + value = function() + return value + + def _loaded_gen_constant(self, tp, name, module, library): + is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() + value = self._load_constant(is_int, tp, name, module) + setattr(library, name, value) + + # ---------- + # enums + + def _generate_gen_enum_decl(self, tp, name): + if tp.partial: + for enumerator in tp.enumerators: + self._generate_gen_const(True, enumerator) + return + # + funcname = '_cffi_enum_%s' % name + self.export_symbols.append(funcname) + prnt = self._prnt + prnt('int %s(char *out_error)' % funcname) + prnt('{') + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + prnt(' if (%s != %d) {' % (enumerator, enumvalue)) + prnt(' snprintf(out_error, 255, "in enum %s: ' + '%s has the real value %d, not %d",') + prnt(' "%s", "%s", (int)%s, %d);' % ( + name, enumerator, enumerator, enumvalue)) + prnt(' return -1;') + prnt(' }') + prnt(' return 0;') + prnt('}') + prnt() + + _loading_gen_enum = _loaded_noop + + def _loading_gen_enum(self, tp, name, module): + if tp.partial: + enumvalues = [self._load_constant(True, tp, enumerator, module) + for enumerator in tp.enumerators] + tp.enumvalues = tuple(enumvalues) + tp.partial = False + else: + BFunc = self.ffi.typeof("int(*)(char*)") + funcname = '_cffi_enum_%s' % name + function = module.load_function(BFunc, funcname) + p = self.ffi.new("char[]", 256) + if function(p) < 0: + raise ffiplatform.VerificationError(self.ffi.string(p)) + + def _loaded_gen_enum(self, tp, name, module, library): + for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): + setattr(library, enumerator, enumvalue) + + # ---------- + # macros: for now only for integers + + def _generate_gen_macro_decl(self, tp, name): + assert tp == '...' + self._generate_gen_const(True, name) + + _loading_gen_macro = _loaded_noop + + def _loaded_gen_macro(self, tp, name, module, library): + value = self._load_constant(True, tp, name, module) + setattr(library, name, value) + + # ---------- + # global variables + + def _generate_gen_variable_decl(self, tp, name): + if isinstance(tp, model.ArrayType): + tp_ptr = model.PointerType(tp.item) + self._generate_gen_const(False, name, tp_ptr) + else: + tp_ptr = model.PointerType(tp) + self._generate_gen_const(False, name, tp_ptr, category='var') + + _loading_gen_variable = _loaded_noop + + def _loaded_gen_variable(self, tp, name, module, library): + if isinstance(tp, model.ArrayType): # int a[5] is "constant" in the + # sense that "a=..." is forbidden + tp_ptr = model.PointerType(tp.item) + value = self._load_constant(False, tp_ptr, name, module) + # 'value' is a <cdata 'type *'> which we have to replace with + # a <cdata 'type[N]'> if the N is actually known + if tp.length is not None: + BArray = self.ffi._get_cached_btype(tp) + value = self.ffi.cast(BArray, value) + setattr(library, name, value) + return + # remove ptr=<cdata 'int *'> from the library instance, and replace + # it by a property on the class, which reads/writes into ptr[0]. + funcname = '_cffi_var_%s' % name + BFunc = self.ffi.typeof(tp.get_c_name('*(*)(void)')) + function = module.load_function(BFunc, funcname) + ptr = function() + def getter(library): + return ptr[0] + def setter(library, value): + ptr[0] = value + setattr(library.__class__, name, property(getter, setter)) + +cffimod_header = r''' +#include <stdio.h> +#include <stddef.h> +#include <stdarg.h> +#include <errno.h> +#include <sys/types.h> /* XXX for ssize_t on some platforms */ + +#ifdef _WIN32 +# include <Windows.h> +# define snprintf _snprintf +typedef __int8 int8_t; +typedef __int16 int16_t; +typedef __int32 int32_t; +typedef __int64 int64_t; +typedef unsigned __int8 uint8_t; +typedef unsigned __int16 uint16_t; +typedef unsigned __int32 uint32_t; +typedef unsigned __int64 uint64_t; +typedef SSIZE_T ssize_t; +#else +# include <stdint.h> +#endif +''' diff --git a/cffi/verifier.py b/cffi/verifier.py index 9c79903..17358c2 100644 --- a/cffi/verifier.py +++ b/cffi/verifier.py @@ -1,25 +1,26 @@ -from __future__ import print_function -import sys, os, hashlib, imp, shutil -from . import model, ffiplatform +import sys, os, binascii, imp, shutil from . import __version__ +from . import ffiplatform class Verifier(object): _status = '?' - def __init__(self, ffi, preamble, **kwds): - import _cffi_backend - if ffi._backend is not _cffi_backend: - raise NotImplementedError( - "verify() is only available for the _cffi_backend") - # + def __init__(self, ffi, preamble, force_generic_engine=False, **kwds): self.ffi = ffi self.preamble = preamble + vengine_class = _locate_engine_class(ffi, force_generic_engine) + self._vengine = vengine_class(self) + self._vengine.patch_extension_kwds(kwds) self.kwds = kwds # - m = hashlib.md5('\x00'.join([sys.version[:3], __version__, preamble] + - ffi._cdefsources).encode()) - modulename = '_cffi_%s' % m.hexdigest() + key = '\x00'.join(['1', sys.version[:3], __version__, preamble] + + ffi._cdefsources).encode('utf-8') + k1 = hex(binascii.crc32(key[0::2]) & 0xffffffff) + k1 = k1.lstrip('0x').rstrip('L') + k2 = hex(binascii.crc32(key[1::2]) & 0xffffffff) + k2 = k2.lstrip('0').rstrip('L') + modulename = '_cffi_%s%s%s' % (self._vengine._class_key, k1, k2) suffix = _get_so_suffix() self.sourcefilename = os.path.join(_TMPDIR, modulename + '.c') self.modulefilename = os.path.join(_TMPDIR, modulename + suffix) @@ -60,7 +61,8 @@ class Verifier(object): return self._load_library() def get_module_name(self): - return os.path.basename(self.modulefilename).split('.', 1)[0] + basename = os.path.basename(self.modulefilename)) + return basename.rsplit('.', 1)[0] def get_extension(self): if self._status == 'init': @@ -69,6 +71,9 @@ class Verifier(object): modname = self.get_module_name() return ffiplatform.get_extension(sourcename, modname, **self.kwds) + def generates_python_module(self): + return self._vengine._gen_python_module + # ---------- def _locate_module(self): @@ -80,122 +85,23 @@ class Verifier(object): if f is not None: f.close() self.modulefilename = filename - self._collect_types() + self._vengine.collect_types() self._status = 'module' - def print(self, what=''): - print(what, file=self._f) - - def _gettypenum(self, type): - # a KeyError here is a bug. please report it! :-) - return self._typesdict[type] - - def _collect_types(self): - self._typesdict = {} - self._generate("collecttype") - - def _do_collect_type(self, tp): - if (not isinstance(tp, model.PrimitiveType) and - tp not in self._typesdict): - num = len(self._typesdict) - self._typesdict[tp] = num - def _write_source(self, file=None): must_close = (file is None) if must_close: _ensure_dir(self.sourcefilename) file = open(self.sourcefilename, 'w') - self._f = file + self._vengine._f = file try: - self._write_source_to_f() + self._vengine.write_source_to_f() finally: - del self._f + del self._vengine._f if must_close: file.close() self._status = 'source' - def _write_source_to_f(self): - self._collect_types() - # - # The new module will have a _cffi_setup() function that receives - # objects from the ffi world, and that calls some setup code in - # the module. This setup code is split in several independent - # functions, e.g. one per constant. The functions are "chained" - # by ending in a tail call to each other. - # - # This is further split in two chained lists, depending on if we - # can do it at import-time or if we must wait for _cffi_setup() to - # provide us with the <ctype> objects. This is needed because we - # need the values of the enum constants in order to build the - # <ctype 'enum'> that we may have to pass to _cffi_setup(). - # - # The following two 'chained_list_constants' items contains - # the head of these two chained lists, as a string that gives the - # call to do, if any. - self._chained_list_constants = ['0', '0'] - # - print = self.print - # first paste some standard set of lines that are mostly '#define' - print(cffimod_header) - print() - # then paste the C source given by the user, verbatim. - print(self.preamble) - print() - # - # call generate_cpy_xxx_decl(), for every xxx found from - # ffi._parser._declarations. This generates all the functions. - self._generate("decl") - # - # implement the function _cffi_setup_custom() as calling the - # head of the chained list. - self._generate_setup_custom() - print() - # - # produce the method table, including the entries for the - # generated Python->C function wrappers, which are done - # by generate_cpy_function_method(). - print('static PyMethodDef _cffi_methods[] = {') - self._generate("method") - print(' {"_cffi_setup", _cffi_setup, METH_VARARGS},') - print(' {NULL, NULL} /* Sentinel */') - print('};') - print() - # - # standard init. - modname = self.get_module_name() - if sys.version < '3': - print('PyMODINIT_FUNC') - print('init%s(void)' % modname) - print('{') - print(' PyObject *lib;') - print(' lib = Py_InitModule("%s", _cffi_methods);' % modname) - print(' if (lib == NULL || %s < 0)' % ( - self._chained_list_constants[False],)) - print(' return;') - print(' _cffi_init();') - print('}') - else: - print('static struct PyModuleDef _cffi_module_def = {') - print(' PyModuleDef_HEAD_INIT,') - print(' "%s",' % modname) - print(' NULL,') - print(' -1,') - print(' _cffi_methods,') - print(' NULL, NULL, NULL, NULL') - print('};') - print('') - print('PyMODINIT_FUNC') - print('PyInit_%s(void)' % modname) - print('{') - print(' PyObject *lib;') - print(' lib = PyModule_Create(&_cffi_module_def);') - print(' if (lib == NULL || %s < 0)' % ( - self._chained_list_constants[False],)) - print(' return NULL;') - print(' _cffi_init();') - print(' return lib;') - print('}') - def _compile_module(self): # compile this C source tmpdir = os.path.dirname(self.sourcefilename) @@ -210,654 +116,31 @@ class Verifier(object): self._status = 'module' def _load_library(self): - # XXX review all usages of 'self' here! - # import it as a new extension module - try: - module = imp.load_dynamic(self.get_module_name(), - self.modulefilename) - except ImportError as e: - error = "importing %r: %s" % (self.modulefilename, e) - raise ffiplatform.VerificationError(error) - # - # call loading_cpy_struct() to get the struct layout inferred by - # the C compiler - self._load(module, 'loading') - # - # the C code will need the <ctype> objects. Collect them in - # order in a list. - revmapping = dict([(value, key) - for (key, value) in self._typesdict.items()]) - lst = [revmapping[i] for i in range(len(revmapping))] - lst = list(map(self.ffi._get_cached_btype, lst)) - # - # build the FFILibrary class and instance and call _cffi_setup(). - # this will set up some fields like '_cffi_types', and only then - # it will invoke the chained list of functions that will really - # build (notably) the constant objects, as <cdata> if they are - # pointers, and store them as attributes on the 'library' object. - class FFILibrary(object): - pass - library = FFILibrary() - module._cffi_setup(lst, ffiplatform.VerificationError, library) - # - # finally, call the loaded_cpy_xxx() functions. This will perform - # the final adjustments, like copying the Python->C wrapper - # functions from the module to the 'library' object, and setting - # up the FFILibrary class with properties for the global C variables. - self._load(module, 'loaded', library=library) - return library - - def _generate(self, step_name): - for name, tp in self.ffi._parser._declarations.items(): - kind, realname = name.split(' ', 1) - try: - method = getattr(self, '_generate_cpy_%s_%s' % (kind, - step_name)) - except AttributeError: - raise ffiplatform.VerificationError( - "not implemented in verify(): %r" % name) - method(tp, realname) - - def _load(self, module, step_name, **kwds): - for name, tp in self.ffi._parser._declarations.items(): - kind, realname = name.split(' ', 1) - method = getattr(self, '_%s_cpy_%s' % (step_name, kind)) - method(tp, realname, module, **kwds) - - def _generate_nothing(self, tp, name): - pass - - def _loaded_noop(self, tp, name, module, **kwds): - pass - - # ---------- - - def _convert_funcarg_to_c(self, tp, fromvar, tovar, errcode): - extraarg = '' - if isinstance(tp, model.PrimitiveType): - converter = '_cffi_to_c_%s' % (tp.name.replace(' ', '_'),) - errvalue = '-1' - # - elif isinstance(tp, model.PointerType): - if (isinstance(tp.totype, model.PrimitiveType) and - tp.totype.name == 'char'): - converter = '_cffi_to_c_char_p' - else: - converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') - extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) - errvalue = 'NULL' - # - elif isinstance(tp, (model.StructOrUnion, model.EnumType)): - # a struct (not a struct pointer) as a function argument - self.print(' if (_cffi_to_c((char *)&%s, _cffi_type(%d), %s) < 0)' - % (tovar, self._gettypenum(tp), fromvar)) - self.print(' %s;' % errcode) - return - # - elif isinstance(tp, model.FunctionPtrType): - converter = '(%s)_cffi_to_c_pointer' % tp.get_c_name('') - extraarg = ', _cffi_type(%d)' % self._gettypenum(tp) - errvalue = 'NULL' - # - else: - raise NotImplementedError(tp) - # - self.print(' %s = %s(%s%s);' % (tovar, converter, fromvar, extraarg)) - self.print(' if (%s == (%s)%s && PyErr_Occurred())' % ( - tovar, tp.get_c_name(''), errvalue)) - self.print(' %s;' % errcode) - - def _convert_expr_from_c(self, tp, var): - if isinstance(tp, model.PrimitiveType): - return '_cffi_from_c_%s(%s)' % (tp.name.replace(' ', '_'), var) - elif isinstance(tp, (model.PointerType, model.FunctionPtrType)): - return '_cffi_from_c_pointer((char *)%s, _cffi_type(%d))' % ( - var, self._gettypenum(tp)) - elif isinstance(tp, model.ArrayType): - return '_cffi_from_c_deref((char *)%s, _cffi_type(%d))' % ( - var, self._gettypenum(tp)) - elif isinstance(tp, model.StructType): - return '_cffi_from_c_struct((char *)&%s, _cffi_type(%d))' % ( - var, self._gettypenum(tp)) - elif isinstance(tp, model.EnumType): - return '_cffi_from_c_deref((char *)&%s, _cffi_type(%d))' % ( - var, self._gettypenum(tp)) - else: - raise NotImplementedError(tp) - - # ---------- - # typedefs: generates no code so far - - _generate_cpy_typedef_collecttype = _generate_nothing - _generate_cpy_typedef_decl = _generate_nothing - _generate_cpy_typedef_method = _generate_nothing - _loading_cpy_typedef = _loaded_noop - _loaded_cpy_typedef = _loaded_noop - - # ---------- - # function declarations - - def _generate_cpy_function_collecttype(self, tp, name): - assert isinstance(tp, model.FunctionPtrType) - if tp.ellipsis: - self._do_collect_type(tp) - else: - for type in tp.args: - self._do_collect_type(type) - self._do_collect_type(tp.result) - - def _generate_cpy_function_decl(self, tp, name): - assert isinstance(tp, model.FunctionPtrType) - if tp.ellipsis: - # cannot support vararg functions better than this: check for its - # exact type (including the fixed arguments), and build it as a - # constant function pointer (no CPython wrapper) - self._generate_cpy_const(False, name, tp) - return - print = self.print - numargs = len(tp.args) - if numargs == 0: - argname = 'no_arg' - elif numargs == 1: - argname = 'arg0' - else: - argname = 'args' - print('static PyObject *') - print('_cffi_f_%s(PyObject *self, PyObject *%s)' % (name, argname)) - print('{') - # - for i, type in enumerate(tp.args): - print(' %s;' % type.get_c_name(' x%d' % i)) - if not isinstance(tp.result, model.VoidType): - result_code = 'result = ' - print(' %s;' % tp.result.get_c_name(' result')) - else: - result_code = '' - # - if len(tp.args) > 1: - rng = range(len(tp.args)) - for i in rng: - print(' PyObject *arg%d;' % i) - print() - print(' if (!PyArg_ParseTuple(args, "%s:%s", %s))' % ( - 'O' * numargs, name, ', '.join(['&arg%d' % i for i in rng]))) - print(' return NULL;') - print() - # - for i, type in enumerate(tp.args): - self._convert_funcarg_to_c(type, 'arg%d' % i, 'x%d' % i, - 'return NULL') - print() - # - print(' _cffi_restore_errno();') - print(' { %s%s(%s); }' % ( - result_code, name, - ', '.join(['x%d' % i for i in range(len(tp.args))]))) - print(' _cffi_save_errno();') - print() - # - if result_code: - print(' return %s;' % - self._convert_expr_from_c(tp.result, 'result')) - else: - print(' Py_INCREF(Py_None);') - print(' return Py_None;') - print('}') - print() - - def _generate_cpy_function_method(self, tp, name): - if tp.ellipsis: - return - numargs = len(tp.args) - if numargs == 0: - meth = 'METH_NOARGS' - elif numargs == 1: - meth = 'METH_O' - else: - meth = 'METH_VARARGS' - self.print(' {"%s", _cffi_f_%s, %s},' % (name, name, meth)) - - _loading_cpy_function = _loaded_noop - - def _loaded_cpy_function(self, tp, name, module, library): - if tp.ellipsis: - return - setattr(library, name, getattr(module, name)) - - # ---------- - # named structs - - _generate_cpy_struct_collecttype = _generate_nothing - - def _generate_cpy_struct_decl(self, tp, name): - assert name == tp.name - self._generate_struct_or_union_decl(tp, 'struct', name) - - def _generate_cpy_struct_method(self, tp, name): - self._generate_struct_or_union_method(tp, 'struct', name) - - def _loading_cpy_struct(self, tp, name, module): - self._loading_struct_or_union(tp, 'struct', name, module) + return self._vengine.load_library() - def _loaded_cpy_struct(self, tp, name, module, **kwds): - self._loaded_struct_or_union(tp) - - def _generate_struct_or_union_decl(self, tp, prefix, name): - if tp.fldnames is None: - return # nothing to do with opaque structs - checkfuncname = '_cffi_check_%s_%s' % (prefix, name) - layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) - cname = ('%s %s' % (prefix, name)).strip() - # - print = self.print - print('static void %s(%s *p)' % (checkfuncname, cname)) - print('{') - print(' /* only to generate compile-time warnings or errors */') - for i in range(len(tp.fldnames)): - fname = tp.fldnames[i] - ftype = tp.fldtypes[i] - if (isinstance(ftype, model.PrimitiveType) - and ftype.is_integer_type()): - # accept all integers, but complain on float or double - print(' (void)((p->%s) << 1);' % fname) - else: - # only accept exactly the type declared. Note the parentheses - # around the '*tmp' below. In most cases they are not needed - # but don't hurt --- except test_struct_array_field. - print(' { %s = &p->%s; (void)tmp; }' % ( - ftype.get_c_name('(*tmp)'), fname)) - print('}') - print('static PyObject *') - print('%s(PyObject *self, PyObject *noarg)' % (layoutfuncname,)) - print('{') - print(' struct _cffi_aligncheck { char x; %s y; };' % cname) - if tp.partial: - print(' static Py_ssize_t nums[] = {') - print(' sizeof(%s),' % cname) - print(' offsetof(struct _cffi_aligncheck, y),') - for fname in tp.fldnames: - print(' offsetof(%s, %s),' % (cname, fname)) - print(' sizeof(((%s *)0)->%s),' % (cname, fname)) - print(' -1') - print(' };') - print(' return _cffi_get_struct_layout(nums);') - else: - ffi = self.ffi - BStruct = ffi._get_cached_btype(tp) - conditions = [ - 'sizeof(%s) != %d' % (cname, ffi.sizeof(BStruct)), - 'offsetof(struct _cffi_aligncheck, y) != %d' % ( - ffi.alignof(BStruct),)] - for fname, ftype in zip(tp.fldnames, tp.fldtypes): - BField = ffi._get_cached_btype(ftype) - conditions += [ - 'offsetof(%s, %s) != %d' % ( - cname, fname, ffi.offsetof(BStruct, fname)), - 'sizeof(((%s *)0)->%s) != %d' % ( - cname, fname, ffi.sizeof(BField))] - print(' if (%s ||' % conditions[0]) - for i in range(1, len(conditions)-1): - print(' %s ||' % conditions[i]) - print(' %s) {' % conditions[-1]) - print(' Py_INCREF(Py_False);') - print(' return Py_False;') - print(' }') - print(' else {') - print(' Py_INCREF(Py_True);') - print(' return Py_True;') - print(' }') - print(' /* the next line is not executed, but compiled */') - print(' %s(0);' % (checkfuncname,)) - print('}') - print() - - def _generate_struct_or_union_method(self, tp, prefix, name): - if tp.fldnames is None: - return # nothing to do with opaque structs - layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) - self.print(' {"%s", %s, METH_NOARGS},' % (layoutfuncname, - layoutfuncname)) - - def _loading_struct_or_union(self, tp, prefix, name, module): - if tp.fldnames is None: - return # nothing to do with opaque structs - layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) - cname = ('%s %s' % (prefix, name)).strip() - # - function = getattr(module, layoutfuncname) - layout = function() - if layout is False: - raise ffiplatform.VerificationError( - "incompatible layout for %s" % cname) - elif layout is True: - assert not tp.partial - else: - totalsize = layout[0] - totalalignment = layout[1] - fieldofs = layout[2::2] - fieldsize = layout[3::2] - assert len(fieldofs) == len(fieldsize) == len(tp.fldnames) - tp.fixedlayout = fieldofs, fieldsize, totalsize, totalalignment - - def _loaded_struct_or_union(self, tp): - if tp.fldnames is None: - return # nothing to do with opaque structs - self.ffi._get_cached_btype(tp) # force 'fixedlayout' to be considered - - # ---------- - # 'anonymous' declarations. These are produced for anonymous structs - # or unions; the 'name' is obtained by a typedef. - - _generate_cpy_anonymous_collecttype = _generate_nothing - - def _generate_cpy_anonymous_decl(self, tp, name): - self._generate_struct_or_union_decl(tp, '', name) - - def _generate_cpy_anonymous_method(self, tp, name): - self._generate_struct_or_union_method(tp, '', name) - - def _loading_cpy_anonymous(self, tp, name, module): - self._loading_struct_or_union(tp, '', name, module) - - def _loaded_cpy_anonymous(self, tp, name, module, **kwds): - self._loaded_struct_or_union(tp) - - # ---------- - # constants, likely declared with '#define' - - def _generate_cpy_const(self, is_int, name, tp=None, category='const', - vartp=None, delayed=True): - print = self.print - funcname = '_cffi_%s_%s' % (category, name) - print('static int %s(PyObject *lib)' % funcname) - print('{') - print(' PyObject *o;') - print(' int res;') - if not is_int: - print(' %s;' % (vartp or tp).get_c_name(' i')) - else: - assert category == 'const' - # - if not is_int: - if category == 'var': - realexpr = '&' + name - else: - realexpr = name - print(' i = (%s);' % (realexpr,)) - print(' o = %s;' % (self._convert_expr_from_c(tp, 'i'),)) - assert delayed - else: - print(' if (LONG_MIN <= (%s) && (%s) <= LONG_MAX)' % (name, name)) - print(' o = PyInt_FromLong((long)(%s));' % (name,)) - print(' else if ((%s) <= 0)' % (name,)) - print(' o = PyLong_FromLongLong((long long)(%s));' % (name,)) - print(' else') - print(' o = PyLong_FromUnsignedLongLong(' - '(unsigned long long)(%s));' % (name,)) - print(' if (o == NULL)') - print(' return -1;') - print(' res = PyObject_SetAttrString(lib, "%s", o);' % name) - print(' Py_DECREF(o);') - print(' if (res < 0)') - print(' return -1;') - print(' return %s;' % self._chained_list_constants[delayed]) - self._chained_list_constants[delayed] = funcname + '(lib)' - print('}') - print() - - def _generate_cpy_constant_collecttype(self, tp, name): - is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() - if not is_int: - self._do_collect_type(tp) - - def _generate_cpy_constant_decl(self, tp, name): - is_int = isinstance(tp, model.PrimitiveType) and tp.is_integer_type() - self._generate_cpy_const(is_int, name, tp) - - _generate_cpy_constant_method = _generate_nothing - _loading_cpy_constant = _loaded_noop - _loaded_cpy_constant = _loaded_noop - - # ---------- - # enums - - def _generate_cpy_enum_decl(self, tp, name): - if tp.partial: - for enumerator in tp.enumerators: - self._generate_cpy_const(True, enumerator, delayed=False) - return - # - funcname = '_cffi_enum_%s' % name - print = self.print - print('static int %s(PyObject *lib)' % funcname) - print('{') - for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): - print(' if (%s != %d) {' % (enumerator, enumvalue)) - print(' PyErr_Format(_cffi_VerificationError,') - print(' "in enum %s: %s has the real value %d, ' - 'not %d",') - print(' "%s", "%s", (int)%s, %d);' % ( - name, enumerator, enumerator, enumvalue)) - print(' return -1;') - print(' }') - print(' return %s;' % self._chained_list_constants[True]) - self._chained_list_constants[True] = funcname + '(lib)' - print('}') - print() - - _generate_cpy_enum_collecttype = _generate_nothing - _generate_cpy_enum_method = _generate_nothing - _loading_cpy_enum = _loaded_noop - - def _loading_cpy_enum(self, tp, name, module): - if tp.partial: - enumvalues = [getattr(module, enumerator) - for enumerator in tp.enumerators] - tp.enumvalues = tuple(enumvalues) - tp.partial = False - - def _loaded_cpy_enum(self, tp, name, module, library): - for enumerator, enumvalue in zip(tp.enumerators, tp.enumvalues): - setattr(library, enumerator, enumvalue) - - # ---------- - # macros: for now only for integers - - def _generate_cpy_macro_decl(self, tp, name): - assert tp == '...' - self._generate_cpy_const(True, name) - - _generate_cpy_macro_collecttype = _generate_nothing - _generate_cpy_macro_method = _generate_nothing - _loading_cpy_macro = _loaded_noop - _loaded_cpy_macro = _loaded_noop - - # ---------- - # global variables +# ____________________________________________________________ - def _generate_cpy_variable_collecttype(self, tp, name): - if isinstance(tp, model.ArrayType): - self._do_collect_type(tp) - else: - tp_ptr = model.PointerType(tp) - self._do_collect_type(tp_ptr) +_FORCE_GENERIC_ENGINE = False # for tests - def _generate_cpy_variable_decl(self, tp, name): - if isinstance(tp, model.ArrayType): - tp_ptr = model.PointerType(tp.item) - self._generate_cpy_const(False, name, tp, vartp=tp_ptr) +def _locate_engine_class(ffi, force_generic_engine): + if _FORCE_GENERIC_ENGINE: + force_generic_engine = True + if not force_generic_engine: + if '__pypy__' in sys.builtin_module_names: + force_generic_engine = True else: - tp_ptr = model.PointerType(tp) - self._generate_cpy_const(False, name, tp_ptr, category='var') - - _generate_cpy_variable_method = _generate_nothing - _loading_cpy_variable = _loaded_noop - - def _loaded_cpy_variable(self, tp, name, module, library): - if isinstance(tp, model.ArrayType): # int a[5] is "constant" in the - return # sense that "a=..." is forbidden - # remove ptr=<cdata 'int *'> from the library instance, and replace - # it by a property on the class, which reads/writes into ptr[0]. - ptr = getattr(library, name) - delattr(library, name) - def getter(library): - return ptr[0] - def setter(library, value): - ptr[0] = value - setattr(library.__class__, name, property(getter, setter)) - - # ---------- - - def _generate_setup_custom(self): - print = self.print - print('static PyObject *_cffi_setup_custom(PyObject *lib)') - print('{') - print(' if (%s < 0)' % self._chained_list_constants[True]) - print(' return NULL;') - print(' Py_INCREF(Py_None);') - print(' return Py_None;') - print('}') - -cffimod_header = r''' -#include <Python.h> -#include <stddef.h> - -#if PY_MAJOR_VERSION < 3 -# define PyCapsule_CheckExact(capsule) (PyCObject_Check(capsule)) -# define PyCapsule_GetPointer(capsule, name) \ - (PyCObject_AsVoidPtr(capsule)) -#endif - -#if PY_MAJOR_VERSION >= 3 -# define PyInt_FromLong PyLong_FromLong -# define PyInt_AsLong PyLong_AsLong -#endif - -#define _cffi_from_c_double PyFloat_FromDouble -#define _cffi_from_c_float PyFloat_FromDouble -#define _cffi_from_c_signed_char PyInt_FromLong -#define _cffi_from_c_short PyInt_FromLong -#define _cffi_from_c_int PyInt_FromLong -#define _cffi_from_c_long PyInt_FromLong -#define _cffi_from_c_unsigned_char PyInt_FromLong -#define _cffi_from_c_unsigned_short PyInt_FromLong -#define _cffi_from_c_unsigned_long PyLong_FromUnsignedLong -#define _cffi_from_c_unsigned_long_long PyLong_FromUnsignedLongLong - -#if SIZEOF_INT < SIZEOF_LONG -# define _cffi_from_c_unsigned_int PyInt_FromLong -#else -# define _cffi_from_c_unsigned_int PyLong_FromUnsignedLong -#endif - -#if SIZEOF_LONG < SIZEOF_LONG_LONG -# define _cffi_from_c_long_long PyLong_FromLongLong -#else -# define _cffi_from_c_long_long PyInt_FromLong -#endif - -#define _cffi_to_c_long PyInt_AsLong -#define _cffi_to_c_double PyFloat_AsDouble -#define _cffi_to_c_float PyFloat_AsDouble - -#define _cffi_to_c_char_p \ - ((char *(*)(PyObject *))_cffi_exports[0]) -#define _cffi_to_c_signed_char \ - ((signed char(*)(PyObject *))_cffi_exports[1]) -#define _cffi_to_c_unsigned_char \ - ((unsigned char(*)(PyObject *))_cffi_exports[2]) -#define _cffi_to_c_short \ - ((short(*)(PyObject *))_cffi_exports[3]) -#define _cffi_to_c_unsigned_short \ - ((unsigned short(*)(PyObject *))_cffi_exports[4]) - -#if SIZEOF_INT < SIZEOF_LONG -# define _cffi_to_c_int \ - ((int(*)(PyObject *))_cffi_exports[5]) -# define _cffi_to_c_unsigned_int \ - ((unsigned int(*)(PyObject *))_cffi_exports[6]) -#else -# define _cffi_to_c_int _cffi_to_c_long -# define _cffi_to_c_unsigned_int _cffi_to_c_unsigned_long -#endif - -#define _cffi_to_c_unsigned_long \ - ((unsigned long(*)(PyObject *))_cffi_exports[7]) -#define _cffi_to_c_unsigned_long_long \ - ((unsigned long long(*)(PyObject *))_cffi_exports[8]) -#define _cffi_to_c_char \ - ((char(*)(PyObject *))_cffi_exports[9]) -#define _cffi_from_c_pointer \ - ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[10]) -#define _cffi_to_c_pointer \ - ((char *(*)(PyObject *, CTypeDescrObject *))_cffi_exports[11]) -#define _cffi_get_struct_layout \ - ((PyObject *(*)(Py_ssize_t[]))_cffi_exports[12]) -#define _cffi_restore_errno \ - ((void(*)(void))_cffi_exports[13]) -#define _cffi_save_errno \ - ((void(*)(void))_cffi_exports[14]) -#define _cffi_from_c_char \ - ((PyObject *(*)(char))_cffi_exports[15]) -#define _cffi_from_c_deref \ - ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[16]) -#define _cffi_to_c \ - ((int(*)(char *, CTypeDescrObject *, PyObject *))_cffi_exports[17]) -#define _cffi_from_c_struct \ - ((PyObject *(*)(char *, CTypeDescrObject *))_cffi_exports[18]) -#define _cffi_to_c_wchar_t \ - ((wchar_t(*)(PyObject *))_cffi_exports[19]) -#define _cffi_from_c_wchar_t \ - ((PyObject *(*)(wchar_t))_cffi_exports[20]) -#define _CFFI_NUM_EXPORTS 21 - -#if SIZEOF_LONG < SIZEOF_LONG_LONG -# define _cffi_to_c_long_long PyLong_AsLongLong -#else -# define _cffi_to_c_long_long _cffi_to_c_long -#endif - -typedef struct _ctypedescr CTypeDescrObject; - -static void *_cffi_exports[_CFFI_NUM_EXPORTS]; -static PyObject *_cffi_types, *_cffi_VerificationError; - -static PyObject *_cffi_setup_custom(PyObject *lib); /* forward */ - -static PyObject *_cffi_setup(PyObject *self, PyObject *args) -{ - PyObject *library; - if (!PyArg_ParseTuple(args, "OOO", &_cffi_types, &_cffi_VerificationError, - &library)) - return NULL; - Py_INCREF(_cffi_types); - Py_INCREF(_cffi_VerificationError); - return _cffi_setup_custom(library); -} - -static void _cffi_init(void) -{ - PyObject *module = PyImport_ImportModule("_cffi_backend"); - PyObject *c_api_object; - - if (module == NULL) - return; - - c_api_object = PyObject_GetAttrString(module, "_C_API"); - if (c_api_object == NULL) - return; - if (!PyCapsule_CheckExact(c_api_object)) { - PyErr_SetNone(PyExc_ImportError); - return; - } - memcpy(_cffi_exports, PyCapsule_GetPointer(c_api_object, "cffi"), - _CFFI_NUM_EXPORTS * sizeof(void *)); -} - -#define _cffi_type(num) ((CTypeDescrObject *)PyList_GET_ITEM(_cffi_types, num)) - -/**********/ -''' + try: + import _cffi_backend + except ImportError: + _cffi_backend = '?' + if ffi._backend is not _cffi_backend: + force_generic_engine = True + if force_generic_engine: + from . import vengine_gen + return vengine_gen.VGenericEngine + else: + from . import vengine_cpy + return vengine_cpy.VCPythonEngine # ____________________________________________________________ diff --git a/demo/_curses.py b/demo/_curses.py index 6309d6a..8747582 100644 --- a/demo/_curses.py +++ b/demo/_curses.py @@ -120,9 +120,9 @@ def _setup(): globals().update(lib.__dict__) for key in range(KEY_MIN, KEY_MAX): key_n = keyname(key) - if key_n == ffi.NULL or str(key_n) == "UNKNOWN KEY": + if key_n == ffi.NULL or ffi.string(key_n) == "UNKNOWN KEY": continue - key_n = str(key_n).replace('(', '').replace(')', '') + key_n = ffi.string(key_n).replace('(', '').replace(')', '') globals()[key_n] = key _setup() diff --git a/demo/bsdopendirtype.py b/demo/bsdopendirtype.py index 17a2fdf..c105843 100644 --- a/demo/bsdopendirtype.py +++ b/demo/bsdopendirtype.py @@ -49,7 +49,7 @@ def opendir(dir): if ffi.errno != 0: raise _posix_error() return - name = str(dirent.d_name) + name = ffi.string(dirent.d_name) if name == '.' or name == '..': continue name = dirname + name diff --git a/demo/gmp.py b/demo/gmp.py index 692ac37..f92d2a6 100644 --- a/demo/gmp.py +++ b/demo/gmp.py @@ -27,4 +27,4 @@ lib.mpz_init_set_str(b, sys.argv[2], 10) # Assume decimal integers lib.mpz_add(a, a, b) # a=a+b s = lib.mpz_get_str(ffi.NULL, 10, a) -print str(s) +print ffi.string(s) diff --git a/demo/pwuid.py b/demo/pwuid.py index 5ed704e..c4d7201 100644 --- a/demo/pwuid.py +++ b/demo/pwuid.py @@ -11,4 +11,4 @@ C = ffi.verify(""" // passed to the real C compiler #include <sys/types.h> #include <pwd.h> """) -print str(C.getpwuid(0).pw_name) +print ffi.string(C.getpwuid(0).pw_name) diff --git a/demo/readdir.py b/demo/readdir.py index 15bc93d..e63c85a 100644 --- a/demo/readdir.py +++ b/demo/readdir.py @@ -48,7 +48,7 @@ def walk(basefd, path): break if result[0] == ffi.NULL: break - name = str(dirent.d_name) + name = ffi.string(dirent.d_name) print '%3d %s' % (dirent.d_type, name) if dirent.d_type == 4 and name != '.' and name != '..': walk(dirfd, name) diff --git a/demo/readdir2.py b/demo/readdir2.py index f4646d8..03a23d8 100644 --- a/demo/readdir2.py +++ b/demo/readdir2.py @@ -55,7 +55,7 @@ def walk(basefd, path): break if result[0] == ffi.NULL: break - name = str(dirent.d_name) + name = ffi.string(dirent.d_name) print '%3d %s' % (dirent.d_type, name) if dirent.d_type == ffi.C.DT_DIR and name != '.' and name != '..': walk(dirfd, name) diff --git a/demo/readdir_ctypes.py b/demo/readdir_ctypes.py new file mode 100644 index 0000000..4fd1d17 --- /dev/null +++ b/demo/readdir_ctypes.py @@ -0,0 +1,69 @@ +# A Linux-only demo +# +# For comparison purposes, this is a ctypes version of readdir.py. +import sys +import ctypes + +if not sys.platform.startswith('linux'): + raise Exception("Linux-only demo") + + +DIR_p = ctypes.c_void_p +ino_t = ctypes.c_long +off_t = ctypes.c_long + +class DIRENT(ctypes.Structure): + _fields_ = [ + ('d_ino', ino_t), # inode number + ('d_off', off_t), # offset to the next dirent + ('d_reclen', ctypes.c_ushort), # length of this record + ('d_type', ctypes.c_ubyte), # type of file; not supported + # by all file system types + ('d_name', ctypes.c_char * 256), # filename + ] +DIRENT_p = ctypes.POINTER(DIRENT) +DIRENT_pp = ctypes.POINTER(DIRENT_p) + +C = ctypes.CDLL(None) + +readdir_r = C.readdir_r +readdir_r.argtypes = [DIR_p, DIRENT_p, DIRENT_pp] +readdir_r.restype = ctypes.c_int + +openat = C.openat +openat.argtypes = [ctypes.c_int, ctypes.c_char_p, ctypes.c_int] +openat.restype = ctypes.c_int + +fdopendir = C.fdopendir +fdopendir.argtypes = [ctypes.c_int] +fdopendir.restype = DIR_p + +closedir = C.closedir +closedir.argtypes = [DIR_p] +closedir.restype = ctypes.c_int + + +def walk(basefd, path): + print '{', path + dirfd = openat(basefd, path, 0) + if dirfd < 0: + # error in openat() + return + dir = fdopendir(dirfd) + dirent = DIRENT() + result = DIRENT_p() + while True: + if readdir_r(dir, dirent, result): + # error in readdir_r() + break + if not result: + break + name = dirent.d_name + print '%3d %s' % (dirent.d_type, name) + if dirent.d_type == 4 and name != '.' and name != '..': + walk(dirfd, name) + closedir(dir) + print '}' + + +walk(-1, "/tmp") diff --git a/doc/source/conf.py b/doc/source/conf.py index 4d9ec4b..e25ac41 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -45,9 +45,9 @@ copyright = u'2012, Armin Rigo, Maciej Fijalkowski' # built documents. # # The short X.Y version. -version = '0.2.1' +version = '0.3' # The full version, including alpha/beta/rc tags. -release = '0.2.1' +release = '0.3' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. diff --git a/doc/source/index.rst b/doc/source/index.rst index efdb0af..9a47603 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -87,9 +87,9 @@ Download and Installation: * https://bitbucket.org/cffi/cffi/downloads - - https://bitbucket.org/cffi/cffi/get/release-0.2.1.tar.bz2 has - a MD5 of c4de415fda3e14209c8a997671a12b83 and SHA of - 790f8bd96713713bbc3030eb698a85cdf43e44ab + - https://bitbucket.org/cffi/cffi/get/release-0.3.tar.bz2 + has a MD5 of xxx and SHA of + xxx - or get it via ``hg clone https://bitbucket.org/cffi/cffi`` @@ -99,8 +99,7 @@ Download and Installation: * or you can directly import and use ``cffi``, but if you don't compile the ``_cffi_backend`` extension module, it will fall back - to using internally ``ctypes`` (much slower and does not support - ``verify()``; we recommend not to use it). + to using internally ``ctypes`` (much slower; we recommend not to use it). * running the tests: ``py.test c/ testing/ -x`` (if you didn't install cffi yet, you may need ``python setup_base.py build`` @@ -194,7 +193,7 @@ Real example (API level) #include <pwd.h> """) p = C.getpwuid(0) - assert str(p.pw_name) == 'root' + assert ffi.string(p.pw_name) == 'root' Note that the above example works independently of the exact layout of ``struct passwd``. It requires a C compiler the first time you run it, @@ -307,7 +306,8 @@ Cleaning up the __pycache__ directory During development, every time you change the C sources that you pass to ``cdef()`` or ``verify()``, then the latter will create a new module -file name, based on the MD5 hash of these strings. This creates more +file name, based on two CRC32 hashes computed from these strings. +This creates more and more files in the ``__pycache__`` directory. It is recommended that you clean it up from time to time. A nice way to do that is to add, in your test suite, a call to ``cffi.verifier.cleanup_tmpdir()``. @@ -345,7 +345,7 @@ can assume to exist are the standard types: * char, short, int, long, long long (both signed and unsigned) -* float, double +* float, double, long double * intN_t, uintN_t (for N=8,16,32,64), intptr_t, uintptr_t, ptrdiff_t, size_t, ssize_t @@ -539,7 +539,20 @@ somewhere else, then make sure you also keep the object alive for as long as needed. (This also applies if you immediately cast the returned pointer to a pointer of a different type: only the original object has ownership, so you must keep it alive. As soon as you forget it, then -the casted pointer will point to garbage.) +the casted pointer will point to garbage.) Example:: + + global_weakkeydict = weakref.WeakKeyDictionary() + + s1 = ffi.new("struct foo *") + fld1 = ffi.new("struct bar *") + fld2 = ffi.new("struct bar *") + s1.thefield1 = fld1 + s1.thefield2 = fld2 + # here the 'fld1' and 'fld2' object must not go away, + # otherwise 's1.thefield1/2' will point to garbage! + global_weakkeydict[s1] = (fld1, fld2) + # now 's1' keeps alive 'fld1' and 'fld2'. When 's1' goes + # away, then the weak dictionary entry will be removed. The cdata objects support mostly the same operations as in C: you can read or write from pointers, arrays and structures. Dereferencing a @@ -615,11 +628,11 @@ which case a terminating null character is appended implicitly:: >>> x[5] # the last item in the array '\x00' >>> x[0] = 'H' # change the first item - >>> str(x) # interpret 'x' as a regular null-terminated string + >>> ffi.string(x) # interpret 'x' as a regular null-terminated string 'Hello' Similarly, arrays of wchar_t can be initialized from a unicode string, -and calling ``unicode()`` on the cdata object returns the current unicode +and calling ``ffi.string()`` on the cdata object returns the current unicode string stored in the wchar_t array (encoding and decoding surrogates as needed if necessary). @@ -658,6 +671,7 @@ Imagine we have something like this: ffi.cdef(""" int main_like(int argv, char *argv[]); """) + lib = ffi.dlopen("some_library.so") Now, everything is simple, except, how do we create the ``char**`` argument here? @@ -665,20 +679,34 @@ The first idea: .. code-block:: python - argv = ffi.new("char *[]", ["arg0", "arg1"]) + lib.main_like(2, ["arg0", "arg1"]) + +does not work, because the initializer receives two Python ``str`` objects +where it was expecting ``<cdata 'char *'>`` objects. You need to use +``ffi.new()`` explicitly to make these objects: + +.. code-block:: python + + lib.main_like(2, [ffi.new("char[]", "arg0"), + ffi.new("char[]", "arg1")]) + +Note that the two ``<cdata 'char[]'>`` objects are kept alive for the +duration of the call: they are only freed when the list itself is freed, +and the list is only freed when the call returns. -Does not work, because the initializer receives python ``str`` instead of -``char*``. Now, the following would almost work: +If you want instead to build an "argv" variable that you want to reuse, +then more care is needed: .. code-block:: python + # DOES NOT WORK! argv = ffi.new("char *[]", [ffi.new("char[]", "arg0"), ffi.new("char[]", "arg1")]) -However, the two ``char[]`` objects will not be automatically kept alive. -To keep them alive, one solution is to make sure that the list is stored -somewhere for long enough. -For example: +In the above example, the inner "arg0" string is deallocated as soon +as "argv" is built. You have to make sure that you keep a reference +to the inner "char[]" objects, either directly or by keeping the list +alive like this: .. code-block:: python @@ -686,7 +714,12 @@ For example: ffi.new("char[]", "arg1")] argv = ffi.new("char *[]", argv_keepalive) -will work. + +.. versionchanged:: 0.3 + In older versions, passing a list as the ``char *[]`` argument did + not work; you needed to make an ``argv_keepalive`` and an ``argv`` + in all cases. + Function calls -------------- @@ -714,11 +747,16 @@ string (but don't pass a normal Python string to functions that take a assert C.strlen("hello") == 5 -So far passing unicode strings as ``wchar_t *`` arguments is not -implemented. You need to write e.g.:: - - >>> C.wcslen(ffi.new("wchar_t[]", u"foo")) - 3 +You can also pass unicode strings as ``wchar_t *`` arguments. Note that +in general, there is no difference between C argument declarations that +use ``type *`` or ``type[]``. For example, ``int *`` is fully +equivalent to ``int[]`` or ``int[5]``. So you can pass an ``int *`` as +a list of integers:: + + ffi.cdef(""" + void do_something_with_array(int *array); + """) + lib.do_something_with_array([1, 2, 3, 4, 5]) CFFI supports passing and returning structs to functions and callbacks. Example (sketch):: @@ -813,10 +851,31 @@ reads and writes of the property ``ffi.errno``. On Windows we also save and restore the ``GetLastError()`` value, but to access it you need to declare and call the ``GetLastError()`` function as usual. +``ffi.string(cdata, [maxlen])``: return a Python string (or unicode +string) from the 'cdata'. *New in version 0.3.* + +.. "versionadded:: 0.3" --- inlined in the previous paragraph + +- If 'cdata' is a pointer or array of characters or bytes, returns the + null-terminated string. The returned string extends until the first + null character, or at most 'maxlen' characters. If 'cdata' is an + array then 'maxlen' defaults to its length. + +- If 'cdata' is a pointer or array of wchar_t, returns a unicode string + following the same rules. + +- If 'cdata' is a single character or byte or a wchar_t, returns it as a + string or unicode string. (Note that in some situation a single + wchar_t may require a Python unicode string of length 2.) + +- If 'cdata' is an enum, returns the value of the enumerator as a + string, or ``#NUMBER`` if the value is out of range. + ``ffi.buffer(pointer, [size])``: return a read-write buffer object that references the raw C data pointed to by the given 'cdata', of 'size' bytes. The 'cdata' must be a pointer or an array. To get a copy of it -in a regular string, call str() on the result. If unspecified, the +in a regular string, use ``ffi.buffer(..)[:]``. To change the content, +use ``ffi.buffer(..)[:] = new_string``. If unspecified, the default size of the buffer is ``sizeof(*pointer)`` or the whole size of the array. Getting a buffer is useful because you can read from it without an extra copy, or write into it to change the original value; @@ -856,6 +915,37 @@ like ``"*"`` or ``"[5]"``. For example ``ffi.getcname(ffi.typeof(x), "*")`` returns the string representation of the C type "pointer to the same type than x". +``ffi.gc(cdata, destructor)``: return a new cdata object that points to the +same data. Later, when this new cdata object is garbage-collected, +``destructor(old_cdata_object)`` will be called. Example of usage: +``ptr = ffi.gc(lib.malloc(42), lib.free)``. *New in version 0.3* (together +with the fact that any cdata object can be weakly referenced). + +.. "versionadded:: 0.3" --- inlined in the previous paragraph + + +Unimplemented features +---------------------- + +All of the ANSI C declarations should be supported, and some of C99. +Here are the major known missing features that are GCC or MSVC +extensions: + +* Any ``__attribute__`` or ``#pragma pack(n)`` + +* Additional types: complex numbers, special-size floating and + fixed point types, vector types, etc. (must be declared with + ``typedef struct { ...; } typename;`` and cannot be accessed directly) + +* Unnamed struct/union fields within struct/union + +* Thread-local variables (access them via getter/setter functions) + +* Variable-length structures, i.e. whose last field is a variable-length + array (work around like in C, e.g. by declaring it as an array of + length 0, allocating a ``char[]`` of the correct size, and casting + it to a struct pointer) + Reference: conversions ---------------------- @@ -874,10 +964,10 @@ allowed. | | (but not a float!). | on the type | | | | Must be within range. | | | +---------------+------------------------+------------------+----------------+ -| ``char`` | a string of length 1 | a string of | str(), int() | +| ``char`` | a string of length 1 | a string of | int() | | | or another <cdata char>| length 1 | | +---------------+------------------------+------------------+----------------+ -| ``wchar_t`` | a unicode of length 1 | a unicode of | unicode(), | +| ``wchar_t`` | a unicode of length 1 | a unicode of | | | | (or maybe 2 if | length 1 | int() | | | surrogates) or | (or maybe 2 if | | | | another <cdata wchar_t>| surrogates) | | @@ -885,33 +975,27 @@ allowed. | ``float``, | a float or anything on | a Python float | float(), int() | | ``double`` | which float() works | | | +---------------+------------------------+------------------+----------------+ +|``long double``| another <cdata> with | a <cdata>, to | float(), int() | +| | a ``long double``, or | avoid loosing | | +| | anything on which | precision `(***)`| | +| | float() works | | | ++---------------+------------------------+------------------+----------------+ | pointers | another <cdata> with | a <cdata> | ``[]``, ``+``, | | | a compatible type (i.e.| | ``-`` | | | same type or ``char*`` | | | | | or ``void*``, or as an | | | -| | array instead) | | | -+---------------+------------------------+ +----------------+ -| ``void *`` | another <cdata> with | | | -| | any pointer or array | | | +| | array instead) `(*)` | | | ++---------------+------------------------+ | | +| ``void *``, | another <cdata> with | | | +| ``char *`` | any pointer or array | | | | | type | | | +---------------+------------------------+ +----------------+ -| ``char *`` | another <cdata> with | | ``[]``, | -| | any pointer or array | | ``+``, ``-``, | -| | type, or | | str() | -| | a Python string when | | | -| | passed as func argument| | | -+---------------+------------------------+ +----------------+ -| ``wchar_t *`` | same as pointers | | ``[]``, | -| | (passing a unicode as | | ``+``, ``-``, | -| | func argument is not | | unicode() | -| | implemented) | | | -+---------------+------------------------+ +----------------+ -| pointers to | same as pointers | | ``[]``, | +| pointers to | same as pointers `(*)` | | ``[]``, | | structure or | | | ``+``, ``-``, | | union | | | and read/write | | | | | struct fields | -+---------------+ | +----------------+ -| function | | | call | ++---------------+------------------------+ +----------------+ +| function | same as pointers | | call `(**)` | | pointers | | | | +---------------+------------------------+------------------+----------------+ | arrays | a list or tuple of | a <cdata> | len(), iter(), | @@ -920,12 +1004,12 @@ allowed. +---------------+------------------------+ +----------------+ | ``char[]`` | same as arrays, or a | | len(), iter(), | | | Python string | | ``[]``, ``+``, | -| | | | ``-``, str() | +| | | | ``-`` | +---------------+------------------------+ +----------------+ | ``wchar_t[]`` | same as arrays, or a | | len(), iter(), | | | Python unicode | | ``[]``, | -| | | | ``+``, ``-``, | -| | | | unicode() | +| | | | ``+``, ``-`` | +| | | | | +---------------+------------------------+------------------+----------------+ | structure | a list or tuple or | a <cdata> | read/write | | | dict of the field | | fields | @@ -935,12 +1019,39 @@ allowed. | union | same as struct, but | | read/write | | | with at most one field | | fields | +---------------+------------------------+------------------+----------------+ -| enum | an integer, or the enum| the enum value | int(), str() | +| enum | an integer, or the enum| the enum value | int() | | | value as a string or | as a string, or | | | | as ``"#NUMBER"`` | ``"#NUMBER"`` | | | | | if out of range | | +---------------+------------------------+------------------+----------------+ +.. versionchanged:: 0.3 + `(*)` Note that when calling a function, as per C, a ``item *`` argument + is identical to a ``item[]`` argument. So you can pass an argument that + is accepted by either C type, like for example passing a Python string + to a ``char *`` argument (because it works for ``char[]`` arguments) + or a list of integers to a ``int *`` argument (it works for ``int[]`` + arguments). Note that even if you want to pass a single ``item``, + you need to specify it in a list of length 1; for example, a ``struct + foo *`` argument might be passed as ``[[field1, field2...]]``. + +As an optimization, the CPython version of CFFI assumes that a function +with a ``char *`` argument to which you pass a Python string will not +actually modify the array of characters passed in, and so passes directly +a pointer inside the Python string object. + +.. versionchanged:: 0.3 + `(**)` C function calls are now done with the GIL released. + +.. versionadded:: 0.3 + `(***)` ``long double`` support. + Such a number is passed around in a cdata object to avoid loosing + precision, because a normal Python floating-point number only contains + enough precision for a ``double``. To convert it to a regular float, + call ``float()``. If you want to operate on such numbers + without any precision loss, you need to define and use a family of C + functions like ``long double add(long double a, long double b);``. + Reference: verifier ------------------- @@ -959,13 +1070,13 @@ can be instantiated directly. It is normally instantiated for you by ``Verifier`` objects have the following public attributes and methods: - ``sourcefilename``: name of a C file. Defaults to - ``__pycache__/_cffi_MD5HASH.c``, with the ``MD5HASH`` part computed + ``__pycache__/_cffi_CRCHASH.c``, with the ``CRCHASH`` part computed from the strings you passed to cdef() and verify() as well as the version numbers of Python and CFFI. Can be changed before calling ``write_source()`` if you want to write the source somewhere else. - ``modulefilename``: name of the ``.so`` file (or ``.pyd`` on Windows). - Defaults to ``__pycache__/_cffi_MD5HASH.so``. Can be changed before + Defaults to ``__pycache__/_cffi_CRCHASH.so``. Can be changed before calling ``compile_module()``. - ``get_module_name()``: extract the module name from ``modulefilename``. @@ -5,14 +5,15 @@ import errno sources = ['c/_cffi_backend.c'] libraries = ['ffi'] -include_dirs = [] +include_dirs = ['/usr/include/ffi', + '/usr/include/libffi'] # may be changed by pkg-config define_macros = [] library_dirs = [] extra_compile_args = [] extra_link_args = [] -def _ask_pkg_config(option, result_prefix=''): +def _ask_pkg_config(resultlist, option, result_prefix=''): try: p = subprocess.Popen(['pkg-config', option, 'libffi'], stdout=subprocess.PIPE, stderr=open('/dev/null', 'w')) @@ -28,15 +29,14 @@ def _ask_pkg_config(option, result_prefix=''): assert x.startswith(result_prefix) res = [x[len(result_prefix):] for x in res] #print 'PKG_CONFIG:', option, res - return res - return [] + resultlist[:] = res def use_pkg_config(): - include_dirs .extend(_ask_pkg_config('--cflags-only-I', '-I')) - extra_compile_args.extend(_ask_pkg_config('--cflags-only-other')) - library_dirs .extend(_ask_pkg_config('--libs-only-L', '-L')) - extra_link_args .extend(_ask_pkg_config('--libs-only-other')) - libraries[:] = _ask_pkg_config('--libs-only-l', '-l') or libraries + _ask_pkg_config(include_dirs, '--cflags-only-I', '-I') + _ask_pkg_config(extra_compile_args, '--cflags-only-other') + _ask_pkg_config(library_dirs, '--libs-only-L', '-L') + _ask_pkg_config(extra_link_args, '--libs-only-other') + _ask_pkg_config(libraries, '--libs-only-l', '-l') if sys.platform == 'win32': @@ -49,8 +49,8 @@ if COMPILE_LIBFFI: "On Windows, you need to copy the directory " "Modules\\_ctypes\\libffi_msvc from the CPython sources (2.6 or 2.7) " "into the top-level directory.") - include_dirs.append(COMPILE_LIBFFI) - libraries.remove('ffi') + include_dirs[:] = [COMPILE_LIBFFI] + libraries[:] = [] _filenames = [filename.lower() for filename in os.listdir(COMPILE_LIBFFI)] _filenames = [filename for filename in _filenames if filename.endswith('.c') or diff --git a/testing/backend_tests.py b/testing/backend_tests.py index f1371aa..019b2eb 100644 --- a/testing/backend_tests.py +++ b/testing/backend_tests.py @@ -8,6 +8,9 @@ SIZE_OF_SHORT = ctypes.sizeof(ctypes.c_short) SIZE_OF_PTR = ctypes.sizeof(ctypes.c_void_p) SIZE_OF_WCHAR = ctypes.sizeof(ctypes.c_wchar) +if sys.version_info >= (3,): + unicode = str + class BackendTests: @@ -59,7 +62,7 @@ class BackendTests: assert int(p) == max q = ffi.cast(c_decl, min - 1) assert ffi.typeof(q) is ffi.typeof(p) and int(q) == max - if sys.version < '3': + if sys.version_info < (3,): p = ffi.cast(c_decl, long(max)) assert int(p) == max q = ffi.cast(c_decl, long(min - 1)) @@ -72,7 +75,7 @@ class BackendTests: py.test.raises(OverflowError, ffi.new, c_decl_ptr, max + 1) assert ffi.new(c_decl_ptr, min)[0] == min assert ffi.new(c_decl_ptr, max)[0] == max - if sys.version < '3': + if sys.version_info < (3,): py.test.raises(OverflowError, ffi.new, c_decl_ptr, long(min - 1)) py.test.raises(OverflowError, ffi.new, c_decl_ptr, long(max + 1)) assert ffi.new(c_decl_ptr, long(min))[0] == min @@ -282,7 +285,7 @@ class BackendTests: assert ffi.new("char*")[0] == b'\x00' assert int(ffi.cast("char", 300)) == 300 - 256 assert bool(ffi.cast("char", 0)) - if sys.version < '3': + if sys.version_info < (3,): py.test.raises(TypeError, ffi.new, "char*", 32) else: assert ffi.new("char*", 32)[0] == b' ' @@ -555,57 +558,65 @@ class BackendTests: assert len(a) == 5 assert ffi.sizeof(a) == 5 * SIZE_OF_INT - def test_str_from_char_pointer(self): + def test_string_from_char_pointer(self): ffi = FFI(backend=self.Backend()) - assert ffi.new("char*", b"x").value == b"x" - assert ffi.new("char*", b"\x00").value == b"" + x = ffi.new("char*", b"x") + assert str(x) == repr(x) + assert ffi.string(x) == b"x" + assert ffi.string(ffi.new("char*", b"\x00")) == "" + py.test.raises(TypeError, ffi.new, "char*", unicode("foo")) def test_unicode_from_wchar_pointer(self): ffi = FFI(backend=self.Backend()) self.check_wchar_t(ffi) - assert ffi.new("wchar_t*", u"x").value == u"x" - assert ffi.new("wchar_t*", u"\x00").value == u"" - x = ffi.new("wchar_t*", u"\x00") - assert str(x) == repr(x) + x = ffi.new("wchar_t*", u"x") + assert unicode(x) == unicode(repr(x)) + assert ffi.string(x) == u"x" + assert ffi.string(ffi.new("wchar_t*", u"\x00")) == u"" def test_string_from_char_array(self): ffi = FFI(backend=self.Backend()) - assert ffi.cast("char", b"x").value == b"x" - p = ffi.new("char[]", b"hello.") - p[5] = b'!' - assert p.value == b"hello!" - p[6] = b'?' - assert p.value == b"hello!?" - p[3] = b'\x00' - assert p.value == b"hel" - py.test.raises(IndexError, "p[7] = b'X'") + p = ffi.new("char[]", "hello.") + p[5] = '!' + assert ffi.string(p) == "hello!" + p[6] = '?' + assert ffi.string(p) == "hello!?" + p[3] = '\x00' + assert ffi.string(p) == "hel" + assert ffi.string(p, 2) == "he" + py.test.raises(IndexError, "p[7] = 'X'") # a = ffi.new("char[]", b"hello\x00world") assert len(a) == 12 p = ffi.cast("char *", a) - assert p.value == b'hello' + assert ffi.string(p) == b'hello' def test_string_from_wchar_array(self): ffi = FFI(backend=self.Backend()) self.check_wchar_t(ffi) - assert ffi.cast("wchar_t", b"x").value == u"x" - assert ffi.cast("wchar_t", u"x").value == u"x" + assert ffi.string(ffi.cast("wchar_t", "x")) == u"x" + assert ffi.string(ffi.cast("wchar_t", u"x")) == u"x" x = ffi.cast("wchar_t", "x") assert str(x) == repr(x) + assert ffi.string(x) == u"x" # p = ffi.new("wchar_t[]", u"hello.") p[5] = u'!' - assert p.value == u"hello!" - p[6] = u'\u1234' - assert p.value == u"hello!\u1234" + assert ffi.string(p) == u"hello!" + p[6] = unichr(1234) + assert ffi.string(p) == u"hello!\u04d2" p[3] = u'\x00' - assert p.value == u"hel" + assert ffi.string(p) == u"hel" + assert ffi.string(p, 123) == u"hel" py.test.raises(IndexError, "p[7] = u'X'") # a = ffi.new("wchar_t[]", u"hello\x00world") assert len(a) == 12 p = ffi.cast("wchar_t *", a) - assert p.value == u'hello' + assert ffi.string(p) == u'hello' + assert ffi.string(p, 123) == u'hello' + assert ffi.string(p, 5) == u'hello' + assert ffi.string(p, 2) == u'he' def test_fetch_const_char_p_field(self): # 'const' is ignored so far @@ -614,7 +625,7 @@ class BackendTests: t = ffi.new("const char[]", b"testing") s = ffi.new("struct foo*", [t]) assert type(s.name) is not str - assert s.name.value == b"testing" + assert ffi.string(s.name) == "testing" py.test.raises(TypeError, "s.name = None") s.name = ffi.NULL assert s.name == ffi.NULL @@ -626,11 +637,8 @@ class BackendTests: ffi.cdef("struct foo { const wchar_t *name; };") t = ffi.new("const wchar_t[]", u"testing") s = ffi.new("struct foo*", [t]) - if sys.version < '3': - assert type(s.name) not in (str, unicode) - else: - assert type(s.name) not in (bytes, str) - assert s.name.value == u"testing" + assert type(s.name) not in (bytes, str, unicode) + assert ffi.string(s.name) == u"testing" s.name = ffi.NULL assert s.name == ffi.NULL @@ -811,6 +819,28 @@ class BackendTests: res = a(1) # and the error reported to stderr assert res == 42 + def test_structptr_argument(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo_s { int a, b; };") + def cb(p): + return p[0].a * 1000 + p[0].b * 100 + p[1].a * 10 + p[1].b + a = ffi.callback("int(*)(struct foo_s[])", cb) + res = a([[5, 6], {'a': 7, 'b': 8}]) + assert res == 5678 + res = a([[5], {'b': 8}]) + assert res == 5008 + + def test_array_argument_as_list(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo_s { int a, b; };") + seen = [] + def cb(argv): + seen.append(ffi.string(argv[0])) + seen.append(ffi.string(argv[1])) + a = ffi.callback("void(*)(char *[])", cb) + a([ffi.new("char[]", "foobar"), ffi.new("char[]", "baz")]) + assert seen == ["foobar", "baz"] + def test_cast_float(self): ffi = FFI(backend=self.Backend()) a = ffi.cast("float", 12) @@ -822,7 +852,7 @@ class BackendTests: a = ffi.cast("int", 12.9) assert int(a) == 12 a = ffi.cast("char", 66.9 + 256) - assert a.value == b"B" + assert ffi.string(a) == b"B" # a = ffi.cast("float", ffi.cast("int", 12)) assert float(a) == 12.0 @@ -833,7 +863,7 @@ class BackendTests: a = ffi.cast("int", ffi.cast("double", 12.9)) assert int(a) == 12 a = ffi.cast("char", ffi.cast("double", 66.9 + 256)) - assert a.value == b"B" + assert ffi.string(a) == b"B" def test_enum(self): ffi = FFI(backend=self.Backend()) @@ -876,12 +906,12 @@ class BackendTests: assert int(ffi.cast("enum foo", "A")) == 0 assert int(ffi.cast("enum foo", "B")) == 42 assert int(ffi.cast("enum foo", "C")) == 43 - assert str(ffi.cast("enum foo", 0)) == "A" - assert str(ffi.cast("enum foo", 42)) == "B" - assert str(ffi.cast("enum foo", 43)) == "C" + assert ffi.string(ffi.cast("enum foo", 0)) == "A" + assert ffi.string(ffi.cast("enum foo", 42)) == "B" + assert ffi.string(ffi.cast("enum foo", 43)) == "C" invalid_value = ffi.cast("enum foo", 2) assert int(invalid_value) == 2 - assert str(invalid_value) == "#2" + assert ffi.string(invalid_value) == "#2" def test_array_of_struct(self): ffi = FFI(backend=self.Backend()) @@ -1119,6 +1149,26 @@ class BackendTests: f.close() os.unlink(filename) + def test_array_in_struct(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo_s { int len; short data[5]; };") + p = ffi.new("struct foo_s *") + p.data[3] = 5 + assert p.data[3] == 5 + assert repr(p.data).startswith("<cdata 'short[5]' 0x") + + def test_struct_containing_array_varsize_workaround(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("struct foo_s { int len; short data[0]; };") + p = ffi.new("char[]", ffi.sizeof("struct foo_s") + 7 * SIZE_OF_SHORT) + q = ffi.cast("struct foo_s *", p) + assert q.len == 0 + # 'q.data' gets not a 'short[0]', but just a 'short *' instead + assert repr(q.data).startswith("<cdata 'short *' 0x") + assert q.data[6] == 0 + q.data[6] = 15 + assert q.data[6] == 15 + def test_new_struct_containing_array_varsize(self): py.test.skip("later?") ffi = FFI(backend=self.Backend()) @@ -1226,4 +1276,49 @@ class BackendTests: ffi = FFI(backend=self.Backend()) ffi.cdef("enum e { AA=0, BB=0, CC=0, DD=0 };") e = ffi.cast("enum e", 'CC') - assert str(e) == "AA" # pick the first one arbitrarily + assert ffi.string(e) == "AA" # pick the first one arbitrarily + + def test_nested_anonymous_struct(self): + py.test.skip("later") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + struct foo_s { + struct { int a, b; }; + union { int c, d; }; + }; + """) + assert ffi.sizeof("struct foo_s") == 3 * SIZE_OF_INT + p = ffi.new("struct foo_s *", [[1], [3]]) + assert p.a == 1 + assert p.b == 0 + assert p.c == 3 + assert p.d == 3 + p.d = 17 + assert p.c == 17 + p.b = 19 + assert p.a == 1 + assert p.b == 19 + assert p.c == 17 + assert p.d == 17 + + def test_cast_to_array_type(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("int[4]", [-5]) + q = ffi.cast("int[3]", p) + assert q[0] == -5 + assert repr(q).startswith("<cdata 'int[3]' 0x") + + def test_gc(self): + ffi = FFI(backend=self.Backend()) + p = ffi.new("int *", 123) + seen = [] + def destructor(p1): + assert p1 is p + assert p1[0] == 123 + seen.append(1) + q = ffi.gc(p, destructor) + import gc; gc.collect() + assert seen == [] + del q + import gc; gc.collect(); gc.collect(); gc.collect() + assert seen == [1] diff --git a/testing/test_ctypes.py b/testing/test_ctypes.py index b7f0ac6..9116675 100644 --- a/testing/test_ctypes.py +++ b/testing/test_ctypes.py @@ -13,3 +13,14 @@ class TestCTypes(backend_tests.BackendTests): def test_array_of_func_ptr(self): py.test.skip("ctypes backend: not supported: " "initializers for function pointers") + + def test_structptr_argument(self): + py.test.skip("ctypes backend: not supported: passing a list " + "for a pointer argument") + + def test_array_argument_as_list(self): + py.test.skip("ctypes backend: not supported: passing a list " + "for a pointer argument") + + def test_cast_to_array_type(self): + py.test.skip("ctypes backend: not supported: casting to array") diff --git a/testing/test_function.py b/testing/test_function.py index ccf76b4..bca2e7e 100644 --- a/testing/test_function.py +++ b/testing/test_function.py @@ -112,8 +112,8 @@ class TestFunction(object): assert res == b'hello\n world\n' def test_fputs(self): - if sys.platform == 'win32': - py.test.skip("no 'stderr'") + if not sys.platform.startswith('linux'): + py.test.skip("probably no symbol 'stdout' in the lib") ffi = FFI(backend=self.Backend()) ffi.cdef(""" int fputs(const char *, void *); @@ -145,14 +145,16 @@ class TestFunction(object): ffi.C.fflush(ffi.NULL) res = fd.getvalue() if sys.platform == 'win32': - NIL = b"00000000" + NIL = "00000000" + elif sys.platform.startswith('linux'): + NIL = "(nil)" else: - NIL = b"(nil)" - assert res == (b"hello with no arguments\n" - b"hello, world!\n" - b"hello, world2!\n" - b"hello int 42 long 84 long long 168\n" - b"hello " + NIL + b"\n") + NIL = "0x0" # OS/X at least + assert res == bytes("hello with no arguments\n" + "hello, world!\n" + "hello, world2!\n" + "hello int 42 long 84 long long 168\n" + "hello " + NIL + "\n") def test_must_specify_type_of_vararg(self): ffi = FFI(backend=self.Backend()) @@ -230,8 +232,8 @@ class TestFunction(object): assert res == 5 def test_write_variable(self): - if sys.platform == 'win32': - py.test.skip("no 'stdout'") + if not sys.platform.startswith('linux'): + py.test.skip("probably no symbol 'stdout' in the lib") ffi = FFI(backend=self.Backend()) ffi.cdef(""" int puts(const char *); @@ -259,7 +261,7 @@ class TestFunction(object): ffi.C = ffi.dlopen(None) p = ffi.new("char[]", b"hello world!") q = ffi.C.strchr(p, ord('w')) - assert q.value == b"world!" + assert ffi.string(q) == b"world!" def test_function_with_struct_argument(self): if sys.platform == 'win32': @@ -272,4 +274,15 @@ class TestFunction(object): ffi.C = ffi.dlopen(None) ina = ffi.new("struct in_addr *", [0x04040404]) a = ffi.C.inet_ntoa(ina[0]) - assert a.value == b'4.4.4.4' + assert ffi.string(a) == b'4.4.4.4' + + def test_function_typedef(self): + py.test.skip("using really obscure C syntax") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + typedef double func_t(double); + func_t sin; + """) + m = ffi.dlopen("m") + x = m.sin(1.23) + assert x == math.sin(1.23) diff --git a/testing/test_ownlib.py b/testing/test_ownlib.py index 547ca64..abe6dc7 100644 --- a/testing/test_ownlib.py +++ b/testing/test_ownlib.py @@ -15,6 +15,8 @@ int test_getting_errno(void) { int test_setting_errno(void) { return errno; } + +int my_array[7] = {0, 1, 2, 3, 4, 5, 6}; """ class TestOwnLib(object): @@ -56,3 +58,43 @@ class TestOwnLib(object): res = ownlib.test_setting_errno() assert res == 42 assert ffi.errno == 42 + + def test_my_array_7(self): + if sys.platform == 'win32': + py.test.skip("fix the auto-generation of the tiny test lib") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + int my_array[7]; + """) + ownlib = ffi.dlopen(self.module) + for i in range(7): + assert ownlib.my_array[i] == i + assert len(ownlib.my_array) == 7 + if self.Backend is CTypesBackend: + py.test.skip("not supported by the ctypes backend") + ownlib.my_array = range(10, 17) + for i in range(7): + assert ownlib.my_array[i] == 10 + i + ownlib.my_array = range(7) + for i in range(7): + assert ownlib.my_array[i] == i + + def test_my_array_no_length(self): + if sys.platform == 'win32': + py.test.skip("fix the auto-generation of the tiny test lib") + if self.Backend is CTypesBackend: + py.test.skip("not supported by the ctypes backend") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + int my_array[]; + """) + ownlib = ffi.dlopen(self.module) + for i in range(7): + assert ownlib.my_array[i] == i + py.test.raises(TypeError, len, ownlib.my_array) + ownlib.my_array = range(10, 17) + for i in range(7): + assert ownlib.my_array[i] == 10 + i + ownlib.my_array = range(7) + for i in range(7): + assert ownlib.my_array[i] == i diff --git a/testing/test_verify.py b/testing/test_verify.py index 6027ff4..12266fc 100644 --- a/testing/test_verify.py +++ b/testing/test_verify.py @@ -15,10 +15,39 @@ def setup_module(): cffi.verifier.cleanup_tmpdir() -def test_missing_function(): +def test_module_type(): + import cffi.verifier ffi = FFI() + lib = ffi.verify() + if hasattr(lib, '_cffi_python_module'): + print 'verify got a PYTHON module' + if hasattr(lib, '_cffi_generic_module'): + print 'verify got a GENERIC module' + expected_generic = (cffi.verifier._FORCE_GENERIC_ENGINE or + '__pypy__' in sys.builtin_module_names) + assert hasattr(lib, '_cffi_python_module') == (not expected_generic) + assert hasattr(lib, '_cffi_generic_module') == expected_generic + +def test_missing_function(ffi=None): + # uses the FFI hacked above with '-Werror' + if ffi is None: + ffi = FFI() ffi.cdef("void some_completely_unknown_function();") - py.test.raises(VerificationError, ffi.verify) + try: + lib = ffi.verify() + except VerificationError: + pass # expected case: we get a VerificationError + else: + # but depending on compiler and loader details, maybe + # 'lib' could actually be imported but will fail if we + # actually try to call the unknown function... Hard + # to test anything more. + pass + +def test_missing_function_import_error(): + # uses the original FFI that just gives a warning during compilation + import cffi + test_missing_function(ffi=cffi.FFI()) def test_simple_case(): ffi = FFI() @@ -102,6 +131,20 @@ def test_all_integer_and_float_types(): else: assert lib.foo(value) == value + 1 +def test_nonstandard_integer_types(): + ffi = FFI() + lst = ffi._backend.nonstandard_integer_types().items() + lst.sort() + verify_lines = [] + for key, value in lst: + ffi.cdef("static const int expected_%s;" % key) + verify_lines.append("static const int expected_%s =" % key) + verify_lines.append(" sizeof(%s) | (((%s)-1) <= 0 ? 0 : 0x1000);" + % (key, key)) + lib = ffi.verify('\n'.join(verify_lines)) + for key, value in lst: + assert getattr(lib, 'expected_%s' % key) == value + def test_char_type(): ffi = FFI() ffi.cdef("char foo(char);") @@ -339,7 +382,7 @@ def test_global_constants_non_int(): ffi.cdef("static char *const PP;") lib = ffi.verify('static char *const PP = "testing!";\n') assert ffi.typeof(lib.PP) == ffi.typeof("char *") - assert lib.PP.value == b"testing!" + assert ffi.string(lib.PP) == b"testing!" def test_nonfull_enum(): ffi = FFI() @@ -415,16 +458,26 @@ def test_access_address_of_variable(): assert lib.somenumber == 42 lib.somenumber = 2 # reset for the next run, if any -def test_access_array_variable(): +def test_access_array_variable(length=5): ffi = FFI() ffi.cdef("int foo(int);\n" - "int somenumber[5];") + "int somenumber[%s];" % (length,)) lib = ffi.verify(""" static int somenumber[] = {2, 2, 3, 4, 5}; static int foo(int i) { return somenumber[i] * 7; } """) + if length == '': + # a global variable of an unknown array length is implicitly + # transformed into a global pointer variable, because we can only + # work with array instances whose length we know. using a pointer + # instead of an array gives the correct effects. + assert repr(lib.somenumber).startswith("<cdata 'int *' 0x") + py.test.raises(TypeError, len, lib.somenumber) + else: + assert repr(lib.somenumber).startswith("<cdata 'int[%s]' 0x" % length) + assert len(lib.somenumber) == 5 assert lib.somenumber[3] == 4 assert lib.foo(3) == 28 lib.somenumber[3] = -6 @@ -433,6 +486,9 @@ def test_access_array_variable(): assert lib.somenumber[4] == 5 lib.somenumber[3] = 4 # reset for the next run, if any +def test_access_array_variable_length_hidden(): + test_access_array_variable(length='') + def test_access_struct_variable(): ffi = FFI() ffi.cdef("struct foo { int x; ...; };\n" @@ -476,11 +532,32 @@ def test_access_callback(): lib.cb = my_callback assert lib.foo(4) == 887 -def test_cannot_verify_with_ctypes(): +def test_access_callback_function_typedef(): + ffi = FFI() + ffi.cdef("typedef int mycallback_t(int);\n" + "mycallback_t *cb;\n" + "int foo(int);\n" + "void reset_cb(void);") + lib = ffi.verify(""" + static int g(int x) { return x * 7; } + static int (*cb)(int); + static int foo(int i) { return cb(i) - 1; } + static void reset_cb(void) { cb = g; } + """) + lib.reset_cb() + assert lib.foo(6) == 41 + my_callback = ffi.callback("int(*)(int)", lambda n: n * 222) + lib.cb = my_callback + assert lib.foo(4) == 887 + +def test_ctypes_backend_forces_generic_engine(): from cffi.backend_ctypes import CTypesBackend ffi = FFI(backend=CTypesBackend()) - ffi.cdef("int a;") - py.test.raises(NotImplementedError, ffi.verify, "int a;") + ffi.cdef("int func(int a);") + lib = ffi.verify("int func(int a) { return a * 42; }") + assert not hasattr(lib, '_cffi_python_module') + assert hasattr(lib, '_cffi_generic_module') + assert lib.func(100) == 4200 def test_call_with_struct_ptr(): ffi = FFI() @@ -635,7 +712,7 @@ def test_func_as_funcptr(): """) foochar = ffi.cast("char *(*)(void)", lib.fooptr) s = foochar() - assert s.value == b"foobar" + assert ffi.string(s) == b"foobar" def test_funcptr_as_argument(): ffi = FFI() @@ -709,12 +786,12 @@ def test_opaque_integer_as_function_result(): # anyway. XXX think about something better :-( ffi = FFI() ffi.cdef(""" - typedef struct { ...; } handle_t; - handle_t foo(void); + typedef struct { ...; } myhandle_t; + myhandle_t foo(void); """) lib = ffi.verify(""" - typedef short handle_t; - handle_t foo(void) { return 42; } + typedef short myhandle_t; + myhandle_t foo(void) { return 42; } """) h = lib.foo() assert ffi.sizeof(h) == ffi.sizeof("short") diff --git a/testing/test_vgen.py b/testing/test_vgen.py new file mode 100644 index 0000000..1a7e05d --- /dev/null +++ b/testing/test_vgen.py @@ -0,0 +1,12 @@ +import cffi.verifier +from .test_verify import * + + +def setup_module(): + cffi.verifier.cleanup_tmpdir() + cffi.verifier._FORCE_GENERIC_ENGINE = True + # Runs all tests with _FORCE_GENERIC_ENGINE = True, to make sure we + # also test vengine_gen.py. + +def teardown_module(): + cffi.verifier._FORCE_GENERIC_ENGINE = False diff --git a/testing/test_vgen2.py b/testing/test_vgen2.py new file mode 100644 index 0000000..34147c8 --- /dev/null +++ b/testing/test_vgen2.py @@ -0,0 +1,13 @@ +import cffi.verifier +from .test_vgen import * + +# This test file runs normally after test_vgen. We only clean up the .c +# sources, to check that it also works when we have only the .so. The +# tests should run much faster than test_vgen. + +def setup_module(): + cffi.verifier.cleanup_tmpdir(keep_so=True) + cffi.verifier._FORCE_GENERIC_ENGINE = True + +def teardown_module(): + cffi.verifier._FORCE_GENERIC_ENGINE = False diff --git a/testing/test_zdistutils.py b/testing/test_zdistutils.py index 86e1567..647c624 100644 --- a/testing/test_zdistutils.py +++ b/testing/test_zdistutils.py @@ -1,137 +1,164 @@ -import os, imp, math, random +import sys, os, imp, math, random import py from cffi import FFI, FFIError -from cffi.verifier import Verifier +from cffi.verifier import Verifier, _locate_engine_class from testing.udir import udir -try: - from StringIO import StringIO -except ImportError: - from io import StringIO - - -def test_write_source(): - ffi = FFI() - ffi.cdef("double sin(double x);") - csrc = '/*hi there!*/\n#include <math.h>\n' - v = Verifier(ffi, csrc) - v.write_source() - with open(v.sourcefilename, 'r') as f: - data = f.read() - assert csrc in data - -def test_write_source_explicit_filename(): - ffi = FFI() - ffi.cdef("double sin(double x);") - csrc = '/*hi there!*/\n#include <math.h>\n' - v = Verifier(ffi, csrc) - v.sourcefilename = filename = str(udir.join('write_source.c')) - v.write_source() - assert filename == v.sourcefilename - with open(filename, 'r') as f: - data = f.read() - assert csrc in data - -def test_write_source_to_file_obj(): - ffi = FFI() - ffi.cdef("double sin(double x);") - csrc = '/*hi there!*/\n#include <math.h>\n' - v = Verifier(ffi, csrc) - f = StringIO() - v.write_source(file=f) - assert csrc in f.getvalue() - -def test_compile_module(): - ffi = FFI() - ffi.cdef("double sin(double x);") - csrc = '/*hi there!*/\n#include <math.h>\n' - v = Verifier(ffi, csrc) - v.compile_module() - assert v.get_module_name().startswith('_cffi_') - mod = imp.load_dynamic(v.get_module_name(), v.modulefilename) - assert hasattr(mod, '_cffi_setup') - -def test_compile_module_explicit_filename(): - ffi = FFI() - ffi.cdef("double sin(double x);") - csrc = '/*hi there!2*/\n#include <math.h>\n' - v = Verifier(ffi, csrc) - v.modulefilename = filename = str(udir.join('test_compile_module.so')) - v.compile_module() - assert filename == v.modulefilename - assert v.get_module_name() == 'test_compile_module' - mod = imp.load_dynamic(v.get_module_name(), v.modulefilename) - assert hasattr(mod, '_cffi_setup') - -def test_name_from_md5_of_cdef(): - names = [] - for csrc in ['double', 'double', 'float']: + +class DistUtilsTest(object): + + def test_locate_engine_class(self): + cls = _locate_engine_class(FFI(), self.generic) + if self.generic: + # asked for the generic engine, which must not generate a + # CPython extension module + assert not cls._gen_python_module + else: + # asked for the CPython engine: check that we got it, unless + # we are running on top of PyPy, where the generic engine is + # always better + if '__pypy__' not in sys.builtin_module_names: + assert cls._gen_python_module + + def test_write_source(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there!*/\n#include <math.h>\n' + v = Verifier(ffi, csrc, force_generic_engine=self.generic) + v.write_source() + with open(v.sourcefilename, 'r') as f: + data = f.read() + assert csrc in data + + def test_write_source_explicit_filename(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there!*/\n#include <math.h>\n' + v = Verifier(ffi, csrc, force_generic_engine=self.generic) + v.sourcefilename = filename = str(udir.join('write_source.c')) + v.write_source() + assert filename == v.sourcefilename + with open(filename, 'r') as f: + data = f.read() + assert csrc in data + + def test_write_source_to_file_obj(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there!*/\n#include <math.h>\n' + v = Verifier(ffi, csrc, force_generic_engine=self.generic) + try: + from StringIO import StringIO + except ImportError: + from io import StringIO + f = StringIO() + v.write_source(file=f) + assert csrc in f.getvalue() + + def test_compile_module(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there!*/\n#include <math.h>\n' + v = Verifier(ffi, csrc, force_generic_engine=self.generic) + v.compile_module() + assert v.get_module_name().startswith('_cffi_') + if v.generates_python_module(): + mod = imp.load_dynamic(v.get_module_name(), v.modulefilename) + assert hasattr(mod, '_cffi_setup') + + def test_compile_module_explicit_filename(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there!2*/\n#include <math.h>\n' + v = Verifier(ffi, csrc, force_generic_engine=self.generic) + basename = self.__class__.__name__ + 'test_compile_module' + v.modulefilename = filename = str(udir.join(basename + '.so')) + v.compile_module() + assert filename == v.modulefilename + assert v.get_module_name() == basename + if v.generates_python_module(): + mod = imp.load_dynamic(v.get_module_name(), v.modulefilename) + assert hasattr(mod, '_cffi_setup') + + def test_name_from_checksum_of_cdef(self): + names = [] + for csrc in ['double', 'double', 'float']: + ffi = FFI() + ffi.cdef("%s sin(double x);" % csrc) + v = Verifier(ffi, "#include <math.h>", + force_generic_engine=self.generic) + names.append(v.get_module_name()) + assert names[0] == names[1] != names[2] + + def test_name_from_checksum_of_csrc(self): + names = [] + for csrc in ['123', '123', '1234']: + ffi = FFI() + ffi.cdef("double sin(double x);") + v = Verifier(ffi, csrc, force_generic_engine=self.generic) + names.append(v.get_module_name()) + assert names[0] == names[1] != names[2] + + def test_load_library(self): ffi = FFI() - ffi.cdef("%s sin(double x);" % csrc) - v = Verifier(ffi, "#include <math.h>") - names.append(v.get_module_name()) - assert names[0] == names[1] != names[2] - -def test_name_from_md5_of_csrc(): - names = [] - for csrc in ['123', '123', '1234']: + ffi.cdef("double sin(double x);") + csrc = '/*hi there!3*/\n#include <math.h>\n' + v = Verifier(ffi, csrc, force_generic_engine=self.generic) + library = v.load_library() + assert library.sin(12.3) == math.sin(12.3) + + def test_verifier_args(self): ffi = FFI() ffi.cdef("double sin(double x);") - v = Verifier(ffi, csrc) - names.append(v.get_module_name()) - assert names[0] == names[1] != names[2] - -def test_load_library(): - ffi = FFI() - ffi.cdef("double sin(double x);") - csrc = '/*hi there!3*/\n#include <math.h>\n' - v = Verifier(ffi, csrc) - library = v.load_library() - assert library.sin(12.3) == math.sin(12.3) - -def test_verifier_args(): - ffi = FFI() - ffi.cdef("double sin(double x);") - csrc = '/*hi there!4*/#include "test_verifier_args.h"\n' - udir.join('test_verifier_args.h').write('#include <math.h>\n') - v = Verifier(ffi, csrc, include_dirs=[str(udir)]) - library = v.load_library() - assert library.sin(12.3) == math.sin(12.3) - -def test_verifier_object_from_ffi(): - ffi = FFI() - ffi.cdef("double sin(double x);") - csrc = "/*6*/\n#include <math.h>" - lib = ffi.verify(csrc) - assert lib.sin(12.3) == math.sin(12.3) - assert isinstance(ffi.verifier, Verifier) - with open(ffi.verifier.sourcefilename, 'r') as f: - data = f.read() - assert csrc in data - -def test_extension_object(): - ffi = FFI() - ffi.cdef("double sin(double x);") - csrc = '''/*7*/ -#include <math.h> -#ifndef TEST_EXTENSION_OBJECT -# error "define_macros missing" -#endif -''' - lib = ffi.verify(csrc, define_macros=[('TEST_EXTENSION_OBJECT', '1')]) - assert lib.sin(12.3) == math.sin(12.3) - v = ffi.verifier - ext = v.get_extension() - assert str(ext.__class__) == 'distutils.extension.Extension' - assert ext.sources == [v.sourcefilename] - assert ext.name == v.get_module_name() - assert ext.define_macros == [('TEST_EXTENSION_OBJECT', '1')] - -def test_extension_forces_write_source(): - ffi = FFI() - ffi.cdef("double sin(double x);") - csrc = '/*hi there!%r*/\n#include <math.h>\n' % random.random() - v = Verifier(ffi, csrc) - assert not os.path.exists(v.sourcefilename) - v.get_extension() - assert os.path.exists(v.sourcefilename) + csrc = '/*hi there!4*/#include "test_verifier_args.h"\n' + udir.join('test_verifier_args.h').write('#include <math.h>\n') + v = Verifier(ffi, csrc, include_dirs=[str(udir)], + force_generic_engine=self.generic) + library = v.load_library() + assert library.sin(12.3) == math.sin(12.3) + + def test_verifier_object_from_ffi(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = "/*6*/\n#include <math.h>" + lib = ffi.verify(csrc, force_generic_engine=self.generic) + assert lib.sin(12.3) == math.sin(12.3) + assert isinstance(ffi.verifier, Verifier) + with open(ffi.verifier.sourcefilename, 'r') as f: + data = f.read() + assert csrc in data + + def test_extension_object(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '''/*7*/ + #include <math.h> + #ifndef TEST_EXTENSION_OBJECT + # error "define_macros missing" + #endif + ''' + lib = ffi.verify(csrc, define_macros=[('TEST_EXTENSION_OBJECT', '1')], + force_generic_engine=self.generic) + assert lib.sin(12.3) == math.sin(12.3) + v = ffi.verifier + ext = v.get_extension() + assert str(ext.__class__) == 'distutils.extension.Extension' + assert ext.sources == [v.sourcefilename] + assert ext.name == v.get_module_name() + assert ext.define_macros == [('TEST_EXTENSION_OBJECT', '1')] + + def test_extension_forces_write_source(self): + ffi = FFI() + ffi.cdef("double sin(double x);") + csrc = '/*hi there!%r*/\n#include <math.h>\n' % random.random() + v = Verifier(ffi, csrc, force_generic_engine=self.generic) + assert not os.path.exists(v.sourcefilename) + v.get_extension() + assert os.path.exists(v.sourcefilename) + + +class TestDistUtilsCPython(DistUtilsTest): + generic = False + +class TestDistUtilsGeneric(DistUtilsTest): + generic = True |
