Skip to content

Commit 0b5894c

Browse files
committed
Bug #20087571: 5.7 EASILY HITS ERROR 1436 (HY000): THREAD STACK OVERRUN ERRORS ON MANY EXPR'S
This bug is a regression of WL7200: that WL introduced an unconditional recursive processing of AND and OR parse tree nodes during the contextualization, when the original parser had some conditional flattening of nested AND/OR expression. That affected very long recursive AND/OR expression: instead of the normal execution the parser failed with a parse error: ER_STACK_OVERRUN_NEED_MORE. This bugfix re-introduces an optimization that flattens recursive AND/OR expressions at parse time (before the itemization/contextualization): (X1 AND X2) AND (Y1 AND Y2) ==> AND (X1, X2, Y1, Y2) (X1 AND X2) AND Y ==> AND (X1, X2, Y) X AND (Y1 AND Y2) ==> AND (X, Y1, Y2) and the same for OR.
1 parent 1cc9ac9 commit 0b5894c

8 files changed

+239
-144
lines changed

mysql-test/r/parser-big.result

+50
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
#
2+
# Bug #20087571: 5.7 EASILY HITS ERROR 1436 (HY000):
3+
# THREAD STACK OVERRUN ERRORS ON MANY EXPR'S
4+
#
5+
SELECT @tmp_max:= @@global.max_allowed_packet;
6+
@tmp_max:= @@global.max_allowed_packet
7+
4194304
8+
SET @@global.max_allowed_packet=16999424;
9+
CREATE TABLE t1 (a INT UNSIGNED, b INT UNSIGNED, PRIMARY KEY (a, b))
10+
ENGINE=INNODB;
11+
CREATE PROCEDURE p1(p_min_terms INT UNSIGNED, p_max_terms INT UNSIGNED, p_incr
12+
INT UNSIGNED)
13+
begin
14+
DECLARE v_sql LONGTEXT CHARSET LATIN1;
15+
DECLARE i INT UNSIGNED DEFAULT 1;
16+
SET i:=greatest(1, p_min_terms);
17+
REPEAT
18+
SET @terms:=f1(i);
19+
SET v_sql:=concat("delete from t1 where ",@terms);
20+
SET @t:=v_sql;
21+
SELECT i AS 'numterms', length(@t) AS 'query lengt', p_incr AS 'increment';
22+
PREPARE s FROM @t;
23+
EXECUTE s;
24+
DEALLOCATE PREPARE s;
25+
SET i:=i + p_incr;
26+
UNTIL i > p_max_terms end repeat;
27+
END|
28+
CREATE FUNCTION f1(p_numterms INT UNSIGNED) RETURNS LONGTEXT
29+
BEGIN
30+
DECLARE ret LONGTEXT DEFAULT '';
31+
SET ret:=CONCAT(REPEAT("(a=1 and b=2) or ",GREATEST(p_numterms-1,1)),"(a=2 and b=1)");
32+
RETURN RET;
33+
END|
34+
SET @min:=950000;
35+
SET @max:=950000;
36+
SET @incr:=100000;
37+
CALL p1(@min, @max, @incr);
38+
numterms query lengt increment
39+
950000 16150017 100000
40+
SELECT LENGTH(@terms);
41+
LENGTH(@terms)
42+
16149996
43+
SELECT @@global.thread_stack, @@global.max_allowed_packet;
44+
@@global.thread_stack @@global.max_allowed_packet
45+
262144 16999424
46+
DROP FUNCTION f1;
47+
DROP PROCEDURE p1;
48+
DROP TABLE t1;
49+
SET @@global.max_allowed_packet:= @tmp_max;
50+
#

mysql-test/t/parser-big.test

