Skip to content

Reject explicit redefinition of struct _IO_FILE in cdef() - #281

Merged
mattip merged 2 commits into
python-cffi:mainfrom
akashchamp:fix-file-special-casing-collision
Sep 27, 2026
Merged

mattip merged 2 commits into
python-cffi:mainfrom
akashchamp:fix-file-special-casing-collision

Conversation

@akashchamp

@akashchamp akashchamp commented Sep 25, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #149.

COMMON_TYPES['FILE'] in commontypes.py special-cases the struct name _IO_FILE internally to back the opaque FILE type. When a cdef() header gives an explicit body for struct _IO_FILE -- which happens with any ordinary preprocessed glibc <stdio.h>, where the real struct is spelled out -- cffi ends up with two conflicting internal type-table entries sharing that name. Nothing catches this 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 -- confirmed as this special-casing collision by @arigo in the thread.

This makes _get_struct_union_enum_type() reject that case immediately with a normal, catchable CDefError, at cdef() parse time, well before the recompiler ever generates anything or the extension gets loaded.

Verification:

  • Reproduced the original abort with a minimal cdef (struct _IO_FILE { int dummy; }; typedef struct _IO_FILE FILE; + ffi.new("struct _IO_FILE*") via an ABI-mode set_source(name, None) build, matching the issue's "out-of-line ABI level" setup): Fatal Python error: do_realize_lazy_struct_lock_held: lost a struct/union!, exit 134.
  • Reproduced it again against the reporter's own repro header (glibc's real preprocessed struct _IO_FILE from <stdio.h>, from https://github.com/lacraig2/repro_cffi).
  • With the fix, both now raise cffi.CDefError from cdef() instead of crashing.
  • Confirmed legitimate FILE usage still works: plain FILE * parameters, a forward-only struct _IO_FILE; declaration, and the workaround already noted in the issue thread (typedef void* FILE;).
  • Added a regression test in testing/cffi0/test_parsing.py.
  • python -m pytest testing/cffi0/test_parsing.py testing/cffi1/test_commontypes.py testing/cffi1/test_recompiler.py testing/cffi1/test_realize_c_type.py testing/cffi1/test_new_ffi_1.py (the modules covering parsing, common-type resolution, the recompiler and struct/type realization): 337 passed, 6 skipped.
  • python -m pytest testing/cffi1/test_verify1.py -k "file or File or FILE or stdout" (existing FILE-usage tests): 3 passed.

Prepared with AI assistance (Claude Code); verified locally with the commands above.

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 <stdio.h>, 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 python-cffi#149.
akashchamp added a commit to akashchamp/cffi that referenced this pull request Sep 25, 2026
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@akashchamp
akashchamp force-pushed the fix-file-special-casing-collision branch from 10204d5 to 22a29b9 Compare September 25, 2026 17:01
@mattip
mattip merged commit a077bdd into python-cffi:main Sep 27, 2026
38 checks passed
@mattip

mattip commented Sep 27, 2026

Copy link
Copy Markdown
Contributor

Thanks @akashchamp

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Fatal python error: do_realize_lazy_struct: lost a struct/union!

3 participants