This pull request's diff hasn't been prepared yet. Log in with GitHub to have us prepare it for review.

Fix a server crash on an AST JSON SELECT holding a function with no arguments #121119

Open
groeneai opened 9:47pm on September 19, 2026 wants to merge 38 Δ into clickhouse/clickhouse master from
fix-json-ast-select-argumentless-function

Pull Request Overview

  • Opened on September 20, 2026
  • Status Open
  • Commit count 13 with first commit September 18, 2026

Total Delta

38 Total Diff Delta

Open Days

Open 0 weekdays

Test Delta

29 Diff Delta in Test Files
Breakdown by Phase

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

Author avatar

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

clickhouse-gh[bot] reviewed on September 20, 2026

Resolved Comment Threads

No resolved comments have been left on this PR.