From 703ac82d9088b27613fb93f499ba1d7413cf7203 Mon Sep 17 00:00:00 2001 From: Akash Kumar <116457960+akashchamp@users.noreply.github.com> Date: Fri, 25 Sep 2026 18:09:33 +0530 Subject: [PATCH 1/2] Reject explicit redefinition of struct _IO_FILE in cdef() cffi special-cases the struct name `_IO_FILE` internally to back the opaque `FILE` type (COMMON_TYPES['FILE'] in commontypes.py). When a cdef() header gives an explicit body for `struct _IO_FILE` -- which happens with any ordinary preprocessed glibc , where the real struct is spelled out -- cffi ends up with two conflicting internal type-table entries sharing that name. The collision isn't caught anywhere at cdef()/build time; it only surfaces later, the first time the type gets realized (e.g. `ffi.new("struct _IO_FILE*")`), as a process-aborting "Fatal Python error: do_realize_lazy_struct: lost a struct/union!" instead of a normal Python exception. Detect the collision in _get_struct_union_enum_type() and raise a CDefError as soon as cdef() sees the offending struct body, well before recompiler.py generates anything or the extension is ever loaded. Fixes #149. --- src/cffi/cparser.py | 17 +++++++++++++++++ testing/cffi0/test_parsing.py | 28 ++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+) 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);") From 22a29b9cb4221921d213b7f2bace2c253187c6d4 Mon Sep 17 00:00:00 2001 From: Akash Kumar <116457960+akashchamp@users.noreply.github.com> Date: Fri, 25 Sep 2026 18:12:32 +0530 Subject: [PATCH 2/2] Add changelog entry for #281 --- doc/source/whatsnew.rst | 6 ++++++ 1 file changed, 6 insertions(+) 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 ======