diff options
author | Armin Rigo <arigo@tunes.org> | 2014-07-05 16:47:52 +0200 |
---|---|---|
committer | Armin Rigo <arigo@tunes.org> | 2014-07-05 16:47:52 +0200 |
commit | dec9ea315348924711debd8e16a0c155f02f130a (patch) | |
tree | be2d9401b49044e8137da0e87427cfccbf86a0d6 | |
parent | d9f22a6bdecb46a0ef359ade64d5686e5591e40c (diff) | |
parent | d006340d73490b19a70721fbf26cd2ba738df487 (diff) | |
download | cffi-dec9ea315348924711debd8e16a0c155f02f130a.tar.gz |
hg merge default
-rw-r--r-- | c/_cffi_backend.c | 30 | ||||
-rw-r--r-- | c/check__thread.c | 1 | ||||
-rw-r--r-- | c/minibuffer.h | 5 | ||||
-rw-r--r-- | c/misc_win32.h | 38 | ||||
-rw-r--r-- | c/test_c.py | 8 | ||||
-rw-r--r-- | cffi/__init__.py | 4 | ||||
-rw-r--r-- | cffi/api.py | 4 | ||||
-rw-r--r-- | cffi/cparser.py | 113 | ||||
-rw-r--r-- | cffi/ffiplatform.py | 1 | ||||
-rw-r--r-- | cffi/vengine_cpy.py | 169 | ||||
-rw-r--r-- | cffi/vengine_gen.py | 52 | ||||
-rw-r--r-- | doc/source/conf.py | 2 | ||||
-rw-r--r-- | doc/source/index.rst | 50 | ||||
-rw-r--r-- | setup.py | 27 | ||||
-rw-r--r-- | testing/backend_tests.py | 59 | ||||
-rw-r--r-- | testing/test_function.py | 15 | ||||
-rw-r--r-- | testing/test_parsing.py | 7 | ||||
-rw-r--r-- | testing/test_verify.py | 88 |
18 files changed, 463 insertions, 210 deletions
diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c index a3a2515..4415264 100644 --- a/c/_cffi_backend.c +++ b/c/_cffi_backend.c @@ -5,7 +5,6 @@ #ifdef MS_WIN32 #include <windows.h> #include "misc_win32.h" -#include <malloc.h> /* for alloca() */ #else #include <stddef.h> #include <stdint.h> @@ -13,9 +12,32 @@ #include <errno.h> #include <ffi.h> #include <sys/mman.h> -#if (defined (__SVR4) && defined (__sun)) || defined(_AIX) -# include <alloca.h> #endif + +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py */ +#if defined(_MSC_VER) +# include <malloc.h> /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + 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; +# else +# include <stdint.h> +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ + typedef unsigned char _Bool; +# endif +#else +# include <stdint.h> +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) +# include <alloca.h> +# endif #endif #include "malloc_closure.h" @@ -5482,7 +5504,7 @@ init_cffi_backend(void) if (v == NULL || PyModule_AddObject(m, "_C_API", v) < 0) INITERROR; - v = PyText_FromString("0.8.2"); + v = PyText_FromString("0.8.3"); if (v == NULL || PyModule_AddObject(m, "__version__", v) < 0) INITERROR; diff --git a/c/check__thread.c b/c/check__thread.c deleted file mode 100644 index 6f3261c..0000000 --- a/c/check__thread.c +++ /dev/null @@ -1 +0,0 @@ -__thread int some_threadlocal_variable_42; diff --git a/c/minibuffer.h b/c/minibuffer.h index 289dc4a..92cf079 100644 --- a/c/minibuffer.h +++ b/c/minibuffer.h @@ -105,8 +105,9 @@ static PyObject *mb_str(MiniBufferObj *self) static int mb_getbuf(MiniBufferObj *self, Py_buffer *view, int flags) { - return PyBuffer_FillInfo(view, NULL, self->mb_data, self->mb_size, - /*readonly=*/0, PyBUF_CONTIG | PyBUF_FORMAT); + return PyBuffer_FillInfo(view, (PyObject *)self, + self->mb_data, self->mb_size, + /*readonly=*/0, flags); } static PySequenceMethods mb_as_sequence = { diff --git a/c/misc_win32.h b/c/misc_win32.h index 70195b2..47f9ea4 100644 --- a/c/misc_win32.h +++ b/c/misc_win32.h @@ -1,3 +1,4 @@ +#include <malloc.h> /* for alloca() */ /************************************************************/ /* errno and GetLastError support */ @@ -192,7 +193,27 @@ static void *dlopen(const char *filename, int flag) static void *dlsym(void *handle, const char *symbol) { - return GetProcAddress((HMODULE)handle, symbol); + void *address = GetProcAddress((HMODULE)handle, symbol); +#ifndef MS_WIN64 + if (!address) { + /* If 'symbol' is not found, then try '_symbol@N' for N in + (0, 4, 8, 12, ..., 124). Unlike ctypes, we try to do that + for any symbol, although in theory it should only be done + for __stdcall functions. + */ + int i; + char *mangled_name = alloca(1 + strlen(symbol) + 1 + 3 + 1); + if (!mangled_name) + return NULL; + for (i = 0; i < 32; i++) { + sprintf(mangled_name, "_%s@%d", symbol, i * 4); + address = GetProcAddress((HMODULE)handle, mangled_name); + if (address) + break; + } + } +#endif + return address; } static void dlclose(void *handle) @@ -210,21 +231,6 @@ static const char *dlerror(void) return buf; } - -/************************************************************/ -/* types */ - -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 unsigned char _Bool; - - /************************************************************/ /* obscure */ diff --git a/c/test_c.py b/c/test_c.py index 1864391..a42b9de 100644 --- a/c/test_c.py +++ b/c/test_c.py @@ -1102,7 +1102,7 @@ def test_load_and_call_function(): def test_read_variable(): ## FIXME: this test assumes glibc specific behavior, it's not compliant with C standard ## https://bugs.pypy.org/issue1643 - if sys.platform == 'win32' or sys.platform == 'darwin' or sys.platform.startswith('freebsd'): + if not sys.platform.startswith("linux"): py.test.skip("untested") BVoidP = new_pointer_type(new_void_type()) ll = find_and_load_library('c') @@ -1112,7 +1112,7 @@ def test_read_variable(): def test_read_variable_as_unknown_length_array(): ## FIXME: this test assumes glibc specific behavior, it's not compliant with C standard ## https://bugs.pypy.org/issue1643 - if sys.platform == 'win32' or sys.platform == 'darwin' or sys.platform.startswith('freebsd'): + if not sys.platform.startswith("linux"): py.test.skip("untested") BCharP = new_pointer_type(new_primitive_type("char")) BArray = new_array_type(BCharP, None) @@ -1124,7 +1124,7 @@ def test_read_variable_as_unknown_length_array(): def test_write_variable(): ## FIXME: this test assumes glibc specific behavior, it's not compliant with C standard ## https://bugs.pypy.org/issue1643 - if sys.platform == 'win32' or sys.platform == 'darwin' or sys.platform.startswith('freebsd'): + if not sys.platform.startswith("linux"): py.test.skip("untested") BVoidP = new_pointer_type(new_void_type()) ll = find_and_load_library('c') @@ -3199,4 +3199,4 @@ def test_packed_with_bitfields(): def test_version(): # this test is here mostly for PyPy - assert __version__ == "0.8.2" + assert __version__ == "0.8.3" diff --git a/cffi/__init__.py b/cffi/__init__.py index fa9e86f..4c84250 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.8.2" -__version_info__ = (0, 8, 2) +__version__ = "0.8.3" +__version_info__ = (0, 8, 3) diff --git a/cffi/api.py b/cffi/api.py index f44f086..0527a4b 100644 --- a/cffi/api.py +++ b/cffi/api.py @@ -443,6 +443,10 @@ def _make_ffi_library(ffi, libname, flags): for enumname, enumval in zip(tp.enumerators, tp.enumvalues): if enumname not in library.__dict__: library.__dict__[enumname] = enumval + for key, val in ffi._parser._int_constants.items(): + if key not in library.__dict__: + library.__dict__[key] = val + copied_enums.append(True) if name in library.__dict__: return diff --git a/cffi/cparser.py b/cffi/cparser.py index 99998ac..a53d4c3 100644 --- a/cffi/cparser.py +++ b/cffi/cparser.py @@ -24,6 +24,7 @@ _r_enum_dotdotdot = re.compile(r"__dotdotdot\d+__$") _r_partial_array = re.compile(r"\[\s*\.\.\.\s*\]") _r_words = re.compile(r"\w+|\S") _parser_cache = None +_r_int_literal = re.compile(r"^0?x?[0-9a-f]+u?l?$", re.IGNORECASE) def _get_parser(): global _parser_cache @@ -99,6 +100,7 @@ class Parser(object): self._structnode2type = weakref.WeakKeyDictionary() self._override = False self._packed = False + self._int_constants = {} def _parse(self, csource): csource, macros = _preprocess(csource) @@ -128,9 +130,10 @@ class Parser(object): finally: if lock is not None: lock.release() - return ast, macros + # csource will be used to find buggy source text + return ast, macros, csource - def convert_pycparser_error(self, e, csource): + def _convert_pycparser_error(self, e, csource): # xxx look for ":NUM:" at the start of str(e) and try to interpret # it as a line number line = None @@ -142,6 +145,12 @@ class Parser(object): csourcelines = csource.splitlines() if 1 <= linenum <= len(csourcelines): line = csourcelines[linenum-1] + return line + + def convert_pycparser_error(self, e, csource): + line = self._convert_pycparser_error(e, csource) + + msg = str(e) if line: msg = 'cannot parse "%s"\n%s' % (line.strip(), msg) else: @@ -160,14 +169,9 @@ class Parser(object): self._packed = prev_packed def _internal_parse(self, csource): - ast, macros = self._parse(csource) + ast, macros, csource = self._parse(csource) # add the macros - for key, value in macros.items(): - value = value.strip() - if value != '...': - raise api.CDefError('only supports the syntax "#define ' - '%s ..." for now (literally)' % key) - self._declare('macro ' + key, value) + self._process_macros(macros) # find the first "__dotdotdot__" and use that as a separator # between the repeated typedefs and the real csource iterator = iter(ast.ext) @@ -175,27 +179,61 @@ class Parser(object): if decl.name == '__dotdotdot__': break # - for decl in iterator: - if isinstance(decl, pycparser.c_ast.Decl): - self._parse_decl(decl) - elif isinstance(decl, pycparser.c_ast.Typedef): - if not decl.name: - raise api.CDefError("typedef does not declare any name", - decl) - if (isinstance(decl.type.type, pycparser.c_ast.IdentifierType) - and decl.type.type.names == ['__dotdotdot__']): - realtype = model.unknown_type(decl.name) - elif (isinstance(decl.type, pycparser.c_ast.PtrDecl) and - isinstance(decl.type.type, pycparser.c_ast.TypeDecl) and - isinstance(decl.type.type.type, - pycparser.c_ast.IdentifierType) and - decl.type.type.type.names == ['__dotdotdot__']): - realtype = model.unknown_ptr_type(decl.name) + try: + for decl in iterator: + if isinstance(decl, pycparser.c_ast.Decl): + self._parse_decl(decl) + elif isinstance(decl, pycparser.c_ast.Typedef): + if not decl.name: + raise api.CDefError("typedef does not declare any name", + decl) + if (isinstance(decl.type.type, pycparser.c_ast.IdentifierType) + and decl.type.type.names == ['__dotdotdot__']): + realtype = model.unknown_type(decl.name) + elif (isinstance(decl.type, pycparser.c_ast.PtrDecl) and + isinstance(decl.type.type, pycparser.c_ast.TypeDecl) and + isinstance(decl.type.type.type, + pycparser.c_ast.IdentifierType) and + decl.type.type.type.names == ['__dotdotdot__']): + realtype = model.unknown_ptr_type(decl.name) + else: + realtype = self._get_type(decl.type, name=decl.name) + self._declare('typedef ' + decl.name, realtype) else: - realtype = self._get_type(decl.type, name=decl.name) - self._declare('typedef ' + decl.name, realtype) + raise api.CDefError("unrecognized construct", decl) + except api.FFIError as e: + msg = self._convert_pycparser_error(e, csource) + if msg: + e.args = (e.args[0] + "\n *** Err: %s" % msg,) + raise + + def _add_constants(self, key, val): + if key in self._int_constants: + raise api.FFIError( + "multiple declarations of constant: %s" % (key,)) + self._int_constants[key] = val + + def _process_macros(self, macros): + for key, value in macros.items(): + value = value.strip() + match = _r_int_literal.search(value) + if match is not None: + int_str = match.group(0).lower().rstrip("ul") + + # "010" is not valid oct in py3 + if (int_str.startswith("0") and + int_str != "0" and + not int_str.startswith("0x")): + int_str = "0o" + int_str[1:] + + pyvalue = int(int_str, 0) + self._add_constants(key, pyvalue) + elif value == '...': + self._declare('macro ' + key, value) else: - raise api.CDefError("unrecognized construct", decl) + raise api.CDefError('only supports the syntax "#define ' + '%s ..." (literally) or "#define ' + '%s 0x1FF" for now' % (key, key)) def _parse_decl(self, decl): node = decl.type @@ -227,7 +265,7 @@ class Parser(object): self._declare('variable ' + decl.name, tp) def parse_type(self, cdecl): - ast, macros = self._parse('void __dummy(\n%s\n);' % cdecl) + ast, macros = self._parse('void __dummy(\n%s\n);' % cdecl)[:2] assert not macros exprnode = ast.ext[-1].type.args.params[0] if isinstance(exprnode, pycparser.c_ast.ID): @@ -306,7 +344,8 @@ class Parser(object): if ident == 'void': return model.void_type if ident == '__dotdotdot__': - raise api.FFIError('bad usage of "..."') + raise api.FFIError(':%d: bad usage of "..."' % + typenode.coord.line) return resolve_common_type(ident) # if isinstance(type, pycparser.c_ast.Struct): @@ -333,7 +372,8 @@ class Parser(object): return self._get_struct_union_enum_type('union', typenode, name, nested=True) # - raise api.FFIError("bad or unsupported type declaration") + raise api.FFIError(":%d: bad or unsupported type declaration" % + typenode.coord.line) def _parse_function_type(self, typenode, funcname=None): params = list(getattr(typenode.args, 'params', [])) @@ -499,6 +539,10 @@ class Parser(object): if (isinstance(exprnode, pycparser.c_ast.UnaryOp) and exprnode.op == '-'): return -self._parse_constant(exprnode.expr) + # load previously defined int constant + if (isinstance(exprnode, pycparser.c_ast.ID) and + exprnode.name in self._int_constants): + return self._int_constants[exprnode.name] # if partial_length_ok: if (isinstance(exprnode, pycparser.c_ast.ID) and @@ -506,8 +550,8 @@ class Parser(object): self._partial_length = True return '...' # - raise api.FFIError("unsupported expression: expected a " - "simple numeric constant") + raise api.FFIError(":%d: unsupported expression: expected a " + "simple numeric constant" % exprnode.coord.line) def _build_enum_type(self, explicit_name, decls): if decls is not None: @@ -522,6 +566,7 @@ class Parser(object): if enum.value is not None: nextenumvalue = self._parse_constant(enum.value) enumvalues.append(nextenumvalue) + self._add_constants(enum.name, nextenumvalue) nextenumvalue += 1 enumvalues = tuple(enumvalues) tp = model.EnumType(explicit_name, enumerators, enumvalues) @@ -535,3 +580,5 @@ class Parser(object): kind = name.split(' ', 1)[0] if kind in ('typedef', 'struct', 'union', 'enum'): self._declare(name, tp) + for k, v in other._int_constants.items(): + self._add_constants(k, v) diff --git a/cffi/ffiplatform.py b/cffi/ffiplatform.py index 460ba90..4515d6c 100644 --- a/cffi/ffiplatform.py +++ b/cffi/ffiplatform.py @@ -38,6 +38,7 @@ def _build(tmpdir, ext): import distutils.errors # dist = Distribution({'ext_modules': [ext]}) + dist.parse_config_files() options = dist.get_option_dict('build_ext') options['force'] = ('ffiplatform', True) options['build_lib'] = ('ffiplatform', tmpdir) diff --git a/cffi/vengine_cpy.py b/cffi/vengine_cpy.py index d9af334..31793f0 100644 --- a/cffi/vengine_cpy.py +++ b/cffi/vengine_cpy.py @@ -89,43 +89,54 @@ class VCPythonEngine(object): # 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(' {"_cffi_setup", _cffi_setup, METH_VARARGS, NULL},') + prnt(' {NULL, NULL, 0, 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' + constants = self._chained_list_constants[False] + prnt('#if PY_MAJOR_VERSION >= 3') + prnt() + 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() + prnt('PyMODINIT_FUNC') + prnt('PyInit_%s(void)' % modname) + prnt('{') + prnt(' PyObject *lib;') + prnt(' lib = PyModule_Create(&_cffi_module_def);') + prnt(' if (lib == NULL)') + prnt(' return NULL;') + prnt(' if (%s < 0 || _cffi_init() < 0) {' % (constants,)) + prnt(' Py_DECREF(lib);') + prnt(' return NULL;') + prnt(' }') + prnt(' return lib;') + prnt('}') + prnt() + prnt('#else') + prnt() prnt('PyMODINIT_FUNC') - prnt('%s(void)' % initname) + prnt('init%s(void)' % modname) 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(' lib = Py_InitModule("%s", _cffi_methods);' % modname) + prnt(' if (lib == NULL)') + prnt(' return;') + prnt(' if (%s < 0 || _cffi_init() < 0)' % (constants,)) + prnt(' return;') + prnt(' return;') prnt('}') + prnt() + prnt('#endif') def load_library(self): # XXX review all usages of 'self' here! @@ -394,7 +405,7 @@ class VCPythonEngine(object): meth = 'METH_O' else: meth = 'METH_VARARGS' - self._prnt(' {"%s", _cffi_f_%s, %s},' % (name, name, meth)) + self._prnt(' {"%s", _cffi_f_%s, %s, NULL},' % (name, name, meth)) _loading_cpy_function = _loaded_noop @@ -481,8 +492,8 @@ class VCPythonEngine(object): 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)) + self._prnt(' {"%s", %s, METH_NOARGS, NULL},' % (layoutfuncname, + layoutfuncname)) def _loading_struct_or_union(self, tp, prefix, name, module): if tp.fldnames is None: @@ -589,13 +600,7 @@ class VCPythonEngine(object): 'variable type'),)) 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(' o = _cffi_from_c_int_const(%s);' % name) prnt(' if (o == NULL)') prnt(' return -1;') if size_too: @@ -632,13 +637,18 @@ class VCPythonEngine(object): # ---------- # enums + def _enum_funcname(self, prefix, name): + # "$enum_$1" => "___D_enum____D_1" + name = name.replace('$', '___D_') + return '_cffi_e_%s_%s' % (prefix, name) + def _generate_cpy_enum_decl(self, tp, name, prefix='enum'): if tp.partial: for enumerator in tp.enumerators: self._generate_cpy_const(True, enumerator, delayed=False) return # - funcname = '_cffi_e_%s_%s' % (prefix, name) + funcname = self._enum_funcname(prefix, name) prnt = self._prnt prnt('static int %s(PyObject *lib)' % funcname) prnt('{') @@ -760,17 +770,30 @@ cffimod_header = r''' #include <Python.h> #include <stddef.h> -#ifdef MS_WIN32 -#include <malloc.h> /* for alloca() */ -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 unsigned char _Bool; +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py */ +#if defined(_MSC_VER) +# include <malloc.h> /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + 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; +# else +# include <stdint.h> +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ + typedef unsigned char _Bool; +# endif +#else +# include <stdint.h> +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) +# include <alloca.h> +# endif #endif #if PY_MAJOR_VERSION < 3 @@ -795,6 +818,15 @@ typedef unsigned char _Bool; #define _cffi_to_c_double PyFloat_AsDouble #define _cffi_to_c_float PyFloat_AsDouble +#define _cffi_from_c_int_const(x) \ + (((x) > 0) ? \ + ((unsigned long long)(x) <= (unsigned long long)LONG_MAX) ? \ + PyInt_FromLong((long)(x)) : \ + PyLong_FromUnsignedLongLong((unsigned long long)(x)) : \ + ((long long)(x) >= (long long)LONG_MIN) ? \ + PyInt_FromLong((long)(x)) : \ + PyLong_FromLongLong((long long)(x))) + #define _cffi_from_c_int(x, type) \ (((type)-1) > 0 ? /* unsigned */ \ (sizeof(type) < sizeof(long) ? PyInt_FromLong(x) : \ @@ -804,14 +836,14 @@ typedef unsigned char _Bool; PyLong_FromLongLong(x))) #define _cffi_to_c_int(o, type) \ - (sizeof(type) == 1 ? (((type)-1) > 0 ? _cffi_to_c_u8(o) \ - : _cffi_to_c_i8(o)) : \ - sizeof(type) == 2 ? (((type)-1) > 0 ? _cffi_to_c_u16(o) \ - : _cffi_to_c_i16(o)) : \ - sizeof(type) == 4 ? (((type)-1) > 0 ? _cffi_to_c_u32(o) \ - : _cffi_to_c_i32(o)) : \ - sizeof(type) == 8 ? (((type)-1) > 0 ? _cffi_to_c_u64(o) \ - : _cffi_to_c_i64(o)) : \ + (sizeof(type) == 1 ? (((type)-1) > 0 ? (type)_cffi_to_c_u8(o) \ + : (type)_cffi_to_c_i8(o)) : \ + sizeof(type) == 2 ? (((type)-1) > 0 ? (type)_cffi_to_c_u16(o) \ + : (type)_cffi_to_c_i16(o)) : \ + sizeof(type) == 4 ? (((type)-1) > 0 ? (type)_cffi_to_c_u32(o) \ + : (type)_cffi_to_c_i32(o)) : \ + sizeof(type) == 8 ? (((type)-1) > 0 ? (type)_cffi_to_c_u64(o) \ + : (type)_cffi_to_c_i64(o)) : \ (Py_FatalError("unsupported size for type " #type), 0)) #define _cffi_to_c_i8 \ @@ -885,25 +917,32 @@ static PyObject *_cffi_setup(PyObject *self, PyObject *args) return PyBool_FromLong(was_alive); } -static void _cffi_init(void) +static int _cffi_init(void) { - PyObject *module = PyImport_ImportModule("_cffi_backend"); - PyObject *c_api_object; + PyObject *module, *c_api_object = NULL; + module = PyImport_ImportModule("_cffi_backend"); if (module == NULL) - return; + goto failure; c_api_object = PyObject_GetAttrString(module, "_C_API"); if (c_api_object == NULL) - return; + goto failure; if (!PyCapsule_CheckExact(c_api_object)) { - Py_DECREF(c_api_object); PyErr_SetNone(PyExc_ImportError); - return; + goto failure; } memcpy(_cffi_exports, PyCapsule_GetPointer(c_api_object, "cffi"), _CFFI_NUM_EXPORTS * sizeof(void *)); + + Py_DECREF(module); Py_DECREF(c_api_object); + return 0; + + failure: + Py_XDECREF(module); + Py_XDECREF(c_api_object); + return -1; } #define _cffi_type(num) ((CTypeDescrObject *)PyList_GET_ITEM(_cffi_types, num)) diff --git a/cffi/vengine_gen.py b/cffi/vengine_gen.py index f8715c7..1a702c9 100644 --- a/cffi/vengine_gen.py +++ b/cffi/vengine_gen.py @@ -249,10 +249,10 @@ class VGenericEngine(object): prnt(' /* %s */' % str(e)) # cannot verify it, ignore prnt('}') self.export_symbols.append(layoutfuncname) - prnt('ssize_t %s(ssize_t i)' % (layoutfuncname,)) + prnt('intptr_t %s(intptr_t i)' % (layoutfuncname,)) prnt('{') prnt(' struct _cffi_aligncheck { char x; %s y; };' % cname) - prnt(' static ssize_t nums[] = {') + prnt(' static intptr_t nums[] = {') prnt(' sizeof(%s),' % cname) prnt(' offsetof(struct _cffi_aligncheck, y),') for fname, ftype, fbitsize in tp.enumfields(): @@ -276,7 +276,7 @@ class VGenericEngine(object): return # nothing to do with opaque structs layoutfuncname = '_cffi_layout_%s_%s' % (prefix, name) # - BFunc = self.ffi._typeof_locked("ssize_t(*)(ssize_t)")[0] + BFunc = self.ffi._typeof_locked("intptr_t(*)(intptr_t)")[0] function = module.load_function(BFunc, layoutfuncname) layout = [] num = 0 @@ -410,13 +410,18 @@ class VGenericEngine(object): # ---------- # enums + def _enum_funcname(self, prefix, name): + # "$enum_$1" => "___D_enum____D_1" + name = name.replace('$', '___D_') + return '_cffi_e_%s_%s' % (prefix, name) + def _generate_gen_enum_decl(self, tp, name, prefix='enum'): if tp.partial: for enumerator in tp.enumerators: self._generate_gen_const(True, enumerator) return # - funcname = '_cffi_e_%s_%s' % (prefix, name) + funcname = self._enum_funcname(prefix, name) self.export_symbols.append(funcname) prnt = self._prnt prnt('int %s(char *out_error)' % funcname) @@ -453,7 +458,7 @@ class VGenericEngine(object): else: BType = self.ffi._typeof_locked("char[]")[0] BFunc = self.ffi._typeof_locked("int(*)(char*)")[0] - funcname = '_cffi_e_%s_%s' % (prefix, name) + funcname = self._enum_funcname(prefix, name) function = module.load_function(BFunc, funcname) p = self.ffi.new(BType, 256) if function(p) < 0: @@ -547,20 +552,29 @@ cffimod_header = r''' #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; -typedef unsigned char _Bool; -#else +/* this block of #ifs should be kept exactly identical between + c/_cffi_backend.c, cffi/vengine_cpy.py, cffi/vengine_gen.py */ +#if defined(_MSC_VER) +# include <malloc.h> /* for alloca() */ +# if _MSC_VER < 1600 /* MSVC < 2010 */ + 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; +# else # include <stdint.h> +# endif +# if _MSC_VER < 1800 /* MSVC < 2013 */ + typedef unsigned char _Bool; +# endif +#else +# include <stdint.h> +# if (defined (__SVR4) && defined (__sun)) || defined(_AIX) +# include <alloca.h> +# endif #endif ''' diff --git a/doc/source/conf.py b/doc/source/conf.py index 17bcbd3..29cdb4d 100644 --- a/doc/source/conf.py +++ b/doc/source/conf.py @@ -47,7 +47,7 @@ copyright = u'2012, Armin Rigo, Maciej Fijalkowski' # The short X.Y version. version = '0.8' # The full version, including alpha/beta/rc tags. -release = '0.8.2' +release = '0.8.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 7f5120f..146faca 100644 --- a/doc/source/index.rst +++ b/doc/source/index.rst @@ -1,31 +1,34 @@ CFFI documentation ================================ -Foreign Function Interface for Python calling C code. The aim of this project -is to provide a convenient and reliable way of calling C code from Python. -The interface is based on `LuaJIT's FFI`_ and follows a few principles: +C Foreign Function Interface for Python. The goal is to provide a +convenient and reliable way to call compiled C code from Python using +interface declarations written in C. -* The goal is to call C code from Python. You should be able to do so - without learning a 3rd language: every alternative requires you to learn - their own language (Cython_, SWIG_) or API (ctypes_). So we tried to - assume that you know Python and C and minimize the extra bits of API that - you need to learn. +The interface is based on `LuaJIT's FFI`_, and follows a few principles: + +* The goal is to call C code from Python without learning a 3rd language: + existing alternatives require users to learn domain specific language + (Cython_, SWIG_) or API (ctypes_). The CFFI design requires users to know + only C and Python, minimizing the extra bits of API that need to be learned. * Keep all the Python-related logic in Python so that you don't need to write much C code (unlike `CPython native C extensions`_). -* Work either at the level of the ABI (Application Binary Interface) - or the API (Application Programming Interface). Usually, C - libraries have a specified C API but often not an ABI (e.g. they may +* The preferred way is to work at the level of the API (Application + Programming Interface): the C compiler is called from the declarations + you write to validate and link to the C language constructs. + Alternatively, it is also possible to work at the ABI level + (Application Binary Interface), the way ctypes_ work. + However, on non-Windows platforms, C libraries typically + have a specified C API but not an ABI (e.g. they may document a "struct" as having at least these fields, but maybe more). - (ctypes_ works at the ABI level, whereas Cython_ and `native C extensions`_ - work at the API level.) -* We try to be complete. For now some C99 constructs are not supported, +* Try to be complete. For now some C99 constructs are not supported, but all C89 should be, including macros (and including macro "abuses", which you can `manually wrap`_ in saner-looking C functions). -* We attempt to support both PyPy and CPython, with a reasonable path +* Attempt to support both PyPy and CPython, with a reasonable path for other Python implementations like IronPython and Jython. * Note that this project is **not** about embedding executable C code in @@ -38,7 +41,7 @@ The interface is based on `LuaJIT's FFI`_ and follows a few principles: .. _`CPython native C extensions`: http://docs.python.org/extending/extending.html .. _`native C extensions`: http://docs.python.org/extending/extending.html .. _`ctypes`: http://docs.python.org/library/ctypes.html -.. _`Weave`: http://www.scipy.org/Weave +.. _`Weave`: http://wiki.scipy.org/Weave .. _`manually wrap`: `The verification step`_ @@ -85,13 +88,13 @@ Requirements: Download and Installation: -* http://pypi.python.org/packages/source/c/cffi/cffi-0.8.2.tar.gz +* http://pypi.python.org/packages/source/c/cffi/cffi-0.8.3.tar.gz - Or grab the most current version by following the instructions below. - - MD5: 37fc88c62f40d04e8a18192433f951ec + - MD5: ... - - SHA: 75a6c433664a7a38d4d03cecbdc72cef4c3cceac + - SHA: ... * Or get it from the `Bitbucket page`_: ``hg clone https://bitbucket.org/cffi/cffi`` @@ -851,7 +854,7 @@ like ``ffi.new("int[%d]" % x)``. Indeed, this is not recommended: ``ffi`` normally caches the string ``"int[]"`` to not need to re-parse it all the time. -.. versionadded:: 0.9 +.. versionadded:: 0.8.2 The ``ffi.cdef()`` call takes an optional argument ``packed``: if True, then all structs declared within this cdef are "packed". This has a meaning similar to ``__attribute__((packed))`` in GCC. It @@ -1195,13 +1198,14 @@ an array.) owned memory will not be freed as long as the buffer is alive. Moreover buffer objects now support weakrefs to them. -.. versionchanged:: 0.9 - Before version 0.9, ``bytes(buf)`` was supported in Python 3 to get +.. versionchanged:: 0.8.2 + Before version 0.8.2, ``bytes(buf)`` was supported in Python 3 to get the content of the buffer, but on Python 2 it would return the repr ``<_cffi_backend.buffer object>``. This has been fixed. But you should avoid using ``str(buf)``: it now gives inconsistent results between Python 2 and Python 3 (this is similar to how ``str()`` - gives inconsistent results on regular byte strings). + gives inconsistent results on regular byte strings). Use ``buf[:]`` + instead. ``ffi.typeof("C type" or cdata object)``: return an object of type @@ -42,25 +42,14 @@ def _ask_pkg_config(resultlist, option, result_prefix='', sysroot=False): resultlist[:] = res def ask_supports_thread(): - if sys.platform == "darwin": - sys.stderr.write("OS/X: confusion between 'cc' versus 'gcc'") - sys.stderr.write(" (see issue 123)\n") - sys.stderr.write("will not use '__thread' in the C code\n") - return - import distutils.errors - from distutils.ccompiler import new_compiler - compiler = new_compiler(force=1) - try: - compiler.compile(['c/check__thread.c']) - except distutils.errors.CompileError: - sys.stderr.write("the above error message can be safely ignored;\n") - sys.stderr.write("will not use '__thread' in the C code\n") - else: + from distutils.core import Distribution + config = Distribution().get_command_obj('config') + ok = config.try_compile('__thread int some_threadlocal_variable_42;') + if ok: define_macros.append(('USE__THREAD', None)) - try: - os.unlink('c/check__thread.o') - except OSError: - pass + else: + sys.stderr.write("Note: will not use '__thread' in the C code\n") + sys.stderr.write("The above error message can be safely ignored\n") def use_pkg_config(): _ask_pkg_config(include_dirs, '--cflags-only-I', '-I', sysroot=True) @@ -124,7 +113,7 @@ Contact `Mailing list <https://groups.google.com/forum/#!forum/python-cffi>`_ """, - version='0.8.2', + version='0.8.3', packages=['cffi'], zip_safe=False, diff --git a/testing/backend_tests.py b/testing/backend_tests.py index 615e366..1ec806f 100644 --- a/testing/backend_tests.py +++ b/testing/backend_tests.py @@ -865,25 +865,25 @@ class BackendTests: def test_enum(self): ffi = FFI(backend=self.Backend()) - ffi.cdef("enum foo { A, B, CC, D };") - assert ffi.string(ffi.cast("enum foo", 0)) == "A" - assert ffi.string(ffi.cast("enum foo", 2)) == "CC" - assert ffi.string(ffi.cast("enum foo", 3)) == "D" + ffi.cdef("enum foo { A0, B0, CC0, D0 };") + assert ffi.string(ffi.cast("enum foo", 0)) == "A0" + assert ffi.string(ffi.cast("enum foo", 2)) == "CC0" + assert ffi.string(ffi.cast("enum foo", 3)) == "D0" assert ffi.string(ffi.cast("enum foo", 4)) == "4" - ffi.cdef("enum bar { A, B=-2, CC, D, E };") - assert ffi.string(ffi.cast("enum bar", 0)) == "A" - assert ffi.string(ffi.cast("enum bar", -2)) == "B" - assert ffi.string(ffi.cast("enum bar", -1)) == "CC" - assert ffi.string(ffi.cast("enum bar", 1)) == "E" + ffi.cdef("enum bar { A1, B1=-2, CC1, D1, E1 };") + assert ffi.string(ffi.cast("enum bar", 0)) == "A1" + assert ffi.string(ffi.cast("enum bar", -2)) == "B1" + assert ffi.string(ffi.cast("enum bar", -1)) == "CC1" + assert ffi.string(ffi.cast("enum bar", 1)) == "E1" assert ffi.cast("enum bar", -2) != ffi.cast("enum bar", -2) assert ffi.cast("enum foo", 0) != ffi.cast("enum bar", 0) assert ffi.cast("enum bar", 0) != ffi.cast("int", 0) - assert repr(ffi.cast("enum bar", -1)) == "<cdata 'enum bar' -1: CC>" + assert repr(ffi.cast("enum bar", -1)) == "<cdata 'enum bar' -1: CC1>" assert repr(ffi.cast("enum foo", -1)) == ( # enums are unsigned, if "<cdata 'enum foo' 4294967295>") # they contain no neg value - ffi.cdef("enum baz { A=0x1000, B=0x2000 };") - assert ffi.string(ffi.cast("enum baz", 0x1000)) == "A" - assert ffi.string(ffi.cast("enum baz", 0x2000)) == "B" + ffi.cdef("enum baz { A2=0x1000, B2=0x2000 };") + assert ffi.string(ffi.cast("enum baz", 0x1000)) == "A2" + assert ffi.string(ffi.cast("enum baz", 0x2000)) == "B2" def test_enum_in_struct(self): ffi = FFI(backend=self.Backend()) @@ -1322,6 +1322,16 @@ class BackendTests: e = ffi.cast("enum e", 0) assert ffi.string(e) == "AA" # pick the first one arbitrarily + def test_enum_refer_previous_enum_value(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef("enum e { AA, BB=2, CC=4, DD=BB, EE, FF=CC, GG=FF };") + assert ffi.string(ffi.cast("enum e", 2)) == "BB" + assert ffi.string(ffi.cast("enum e", 3)) == "EE" + assert ffi.sizeof("char[DD]") == 2 + assert ffi.sizeof("char[EE]") == 3 + assert ffi.sizeof("char[FF]") == 4 + assert ffi.sizeof("char[GG]") == 4 + def test_nested_anonymous_struct(self): ffi = FFI(backend=self.Backend()) ffi.cdef(""" @@ -1543,6 +1553,7 @@ class BackendTests: ffi2.include(ffi1) p = ffi2.cast("enum foo", 1) assert ffi2.string(p) == "FB" + assert ffi2.sizeof("char[FC]") == 2 def test_include_typedef_2(self): backend = self.Backend() @@ -1570,3 +1581,25 @@ class BackendTests: assert s[0].a == b'X' assert s[1].b == -4892220 assert s[1].a == b'Y' + + def test_define_integer_constant(self): + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + #define DOT_0 0 + #define DOT 100 + #define DOT_OCT 0100l + #define DOT_HEX 0x100u + #define DOT_HEX2 0X10 + #define DOT_UL 1000UL + enum foo {AA, BB=DOT, CC}; + """) + lib = ffi.dlopen(None) + assert ffi.string(ffi.cast("enum foo", 100)) == "BB" + assert lib.DOT_0 == 0 + assert lib.DOT == 100 + assert lib.DOT_OCT == 0o100 + assert lib.DOT_HEX == 0x100 + assert lib.DOT_HEX2 == 0x10 + assert lib.DOT_UL == 1000 + + diff --git a/testing/test_function.py b/testing/test_function.py index aab35de..8edd4ae 100644 --- a/testing/test_function.py +++ b/testing/test_function.py @@ -402,3 +402,18 @@ class TestFunction(object): if wr() is not None: import gc; gc.collect() assert wr() is None # 'data' does not leak + + def test_windows_stdcall(self): + if sys.platform != 'win32': + py.test.skip("Windows-only test") + if self.Backend is CTypesBackend: + py.test.skip("not with the ctypes backend") + ffi = FFI(backend=self.Backend()) + ffi.cdef(""" + BOOL QueryPerformanceFrequency(LONGLONG *lpFrequency); + """) + m = ffi.dlopen("Kernel32.dll") + p_freq = ffi.new("LONGLONG *") + res = m.QueryPerformanceFrequency(p_freq) + assert res != 0 + assert p_freq[0] != 0 diff --git a/testing/test_parsing.py b/testing/test_parsing.py index 3b59a69..594796a 100644 --- a/testing/test_parsing.py +++ b/testing/test_parsing.py @@ -161,9 +161,10 @@ def test_remove_comments(): def test_define_not_supported_for_now(): ffi = FFI(backend=FakeBackend()) - e = py.test.raises(CDefError, ffi.cdef, "#define FOO 42") - assert str(e.value) == \ - 'only supports the syntax "#define FOO ..." for now (literally)' + e = py.test.raises(CDefError, ffi.cdef, '#define FOO "blah"') + assert str(e.value) == ( + 'only supports the syntax "#define FOO ..." (literally)' + ' or "#define FOO 0x1FF" for now') def test_unnamed_struct(): ffi = FFI(backend=FakeBackend()) diff --git a/testing/test_verify.py b/testing/test_verify.py index ac0aaa4..9e4f51f 100644 --- a/testing/test_verify.py +++ b/testing/test_verify.py @@ -1,4 +1,4 @@ -import py +import py, re import sys, os, math, weakref from cffi import FFI, VerificationError, VerificationMissing, model from testing.support import * @@ -29,6 +29,24 @@ else: def setup_module(): import cffi.verifier cffi.verifier.cleanup_tmpdir() + # + # check that no $ sign is produced in the C file; it used to be the + # case that anonymous enums would produce '$enum_$1', which was + # used as part of a function name. GCC accepts such names, but it's + # apparently non-standard. + _r_comment = re.compile(r"/\*.*?\*/|//.*?$", re.DOTALL | re.MULTILINE) + _r_string = re.compile(r'\".*?\"') + def _write_source_and_check(self, file=None): + base_write_source(self, file) + if file is None: + f = open(self.sourcefilename) + data = f.read() + f.close() + data = _r_comment.sub(' ', data) + data = _r_string.sub('"skipped"', data) + assert '$' not in data + base_write_source = cffi.verifier.Verifier._write_source + cffi.verifier.Verifier._write_source = _write_source_and_check def test_module_type(): @@ -153,6 +171,9 @@ def test_longdouble_precision(): all_primitive_types = model.PrimitiveType.ALL_PRIMITIVE_TYPES +if sys.platform == 'win32': + all_primitive_types = all_primitive_types.copy() + del all_primitive_types['ssize_t'] all_integer_types = sorted(tp for tp in all_primitive_types if all_primitive_types[tp] == 'i') all_float_types = sorted(tp for tp in all_primitive_types @@ -1452,8 +1473,8 @@ def test_keepalive_ffi(): assert func() == 42 def test_FILE_stored_in_stdout(): - if sys.platform == 'win32': - py.test.skip("MSVC: cannot assign to stdout") + if not sys.platform.startswith('linux'): + py.test.skip("likely, we cannot assign to stdout") ffi = FFI() ffi.cdef("int printf(const char *, ...); FILE *setstdout(FILE *);") lib = ffi.verify(""" @@ -1636,8 +1657,8 @@ def test_callback_indirection(): ffi = FFI() ffi.cdef(""" int (*python_callback)(int how_many, int *values); - void *const c_callback; /* pass this ptr to C routines */ - int some_c_function(void *cb); + int (*const c_callback)(int,...); /* pass this ptr to C routines */ + int some_c_function(int(*cb)(int,...)); """) lib = ffi.verify(""" #include <stdarg.h> @@ -1884,3 +1905,60 @@ def test_ptr_to_opaque(): p = lib.f2(42) x = lib.f1(p) assert x == 42 + +def _run_in_multiple_threads(test1): + test1() + import sys + try: + import thread + except ImportError: + import _thread as thread + errors = [] + def wrapper(lock): + try: + test1() + except: + errors.append(sys.exc_info()) + lock.release() + locks = [] + for i in range(10): + _lock = thread.allocate_lock() + _lock.acquire() + thread.start_new_thread(wrapper, (_lock,)) + locks.append(_lock) + for _lock in locks: + _lock.acquire() + if errors: + raise errors[0][1] + +def test_errno_working_even_with_pypys_jit(): + ffi = FFI() + ffi.cdef("int f(int);") + lib = ffi.verify(""" + #include <errno.h> + int f(int x) { return (errno = errno + x); } + """) + @_run_in_multiple_threads + def test1(): + ffi.errno = 0 + for i in range(10000): + e = lib.f(1) + assert e == i + 1 + assert ffi.errno == e + for i in range(10000): + ffi.errno = i + e = lib.f(42) + assert e == i + 42 + +def test_getlasterror_working_even_with_pypys_jit(): + if sys.platform != 'win32': + py.test.skip("win32-only test") + ffi = FFI() + ffi.cdef("void SetLastError(DWORD);") + lib = ffi.dlopen("Kernel32.dll") + @_run_in_multiple_threads + def test1(): + for i in range(10000): + n = (1 << 29) + i + lib.SetLastError(n) + assert ffi.getwinerror()[0] == n |