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 5 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.
🟢 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.
🟡 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.
🔵 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.
🟢 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
|
Thanks for the PR.
I am not sure this is needed. @raul what do you think? I would rather see a missing test for the |
Signed-off-by: 1fanwang <1fannnw@gmail.com>
|
Done in 4415d41. This adds the focused Expression.equals(None) regression. |
There was a problem hiding this comment.
🟡 Changes recommended
The subprocess guard can exercise unrelated crashing methods and may skip the intended Cython methods, so it does not reliably validate the fix.
Get a fresh assessment by requesting another Copilot review.
Review details
Suppressed comments (1)
python/pyarrow/tests/test_misc.py:360
- This method guard is effectively vacuous for the Cython methods it is meant to cover. The child process uses stdlib
inspect, but this repository's Cython build does not reliably expose__text_signature__;dev/archery/archery/lang/python.py:169-184adds a docstring parser specifically becauseinspect.signaturefails. Thisexcepttherefore skipsExpression.equalsand similar methods, so the test can pass without exercising the regression; use the repository's Cython signature parser or explicitly include the methods under test.
sig = inspect.signature(attr)
except BaseException:
continue
- Files reviewed: 5/5 changed files
- Comments generated: 1
- Review effort level: Lite
| if len(required) == 1: | ||
| targets.append((label, name)) |
Rationale for this change
A null expression passed to partition-key extraction or expression equality can terminate Python rather than raise an exception. Applications cannot catch that failure.
Fixes #51293.
What changes are included in this PR?
The two expression parameters reject null at the Cython boundary. Direct regression cases cover partition-key extraction and equality. The existing subprocess tests exercise public functions and methods.
Are these changes tested?
Both null-expression calls were reproduced in separate processes with 20-second timeouts, then run against rebuilt native extensions. Valid equality and partition-key extraction retained their results. The broader callable sweeps were not rerun.
Raw logs
Are there any user-facing changes?
Null expressions raise TypeError instead of terminating Python. Valid calls keep their existing behavior.
New Contributor's Guide |
Contributing Overview |
AI-generated Code Guidance