Fix heap-use-after-free in `executeAggregateMultiply` self-merge by groeneai · Pull Request #103536 · ClickHouse/ClickHouse · GitHub
Skip to content

Fix heap-use-after-free in executeAggregateMultiply self-merge#103536

Merged
alexey-milovidov merged 4 commits into
ClickHouse:masterfrom
groeneai:fix-aggregate-multiply-self-merge-uaf-0988
May 14, 2026
Merged

Fix heap-use-after-free in executeAggregateMultiply self-merge#103536
alexey-milovidov merged 4 commits into
ClickHouse:masterfrom
groeneai:fix-aggregate-multiply-self-merge-uaf-0988

Conversation

@groeneai

@groeneai groeneai commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Motivation

Master fuzzer caught a heap-use-after-free in FunctionBinaryArithmetic::executeAggregateMultiply (STID 0988-40af) on Stress test (experimental, serverfuzz, arm_asan_ubsan, s3), sha 20f4b50bb190f87122f53eee1f99887ec9d8d771:

AddressSanitizer: heap-use-after-free
READ of size 48 ... in PODArray::insert_assume_reserved ...
FunctionBinaryArithmetic<MultiplyImpl, ...>::executeAggregateMultiply at src/Functions/FunctionBinaryArithmetic.h:1221

CI report: https://s3.amazonaws.com/clickhouse-test-reports/json.html?REF=master&sha=20f4b50bb190f87122f53eee1f99887ec9d8d771&name_0=MasterCI&name_1=Stress%20test%20%28experimental%2C%20serverfuzz%2C%20arm_asan_ubsan%2C%20s3%29

Root cause

The exponentiation-by-squaring loop in executeAggregateMultiply doubled the running aggregate state by calling

function->merge(vec_from[i], vec_from[i], arena.get());

passing the same state as both source and destination. For aggregate functions whose merge implementation appends the source's internal storage into the destination's (QuantileExact, groupArray, groupUniqArray, windowFunnel, sequenceMatch, etc.), the destination's PODArray::insert(begin, end) reallocates mid-merge, and the source iterators (referencing the same, now-freed buffer) are read by the trailing memcpy in PODArray::insert_assume_reserved.

The hazard was already documented in src/Common/PODArray.h:482:

/// Do not insert into the array a piece of itself. Because with the resize, the iterators on themselves can be invalidated.
template <typename It1, typename It2, typename ... TAllocatorParams>
void insert(It1 from_begin, It2 from_end, TAllocatorParams &&... allocator_params)

Fix

Copy vec_from into an independent column before each squaring step and merge the copy into vec_from. Source and destination no longer alias.

Reproduction (deterministic, ASan)

SELECT finalizeAggregation(state * 4)
FROM (SELECT quantilesExactState(0.5)(number) AS state FROM numbers(50));

Without the fix this aborts every run with a heap-use-after-free; with the fix the regression test (tests/queries/0_stateless/04117_aggregate_state_multiply_self_merge_uaf) passes 50/50 ASan iterations.

Pre-PR validation

# Question Answer
a Deterministic repro? Yes — quantilesExactState(0.5)(number) FROM numbers(50) * N (N >= 2) UAF every time on ASan.
b Root cause explained? Yes — func->merge(state, state) aliases src=dst; PODArray::insert(begin,end) reallocates and insert_assume_reserved's memcpy then reads from the freed source buffer.
c Fix matches root cause? Yes — the alias is broken by copying vec_from to an independent column_temp before each squaring step.
d Test intent preserved? Yes — new regression test 04117_aggregate_state_multiply_self_merge_uaf exercises the squaring path across multipliers (1,2,3,4,5,7,8,16,256), quantilesExactState (the aggregate the fuzzer caught), groupArrayState (length grows), and the agg_state_is_const branch. No existing test weakened.
e Both directions demonstrated? Yes — ASan crashes without fix; passes 50/50 with fix.
f Fix is general? Yes — addresses the alias in executeAggregateMultiply itself, covering every aggregate function. Sibling paths (executeAggregateAddition, executeDateTime64Subtraction) take two distinct sources and were verified to be safe.

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):

Fix heap-use-after-free in multiplying an AggregateFunction state by an integer (e.g. quantilesExactState(...) * N). The exponentiation-by-squaring loop merged the state with itself, which is undefined when the aggregate function's merge reallocates its internal storage. Closes STID 0988-40af.

