Pull Request Overview
- Opened on September 20, 2026
- Status Open
- Commit count 13 with first commit September 18, 2026
Total Delta
Open Days
Test Delta
How long has this pull request spent in each phase of its lifecycle?
| Fraction of total time | Business days | Phase |
|---|---|---|
|
|
1.0 day | Authoring 12 commits before pull request opened for review |
|
|
0.0 days | Awaiting first review |
|
|
0.9 days | Revising work with 1 commit in response to 1 review that left 5 comments |
Total time for pull request still awaiting merge: 1.9 business days
Fix a server crash on an AST JSON SELECT holding a function with no arguments
Changelog category (leave one):
- Bug Fix (user-visible misbehavior in an official stable release)
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Fixed a server crash when a clickhouse_json payload restores a function with no arguments list into a SELECT expression and EXPLAIN AST optimize = 1 hands that query to the pre-analyzer select interpreter. Such a payload is now rejected with BAD_ARGUMENTS at deserialization.
Description
SET enable_json_ast_dialect = 1, dialect = 'clickhouse_json' is settable per query by any user. A payload whose Function node lost its "arguments" member terminates a real server: SIGABRT on debug, UBSan member access within null pointer of type 'DB::IAST'. Pre-existing on master; raised as a deferred route on #120882 (comment 4053485628), no upstream issue.
ASTFunction::arguments is genuinely optional (a table engine, a CODEC element, an index TYPE), so ASTFunction::readJSON cannot require it; the analyzer answers Code: 42 before anything reads it. EXPLAIN AST is the one route that escapes it: InterpreterFactory.cpp:246 turns the analyzer off internally for ParsedAST, and optimize = 1 builds a legacy InterpreterSelectQuery, whose visitors dereference that list with no arity check. I measured three such sites in two subsystems (misc.h:48, GetAggregatesVisitor.cpp:51, ITableFunction.cpp:84), so a crash-site guard is not the fix.
The invariant goes on the SELECT node instead, in ASTSelectQuery::readJSON: it then fires wherever an ASTSelectQuery is deserialized, whatever wraps it, including a bare SelectQuery under EXPLAIN's query slot. Four lines cover 18 slots. ASTColumnsApplyTransformer keeps parameters and lambda outside IAST::children, beyond the recursive walk, so those two are screened at their own slot.
That is deliberately wider than the crash: such a payload is now refused with BAD_ARGUMENTS at deserialization in every statement, not only under EXPLAIN. Only payloads the SQL parser cannot produce are affected; 79,815 corpus payloads keep identical accept sets.
Two existing references change here: 05231's three rows report the inner slot where rather than select, since the SELECT screens its own slots first; 05227's last row asserted the lambda gap as unscreened, which this closes.
Based on #120882, which adds the two screening helpers; its commits show until it merges.
Validation: 7 carriers crash before the fix and answer BAD_ARGUMENTS after, each falsified by reverting one of the six changed lines.
Workflow [PR]
Sync PR [sync-upstream/pr/121119]
<!-- CI automatic block end :ci_links: -->
Comments Threads Pending Resolution
Resolved Comment Threads
No resolved comments have been left on this PR.