+61
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
--source include/big_test.inc
2+
3+
--echo #
4+
--echo # Bug #20087571: 5.7 EASILY HITS ERROR 1436 (HY000):
5+
--echo # THREAD STACK OVERRUN ERRORS ON MANY EXPR'S
6+
--echo #
7+
8+
# we need a larger max_allowed_packet for the REPEAT() function
9+
SELECT @tmp_max:= @@global.max_allowed_packet;
10+
SET @@global.max_allowed_packet=16999424;
11+
# switching connection to allow the new max_allowed_packet take effect
12+
--connect (newconn, localhost, root,,)
13+
14+
CREATE TABLE t1 (a INT UNSIGNED, b INT UNSIGNED, PRIMARY KEY (a, b))
15+
ENGINE=INNODB;
16+
17+
DELIMITER |;
18+
19+
CREATE PROCEDURE p1(p_min_terms INT UNSIGNED, p_max_terms INT UNSIGNED, p_incr
20+
INT UNSIGNED)
21+
begin
22+
DECLARE v_sql LONGTEXT CHARSET LATIN1;
23+
DECLARE i INT UNSIGNED DEFAULT 1;
24+
SET i:=greatest(1, p_min_terms);
25+
REPEAT
26+
SET @terms:=f1(i);
27+
SET v_sql:=concat("delete from t1 where ",@terms);
28+
SET @t:=v_sql;
29+
SELECT i AS 'numterms', length(@t) AS 'query lengt', p_incr AS 'increment';
30+
PREPARE s FROM @t;
31+
EXECUTE s;
32+
DEALLOCATE PREPARE s;
33+
SET i:=i + p_incr;
34+
UNTIL i > p_max_terms end repeat;
35+
END|
36+
37+
CREATE FUNCTION f1(p_numterms INT UNSIGNED) RETURNS LONGTEXT
38+
BEGIN
39+
DECLARE ret LONGTEXT DEFAULT '';
40+
SET ret:=CONCAT(REPEAT("(a=1 and b=2) or ",GREATEST(p_numterms-1,1)),"(a=2 and b=1)");
41+
RETURN RET;
42+
END|
43+
44+
DELIMITER ;|
45+
46+
SET @min:=950000;
47+
SET @max:=950000;
48+
SET @incr:=100000;
49+
CALL p1(@min, @max, @incr);
50+
SELECT LENGTH(@terms);
51+
SELECT @@global.thread_stack, @@global.max_allowed_packet;
52+
53+
DROP FUNCTION f1;
54+
DROP PROCEDURE p1;
55+
DROP TABLE t1;
56+
57+
--connection default
58+
SET @@global.max_allowed_packet:= @tmp_max;
59+
--disconnect newconn
60+
61+
--echo #

sql/item_cmpfunc.cc

+27
Original file line numberDiff line numberDiff line change
@@ -5321,6 +5321,33 @@ Item_cond::Item_cond(THD *thd, Item_cond *item)
53215321
*/
53225322
}
53235323

5324+
/**
5325+
Contextualization for Item_cond functional items
5326+
5327+
Item_cond successors use Item_cond::list instead of Item_func::args
5328+
and Item_func::arg_count, so we can't itemize parse-time Item_cond
5329+
objects by forwarding a contextualization process to the parent Item_func
5330+
class: we need to overload this function to run a contextualization
5331+
the Item_cond::list items.
5332+
*/
5333+
bool Item_cond::itemize(Parse_context *pc, Item **res)
5334+
{
5335+
if (skip_itemize(res))
5336+
return false;
5337+
if (super::itemize(pc, res))
5338+
return true;
5339+
5340+
List_iterator<Item> li(list);
5341+
Item *item;
5342+
while ((item= li++))
5343+
{
5344+
if (item->itemize(pc, &item))
5345+
return true;
5346+
li.replace(item);
5347+
}
5348+
return false;
5349+
}
5350+
53245351