Documentation entry for user-facing changes

  • Documentation entry is not required (internal correctness fix; user-visible API and result are unchanged).

Version info

  • Merged into: 26.5.1.652
  • Backported to: 26.4.3.32, 26.3.11.29, 26.2.19.38, 25.8.24.19

The exponentiation-by-squaring loop in `FunctionBinaryArithmetic::
executeAggregateMultiply` squared the running aggregate state with

    function->merge(vec_from[i], vec_from[i], arena.get());

passing the same state as both source and destination. For aggregate
functions whose `merge` implementation appends the source's internal
storage into the destination (`QuantileExact`, `groupArray`,
`groupUniqArray`, `windowFunnel`, `sequenceMatch`, etc.), the
destination reallocates its `PODArray` mid-merge and the source
iterators (referencing the same, now-freed buffer) are read by the
trailing `memcpy` in `PODArray::insert_assume_reserved` -> heap-use-
after-free. The hazard was already documented at
`src/Common/PODArray.h:482`: "Do not insert into the array a piece of
itself. Because with the resize, the iterators on themselves can be
invalidated."

Fix: copy `vec_from` into an independent column before each squaring
step and merge the copy into `vec_from`, so source and destination no
longer alias.

Reproduction (Address Sanitizer): caught by master fuzzer
(`Stress test (experimental, serverfuzz, arm_asan_ubsan, s3)`,
STID 0988-40af, sha 20f4b50):

    SELECT finalizeAggregation(state * 4)
    FROM (SELECT quantilesExactState(0.5)(number) AS state FROM numbers(50));

This deterministically triggers the UAF on the previous code and
passes with the fix.

CI report:
https://s3.amazonaws.com/clickhouse-test-reports/json.html?REF=master&sha=20f4b50bb190f87122f53eee1f99887ec9d8d771&name_0=MasterCI&name_1=Stress%20test%20%28experimental%2C%20serverfuzz%2C%20arm_asan_ubsan%2C%20s3%29
@groeneai

Copy link
Copy Markdown
Contributor Author

cc @rschu1ze @alexey-milovidov — could you review this? It fixes a heap-use-after-free in the exponentiation-by-squaring path of executeAggregateMultiply where the loop did func->merge(state, state), aliasing src and dst into the same aggregate function state — UB for any aggregate whose merge reallocates its internal storage (caught by master fuzzer as STID 0988-40af on quantilesExactState * N).

@groeneai

Copy link
Copy Markdown
Contributor Author

Pre-PR validation gate (TASK.md Phase 4 step 9)

# Question Answer
a Deterministic repro? Yes — SELECT finalizeAggregation(state * 4) FROM (SELECT quantilesExactState(0.5)(number) AS state FROM numbers(50)); triggers the UAF every run on Address Sanitizer build.
b Root cause explained? Yes — the squaring branch did function->merge(vec_from[i], vec_from[i], arena.get()), passing the same aggregate state as both src and dst. For aggregate functions whose merge calls array.insert(rhs.array.begin(), rhs.array.end()) (QuantileExact, groupArray, etc.), PODArray::insert first calls insertPrepare which reallocates, then dereferences the original from_begin/from_end iterators inside insert_assume_reserved's memcpy — those iterators point to the freed buffer. The hazard is documented in src/Common/PODArray.h:482.
c Fix matches root cause? Yes — copy vec_from into an independent column_temp before each squaring step and merge the copy into vec_from. Source and destination no longer alias. No bound-widening, no no-random-* tag, no setting pinning.
d Test intent preserved? / New tests added? New regression test 04117_aggregate_state_multiply_self_merge_uaf exercising the squaring path across multipliers (1,2,3,4,5,7,8,16,256), quantilesExactState (the variant the fuzzer caught), groupArrayState (length grows linearly with N), and the agg_state_is_const constant-column branch. No existing test weakened.
e Demonstrated in both directions? Yes — verified that with the fix reverted, the regression test aborts under ASan with the same heap-use-after-free signature inside PODArray::insert_assume_reserved; with the fix restored, 50/50 ASan iterations pass and the output matches .reference.
f Fix is general, not a narrow patch? Yes — the fix lives in executeAggregateMultiply itself, so every aggregate function is covered, not just the one the fuzzer happened to surface. Sibling code paths checked: executeAggregateAddition merges rhs into a fresh column_to (different memory, no alias); executeDateTime64Subtraction does not call func->merge. Searched src/ for function->merge(\1, \1) patterns — only the call we just fixed matched.

