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 | |
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.
-rw-r--r-- | sql/field.h | 20 | ||||
-rw-r--r-- | sql/field_conv.cc | 163 |
2 files changed, 104 insertions, 79 deletions
diff --git a/sql/field.h b/sql/field.h index 73922460037..cc4b79a9e4a 100644 --- a/sql/field.h +++ b/sql/field.h @@ -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); 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()); |