diff options
author | Armin Rigo <arigo@tunes.org> | 2012-08-04 17:46:09 +0200 |
---|---|---|
committer | Armin Rigo <arigo@tunes.org> | 2012-08-04 17:46:09 +0200 |
commit | e2024b64eb37c607c0742caa3bb1af682e7855f5 (patch) | |
tree | 87499ea81aab472fb763bd2f5c146223217ee0ce | |
parent | 599300811cc1015773147b625d20f3e033865e2f (diff) | |
download | cffi-e2024b64eb37c607c0742caa3bb1af682e7855f5.tar.gz |
Merge the two verifiers into two VEngine classes. There is still a little bit
of code duplication but not too much.
-rw-r--r-- | cffi/vengine_cpy.py | 729 | ||||
-rw-r--r-- | cffi/vengine_gen.py | 420 | ||||
-rw-r--r-- | cffi/verifier.py | 466 | ||||
-rw-r--r-- | testing/test_verify.py | 22 | ||||
-rw-r--r-- | testing/test_vgen.py | 12 | ||||
-rw-r--r-- | testing/test_vgen2.py | 13 | ||||
-rw-r--r-- | testing/test_zdistutils.py | 277 |
7 files changed, 1380 insertions, 559 deletions
diff --git a/cffi/vengine_cpy.py b/cffi/vengine_cpy.py new file mode 100644 index 0000000..3ee3140 --- /dev/null +++ b/cffi/vengine_cpy.py @@ -0,0 +1,729 @@ +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 collect_types(self): + self._typesdict = {} + self._generate("collecttype") + + def _prnt(self, what=''): + print >> self._f, what + + 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() + prnt('PyMODINIT_FUNC') + prnt('init%s(void)' % modname) + prnt('{') + prnt(' PyObject *lib;') + prnt(' lib = Py_InitModule("%s", _cffi_methods);' % modname) + prnt(' if (lib == NULL || %s < 0)' % ( + self._chained_list_constants[False],)) + prnt(' return;') + prnt(' _cffi_init();') + 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, 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 = 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.iteritems(): + 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.iteritems(): + 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> + +#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 (!PyCObject_Check(c_api_object)) { + PyErr_SetNone(PyExc_ImportError); + return; + } + memcpy(_cffi_exports, PyCObject_AsVoidPtr(c_api_object), + _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..68b9224 --- /dev/null +++ b/cffi/vengine_gen.py @@ -0,0 +1,420 @@ +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 + + 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') + + 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' + funcdecl = ' _cffi_f_%s(%s)' % (name, 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('}') + 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) + 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 + 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) + 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 <stdint.h> +#include <stdarg.h> +#include <errno.h> +#include <sys/types.h> /* XXX for ssize_t */ +''' diff --git a/cffi/verifier.py b/cffi/verifier.py index 322169b..00aa5f5 100644 --- a/cffi/verifier.py +++ b/cffi/verifier.py @@ -1,26 +1,25 @@ import sys, os, binascii, imp, shutil -from . import model, ffiplatform 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 self.kwds = kwds + vengine_class = _locate_engine_class(ffi, force_generic_engine) + self._vengine = vengine_class(self) # - key = '\x00'.join(['2', sys.version[:3], __version__, preamble] + + key = '\x00'.join(['1', sys.version[:3], __version__, preamble] + ffi._cdefsources) - k1 = hex(binascii.crc32(key[0::2]) & 0xffffffff).lstrip('0').rstrip('L') - k2 = hex(binascii.crc32(key[1::2]) & 0xffffffff).lstrip('0').rstrip('L') - modulename = '_cffi_%s%s' % (k1, k2) + 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) @@ -71,7 +70,7 @@ class Verifier(object): return ffiplatform.get_extension(sourcename, modname, **self.kwds) def generates_python_module(self): - return False + return self._vengine._gen_python_module # ---------- @@ -84,36 +83,23 @@ class Verifier(object): if f is not None: f.close() self.modulefilename = filename + self._vengine.collect_types() self._status = 'module' - def _prnt(self, what=''): - print >> self._f, what - 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): - 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.preamble) - # - # call generate_cpy_xxx_decl(), for every xxx found from - # ffi._parser._declarations. This generates all the functions. - self._generate("decl") - def _compile_module(self): # compile this C source tmpdir = os.path.dirname(self.sourcefilename) @@ -128,411 +114,31 @@ class Verifier(object): self._status = 'module' def _load_library(self): - # XXX review all usages of 'self' here! - # import it with the CFFI backend - backend = self.ffi._backend - module = backend.load_library(self.modulefilename) - # - # 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 = 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_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.iteritems(): - 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) + return self._vengine.load_library() - 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_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 - - # ---------- - # typedefs: generates no code so far - - _generate_cpy_typedef_decl = _generate_nothing - _loading_cpy_typedef = _loaded_noop - _loaded_cpy_typedef = _loaded_noop - - # ---------- - # function declarations - - 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 _cffi_f_%s wrapper) - self._generate_cpy_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' - funcdecl = ' _cffi_f_%s(%s)' % (name, 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_cpy_function = _loaded_noop - - def _loaded_cpy_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_cpy_struct_decl(self, tp, name): - assert name == tp.name - self._generate_struct_or_union_decl(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('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_cpy_anonymous_decl(self, tp, name): - self._generate_struct_or_union_decl(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'): - prnt = self._prnt - funcname = '_cffi_%s_%s' % (category, name) - 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_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) - - _loading_cpy_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_cpy_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_cpy_enum_decl(self, tp, name): - if tp.partial: - for enumerator in tp.enumerators: - self._generate_cpy_const(True, enumerator) - return - # - funcname = '_cffi_enum_%s' % name - 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_cpy_enum = _loaded_noop - - def _loading_cpy_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_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) - - _loading_cpy_macro = _loaded_noop - - def _loaded_cpy_macro(self, tp, name, module, library): - value = self._load_constant(True, tp, name, module) - setattr(library, name, value) +# ____________________________________________________________ - # ---------- - # global variables +_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_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') - - _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 - # sense that "a=..." is forbidden - tp_ptr = model.PointerType(tp.item) - value = self._load_constant(False, tp_ptr, name, module) - 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 <stdint.h> -#include <stdarg.h> -#include <errno.h> -#include <sys/types.h> /* XXX for ssize_t */ -''' + 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/testing/test_verify.py b/testing/test_verify.py index e9f5d74..13e7192 100644 --- a/testing/test_verify.py +++ b/testing/test_verify.py @@ -15,6 +15,19 @@ def setup_module(): cffi.verifier.cleanup_tmpdir() +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 = FFI() ffi.cdef("void some_completely_unknown_function();") @@ -474,11 +487,14 @@ def test_access_callback(): lib.cb = my_callback assert lib.foo(4) == 887 -def test_cannot_verify_with_ctypes(): +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() 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 7272ab5..92b5dd8 100644 --- a/testing/test_zdistutils.py +++ b/testing/test_zdistutils.py @@ -1,134 +1,159 @@ -import os, imp, math, StringIO, random +import sys, os, imp, math, StringIO, 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 -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 file(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 file(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.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_') - 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(): - 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' - 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(): - 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("%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_checksum_of_csrc(): - names = [] - for csrc in ['123', '123', '1234']: + 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 file(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);") - 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 file(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!*/\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 file(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) + f = StringIO.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) + 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' + 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("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);") + 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 file(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 |