Session: cron:clickhouse-ci-task-worker:20260425-151500

@alexey-milovidov alexey-milovidov added the can be tested Allows running workflows for external contributors label Apr 25, 2026
@clickhouse-gh

clickhouse-gh Bot commented Apr 25, 2026

Copy link
Copy Markdown
Contributor

Workflow [PR], commit [a29f7c2]

Summary:


AI Review

Summary

This PR fixes self-merge undefined behavior in FunctionBinaryArithmetic::executeAggregateMultiply by eliminating merge(place, place) in the squaring path, adds targeted regression coverage for both the original groupArray/quantilesExact reproducer and the MovingImpl variant, and updates the 03406_reservoir_sample_self_merging reference to the corrected behavior. I did not find unresolved correctness, safety, performance, or PR-metadata issues in the current HEAD.

Final Verdict

Status: ✅ Approve

@alexey-milovidov alexey-milovidov left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is a good fix.

@alexey-milovidov

Copy link
Copy Markdown
Member

@groeneai, please send an additional PR that will add an assert into IAggregateFunction::merge that it isn't called with the same state. The assert should be in a single place, e.g., it can go into IAggregateFunctionHelper. Or if it's not possible, change the virtual method merge to mergeImpl everywhere and make the merge method to check the assert and call mergeImpl - something on that theme.

@alexey-milovidov

Copy link
Copy Markdown
Member

@groeneai, the changelog category is not recognized.

@groeneai

Copy link
Copy Markdown
Contributor Author

@alexey-milovidov thanks for the review!

  1. Changelog category: fixed — the headers were missing the trailing : that tests/ci/changelog.py expects (its regex requires either Changelog category flush to end-of-line or (...): with a colon when there is a parenthetical). Updated both the category and entry headers.

  2. Self-aliasing assert: filed as Add IAggregateFunction::merge self-aliasing chassert (follow-up to #103536) #103543 (draft, stacked on this PR). Implementation is the NVI wrapper variant from your second suggestion — IAggregateFunction::merge is now a non-virtual entry point that does chassert(place != rhs) and forwards to a new pure-virtual mergeImpl; 71 concrete aggregate-function overrides renamed mergemergeImpl. Same wrapper for the parallel merge overload, plus the assert is repeated at the two devirtualised call sites in IAggregateFunctionHelper::mergeBatch / mergeAndDestroyBatch. chassert is a no-op outside DEBUG_OR_SANITIZER_BUILD, so release builds pay zero cost.

#103543 will narrow to just the chassert commit once this PR lands.

This PR's `executeAggregateMultiply` rework copies the aggregate state
into an independent column before each squaring step, so source and
destination no longer alias on entry to `merge`. For aggregate functions
whose `merge` reallocates and then reads the source (e.g. `quantilesExact`,
`groupArray`, `groupUniqArray`, `windowFunnel`, `sequenceMatch`), this
fixes the heap-use-after-free.

`ReservoirSampler::merge` (used by `medianState` / `quantileState`) had
been guarded since ClickHouse#79031 with an early-return when `this == &b`. That
guard is unreachable from `executeAggregateMultiply` now: the squaring
step never feeds the same state as both arguments. Squaring becomes a
real merge instead of a no-op, so `state * N` for a ReservoirSampler-
backed aggregate now actually accumulates `N` copies of the input rather
than collapsing to a single copy.

`03406_reservoir_sample_self_merging` was added in ClickHouse#79031 with reference
values that match the no-op semantics:

  10 * medianState(numbers(10))      -> 4.5      (100 inserts <= 8192, exact)
  10 * medianState(numbers(1000))    -> 499.5    (was no-op; same as state)
  10 * medianState(numbers(100000))  -> 50501.5  (already over reservoir)

Under the corrected semantics the middle case crosses the 8192-sample
reservoir cap (10 x 1000 = 10000 inserts) and the deterministic seed
yields `495` for the median. The first and third cases are unchanged:
the small one stays under the cap, the large one was already saturated.
The test still verifies the absence of UAF / `LOGICAL_ERROR` on
`state * N` for ReservoirSampler-backed aggregates - it's a regression
test for ClickHouse#78299 - and only the boundary value moves.

CI report:
https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=103543&sha=f7dd195af45870446b0c48654753c337438e90b2&name_0=PR
groeneai added a commit to groeneai/ClickHouse that referenced this pull request Apr 26, 2026
Wrap the public `merge` method of `IAggregateFunction` in a non-virtual
NVI entry point that asserts `place != rhs` (in debug or sanitizer builds)
before forwarding to a new pure-virtual `mergeImpl`.

The same hazard motivated PR ClickHouse#103536 (heap-use-after-free in
`executeAggregateMultiply` self-merge, STID 0988-40af). For aggregate
functions whose `merge` reallocates the destination's internal storage and
then reads from the source (`quantilesExact`, `groupArray`,
`groupUniqArray`, `windowFunnel`, `sequenceMatch`, …), passing the same
pointer as both source and destination is undefined. The new
`chassert(place != rhs, …)` aborts on this in DEBUG_OR_SANITIZER_BUILD,
catching future occurrences of the entire bug class at the call site
instead of letting them surface as memory corruption.

