From 987ac4cd156fd94e7e7637aa84e2c5a2d155bfd4 Mon Sep 17 00:00:00 2001 From: Darek Date: Sat, 17 Oct 2026 08:52:30 +0200 Subject: [PATCH] fix(planner): give a re-evaluated expression its own slot in the group WITH x ORDER BY x + x SKIP 0 RETURN x + x AS y crashed intermittently with heap corruption. A projection keeps out-of-scope expressions in their groups, so the ORDER BY key x + x, dropped by the WITH, stayed registered under its unique name. The RETURN's x + x has the same name, and FactorizationGroup::insertExpression kept the old name-to-position entry, so the projection installed its result vector in the slot the ORDER BY scan still wrote through: a use after free. The group now maps the name to the new expression's position. Literals get a unique name per binding, so x + 1 in both places was never affected. Co-Authored-By: Claude Opus 5.5 --- src/include/planner/operator/schema.h | 6 ++++-- .../order_by/order_by_key_repeated.test | 21 +++++++++++++++++++ 2 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 test/test_files/order_by/order_by_key_repeated.test diff --git a/src/include/planner/operator/schema.h b/src/include/planner/operator/schema.h index 22769a455..28d5c892e 100644 --- a/src/include/planner/operator/schema.h +++ b/src/include/planner/operator/schema.h @@ -38,8 +38,10 @@ class FactorizationGroup { double getMultiplier() const { return cardinalityMultiplier; } void insertExpression(const std::shared_ptr& expression) { - DASSERT(!expressionNameToPos.contains(expression->getUniqueName())); - expressionNameToPos.insert({expression->getUniqueName(), expressions.size()}); + // A projection keeps out-of-scope expressions in their groups, so one with the same unique + // name may already be here, e.g. an ORDER BY key a WITH dropped. The new expression gets + // its own position: the old one's is still written by the operator that produced it. + expressionNameToPos.insert_or_assign(expression->getUniqueName(), expressions.size()); expressions.push_back(expression); } binder::expression_vector getExpressions() const { return expressions; } diff --git a/test/test_files/order_by/order_by_key_repeated.test b/test/test_files/order_by/order_by_key_repeated.test new file mode 100644 index 000000000..25f4ec720 --- /dev/null +++ b/test/test_files/order_by/order_by_key_repeated.test @@ -0,0 +1,21 @@ +-DATASET CSV empty + +-- + +-CASE ProjectionRepeatsDiscardedOrderByKey +-LOOP i 1 2000 +-STATEMENT UNWIND [10, 20, NULL, 5] AS x WITH x ORDER BY x + x SKIP 0 RETURN x + x AS y +-CHECK_ORDER +---- 4 +10 +20 +40 + +-STATEMENT UNWIND [10, 20, NULL, 5] AS x WITH x ORDER BY x * x SKIP 0 RETURN x * x AS y +-CHECK_ORDER +---- 4 +25 +100 +400 + +-ENDLOOP