Use string length of input to strdup to determine the usable size of the resulting object. Avoid doing the same for strndup since there's a chance that the input may be too large, resulting in an unnecessary overhead or worse, the input may not be NULL terminated, resulting in a crash where there would otherwise have been none.
gcc/ChangeLog: * tree-object-size.cc (get_whole_object): New function. (addr_object_size): Use it. (strdup_object_size): New function. (call_object_size): Use it. (pass_data_object_sizes, pass_data_early_object_sizes): Set todo_flags_finish to TODO_update_ssa_no_phi. gcc/testsuite/ChangeLog: * gcc.dg/builtin-dynamic-object-size-0.c (test_strdup, test_strndup, test_strdup_min, test_strndup_min): New tests. (main): Call them. * gcc.dg/builtin-dynamic-object-size-1.c: Silence overread warnings. * gcc.dg/builtin-dynamic-object-size-2.c: Likewise. * gcc.dg/builtin-dynamic-object-size-3.c: Likewise. * gcc.dg/builtin-dynamic-object-size-4.c: Likewise. * gcc.dg/builtin-object-size-1.c: Silence overread warnings. Declare free, strdup and strndup. (test11): New test. (main): Call it. * gcc.dg/builtin-object-size-2.c: Silence overread warnings. Declare free, strdup and strndup. (test9): New test. (main): Call it. * gcc.dg/builtin-object-size-3.c: Silence overread warnings. Declare free, strdup and strndup. (test11): New test. (main): Call it. * gcc.dg/builtin-object-size-4.c: Silence overread warnings. Declare free, strdup and strndup. (test9): New test. (main): Call it. --- .../gcc.dg/builtin-dynamic-object-size-0.c | 43 +++++++++++ .../gcc.dg/builtin-dynamic-object-size-1.c | 2 +- .../gcc.dg/builtin-dynamic-object-size-2.c | 2 +- .../gcc.dg/builtin-dynamic-object-size-3.c | 2 +- .../gcc.dg/builtin-dynamic-object-size-4.c | 2 +- gcc/testsuite/gcc.dg/builtin-object-size-1.c | 64 +++++++++++++++- gcc/testsuite/gcc.dg/builtin-object-size-2.c | 63 ++++++++++++++- gcc/testsuite/gcc.dg/builtin-object-size-3.c | 63 ++++++++++++++- gcc/testsuite/gcc.dg/builtin-object-size-4.c | 63 ++++++++++++++- gcc/tree-object-size.cc | 76 +++++++++++++++++-- 10 files changed, 366 insertions(+), 14 deletions(-) diff --git a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c index 01a280b2d7b..7f023708b15 100644 --- a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c +++ b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-0.c @@ -479,6 +479,40 @@ test_loop (int *obj, size_t sz, size_t start, size_t end, int incr) return __builtin_dynamic_object_size (ptr, 0); } +/* strdup/strndup. */ + +size_t +__attribute__ ((noinline)) +test_strdup (const char *in) +{ + char *res = __builtin_strdup (in); + return __builtin_dynamic_object_size (res, 0); +} + +size_t +__attribute__ ((noinline)) +test_strndup (const char *in, size_t bound) +{ + char *res = __builtin_strndup (in, bound); + return __builtin_dynamic_object_size (res, 0); +} + +size_t +__attribute__ ((noinline)) +test_strdup_min (const char *in) +{ + char *res = __builtin_strdup (in); + return __builtin_dynamic_object_size (res, 2); +} + +size_t +__attribute__ ((noinline)) +test_strndup_min (const char *in, size_t bound) +{ + char *res = __builtin_strndup (in, bound); + return __builtin_dynamic_object_size (res, 2); +} + /* Other tests. */ struct TV4 @@ -651,6 +685,15 @@ main (int argc, char **argv) int *t = test_pr105736 (&val3); if (__builtin_dynamic_object_size (t, 0) != -1) FAIL (); + const char *str = "hello world"; + if (test_strdup (str) != __builtin_strlen (str) + 1) + FAIL (); + if (test_strndup (str, 4) != 5) + FAIL (); + if (test_strdup_min (str) != __builtin_strlen (str) + 1) + FAIL (); + if (test_strndup_min (str, 4) != 0) + FAIL (); if (nfails > 0) __builtin_abort (); diff --git a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-1.c b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-1.c index 7cc8b1c9488..8f17c8edcaf 100644 --- a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-1.c +++ b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-1.c @@ -1,5 +1,5 @@ /* { dg-do run } */ -/* { dg-options "-O2" } */ +/* { dg-options "-O2 -Wno-stringop-overread" } */ /* { dg-require-effective-target alloca } */ #define __builtin_object_size __builtin_dynamic_object_size diff --git a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-2.c b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-2.c index 267dbf48ca7..3677782ff1c 100644 --- a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-2.c +++ b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-2.c @@ -1,5 +1,5 @@ /* { dg-do run } */ -/* { dg-options "-O2" } */ +/* { dg-options "-O2 -Wno-stringop-overread" } */ /* { dg-require-effective-target alloca } */ #define __builtin_object_size __builtin_dynamic_object_size diff --git a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-3.c b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-3.c index fb9dc56da7e..5b6987b7773 100644 --- a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-3.c +++ b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-3.c @@ -1,5 +1,5 @@ /* { dg-do run } */ -/* { dg-options "-O2" } */ +/* { dg-options "-O2 -Wno-stringop-overread" } */ /* { dg-require-effective-target alloca } */ #define __builtin_object_size __builtin_dynamic_object_size diff --git a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-4.c b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-4.c index 870548b4206..9d796224e96 100644 --- a/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-4.c +++ b/gcc/testsuite/gcc.dg/builtin-dynamic-object-size-4.c @@ -1,5 +1,5 @@ /* { dg-do run } */ -/* { dg-options "-O2" } */ +/* { dg-options "-O2 -Wno-stringop-overread" } */ /* { dg-require-effective-target alloca } */ #define __builtin_object_size __builtin_dynamic_object_size diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-1.c b/gcc/testsuite/gcc.dg/builtin-object-size-1.c index b772e2da9b9..4fbd372d97a 100644 --- a/gcc/testsuite/gcc.dg/builtin-object-size-1.c +++ b/gcc/testsuite/gcc.dg/builtin-object-size-1.c @@ -1,5 +1,5 @@ /* { dg-do run } */ -/* { dg-options "-O2" } */ +/* { dg-options "-O2 -Wno-stringop-overread" } */ /* { dg-require-effective-target alloca } */ typedef __SIZE_TYPE__ size_t; @@ -7,10 +7,13 @@ extern void abort (void); extern void exit (int); extern void *malloc (size_t); extern void *calloc (size_t, size_t); +extern void free (void *); extern void *alloca (size_t); extern void *memcpy (void *, const void *, size_t); extern void *memset (void *, int, size_t); extern char *strcpy (char *, const char *); +extern char *strdup (const char *); +extern char *strndup (const char *, size_t); struct A { @@ -629,6 +632,64 @@ test10 (void) } } +/* Tests for strdup/strndup. */ +size_t +__attribute__ ((noinline)) +test11 (void) +{ + int i = 0; + const char *ptr = "abcdefghijklmnopqrstuvwxyz"; + char *res = strndup (ptr, 21); + if (__builtin_object_size (res, 0) != 22) + abort (); + + free (res); + + res = strndup (ptr, 32); + if (__builtin_object_size (res, 0) != 27) + abort (); + + free (res); + + res = strdup (ptr); + if (__builtin_object_size (res, 0) != 27) + abort (); + + free (res); + + char *ptr2 = malloc (64); + strcpy (ptr2, ptr); + + res = strndup (ptr2, 21); + if (__builtin_object_size (res, 0) != 22) + abort (); + + free (res); + + res = strndup (ptr2, 32); + if (__builtin_object_size (res, 0) != 33) + abort (); + + free (res); + + res = strndup (ptr2, 128); + if (__builtin_object_size (res, 0) != 64) + abort (); + + free (res); + + res = strdup (ptr2); +#ifdef __builtin_object_size + if (__builtin_object_size (res, 0) != 27) +#else + if (__builtin_object_size (res, 0) != 64) +#endif + abort (); + + free (res); + free (ptr2); +} + int main (void) { @@ -644,5 +705,6 @@ main (void) test8 (); test9 (1); test10 (); + test11 (); exit (0); } diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-2.c b/gcc/testsuite/gcc.dg/builtin-object-size-2.c index 2729538da17..beb271c5afc 100644 --- a/gcc/testsuite/gcc.dg/builtin-object-size-2.c +++ b/gcc/testsuite/gcc.dg/builtin-object-size-2.c @@ -1,5 +1,5 @@ /* { dg-do run } */ -/* { dg-options "-O2" } */ +/* { dg-options "-O2 -Wno-stringop-overread" } */ /* { dg-require-effective-target alloca } */ typedef __SIZE_TYPE__ size_t; @@ -7,10 +7,13 @@ extern void abort (void); extern void exit (int); extern void *malloc (size_t); extern void *calloc (size_t, size_t); +extern void free (void *); extern void *alloca (size_t); extern void *memcpy (void *, const void *, size_t); extern void *memset (void *, int, size_t); extern char *strcpy (char *, const char *); +extern char *strdup (const char *); +extern char *strndup (const char *, size_t); struct A { @@ -544,6 +547,63 @@ test8 (unsigned cond) #endif } +/* Tests for strdup/strndup. */ +size_t +__attribute__ ((noinline)) +test9 (void) +{ + const char *ptr = "abcdefghijklmnopqrstuvwxyz"; + char *res = strndup (ptr, 21); + if (__builtin_object_size (res, 1) != 22) + abort (); + + free (res); + + res = strndup (ptr, 32); + if (__builtin_object_size (res, 1) != 27) + abort (); + + free (res); + + res = strdup (ptr); + if (__builtin_object_size (res, 1) != 27) + abort (); + + free (res); + + char *ptr2 = malloc (64); + strcpy (ptr2, ptr); + + res = strndup (ptr2, 21); + if (__builtin_object_size (res, 1) != 22) + abort (); + + free (res); + + res = strndup (ptr2, 32); + if (__builtin_object_size (res, 1) != 33) + abort (); + + free (res); + + res = strndup (ptr2, 128); + if (__builtin_object_size (res, 1) != 64) + abort (); + + free (res); + + res = strdup (ptr2); +#ifdef __builtin_object_size + if (__builtin_object_size (res, 1) != 27) +#else + if (__builtin_object_size (res, 1) != 64) +#endif + abort (); + + free (res); + free (ptr2); +} + int main (void) { @@ -557,5 +617,6 @@ main (void) test6 (); test7 (); test8 (1); + test9 (); exit (0); } diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-3.c b/gcc/testsuite/gcc.dg/builtin-object-size-3.c index 44a99189776..5c878a14647 100644 --- a/gcc/testsuite/gcc.dg/builtin-object-size-3.c +++ b/gcc/testsuite/gcc.dg/builtin-object-size-3.c @@ -1,5 +1,5 @@ /* { dg-do run } */ -/* { dg-options "-O2" } */ +/* { dg-options "-O2 -Wno-stringop-overread" } */ /* { dg-require-effective-target alloca } */ typedef __SIZE_TYPE__ size_t; @@ -7,10 +7,13 @@ extern void abort (void); extern void exit (int); extern void *malloc (size_t); extern void *calloc (size_t, size_t); +extern void free (void *); extern void *alloca (size_t); extern void *memcpy (void *, const void *, size_t); extern void *memset (void *, int, size_t); extern char *strcpy (char *, const char *); +extern char *strdup (const char *); +extern char *strndup (const char *, size_t); struct A { @@ -636,6 +639,63 @@ test10 (void) } } +/* Tests for strdup/strndup. */ +size_t +__attribute__ ((noinline)) +test11 (void) +{ + const char *ptr = "abcdefghijklmnopqrstuvwxyz"; + char *res = strndup (ptr, 21); + if (__builtin_object_size (res, 2) != 22) + abort (); + + free (res); + + res = strndup (ptr, 32); + if (__builtin_object_size (res, 2) != 27) + abort (); + + free (res); + + res = strdup (ptr); + if (__builtin_object_size (res, 2) != 27) + abort (); + + free (res); + + char *ptr2 = malloc (64); + strcpy (ptr2, ptr); + + res = strndup (ptr2, 21); + if (__builtin_object_size (res, 2) != 0) + abort (); + + free (res); + + res = strndup (ptr2, 32); + if (__builtin_object_size (res, 2) != 0) + abort (); + + free (res); + + res = strndup (ptr2, 128); + if (__builtin_object_size (res, 2) != 0) + abort (); + + free (res); + + res = strdup (ptr2); +#ifdef __builtin_object_size + if (__builtin_object_size (res, 2) != 27) +#else + if (__builtin_object_size (res, 2) != 0) +#endif + abort (); + + free (res); + free (ptr2); +} + int main (void) { @@ -651,5 +711,6 @@ main (void) test8 (); test9 (1); test10 (); + test11 (); exit (0); } diff --git a/gcc/testsuite/gcc.dg/builtin-object-size-4.c b/gcc/testsuite/gcc.dg/builtin-object-size-4.c index b9fddfed036..0b1cb1e528a 100644 --- a/gcc/testsuite/gcc.dg/builtin-object-size-4.c +++ b/gcc/testsuite/gcc.dg/builtin-object-size-4.c @@ -1,5 +1,5 @@ /* { dg-do run } */ -/* { dg-options "-O2" } */ +/* { dg-options "-O2 -Wno-stringop-overread" } */ /* { dg-require-effective-target alloca } */ typedef __SIZE_TYPE__ size_t; @@ -7,10 +7,13 @@ extern void abort (void); extern void exit (int); extern void *malloc (size_t); extern void *calloc (size_t, size_t); +extern void free (void *); extern void *alloca (size_t); extern void *memcpy (void *, const void *, size_t); extern void *memset (void *, int, size_t); extern char *strcpy (char *, const char *); +extern char *strdup (const char *); +extern char *strndup (const char *, size_t); struct A { @@ -517,6 +520,63 @@ test8 (unsigned cond) #endif } +/* Tests for strdup/strndup. */ +size_t +__attribute__ ((noinline)) +test9 (void) +{ + const char *ptr = "abcdefghijklmnopqrstuvwxyz"; + char *res = strndup (ptr, 21); + if (__builtin_object_size (res, 3) != 22) + abort (); + + free (res); + + res = strndup (ptr, 32); + if (__builtin_object_size (res, 3) != 27) + abort (); + + free (res); + + res = strdup (ptr); + if (__builtin_object_size (res, 3) != 27) + abort (); + + free (res); + + char *ptr2 = malloc (64); + strcpy (ptr2, ptr); + + res = strndup (ptr2, 21); + if (__builtin_object_size (res, 3) != 0) + abort (); + + free (res); + + res = strndup (ptr2, 32); + if (__builtin_object_size (res, 3) != 0) + abort (); + + free (res); + + res = strndup (ptr2, 128); + if (__builtin_object_size (res, 3) != 0) + abort (); + + free (res); + + res = strdup (ptr2); +#ifdef __builtin_object_size + if (__builtin_object_size (res, 3) != 27) +#else + if (__builtin_object_size (res, 3) != 0) +#endif + abort (); + + free (res); + free (ptr2); +} + int main (void) { @@ -530,5 +590,6 @@ main (void) test6 (); test7 (); test8 (1); + test9 (); exit (0); } diff --git a/gcc/tree-object-size.cc b/gcc/tree-object-size.cc index 4eb454a4a33..c075b71db56 100644 --- a/gcc/tree-object-size.cc +++ b/gcc/tree-object-size.cc @@ -495,6 +495,18 @@ decl_init_size (tree decl, bool min) return size; } +/* Get the outermost object that PTR may point into. */ + +static tree +get_whole_object (const_tree ptr) +{ + tree pt_var = TREE_OPERAND (ptr, 0); + while (handled_component_p (pt_var)) + pt_var = TREE_OPERAND (pt_var, 0); + + return pt_var; +} + /* Compute __builtin_object_size for PTR, which is a ADDR_EXPR. OBJECT_SIZE_TYPE is the second argument from __builtin_object_size. If unknown, return size_unknown (object_size_type). */ @@ -514,9 +526,7 @@ addr_object_size (struct object_size_info *osi, const_tree ptr, if (pwholesize) *pwholesize = size_unknown (object_size_type); - pt_var = TREE_OPERAND (ptr, 0); - while (handled_component_p (pt_var)) - pt_var = TREE_OPERAND (pt_var, 0); + pt_var = get_whole_object (ptr); if (!pt_var) return false; @@ -789,6 +799,53 @@ alloc_object_size (const gcall *call, int object_size_type) return bytes ? bytes : size_unknown (object_size_type); } +/* Compute __builtin_object_size for CALL, which is a call to either + BUILT_IN_STRDUP or BUILT_IN_STRNDUP; IS_STRNDUP indicates which it is. + OBJECT_SIZE_TYPE is the second argument from __builtin_object_size. + If unknown, return size_unknown (object_size_type). */ + +static tree +strdup_object_size (const gcall *call, int object_size_type, bool is_strndup) +{ + tree src = gimple_call_arg (call, 0); + tree sz = size_unknown (object_size_type); + tree n = is_strndup ? gimple_call_arg (call, 1) : NULL_TREE; + + /* For strdup, simply emit strlen (SRC) + 1 and let the optimizer fold it the + way it likes. */ + if (!is_strndup) + { + tree strlen_fn = builtin_decl_implicit (BUILT_IN_STRLEN); + if (strlen_fn) + sz = fold_build2 (PLUS_EXPR, sizetype, size_one_node, + build_call_expr (strlen_fn, 1, src)); + } + + /* In all other cases, return the size of SRC since the object size cannot + exceed that. We cannot do this for OST_MINIMUM unless SRC points into a + string constant since otherwise the object size could go all the way down + to zero. */ + if (!size_valid_p (sz, object_size_type) + || size_unknown_p (sz, object_size_type)) + { + tree wholesrc = NULL_TREE; + if (TREE_CODE (src) == ADDR_EXPR) + wholesrc = get_whole_object (src); + + if (!(object_size_type & OST_MINIMUM) + || (wholesrc && TREE_CODE (wholesrc) == STRING_CST)) + compute_builtin_object_size (src, object_size_type, &sz); + } + + if (!n) + return sz; + + /* Factor in the N. Note that with OST_MINIMUM, even if N is known we return + 0 since the size could be less than N. */ + return fold_build2 (MIN_EXPR, sizetype, + fold_build2 (PLUS_EXPR, sizetype, size_one_node, n), + sz); +} /* If object size is propagated from one of function's arguments directly to its return value, return that argument for GIMPLE_CALL statement CALL. @@ -1235,12 +1292,19 @@ call_object_size (struct object_size_info *osi, tree ptr, gcall *call) { int object_size_type = osi->object_size_type; unsigned int varno = SSA_NAME_VERSION (ptr); + tree bytes = NULL_TREE; gcc_assert (is_gimple_call (call)); gcc_assert (!object_sizes_unknown_p (object_size_type, varno)); gcc_assert (osi->pass == 0); - tree bytes = alloc_object_size (call, object_size_type); + + bool is_strdup = gimple_call_builtin_p (call, BUILT_IN_STRDUP); + bool is_strndup = gimple_call_builtin_p (call, BUILT_IN_STRNDUP); + if (is_strdup || is_strndup) + bytes = strdup_object_size (call, object_size_type, is_strndup); + else + bytes = alloc_object_size (call, object_size_type); if (!size_valid_p (bytes, object_size_type)) bytes = size_unknown (object_size_type); @@ -2113,7 +2177,7 @@ const pass_data pass_data_object_sizes = PROP_objsz, /* properties_provided */ 0, /* properties_destroyed */ 0, /* todo_flags_start */ - 0, /* todo_flags_finish */ + TODO_update_ssa_no_phi, /* todo_flags_finish */ }; class pass_object_sizes : public gimple_opt_pass @@ -2153,7 +2217,7 @@ const pass_data pass_data_early_object_sizes = 0, /* properties_provided */ 0, /* properties_destroyed */ 0, /* todo_flags_start */ - 0, /* todo_flags_finish */ + TODO_update_ssa_no_phi, /* todo_flags_finish */ }; class pass_early_object_sizes : public gimple_opt_pass -- 2.37.1