diff options
author | Sreeharsha Ramanavarapu <sreeharsha.ramanavarapu@oracle.com> | 2015-11-03 07:59:57 +0530 |
---|---|---|
committer | Sreeharsha Ramanavarapu <sreeharsha.ramanavarapu@oracle.com> | 2015-11-03 07:59:57 +0530 |
commit | 75bfdea40f9c2954d8eadccbadc795231e310ab7 (patch) | |
tree | 851583edfc456d198392615c09ef09050d964de7 /sql/field_conv.cc | |
parent | 5e9a50efc37c233f1e2a3616f8bcb36315aba4c2 (diff) | |
download | mariadb-git-75bfdea40f9c2954d8eadccbadc795231e310ab7.tar.gz |
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.
Diffstat (limited to 'sql/field_conv.cc')
-rw-r--r-- | sql/field_conv.cc | 163 |
1 files changed, 94 insertions, 69 deletions
diff --git a/sql/field_conv.cc b/sql/field_conv.cc index 14c4fd257fe..7eb49b9dd92 100644 --- a/sql/field_conv.cc +++ b/sql/field_conv.cc @@ -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()); |