Skip to content

Commit d26a6f4

Browse files
MDEV-38747: ASAN errors in Optimizer_hint_parser::Identifier::to_ident_cli
Summary: A trigger specifying a hint where the hint has a query block name will cause an ASAN failure because hint resolution occurs after query parsing, not during query parsing. The trigger execution logic uses a stack-local string to hold the query and hint text during parsing. During trigger execution, query parsing and query execution happen in different function contexts, so the query string used during parsing goes out of scope, freeing its memory. But as hint resolution occurs after parsing is complete (and hints merely point into the query string, they don't copy from it), the hints refer into a deallocated query string upon hint resolution. Details: Prior to the commit introducing this bug, hint resolution was done via a call to `LEX::resolve_optimizer_hints_in_last_select` when parsing the `query_specification:` grammar rule. This meant that any string containing the query (and hints) was in scope for the entire lifetime of query parsing and hint resolution. In the patch introducing this bug, `resolve_optimizer_hints_in_last_select` was replaced with `handle_parsed_optimizer_hints_in_last_select`, changing the parsing such that it merely cached hints for resolution during query execution. Later, after parsing ends and upon query execution, `mysql_execute_command` calls `LEX::resolve_optimizer_hints` to resolve hints. When executing a typical SQL command trigger, `sp_lex_instr::parse_expr` reparses the query associated with the trigger and does so using a stack-local String variable to hold the query text. `sp_lex_instr::parse_expr` returns after query parsing completes but before hint resolution begins. Since the string holding the query was stack-local in `sp_lex_instr::parse_expr` and destroyed when the method returned, the query string (and hints with it) were deallocated, leading to the ASAN failure on hint resolution. When executing the trigger, `sp_instr_stmt::exec_core` calls `mysql_execute_command` which calls `LEX::resolve_optimizer_hints` to complete hint resolution but the query string that the hints depends on no longer exists at this point. As noted, the stack-local `query_string` variable in `sp_lex_inst::parse_expr` goes out-of-scope and is freed when the `sp_lex_instr::parse_expr` returns. In contrast, in the general case, when a `COM_QUERY` is processed during `dispatch_command`, the query string lives on the `THD` for the lifetime of the query independent of some particular function's scope. For triggers, the necessary lifetime of that query string needs to be as long as `sp_lex_keeper::validate_lex_and_exec_core` which covers both the query string parsing via `sp_lex_instr::parse_expr` and the procedure's execution during `reset_lex_and_exec_core`. Consequently, this patch lifts the `query_string` buffer up out of `parse_expr` and onto the `sp_lex_instr` itself which guarantees that its lifetime is as long as the instruction, which also guarantees the query string's lifetime extends across parsing and execution, including hint resolution. This also covers any cases where the trigger is successfully executed consecutive times but not reparsed between those executions. QB_NAME is not the only affected hint kind; hints with some query block identifier text for the query block, like ``` NO_MERGE(`@select#1`) ``` will also cause the crash while `NO_MERGE()` will not.
1 parent 75c4adf commit d26a6f4

4 files changed

Lines changed: 35 additions & 7 deletions

File tree

mysql-test/main/sp.result

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8867,3 +8867,12 @@ SPECIFIC_NAME
88678867
upper
88688868
DROP FUNCTION upper;
88698869
# End of 11.8 tests
8870+
#
8871+
# MDEV-38747: ASAN errors in Optimizer_hint_parser::Identifier::to_ident_cli
8872+
#
8873+
CREATE TABLE t (a INT);
8874+
INSERT INTO t VALUES (1),(2);
8875+
CREATE TEMPORARY TABLE tmp (b INT);
8876+
CREATE TRIGGER tr AFTER DELETE ON t FOR EACH ROW CREATE OR REPLACE TEMPORARY TABLE tmp AS SELECT /*+ QB_NAME(xxxx) */ 1;
8877+
DELETE FROM t;
8878+
DROP TABLE t;

mysql-test/main/sp.test

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9618,3 +9618,14 @@ SELECT SPECIFIC_NAME FROM INFORMATION_SCHEMA.PARAMETERS WHERE SPECIFIC_SCHEMA='t
96189618
DROP FUNCTION upper;
96199619

96209620
--echo # End of 11.8 tests
9621+
9622+
--echo #
9623+
--echo # MDEV-38747: ASAN errors in Optimizer_hint_parser::Identifier::to_ident_cli
9624+
--echo #
9625+
CREATE TABLE t (a INT);
9626+
INSERT INTO t VALUES (1),(2);
9627+
CREATE TEMPORARY TABLE tmp (b INT);
9628+
# Can be SELECT ... old.a, or SELECT ... * FROM sometable, etc.
9629+
CREATE TRIGGER tr AFTER DELETE ON t FOR EACH ROW CREATE OR REPLACE TEMPORARY TABLE tmp AS SELECT /*+ QB_NAME(xxxx) */ 1;
9630+
DELETE FROM t;
9631+
DROP TABLE t;

sql/sp_instr.cc

Lines changed: 6 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -899,11 +899,10 @@ bool sp_lex_instr::setup_memroot_for_reparsing(sp_head *sphead,
899899

900900
LEX* sp_lex_instr::parse_expr(THD *thd, sp_head *sp, LEX *sp_instr_lex)
901901
{
902-
String sql_query;
902+
sql_query_cache.free();
903+
get_query(&sql_query_cache);
903904

904-
get_query(&sql_query);
905-
906-
if (sql_query.length() == 0)
905+
if (sql_query_cache.length() == 0)
907906
{
908907
/**
909908
The instruction has returned zero-length query string. That means, the
@@ -953,7 +952,7 @@ LEX* sp_lex_instr::parse_expr(THD *thd, sp_head *sp, LEX *sp_instr_lex)
953952
state= STMT_INITIALIZED_FOR_SP;
954953

955954
/*
956-
First, set up a men_root for the statement is going to re-compile.
955+
First, set up a mem_root for the statement is going to re-compile.
957956
*/
958957
bool mem_root_allocated;
959958
if (setup_memroot_for_reparsing(sp, &mem_root_allocated))
@@ -969,7 +968,7 @@ LEX* sp_lex_instr::parse_expr(THD *thd, sp_head *sp, LEX *sp_instr_lex)
969968

970969
Parser_state parser_state;
971970

972-
if (parser_state.init(thd, sql_query.c_ptr(), sql_query.length()))
971+
if (parser_state.init(thd, sql_query_cache.c_ptr(), sql_query_cache.length()))
973972
return nullptr;
974973

975974
/*
@@ -1068,7 +1067,7 @@ LEX* sp_lex_instr::parse_expr(THD *thd, sp_head *sp, LEX *sp_instr_lex)
10681067
the current statement being parsed.
10691068
*/
10701069
const char *m_tmp_query_bak= sp->m_tmp_query;
1071-
sp->m_tmp_query= sql_query.c_ptr();
1070+
sp->m_tmp_query= sql_query_cache.c_ptr();
10721071

10731072
/*
10741073
Hint the parser that re-parsing of a failed SP instruction is in progress

sql/sp_instr.h

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -503,6 +503,15 @@ class sp_lex_instr : public sp_instr
503503
sp_lex_keeper m_lex_keeper;
504504

505505
private:
506+
/**
507+
Buffer responsible for keeping the query text over the lifetime of
508+
*this. If reparsing is required, then this string is cleared and
509+
replaced with the new text on reparse. Allows for deferred hint
510+
resolution when query hints are specified as part of the trigger
511+
definition.
512+
*/
513+
String sql_query_cache;
514+
506515
/**
507516
List of Item_trigger_field objects created on parsing of a SQL statement
508517
corresponding to this SP-instruction.

0 commit comments

Comments
 (0)