用于EagleEye3.0 规则集漏报和误报测试的示例项目,项目收集于github和gitee
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

544 lines
17 KiB

5 months ago
/* Copyright (c) 2002, 2019, Oracle and/or its affiliates. All rights reserved.
This program is free software; you can redistribute it and/or modify
it under the terms of the GNU General Public License, version 2.0,
as published by the Free Software Foundation.
This program is also distributed with certain software (including
but not limited to OpenSSL) that is licensed under separate terms,
as designated in a particular file or component or in included license
documentation. The authors of MySQL hereby grant you an additional
permission to link the program and your derivative works with the
separately licensed software that they have included with MySQL.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License, version 2.0, for more details.
You should have received a copy of the GNU General Public License
along with this program; if not, write to the Free Software
Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */
#include "sql/sp_rcontext.h"
#include <atomic>
#include <new>
#include "my_alloc.h"
#include "my_dbug.h"
#include "my_sys.h"
#include "mysql/psi/psi_base.h"
#include "mysqld_error.h"
#include "sql/current_thd.h"
#include "sql/derror.h" // ER_THD
#include "sql/field.h"
#include "sql/protocol.h"
#include "sql/sp.h" // sp_eval_instr
#include "sql/sp_instr.h" // sp_instr
#include "sql/sp_pcontext.h" // sp_pcontext
#include "sql/sql_class.h" // THD
#include "sql/sql_cursor.h" // mysql_open_cursor
#include "sql/sql_list.h"
#include "sql/sql_tmp_table.h" // create_tmp_table_from_fields
#include "template_utils.h" // delete_container_pointers
class SELECT_LEX_UNIT;
extern "C" void sql_alloc_error_handler(void);
///////////////////////////////////////////////////////////////////////////
// sp_rcontext implementation.
///////////////////////////////////////////////////////////////////////////
sp_rcontext::sp_rcontext(const sp_pcontext *root_parsing_ctx,
Field *return_value_fld, bool in_sub_stmt)
: end_partial_result_set(false),
m_root_parsing_ctx(root_parsing_ctx),
m_var_table(NULL),
m_return_value_fld(return_value_fld),
m_return_value_set(false),
m_in_sub_stmt(in_sub_stmt),
m_visible_handlers(PSI_INSTRUMENT_ME),
m_activated_handlers(PSI_INSTRUMENT_ME),
m_ccount(0) {}
sp_rcontext::~sp_rcontext() {
if (m_var_table) {
free_blobs(m_var_table);
destroy(m_var_table);
}
delete_container_pointers(m_activated_handlers);
delete_container_pointers(m_visible_handlers);
pop_all_cursors();
// Leave m_var_items and m_case_expr_holders untouched.
// They are allocated in mem roots and will be freed accordingly.
}
sp_rcontext *sp_rcontext::create(THD *thd, const sp_pcontext *root_parsing_ctx,
Field *return_value_fld) {
sp_rcontext *ctx = new (thd->mem_root)
sp_rcontext(root_parsing_ctx, return_value_fld, thd->in_sub_stmt);
if (!ctx) return NULL;
if (ctx->alloc_arrays(thd) || ctx->init_var_table(thd) ||
ctx->init_var_items(thd)) {
destroy(ctx);
return NULL;
}
return ctx;
}
bool sp_rcontext::alloc_arrays(THD *thd) {
{
size_t n = m_root_parsing_ctx->max_cursor_index();
m_cstack.reset(
static_cast<sp_cursor **>(thd->alloc(n * sizeof(sp_cursor *))), n);
}
{
size_t n = m_root_parsing_ctx->get_num_case_exprs();
m_case_expr_holders.reset(
static_cast<Item_cache **>(thd->mem_calloc(n * sizeof(Item_cache *))),
n);
}
return !m_cstack.array() || !m_case_expr_holders.array();
}
bool sp_rcontext::init_var_table(THD *thd) {
List<Create_field> field_def_lst;
if (!m_root_parsing_ctx->max_var_index()) return false;
m_root_parsing_ctx->retrieve_field_definitions(&field_def_lst);
DBUG_ASSERT(field_def_lst.elements == m_root_parsing_ctx->max_var_index());
if (!(m_var_table = create_tmp_table_from_fields(thd, field_def_lst)))
return true;
m_var_table->copy_blobs = true;
m_var_table->alias = "";
return false;
}
bool sp_rcontext::init_var_items(THD *thd) {
uint num_vars = m_root_parsing_ctx->max_var_index();
m_var_items.reset(static_cast<Item **>(thd->alloc(num_vars * sizeof(Item *))),
num_vars);
if (!m_var_items.array()) return true;
for (uint idx = 0; idx < num_vars; ++idx) {
if (!(m_var_items[idx] = new Item_field(m_var_table->field[idx])))
return true;
}
return false;
}
bool sp_rcontext::set_return_value(THD *thd, Item **return_value_item) {
DBUG_ASSERT(m_return_value_fld);
m_return_value_set = true;
return sp_eval_expr(thd, m_return_value_fld, return_value_item);
}
bool sp_rcontext::push_cursor(sp_instr_cpush *i) {
/*
We should create cursors on the system heap because:
- they could be (and usually are) used in several instructions,
thus they can not be stored on an execution mem-root;
- a cursor can be pushed/popped many times in a loop, having these objects
on callers' mem-root would lead to a memory leak in every iteration.
*/
sp_cursor *c = new (std::nothrow) sp_cursor(i);
if (!c) {
sql_alloc_error_handler();
return true;
}
m_cstack[m_ccount++] = c;
return false;
}
void sp_rcontext::pop_cursors(uint count) {
DBUG_ASSERT(m_ccount >= count);
while (count--) delete m_cstack[--m_ccount];
}
bool sp_rcontext::push_handler(sp_handler *handler, uint first_ip) {
/*
We should create handler entries on the system heap because:
- they could be (and usually are) used in several instructions,
thus they can not be stored on an execution mem-root;
- a handler can be pushed/popped many times in a loop, having these
objects on callers' mem-root would lead to a memory leak in every
iteration.
*/
sp_handler_entry *he = new (std::nothrow) sp_handler_entry(handler, first_ip);
if (!he) {
sql_alloc_error_handler();
return true;
}
return m_visible_handlers.push_back(he);
}
void sp_rcontext::pop_handlers(sp_pcontext *current_scope) {
for (int i = static_cast<int>(m_visible_handlers.size()) - 1; i >= 0; --i) {
int handler_level = m_visible_handlers.at(i)->handler->scope->get_level();
if (handler_level >= current_scope->get_level()) {
delete m_visible_handlers.back();
m_visible_handlers.pop_back();
}
}
}
void sp_rcontext::pop_handler_frame(THD *thd) {
Handler_call_frame *frame = m_activated_handlers.back();
m_activated_handlers.pop_back();
// Also pop matching DA and copy new conditions.
DBUG_ASSERT(thd->get_stmt_da() == &frame->handler_da);
thd->pop_diagnostics_area();
// Out with the old, in with the new!
thd->get_stmt_da()->reset_condition_info(thd);
thd->get_stmt_da()->copy_new_sql_conditions(thd, &frame->handler_da);
delete frame;
}
void sp_rcontext::exit_handler(THD *thd, sp_pcontext *target_scope) {
/*
The handler has successfully completed. We should now pop the current
handler frame and pop the diagnostics area which was pushed when the
handler was activated.
The diagnostics area of the caller should then contain only any
conditions pushed during handler execution. The conditions present
when the handler was activated should be removed since they have
been successfully handled.
*/
pop_handler_frame(thd);
// Pop frames below the target scope level.
for (int i = static_cast<int>(m_activated_handlers.size()) - 1; i >= 0; --i) {
int handler_level = m_activated_handlers.at(i)->handler->scope->get_level();
if (handler_level <= target_scope->get_level()) break;
pop_handler_frame(thd);
}
/*
Condition was successfully handled, reset condition count
so we don't trigger the handler again for the same warning.
*/
thd->get_stmt_da()->reset_statement_cond_count();
}
bool sp_rcontext::handle_sql_condition(THD *thd, uint *ip,
const sp_instr *cur_spi) {
DBUG_TRACE;
/*
If this is a fatal sub-statement error, and this runtime
context corresponds to a sub-statement, no CONTINUE/EXIT
handlers from this context are applicable: try to locate one
in the outer scope.
*/
if (thd->is_fatal_sub_stmt_error && m_in_sub_stmt) return false;
Diagnostics_area *da = thd->get_stmt_da();
const sp_handler *found_handler = NULL;
Sql_condition *found_condition = NULL;
if (thd->is_error()) {
sp_pcontext *cur_pctx = cur_spi->get_parsing_ctx();
found_handler = cur_pctx->find_handler(
da->returned_sqlstate(), da->mysql_errno(), Sql_condition::SL_ERROR);
if (found_handler) {
found_condition = da->error_condition();
/*
error_condition can be NULL if the Diagnostics Area was full
when the error was raised. It can also be NULL if
Diagnostics_area::set_error_status(uint sql_error) was used.
In these cases, make a temporary Sql_condition here so the
error can be handled.
*/
if (!found_condition) {
found_condition = new (callers_arena->mem_root) Sql_condition(
callers_arena->mem_root, da->mysql_errno(), da->returned_sqlstate(),
Sql_condition::SL_ERROR, da->message_text());
}
}
} else if (da->current_statement_cond_count()) {
Diagnostics_area::Sql_condition_iterator it = da->sql_conditions();
const Sql_condition *c;
/*
Here we need to find the last warning/note from the stack.
In MySQL most substantial warning is the last one.
(We could have used a reverse iterator here if one existed.)
We ignore preexisting conditions so we don't throw for them
again if the next statement isn't one that pre-clears the
DA. (Critically, that includes hpush_jump, i.e. any handlers
declared within the one we're calling. At that point, the
catcher for our throw would become very hard to predict!)
One benefit of not simply clearing the DA as we enter a handler
(instead of resetting the condition cound further down in this
exact function as we do now) and forcing the user to utilize
GET STACKED DIAGNOSTICS is that this way, we can make
SHOW WARNINGS|ERRORS work.
*/
while ((c = it++)) {
if (c->severity() == Sql_condition::SL_WARNING ||
c->severity() == Sql_condition::SL_NOTE) {
sp_pcontext *cur_pctx = cur_spi->get_parsing_ctx();
const sp_handler *handler = cur_pctx->find_handler(
c->returned_sqlstate(), c->mysql_errno(), c->severity());
if (handler) {
found_handler = handler;
found_condition = const_cast<Sql_condition *>(c);
}
}
}
}
if (!found_handler) return false;
// At this point, we know that:
// - there is a pending SQL-condition (error or warning);
// - there is an SQL-handler for it.
DBUG_ASSERT(found_condition);
sp_handler_entry *handler_entry = NULL;
for (size_t i = 0; i < m_visible_handlers.size(); ++i) {
sp_handler_entry *h = m_visible_handlers.at(i);
if (h->handler == found_handler) {
handler_entry = h;
break;
}
}
/*
handler_entry usually should not be NULL here, as that indicates
that the parser context thinks a HANDLER should be activated,
but the runtime context cannot find it.
However, this can happen (and this is in line with the Standard)
if SQL-condition has been raised before DECLARE HANDLER instruction
is processed.
For example:
CREATE PROCEDURE p()
BEGIN
DECLARE v INT DEFAULT 'get'; -- raises SQL-warning here
DECLARE EXIT HANDLER ... -- this handler does not catch the warning
END
*/
if (!handler_entry) return false;
uint continue_ip = handler_entry->handler->type == sp_handler::CONTINUE
? cur_spi->get_cont_dest()
: 0;
/* Add a frame to handler-call-stack. */
Handler_call_frame *frame = new (std::nothrow)
Handler_call_frame(found_handler, found_condition, continue_ip);
if (!frame) {
sql_alloc_error_handler();
return false;
}
m_activated_handlers.push_back(frame);
/* End aborted result set. */
if (end_partial_result_set) thd->get_protocol()->end_partial_result_set();
/* Reset error state. */
thd->clear_error();
thd->killed = THD::NOT_KILLED; // Some errors set thd->killed
// (e.g. "bad data").
thd->push_diagnostics_area(&frame->handler_da);
/*
Mark current conditions so we later will know which conditions
were added during handler execution (if any).
*/
frame->handler_da.mark_preexisting_sql_conditions();
frame->handler_da.reset_statement_cond_count();
*ip = handler_entry->first_ip;
return true;
}
bool sp_rcontext::set_variable(THD *thd, Field *field, Item **value) {
if (!value) {
field->set_null();
return false;
}
return sp_eval_expr(thd, field, value);
}
Item_cache *sp_rcontext::create_case_expr_holder(THD *thd,
const Item *item) const {
Item_cache *holder;
Query_arena backup_arena;
thd->swap_query_arena(*thd->sp_runtime_ctx->callers_arena, &backup_arena);
holder = Item_cache::get_cache(item);
thd->swap_query_arena(backup_arena, thd->sp_runtime_ctx->callers_arena);
return holder;
}
bool sp_rcontext::set_case_expr(THD *thd, int case_expr_id,
Item **case_expr_item_ptr) {
Item *case_expr_item = sp_prepare_func_item(thd, case_expr_item_ptr);
if (!case_expr_item) return true;
if (!m_case_expr_holders[case_expr_id] ||
m_case_expr_holders[case_expr_id]->result_type() !=
case_expr_item->result_type()) {
m_case_expr_holders[case_expr_id] =
create_case_expr_holder(thd, case_expr_item);
}
m_case_expr_holders[case_expr_id]->store(case_expr_item);
m_case_expr_holders[case_expr_id]->cache_value();
return false;
}
///////////////////////////////////////////////////////////////////////////
// sp_cursor implementation.
///////////////////////////////////////////////////////////////////////////
/**
Open an SP cursor
@param thd Thread context
@return Error status
*/
bool sp_cursor::open(THD *thd) {
if (m_server_side_cursor) {
my_error(ER_SP_CURSOR_ALREADY_OPEN, MYF(0));
return true;
}
return mysql_open_cursor(thd, &m_result, &m_server_side_cursor);
}
bool sp_cursor::close() {
if (!m_server_side_cursor) {
my_error(ER_SP_CURSOR_NOT_OPEN, MYF(0));
return true;
}
destroy();
return false;
}
void sp_cursor::destroy() {
delete m_server_side_cursor;
m_server_side_cursor = NULL;
}
bool sp_cursor::fetch(List<sp_variable> *vars) {
if (!m_server_side_cursor) {
my_error(ER_SP_CURSOR_NOT_OPEN, MYF(0));
return true;
}
if (vars->elements != m_result.get_field_count()) {
my_error(ER_SP_WRONG_NO_OF_FETCH_ARGS, MYF(0));
return true;
}
DBUG_EXECUTE_IF(
"bug23032_emit_warning",
push_warning(current_thd, Sql_condition::SL_WARNING, ER_UNKNOWN_ERROR,
ER_THD(current_thd, ER_UNKNOWN_ERROR)););
m_result.set_spvar_list(vars);
/* Attempt to fetch one row */
if (m_server_side_cursor->is_open()) {
if (m_server_side_cursor->fetch(1)) return true;
}
/*
If the cursor was pointing after the last row, the fetch will
close it instead of sending any rows.
*/
if (!m_server_side_cursor->is_open()) {
my_error(ER_SP_FETCH_NO_DATA, MYF(0));
return true;
}
return false;
}
///////////////////////////////////////////////////////////////////////////
// sp_cursor::Query_fetch_into_spvars implementation.
///////////////////////////////////////////////////////////////////////////
bool sp_cursor::Query_fetch_into_spvars::prepare(THD *thd, List<Item> &fields,
SELECT_LEX_UNIT *u) {
/*
Cache the number of columns in the result set in order to easily
return an error if column count does not match value count.
*/
field_count = fields.elements;
return Query_result_interceptor::prepare(thd, fields, u);
}
bool sp_cursor::Query_fetch_into_spvars::send_data(THD *thd,
List<Item> &items) {
List_iterator_fast<sp_variable> spvar_iter(*spvar_list);
List_iterator_fast<Item> item_iter(items);
sp_variable *spvar;
Item *item;
/* Must be ensured by the caller */
DBUG_ASSERT(spvar_list->elements == items.elements);
/*
Assign the row fetched from a server side cursor to stored
procedure variables.
*/
for (; spvar = spvar_iter++, item = item_iter++;) {
if (thd->sp_runtime_ctx->set_variable(thd, spvar->offset, &item))
return true;
}
return false;
}