53255352
void Item_cond::copy_andor_arguments(THD *thd, Item_cond *item)
53265353
{

sql/item_cmpfunc.h

+22-17
Original file line numberDiff line numberDiff line change
@@ -142,6 +142,9 @@ class Item_bool_func :public Item_int_func
142142
{
143143
public:
144144
Item_bool_func() : Item_int_func(), m_created_by_in2exists(false) {}
145+
explicit Item_bool_func(const POS &pos)
146+
: Item_int_func(pos), m_created_by_in2exists(false)
147+
{}
145148

146149
Item_bool_func(Item *a) : Item_int_func(a),
147150
m_created_by_in2exists(false) {}
@@ -1917,6 +1920,8 @@ class Item_func_regex :public Item_bool_func
19171920

19181921
class Item_cond :public Item_bool_func
19191922
{
1923+
typedef Item_bool_func super;
1924+
19201925
protected:
19211926
List<Item> list;
19221927
bool abort_on_null;
@@ -1925,12 +1930,20 @@ class Item_cond :public Item_bool_func
19251930
/* Item_cond() is only used to create top level items */
19261931
Item_cond(): Item_bool_func(), abort_on_null(1)
19271932
{ const_item_cache=0; }
1933+
19281934
Item_cond(Item *i1,Item *i2)
19291935
:Item_bool_func(), abort_on_null(0)
19301936
{
19311937
list.push_back(i1);
19321938
list.push_back(i2);
19331939
}
1940+
Item_cond(const POS &pos, Item *i1, Item *i2)
1941+
:Item_bool_func(pos), abort_on_null(0)
1942+
{
1943+
list.push_back(i1);
1944+
list.push_back(i2);
1945+
}
1946+
19341947
Item_cond(THD *thd, Item_cond *item);
19351948
Item_cond(List<Item> &nlist)
19361949
:Item_bool_func(), list(nlist), abort_on_null(0) {}
@@ -1949,6 +1962,9 @@ class Item_cond :public Item_bool_func
19491962
DBUG_ASSERT(nlist->elements);
19501963
list.prepand(nlist);
19511964
}
1965+
1966+
virtual bool itemize(Parse_context *pc, Item **res);
1967+
19521968
bool fix_fields(THD *, Item **ref);
19531969
void fix_after_pullout(st_select_lex *parent_select,
19541970
st_select_lex *removed_select);
@@ -2151,7 +2167,10 @@ class Item_cond_and :public Item_cond
21512167
the current and level and reference
21522168
to multiple equalities of upper and levels */
21532169
Item_cond_and() :Item_cond() {}
2170+
21542171
Item_cond_and(Item *i1,Item *i2) :Item_cond(i1,i2) {}
2172+
Item_cond_and(const POS &pos, Item *i1, Item *i2) :Item_cond(pos, i1, i2) {}
2173+
21552174
Item_cond_and(THD *thd, Item_cond_and *item) :Item_cond(thd, item) {}
21562175
Item_cond_and(List<Item> &list_arg): Item_cond(list_arg) {}
21572176
enum Functype functype() const { return COND_AND_FUNC; }
@@ -2172,20 +2191,15 @@ class Item_cond_and :public Item_cond
21722191
double rows_in_table);
21732192
};
21742193

2175-
inline bool is_cond_and(Item *item)
2176-
{
2177-
if (item->type() != Item::COND_ITEM)
2178-
return FALSE;
2179-
2180-
Item_cond *cond_item= (Item_cond*) item;
2181-
return (cond_item->functype() == Item_func::COND_AND_FUNC);
2182-
}
21832194

21842195
class Item_cond_or :public Item_cond
21852196
{
21862197
public:
21872198
Item_cond_or() :Item_cond() {}
2199+
21882200
Item_cond_or(Item *i1,Item *i2) :Item_cond(i1,i2) {}
2201+
Item_cond_or(const POS &pos, Item *i1,Item *i2) :Item_cond(pos, i1, i2) {}
2202+
21892203
Item_cond_or(THD *thd, Item_cond_or *item) :Item_cond(thd, item) {}
21902204
Item_cond_or(List<Item> &list_arg): Item_cond(list_arg) {}
21912205
enum Functype functype() const { return COND_OR_FUNC; }
@@ -2206,15 +2220,6 @@ class Item_cond_or :public Item_cond
22062220
double rows_in_table);
22072221
};
22082222

2209-
inline bool is_cond_or(Item *item)
2210-
{
2211-
if (item->type() != Item::COND_ITEM)
2212-
return FALSE;
2213-
2214-
Item_cond *cond_item= (Item_cond*) item;
2215-
return (cond_item->functype() == Item_func::COND_OR_FUNC);
2216-
}
2217-
22182223
/* Some useful inline functions */
22192224

22202225
inline Item *and_conds(Item *a, Item *b)

sql/parse_tree_helpers.h

+73-2
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@
1616
#ifndef PARSE_TREE_HELPERS_INCLUDED
1717
#define PARSE_TREE_HELPERS_INCLUDED
1818

19-
#include "item.h" // Item
19+
#include "item_func.h" // Item etc.
2020
#include "set_var.h" // enum_var_type
2121

2222
typedef class st_select_lex SELECT_LEX;
@@ -38,7 +38,7 @@ class Parse_tree_item : public Item
3838
public:
3939
explicit Parse_tree_item(const POS &pos) : Item(pos) {}
4040

41-
virtual enum Type type() const { DBUG_ASSERT(0); return INVALID_ITEM; }
41+
virtual enum Type type() const { return INVALID_ITEM; }
4242
virtual double val_real() { DBUG_ASSERT(0); return 0; }
4343
virtual longlong val_int() { DBUG_ASSERT(0); return 0; }
4444
virtual String *val_str(String *) { DBUG_ASSERT(0); return NULL; }
@@ -104,6 +104,77 @@ class PT_item_list : public Parse_tree_node
104104
};
105105

106106

107+
/**
108+
Helper function to imitate dynamic_cast for Item_cond hierarchy
109+
110+
@param To destination type (Item_cond_and etc.)
111+
@param Tag Functype tag to compare from->functype() with
112+
@param from source item
113+
114+
@return typecasted item of the type To or NULL
115+
*/
116+
template<class To, Item_func::Functype Tag>
117+
To *item_cond_cast(Item * const from)
118+
{
119+
return ((from->type() == Item::COND_ITEM &&
120+
static_cast<Item_func *>(from)->functype() == Tag) ?
121+
static_cast<To *>(from) : NULL);
122+
}
123+
124+
125+
/**
126+
Flatten associative operators at parse time
127+
128+
This function flattens AND and OR operators at parse time if applicable,
129+
otherwise it creates new Item_cond_and or Item_cond_or respectively.
130+
131+
@param Class Item_cond_and or Item_cond_or
132+
@param Tag COND_AND_FUNC (for Item_cond_and) or COND_OR_FUNC otherwise
133+
134+
@param mem_root MEM_ROOT
135+
@param pos parse location
136+
@param left left argument of the operator
137+
@param right right argument of the operator
138+
139+
@return resulting parse tree Item
140+
*/
141+
template<class Class, Item_func::Functype Tag>
142+
Item *flatten_associative_operator(MEM_ROOT *mem_root, const POS &pos,
143+
Item *left, Item *right)
144+
{
145+
if (left == NULL || right == NULL)
146+
return NULL;
147+
Class *left_func= item_cond_cast<Class, Tag>(left);
148+
Class *right_func= item_cond_cast<Class, Tag>(right);
149+
if (left_func)
150+
{
151+
if (right_func)
152+
{
153+
// (X1 op X2) op (Y1 op Y2) ==> op (X1, X2, Y1, Y2)
154+
right_func->add_at_head(left_func->argument_list());
155+
return right;
156+
}
157+
else
158+
{
159+
// (X1 op X2) op Y ==> op (X1, X2, Y)
160+
left_func->add(right);
161+
return left;
162+
}
163+
}
164+
else if (right_func)
165+
{
166+
// X op (Y1 op Y2) ==> op (X, Y1, Y2)
167+
right_func->add_at_head(left);
168+
return right;
169+
}
170+
else
171+
{
172+
/* X op Y */
173+
return new (mem_root) Class(pos, left, right);
174+
}
175+
}
176+
177+
107178
Item_splocal* create_item_for_sp_var(THD *thd,
108179
LEX_STRING name,
109180
class sp_variable *spv,

0 commit comments

Comments
 (0)