1
0
mirror of https://github.com/MariaDB/server.git synced 2026-10-23 18:49:29 +03:00

MDEV-29483 Test for wrong result with window function and IN subquery

Add a test case to main.win that still returns a wrong result with
the previous commit applied. The result file records the current
(wrong) output: an empty set. The expected result is one row 'b'.

  CREATE TABLE t1 (c0 TEXT, c1 INT);
  INSERT INTO t1 VALUES ('a', 1), ('b', 2);
  CREATE TABLE t2 (c0 TEXT);
  INSERT INTO t2 VALUES ('b');
  SELECT COALESCE(c0, LAST_VALUE(c0) OVER (ORDER BY c1))
  FROM (SELECT c0, c1 FROM t1) dt
  WHERE c1 = 0 OR c0 IN (SELECT c0 FROM t2);

Findings so far:

1. The derived table dt is merged. The outer references to c0 in the
   WHERE clause and in the select list become Item_direct_view_ref
   objects pointing to one shared Item_field.

2. The window function forces a post-join aggregation temporary table
   (JOIN::make_aggr_tables_info() -> create_tmp_table()).
   create_tmp_field() with modify_item redirects result_field of that
   shared Item_field to the temporary table field.

3. The WHERE clause is evaluated in evaluate_join_record() for each
   join row, before the row is written to the temporary table. The IN
   predicate caches its left expression: Item_in_optimizer::val_bool()
   -> Item_cache_str::cache_value() -> Item_direct_view_ref::str_result()
   -> Item_field::str_result(), which reads result_field, not field.

4. So the WHERE clause compares the value copied by copy_fields() for
   the last row that passed the WHERE clause, not the current row. In
   the test, row 'a' sees an empty temporary table field and is
   filtered out, so nothing is copied, and row 'b' sees the same empty
   field and is filtered out too.

5. The heap-use-after-free from the original report is the same bug
   with a BLOB column on InnoDB. copy_fields() copied the BLOB with
   do_field_eq(), which keeps a pointer into the InnoDB row blob heap.
   InnoDB frees that heap on the next row fetch
   (row_mysql_prebuilt_free_blob_heap()), and the next WHERE
   evaluation dereferences it in Binary_string::copy(). Recorded with
   rr: the only free of the crashing block happens in the t1 scan on
   the row1 -> row2 transition, and at the crash result_field belongs
   to the temporary table while field holds row2's own value.

6. The previous commit deep-copies BLOBs for window function queries,
   which removes the use-after-free but not the stale read. The wrong
   result is not BLOB-specific and does not depend on InnoDB.

7. With a plain c0 in the select list instead of the COALESCE()
   expression the result is correct, so the shape of the select list
   item affects whether result_field is redirected.

The real fix belongs to the optimizer layer: either the merged
derived table must not share an item between a WHERE condition and a
select list item that gets a temporary table result_field, or the
temporary table setup must not redirect such an item, or the IN
predicate cache must read field when evaluated before the temporary
table is filled. MDEV-39868 (commit c0c4b5573f) is a similar case of
merged derived table columns sharing one Item_field.
This commit is contained in:
Aleksey Midenkov
2026-10-05 12:25:28 +03:00
parent be45cd469b
commit bb3a52fabf
2 changed files with 27 additions and 0 deletions

View File

@@ -4794,5 +4794,18 @@ c1 c2
2 2
DROP TABLE t1;
#
# MDEV-29483 Heap-use-after-free (Binary_string::copy()) with window functions
#
create table t1 (c0 text, c1 int);
insert into t1 values ('a', 1), ('b', 2);
create table t2 (c0 text);
insert into t2 values ('b');
# Expected: one row 'b'
select coalesce(c0, last_value(c0) over (order by c1)) from (select c0, c1 from t1) dt
where c1 = 0 or c0 in (select c0 from t2);
coalesce(c0, last_value(c0) over (order by c1))
b
drop table t1, t2;
#
# End of 10.11 tests
#

View File

@@ -3108,6 +3108,20 @@ from t1;
DROP TABLE t1;
--echo #
--echo # MDEV-29483 Heap-use-after-free (Binary_string::copy()) with window functions
--echo #
create table t1 (c0 text, c1 int);
insert into t1 values ('a', 1), ('b', 2);
create table t2 (c0 text);
insert into t2 values ('b');
--echo # Expected: one row 'b'
select coalesce(c0, last_value(c0) over (order by c1)) from (select c0, c1 from t1) dt
where c1 = 0 or c0 in (select c0 from t2);
drop table t1, t2;
--echo #
--echo # End of 10.11 tests
--echo #