From 6c56c92a6c31ce4e85e3032ba72177dcadfa8aa5 Mon Sep 17 00:00:00 2001 From: Rucha Deodhar Date: Thu, 25 Sep 2025 01:10:14 +0530 Subject: [PATCH] MDEV-36809: json_array_intersect crashs when unused table ref provided Analysis: So, there were two problems that needed to be fixed. 1) To fix the crash. 2) After fixing the crash, the result was wrong. Reason for crash: When we pass the hash to get_intersect_between_arrays(), We were initialially not passing it value, so the operations were not performed correctly. Reason for wrong result: The number of rows that it was returning were same as that in the table, but, only the first row had correct ouput, rest of them were NULL (it should also be the result of interection). This was because we modified the "items" HASH by deleting the "seen" elements. So for next rows, it did not have the elements it should have in the hash. Fix: 1) To fix the crash: pass the HASH by reference 2) To fix incorrect result: Maintain a separate "seen" hash, if an item is found the the "items" hash, delete it ony temporarily and put it in the seen hash. At then end, put the items from "seen" back into "items" and reset "seen". --- mysql-test/main/func_json.result | 14 ++++++++++ mysql-test/main/func_json.test | 8 ++++++ sql/item_jsonfunc.cc | 48 ++++++++++++++++++++++---------- sql/item_jsonfunc.h | 17 +++++++---- 4 files changed, 67 insertions(+), 20 deletions(-) diff --git a/mysql-test/main/func_json.result b/mysql-test/main/func_json.result index bcf0477a0f9..9957fb3dd26 100644 --- a/mysql-test/main/func_json.result +++ b/mysql-test/main/func_json.result @@ -5272,6 +5272,8 @@ SET @obj1='{ "a": 1,"b": 2,"c": 3}'; SELECT JSON_OBJECT_FILTER_KEYS (@obj1,@arr1); JSON_OBJECT_FILTER_KEYS (@obj1,@arr1) NULL +SET character_set_database=default; +SET CHARACTER SET default; # End of 11.2 Test # Beginning of 11.4 Test # @@ -5283,4 +5285,16 @@ NULL SELECT json_array_intersect(@a,@b); json_array_intersect(@a,@b) NULL +# MDEV-36809: json_array_intersect crashs when unused table ref provided +# +select json_array_intersect('[["1", "7"], ["2", "6"], ["4", "5"], ["3", "8"]]', '[["2","6"],["3","8"],["4","5"],["1","7"]]') as result from mysql.user; +result +[["2", "6"], ["3", "8"], ["4", "5"], ["1", "7"]] +[["2", "6"], ["3", "8"], ["4", "5"], ["1", "7"]] +[["2", "6"], ["3", "8"], ["4", "5"], ["1", "7"]] +[["2", "6"], ["3", "8"], ["4", "5"], ["1", "7"]] +[["2", "6"], ["3", "8"], ["4", "5"], ["1", "7"]] +SELECT ( WITH x AS ( WITH x ( x ) AS ( SELECT ( 1.000000 ) ) SELECT x FROM x ) SELECT * FROM x WHERE ( SELECT AVG ( x ) OVER ( ORDER BY JSON_ARRAY_INTERSECT ( '[["1", "7"], ["2", "6"], ["3", "8"]]' , '[["2","6"],["3","8"],["4","5"],["1","7"]]' ) ) FROM x ) ); +( WITH x AS ( WITH x ( x ) AS ( SELECT ( 1.000000 ) ) SELECT x FROM x ) SELECT * FROM x WHERE ( SELECT AVG ( x ) OVER ( ORDER BY JSON_ARRAY_INTERSECT ( '[["1", "7"], ["2", "6"], ["3", "8"]]' , '[["2","6"],["3","8"],["4","5"],["1","7"]]' ) ) FROM x ) ) +1.000000 # End of 11.4 Test diff --git a/mysql-test/main/func_json.test b/mysql-test/main/func_json.test index e56c5ee4820..6219d7ac9c1 100644 --- a/mysql-test/main/func_json.test +++ b/mysql-test/main/func_json.test @@ -4175,6 +4175,9 @@ SET CHARACTER SET utf8; SET @obj1='{ "a": 1,"b": 2,"c": 3}'; SELECT JSON_OBJECT_FILTER_KEYS (@obj1,@arr1); +SET character_set_database=default; +SET CHARACTER SET default; + --echo # End of 11.2 Test --echo # Beginning of 11.4 Test @@ -4188,4 +4191,9 @@ SELECT JSON_OBJECT_FILTER_KEYS (@obj1,@arr1); SELECT json_array_intersect(@a,@b); +--echo # MDEV-36809: json_array_intersect crashs when unused table ref provided +--echo # +select json_array_intersect('[["1", "7"], ["2", "6"], ["4", "5"], ["3", "8"]]', '[["2","6"],["3","8"],["4","5"],["1","7"]]') as result from mysql.user; +SELECT ( WITH x AS ( WITH x ( x ) AS ( SELECT ( 1.000000 ) ) SELECT x FROM x ) SELECT * FROM x WHERE ( SELECT AVG ( x ) OVER ( ORDER BY JSON_ARRAY_INTERSECT ( '[["1", "7"], ["2", "6"], ["3", "8"]]' , '[["2","6"],["3","8"],["4","5"],["1","7"]]' ) ) FROM x ) ); + --echo # End of 11.4 Test diff --git a/sql/item_jsonfunc.cc b/sql/item_jsonfunc.cc index c2fc76f07ca..ac9f5b57f4d 100644 --- a/sql/item_jsonfunc.cc +++ b/sql/item_jsonfunc.cc @@ -5242,14 +5242,14 @@ bool Item_func_json_key_value::fix_length_and_dec(THD *thd) } -static bool create_hash(json_engine_t *value, HASH *items, bool &hash_inited, +static bool create_hash(json_engine_t *value, HASH *items, bool &item_hash_inited, MEM_ROOT *hash_root) { int level= value->stack_p; if (my_hash_init(PSI_INSTRUMENT_ME, items, value->s.cs, 0, 0, 0, get_key_name, NULL, 0)) return true; - hash_inited= true; + item_hash_inited= true; while (json_scan_next(value) == 0 && value->stack_p >= level) { @@ -5318,6 +5318,11 @@ static bool get_current_value(json_engine_t *js, const uchar *&value_start, return false; } +static my_bool restore_entry(void *element, void *arg) +{ + HASH *items = (HASH*) arg; + return my_hash_insert(items, (const uchar*) element); +} /* If the outermost layer of JSON is an array, @@ -5330,14 +5335,16 @@ static bool get_current_value(json_engine_t *js, const uchar *&value_start, FALSE - if two array documents have intersection TRUE - If two array documents do not have intersection */ -static bool get_intersect_between_arrays(String *str, json_engine_t *value, - HASH items) +bool Item_func_json_array_intersect:: + get_intersect_between_arrays(String *str, json_engine_t *value, + HASH *items, HASH *seen) { bool res= true, has_value= false; int level= value->stack_p; - String temp_str(0); + temp_str.length(0); temp_str.append('['); + while (json_scan_next(value) == 0 && value->stack_p >= level) { const uchar *value_start= NULL; @@ -5373,14 +5380,14 @@ static bool get_intersect_between_arrays(String *str, json_engine_t *value, of times the value appears in the hash table. */ uchar * found= NULL; - if ((found= my_hash_search(&items, + if ((found= my_hash_search(items, (const uchar *) new_entry, strlen(new_entry)))) { has_value= true; temp_str.append( (const char*) value_start, value_len); temp_str.append(','); - if (my_hash_delete(&items, found)) + if (my_hash_delete(items, found) || my_hash_insert(seen, (const uchar *)found)) { free(new_entry); goto error; @@ -5399,6 +5406,8 @@ static bool get_intersect_between_arrays(String *str, json_engine_t *value, } error: + my_hash_iterate(seen, restore_entry, items); + my_hash_reset(seen); return res; } @@ -5417,12 +5426,14 @@ String* Item_func_json_array_intersect::val_str(String *str) { if (args[0]->null_value) goto null_return; - if (hash_inited) + if (item_hash_inited) my_hash_free(&items); + if (seen_hash_inited) + my_hash_free(&seen); if (root_inited) free_root(&hash_root, MYF(0)); root_inited= false; - hash_inited= false; + item_hash_inited= false; prepare_json_and_create_hash(&je1, js1); } @@ -5438,7 +5449,7 @@ String* Item_func_json_array_intersect::val_str(String *str) if (json_read_value(&je2) || je2.value_type != JSON_VALUE_ARRAY) goto error_return; - if (get_intersect_between_arrays(str, &je2, items)) + if (get_intersect_between_arrays(str, &je2, &items, &seen)) goto error_return; if (str->length()) @@ -5465,7 +5476,7 @@ null_return: return NULL; } -void Item_func_json_array_intersect::prepare_json_and_create_hash(json_engine_t *je1, String *js) +bool Item_func_json_array_intersect::prepare_json_and_create_hash(json_engine_t *je1, String *js) { json_scan_start(je1, js->charset(), (const uchar *) js->ptr(), (const uchar *) js->ptr() + js->length()); @@ -5473,12 +5484,17 @@ void Item_func_json_array_intersect::prepare_json_and_create_hash(json_engine_t Scan value uses the hash table to get the intersection of two arrays. */ + if (my_hash_init(PSI_INSTRUMENT_ME, &seen, je1->s.cs, 0, 0, 0, + get_key_name, NULL, 0)) + return true; + seen_hash_inited= true; + if (!root_inited) init_alloc_root(PSI_NOT_INSTRUMENTED, &hash_root, 1024, 0, MYF(0)); root_inited= true; if (json_read_value(je1) || je1->value_type != JSON_VALUE_ARRAY || - create_hash(je1, &items, hash_inited, &hash_root)) + create_hash(je1, &items, item_hash_inited, &hash_root)) { if (je1->s.error) report_json_error(js, je1, 0); @@ -5487,6 +5503,8 @@ void Item_func_json_array_intersect::prepare_json_and_create_hash(json_engine_t max_length= (args[0]->max_length < args[1]->max_length) ? args[0]->max_length : args[1]->max_length; + + return false; } bool Item_func_json_array_intersect::fix_length_and_dec(THD *thd) @@ -5509,8 +5527,10 @@ bool Item_func_json_array_intersect::fix_length_and_dec(THD *thd) js1= args[0]->val_json(&tmp_js1); - if (js1) - prepare_json_and_create_hash(&je1, js1); + if (js1 && prepare_json_and_create_hash(&je1, js1)) + { + return TRUE; + } end: set_maybe_null(); diff --git a/sql/item_jsonfunc.h b/sql/item_jsonfunc.h index 14080b2b242..3d48cf95dd7 100644 --- a/sql/item_jsonfunc.h +++ b/sql/item_jsonfunc.h @@ -881,14 +881,15 @@ public: class Item_func_json_array_intersect: public Item_str_func { protected: - String tmp_js1, tmp_js2; - bool hash_inited, root_inited; - HASH items; + String tmp_js1, tmp_js2, temp_str; + bool item_hash_inited, seen_hash_inited, root_inited; + HASH items, seen; MEM_ROOT hash_root; bool parse_for_each_row; public: Item_func_json_array_intersect(THD *thd, Item *a, Item *b): - Item_str_func(thd, a, b) { hash_inited= root_inited= parse_for_each_row= false; } + Item_str_func(thd, a, b) + { item_hash_inited= seen_hash_inited= root_inited= parse_for_each_row= false; } String *val_str(String *) override; bool fix_length_and_dec(THD *thd) override; LEX_CSTRING func_name_cstring() const override @@ -901,12 +902,16 @@ public: void cleanup() override { Item_str_func::cleanup(); - if (hash_inited) + if (item_hash_inited) my_hash_free(&items); + if (seen_hash_inited) + my_hash_free(&seen); if (root_inited) free_root(&hash_root, MYF(0)); } - void prepare_json_and_create_hash(json_engine_t *je1, String *js); + bool prepare_json_and_create_hash(json_engine_t *je1, String *js); + bool get_intersect_between_arrays(String *str, json_engine_t *value, + HASH *items, HASH *seen); }; class Item_func_json_object_filter_keys: public Item_str_func