Author: Armin Rigo <ar...@tunes.org> Branch: release-1.11 Changeset: r3108:5ff833c65f91 Date: 2018-02-27 19:12 +0100 http://bitbucket.org/cffi/cffi/changeset/5ff833c65f91/
Log: hg merge default diff --git a/c/_cffi_backend.c b/c/_cffi_backend.c --- a/c/_cffi_backend.c +++ b/c/_cffi_backend.c @@ -3794,27 +3794,29 @@ static int check_bytes_for_float_compatible(PyObject *io, double *out_value) { if (PyBytes_Check(io)) { - if (PyBytes_GET_SIZE(io) != 1) { - Py_DECREF(io); - return -1; - } + if (PyBytes_GET_SIZE(io) != 1) + goto error; *out_value = (unsigned char)PyBytes_AS_STRING(io)[0]; return 1; } else if (PyUnicode_Check(io)) { char ignored[80]; cffi_char32_t ordinal; - if (_my_PyUnicode_AsSingleChar32(io, &ordinal, ignored) < 0) { - Py_DECREF(io); - return -1; - } + if (_my_PyUnicode_AsSingleChar32(io, &ordinal, ignored) < 0) + goto error; /* the signness of the 32-bit version of wide chars should not * matter here, because 'ordinal' comes from a normal Python * unicode string */ *out_value = ordinal; return 1; } + *out_value = 0; /* silence a gcc warning if this function is inlined */ return 0; + + error: + Py_DECREF(io); + *out_value = 0; /* silence a gcc warning if this function is inlined */ + return -1; } static PyObject *do_cast(CTypeDescrObject *ct, PyObject *ob) @@ -3982,7 +3984,8 @@ static void dl_dealloc(DynLibObject *dlobj) { - dlclose(dlobj->dl_handle); + if (dlobj->dl_handle != NULL) + dlclose(dlobj->dl_handle); free(dlobj->dl_name); PyObject_Del(dlobj); } @@ -3992,6 +3995,17 @@ return PyText_FromFormat("<clibrary '%s'>", dlobj->dl_name); } +static int dl_check_closed(DynLibObject *dlobj) +{ + if (dlobj->dl_handle == NULL) + { + PyErr_Format(PyExc_ValueError, "library '%s' has already been closed", + dlobj->dl_name); + return -1; + } + return 0; +} + static PyObject *dl_load_function(DynLibObject *dlobj, PyObject *args) { CTypeDescrObject *ct; @@ -4002,6 +4016,9 @@ &CTypeDescr_Type, &ct, &funcname)) return NULL; + if (dl_check_closed(dlobj) < 0) + return NULL; + if (!(ct->ct_flags & (CT_FUNCTIONPTR | CT_POINTER | CT_ARRAY))) { PyErr_Format(PyExc_TypeError, "function or pointer or array cdata expected, got '%s'", @@ -4034,6 +4051,9 @@ &CTypeDescr_Type, &ct, &varname)) return NULL; + if (dl_check_closed(dlobj) < 0) + return NULL; + dlerror(); /* clear error condition */ data = dlsym(dlobj->dl_handle, varname); if (data == NULL) { @@ -4059,6 +4079,9 @@ &CTypeDescr_Type, &ct, &varname, &value)) return NULL; + if (dl_check_closed(dlobj) < 0) + return NULL; + dlerror(); /* clear error condition */ data = dlsym(dlobj->dl_handle, varname); if (data == NULL) { @@ -4074,10 +4097,21 @@ return Py_None; } +static PyObject *dl_close_lib(DynLibObject *dlobj, PyObject *no_args) +{ + if (dl_check_closed(dlobj) < 0) + return NULL; + dlclose(dlobj->dl_handle); + dlobj->dl_handle = NULL; + Py_INCREF(Py_None); + return Py_None; +} + static PyMethodDef dl_methods[] = { {"load_function", (PyCFunction)dl_load_function, METH_VARARGS}, {"read_variable", (PyCFunction)dl_read_variable, METH_VARARGS}, {"write_variable", (PyCFunction)dl_write_variable, METH_VARARGS}, + {"close_lib", (PyCFunction)dl_close_lib, METH_NOARGS}, {NULL, NULL} /* sentinel */ }; @@ -4113,44 +4147,95 @@ dl_methods, /* tp_methods */ }; -static PyObject *b_load_library(PyObject *self, PyObject *args) -{ - char *filename_or_null, *printable_filename; +static void *b_do_dlopen(PyObject *args, char **p_printable_filename, + PyObject **p_temp) +{ + /* Logic to call the correct version of dlopen(). Returns NULL in case of error. + Otherwise, '*p_printable_filename' will point to a printable char version of + the filename (maybe utf-8-encoded). '*p_temp' will be set either to NULL or + to a temporary object that must be freed after looking at printable_filename. + */ void *handle; - DynLibObject *dlobj; + char *filename_or_null; int flags = 0; - + *p_temp = NULL; + if (PyTuple_GET_SIZE(args) == 0 || PyTuple_GET_ITEM(args, 0) == Py_None) { PyObject *dummy; if (!PyArg_ParseTuple(args, "|Oi:load_library", &dummy, &flags)) return NULL; filename_or_null = NULL; - } - else if (!PyArg_ParseTuple(args, "et|i:load_library", - Py_FileSystemDefaultEncoding, &filename_or_null, - &flags)) - return NULL; - + *p_printable_filename = "<None>"; + } + else + { + PyObject *s = PyTuple_GET_ITEM(args, 0); +#ifdef MS_WIN32 + Py_UNICODE *filenameW; + if (PyArg_ParseTuple(args, "u|i:load_library", &filenameW, &flags)) + { +#if PY_MAJOR_VERSION < 3 + s = PyUnicode_AsUTF8String(s); + if (s == NULL) + return NULL; + *p_temp = s; +#endif + *p_printable_filename = PyText_AsUTF8(s); + if (*p_printable_filename == NULL) + return NULL; + + handle = dlopenW(filenameW); + goto got_handle; + } + PyErr_Clear(); +#endif + if (!PyArg_ParseTuple(args, "et|i:load_library", + Py_FileSystemDefaultEncoding, &filename_or_null, &flags)) + return NULL; + + *p_printable_filename = PyText_AsUTF8(s); + if (*p_printable_filename == NULL) + return NULL; + } if ((flags & (RTLD_NOW | RTLD_LAZY)) == 0) flags |= RTLD_NOW; - printable_filename = filename_or_null ? filename_or_null : "<None>"; handle = dlopen(filename_or_null, flags); + +#ifdef MS_WIN32 + got_handle: +#endif if (handle == NULL) { const char *error = dlerror(); - PyErr_Format(PyExc_OSError, "cannot load library %s: %s", - printable_filename, error); + PyErr_Format(PyExc_OSError, "cannot load library '%s': %s", + *p_printable_filename, error); return NULL; } + return handle; +} + +static PyObject *b_load_library(PyObject *self, PyObject *args) +{ + char *printable_filename; + PyObject *temp; + void *handle; + DynLibObject *dlobj = NULL; + + handle = b_do_dlopen(args, &printable_filename, &temp); + if (handle == NULL) + goto error; dlobj = PyObject_New(DynLibObject, &dl_type); if (dlobj == NULL) { dlclose(handle); - return NULL; + goto error; } dlobj->dl_handle = handle; dlobj->dl_name = strdup(printable_filename); + + error: + Py_XDECREF(temp); return (PyObject *)dlobj; } @@ -4796,7 +4881,6 @@ if (PyText_GetSize(fname) == 0 && ftype->ct_flags & (CT_STRUCT|CT_UNION)) { /* a nested anonymous struct or union */ - /* note: it seems we only get here with ffi.verify() */ CFieldObject *cfsrc = (CFieldObject *)ftype->ct_extra; for (; cfsrc != NULL; cfsrc = cfsrc->cf_next) { /* broken complexity in the call to get_field_name(), diff --git a/c/cdlopen.c b/c/cdlopen.c --- a/c/cdlopen.c +++ b/c/cdlopen.c @@ -40,35 +40,18 @@ static PyObject *ffi_dlopen(PyObject *self, PyObject *args) { - char *filename_or_null, *printable_filename; + char *modname; + PyObject *temp, *result = NULL; void *handle; - int flags = 0; - if (PyTuple_GET_SIZE(args) == 0 || PyTuple_GET_ITEM(args, 0) == Py_None) { - PyObject *dummy; - if (!PyArg_ParseTuple(args, "|Oi:load_library", - &dummy, &flags)) - return NULL; - filename_or_null = NULL; + handle = b_do_dlopen(args, &modname, &temp); + if (handle != NULL) + { + result = (PyObject *)lib_internal_new((FFIObject *)self, + modname, handle); } - else if (!PyArg_ParseTuple(args, "et|i:load_library", - Py_FileSystemDefaultEncoding, &filename_or_null, - &flags)) - return NULL; - - if ((flags & (RTLD_NOW | RTLD_LAZY)) == 0) - flags |= RTLD_NOW; - printable_filename = filename_or_null ? filename_or_null : "<None>"; - - handle = dlopen(filename_or_null, flags); - if (handle == NULL) { - const char *error = dlerror(); - PyErr_Format(PyExc_OSError, "cannot load library '%s': %s", - printable_filename, error); - return NULL; - } - return (PyObject *)lib_internal_new((FFIObject *)self, - printable_filename, handle); + Py_XDECREF(temp); + return result; } static PyObject *ffi_dlclose(PyObject *self, PyObject *args) diff --git a/c/misc_win32.h b/c/misc_win32.h --- a/c/misc_win32.h +++ b/c/misc_win32.h @@ -192,7 +192,12 @@ static void *dlopen(const char *filename, int flag) { - return (void *)LoadLibrary(filename); + return (void *)LoadLibraryA(filename); +} + +static void *dlopenW(const wchar_t *filename) +{ + return (void *)LoadLibraryW(filename); } static void *dlsym(void *handle, const char *symbol) diff --git a/c/realize_c_type.c b/c/realize_c_type.c --- a/c/realize_c_type.c +++ b/c/realize_c_type.c @@ -737,13 +737,13 @@ return -1; } - if (fld->field_offset == (size_t)-1) { + if (ctf != NULL && fld->field_offset == (size_t)-1) { /* unnamed struct, with field positions and sizes entirely determined by complete_struct_or_union() and not checked. Or, bitfields (field_size >= 0), similarly not checked. */ assert(fld->field_size == (size_t)-1 || fbitsize >= 0); } - else if (detect_custom_layout(ct, SF_STD_FIELD_POS, + else if (ctf == NULL || detect_custom_layout(ct, SF_STD_FIELD_POS, ctf->ct_size, fld->field_size, "wrong size for field '", fld->name, "'") < 0) { diff --git a/cffi/_embedding.h b/cffi/_embedding.h --- a/cffi/_embedding.h +++ b/cffi/_embedding.h @@ -146,32 +146,6 @@ PyGILState_STATE state; PyObject *pycode=NULL, *global_dict=NULL, *x; -#if PY_MAJOR_VERSION >= 3 - /* see comments in _cffi_carefully_make_gil() about the - Python2/Python3 difference - */ -#else - /* Acquire the GIL. We have no threadstate here. If Python is - already initialized, it is possible that there is already one - existing for this thread, but it is not made current now. - */ - PyEval_AcquireLock(); - - _cffi_py_initialize(); - - /* The Py_InitializeEx() sometimes made a threadstate for us, but - not always. Indeed Py_InitializeEx() could be called and do - nothing. So do we have a threadstate, or not? We don't know, - but we can replace it with NULL in all cases. - */ - (void)PyThreadState_Swap(NULL); - - /* Now we can release the GIL and re-acquire immediately using the - logic of PyGILState(), which handles making or installing the - correct threadstate. - */ - PyEval_ReleaseLock(); -#endif state = PyGILState_Ensure(); /* Call the initxxx() function from the present module. It will @@ -278,16 +252,14 @@ that we don't hold the GIL before (if it exists), and we don't hold it afterwards. - What it really does is completely different in Python 2 and - Python 3. + (What it really does used to be completely different in Python 2 + and Python 3, with the Python 2 solution avoiding the spin-lock + around the Py_InitializeEx() call. However, after recent changes + to CPython 2.7 (issue #358) it no longer works. So we use the + Python 3 solution everywhere.) - Python 2 - ======== - - Initialize the GIL, without initializing the rest of Python, - by calling PyEval_InitThreads(). - - PyEval_InitThreads() must not be called concurrently at all. + This initializes Python by calling Py_InitializeEx(). + Important: this must not be called concurrently at all. So we use a global variable as a simple spin lock. This global variable must be from 'libpythonX.Y.so', not from this cffi-based extension module, because it must be shared from @@ -297,18 +269,6 @@ string "ENDMARKER". We change it temporarily to point to the next character in that string. (Yes, I know it's REALLY obscure.) - - Python 3 - ======== - - In Python 3, PyEval_InitThreads() cannot be called before - Py_InitializeEx() any more. So this function calls - Py_InitializeEx() first. It uses the same obscure logic to - make sure we never call it concurrently. - - Arguably, this is less good on the spinlock, because - Py_InitializeEx() takes much longer to run than - PyEval_InitThreads(). But I didn't find a way around it. */ #ifdef WITH_THREAD @@ -332,8 +292,7 @@ } #endif -#if PY_MAJOR_VERSION >= 3 - /* Python 3: call Py_InitializeEx() */ + /* call Py_InitializeEx() */ { PyGILState_STATE state = PyGILState_UNLOCKED; if (!Py_IsInitialized()) @@ -344,17 +303,6 @@ PyEval_InitThreads(); PyGILState_Release(state); } -#else - /* Python 2: call PyEval_InitThreads() */ -# ifdef WITH_THREAD - if (!PyEval_ThreadsInitialized()) { - PyEval_InitThreads(); /* makes the GIL */ - PyEval_ReleaseLock(); /* then release it */ - } - /* else: there is already a GIL, but we still needed to do the - spinlock dance to make sure that we see it as fully ready */ -# endif -#endif #ifdef WITH_THREAD /* release the lock */ diff --git a/cffi/api.py b/cffi/api.py --- a/cffi/api.py +++ b/cffi/api.py @@ -143,6 +143,13 @@ self._libraries.append(lib) return lib + def dlclose(self, lib): + """Close a library obtained with ffi.dlopen(). After this call, + access to functions or variables from the library will fail + (possibly with a segmentation fault). + """ + type(lib).__cffi_close__(lib) + def _typeof_locked(self, cdecl): # call me with the lock! key = cdecl @@ -898,6 +905,9 @@ return addressof_var(name) raise AttributeError("cffi library has no function or " "global variable named '%s'" % (name,)) + def __cffi_close__(self): + backendlib.close_lib() + self.__dict__.clear() # if libname is not None: try: diff --git a/cffi/model.py b/cffi/model.py --- a/cffi/model.py +++ b/cffi/model.py @@ -352,21 +352,20 @@ self.fldquals = fldquals self.build_c_name_with_marker() - def has_anonymous_struct_fields(self): - if self.fldtypes is None: - return False - for name, type in zip(self.fldnames, self.fldtypes): - if name == '' and isinstance(type, StructOrUnion): - return True - return False + def anonymous_struct_fields(self): + if self.fldtypes is not None: + for name, type in zip(self.fldnames, self.fldtypes): + if name == '' and isinstance(type, StructOrUnion): + yield type - def enumfields(self): + def enumfields(self, expand_anonymous_struct_union=True): fldquals = self.fldquals if fldquals is None: fldquals = (0,) * len(self.fldnames) for name, type, bitsize, quals in zip(self.fldnames, self.fldtypes, self.fldbitsize, fldquals): - if name == '' and isinstance(type, StructOrUnion): + if (name == '' and isinstance(type, StructOrUnion) + and expand_anonymous_struct_union): # nested anonymous struct/union for result in type.enumfields(): yield result diff --git a/cffi/recompiler.py b/cffi/recompiler.py --- a/cffi/recompiler.py +++ b/cffi/recompiler.py @@ -836,6 +836,10 @@ def _struct_collecttype(self, tp): self._do_collect_type(tp) + if self.target_is_python: + # also requires nested anon struct/unions in ABI mode, recursively + for fldtype in tp.anonymous_struct_fields(): + self._struct_collecttype(fldtype) def _struct_decl(self, tp, cname, approxname): if tp.fldtypes is None: @@ -884,7 +888,7 @@ named_ptr not in self.ffi._parser._included_declarations)): if tp.fldtypes is None: pass # opaque - elif tp.partial or tp.has_anonymous_struct_fields(): + elif tp.partial or any(tp.anonymous_struct_fields()): pass # field layout obtained silently from the C compiler else: flags.append("_CFFI_F_CHECK_FIELDS") @@ -896,7 +900,8 @@ flags = '|'.join(flags) or '0' c_fields = [] if reason_for_not_expanding is None: - enumfields = list(tp.enumfields()) + expand_anonymous_struct_union = not self.target_is_python + enumfields = list(tp.enumfields(expand_anonymous_struct_union)) for fldname, fldtype, fbitsize, fqual in enumfields: fldtype = self._field_type(tp, fldname, fldtype) self._check_not_opaque(fldtype, diff --git a/cffi/setuptools_ext.py b/cffi/setuptools_ext.py --- a/cffi/setuptools_ext.py +++ b/cffi/setuptools_ext.py @@ -148,8 +148,8 @@ def _add_py_module(dist, ffi, module_name): from distutils.dir_util import mkpath - from distutils.command.build_py import build_py - from distutils.command.build_ext import build_ext + from setuptools.command.build_py import build_py + from setuptools.command.build_ext import build_ext from distutils import log from cffi import recompiler @@ -169,6 +169,17 @@ generate_mod(os.path.join(self.build_lib, *module_path)) dist.cmdclass['build_py'] = build_py_make_mod + # distutils and setuptools have no notion I could find of a + # generated python module. If we don't add module_name to + # dist.py_modules, then things mostly work but there are some + # combination of options (--root and --record) that will miss + # the module. So we add it here, which gives a few apparently + # harmless warnings about not finding the file outside the + # build directory. + if dist.py_modules is None: + dist.py_modules = [] + dist.py_modules.append(module_name) + # the following is only for "build_ext -i" base_class_2 = dist.cmdclass.get('build_ext', build_ext) class build_ext_make_mod(base_class_2): diff --git a/doc/source/cdef.rst b/doc/source/cdef.rst --- a/doc/source/cdef.rst +++ b/doc/source/cdef.rst @@ -560,7 +560,13 @@ ``NAME.cpython-35m-x86_64-linux-gnu.so``. You can manually rename it to ``NAME.abi3.so``, or use setuptools version 26 or later. Also, note that compiling with a debug version of Python will not actually define -``Py_LIMITED_API``, as doing so makes ``Python.h`` unhappy. +``Py_LIMITED_API``, as doing so makes ``Python.h`` unhappy. Finally, +``Py_LIMITED_API`` is not defined on Windows, because this makes +modules which cannot be used with ``virtualenv`` (issues `#355`__ and +`#350`__). + +.. __: https://bitbucket.org/cffi/cffi/issues/355/importerror-dll-load-failed-on-windows +.. __: https://bitbucket.org/cffi/cffi/issues/350/issue-with-py_limited_api-on-windows **ffibuilder.compile(tmpdir='.', verbose=False, debug=None):** explicitly generate the .py or .c file, diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst --- a/doc/source/whatsnew.rst +++ b/doc/source/whatsnew.rst @@ -2,6 +2,42 @@ What's New ====================== + +v1.11.5 +======= + +* `Issue #357`_: fix ``ffi.emit_python_code()`` which generated a buggy + Python file if you are using a ``struct`` with an anonymous ``union`` + field or vice-versa. + +* Windows: ``ffi.dlopen()`` should now handle unicode filenames. + +* ABI mode: implemented ``ffi.dlclose()`` for the in-line case (it used + to be present only in the out-of-line case). + +* Fixed a corner case for ``setup.py install --record=xx --root=yy`` + with an out-of-line ABI module. Also fixed `Issue #345`_. + +* More hacks on Windows for running CFFI's own ``setup.py``. + +* `Issue #358`_: in embedding, to protect against (the rare case of) + Python initialization from several threads in parallel, we have to use + a spin-lock. On CPython 3 it is worse because it might spin-lock for + a long time (execution of ``Py_InitializeEx()``). Sadly, recent + changes to CPython make that solution needed on CPython 2 too. + +* CPython 3 on Windows: we no longer compile with ``Py_LIMITED_API`` + by default because such modules cannot be used with virtualenv. + `Issue #350`_ mentions a workaround if you still want that and are not + concerned about virtualenv: pass a ``define_macros=[("Py_LIMITED_API", + None)]`` to the ``ffibuilder.set_source()`` call. + +.. _`Issue #345`: https://bitbucket.org/cffi/cffi/issues/345/ +.. _`Issue #350`: https://bitbucket.org/cffi/cffi/issues/350/ +.. _`Issue #358`: https://bitbucket.org/cffi/cffi/issues/358/ +.. _`Issue #357`: https://bitbucket.org/cffi/cffi/issues/357/ + + v1.11.4 ======= diff --git a/setup.py b/setup.py --- a/setup.py +++ b/setup.py @@ -2,6 +2,10 @@ import subprocess import errno +# on Windows we give up and always import setuptools early to fix things for us +if sys.platform == "win32": + import setuptools + sources = ['c/_cffi_backend.c'] libraries = ['ffi'] diff --git a/testing/cffi0/test_function.py b/testing/cffi0/test_function.py --- a/testing/cffi0/test_function.py +++ b/testing/cffi0/test_function.py @@ -499,3 +499,23 @@ """) m = ffi.dlopen(lib_m) assert dir(m) == ['MYE1', 'MYE2', 'MYFOO', 'myconst', 'myfunc', 'myvar'] + + def test_dlclose(self): + if self.Backend is CTypesBackend: + py.test.skip("not with the ctypes backend") + ffi = FFI(backend=self.Backend()) + ffi.cdef("int foobar(void); int foobaz;") + lib = ffi.dlopen(lib_m) + ffi.dlclose(lib) + e = py.test.raises(ValueError, ffi.dlclose, lib) + assert str(e.value).startswith("library '") + assert str(e.value).endswith("' has already been closed") + e = py.test.raises(ValueError, getattr, lib, 'foobar') + assert str(e.value).startswith("library '") + assert str(e.value).endswith("' has already been closed") + e = py.test.raises(ValueError, getattr, lib, 'foobaz') + assert str(e.value).startswith("library '") + assert str(e.value).endswith("' has already been closed") + e = py.test.raises(ValueError, setattr, lib, 'foobaz', 42) + assert str(e.value).startswith("library '") + assert str(e.value).endswith("' has already been closed") diff --git a/testing/cffi0/test_ownlib.py b/testing/cffi0/test_ownlib.py --- a/testing/cffi0/test_ownlib.py +++ b/testing/cffi0/test_ownlib.py @@ -114,8 +114,12 @@ if sys.platform == 'win32': import os # did we already build it? - if os.path.exists(str(udir.join('testownlib.dll'))): - cls.module = str(udir.join('testownlib.dll')) + if cls.Backend is CTypesBackend: + dll_path = str(udir) + '\\testownlib1.dll' # only ascii for the ctypes backend + else: + dll_path = str(udir) + '\\' + (u+'testownlib\u03be.dll') # non-ascii char + if os.path.exists(dll_path): + cls.module = dll_path return # try (not too hard) to find the version used to compile this python # no mingw @@ -135,8 +139,9 @@ if os.path.isfile(vcvarsall): cmd = '"%s" %s' % (vcvarsall, arch) + ' & cl.exe testownlib.c ' \ ' /LD /Fetestownlib.dll' - subprocess.check_call(cmd, cwd = str(udir), shell=True) - cls.module = str(udir.join('testownlib.dll')) + subprocess.check_call(cmd, cwd = str(udir), shell=True) + os.rename(str(udir) + '\\testownlib.dll', dll_path) + cls.module = dll_path else: subprocess.check_call( 'cc testownlib.c -shared -fPIC -o testownlib.so', diff --git a/testing/cffi1/test_re_python.py b/testing/cffi1/test_re_python.py --- a/testing/cffi1/test_re_python.py +++ b/testing/cffi1/test_re_python.py @@ -1,8 +1,9 @@ -import sys +import sys, os import py from cffi import FFI from cffi import recompiler, ffiplatform, VerificationMissing from testing.udir import udir +from testing.support import u def setup_module(mod): @@ -35,6 +36,13 @@ 'globalconst42', 'globalconsthello'] ) outputfilename = ffiplatform.compile(str(tmpdir), ext) + if sys.platform == "win32": + # test with a non-ascii char + outputfn1 = outputfilename + ofn, oext = os.path.splitext(outputfn1) + outputfilename = ofn + (u+'\u03be') + oext + #print(repr(outputfn1) + ' ==> ' + repr(outputfilename)) + os.rename(outputfn1, outputfilename) mod.extmod = outputfilename mod.tmpdir = tmpdir # @@ -55,6 +63,9 @@ typedef struct bar_s { int x; signed char a[]; } bar_t; enum foo_e { AA, BB, CC }; int strlen(const char *); + struct with_union { union { int a; char b; }; }; + union with_struct { struct { int a; char b; }; }; + struct NVGcolor { union { float rgba[4]; struct { float r,g,b,a; }; }; }; """) ffi.set_source('re_python_pysrc', None) ffi.emit_python_code(str(tmpdir.join('re_python_pysrc.py'))) @@ -104,12 +115,16 @@ from re_python_pysrc import ffi lib = ffi.dlopen(extmod) ffi.dlclose(lib) + if type(extmod) is not str: # unicode, on python 2 + str_extmod = extmod.encode('utf-8') + else: + str_extmod = extmod e = py.test.raises(ffi.error, ffi.dlclose, lib) assert str(e.value).startswith( - "library '%s' is already closed" % (extmod,)) + "library '%s' is already closed" % (str_extmod,)) e = py.test.raises(ffi.error, getattr, lib, 'add42') assert str(e.value) == ( - "library '%s' has been closed" % (extmod,)) + "library '%s' has been closed" % (str_extmod,)) def test_constant_via_lib(): from re_python_pysrc import ffi @@ -212,3 +227,23 @@ ffi.set_source('test_partial_enum', None) py.test.raises(VerificationMissing, ffi.emit_python_code, str(tmpdir.join('test_partial_enum.py'))) + +def test_anonymous_union_inside_struct(): + # based on issue #357 + from re_python_pysrc import ffi + INT = ffi.sizeof("int") + assert ffi.offsetof("struct with_union", "a") == 0 + assert ffi.offsetof("struct with_union", "b") == 0 + assert ffi.sizeof("struct with_union") == INT + # + assert ffi.offsetof("union with_struct", "a") == 0 + assert ffi.offsetof("union with_struct", "b") == INT + assert ffi.sizeof("union with_struct") >= INT + 1 + # + FLOAT = ffi.sizeof("float") + assert ffi.sizeof("struct NVGcolor") == FLOAT * 4 + assert ffi.offsetof("struct NVGcolor", "rgba") == 0 + assert ffi.offsetof("struct NVGcolor", "r") == 0 + assert ffi.offsetof("struct NVGcolor", "g") == FLOAT + assert ffi.offsetof("struct NVGcolor", "b") == FLOAT * 2 + assert ffi.offsetof("struct NVGcolor", "a") == FLOAT * 3 diff --git a/testing/cffi1/test_recompiler.py b/testing/cffi1/test_recompiler.py --- a/testing/cffi1/test_recompiler.py +++ b/testing/cffi1/test_recompiler.py @@ -2297,3 +2297,11 @@ else: assert lib.__loader__ is None assert lib.__spec__ is None + +def test_realize_struct_error(): + ffi = FFI() + ffi.cdef("""typedef ... foo_t; struct foo_s { void (*x)(foo_t); };""") + lib = verify(ffi, "test_realize_struct_error", """ + typedef int foo_t; struct foo_s { void (*x)(foo_t); }; + """) + py.test.raises(TypeError, ffi.new, "struct foo_s *") _______________________________________________ pypy-commit mailing list pypy-commit@python.org https://mail.python.org/mailman/listinfo/pypy-commit