Fix crash in -Tuple combinator with RESPECT NULLS producing a state#110930
Conversation
The -Tuple combinator resolves its nested functions with the requested NullsAction applied (RESPECT NULLS selects the window-variant any_respect_nulls, whose state layout differs from the aggregation-variant any), but the combined AggregateFunctionTuple kept the pre-action base name (anyTuple instead of any_respect_nullsTuple). The built state used the window layout while its serialized -State type name claimed the aggregation layout, so on a type-name round-trip (distributed shard to initiator, storage, plan (de)serialization) the state was reconstructed with the wrong implementation and read as a different SingleValueData, dereferencing garbage and crashing in SerializationTuple::serializeBinary. Name the combined function after its resolved nested function, matching the single-nested combinator path and AggregateFunctionTuple::getNormalizedStateType. Found by the AST fuzzer (STID 3227-3f13). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Pre-PR validation gate (click to expand)
Session id: cron:clickhouse-author-slot-0:20260718-070200 |
Internal second-model review (adjudicated) — click to expandAn independent model reviewed this diff before it was opened. Findings and dispositions: ❌ Blockers
💡 Nits
(An earlier "empty diff" finding was a process artifact — the review ran before the commit; re-run on the committed diff.) |
|
cc @azat @alexey-milovidov — could you review this? It fixes a crash where the |
|
Workflow [PR], commit [8600427] Summary: ✅
AI ReviewSummaryThis PR fixes the Final Verdict
|
|
The AST fuzzer surfaced this crash again from a second query shape (STID 4514-3f61, Reproduced deterministically on debug and confirmed fixed by this PR (both directions): SET distributed_aggregation_memory_efficient=1, group_by_two_level_threshold=1, prefer_localhost_replica=0;
-- t_ns(k UInt8, ns Nullable(String))
SELECT first_valueDistinctTupleOrNull(tuple(ns)) RESPECT NULLS
FROM remote('127.0.0.{1,2}', currentDatabase(), t_ns); -- SIGSEGV without this PR, OK with itSo this PR also closes STID 4514-3f61. The existing |
The -Tuple combinator names the combined state after a shared nested aggregate spelling that a -State type-name round-trip uses to reconstruct every element. Taking that name from one resolved element (nested_functions.front()->getName()) breaks multi-element tuples: an only-null element collapses to a placeholder, so countTupleState((NULL, number)) was named nothingUInt64Tuple and reparsing that name reconstructed nothingUInt64 for both elements, dropping the second element's real count state (CANNOT_CONVERT_TYPE on round-trip). Derive the shared name from the action-adjusted base aggregate instead: getAliasToOrName(nested_name) with the NullsAction applied via the same respect_nulls/ignore_nulls maps getImpl() uses per element. This keeps the original RESPECT NULLS fix (any -> any_respect_nulls) and makes the tuple name order-independent.
|
This PR also fixes a second, distinct crash site: Original fuzzed query: Minimal deterministic repro (no SELECT anyTupleArgMinMerge(s) FROM (
SELECT CAST(anyTupleArgMinState(tuple(dummy), 'a') RESPECT NULLS AS AggregateFunction(anyTupleArgMin, Tuple(UInt8), String)) AS s FROM system.one
UNION ALL
SELECT CAST(anyTupleArgMinState(tuple(dummy), 'b') RESPECT NULLS AS AggregateFunction(anyTupleArgMin, Tuple(UInt8), String)) AS s FROM system.one
);Why it is the same bug (state type name,
The Verified both directions on debug:
Suggest adding an |
LLVM Coverage Report
Changed lines: Changed C/C++ lines covered: 11/18 (61.11%) · Uncovered code |
CI finish ledger — 8600427CI fully finished, all green. 159 checks passed, 18 skipped, 0 failed. No red checks to own. Session id: cron:our-pr-ci-monitor:20260718-200000 |
Changelog category (leave one):
Changelog entry (a user-readable short description of the changes that goes into CHANGELOG.md):
Fixed a crash when an aggregate function with the
-Tuplecombinator was used withRESPECT NULLSand produced an intermediate state (-State, distributed aggregation,WITH ROLLUP/WITH CUBE). The combined function was named after the wrong state variant, so its serializedAggregateFunctiontype re-resolved to an incompatible in-memory layout on a round-trip.Description
The
-Tuplecombinator resolves its nested functions with the requestedNullsActionapplied —RESPECT NULLSselects the window-variantany_respect_nulls, whose state layout differs from the aggregation-variantany— but the combinedAggregateFunctionTuplewas built with the pre-action base name (anyTupleinstead ofany_respect_nullsTuple). The built state used the window layout while its serialized-Statetype name claimed the aggregation layout, so on a type-name round-trip (distributed shard -> initiator, storage, plan (de)serialization) the state was reconstructed with the wrong implementation and read as a differentSingleValueData, dereferencing garbage and crashing inSerializationTuple::serializeBinary.The fix names the combined function after its resolved nested function (which reflects the action and state variant), matching what the single-nested combinator path and
AggregateFunctionTuple::getNormalizedStateType()already do. Found by the AST fuzzer:https://s3.amazonaws.com/clickhouse-test-reports/json.html (AST fuzzer amd_debug, commit c6b6f90, STID 3227-3f13).
Version info
26.7.1.1162(included in26.7and later)