Same wrapper added for the parallel `merge` overload (with `ThreadPool`)
and for the `mergeBatch` / `mergeAndDestroyBatch` paths in
`IAggregateFunctionHelper` (which devirtualise the call and so bypass the
public wrapper). 71 concrete aggregate-function overrides renamed from
`merge` to `mergeImpl`. Callers are unchanged — they still call `merge`,
which now performs the check.

Closes the follow-up requested by @alexey-milovidov on PR ClickHouse#103536.
@clickhouse-gh clickhouse-gh Bot added the pr-bugfix Pull request with bugfix, not backported by default label Apr 26, 2026
@groeneai

Copy link
Copy Markdown
Contributor Author

@alexey-milovidov - thanks for the review.

Pushing one more commit to address the regression in 03406_reservoir_sample_self_merging that this PR introduces. Same branch, new commit 9da5e773e8d:

- 499.5
+ 495

Why the value moved. ReservoirSampler::merge carries an early-return guard from #79031 (if (this == &b) return;) that turned the squaring step in executeAggregateMultiply into a no-op when src and dst aliased. This PR copies the state into an independent column before squaring, so the alias is gone and the merge actually runs. For medianState-backed aggregates that means state * N now genuinely accumulates N copies of the input rather than collapsing to one copy.

The original test was added in #79031 with reference values that match the no-op semantics. The middle case crosses the 8192-sample reservoir cap (10 × 1000 = 10000 inserts), and the deterministic pcg32_fast seed (123456) yields 495 for the median under the corrected behavior. The other two cases are unaffected: the small one (100 inserts) stays under the cap, and the large one (1_000_000 inserts) was already saturated.

The test still serves its original purpose - regression coverage for #78299 (no UAF / LOGICAL_ERROR on state * N for ReservoirSampler-backed aggregates). I verified locally:

Session id: cron:clickhouse-ci-task-worker:20260426-214500

@groeneai

Copy link
Copy Markdown
Contributor Author

[2026-05-10] Recurrence on master + chain block — quick check-in.

The bug this PR fixes hit master again today: STID 0988-3351 (AST fuzzer, arm_asan_ubsan, commit 8e5ccac9, 2026-05-10 19:59 UTC). Same self-merge UAF in executeAggregateMultiply, just with AggregateFunctionOrFill<false> wrapping QuantileExact this time — stack frames identical at the source level to STID 0988-40af that triggered this PR. CIDB shows 3 master hits total for the family since 2026-04-25 (1× 0988-40af + 2× 0988-3351), all matching this fix's code path.

This PR is approved and all AST fuzzer + build checks are green. The only failing required check is Unit tests (msan, function_prop_fuzzer), which is the known-chronic Volnitsky.h:724 putNGramBase MSan bug — unrelated to anything in this PR. I have a separate fix for that in #103864 (open since 2026-05-02, awaiting review from @vdimir).

cc @alexey-milovidov — could you either (a) merge this despite the unrelated function_prop_fuzzer failure (the bug is genuinely recurring on master and mergeable=true confirms the rest is clean), or (b) take a quick look at #103864 so this PR can land cleanly? The chain is: #103864 unblocks unit tests → #103536 lands → #103543 (the chassert follow-up you endorsed) can come out of draft.

