Skip to content

extend pyo3-ffi-check to check functions#5965

Merged
davidhewitt merged 16 commits into
PyO3:mainfrom
davidhewitt:ffi-check-functions
May 11, 2026
Merged

extend pyo3-ffi-check to check functions#5965
davidhewitt merged 16 commits into
PyO3:mainfrom
davidhewitt:ffi-check-functions

Conversation

@davidhewitt
Copy link
Copy Markdown
Member

This attempts to add more correctness to pyo3-ffi by validating that external function symbols actually exist and have the same number of arguments. This is quite a step forward, where pyo3-ffi-check currently doesn't check anything for functions.

@ngoldbaum
Copy link
Copy Markdown
Contributor

Let me know if you want some support with this. I'd really like to extend the FFI checking to abi3 and abi3t as well.

@davidhewitt davidhewitt added the CI-no-fail-fast If one job fails, allow the rest to keep testing label Apr 13, 2026
@davidhewitt
Copy link
Copy Markdown
Member Author

Help is welcome, yes. Fixing all the definitions for all versions looks like it might take a bit of work, worse I am running into variants of #4882 when trying to try to test for different versions. Maybe I will first try to get #5862 merged first which will simplify the build process and make it easier to probe.

Feel free to push to this PR in the meanwhile.

@davidhewitt davidhewitt mentioned this pull request Apr 30, 2026
@codspeed-hq
Copy link
Copy Markdown

codspeed-hq Bot commented May 10, 2026

Merging this PR will not alter performance

✅ 125 untouched benchmarks


Comparing davidhewitt:ffi-check-functions (79c352c) with main (65d6a36)

Open in CodSpeed

@davidhewitt davidhewitt added the CI-skip-changelog Skip checking changelog entry label May 10, 2026
Copy link
Copy Markdown
Member Author

@davidhewitt davidhewitt left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this is ready for review. It's quite a bit of fiddly machinery; ultimately what we gain is:

  • We have a way to verify symbols are correctly exposing either an exported symbol, a static inline function, or a macro reimplementation.
  • We have a way to verify that all functions have the correct argument count (not necessarily correct types).

This has caught quite a number of cases where we've got slightly incorrect definitions one way or another. I originally started fixing in this PR, but I decided to roll that work back because the fixes became numerous enough that I felt they deserved their own PRs.

One exception - I updated a bunch of functions which are removed on Python 3.15, as prerelease software I didn't think they needed a changelog and it meant we got to a green CI in fewer hops.

Comment on lines +443 to +508
// TODO: probably need to clean these up
const EXCLUDED_SYMBOLS: &[&str] = &[
// CPython deprecated these but the symbols still exist, pyo3-ffi will probably clean them up anyway
"_PyCode_GetExtra",
"_PyCode_SetExtra",
"_PyEval_RequestCodeExtraIndex",
// FIXME: probably outdated definitions that fail to build, need investigation,
// temporarily here to make the build pass to get CI running
"_PyFloat_CAST",
"_PyObject_MakeTpCall",
"_PyRun_AnyFileObject",
"_PyRun_InteractiveLoopObject",
"_PyRun_SimpleFileObject",
"_PySequence_IterSearch",
"_PySet_NextEntry",
"_PyUnicode_CheckConsistency",
"_Py_CheckFunctionResult",
"PyCode_New",
"PyCode_NewWithPosOnlyArgs",
"PyCFunction_New",
"PyObject_GET_WEAKREFS_LISTPTR",
"PyFrame_BlockSetup",
"PySys_AddWarnOption",
"PySys_AddWarnOptionUnicode",
"PySys_AddXOption",
"PySys_HasWarnOptions",
"PySys_SetPath",
"PyUnicode_ClearFreeList",
"PyUnicode_Encode",
"PyUnicode_EncodeASCII",
"PyUnicode_EncodeCharmap",
"PyUnicode_EncodeDecimal",
"PyUnicode_EncodeLatin1",
"PyUnicode_EncodeRawUnicodeEscape",
"PyUnicode_EncodeUTF7",
"PyUnicode_EncodeUTF8",
"PyUnicode_EncodeUTF16",
"PyUnicode_EncodeUTF32",
"PyUnicode_EncodeUnicodeEscape",
"PyUnicode_TransformDecimalToASCII",
"PyUnicode_TranslateCharmap",
"_Py_HashBytes",
// This symbol was not in headers but still public until Python 3.10,
// should be able to remove this exclusion once support for 3.9 dropped
"Py_GetArgcArgv",
// pyo3-ffi defined these functions for 3.8 but they only exist for 3.9+
"PyObject_CallOneArg",
"PyObject_Vectorcall",
"PyVectorcall_Function",
"PyObject_VectorcallDict",
// Needs fixing: since 3.9 it takes thread state as first argument
"_PyEval_EvalFrameDefault",
// Needs fixing: argument count is wrong on 3.13 against headers
"_PyLong_AsByteArray",
// CPython gates these on a HAVE_FORK macro, pyo3-ffi needs to replicate this?
"PyOS_BeforeFork",
"PyOS_AfterFork_Parent",
"PyOS_AfterFork_Child",
// Private symbols that pyo3-ffi should stop exporting
"_PyUnicode_COMPACT_DATA",
"_PyUnicode_NONCOMPACT_DATA",
"_PyUnicode_Ready",
// See https://github.com/python/cpython/pull/139166/changes#r3214904694
"Py_IS_TYPE",
"Py_SIZE",
];
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All these symbols are potentially slightly wrong against one or more Python versions; I would like to fix these however I think it makes sense to do so in one or more PRs after this PR itself is merged.

@davidhewitt davidhewitt requested a review from ngoldbaum May 10, 2026 13:38
Copy link
Copy Markdown
Contributor

@ngoldbaum ngoldbaum left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I hacked in support for testing an abi3t builds and it didn't detect any new issues (🎉). Claude spotted a couple minor issues inline. Overall looks great, we should merge ASAP.

Comment thread uv.lock

[options]
exclude-newer = "0001-01-01T00:00:00Z" # This has no effect and is included for backwards compatibility when using relative exclude-newer values.
exclude-newer-span = "P7D"
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this intended to come in with this PR?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Given all the security breaches, I set a global cooldown in my uv settings. I can't find a way to prevent uv from subsequently re-locking.

Given it should be harmless, I was happy to let this in, I'll revert it later if it's a problem? Otherwise I get this diff on my local working copy every patch I work on :/

"PyCode_NewWithPosOnlyArgs",
"PyCFunction_New",
"PyObject_GET_WEAKREFS_LISTPTR",
"PyFrame_BlockSetup",
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are a number of symbols that appear in this list and the list above. Was that intentional? If not, probably best to de-duplicate.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, I wrote a compile-time assert there are no deduplicates and cleaned this up 👍

Comment thread pyo3-ffi-check/macro/src/lib.rs Outdated
Comment thread pyo3-ffi-check/macro/src/lib.rs Outdated
davidhewitt and others added 2 commits May 11, 2026 07:41
Co-authored-by: Nathan Goldbaum <nathan.goldbaum@gmail.com>
@davidhewitt davidhewitt enabled auto-merge May 11, 2026 06:56
@davidhewitt davidhewitt added this pull request to the merge queue May 11, 2026
Merged via the queue into PyO3:main with commit 6a9823b May 11, 2026
82 of 88 checks passed
@davidhewitt davidhewitt deleted the ffi-check-functions branch May 11, 2026 09:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

CI-build-full CI-no-fail-fast If one job fails, allow the rest to keep testing CI-skip-changelog Skip checking changelog entry

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants