Skip to content

Navigation Menu

Sign in
Sign up

GH-51293: [Python] Reject a null Expression in two public APIs - #51294

Open
1fanwang wants to merge 4 commits into
apache:main from
1fanwang:1fannnw/gh-51293-partition-keys-none
Open

GH-51293: [Python] Reject a null Expression in two public APIs #51294
1fanwang wants to merge 4 commits into
apache:main from
1fanwang:1fannnw/gh-51293-partition-keys-none

Conversation

@1fanwang

@1fanwang 1fanwang commented Sep 10, 2026
edited by github-actions Bot
Loading

Copy link
Copy Markdown
Contributor

Rationale for this change

Two public pyarrow callables kill the interpreter instead of raising when passed
None. Both take a typed Cython parameter not declared not None, so the null
reaches code that dereferences it.

On the released 25.0.1 build:

$ python -c "import pyarrow.dataset as ds; ds.get_partition_keys(None)"
Bus error: 10
$ python -c "import pyarrow.dataset as ds; ds.field('a').equals(None)"
Segmentation fault: 11

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 the None case to test_partition_keys.

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.

They assert only that nothing terminates the interpreter, since many typed
parameters accept None deliberately.

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:

TypeError: Argument 'partition_expression' has incorrect type (expected pyarrow._compute.Expression, got NoneType)
TypeError: Argument 'other' has incorrect type (expected pyarrow._compute.Expression, got NoneType)

Valid input is unchanged: get_partition_keys on a == 1 & b == 'x' still returns
{'a': 1, 'b': 'x'}.

Each guard fails on the unpatched build and names the culprit:

AssertionError: passing None to Expression.equals terminated the interpreter
(returncode -11); declare its typed parameter 'not None'

Both pass once the annotations are in place:

$ pytest python/pyarrow/tests/test_misc.py -k reject_none -q
.. [100%]
2 passed, 123 deselected, 4 warnings in 2.05s

Are there any user-facing changes?

Passing None raises TypeError instead of terminating the process. No valid call
changes behavior.

This PR removes two interpreter crashes reachable from public APIs.

Closes #51293.

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>
Copilot AI lite review requested due to automatic review settings September 10, 2026 18:43

Copy link
Copy Markdown

⚠️ GitHub issue #51293 has been automatically assigned in GitHub to PR creator.

Copilot AI left a comment

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.

🟢 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’s partition_expression parameter as not None in python/pyarrow/_dataset.pyx so None is rejected before dereferencing.
  • Add a regression test ensuring ds.get_partition_keys(None) raises TypeError.
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>
Copilot AI review requested due to automatic review settings September 10, 2026 18:50

Copilot AI left a comment

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.

🟡 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

Comment thread python/pyarrow/tests/test_misc.py
Comment thread python/pyarrow/tests/test_misc.py Outdated
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>
Copilot AI review requested due to automatic review settings September 10, 2026 20:17

Copilot AI left a comment

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.

🔵 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>
Copilot AI review requested due to automatic review settings September 10, 2026 20:26

Copilot AI left a comment

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.

🟢 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.equals docstring references pyarrow.dataset.Expression, but this type lives in pyarrow.compute (and this method is defined in pyarrow/_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 private inspect implementation detail. Prefer the public inspect.Parameter.empty to reduce risk of breakage across Python versions.
  • Files reviewed: 4/4 changed files
  • Comments generated: 0 new
  • Review effort level: Lite

@1fanwang 1fanwang changed the title (削除) GH-51293: [Python] Reject a null partition expression (削除ここまで) (追記) GH-51293: [Python] Reject a null Expression in two public APIs (追記ここまで) Sep 10, 2026

AlenkaF commented Sep 11, 2026

Copy link
Copy Markdown
Member

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.

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

Reviewers

Copilot code review Copilot
Copilot review effort, defaults to Lite
Applies to this pull request for everyone.Learn more about Copilot code review.
Copilot left review comments
@AlenkaF AlenkaF Awaiting requested review from AlenkaF AlenkaF is a code owner
@raulcd raulcd Awaiting requested review from raulcd raulcd is a code owner
@rok rok Awaiting requested review from rok rok is a code owner

Assignees

No one assigned

Projects

None yet

Milestone

No milestone

Development

Successfully merging this pull request may close these issues.

[Python] get_partition_keys segfaults when the partition expression is None

AltStyle によって変換されたページ (->オリジナル) /