-
Notifications
You must be signed in to change notification settings - Fork 4.3k
GH-51293: [Python] Reject a null Expression in two public APIs - #51294
GH-51293: [Python] Reject a null Expression in two public APIs #512941fanwang wants to merge 4 commits into
Conversation
get_partition_keys took an Expression without rejecting None, so passing None dereferenced a null pointer and terminated the interpreter. Generated-by: GitHub Copilot CLI (Claude Opus 5) Signed-off-by: 1fanwang <1fannnw@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟢 Approval recommended
The change is minimal, directly addresses a crash, and includes a focused regression test validating the new behavior.
Pull request overview
This PR fixes a hard crash in the PyArrow Dataset Python API by rejecting a None partition expression at the Cython boundary, converting an interpreter "bus error" into a normal Python TypeError and adding a regression test.
Changes:
- Mark
get_partition_keys’spartition_expressionparameter asnot Noneinpython/pyarrow/_dataset.pyxsoNoneis rejected before dereferencing. - Add a regression test ensuring
ds.get_partition_keys(None)raisesTypeError.
File summaries
| File | Description |
|---|---|
| python/pyarrow/_dataset.pyx | Adds not None to the typed Expression parameter to prevent null from reaching Cython internals. |
| python/pyarrow/tests/test_dataset.py | Adds a regression test asserting TypeError is raised for None input. |
Review details
- Files reviewed: 2/2 changed files
- Comments generated: 0
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
...lables Sweeps public module-level callables with None in a child process and fails with the offending name if the interpreter dies by signal, so a typed Cython parameter missing "not None" is caught as a test failure rather than a segfault in someone else's code. Generated-by: GitHub Copilot CLI (Claude Opus 5) Signed-off-by: 1fanwang <1fannnw@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 Changes recommended
The new subprocess-based sweep test can hang indefinitely and may miss post-"DONE" crashes unless it asserts returncode == 0 and uses a timeout.
Once you've addressed the issues Copilot identified, you can request another Copilot review.
Review details
- Files reviewed: 3/3 changed files
- Comments generated: 2
- Review effort level: Lite
The sweep in the previous commit only covered module-level functions. Extending it to methods reached from a live object found Expression.equals taking a typed Expression without rejecting None, which segfaulted. Generated-by: GitHub Copilot CLI (Claude Opus 5) Signed-off-by: 1fanwang <1fannnw@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🔵 Needs a closer look
The new subprocess-based sweep tests need a timeout (to avoid hanging CI) and a couple of small documentation/comment fixes for correctness and debuggability.
Review details
Suppressed comments (4)
Previously missed (1) — in code that hasn't changed since the last review.
python/pyarrow/tests/test_misc.py:319
- This subprocess sweep has no timeout, so a single blocking callable can hang the entire test run. Consider adding a reasonable timeout (and include stderr in the failure) to make CI failures deterministic and easier to debug.
This issue also appears on line 377 of the same file.
python/pyarrow/tests/test_misc.py:280
- The comment says passing None to any public callable must produce a Python exception, but the test intentionally allows callables to accept None (it only asserts the subprocess doesn't crash). Please update the comment to match the actual contract (no crash).
# GH-51293: a typed Cython parameter that is not declared "not None"
# lets None reach code that dereferences it, killing the interpreter
# instead of raising. Passing None to any public callable must produce
# a Python exception, never a fatal signal.
python/pyarrow/tests/test_misc.py:381
- Same issue as the callable sweep above: this subprocess.run has no timeout and the failure message drops stderr, which can lead to hung CI jobs and harder debugging when something goes wrong.
res = subprocess.run([sys.executable, "-c", code],
capture_output=True, text=True)
lines = res.stdout.splitlines()
if not lines or lines[-1] != "DONE":
culprit = lines[-1] if lines else "<no output>"
python/pyarrow/_compute.pyx:2687
- The equals() docstring parameter type references pyarrow.dataset.Expression, but this is pyarrow.compute.Expression (and the method signature enforces that). This mismatch can confuse users reading the docs/help().
Parameters
----------
other : pyarrow.dataset.Expression
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
...cisely Add a timeout so a blocking callable cannot hang CI, fail on a non-zero returncode so a crash during interpreter shutdown is caught, and correct the comment: some public APIs accept None, so the invariant is only that none of them terminate the interpreter. Generated-by: GitHub Copilot CLI (Claude Opus 5) Signed-off-by: 1fanwang <1fannnw@gmail.com>
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟢 Approval recommended
The functional changes directly address the reported interpreter-crash bug and are covered by targeted and guard-style tests; remaining feedback is limited to minor doc/test robustness improvements.
Review details
Suppressed comments (2)
Previously missed (2) — in code that hasn't changed since the last review.
python/pyarrow/_compute.pyx:2687
- The
Expression.equalsdocstring referencespyarrow.dataset.Expression, but this type lives inpyarrow.compute(and this method is defined inpyarrow/_compute.pyx). Updating the docstring avoids confusing API users.
def equals(self, Expression other not None):
"""
Parameters
----------
other : pyarrow.dataset.Expression
python/pyarrow/tests/test_misc.py:363
- The subprocess method sweep uses
inspect._empty, which is a privateinspectimplementation detail. Prefer the publicinspect.Parameter.emptyto reduce risk of breakage across Python versions.
- Files reviewed: 4/4 changed files
- Comments generated: 0 new
- Review effort level: Lite
AlenkaF
commented
Sep 11, 2026
Thanks for the PR.
Since a hand-audit goes stale, this adds two guard tests that pass None to public
callables in a child process and fail naming the offender if the interpreter dies.
One covers module-level functions, the other methods reached from a live object. The
method sweep found Expression.equals after the function sweep came back clean.
I am not sure this is needed. @raul what do you think?
I would rather see a missing test for the Expression.equals(None) to be added.
Uh oh!
There was an error while loading. Please reload this page.
Rationale for this change
Two public pyarrow callables kill the interpreter instead of raising when passed
None. Both take a typed Cython parameter not declarednot None, so the nullreaches code that dereferences it.
On the released 25.0.1 build:
A caller that forwards an unchecked expression loses the interpreter with no
traceback pointing at the call.
This came out of the Cython sweep @AlenkaF asked for in
#51163 (review).
What changes are included in this PR?
Declare both parameters
not None, and add theNonecase totest_partition_keys.Since a hand-audit goes stale, this adds two guard tests that pass
Noneto publiccallables in a child process and fail naming the offender if the interpreter dies.
One covers module-level functions, the other methods reached from a live object. The
method sweep found
Expression.equalsafter the function sweep came back clean.They assert only that nothing terminates the interpreter, since many typed
parameters accept
Nonedeliberately.Are these changes tested?
Yes. Rebuilt 25.0.1 with only these annotations, against matching Arrow C++ 25.0.1,
so before and after differ by that alone:
Valid input is unchanged:
get_partition_keysona == 1 & b == 'x'still returns{'a': 1, 'b': 'x'}.Each guard fails on the unpatched build and names the culprit:
Both pass once the annotations are in place:
Are there any user-facing changes?
Passing
NoneraisesTypeErrorinstead of terminating the process. No valid callchanges behavior.
This PR removes two interpreter crashes reachable from public APIs.
Closes #51293.