Bug #22123583: MYSQL 5.5: MAIN.SP HAS VALGRIND ISSUES

Issue:
-----
When a varchar column is used to fill the record in an
internal temporary table, the length of the string stored
in the column is not taken into account. Instead the
default length of packed data is used to copy with memmove.
This will cause valgrind issues since some bytes are
uninitialized.

SOLUTION:
---------
The solution is to take into account the length of the
string stored in the column while filling the record.

This fix is a backport of BUG#13389854.
parent 5e9a50ef
......@@ -209,7 +209,7 @@ public:
DBUG_ENTER("Field::pack_length_from_metadata");
DBUG_RETURN(field_metadata);
}
virtual uint row_pack_length() { return 0; }
virtual uint row_pack_length() const { return 0; }
virtual int save_field_metadata(uchar *first_byte)
{ return do_save_field_metadata(first_byte); }
......@@ -733,7 +733,7 @@ public:
int store_decimal(const my_decimal *);
my_decimal *val_decimal(my_decimal *);
uint is_equal(Create_field *new_field);
uint row_pack_length() { return pack_length(); }
uint row_pack_length() const { return pack_length(); }
uint32 pack_length_from_metadata(uint field_metadata) {
uint32 length= pack_length();
DBUG_PRINT("result", ("pack_length_from_metadata(%d): %u",
......@@ -923,7 +923,7 @@ public:
uint size_of() const { return sizeof(*this); }
uint32 pack_length() const { return (uint32) bin_size; }
uint pack_length_from_metadata(uint field_metadata);
uint row_pack_length() { return pack_length(); }
uint row_pack_length() const { return pack_length(); }
bool compatible_field_size(uint field_metadata, Relay_log_info *rli,
uint16 mflags, int *order_var);
uint is_equal(Create_field *new_field);
......@@ -1195,7 +1195,7 @@ public:
int cmp(const uchar *,const uchar *);
void sort_string(uchar *buff,uint length);
uint32 pack_length() const { return sizeof(float); }
uint row_pack_length() { return pack_length(); }
uint row_pack_length() const { return pack_length(); }
void sql_type(String &str) const;
private:
int do_save_field_metadata(uchar *first_byte);
......@@ -1235,7 +1235,7 @@ public:
int cmp(const uchar *,const uchar *);
void sort_string(uchar *buff,uint length);
uint32 pack_length() const { return sizeof(double); }
uint row_pack_length() { return pack_length(); }
uint row_pack_length() const { return pack_length(); }
void sql_type(String &str) const;
private:
int do_save_field_metadata(uchar *first_byte);
......@@ -1621,7 +1621,7 @@ public:
}
bool compatible_field_size(uint field_metadata, Relay_log_info *rli,
uint16 mflags, int *order_var);
uint row_pack_length() { return field_length; }
uint row_pack_length() const { return field_length; }
int pack_cmp(const uchar *a,const uchar *b,uint key_length,
my_bool insert_or_update);
int pack_cmp(const uchar *b,uint key_length,my_bool insert_or_update);
......@@ -1671,7 +1671,7 @@ public:
enum_field_types type() const { return MYSQL_TYPE_VARCHAR; }
bool match_collation_to_optimize_range() const { return TRUE; }
enum ha_base_keytype key_type() const;
uint row_pack_length() { return field_length; }
uint row_pack_length() const { return field_length; }
bool zero_pack() const { return 0; }
int reset(void) { bzero(ptr,field_length+length_bytes); return 0; }
uint32 pack_length() const { return (uint32) field_length+length_bytes; }
......@@ -1797,7 +1797,7 @@ public:
*/
uint32 pack_length_no_ptr() const
{ return (uint32) (packlength); }
uint row_pack_length() { return pack_length_no_ptr(); }
uint row_pack_length() const { return pack_length_no_ptr(); }
uint32 sort_length() const;
virtual uint32 max_data_length() const
{
......@@ -1960,7 +1960,7 @@ public:
enum_field_types real_type() const { return MYSQL_TYPE_ENUM; }
uint pack_length_from_metadata(uint field_metadata)
{ return (field_metadata & 0x00ff); }
uint row_pack_length() { return pack_length(); }
uint row_pack_length() const { return pack_length(); }
virtual bool zero_pack() const { return 0; }
bool optimize_range(uint idx, uint part) { return 0; }
bool eq_def(Field *field);
......@@ -2081,7 +2081,7 @@ public:
uint32 pack_length() const { return (uint32) (field_length + 7) / 8; }
uint32 pack_length_in_rec() const { return bytes_in_rec; }
uint pack_length_from_metadata(uint field_metadata);
uint row_pack_length()
uint row_pack_length() const
{ return (bytes_in_rec + ((bit_len > 0) ? 1 : 0)); }
bool compatible_field_size(uint metadata, Relay_log_info *rli,
uint16 mflags, int *order_var);
......
......@@ -447,79 +447,99 @@ static void do_expand_string(Copy_field *copy)
copy->to_length-copy->from_length, ' ');
}
/**
Find how many bytes should be copied between Field_varstring fields
so that only the bytes in use in the 'from' field are copied.
Handles single and multi-byte charsets. Adds warning if not all
bytes in 'from' will fit into 'to'.
@param to Variable length field we're copying to
@param from Variable length field we're copying from
static void do_varstring1(Copy_field *copy)
@return Number of bytes that should be copied from 'from' to 'to'.
*/
static uint get_varstring_copy_length(Field_varstring *to,
const Field_varstring *from)
{
uint length= (uint) *(uchar*) copy->from_ptr;
if (length > copy->to_length- 1)
CHARSET_INFO * const cs= from->charset();
const bool is_multibyte_charset= (cs->mbmaxlen != 1);
const uint to_byte_length= to->row_pack_length();
uint bytes_to_copy;
if (from->length_bytes == 1)
bytes_to_copy= *from->ptr;
else
bytes_to_copy= uint2korr(from->ptr);
if (is_multibyte_charset)
{
int well_formed_error;
const char *from_beg= reinterpret_cast<char*>(from->ptr + from->length_bytes);
const uint to_char_length= (to_byte_length) / cs->mbmaxlen;
const uint from_byte_length= bytes_to_copy;
bytes_to_copy=
cs->cset->well_formed_len(cs, from_beg,
from_beg + from_byte_length,
to_char_length,
&well_formed_error);
if (bytes_to_copy < from_byte_length)
{
if (from->table->in_use->count_cuted_fields)
to->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN,
WARN_DATA_TRUNCATED, 1);
}
}
else
{
length=copy->to_length - 1;
if (copy->from_field->table->in_use->count_cuted_fields)
copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN,
WARN_DATA_TRUNCATED, 1);
if (bytes_to_copy > (to_byte_length))
{
bytes_to_copy= to_byte_length;
if (from->table->in_use->count_cuted_fields)
to->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN,
WARN_DATA_TRUNCATED, 1);
}
}
*(uchar*) copy->to_ptr= (uchar) length;
memcpy(copy->to_ptr+1, copy->from_ptr + 1, length);
return bytes_to_copy;
}
/**
A variable length string field consists of:
(a) 1 or 2 length bytes, depending on the VARCHAR column definition
(b) as many relevant character bytes, as defined in the length byte(s)
(c) unused padding up to the full length of the column
static void do_varstring1_mb(Copy_field *copy)
{
int well_formed_error;
CHARSET_INFO *cs= copy->from_field->charset();
uint from_length= (uint) *(uchar*) copy->from_ptr;
const uchar *from_ptr= copy->from_ptr + 1;
uint to_char_length= (copy->to_length - 1) / cs->mbmaxlen;
uint length= cs->cset->well_formed_len(cs, (char*) from_ptr,
(char*) from_ptr + from_length,
to_char_length, &well_formed_error);
if (length < from_length)
{
if (current_thd->count_cuted_fields)
copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN,
WARN_DATA_TRUNCATED, 1);
}
*copy->to_ptr= (uchar) length;
memcpy(copy->to_ptr + 1, from_ptr, length);
}
This function only copies (a) and (b)
Condition for using this function: to and from must use the same
number of bytes for length, i.e: to->length_bytes==from->length_bytes
static void do_varstring2(Copy_field *copy)
@param to Variable length field we're copying to
@param from Variable length field we're copying from
*/
static void copy_field_varstring(Field_varstring * const to,
const Field_varstring * const from)
{
uint length=uint2korr(copy->from_ptr);
if (length > copy->to_length- HA_KEY_BLOB_LENGTH)
{
length=copy->to_length-HA_KEY_BLOB_LENGTH;
if (copy->from_field->table->in_use->count_cuted_fields)
copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN,
WARN_DATA_TRUNCATED, 1);
}
int2store(copy->to_ptr,length);
memcpy(copy->to_ptr+HA_KEY_BLOB_LENGTH, copy->from_ptr + HA_KEY_BLOB_LENGTH,
length);
}
const uint length_bytes= from->length_bytes;
DBUG_ASSERT(length_bytes == to->length_bytes);
DBUG_ASSERT(length_bytes == 1 || length_bytes == 2);
const uint bytes_to_copy= get_varstring_copy_length(to, from);
if (length_bytes == 1)
*to->ptr= static_cast<uchar>(bytes_to_copy);
else
int2store(to->ptr, bytes_to_copy);
// memcpy should not be used for overlaping memory blocks
DBUG_ASSERT(to->ptr != from->ptr);
memcpy(to->ptr + length_bytes, from->ptr + length_bytes, bytes_to_copy);
}
static void do_varstring2_mb(Copy_field *copy)
static void do_varstring(Copy_field *copy)
{
int well_formed_error;
CHARSET_INFO *cs= copy->from_field->charset();
uint char_length= (copy->to_length - HA_KEY_BLOB_LENGTH) / cs->mbmaxlen;
uint from_length= uint2korr(copy->from_ptr);
const uchar *from_beg= copy->from_ptr + HA_KEY_BLOB_LENGTH;
uint length= cs->cset->well_formed_len(cs, (char*) from_beg,
(char*) from_beg + from_length,
char_length, &well_formed_error);
if (length < from_length)
{
if (current_thd->count_cuted_fields)
copy->to_field->set_warning(MYSQL_ERROR::WARN_LEVEL_WARN,
WARN_DATA_TRUNCATED, 1);
}
int2store(copy->to_ptr, length);
memcpy(copy->to_ptr+HA_KEY_BLOB_LENGTH, from_beg, length);
copy_field_varstring(static_cast<Field_varstring*>(copy->to_field),
static_cast<Field_varstring*>(copy->from_field));
}
/***************************************************************************
** The different functions that fills in a Copy_field class
......@@ -711,11 +731,7 @@ Copy_field::get_copy_func(Field *to,Field *from)
((Field_varstring*) from)->length_bytes)
return do_field_string;
else
return (((Field_varstring*) to)->length_bytes == 1 ?
(from->charset()->mbmaxlen == 1 ? do_varstring1 :
do_varstring1_mb) :
(from->charset()->mbmaxlen == 1 ? do_varstring2 :
do_varstring2_mb));
return do_varstring;
}
else if (to_length < from_length)
return (from->charset()->mbmaxlen == 1 ?
......@@ -771,8 +787,20 @@ Copy_field::get_copy_func(Field *to,Field *from)
int field_conv(Field *to,Field *from)
{
if (to->real_type() == from->real_type() &&
!(to->type() == MYSQL_TYPE_BLOB && to->table->copy_blobs))
!(to->type() == MYSQL_TYPE_BLOB && to->table->copy_blobs) &&
to->charset() == from->charset())
{
if (to->real_type() == MYSQL_TYPE_VARCHAR &&
from->real_type() == MYSQL_TYPE_VARCHAR)
{
Field_varstring *to_vc= static_cast<Field_varstring*>(to);
const Field_varstring *from_vc= static_cast<Field_varstring*>(from);
if (to_vc->length_bytes == from_vc->length_bytes)
{
copy_field_varstring(to_vc, from_vc);
return 0;
}
}
if (to->pack_length() == from->pack_length() &&
!(to->flags & UNSIGNED_FLAG && !(from->flags & UNSIGNED_FLAG)) &&
to->real_type() != MYSQL_TYPE_ENUM &&
......@@ -781,15 +809,12 @@ int field_conv(Field *to,Field *from)
(to->real_type() != MYSQL_TYPE_NEWDECIMAL ||
(to->field_length == from->field_length &&
(((Field_num*)to)->dec == ((Field_num*)from)->dec))) &&
from->charset() == to->charset() &&
to->table->s->db_low_byte_first == from->table->s->db_low_byte_first &&
(!(to->table->in_use->variables.sql_mode &
(MODE_NO_ZERO_IN_DATE | MODE_NO_ZERO_DATE | MODE_INVALID_DATES)) ||
(to->type() != MYSQL_TYPE_DATE &&
to->type() != MYSQL_TYPE_DATETIME)) &&
(from->real_type() != MYSQL_TYPE_VARCHAR ||
((Field_varstring*)from)->length_bytes ==
((Field_varstring*)to)->length_bytes))
(from->real_type() != MYSQL_TYPE_VARCHAR))
{ // Identical fields
// to->ptr==from->ptr may happen if one does 'UPDATE ... SET x=x'
memmove(to->ptr, from->ptr, to->pack_length());
......
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