summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSergei Petrunia <sergey@mariadb.com>2022-04-26 23:29:29 +0300
committerSergei Petrunia <sergey@mariadb.com>2022-04-29 11:00:37 +0300
commit8db9aa496c59e48238ed5718d683fb6bf2ae2445 (patch)
tree66a006f61fa64dc5e1cb509d655fa90c11031a59
parent3f68c2169e16814e8e268d0f9a9f29aee522453e (diff)
downloadmariadb-git-8db9aa496c59e48238ed5718d683fb6bf2ae2445.tar.gz
MDEV-28268: Server crashes in Expression_cache_tracker::fetch_current_stats
(cherry-pick into preview-10.9-MDEV-27021-explain tree) Expression_cache_tmptable object uses an Expression_cache_tracker object to report the statistics. In the common scenario, Expression_cache_tmptable destructor sets tracker->cache=NULL. The tracker object survives after the expression cache is deleted and one may call cache_tracker->fetch_current_stats() for it with no harm. However a degenerate cache with no parameters does not set tracker->cache=NULL in Expression_cache_tmptable destructor which results in an attempt to use freed data in the cache_tracker->fetch_current_stats() call. Fixed by setting tracker->cache to NULL and wrapping the assignment into a function.
-rw-r--r--mysql-test/main/subselect4.result90
-rw-r--r--mysql-test/main/subselect4.test16
-rw-r--r--sql/sql_expression_cache.cc4
-rw-r--r--sql/sql_expression_cache.h5
4 files changed, 114 insertions, 1 deletions
diff --git a/mysql-test/main/subselect4.result b/mysql-test/main/subselect4.result
index 2eba69149e3..c23352c6767 100644
--- a/mysql-test/main/subselect4.result
+++ b/mysql-test/main/subselect4.result
@@ -2906,6 +2906,96 @@ id select_type table type possible_keys key key_len ref rows Extra
2 DEPENDENT SUBQUERY tn eq_ref PRIMARY PRIMARY 32 test.tms.key1 1 Using where
set optimizer_switch=@tmp_os;
drop table t1, t10, t11;
+#
+# MDEV-28268: Server crashes in Expression_cache_tracker::fetch_current_stats
+#
+CREATE TABLE t1 (a INT, b INT);
+INSERT INTO t1 VALUES (1,2),(3,4);
+ANALYZE FORMAT=JSON
+SELECT DISTINCT
+(SELECT MIN(a) FROM t1 WHERE b <= ANY (SELECT a FROM t1)) AS f
+FROM t1;
+ANALYZE
+{
+ "query_block": {
+ "select_id": 1,
+ "r_loops": 1,
+ "r_total_time_ms": "REPLACED",
+ "duplicate_removal": {
+ "temporary_table": {
+ "nested_loop": [
+ {
+ "table": {
+ "table_name": "t1",
+ "access_type": "ALL",
+ "r_loops": 1,
+ "rows": 2,
+ "r_rows": 2,
+ "r_table_time_ms": "REPLACED",
+ "r_other_time_ms": "REPLACED",
+ "filtered": 100,
+ "r_filtered": 100
+ }
+ }
+ ],
+ "subqueries": [
+ {
+ "expression_cache": {
+ "state": "disabled",
+ "r_loops": 0,
+ "query_block": {
+ "select_id": 2,
+ "r_loops": 1,
+ "r_total_time_ms": "REPLACED",
+ "nested_loop": [
+ {
+ "table": {
+ "table_name": "t1",
+ "access_type": "ALL",
+ "r_loops": 1,
+ "rows": 2,
+ "r_rows": 2,
+ "r_table_time_ms": "REPLACED",
+ "r_other_time_ms": "REPLACED",
+ "filtered": 100,
+ "r_filtered": 50,
+ "attached_condition": "<nop>(<in_optimizer>(t1.b,(subquery#3) >= 4))"
+ }
+ }
+ ],
+ "subqueries": [
+ {
+ "query_block": {
+ "select_id": 3,
+ "r_loops": 1,
+ "r_total_time_ms": "REPLACED",
+ "nested_loop": [
+ {
+ "table": {
+ "table_name": "t1",
+ "access_type": "ALL",
+ "r_loops": 1,
+ "rows": 2,
+ "r_rows": 2,
+ "r_table_time_ms": "REPLACED",
+ "r_other_time_ms": "REPLACED",
+ "filtered": 100,
+ "r_filtered": 100
+ }
+ }
+ ]
+ }
+ }
+ ]
+ }
+ }
+ }
+ ]
+ }
+ }
+ }
+}
+DROP TABLE t1;
# End of 10.2 tests
# End of 10.3 tests
#
diff --git a/mysql-test/main/subselect4.test b/mysql-test/main/subselect4.test
index cde9b4c25c3..95e73755226 100644
--- a/mysql-test/main/subselect4.test
+++ b/mysql-test/main/subselect4.test
@@ -2425,6 +2425,21 @@ set optimizer_switch=@tmp_os;
drop table t1, t10, t11;
+--echo #
+--echo # MDEV-28268: Server crashes in Expression_cache_tracker::fetch_current_stats
+--echo #
+CREATE TABLE t1 (a INT, b INT);
+INSERT INTO t1 VALUES (1,2),(3,4);
+
+--source include/analyze-format.inc
+ANALYZE FORMAT=JSON
+SELECT DISTINCT
+ (SELECT MIN(a) FROM t1 WHERE b <= ANY (SELECT a FROM t1)) AS f
+FROM t1;
+
+# Cleanup
+DROP TABLE t1;
+
--echo # End of 10.2 tests
--echo # End of 10.3 tests
@@ -2470,3 +2485,4 @@ select * from t1 where t1.a in (select t2.a from t2 order by t2.b);
drop table t0, t1, t2;
--echo # End of 10.4 tests
+
diff --git a/sql/sql_expression_cache.cc b/sql/sql_expression_cache.cc
index 351ec258ddb..bc44ecd79fa 100644
--- a/sql/sql_expression_cache.cc
+++ b/sql/sql_expression_cache.cc
@@ -63,7 +63,7 @@ void Expression_cache_tmptable::disable_cache()
cache_table= NULL;
update_tracker();
if (tracker)
- tracker->cache= NULL;
+ tracker->detach_from_cache();
}
@@ -189,6 +189,8 @@ Expression_cache_tmptable::~Expression_cache_tmptable()
else
{
update_tracker();
+ if (tracker)
+ tracker->detach_from_cache();
tracker= NULL;
}
}
diff --git a/sql/sql_expression_cache.h b/sql/sql_expression_cache.h
index 031773adb9f..9c618a5ae9b 100644
--- a/sql/sql_expression_cache.h
+++ b/sql/sql_expression_cache.h
@@ -83,7 +83,11 @@ public:
cache(c), hit(0), miss(0), state(UNINITED)
{}
+private:
+ // This can be NULL if the cache is already deleted
Expression_cache *cache;
+
+public:
ulong hit, miss;
enum expr_cache_state state;
@@ -91,6 +95,7 @@ public:
void set(ulong h, ulong m, enum expr_cache_state s)
{hit= h; miss= m; state= s;}
+ void detach_from_cache() { cache= NULL; }
void fetch_current_stats()
{
if (cache)