diff --git a/mysql-test/main/analyze_engine_stats.result b/mysql-test/main/analyze_engine_stats.result index d21a55102da..8d44428c1d5 100644 --- a/mysql-test/main/analyze_engine_stats.result +++ b/mysql-test/main/analyze_engine_stats.result @@ -103,3 +103,70 @@ select cast(json_extract(@out,'$[0]') as DOUBLE) > 0 as PAGES_UPDATED_MORE_THAN_ PAGES_UPDATED_MORE_THAN_ZERO 1 drop table t1; +# +# MDEV-31764: ASAN use-after-poison in trace_engine_stats upon ANALYZE FORMAT=JSON +# +ANALYZE FORMAT=JSON SELECT count(*) FROM information_schema.GLOBAL_STATUS; +# Another testcase without I_S: +CREATE TABLE t1 (a INT); +INSERT INTO t1 SELECT seq FROM seq_1_to_100; +CREATE TABLE t2 (s INT); +INSERT INTO t2 SELECT seq FROM seq_1_to_10; +# Must use SJ-Materialization to hit the issue with temp.table: +ANALYZE FORMAT=JSON SELECT * FROM t1 WHERE a IN (SELECT s FROM t2); +ANALYZE +{ + "query_block": { + "select_id": 1, + "r_loops": 1, + "r_total_time_ms": "REPLACED", + "const_condition": "1", + "table": { + "table_name": "t1", + "access_type": "ALL", + "r_loops": 1, + "rows": 100, + "r_rows": 100, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": REPLACED, + "filtered": 100, + "r_filtered": 100 + }, + "table": { + "table_name": "", + "access_type": "eq_ref", + "possible_keys": ["distinct_key"], + "key": "distinct_key", + "key_length": "4", + "used_key_parts": ["s"], + "ref": ["func"], + "r_loops": 100, + "rows": 1, + "r_rows": 0.1, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "filtered": 100, + "r_filtered": 100, + "materialized": { + "unique": 1, + "query_block": { + "select_id": 2, + "table": { + "table_name": "t2", + "access_type": "ALL", + "r_loops": 1, + "rows": 10, + "r_rows": 10, + "r_table_time_ms": "REPLACED", + "r_other_time_ms": "REPLACED", + "r_engine_stats": REPLACED, + "filtered": 100, + "r_filtered": 100 + } + } + } + } + } +} +DROP TABLE t1, t2; diff --git a/mysql-test/main/analyze_engine_stats.test b/mysql-test/main/analyze_engine_stats.test index a26c65a1aa4..019ec39064a 100644 --- a/mysql-test/main/analyze_engine_stats.test +++ b/mysql-test/main/analyze_engine_stats.test @@ -62,3 +62,23 @@ select cast(json_extract(@out,'$[0]') as DOUBLE) > 0 as PAGES_UPDATED_MORE_THAN_ drop table t1; +--echo # +--echo # MDEV-31764: ASAN use-after-poison in trace_engine_stats upon ANALYZE FORMAT=JSON +--echo # + +--disable_result_log +ANALYZE FORMAT=JSON SELECT count(*) FROM information_schema.GLOBAL_STATUS; +--enable_result_log + +--echo # Another testcase without I_S: + +CREATE TABLE t1 (a INT); +INSERT INTO t1 SELECT seq FROM seq_1_to_100; +CREATE TABLE t2 (s INT); +INSERT INTO t2 SELECT seq FROM seq_1_to_10; + +--echo # Must use SJ-Materialization to hit the issue with temp.table: +--source include/analyze-format.inc +ANALYZE FORMAT=JSON SELECT * FROM t1 WHERE a IN (SELECT s FROM t2); + +DROP TABLE t1, t2; diff --git a/sql/sql_delete.cc b/sql/sql_delete.cc index 7236d01e1b3..296d075d7f9 100644 --- a/sql/sql_delete.cc +++ b/sql/sql_delete.cc @@ -124,9 +124,12 @@ bool Update_plan::save_explain_data_intern(THD *thd, if (is_analyze || (thd->variables.log_slow_verbosity & LOG_SLOW_VERBOSITY_ENGINE)) + { table->file->set_time_tracker(&explain->table_tracker); - explain->handler_for_stats= table->file; + if (table->file->handler_stats && table->s->tmp_table != INTERNAL_TMP_TABLE) + explain->handler_for_stats= table->file; + } select_lex->set_explain_type(TRUE); explain->select_type= select_lex->type; diff --git a/sql/sql_explain.h b/sql/sql_explain.h index 7b93ba74a42..c0783d5fad1 100644 --- a/sql/sql_explain.h +++ b/sql/sql_explain.h @@ -845,12 +845,18 @@ public: Gap_time_tracker extra_time_tracker; /* - Note: This pointer is only valid until notify_tables_are_closed() is - called. After that, the tables may be freed or reused, together with their - handler_stats objects. + Handler object to get the handler_stats from. + Notes: + This pointer is only valid until notify_tables_are_closed() is called. + After that, the tables may be freed or reused, together with their + handler_stats objects. notify_tables_are_closed() disables printing of FORMAT=JSON output. r_engine_stats is only printed in FORMAT=JSON output, so we're fine. + + We do not store pointers to temporary (aka "work") tables here. + Temporary tables may be freed (e.g. by JOIN::cleanup()) or re-created + during query execution (when HEAP table is converted into Aria). */ handler *handler_for_stats; diff --git a/sql/sql_select.cc b/sql/sql_select.cc index 09ef2700bbd..44a2f1f62ea 100644 --- a/sql/sql_select.cc +++ b/sql/sql_select.cc @@ -27755,12 +27755,16 @@ bool JOIN_TAB::save_explain_data(Explain_table_access *eta, LOG_SLOW_VERBOSITY_ENGINE)) { table->file->set_time_tracker(&eta->op_tracker); + /* Set handler_for_stats even if we are not running an ANALYZE command. There's no harm, and in case somebody runs a SHOW ANALYZE command we'll be able to print the engine statistics. */ - eta->handler_for_stats= table->file; + if (table->file->handler_stats && + table->s->tmp_table != INTERNAL_TMP_TABLE) + eta->handler_for_stats= table->file; + if (likely(thd->lex->analyze_stmt)) { eta->op_tracker.set_gap_tracker(&eta->extra_time_tracker);