From cc831f16c82f00d3531e09c2f5c59eadc0abb0d7 Mon Sep 17 00:00:00 2001 From: Dmitry Shulga Date: Mon, 24 Mar 2025 12:38:46 +0700 Subject: [PATCH] MDEV-36079: Stored routine with a cursor crashes on the second execution if a DDL statement happened Attempt to run a cursor after change in metadata of tables it depends on resulted in firing assertion on allocating a memory from a memory root marked as read only. On every execution of a cursor its Query_arena is set up as a statement arena (see sp_lex_keeper::cursor_reset_lex_and_exec_core()). As a consequence, any memory allocations happened on execution of the cursor's query should be taken from the cursor's memory root. The reason of allocating a memory from the memory root marked as read only is that the cursor's memory root points to a memory root of sp_head that could be already marked as read only after first successful execution and this relation isn't changed on re-parsing of the cursor's query. To fix the issue, memory root of cursor is adjusted in the method sp_lex_instr::parse_expr() to point to the new memory root just created for re-parsing of failed query. --- mysql-test/main/ps_mem_leaks.result | 25 ++++++++++++++++++++++ mysql-test/main/ps_mem_leaks.test | 32 +++++++++++++++++++++++++++++ sql/sp_instr.cc | 7 +++++++ 3 files changed, 64 insertions(+) diff --git a/mysql-test/main/ps_mem_leaks.result b/mysql-test/main/ps_mem_leaks.result index a1b6063537e..880d86e3d2c 100644 --- a/mysql-test/main/ps_mem_leaks.result +++ b/mysql-test/main/ps_mem_leaks.result @@ -441,3 +441,28 @@ SET @@debug_dbug=@save_dbug; DROP TABLE t1; DROP PROCEDURE p1; # End of 11.2 tests +# MDEV-36079: Stored routine with a cursor crashes on +# the second execution if a DDL statement happened +CREATE OR REPLACE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); +CREATE PROCEDURE p1() +BEGIN +DECLARE va INT DEFAULT 0; +DECLARE cur CURSOR FOR SELECT a FROM t1; +OPEN cur; +FETCH cur INTO va; +SELECT va; +CLOSE cur; +END; +$ +CALL p1; +va +1 +ALTER TABLE t1 MODIFY a INT UNSIGNED; +CALL p1; +va +1 +# Clean up +DROP PROCEDURE p1; +DROP TABLE t1; +# End of 11.8 tests diff --git a/mysql-test/main/ps_mem_leaks.test b/mysql-test/main/ps_mem_leaks.test index 2aba6b87db7..09e91b25538 100644 --- a/mysql-test/main/ps_mem_leaks.test +++ b/mysql-test/main/ps_mem_leaks.test @@ -469,3 +469,35 @@ DROP TABLE t1; DROP PROCEDURE p1; --echo # End of 11.2 tests + +--echo # MDEV-36079: Stored routine with a cursor crashes on +--echo # the second execution if a DDL statement happened +CREATE OR REPLACE TABLE t1 (a INT); +INSERT INTO t1 VALUES (1); + +--delimiter $ +CREATE PROCEDURE p1() +BEGIN + DECLARE va INT DEFAULT 0; + DECLARE cur CURSOR FOR SELECT a FROM t1; + + OPEN cur; + FETCH cur INTO va; + SELECT va; + CLOSE cur; +END; +$ + +--delimiter ; + +CALL p1; + +ALTER TABLE t1 MODIFY a INT UNSIGNED; + +CALL p1; + +--echo # Clean up +DROP PROCEDURE p1; +DROP TABLE t1; + +--echo # End of 11.8 tests diff --git a/sql/sp_instr.cc b/sql/sp_instr.cc index a87d1c31756..cc0c9af2888 100644 --- a/sql/sp_instr.cc +++ b/sql/sp_instr.cc @@ -862,6 +862,13 @@ LEX* sp_lex_instr::parse_expr(THD *thd, sp_head *sp, LEX *sp_instr_lex) cleanup_items(cursor_lex->free_list); cursor_free_list= &cursor_lex->free_list; DBUG_ASSERT(thd->lex == sp_instr_lex); + /* + Adjust mem_root of the cursor's Query_arena to point the just created + memory root allocated for re-parsing, else we would have the pointer to + sp_head's memory_root that has already been marked as read_only after + the first successful execution of the stored routine. + */ + cursor_lex->query_arena()->mem_root= m_mem_root_for_reparsing; lex_start(thd); }