Skip to content

Commit df389d0

Browse files
Alexey KopytovAlexey Kopytov
authored andcommitted
Bug#55077: Assertion failed: width > 0 && to != ((void *)0),
file .\dtoa.c The assertion failure was correct because the 'width' argument of my_gcvt() has the signed integer type, whereas the unsigned value UINT_MAX32 was being passed by the caller (Field_double::val_str()) leading to a negative width in my_gcvt(). The following chain of problems was found by further analysis: 1. The display width for a floating point number is calculated in Field_double::val_str() as either field_length or the maximum possible length of string representation of a floating point number, whichever is greater. Since in the bug's test case field_length is UINT_MAX32, we get the same value as the display width. This does not make any sense because for numeric values field_length only matters for ZEROFILL columns, otherwise it does not make sense to allocate that much memory just to print a number. Field_float::val_str() has a similar problem. 2. Even if the above wasn't the case, we would still get a crash on a slightly different test case when trying to allocate UINT_MAX32 bytes with String::alloc() because the latter does not handle such large input values correctly due to alignment overflows. 3. Even when String::alloc() is fixed to return an error when an alignment overflow occurs, there is still a problem because almost no callers check its return value, and Field_double::val_str() is not an exception (same for Field_float::val_str()). 4. Even if all of the above wasn't the case, creating a Field_double object with UINT_MAX32 as its field_length does not make much sense either, since the .frm code limits it to MAX_FIELD_CHARLENGTH (255) bytes. Such a beast can only be created by create_tmp_field_from_item() from an Item with REAL_RESULT as its result_type() and UINT_MAX32 as its max_length. 5. For the bug's test case, the above condition (REAL_RESULT Item with max_length = UINT_MAX32) was a result of Item_func_if::fix_length_and_dec() "shortcutting" aggregation of argument types when one of the arguments was a constant NULL. In this case, the attributes of the aggregated type were simply copied from the other, non-NULL argument, but max_length was still calculated as per the general, non-shortcut case, by choosing the greatest of argument's max_length, which is obviously not correct. The patch addresses all of the above problems, even though fixing the assertion failure for the particular test case would require only a subset of the above problems to be solved.
1 parent 593c6db commit df389d0

6 files changed

Lines changed: 69 additions & 21 deletions

File tree

client/sql_string.cc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@
3131
** String functions
3232
*****************************************************************************/
3333

34-
bool String::real_alloc(uint32 arg_length)
34+
bool String::real_alloc(uint32 length)
3535
{
36-
arg_length=ALIGN_SIZE(arg_length+1);
36+
uint32 arg_length= ALIGN_SIZE(length + 1);
37+
DBUG_ASSERT(arg_length > length);
38+
if (arg_length <= length)
39+
return TRUE; /* Overflow */
3740
str_length=0;
3841
if (Alloced_length < arg_length)
3942
{
@@ -56,6 +59,9 @@ bool String::real_alloc(uint32 arg_length)
5659
bool String::realloc(uint32 alloc_length)
5760
{
5861
uint32 len=ALIGN_SIZE(alloc_length+1);
62+
DBUG_ASSERT(len > alloc_length);
63+
if (len <= alloc_length)
64+
return TRUE; /* Overflow */
5965
if (Alloced_length < len)
6066
{
6167
char *new_ptr;

mysql-test/r/func_if.result

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -186,3 +186,13 @@ MAX(IFNULL(CAST(c AS UNSIGNED), 0))
186186
12345678901234567890
187187
DROP TABLE t1;
188188
End of 5.0 tests
189+
#
190+
# Bug#55077: Assertion failed: width > 0 && to != ((void *)0), file .\dtoa.c
191+
#
192+
CREATE TABLE t1 (a LONGBLOB, b DOUBLE);
193+
INSERT INTO t1 VALUES (NULL, 0), (NULL, 1);
194+
SELECT IF(b, (SELECT a FROM t1 LIMIT 1), b) c FROM t1 GROUP BY c;
195+
c
196+
NULL
197+
0
198+
DROP TABLE t1;

mysql-test/t/func_if.test

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -165,3 +165,15 @@ DROP TABLE t1;
165165

166166

167167
--echo End of 5.0 tests
168+
169+
170+
--echo #
171+
--echo # Bug#55077: Assertion failed: width > 0 && to != ((void *)0), file .\dtoa.c
172+
--echo #
173+
174+
CREATE TABLE t1 (a LONGBLOB, b DOUBLE);
175+
INSERT INTO t1 VALUES (NULL, 0), (NULL, 1);
176+
177+
SELECT IF(b, (SELECT a FROM t1 LIMIT 1), b) c FROM t1 GROUP BY c;
178+
179+
DROP TABLE t1;

sql/field.cc

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4189,6 +4189,7 @@ String *Field_float::val_str(String *val_buffer,
41894189
String *val_ptr __attribute__((unused)))
41904190
{
41914191
ASSERT_COLUMN_MARKED_FOR_READ;
4192+
DBUG_ASSERT(field_length <= MAX_FIELD_CHARLENGTH);
41924193
float nr;
41934194
#ifdef WORDS_BIGENDIAN
41944195
if (table->s->db_low_byte_first)
@@ -4199,8 +4200,13 @@ String *Field_float::val_str(String *val_buffer,
41994200
#endif
42004201
memcpy(&nr, ptr, sizeof(nr));
42014202

4202-
uint to_length=max(field_length,70);
4203-
val_buffer->alloc(to_length);
4203+
uint to_length= 70;
4204+
if (val_buffer->alloc(to_length))
4205+
{
4206+
my_error(ER_OUT_OF_RESOURCES, MYF(0));
4207+
return val_buffer;
4208+
}
4209+
42044210
char *to=(char*) val_buffer->ptr();
42054211
size_t len;
42064212

@@ -4209,7 +4215,7 @@ String *Field_float::val_str(String *val_buffer,
42094215
else
42104216
{
42114217
/*
4212-
We are safe here because the buffer length is >= 70, and
4218+
We are safe here because the buffer length is 70, and
42134219
fabs(float) < 10^39, dec < NOT_FIXED_DEC. So the resulting string
42144220
will be not longer than 69 chars + terminating '\0'.
42154221
*/
@@ -4506,6 +4512,7 @@ String *Field_double::val_str(String *val_buffer,
45064512
String *val_ptr __attribute__((unused)))
45074513
{
45084514
ASSERT_COLUMN_MARKED_FOR_READ;
4515+
DBUG_ASSERT(field_length <= MAX_FIELD_CHARLENGTH);
45094516
double nr;
45104517
#ifdef WORDS_BIGENDIAN
45114518
if (table->s->db_low_byte_first)
@@ -4515,9 +4522,13 @@ String *Field_double::val_str(String *val_buffer,
45154522
else
45164523
#endif
45174524
doubleget(nr,ptr);
4525+
uint to_length= DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE;
4526+
if (val_buffer->alloc(to_length))
4527+
{
4528+
my_error(ER_OUT_OF_RESOURCES, MYF(0));
4529+
return val_buffer;
4530+
}
45184531

4519-
uint to_length=max(field_length, DOUBLE_TO_STRING_CONVERSION_BUFFER_SIZE);
4520-
val_buffer->alloc(to_length);
45214532
char *to=(char*) val_buffer->ptr();
45224533
size_t len;
45234534

sql/item_cmpfunc.cc

Lines changed: 15 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -2560,27 +2560,30 @@ Item_func_if::fix_length_and_dec()
25602560
cached_result_type= arg2_type;
25612561
collation.set(args[2]->collation.collation);
25622562
cached_field_type= args[2]->field_type();
2563+
max_length= args[2]->max_length;
2564+
return;
25632565
}
2564-
else if (null2)
2566+
2567+
if (null2)
25652568
{
25662569
cached_result_type= arg1_type;
25672570
collation.set(args[1]->collation.collation);
25682571
cached_field_type= args[1]->field_type();
2572+
max_length= args[1]->max_length;
2573+
return;
2574+
}
2575+
2576+
agg_result_type(&cached_result_type, args + 1, 2);
2577+
if (cached_result_type == STRING_RESULT)
2578+
{
2579+
if (agg_arg_charsets_for_string_result(collation, args + 1, 2))
2580+
return;
25692581
}
25702582
else
25712583
{
2572-
agg_result_type(&cached_result_type, args+1, 2);
2573-
if (cached_result_type == STRING_RESULT)
2574-
{
2575-
if (agg_arg_charsets_for_string_result(collation, args + 1, 2))
2576-
return;
2577-
}
2578-
else
2579-
{
2580-
collation.set_numeric(); // Number
2581-
}
2582-
cached_field_type= agg_field_type(args + 1, 2);
2584+
collation.set_numeric(); // Number
25832585
}
2586+
cached_field_type= agg_field_type(args + 1, 2);
25842587

25852588
uint32 char_length;
25862589
if ((cached_result_type == DECIMAL_RESULT )

sql/sql_string.cc

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,9 +31,12 @@
3131
** String functions
3232
*****************************************************************************/
3333

34-
bool String::real_alloc(uint32 arg_length)
34+
bool String::real_alloc(uint32 length)
3535
{
36-
arg_length=ALIGN_SIZE(arg_length+1);
36+
uint32 arg_length= ALIGN_SIZE(length + 1);
37+
DBUG_ASSERT(arg_length > length);
38+
if (arg_length <= length)
39+
return TRUE; /* Overflow */
3740
str_length=0;
3841
if (Alloced_length < arg_length)
3942
{
@@ -56,6 +59,9 @@ bool String::real_alloc(uint32 arg_length)
5659
bool String::realloc(uint32 alloc_length)
5760
{
5861
uint32 len=ALIGN_SIZE(alloc_length+1);
62+
DBUG_ASSERT(len > alloc_length);
63+
if (len <= alloc_length)
64+
return TRUE; /* Overflow */
5965
if (Alloced_length < len)
6066
{
6167
char *new_ptr;

0 commit comments

Comments
 (0)