diff options
author | Martin Sebor <msebor@redhat.com> | 2020-12-14 13:30:00 -0700 |
---|---|---|
committer | Martin Sebor <msebor@redhat.com> | 2020-12-14 13:30:00 -0700 |
commit | fe7f75cf16783589eedbab597e6d0b8d35d7e470 (patch) | |
tree | 7fec98daa8cd2d99d08d302f753fb7416f3e901c /gcc/builtins.c | |
parent | e63ae8c083a3218400206b80d4eedd9950fc5d3f (diff) | |
download | gcc-fe7f75cf16783589eedbab597e6d0b8d35d7e470.zip gcc-fe7f75cf16783589eedbab597e6d0b8d35d7e470.tar.gz gcc-fe7f75cf16783589eedbab597e6d0b8d35d7e470.tar.bz2 |
Correct/improve maybe_emit_free_warning (PR middle-end/98166, PR c++/57111, PR middle-end/98160).
Resolves:
PR middle-end/98166 - bogus -Wmismatched-dealloc on user-defined allocator and inlining
PR c++/57111 - 57111 - Generalize -Wfree-nonheap-object to delete
PR middle-end/98160 - ICE in default_tree_printer at gcc/tree-diagnostic.c:270
gcc/ChangeLog:
PR middle-end/98166
PR c++/57111
PR middle-end/98160
* builtins.c (check_access): Call tree_inlined_location
fndecl_alloc_p): Handle BUILT_IN_ALIGNED_ALLOC and
BUILT_IN_GOMP_ALLOC.
call_dealloc_p): Remove unused function.
(new_delete_mismatch_p): Call valid_new_delete_pair_p and rework.
(matching_alloc_calls_p): Handle built-in deallocation functions.
(warn_dealloc_offset): Corrct the handling of user-defined operators
delete.
(maybe_emit_free_warning): Avoid assuming expression is a decl.
Simplify.
* doc/extend.texi (attribute malloc): Update.
* tree-ssa-dce.c (valid_new_delete_pair_p): Factor code out into
valid_new_delete_pair_p in tree.c.
* tree.c (tree_inlined_location): Define new function.
(valid_new_delete_pair_p): Define.
* tree.h (tree_inlined_location): Declare.
(valid_new_delete_pair_p): Declare.
gcc/c-family/ChangeLog:
PR middle-end/98166
PR c++/57111
PR middle-end/98160
* c-attribs.c (maybe_add_noinline): New function.
(handle_malloc_attribute): Call it. Use ATTR_FLAG_INTERNAL.
Implicitly add attribute noinline to functions not declared inline
and warn on those.
libstdc++-v3/ChangeLog:
* testsuite/ext/vstring/requirements/exception/basic.cc: Suppress
a false positive warning.
* testsuite/ext/vstring/requirements/exception/propagation_consistent.cc:
Same.
gcc/testsuite/ChangeLog:
PR middle-end/98166
PR c++/57111
PR middle-end/98160
* g++.dg/warn/Wmismatched-dealloc-2.C: Adjust test of expected warning.
* g++.dg/warn/Wmismatched-new-delete.C: Same.
* gcc.dg/Wmismatched-dealloc.c: Same.
* c-c++-common/Wfree-nonheap-object-2.c: New test.
* c-c++-common/Wfree-nonheap-object-3.c: New test.
* c-c++-common/Wfree-nonheap-object.c: New test.
* c-c++-common/Wmismatched-dealloc.c: New test.
* g++.dg/warn/Wfree-nonheap-object-3.C: New test.
* g++.dg/warn/Wfree-nonheap-object-4.C: New test.
* g++.dg/warn/Wmismatched-dealloc-2.C: New test.
* g++.dg/warn/Wmismatched-new-delete-2.C: New test.
* g++.dg/warn/Wmismatched-new-delete.C: New test.
* gcc.dg/Wmismatched-dealloc-2.c: New test.
* gcc.dg/Wmismatched-dealloc-3.c: New test.
* gcc.dg/Wmismatched-dealloc.c: New test.
Diffstat (limited to 'gcc/builtins.c')
-rw-r--r-- | gcc/builtins.c | 388 |
1 files changed, 315 insertions, 73 deletions
diff --git a/gcc/builtins.c b/gcc/builtins.c index faa5030..28e4444 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -4738,9 +4738,7 @@ check_access (tree exp, tree dstwrite, && TREE_CODE (range[0]) == INTEGER_CST && tree_int_cst_lt (maxobjsize, range[0])) { - location_t loc = tree_nonartificial_location (exp); - loc = expansion_point_location_if_in_system_header (loc); - + location_t loc = tree_inlined_location (exp); maybe_warn_for_bound (OPT_Wstringop_overflow_, loc, exp, func, range, NULL_TREE, pad); return false; @@ -4766,9 +4764,7 @@ check_access (tree exp, tree dstwrite, || (pad && pad->dst.ref && TREE_NO_WARNING (pad->dst.ref))) return false; - location_t loc = tree_nonartificial_location (exp); - loc = expansion_point_location_if_in_system_header (loc); - + location_t loc = tree_inlined_location (exp); bool warned = false; if (dstwrite == slen && at_least_one) { @@ -4821,9 +4817,7 @@ check_access (tree exp, tree dstwrite, PAD is nonnull and BNDRNG is valid. */ get_size_range (maxread, range, pad ? pad->src.bndrng : NULL); - location_t loc = tree_nonartificial_location (exp); - loc = expansion_point_location_if_in_system_header (loc); - + location_t loc = tree_inlined_location (exp); tree size = dstsize; if (pad && pad->mode == access_read_only) size = wide_int_to_tree (sizetype, pad->src.sizrng[1]); @@ -4882,9 +4876,7 @@ check_access (tree exp, tree dstwrite, || (pad && pad->src.ref && TREE_NO_WARNING (pad->src.ref))) return false; - location_t loc = tree_nonartificial_location (exp); - loc = expansion_point_location_if_in_system_header (loc); - + location_t loc = tree_inlined_location (exp); const bool read = mode == access_read_only || mode == access_read_write; const bool maybe = pad && pad->dst.parmarray; @@ -6381,9 +6373,7 @@ check_strncat_sizes (tree exp, tree objsize) if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (objsize) && tree_int_cst_equal (objsize, maxread)) { - location_t loc = tree_nonartificial_location (exp); - loc = expansion_point_location_if_in_system_header (loc); - + location_t loc = tree_inlined_location (exp); warning_at (loc, OPT_Wstringop_overflow_, "%K%qD specified bound %E equals destination size", exp, get_callee_fndecl (exp), maxread); @@ -6456,9 +6446,7 @@ expand_builtin_strncat (tree exp, rtx) if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (destsize) && tree_int_cst_equal (destsize, maxread)) { - location_t loc = tree_nonartificial_location (exp); - loc = expansion_point_location_if_in_system_header (loc); - + location_t loc = tree_inlined_location (exp); warning_at (loc, OPT_Wstringop_overflow_, "%K%qD specified bound %E equals destination size", exp, get_callee_fndecl (exp), maxread); @@ -7040,9 +7028,7 @@ expand_builtin_strncmp (tree exp, ATTRIBUTE_UNUSED rtx target, || !check_nul_terminated_array (exp, arg2, arg3)) return NULL_RTX; - location_t loc = tree_nonartificial_location (exp); - loc = expansion_point_location_if_in_system_header (loc); - + location_t loc = tree_inlined_location (exp); tree len1 = c_strlen (arg1, 1); tree len2 = c_strlen (arg2, 1); @@ -12970,7 +12956,9 @@ fndecl_alloc_p (tree fndecl, bool all_alloc) case BUILT_IN_ALLOCA: case BUILT_IN_ALLOCA_WITH_ALIGN: return all_alloc; + case BUILT_IN_ALIGNED_ALLOC: case BUILT_IN_CALLOC: + case BUILT_IN_GOMP_ALLOC: case BUILT_IN_MALLOC: case BUILT_IN_REALLOC: case BUILT_IN_STRDUP: @@ -13065,12 +13053,119 @@ call_dealloc_argno (tree exp) return UINT_MAX; } -/* Return true if STMT is a call to a deallocation function. */ +/* Return true if DELETE_DECL is an operator delete that's not suitable + to call with a pointer returned fron NEW_DECL. */ -static inline bool -call_dealloc_p (tree exp) +static bool +new_delete_mismatch_p (tree new_decl, tree delete_decl) { - return call_dealloc_argno (exp) != UINT_MAX; + tree new_name = DECL_ASSEMBLER_NAME (new_decl); + tree delete_name = DECL_ASSEMBLER_NAME (delete_decl); + + /* valid_new_delete_pair_p() returns a conservative result. A true + result is reliable but a false result doesn't necessarily mean + the operators don't match. */ + if (valid_new_delete_pair_p (new_name, delete_name)) + return false; + + const char *new_str = IDENTIFIER_POINTER (new_name); + const char *del_str = IDENTIFIER_POINTER (delete_name); + + if (*new_str != '_') + return *new_str != *del_str; + + ++del_str; + if (*++new_str != 'Z') + return *new_str != *del_str; + + ++del_str; + if (*++new_str == 'n') + return *del_str != 'd'; + + if (*new_str != 'N') + return *del_str != 'N'; + + /* Handle user-defined member operators below. */ + ++new_str; + ++del_str; + + do + { + /* Determine if both operators are members of the same type. + If not, they don't match. */ + char *new_end, *del_end; + unsigned long nlen = strtoul (new_str, &new_end, 10); + unsigned long dlen = strtoul (del_str, &del_end, 10); + if (nlen != dlen) + return true; + + /* Skip past the name length. */ + new_str = new_end; + del_str = del_end; + + /* Skip past the names making sure each has the expected length + (it would suggest some sort of a corruption if they didn't). */ + while (nlen--) + if (!*++new_end) + return true; + + for (nlen = dlen; nlen--; ) + if (!*++del_end) + return true; + + /* The names have the expected length. Compare them. */ + if (memcmp (new_str, del_str, dlen)) + return true; + + new_str = new_end; + del_str = del_end; + + if (*new_str == 'I') + { + /* Template instantiation. */ + do + { + ++new_str; + ++del_str; + + if (*new_str == 'n') + break; + if (*new_str != *del_str) + return true; + } + while (*new_str); + } + + if (*new_str == 'n') + { + if (*del_str != 'd') + return true; + + ++del_str; + if (*++new_str == 'w' && *del_str != 'l') + return true; + if (*new_str == 'a' && *del_str != 'a') + return true; + ++new_str; + ++del_str; + break; + } + } while (true); + + if (*new_str != 'E') + return *del_str != *new_str; + + ++new_str; + ++del_str; + if (*new_str != 'j' && *new_str != 'm' && *new_str != 'y') + return true; + if (*del_str != 'P' || *++del_str != 'v') + return true; + + /* Ignore any remaining arguments. Since both operators are members + of the same class, mismatches in those should be detectable and + diagnosed by the front end. */ + return false; } /* ALLOC_DECL and DEALLOC_DECL are pair of allocation and deallocation @@ -13080,18 +13175,17 @@ call_dealloc_p (tree exp) static bool matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl) { + /* Set to alloc_kind_t::builtin if ALLOC_DECL is associated with + a built-in deallocator. */ + enum class alloc_kind_t { none, builtin, user } + alloc_dealloc_kind = alloc_kind_t::none; + if (DECL_IS_OPERATOR_NEW_P (alloc_decl)) { if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl)) - { - /* Return true iff both functions are of the same array or - singleton form and false otherwise. */ - tree alloc_id = DECL_NAME (alloc_decl); - tree dealloc_id = DECL_NAME (dealloc_decl); - const char *alloc_fname = IDENTIFIER_POINTER (alloc_id); - const char *dealloc_fname = IDENTIFIER_POINTER (dealloc_id); - return !strchr (alloc_fname, '[') == !strchr (dealloc_fname, '['); - } + /* Return true iff both functions are of the same array or + singleton form and false otherwise. */ + return !new_delete_mismatch_p (alloc_decl, dealloc_decl); /* Return false for deallocation functions that are known not to match. */ @@ -13110,7 +13204,9 @@ matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl) case BUILT_IN_ALLOCA_WITH_ALIGN: return false; + case BUILT_IN_ALIGNED_ALLOC: case BUILT_IN_CALLOC: + case BUILT_IN_GOMP_ALLOC: case BUILT_IN_MALLOC: case BUILT_IN_REALLOC: case BUILT_IN_STRDUP: @@ -13121,6 +13217,8 @@ matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl) if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE) || fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC)) return true; + + alloc_dealloc_kind = alloc_kind_t::builtin; break; default: @@ -13128,30 +13226,151 @@ matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl) } } - /* If DEALLOC_DECL has internal "*dealloc" attribute scan the list of - its associated allocation functions for ALLOC_DECL. If it's found - they are a matching pair, otherwise they're not. */ - tree attrs = DECL_ATTRIBUTES (dealloc_decl); - if (!attrs) - return false; + /* Set if DEALLOC_DECL both allocates and deallocates. */ + alloc_kind_t realloc_kind = alloc_kind_t::none; + + if (fndecl_built_in_p (dealloc_decl, BUILT_IN_NORMAL)) + { + built_in_function dealloc_code = DECL_FUNCTION_CODE (dealloc_decl); + if (dealloc_code == BUILT_IN_REALLOC) + realloc_kind = alloc_kind_t::builtin; + + for (tree amats = DECL_ATTRIBUTES (alloc_decl); + (amats = lookup_attribute ("malloc", amats)); + amats = TREE_CHAIN (amats)) + { + tree args = TREE_VALUE (amats); + if (!args) + continue; + + tree fndecl = TREE_VALUE (args); + if (!fndecl || !DECL_P (fndecl)) + continue; + + if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL) + && dealloc_code == DECL_FUNCTION_CODE (fndecl)) + return true; + } + } + + const bool alloc_builtin = fndecl_built_in_p (alloc_decl, BUILT_IN_NORMAL); + alloc_kind_t realloc_dealloc_kind = alloc_kind_t::none; - for (tree funs = attrs; - (funs = lookup_attribute ("*dealloc", funs)); - funs = TREE_CHAIN (funs)) + /* If DEALLOC_DECL has an internal "*dealloc" attribute scan the list + of its associated allocation functions for ALLOC_DECL. + If the corresponding ALLOC_DECL is found they're a matching pair, + otherwise they're not. + With DDATS set to the Deallocator's *Dealloc ATtributes... */ + for (tree ddats = DECL_ATTRIBUTES (dealloc_decl); + (ddats = lookup_attribute ("*dealloc", ddats)); + ddats = TREE_CHAIN (ddats)) { - tree args = TREE_VALUE (funs); + tree args = TREE_VALUE (ddats); if (!args) continue; - tree fname = TREE_VALUE (args); - if (!fname) + tree alloc = TREE_VALUE (args); + if (!alloc) continue; - if (fname == DECL_NAME (alloc_decl)) + if (alloc == DECL_NAME (dealloc_decl)) + realloc_kind = alloc_kind_t::user; + + if (DECL_P (alloc)) + { + gcc_checking_assert (fndecl_built_in_p (alloc, BUILT_IN_NORMAL)); + + switch (DECL_FUNCTION_CODE (alloc)) + { + case BUILT_IN_ALIGNED_ALLOC: + case BUILT_IN_CALLOC: + case BUILT_IN_GOMP_ALLOC: + case BUILT_IN_MALLOC: + case BUILT_IN_REALLOC: + case BUILT_IN_STRDUP: + case BUILT_IN_STRNDUP: + realloc_dealloc_kind = alloc_kind_t::builtin; + break; + default: + break; + } + + if (!alloc_builtin) + continue; + + if (DECL_FUNCTION_CODE (alloc) != DECL_FUNCTION_CODE (alloc_decl)) + continue; + + return true; + } + + if (alloc == DECL_NAME (alloc_decl)) return true; } - return false; + if (realloc_kind == alloc_kind_t::none) + return false; + + hash_set<tree> common_deallocs; + /* Special handling for deallocators. Iterate over both the allocator's + and the reallocator's associated deallocator functions looking for + the first one in common. If one is found, the de/reallocator is + a match for the allocator even though the latter isn't directly + associated with the former. This simplifies declarations in system + headers. + With AMATS set to the Allocator's Malloc ATtributes, + and RMATS set to Reallocator's Malloc ATtributes... */ + for (tree amats = DECL_ATTRIBUTES (alloc_decl), + rmats = DECL_ATTRIBUTES (dealloc_decl); + (amats = lookup_attribute ("malloc", amats)) + || (rmats = lookup_attribute ("malloc", rmats)); + amats = amats ? TREE_CHAIN (amats) : NULL_TREE, + rmats = rmats ? TREE_CHAIN (rmats) : NULL_TREE) + { + if (tree args = amats ? TREE_VALUE (amats) : NULL_TREE) + if (tree adealloc = TREE_VALUE (args)) + { + if (DECL_P (adealloc) + && fndecl_built_in_p (adealloc, BUILT_IN_NORMAL)) + { + built_in_function fncode = DECL_FUNCTION_CODE (adealloc); + if (fncode == BUILT_IN_FREE || fncode == BUILT_IN_REALLOC) + { + if (realloc_kind == alloc_kind_t::builtin) + return true; + alloc_dealloc_kind = alloc_kind_t::builtin; + } + continue; + } + + common_deallocs.add (adealloc); + } + + if (tree args = rmats ? TREE_VALUE (rmats) : NULL_TREE) + if (tree ddealloc = TREE_VALUE (args)) + { + if (DECL_P (ddealloc) + && fndecl_built_in_p (ddealloc, BUILT_IN_NORMAL)) + { + built_in_function fncode = DECL_FUNCTION_CODE (ddealloc); + if (fncode == BUILT_IN_FREE || fncode == BUILT_IN_REALLOC) + { + if (alloc_dealloc_kind == alloc_kind_t::builtin) + return true; + realloc_dealloc_kind = alloc_kind_t::builtin; + } + continue; + } + + if (common_deallocs.add (ddealloc)) + return true; + } + } + + /* Succeed only if ALLOC_DECL and the reallocator DEALLOC_DECL share + a built-in deallocator. */ + return (alloc_dealloc_kind == alloc_kind_t::builtin + && realloc_dealloc_kind == alloc_kind_t::builtin); } /* Return true if DEALLOC_DECL is a function suitable to deallocate @@ -13167,15 +13386,36 @@ matching_alloc_calls_p (gimple *alloc, tree dealloc_decl) return matching_alloc_calls_p (alloc_decl, dealloc_decl); } -/* Diagnose a call to FNDECL to deallocate a pointer referenced by - AREF that includes a nonzero offset. Such a pointer cannot refer - to the beginning of an allocated object. A negative offset may - refer to it only if the target pointer is unknown. */ +/* Diagnose a call EXP to deallocate a pointer referenced by AREF if it + includes a nonzero offset. Such a pointer cannot refer to the beginning + of an allocated object. A negative offset may refer to it only if + the target pointer is unknown. */ static bool -warn_dealloc_offset (location_t loc, tree exp, tree fndecl, - const access_ref &aref) +warn_dealloc_offset (location_t loc, tree exp, const access_ref &aref) { + if (aref.deref || aref.offrng[0] <= 0 || aref.offrng[1] <= 0) + return false; + + tree dealloc_decl = get_callee_fndecl (exp); + if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl) + && !DECL_IS_REPLACEABLE_OPERATOR (dealloc_decl)) + { + /* A call to a user-defined operator delete with a pointer plus offset + may be valid if it's returned from an unknown function (i.e., one + that's not operator new). */ + if (TREE_CODE (aref.ref) == SSA_NAME) + { + gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref); + if (is_gimple_call (def_stmt)) + { + tree alloc_decl = gimple_call_fndecl (def_stmt); + if (!DECL_IS_OPERATOR_NEW_P (alloc_decl)) + return false; + } + } + } + char offstr[80]; offstr[0] = '\0'; if (wi::fits_shwi_p (aref.offrng[0])) @@ -13192,7 +13432,7 @@ warn_dealloc_offset (location_t loc, tree exp, tree fndecl, if (!warning_at (loc, OPT_Wfree_nonheap_object, "%K%qD called on pointer %qE with nonzero offset%s", - exp, fndecl, aref.ref, offstr)) + exp, dealloc_decl, aref.ref, offstr)) return false; if (DECL_P (aref.ref)) @@ -13202,9 +13442,16 @@ warn_dealloc_offset (location_t loc, tree exp, tree fndecl, gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref); if (is_gimple_call (def_stmt)) { + location_t def_loc = gimple_location (def_stmt); tree alloc_decl = gimple_call_fndecl (def_stmt); - inform (gimple_location (def_stmt), - "returned from a call to %qD", alloc_decl); + if (alloc_decl) + inform (def_loc, + "returned from %qD", alloc_decl); + else if (tree alloc_fntype = gimple_call_fntype (def_stmt)) + inform (def_loc, + "returned from %qT", alloc_fntype); + else + inform (def_loc, "obtained here"); } } @@ -13240,8 +13487,7 @@ maybe_emit_free_warning (tree exp) return; tree dealloc_decl = get_callee_fndecl (exp); - location_t loc = tree_nonartificial_location (exp); - loc = expansion_point_location_if_in_system_header (loc); + location_t loc = tree_inlined_location (exp); if (DECL_P (ref) || EXPR_P (ref)) { @@ -13251,18 +13497,18 @@ maybe_emit_free_warning (tree exp) "%K%qD called on unallocated object %qD", exp, dealloc_decl, ref)) { - inform (DECL_SOURCE_LOCATION (ref), - "declared here"); + loc = (DECL_P (ref) + ? DECL_SOURCE_LOCATION (ref) + : EXPR_LOCATION (ref)); + inform (loc, "declared here"); return; } /* Diagnose freeing a pointer that includes a positive offset. Such a pointer cannot refer to the beginning of an allocated object. A negative offset may refer to it. */ - if (!aref.deref - && aref.sizrng[0] != aref.sizrng[1] - && aref.offrng[0] > 0 && aref.offrng[1] > 0 - && warn_dealloc_offset (loc, exp, dealloc_decl, aref)) + if (aref.sizrng[0] != aref.sizrng[1] + && warn_dealloc_offset (loc, exp, aref)) return; } else if (CONSTANT_CLASS_P (ref)) @@ -13295,9 +13541,7 @@ maybe_emit_free_warning (tree exp) { if (matching_alloc_calls_p (def_stmt, dealloc_decl)) { - if (!aref.deref - && aref.offrng[0] > 0 && aref.offrng[1] > 0 - && warn_dealloc_offset (loc, exp, dealloc_decl, aref)) + if (warn_dealloc_offset (loc, exp, aref)) return; } else @@ -13320,16 +13564,14 @@ maybe_emit_free_warning (tree exp) "%K%qD called on pointer to " "an unallocated object", exp, dealloc_decl); - else if (!aref.deref - && aref.offrng[0] > 0 && aref.offrng[1] > 0 - && warn_dealloc_offset (loc, exp, dealloc_decl, aref)) + else if (warn_dealloc_offset (loc, exp, aref)) return; if (warned) { tree fndecl = gimple_call_fndecl (def_stmt); inform (gimple_location (def_stmt), - "returned from a call to %qD", fndecl); + "returned from %qD", fndecl); return; } } @@ -13341,7 +13583,7 @@ maybe_emit_free_warning (tree exp) && !aref.deref && aref.sizrng[0] != aref.sizrng[1] && aref.offrng[0] > 0 && aref.offrng[1] > 0 - && warn_dealloc_offset (loc, exp, dealloc_decl, aref)) + && warn_dealloc_offset (loc, exp, aref)) return; } } |