diff --git a/doc/source/whatsnew.rst b/doc/source/whatsnew.rst index c331f0d5..8ef0a42a 100644 --- a/doc/source/whatsnew.rst +++ b/doc/source/whatsnew.rst @@ -11,8 +11,14 @@ v2.2.0.dev0 (`#265`_). * The C source generated by ``cffi-gen-src`` and ``ffibuilder.emit_c_code()`` no longer depends on the interpreter running the generator. +* ``cdef()`` now raises a clean ``CDefError`` instead of crashing the whole + process with a fatal error when given an explicit definition of + ``struct _IO_FILE``, the name cffi uses internally for the opaque + ``FILE`` type. This could happen with an already-preprocessed system + header, which spells out the real struct. (`#281`_). .. _`#265`: https://github.com/python-cffi/cffi/pull/265 +.. _`#281`: https://github.com/python-cffi/cffi/pull/281 v2.1.0 ====== diff --git a/src/cffi/cparser.py b/src/cffi/cparser.py index 28f60ca0..30b97788 100644 --- a/src/cffi/cparser.py +++ b/src/cffi/cparser.py @@ -825,6 +825,23 @@ def _get_struct_union_enum_type(self, kind, type, name=None, nested=False): if type.decls is None: return tp # + if (kind == 'struct' and name is not None and + name == COMMON_TYPES['FILE'].name): + # cffi internally special-cases the C name used here (e.g. + # '_IO_FILE' on Linux) to mean the opaque FILE type: giving an + # explicit body for it collides with that special-casing and + # used to crash the process with a fatal error at runtime + # instead of failing cleanly here (issue #149). This commonly + # happens when cdef() is fed an already-preprocessed system + # header, which spells out the real definition of this struct. + raise CDefError( + "'%s %s' is the struct name that cffi uses internally for " + "the opaque 'FILE' type; giving an explicit definition for " + "it in cdef() is not supported. If this comes from a " + "preprocessed header, remove that struct definition (its " + "fields are not going to be used by cffi anyway)." + % (kind, name)) + # if tp.fldnames is not None: raise CDefError("duplicate declaration of struct %s" % name) fldnames = [] diff --git a/testing/cffi0/test_parsing.py b/testing/cffi0/test_parsing.py index 3e47bcdd..4866580b 100644 --- a/testing/cffi0/test_parsing.py +++ b/testing/cffi0/test_parsing.py @@ -382,6 +382,34 @@ def test_redefine_common_type(): ffi = FFI() ffi.cdef("typedef bool (*fn_t)(bool, bool);") # "bool," but within "( )" +def test_explicit_struct_IO_FILE_rejected(): + # Explicitly giving a body for the struct that cffi uses internally + # to back the opaque 'FILE' type used to build fine but then crash + # the whole process with a fatal error the first time the type was + # used (issue #149). It's now rejected right away in cdef(). This + # is a realistic case: it happens when cdef() is fed a header that + # went through the C preprocessor, which spells out the real + # definition of 'struct _IO_FILE' coming from . + ffi = FFI() + e = pytest.raises(CDefError, ffi.cdef, """ + struct _IO_FILE { + int dummy; + }; + typedef struct _IO_FILE FILE; + """) + assert 'struct _IO_FILE' in str(e.value) + assert 'FILE' in str(e.value) + # a forward declaration only (no body) is not ambiguous and is fine + ffi = FFI() + ffi.cdef(""" + struct _IO_FILE; + typedef struct _IO_FILE FILE; + int fputs(const char *, FILE *); + """) + # plain use of FILE without redefining the struct still works + ffi = FFI() + ffi.cdef("int fputs(const char *, FILE *);") + def test_bool(): ffi = FFI() ffi.cdef("void f(bool);")