Commit 356b3df7 authored by Davi Arnaut's avatar Davi Arnaut

Bug#47627 SET @@{global.session}.local_variable in stored routine causes crash

This patch borrows ideas, text and code from Kristofer
Pettersson's patch.

An assignment of a system variable sharing the same base
name as a declared stored procedure variable in the same
context could lead to a crash.

The reason was that during the parsing of the syntactic
rule 'option_value' an uninitialized set_var object was
pushed to the parameter stack of the SET statement. The
parent rule 'option_type_value' interpreted the existence
of variables on the parameter stack as an assignment and
wrapped it in a sp_instr_set object.

As the procedure later was executed an attempt was made
to run the method 'check()' on an uninitialized member
object (NULL value) belonging to the previously created
but uninitialized object.

This patch refactors the 'internal_variable_name' rule and
copies the semantic analysis part to the depending parent
rule: 'option_value'. This makes it possible to account
for any prefixes affecting the interpretation of the
internal_variable_name.

mysql-test/r/sp.result:
  Add test case result.
mysql-test/t/sp.test:
  Add test case for bug.
sql/sql_yacc.yy:
  - Reduce churn in rule sys_option_value by moving to
    specialized functions.
  - Comment the the lookup in the rule internel_variable_name
    is a best effort operation.
  - Lookup for a system variable in the option_value if one was
    not found (the variable could have been shadowed)