Master AST fuzzer hit `Segmentation fault (STID: 0988-2212)` on
`AST fuzzer (amd_msan)` for `groupArrayMovingSum` on PR ClickHouse#100394
and on `AST fuzzer (arm_asan_ubsan)` / `amd_debug` for PRs ClickHouse#101033,
ClickHouse#101062, ClickHouse#104531 — same bug class as STID 0988-40af / 0988-3351.
The fuzzer mutated a query into `groupArrayMovingSumState(...) * N`,
which goes through `FunctionBinaryArithmetic::executeAggregateMultiply`.
The exponentiation-by-squaring loop called
`function->merge(vec_from[i], vec_from[i], arena.get())`, and
`MovingImpl::merge` then did
`cur_elems.value.insert(rhs_elems.value.begin(), rhs_elems.value.end(), arena)`
where `rhs_elems` aliased `cur_elems.value`, so the source iterators
referenced the buffer that `insertPrepare` was about to reallocate.

The fix in this PR (copy `vec_from` into an independent column before
each squaring step) eliminates the self-alias at the call site, so
`MovingImpl::merge` is never invoked with `place == rhs` from
`executeAggregateMultiply` anymore.

The new test exercises every `MovingImpl` instantiation reachable from
`groupArrayMovingSum` / `groupArrayMovingAvg`:
  - integer accumulator,
  - decimal accumulator (the variant flagged on PR ClickHouse#101062),
  - windowed parameterisation (`LimitNumElements::value = true`),
  - the `agg_state_is_const` path through `arrayJoin([state, state])`.

Verified locally on Debug build that pre-fix the squaring path aborts
with the PODArray `assertNotIntersects` assertion (Server `<Fatal>`),
while `state * 1` (no squaring branch) returns `50` — matching the
single-state baseline used to derive the reference values.

CI report: https://s3.amazonaws.com/clickhouse-test-reports/json.html?PR=100394&sha=7309f23dc7ccee494b41416c8fff18c02d0697a7&name_0=PR&name_1=AST%20fuzzer%20%28amd_msan%29
@groeneai

Copy link
Copy Markdown
Contributor Author

Additional evidence — the bug class flagged by @alexey-milovidov on #100394 (STID 0988-2212, MovingImpl::merge self-merge UAF on AST fuzzer (amd_msan)) is the same root cause as STID 0988-40af. 30-day CIDB shows it on 4 unrelated PRs (#100394, #101033, #101062, #104531) across amd_msan, arm_asan_ubsan, and amd_debug builds.

I just pushed bb19c134942 adding 04235_moving_impl_multiply_self_merge_uaf_0988_2212 — a regression test that exercises every MovingImpl instantiation reachable from groupArrayMovingSum / groupArrayMovingAvg:

Reproduction is deterministic on master Debug build:

SELECT length(finalizeAggregation(state * 2))
FROM (SELECT groupArrayMovingSumState(number) AS state FROM numbers(50));

aborts on the PODArray::assertNotIntersects assertion. With this PR's fix the new test returns the expected N * 50 lengths.

Comment thread src/Functions/FunctionBinaryArithmetic.h
`executeAggregateMultiply` previously constructed `column_temp` once
outside the squaring loop and reset it between iterations with
`popBack(column_temp->size())`. `ColumnAggregateFunction::popBack`
destroys the popped aggregate states but does not release the column's
arena memory (the `Arenas` list is monotonic and is only freed when
the column itself is destroyed). Each squaring iteration's
`insertFrom` therefore allocated new state memory in the same arena,
causing the temporary footprint to grow with the multiplier instead of
staying bounded by one copy of the input column.

Construct `column_temp` inside the squaring branch so the column
(and its arena) is destroyed at the end of each iteration. Peak
temporary footprint is now bounded by `size * state_size`.

Caught by the clickhouse-gh[bot] inline review on
ClickHouse#103536 .
@clickhouse-gh

clickhouse-gh Bot commented May 13, 2026

Copy link
Copy Markdown
Contributor

LLVM Coverage Report

Metric Baseline Current Δ
Lines 84.10% 84.10% +0.00%
Functions 91.10% 91.10% +0.00%
Branches 76.50% 76.50% +0.00%

Changed lines: 100.00% (12/12) · Uncovered code

Full report · Diff report

@alexey-milovidov alexey-milovidov added this pull request to the merge queue May 14, 2026
Merged via the queue into ClickHouse:master with commit 161185f May 14, 2026
166 checks passed
@clickgapai

Copy link
Copy Markdown
Contributor

@robot-clickhouse-ci-2 robot-clickhouse-ci-2 added the pr-synced-to-cloud The PR is synced to the cloud repo label May 14, 2026
@Algunenano Algunenano added the pr-must-backport Pull request should be backported intentionally. Use this label with great care! label May 18, 2026
@robot-clickhouse-ci-1 robot-clickhouse-ci-1 added the pr-must-backport-synced The `*-must-backport` labels are synced into the cloud Sync PR label May 18, 2026
robot-ch-test-poll4 added a commit that referenced this pull request May 18, 2026
Cherry pick #103536 to 25.8: Fix heap-use-after-free in `executeAggregateMultiply` self-merge
robot-clickhouse added a commit that referenced this pull request May 18, 2026
robot-ch-test-poll4 added a commit that referenced this pull request May 18, 2026
Cherry pick #103536 to 26.2: Fix heap-use-after-free in `executeAggregateMultiply` self-merge
robot-clickhouse added a commit that referenced this pull request May 18, 2026
robot-ch-test-poll4 added a commit that referenced this pull request May 18, 2026
Cherry pick #103536 to 26.3: Fix heap-use-after-free in `executeAggregateMultiply` self-merge
robot-clickhouse added a commit that referenced this pull request May 18, 2026
robot-ch-test-poll4 added a commit that referenced this pull request May 18, 2026
Cherry pick #103536 to 26.4: Fix heap-use-after-free in `executeAggregateMultiply` self-merge
robot-clickhouse added a commit that referenced this pull request May 18, 2026
@robot-clickhouse robot-clickhouse added the pr-backports-created Backport PRs are successfully created, it won't be processed by CI script anymore label May 18, 2026
alexey-milovidov pushed a commit that referenced this pull request May 19, 2026
…eMultiply` self-merge (#105212)

Co-authored-by: robot-clickhouse <robot-clickhouse@users.noreply.github.com>
alexey-milovidov pushed a commit that referenced this pull request May 19, 2026
…eMultiply` self-merge (#105218)

Co-authored-by: robot-clickhouse <robot-clickhouse@users.noreply.github.com>
Algunenano added a commit that referenced this pull request May 19, 2026
Backport #103536 to 26.3: Fix heap-use-after-free in `executeAggregateMultiply` self-merge
Algunenano added a commit that referenced this pull request May 19, 2026
Backport #103536 to 26.2: Fix heap-use-after-free in `executeAggregateMultiply` self-merge
groeneai added a commit to groeneai/ClickHouse that referenced this pull request Jun 11, 2026
Wrap the public `merge` method of `IAggregateFunction` in a non-virtual
NVI entry point that asserts `place != rhs` (in debug or sanitizer builds)
before forwarding to a new pure-virtual `mergeImpl`.

The same hazard motivated PR ClickHouse#103536 (heap-use-after-free in
`executeAggregateMultiply` self-merge, STID 0988-40af). For aggregate
functions whose `merge` reallocates the destination's internal storage and
then reads from the source (`quantilesExact`, `groupArray`,
`groupUniqArray`, `windowFunnel`, `sequenceMatch`, …), passing the same
pointer as both source and destination is undefined. The new
`chassert(place != rhs, …)` aborts on this in DEBUG_OR_SANITIZER_BUILD,
catching future occurrences of the entire bug class at the call site
instead of letting them surface as memory corruption.

Same wrapper added for the parallel `merge` overload (with `ThreadPool`)
and for the `mergeBatch` / `mergeAndDestroyBatch` paths in
`IAggregateFunctionHelper` (which devirtualise the call and so bypass the
public wrapper). 71 concrete aggregate-function overrides renamed from
`merge` to `mergeImpl`. Callers are unchanged — they still call `merge`,
which now performs the check.

Closes the follow-up requested by @alexey-milovidov on PR ClickHouse#103536.
alexey-milovidov added a commit that referenced this pull request Jun 27, 2026
…as-assert-on-103536

Add IAggregateFunction::merge self-aliasing chassert (follow-up to #103536)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

can be tested Allows running workflows for external contributors pr-backports-created Backport PRs are successfully created, it won't be processed by CI script anymore pr-bugfix Pull request with bugfix, not backported by default pr-must-backport Pull request should be backported intentionally. Use this label with great care! pr-must-backport-synced The `*-must-backport` labels are synced into the cloud Sync PR pr-synced-to-cloud The PR is synced to the cloud repo

Projects

None yet

Development

Successfully merging this pull request may close these issues.

7 participants