parent cf872354
...@@ -6979,6 +6979,64 @@ CALL p1; ...@@ -6979,6 +6979,64 @@ CALL p1;
ERROR 42S22: Unknown column 'A.b' in 'IN/ALL/ANY subquery' ERROR 42S22: Unknown column 'A.b' in 'IN/ALL/ANY subquery'
DROP PROCEDURE p1; DROP PROCEDURE p1;
DROP TABLE t1, t2; DROP TABLE t1, t2;
#
# Bug#47627: SET @@{global.session}.local_variable in stored routine causes crash
# Bug#48626: Crash or lost connection using SET for declared variables with @@
#
DROP PROCEDURE IF EXISTS p1;
DROP PROCEDURE IF EXISTS p2;
DROP PROCEDURE IF EXISTS p3;
CREATE PROCEDURE p1()
BEGIN
DECLARE v INT DEFAULT 0;
SET @@SESSION.v= 10;
END//
ERROR HY000: Unknown system variable 'v'
CREATE PROCEDURE p2()
BEGIN
DECLARE v INT DEFAULT 0;
SET v= 10;
END//
call p2()//
CREATE PROCEDURE p3()
BEGIN
DECLARE v INT DEFAULT 0;
SELECT @@SESSION.v;
END//
ERROR HY000: Unknown system variable 'v'
CREATE PROCEDURE p4()
BEGIN
DECLARE v INT DEFAULT 0;
SET @@GLOBAL.v= 10;
END//
ERROR HY000: Unknown system variable 'v'
CREATE PROCEDURE p5()
BEGIN
DECLARE init_connect INT DEFAULT 0;
SET init_connect= 10;
SET @@GLOBAL.init_connect= 'SELECT 1';
SET @@SESSION.IDENTITY= 1;
SELECT @@SESSION.IDENTITY;
SELECT @@GLOBAL.init_connect;
SELECT init_connect;
END//
CREATE PROCEDURE p6()
BEGIN
DECLARE v INT DEFAULT 0;
SET @@v= 0;
END//
ERROR HY000: Unknown system variable 'v'
SET @old_init_connect= @@GLOBAL.init_connect;
CALL p5();
@@SESSION.IDENTITY
1
@@GLOBAL.init_connect
SELECT 1
init_connect
10
SET @@GLOBAL.init_connect= @old_init_connect;
DROP PROCEDURE p2;
DROP PROCEDURE p5;
# ------------------------------------------------------------------ # ------------------------------------------------------------------
# -- End of 5.1 tests # -- End of 5.1 tests
# ------------------------------------------------------------------ # ------------------------------------------------------------------
...@@ -8263,6 +8263,73 @@ CALL p1; ...@@ -8263,6 +8263,73 @@ CALL p1;
DROP PROCEDURE p1; DROP PROCEDURE p1;
DROP TABLE t1, t2; DROP TABLE t1, t2;
--echo #
--echo # Bug#47627: SET @@{global.session}.local_variable in stored routine causes crash
--echo # Bug#48626: Crash or lost connection using SET for declared variables with @@
--echo #
--disable_warnings
DROP PROCEDURE IF EXISTS p1;
DROP PROCEDURE IF EXISTS p2;
DROP PROCEDURE IF EXISTS p3;
--enable_warnings
delimiter //;
--error ER_UNKNOWN_SYSTEM_VARIABLE
CREATE PROCEDURE p1()
BEGIN
DECLARE v INT DEFAULT 0;
SET @@SESSION.v= 10;
END//
CREATE PROCEDURE p2()
BEGIN
DECLARE v INT DEFAULT 0;
SET v= 10;
END//
call p2()//
--error ER_UNKNOWN_SYSTEM_VARIABLE
CREATE PROCEDURE p3()
BEGIN
DECLARE v INT DEFAULT 0;
SELECT @@SESSION.v;
END//
--error ER_UNKNOWN_SYSTEM_VARIABLE
CREATE PROCEDURE p4()
BEGIN
DECLARE v INT DEFAULT 0;
SET @@GLOBAL.v= 10;
END//
CREATE PROCEDURE p5()
BEGIN
DECLARE init_connect INT DEFAULT 0;
SET init_connect= 10;
SET @@GLOBAL.init_connect= 'SELECT 1';
SET @@SESSION.IDENTITY= 1;
SELECT @@SESSION.IDENTITY;
SELECT @@GLOBAL.init_connect;
SELECT init_connect;
END//
--error ER_UNKNOWN_SYSTEM_VARIABLE
CREATE PROCEDURE p6()
BEGIN
DECLARE v INT DEFAULT 0;
SET @@v= 0;
END//
delimiter ;//
SET @old_init_connect= @@GLOBAL.init_connect;
CALL p5();
SET @@GLOBAL.init_connect= @old_init_connect;
DROP PROCEDURE p2;
DROP PROCEDURE p5;
--echo # ------------------------------------------------------------------ --echo # ------------------------------------------------------------------
--echo # -- End of 5.1 tests --echo # -- End of 5.1 tests
......
...@@ -389,6 +389,138 @@ void case_stmt_action_end_case(LEX *lex, bool simple) ...@@ -389,6 +389,138 @@ void case_stmt_action_end_case(LEX *lex, bool simple)
lex->sphead->do_cont_backpatch(); lex->sphead->do_cont_backpatch();
} }
static bool
find_sys_var_null_base(THD *thd, struct sys_var_with_base *tmp)
{
tmp->var= find_sys_var(thd, tmp->base_name.str, tmp->base_name.length);
if (tmp->var == NULL)
my_error(ER_UNKNOWN_SYSTEM_VARIABLE, MYF(0), tmp->base_name.str);
else
tmp->base_name= null_lex_str;
return thd->is_error();
}
/**
Helper action for a SET statement.
Used to push a system variable into the assignment list.
@param thd the current thread
@param tmp the system variable with base name
@param var_type the scope of the variable
@param val the value being assigned to the variable
@return TRUE if error, FALSE otherwise.
*/
static bool
set_system_variable(THD *thd, struct sys_var_with_base *tmp,
enum enum_var_type var_type, Item *val)
{
set_var *var;
LEX *lex= thd->lex;
/* No AUTOCOMMIT from a stored function or trigger. */
if (lex->spcont && tmp->var == &sys_autocommit)
lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT;
if (! (var= new set_var(var_type, tmp->var, &tmp->base_name, val)))
return TRUE;
return lex->var_list.push_back(var);
}
/**
Helper action for a SET statement.
Used to push a SP local variable into the assignment list.
@param thd the current thread
@param var_type the SP local variable
@param val the value being assigned to the variable
@return TRUE if error, FALSE otherwise.
*/
static bool
set_local_variable(THD *thd, sp_variable_t *spv, Item *val)
{
Item *it;
LEX *lex= thd->lex;
sp_instr_set *sp_set;
if (val)
it= val;
else if (spv->dflt)
it= spv->dflt;
else
{
it= new (thd->mem_root) Item_null();
if (it == NULL)
return TRUE;
}
sp_set= new sp_instr_set(lex->sphead->instructions(), lex->spcont,
spv->offset, it, spv->type, lex, TRUE);
return (sp_set == NULL || lex->sphead->add_instr(sp_set));
}
/**
Helper action for a SET statement.
Used to SET a field of NEW row.
@param thd the current thread
@param name the field name
@param val the value being assigned to the row
@return TRUE if error, FALSE otherwise.
*/
static bool
set_trigger_new_row(THD *thd, LEX_STRING *name, Item *val)
{
LEX *lex= thd->lex;
Item_trigger_field *trg_fld;
sp_instr_set_trigger_field *sp_fld;
/* QQ: Shouldn't this be field's default value ? */
if (! val)
val= new Item_null();
DBUG_ASSERT(lex->trg_chistics.action_time == TRG_ACTION_BEFORE &&
(lex->trg_chistics.event == TRG_EVENT_INSERT ||
lex->trg_chistics.event == TRG_EVENT_UPDATE));
trg_fld= new (thd->mem_root)
Item_trigger_field(lex->current_context(),
Item_trigger_field::NEW_ROW,
name->str, UPDATE_ACL, FALSE);
if (trg_fld == NULL)
return TRUE;
sp_fld= new sp_instr_set_trigger_field(lex->sphead->instructions(),
lex->spcont, trg_fld, val, lex);
if (sp_fld == NULL)
return TRUE;
/*
Let us add this item to list of all Item_trigger_field
objects in trigger.
*/
lex->trg_table_fields.link_in_list((uchar *) trg_fld,
(uchar **) &trg_fld->next_trg_field);
return lex->sphead->add_instr(sp_fld);
}
/** /**
Helper to resolve the SQL:2003 Syntax exception 1) in <in predicate>. Helper to resolve the SQL:2003 Syntax exception 1) in <in predicate>.
See SQL:2003, Part 2, section 8.4 <in predicate>, Note 184, page 383. See SQL:2003, Part 2, section 8.4 <in predicate>, Note 184, page 383.
...@@ -11809,98 +11941,42 @@ sys_option_value: ...@@ -11809,98 +11941,42 @@ sys_option_value:
option_type internal_variable_name equal set_expr_or_default option_type internal_variable_name equal set_expr_or_default
{ {
THD *thd= YYTHD; THD *thd= YYTHD;
LEX *lex=Lex; LEX *lex= Lex;
LEX_STRING *name= &$2.base_name;
if ($2.var == trg_new_row_fake_var) if ($2.var == trg_new_row_fake_var)
{ {
/* We are in trigger and assigning value to field of new row */ /* We are in trigger and assigning value to field of new row */
Item *it;
Item_trigger_field *trg_fld;
sp_instr_set_trigger_field *sp_fld;
LINT_INIT(sp_fld);
if ($1) if ($1)
{ {
my_parse_error(ER(ER_SYNTAX_ERROR)); my_parse_error(ER(ER_SYNTAX_ERROR));
MYSQL_YYABORT; MYSQL_YYABORT;
} }
if ($4) if (set_trigger_new_row(YYTHD, name, $4))
it= $4;
else
{
/* QQ: Shouldn't this be field's default value ? */
it= new Item_null();
}
DBUG_ASSERT(lex->trg_chistics.action_time == TRG_ACTION_BEFORE &&
(lex->trg_chistics.event == TRG_EVENT_INSERT ||
lex->trg_chistics.event == TRG_EVENT_UPDATE));
trg_fld= new (thd->mem_root)
Item_trigger_field(Lex->current_context(),
Item_trigger_field::NEW_ROW,
$2.base_name.str,
UPDATE_ACL, FALSE);
if (trg_fld == NULL)
MYSQL_YYABORT;
sp_fld= new sp_instr_set_trigger_field(lex->sphead->
instructions(),
lex->spcont,
trg_fld,
it, lex);
if (sp_fld == NULL)
MYSQL_YYABORT;
/*
Let us add this item to list of all Item_trigger_field
objects in trigger.
*/
lex->trg_table_fields.link_in_list((uchar *)trg_fld,
(uchar **) &trg_fld->
next_trg_field);
if (lex->sphead->add_instr(sp_fld))
MYSQL_YYABORT; MYSQL_YYABORT;
} }
else if ($2.var) else if ($2.var)
{ /* System variable */ {
if ($1) if ($1)
lex->option_type= $1; lex->option_type= $1;
set_var *var= new set_var(lex->option_type, $2.var,
&$2.base_name, $4); /* It is a system variable. */
if (var == NULL) if (set_system_variable(thd, &$2, lex->option_type, $4))
MYSQL_YYABORT; MYSQL_YYABORT;
lex->var_list.push_back(var);
} }
else else
{ {
/* An SP local variable */ sp_pcontext *spc= lex->spcont;
sp_pcontext *ctx= lex->spcont; sp_variable_t *spv= spc->find_variable(name);
sp_variable_t *spv;
sp_instr_set *sp_set;
Item *it;
if ($1) if ($1)
{ {
my_parse_error(ER(ER_SYNTAX_ERROR)); my_parse_error(ER(ER_SYNTAX_ERROR));
MYSQL_YYABORT; MYSQL_YYABORT;
} }
spv= ctx->find_variable(&$2.base_name); /* It is a local variable. */
if (set_local_variable(thd, spv, $4))
if ($4)
it= $4;
else if (spv->dflt)
it= spv->dflt;
else
{
it= new (thd->mem_root) Item_null();
if (it == NULL)
MYSQL_YYABORT;
}
sp_set= new sp_instr_set(lex->sphead->instructions(), ctx,
spv->offset, it, spv->type, lex, TRUE);
if (sp_set == NULL ||
lex->sphead->add_instr(sp_set))
MYSQL_YYABORT; MYSQL_YYABORT;
} }
} }
...@@ -11936,11 +12012,16 @@ option_value: ...@@ -11936,11 +12012,16 @@ option_value:
} }
| '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default | '@' '@' opt_var_ident_type internal_variable_name equal set_expr_or_default
{ {
LEX *lex=Lex; THD *thd= YYTHD;
set_var *var= new set_var($3, $4.var, &$4.base_name, $6); struct sys_var_with_base tmp= $4;
if (var == NULL) /* Lookup if necessary: must be a system variable. */
if (tmp.var == NULL)
{
if (find_sys_var_null_base(thd, &tmp))
MYSQL_YYABORT;
}
if (set_system_variable(thd, &tmp, $3, $6))
MYSQL_YYABORT; MYSQL_YYABORT;
lex->var_list.push_back(var);
} }
| charset old_or_new_charset_name_or_default | charset old_or_new_charset_name_or_default
{ {
...@@ -12033,31 +12114,26 @@ internal_variable_name: ...@@ -12033,31 +12114,26 @@ internal_variable_name:
ident ident
{ {
THD *thd= YYTHD; THD *thd= YYTHD;
LEX *lex= thd->lex; sp_pcontext *spc= thd->lex->spcont;
sp_pcontext *spc= lex->spcont;
sp_variable_t *spv; sp_variable_t *spv;
/* We have to lookup here since local vars can shadow sysvars */ /* Best effort lookup for system variable. */
if (!spc || !(spv = spc->find_variable(&$1))) if (!spc || !(spv = spc->find_variable(&$1)))
{ {
struct sys_var_with_base tmp= {NULL, $1};
/* Not an SP local variable */ /* Not an SP local variable */
sys_var *tmp=find_sys_var(thd, $1.str, $1.length); if (find_sys_var_null_base(thd, &tmp))
if (!tmp)
MYSQL_YYABORT; MYSQL_YYABORT;
$$.var= tmp;
$$.base_name= null_lex_str; $$= tmp;
if (spc && tmp == &sys_autocommit)
{
/*
We don't allow setting AUTOCOMMIT from a stored function
or trigger.
*/
lex->sphead->m_flags|= sp_head::HAS_SET_AUTOCOMMIT_STMT;
}
} }
else else
{ {
/* An SP local variable */ /*
Possibly an SP local variable (or a shadowed sysvar).
Will depend on the context of the SET statement.
*/
$$.var= NULL; $$.var= NULL;
$$.base_name= $1; $$.base_name= $1;
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment