From: Martin Sebor Date: Mon, 14 Dec 2020 20:30:00 +0000 (-0700) Subject: Correct/improve maybe_emit_free_warning (PR middle-end/98166, PR c++/57111, PR middle... X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=fe7f75cf16783589eedbab597e6d0b8d35d7e470;p=gcc.git 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. --- diff --git a/gcc/builtins.c b/gcc/builtins.c index faa5030853b..28e44445ab2 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 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; } } diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c index f7dad7a91d7..29e26728300 100644 --- a/gcc/c-family/c-attribs.c +++ b/gcc/c-family/c-attribs.c @@ -3130,12 +3130,64 @@ handle_no_profile_instrument_function_attribute (tree *node, tree name, tree, return NULL_TREE; } +/* If ALLOC_DECL and DEALLOC_DECL are a pair of user-defined functions, + if they are declared inline issue warnings and return null. Otherwise + create attribute noinline, install it in ALLOC_DECL, and return it. + Otherwise return null. */ + +static tree +maybe_add_noinline (tree name, tree alloc_decl, tree dealloc_decl, + bool *no_add_attrs) +{ + if (fndecl_built_in_p (alloc_decl) || fndecl_built_in_p (dealloc_decl)) + return NULL_TREE; + + /* When inlining (or optimization) is enabled and the allocator and + deallocator are not built-in functions, ignore the attribute on + functions declared inline since it could lead to false positives + when inlining one or the other call would wind up calling + a mismatched allocator or deallocator. */ + if ((optimize && DECL_DECLARED_INLINE_P (alloc_decl)) + || lookup_attribute ("always_inline", DECL_ATTRIBUTES (alloc_decl))) + { + warning (OPT_Wattributes, + "%<%E (%E)%> attribute ignored on functions " + "declared %qs", name, DECL_NAME (dealloc_decl), "inline"); + *no_add_attrs = true; + return NULL_TREE; + } + + if ((optimize && DECL_DECLARED_INLINE_P (dealloc_decl)) + || lookup_attribute ("always_inline", DECL_ATTRIBUTES (dealloc_decl))) + { + warning (OPT_Wattributes, + "%<%E (%E)%> attribute ignored with deallocation " + "functions declared %qs", + name, DECL_NAME (dealloc_decl), "inline"); + inform (DECL_SOURCE_LOCATION (dealloc_decl), + "deallocation function declared here" ); + *no_add_attrs = true; + return NULL_TREE; + } + + /* Disable inlining for non-standard deallocators to avoid false + positives due to mismatches between the inlined implementation + of one and not the other pair of functions. */ + tree attr = tree_cons (get_identifier ("noinline"), NULL_TREE, NULL_TREE); + decl_attributes (&alloc_decl, attr, 0); + return attr; +} + /* Handle the "malloc" attribute. */ static tree -handle_malloc_attribute (tree *node, tree name, tree args, - int ARG_UNUSED (flags), bool *no_add_attrs) +handle_malloc_attribute (tree *node, tree name, tree args, int flags, + bool *no_add_attrs) { + if (flags & ATTR_FLAG_INTERNAL) + /* Recursive call. */ + return NULL_TREE; + tree fndecl = *node; if (TREE_CODE (*node) != FUNCTION_DECL) @@ -3174,11 +3226,21 @@ handle_malloc_attribute (tree *node, tree name, tree args, return NULL_TREE; } - /* In C++ the argument may be wrapped in a cast to disambiguate one - of a number of overloads (such as operator delete). Strip it. */ STRIP_NOPS (dealloc); if (TREE_CODE (dealloc) == ADDR_EXPR) - dealloc = TREE_OPERAND (dealloc, 0); + { + /* In C++ the argument may be wrapped in a cast to disambiguate + one of a number of overloads (such as operator delete). To + make things interesting, the cast looks different between + different C++ versions. Strip it and install the attribute + with the disambiguated function. */ + dealloc = TREE_OPERAND (dealloc, 0); + + *no_add_attrs = true; + tree attr = tree_cons (NULL_TREE, dealloc, TREE_CHAIN (args)); + attr = build_tree_list (name, attr); + return decl_attributes (node, attr, 0); + } if (TREE_CODE (dealloc) != FUNCTION_DECL) { @@ -3233,10 +3295,21 @@ handle_malloc_attribute (tree *node, tree name, tree args, return NULL_TREE; } - *no_add_attrs = false; - tree attr_free = build_tree_list (NULL_TREE, DECL_NAME (fndecl)); - attr_free = build_tree_list (get_identifier ("*dealloc"), attr_free); - decl_attributes (&dealloc, attr_free, 0); + /* Disable inlining for non-standard deallocators to avoid false + positives (or warn if either function is explicitly inline). */ + tree at_noinline = + maybe_add_noinline (name, fndecl, dealloc, no_add_attrs); + if (*no_add_attrs) + return NULL_TREE; + + /* Add attribute *dealloc to the deallocator function associating + it with this one. Ideally, the attribute would reference + the DECL of the deallocator but since that changes for each + redeclaration, use DECL_NAME instead. (DECL_ASSEMBLER_NAME + need not be set set this point and setting it here is too early. */ + tree attrs = build_tree_list (NULL_TREE, DECL_NAME (fndecl)); + attrs = tree_cons (get_identifier ("*dealloc"), attrs, at_noinline); + decl_attributes (&dealloc, attrs, 0); return NULL_TREE; } @@ -3248,15 +3321,21 @@ handle_malloc_attribute (tree *node, tree name, tree args, return NULL_TREE; } + /* As above, disable inlining for non-standard deallocators to avoid + false positives (or warn). */ + tree at_noinline = + maybe_add_noinline (name, fndecl, dealloc, no_add_attrs); + if (*no_add_attrs) + return NULL_TREE; + /* It's valid to declare the same function with multiple instances of attribute malloc, each naming the same or different deallocator functions, and each referencing either the same or a different positional argument. */ - *no_add_attrs = false; - tree attr_free = tree_cons (NULL_TREE, argpos, NULL_TREE); - attr_free = tree_cons (NULL_TREE, DECL_NAME (fndecl), attr_free); - attr_free = build_tree_list (get_identifier ("*dealloc"), attr_free); - decl_attributes (&dealloc, attr_free, 0); + tree attrs = tree_cons (NULL_TREE, argpos, NULL_TREE); + attrs = tree_cons (NULL_TREE, DECL_NAME (fndecl), attrs); + attrs = tree_cons (get_identifier ("*dealloc"), attrs, at_noinline); + decl_attributes (&dealloc, attrs, 0); return NULL_TREE; } @@ -3274,11 +3353,13 @@ handle_dealloc_attribute (tree *node, tree name, tree args, int, if (!attrs) return NULL_TREE; - tree arg_fname = TREE_VALUE (args); + tree arg = TREE_VALUE (args); args = TREE_CHAIN (args); - tree arg_pos = args ? TREE_VALUE (args) : NULL_TREE; + tree arg_pos = args ? TREE_VALUE (args) : integer_zero_node; - gcc_checking_assert (TREE_CODE (arg_fname) == IDENTIFIER_NODE); + gcc_checking_assert ((DECL_P (arg) + && fndecl_built_in_p (arg, BUILT_IN_NORMAL)) + || TREE_CODE (arg) == IDENTIFIER_NODE); const char* const namestr = IDENTIFIER_POINTER (name); for (tree at = attrs; (at = lookup_attribute (namestr, at)); @@ -3290,12 +3371,12 @@ handle_dealloc_attribute (tree *node, tree name, tree args, int, tree pos = TREE_CHAIN (alloc); alloc = TREE_VALUE (alloc); - pos = pos ? TREE_VALUE (pos) : NULL_TREE; - gcc_checking_assert (TREE_CODE (alloc) == IDENTIFIER_NODE); + pos = pos ? TREE_VALUE (pos) : integer_zero_node; + gcc_checking_assert ((DECL_P (alloc) + && fndecl_built_in_p (alloc, BUILT_IN_NORMAL)) + || TREE_CODE (alloc) == IDENTIFIER_NODE); - if (alloc == arg_fname - && ((!pos && !arg_pos) - || (pos && arg_pos && tree_int_cst_equal (pos, arg_pos)))) + if (alloc == arg && tree_int_cst_equal (pos, arg_pos)) { /* The function already has the attribute either without any arguments or with the same arguments as the attribute that's diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 0c969085d1f..e73464a7f19 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -3257,37 +3257,37 @@ as they may return pointers to storage containing pointers to existing objects. Associating a function with a @var{deallocator} helps detect calls to -mismatched allocation and deallocation functions and diagnose them -under the control of options such as @option{-Wmismatched-dealloc}. -To indicate that an allocation function both satisifies the nonaliasing -property and has a deallocator associated with it, both the plain form -of the attribute and the one with the @var{deallocator} argument must -be used. +mismatched allocation and deallocation functions and diagnose them under +the control of options such as @option{-Wmismatched-dealloc}. To indicate +that an allocation function both satisifies the nonaliasing property and +has a deallocator associated with it, both the plain form of the attribute +and the one with the @var{deallocator} argument must be used. The same +function can be both an allocator and a deallocator. Since inlining one +of the associated functions but not the other could result in apparent +mismatches, this form of attribute @code{malloc} is not accepted on inline +functions. For the same reason, using the attribute prevents both +the allocation and deallocation functions from being expanded inline. For example, besides stating that the functions return pointers that do -not alias any others, the following declarations make the @code{fclose} -and @code{frepen} functions suitable deallocators for pointers returned -from all the functions that return them, and the @code{pclose} function -as the only other suitable deallocator besides @code{freopen} for pointers -returned from @code{popen}. The deallocator functions must declared -before they can be referenced in the attribute. +not alias any others, the following declarations make @code{fclose} +a suitable deallocator for pointers returned from all functions except +@code{popen}, and @code{pclose} as the only suitable deallocator for +pointers returned from @code{popen}. The deallocator functions must +declared before they can be referenced in the attribute. @smallexample -int fclose (FILE*); -FILE* freopen (const char*, const char*, FILE*); -int pclose (FILE*); +int fclose (FILE*); +int pclose (FILE*); -__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3))) +__attribute__ ((malloc, malloc (fclose (1)))) FILE* fdopen (int); -__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3))) +__attribute__ ((malloc, malloc (fclose (1)))) FILE* fopen (const char*, const char*); -__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3))) +__attribute__ ((malloc, malloc (fclose (1)))) FILE* fmemopen(void *, size_t, const char *); -__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3))) - FILE* freopen (const char*, const char*, FILE*); -__attribute__ ((malloc, malloc (pclose), malloc (freopen, 3))) +__attribute__ ((malloc, malloc (pclose (1)))) FILE* popen (const char*, const char*); -__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3))) +__attribute__ ((malloc, malloc (fclose (1)))) FILE* tmpfile (void); @end smallexample diff --git a/gcc/testsuite/c-c++-common/Wfree-nonheap-object-2.c b/gcc/testsuite/c-c++-common/Wfree-nonheap-object-2.c new file mode 100644 index 00000000000..0aedf1babbc --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wfree-nonheap-object-2.c @@ -0,0 +1,52 @@ +/* PR middle-end/98166: bogus -Wmismatched-dealloc on user-defined allocator + and inlining + Verify that the allocator can be declared inline without a warning when + it's associated with a standard deallocator. Associating an inline + deallocator with an allocator would cause false positives when the former + calls a deallocation function the allocator isn't associated with, so + that triggers a warning on declaration. + { dg-do compile } + { dg-options "-O2 -Wall" } */ + +__attribute__ ((malloc (__builtin_free))) +inline int* +alloc_int (int n) +{ + return (int*)__builtin_malloc (n + sizeof (int)); +} + +void test_nowarn_int (int n) +{ + { + int *p = alloc_int (n); + __builtin_free (p); + } + + { + int *p = alloc_int (n); + __builtin_free (p + 1); // { dg-warning "\\\[-Wfree-nonheap-object" } + } +} + + +inline void +dealloc_long (long *p) +{ + __builtin_free (p); // { dg-warning "'__builtin_free|void __builtin_free\\(void\\*\\)' called on pointer 'p|' with nonzero offset" } +} + +__attribute__ ((malloc (dealloc_long))) +long* alloc_long (int); // { dg-warning "'malloc \\\(dealloc_long\\\)' attribute ignored with deallocation functions declared 'inline'" } + +void test_nowarn_long (int n) +{ + { + long *p = alloc_long (n); + dealloc_long (p); + } + + { + long *p = alloc_long (n); + dealloc_long (p + 1); + } +} diff --git a/gcc/testsuite/c-c++-common/Wfree-nonheap-object-3.c b/gcc/testsuite/c-c++-common/Wfree-nonheap-object-3.c new file mode 100644 index 00000000000..41a5b50362e --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wfree-nonheap-object-3.c @@ -0,0 +1,70 @@ +/* PR middle-end/98166: bogus -Wmismatched-dealloc on user-defined allocator + and inlining + Verify that without inlining, both the allocator and the deallocator + can be declared inline without a warning and that mismatched calls are + detected, but that declaring them always_inline does trigger a warning. + { dg-do compile } + { dg-options "-Wall" } */ + +__attribute__ ((malloc (__builtin_free))) +inline int* +alloc_int (int n) +{ + return (int*)__builtin_malloc (n + sizeof (int)); +} + +void test_nowarn_int (int n) +{ + { + int *p = alloc_int (n); + __builtin_free (p); + } + + { + int *p = alloc_int (n); + __builtin_free (p + 1); // { dg-warning "'__builtin_free|void __builtin_free\\(void\\*\\)' called on pointer 'p|' with nonzero offset" } + } +} + + +inline void +dealloc_long (long *p) { __builtin_free (p); } + +__attribute__ ((malloc (dealloc_long))) +long* alloc_long (int); + +void test_nowarn_long (int n) +{ + { + long *p = alloc_long (n); + dealloc_long (p); + } + + { + long *p = alloc_long (n); + dealloc_long (p + 1); // { dg-warning "'dealloc_long' called on pointer 'p|' with nonzero offset" } + } +} + + +inline __attribute__ ((always_inline)) void +dealloc_float (float *p) // { dg-message "deallocation function declared here" } +{ + __builtin_free (p); // { dg-warning "'__builtin_free|void __builtin_free\\(void\\*\\)' called on pointer 'p|' with nonzero offset" } +} + +__attribute__ ((malloc (dealloc_float))) +float* alloc_float (int); // { dg-warning "'malloc \\(dealloc_float\\)' attribute ignored with deallocation functions declared 'inline'" } + +void test_nowarn_float (int n) +{ + { + float *p = alloc_float (n); + dealloc_float (p); + } + + { + float *p = alloc_float (n); + dealloc_float (p + 2); + } +} diff --git a/gcc/testsuite/c-c++-common/Wfree-nonheap-object.c b/gcc/testsuite/c-c++-common/Wfree-nonheap-object.c new file mode 100644 index 00000000000..dfbb296e9a7 --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wfree-nonheap-object.c @@ -0,0 +1,50 @@ +/* Verify that built-in forms of functions can be used interchangeably + with their ordinary (library) forms in attribute malloc. + { dg-do compile } + { dg-options "-Wall" } */ + +char* f (void) __attribute__ ((malloc (__builtin_free))); + +#if __cplusplus +extern "C" { +#endif + +void free (void*); + +#if __cplusplus +} +#endif + +char* g (void) __attribute__ ((malloc (free))); + + +void test_nowarm (void) +{ + char *p = f (); + free (p); + + p = g (); + free (p); + + p = f (); + __builtin_free (p); + + p = g (); + __builtin_free (p); +} + + +void test_warn (void) +{ + char *p = f (); + free (p + 1); // { dg-warning "'free|void free\\(void\\*\\)' called on pointer 'p|' with nonzero offset" } + + p = g (); + free (p + 2); // { dg-warning "'free|void free\\(void\\*\\)' called on pointer 'p|' with nonzero offset" } + + p = f (); + __builtin_free (p + 3); // { dg-warning "'__builtin_free|void __builtin_free\\(void\\*\\)' called on pointer 'p|' with nonzero offset" } + + p = g (); + __builtin_free (p + 4); // { dg-warning "'__builtin_free|void __builtin_free\\(void\\*\\)' called on pointer 'p|' with nonzero offset" } +} diff --git a/gcc/testsuite/c-c++-common/Wmismatched-dealloc.c b/gcc/testsuite/c-c++-common/Wmismatched-dealloc.c new file mode 100644 index 00000000000..27af2c2316b --- /dev/null +++ b/gcc/testsuite/c-c++-common/Wmismatched-dealloc.c @@ -0,0 +1,67 @@ +/* PR middle-end/98166: bogus -Wmismatched-dealloc on user-defined allocator + and inlining + { dg-do compile } + { dg-options "-O2 -Wall" } */ + + +void dealloc_shrt (short *p) +{ + /* A positive offset would be diagnosed but a negative one must + not be. */ + __builtin_free (p - 1); // { dg-bogus "-Wmismatched-dealloc" } +} + +__attribute__ ((malloc (dealloc_shrt))) +short* alloc_shrt (int n) /* { return malloc (n) + 1; } */; + +void test_nowarn_shrt (int n) +{ + short *p = alloc_shrt (n); + dealloc_shrt (p); +} + + +void dealloc_int (int *p) /* { free (p - 1); } */; + +__attribute__ ((malloc (dealloc_int, 1))) +int* alloc_int (int n) +{ + return (int*)__builtin_malloc (n) + 1; +} + +void test_nowarn_int (int n) +{ + int *p = alloc_int (n); + dealloc_int (p); // { dg-bogus "-Wmismatched-dealloc" } +} + + +void dealloc_long (int, long *p) /* { free (p - 2); } */; + +__attribute__ ((malloc (dealloc_long, 2))) +inline long* +alloc_long (int n) { // { dg-warning "'malloc \\(\[^\n\r\]*dealloc_long\[^\n\r\]*\\)' attribute ignored on functions declared 'inline'" } + return (long*)__builtin_malloc (n) + 2; +} + +void test_nowarn_long (int n) +{ + long *p = alloc_long (n); + dealloc_long (0, p); // { dg-bogus "\\\[-Wmismatched-dealloc" } +} + + +inline void +dealloc_float (int, int, float *p) // { dg-message "deallocation function declared here" } +{ + __builtin_free (p - 3); +} + +__attribute__ ((malloc (dealloc_float, 3))) +float* alloc_float (int n); // { dg-warning "'malloc \\(\[^\n\r\]*dealloc_float\[^\n\r\]*\\)' attribute ignored with deallocation functions declared 'inline'" } + +void test_nowarn_float (int n) +{ + float *p = alloc_float (n); + dealloc_float (0, 1, p); // { dg-bogus "\\\[-Wmismatched-dealloc" } +} diff --git a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-3.C b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-3.C new file mode 100644 index 00000000000..47f97dcb636 --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-3.C @@ -0,0 +1,38 @@ +/* PR c++/57111 - Generalize -Wfree-nonheap-object to delete + Verify that even without -Wsystem-headers the warning is issued + for pairs of library functions defined in system headers. + { dg-do compile { target c++11 } } + { dg-options "-O2 -Wall" } */ + +#include +#include + +void test_string () +{ + std::string str ("abc"); // { dg-message "declared here" } + + const char *s = str.c_str (); + __builtin_printf ("%s\n", s); + + /* Because the delete call is made directly in the function this + does not exercise the same thing as test_unique_ptr. */ + delete s; // { dg-warning "'void operator delete\\(void\\*\[^\\)\]*\\)' called on unallocated object 'str'" } +} + +void test_unique_ptr () +{ + int arr[]= { 1, 2 }; // { dg-message "declared here" } + + std::unique_ptr up (arr); + __builtin_printf ("%i %i\n", up[0], up[1]); + + /* TO DO: verify that the warning is printed, including its inlining + context (the directive below doesn't work): + { Xdg-message "In member function.*inlined from 'void test_unique_ptr\\(\\)'.*warning: 'void operator delete \\\[]\\(void\\*\\)' called on unallocated object 'arr'" "" { target *-*-* } 0 } */ + + /* Here, the delete call is made indirectly from std::unique_ptr + dtor. */ +} + +/* Prune out the warning from test_unique_ptr(). + { dg-prune-output "-Wfree-nonheap-object" } */ diff --git a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-4.C b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-4.C new file mode 100644 index 00000000000..943ef0cd1ab --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-4.C @@ -0,0 +1,26 @@ +/* PR middle-end/98160: bogus -Wfree-nonheap-object calling member delete + on the result of inline member new plus offset + { dg-do compile } + { dg-options "-O2" } */ + +struct MemoryManager { void* allocate (); }; + +struct XMemory +{ + void* operator new (__SIZE_TYPE__, MemoryManager *mgr) + { + void *p = mgr->allocate (); + return (char*)p + sizeof(MemoryManager); + } + + void operator delete (void*, MemoryManager*); +}; + +struct XMLMutex: XMemory { + XMLMutex(); +}; + +void gValidatorMutex (MemoryManager *mgr) +{ + new (mgr) XMLMutex; // { dg-bogus "\\\[-Wfree-nonheap-object" } +} diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C index 7ecc99a325c..3aea02fa63d 100644 --- a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C @@ -59,13 +59,13 @@ void test_my_new () { void *p = my_new (1); - // { dg-message "returned from a call to 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } operator delete[] (p); // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function \\\[-Wmismatched-new-delete" "" { target *-*-* } .-1 } } { void *p = my_new (1); - // { dg-message "returned from a call to 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } sink (p); operator delete[] (p); // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function \\\[-Wmismatched-new-delete" "" { target *-*-* } .-1 } @@ -89,7 +89,7 @@ void test_my_new () { void *p = my_new (1); - // { dg-message "returned from a call to 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } sink (p); my_array_delete ("3", p); // { dg-warning "'void my_array_delete\\\(const char\\\*, void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -97,7 +97,7 @@ void test_my_new () { void *p = my_new (1); - // { dg-message "returned from a call to 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } sink (p); free (p); // { dg-warning "'void free\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -105,7 +105,7 @@ void test_my_new () { void *p = my_new (1); - // { dg-message "returned from a call to 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'int\\\* my_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } sink (p); p = realloc (p, 123); // { dg-warning "'void\\\* realloc\\\(void\\\*, size_t\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -132,13 +132,13 @@ void test_my_array_new () { void *p = my_array_new (1); - // { dg-message "returned from a call to 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } operator delete (p); // { dg-warning "'void operator delete\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function \\\[-Wmismatched-new-delete" "" { target *-*-* } .-1 } } { void *p = my_array_new (1); - // { dg-message "returned from a call to 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } sink (p); operator delete (p); // { dg-warning "'void operator delete\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function \\\[-Wmismatched-new-delete" "" { target *-*-* } .-1 } @@ -161,7 +161,7 @@ void test_my_array_new () } { void *p = my_array_new (1); - // { dg-message "returned from a call to 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } sink (p); my_delete ("3", p); // { dg-warning "'void my_delete\\\(const char\\\*, void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -169,7 +169,7 @@ void test_my_array_new () { void *p = my_array_new (1); - // { dg-message "returned from a call to 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } sink (p); free (p); // { dg-warning "'void free\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -177,7 +177,7 @@ void test_my_array_new () { void *p = my_array_new (1); - // { dg-message "returned from a call to 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'int\\\* my_array_new\\\(size_t\\\)'" "note" { target *-*-* } .-1 } sink (p); p = realloc (p, 123); // { dg-warning "'void\\\* realloc\\\(void\\\*, size_t\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-2.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-2.C new file mode 100644 index 00000000000..d0d53b38b93 --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete-2.C @@ -0,0 +1,249 @@ +/* Verify that implicit and explicit calls to member operator new and delete + are handled correctly. + { dg-do compile } + { dg-options "-Wmismatched-new-delete" } */ + +typedef __SIZE_TYPE__ size_t; + +namespace std +{ +#if __cplusplus >= 201703L +enum class align_val_t: size_t { }; +#else +enum align_val_t { }; +#endif + +struct nothrow_t { }; +const nothrow_t nothrow = { }; +} + +void sink (void*, ...); + +struct POD +{ + void* operator new (size_t); + void operator delete (void*); + + void* operator new[] (size_t); + void operator delete[] (void*); +}; + +POD* nowarn_pod () +{ + POD *p = new POD; + delete p; + return new POD; +} + +void warn_pod_array_mismatch () +{ + POD *p = new POD; + delete[] p; // { dg-warning "'static void POD::operator delete \\\[]\\(void\\*\\)' called on pointer returned from a mismatched allocation function" } + p = new POD[3]; + delete p; // { dg-warning "'static void POD::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" } +} + + +struct X1 +{ + X1 (); + + void* operator new (size_t); + void* operator new (size_t, std::align_val_t); + void* operator new (size_t, std::nothrow_t) throw (); + void* operator new (size_t, std::align_val_t, std::nothrow_t) throw (); + + void* operator new[] (size_t); + void* operator new[] (size_t, std::align_val_t); + void* operator new[] (size_t, std::nothrow_t) throw (); + void* operator new[] (size_t, std::align_val_t, std::nothrow_t) throw (); + + void operator delete (void*); + void operator delete (void*, size_t); + void operator delete (void*, std::align_val_t); + void operator delete (void*, size_t, std::align_val_t); + void operator delete (void*, std::nothrow_t) throw (); + void operator delete (void*, std::align_val_t, std::nothrow_t) throw (); + + void operator delete[] (void*); + void operator delete[] (void*, size_t); + void operator delete[] (void*, std::align_val_t); + void operator delete[] (void*, size_t, std::align_val_t); + void operator delete[] (void*, std::nothrow_t) throw (); + void operator delete[] (void*, std::align_val_t, std::nothrow_t) throw (); +}; + +X1* nowarn_x1 () +{ + return new X1; +} + +X1* nowarn_x1_array () +{ + return new X1[2]; +} + +X1* nowarn_align_val () +{ + X1 *p = new (std::align_val_t (32)) X1; + delete p; + return new (std::align_val_t (64)) X1; +} + +X1* nowarn_align_val_array () +{ + X1 *p = new (std::align_val_t (32)) X1[2]; + delete[] p; + return new (std::align_val_t (64)) X1[2]; +} + +X1* nowarn_x1_nothrow () +{ + X1 *p = new (std::nothrow) X1; + delete p; + return new (std::nothrow) X1; +} + +X1* nowarn_x1_nothrow_array () +{ + X1 *p = new (std::nothrow) X1[3]; + delete[] p; + return new (std::nothrow) X1[3]; +} + +X1* nowarn_align_val_nothrow () +{ + X1 *p = new (std::align_val_t (32), std::nothrow) X1; + delete p; + return new (std::align_val_t (64), std::nothrow) X1; +} + +X1* nowarn_align_val_nothrow_array () +{ + X1 *p = new (std::align_val_t (32), std::nothrow) X1[4]; + delete[] p; + return new (std::align_val_t (64), std::nothrow) X1[4]; +} + +void warn_x1_array_mismatch () +{ + { + X1 *p = new X1; + delete[] p; // { dg-warning "'static void X1::operator delete \\\[]\\(void\\*\\)' called on pointer returned from a mismatched allocation function" } + } + { + X1 *p = new X1[2]; + delete p; // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" } + } + { + X1 *p = new (std::align_val_t (32)) X1[2]; + delete p; // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" } + } + { + // The following requires optimization (see warn_x1_array_mismatch()). + X1 *p = new (std::nothrow) X1[3]; + delete p; // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" "pr?????" { xfail *-*-* } } + } +} + +#pragma GCC push_options +#pragma GCC optimize "1" + +void warn_x1_nothrow_array_mismatch () +{ + X1 *p = new (std::nothrow) X1[3]; + delete p; // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" } +} + +#pragma GCC pop_options + + +struct X2: X1 +{ + X2 (); + + void* operator new (size_t); + void operator delete (void*); +}; + +X2* nowarn_x2 () +{ + X2 *p = new X2; + sink (p); + return new X2; +} + +void warn_x2 () +{ + X1 *p = new X2; // { dg-message "returned from 'static void\\* X2::operator new\\(size_t\\)'" "note" } + sink (p); + delete p; // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" } +} + +namespace N { +namespace NS { +namespace NmSpc { +namespace NameSpace { + +namespace dl { // same name as operator delete +namespace nw { // and as operator new + +struct X3: X2 +{ + X3 (); + + void* operator new (size_t); + void operator delete (void*); +}; + +X3* nowarn_x3 () +{ + X3 *p = new X3; + sink (p); + return new X3; +} + +void warn_x3 () +{ + X1 *p = new X3; // { dg-message "returned from 'static void\\* N::NS::NmSpc::NameSpace::dl::nw::X3::operator new\\(size_t\\)'" "note" } + sink (p); + delete p; // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" } +} + +template +struct X4: X2 +{ + X4 (); + + void* operator new (size_t); + void operator delete (void*); +}; + +void* nowarn_x4 () +{ + X4<0> *p = new X4<0>; + sink (p); + return new X4<1>; +} + +void warn_x4 () +{ + X1 *p = new X4<1>; // { dg-message "returned from 'static void\\* N::NS::NmSpc::NameSpace::dl::nw::X4::operator new\\(size_t\\) \\\[with int N = 1]'" "note" } + sink (p); + delete p; // { dg-warning "'static void X1::operator delete\\(void\\*\\)' called on pointer returned from a mismatched allocation function" } +} + +void warn_x4_inst_mismatch () +{ + void *p = new X4<2>; // { dg-message "returned from 'static void\\* N::NS::NmSpc::NameSpace::dl::nw::X4::operator new\\(size_t\\) \\\[with int N = 2]'" "note" } + sink (p); + X4<3> *q = (X4<3>*)p; + delete q; // { dg-warning "'static void N::NS::NmSpc::NameSpace::dl::nw::X4::operator delete\\(void\\*\\) \\\[with int N = 3]' called on pointer returned from a mismatched allocation function" } +} + +} // nw +} // dl +} // NameSpace +} // NmSpc +} // NS +} // N diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C index ed1090be5c5..fc07149995d 100644 --- a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C @@ -44,14 +44,14 @@ void warn_new_free (int n) { { void *p = operator new (n); - // { dg-message "returned from a call to 'void\\\* operator new\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'void\\\* operator new\\\(" "note" { target *-*-* } .-1 } sink (p); free (p); // { dg-warning "'void free\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } } { char *p = new char[n]; - // { dg-message "returned from a call to 'void\\\* operator new \\\[" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'void\\\* operator new \\\[" "note" { target *-*-* } .-1 } sink (p); free (p); // { dg-warning "'void free\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -66,7 +66,7 @@ void warn_new_realloc (int n) { { void *p = operator new (n); - // { dg-message "returned from a call to 'void\\\* operator new\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'void\\\* operator new\\\(" "note" { target *-*-* } .-1 } sink (p); p = realloc (p, n * 2); // { dg-warning "'void\\\* realloc\\\(\[^)\]+\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -74,7 +74,7 @@ void warn_new_realloc (int n) } { void *p = new char[n]; - // { dg-message "returned from a call to 'void\\\* operator new \\\[" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'void\\\* operator new \\\[" "note" { target *-*-* } .-1 } sink (p); p = realloc (p, n * 2); // { dg-warning "'void\\\* realloc\\\(\[^)\]+\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -89,7 +89,7 @@ void warn_new_realloc (int n) void warn_malloc_op_delete (int n) { char *p = (char *)malloc (n); - // { dg-message "returned from a call to 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 } sink (p); operator delete (p); // { dg-warning "'void operator delete\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -97,13 +97,13 @@ void warn_malloc_op_delete (int n) /* Verify a warning for an invocation of either form of the delete - expression with a pointer returned from a call to malloc(). */ + expression with a pointer returned from malloc(). */ void warn_malloc_delete (int n) { { char *p = (char *)malloc (n); - // { dg-message "returned from a call to 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 } sink (p); /* C++98 calls operator delete (void*) but later versions call operator delete (void*, size_t). The difference doesn't matter @@ -114,7 +114,7 @@ void warn_malloc_delete (int n) { char *p = (char *)malloc (n); - // { dg-message "returned from a call to 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'void\\\* malloc\\\(" "note" { target *-*-* } .-1 } sink (p); delete[] p; // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -123,13 +123,13 @@ void warn_malloc_delete (int n) /* Verify a warning for an invocation of either form of the delete - expression with a pointer returned from a call to realloc(). */ + expression with a pointer returned from realloc(). */ void warn_realloc_delete (void *p1, void *p2, int n) { { char *q = (char *)realloc (p1, n); - // { dg-message "returned from a call to 'void\\\* realloc\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'void\\\* realloc\\\(" "note" { target *-*-* } .-1 } sink (q); /* C++98 calls operator delete (void*) but later versions call operator delete (void*, size_t). The difference doesn't matter @@ -140,7 +140,7 @@ void warn_realloc_delete (void *p1, void *p2, int n) { char *q = (char *)realloc (p2, n); - // { dg-message "returned from a call to 'void\\\* realloc\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'void\\\* realloc\\\(" "note" { target *-*-* } .-1 } sink (q); delete[] q; // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -149,13 +149,13 @@ void warn_realloc_delete (void *p1, void *p2, int n) /* Verify a warning for an invocation of either form of the delete - expression with a pointer returned from a call to strdup(). */ + expression with a pointer returned from strdup(). */ void warn_strdup_delete (const char *s1, const char *s2) { { char *q = strdup (s1); - // { dg-message "returned from a call to 'char\\\* strdup\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'char\\\* strdup\\\(" "note" { target *-*-* } .-1 } sink (q); /* C++98 calls operator delete (void*) but later versions call operator delete (void*, size_t). The difference doesn't matter @@ -166,7 +166,7 @@ void warn_strdup_delete (const char *s1, const char *s2) { char *q = strdup (s2); - // { dg-message "returned from a call to 'char\\\* strdup\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'char\\\* strdup\\\(" "note" { target *-*-* } .-1 } sink (q); delete[] q; // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } @@ -176,13 +176,13 @@ void warn_strdup_delete (const char *s1, const char *s2) /* Verify a warning for an invocation of either form of the delete - expression with a pointer returned from a call to strndup(). */ + expression with a pointer returned from strndup(). */ void warn_strdup_delete (const char *s1, const char *s2, size_t n) { { char *q = strndup (s1, n); - // { dg-message "returned from a call to 'char\\\* strndup\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'char\\\* strndup\\\(" "note" { target *-*-* } .-1 } sink (q); /* C++98 calls operator delete (void*) but later versions call operator delete (void*, size_t). The difference doesn't matter @@ -193,7 +193,7 @@ void warn_strdup_delete (const char *s1, const char *s2, size_t n) { char *q = strndup (s2, n); - // { dg-message "returned from a call to 'char\\\* strndup\\\(" "note" { target *-*-* } .-1 } + // { dg-message "returned from 'char\\\* strndup\\\(" "note" { target *-*-* } .-1 } sink (q); delete[] q; // { dg-warning "'void operator delete \\\[]\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c new file mode 100644 index 00000000000..21a5ea7c5da --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-2.c @@ -0,0 +1,141 @@ +/* PR middle-end/94527 - Add an attribute that marks a function as freeing + an object + Verify that attribute malloc with one or two arguments has the expected + effect on diagnostics. + { dg-options "-Wall -ftrack-macro-expansion=0" } */ + +#define A(...) __attribute__ ((malloc (__VA_ARGS__), noipa)) + +typedef __SIZE_TYPE__ size_t; +typedef struct A A; +typedef struct B B; + +/* A pointer returned by any of the four functions must be deallocated + either by dealloc() or by realloc_{A,B}(). */ +A (__builtin_free) A* alloc_A (int); +A (__builtin_free) B* alloc_B (int); +A (__builtin_free) A* realloc_A (A *p, int n) { return p; } +A (__builtin_free) B* realloc_B (B *p, int n) { return p; } + +A (realloc_A) A* alloc_A (int); +A (realloc_B) B* alloc_B (int); +A (realloc_A) A* realloc_A (A*, int); +A (realloc_B) B* realloc_B (B*, int); + +void dealloc (void*); +A (dealloc) void* alloc (int); + +void sink (void*); + +void test_alloc_A (void) +{ + { + void *p = alloc_A (1); + p = realloc_A (p, 2); + __builtin_free (p); + } + + { + void *p = alloc_A (1); + /* Verify that calling realloc doesn't trigger a warning even though + alloc_A is not directly associated with it. */ + p = __builtin_realloc (p, 2); + sink (p); + } + + { + void *p = alloc_A (1); // { dg-message "returned from 'alloc_A'" } + dealloc (p); // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" } + } + + { + /* Because alloc_A() and realloc_B() share free() as a deallocator + they must also be valid as each other's deallocators. */ + void *p = alloc_A (1); + p = realloc_B ((B*)p, 2); + __builtin_free (p); + } + + { + void *p = alloc_A (1); + p = realloc_A (p, 2); + p = __builtin_realloc (p, 3); + __builtin_free (p); + } +} + + +void test_realloc_A (void *ptr) +{ + { + void *p = realloc_A (0, 1); + p = realloc_A (p, 2); + __builtin_free (p); + } + + { + void *p = realloc_A (ptr, 2); + p = realloc_A (p, 2); + __builtin_free (p); + } + + { + void *p = realloc_A (0, 3); + p = __builtin_realloc (p, 2); + sink (p); + } + + { + void *p = realloc_A (0, 4); // { dg-message "returned from 'realloc_A'" } + dealloc (p); // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" } + } + + { + /* Because realloc_A() and realloc_B() share free() as a deallocator + they must also be valid as each other's deallocators. */ + void *p = realloc_A (0, 5); + p = realloc_B ((B*)p, 2); + __builtin_free (p); + } + + { + void *p = realloc_A (0, 6); + p = realloc_A ((A*)p, 2); + p = __builtin_realloc (p, 3); + __builtin_free (p); + } +} + + +void test_realloc (void *ptr) +{ + extern void free (void*); + extern void* realloc (void*, size_t); + + { + void *p = realloc (ptr, 1); + p = realloc_A (p, 2); + __builtin_free (p); + } + + { + void *p = realloc (ptr, 2); + p = realloc_A (p, 2); + free (p); + } + + { + void *p = realloc (ptr, 3); + free (p); + } + + { + void *p = realloc (ptr, 4); + __builtin_free (p); + } + + { + void *p = realloc (ptr, 5); // { dg-message "returned from 'realloc'" } + dealloc (p); // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" } + } +} diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c new file mode 100644 index 00000000000..5afcea39b5e --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc-3.c @@ -0,0 +1,265 @@ +/* Verify that Glibc declarations are handled correctly + { dg-do compile } + { dg-options "-Wall" } */ + +#define A(...) __attribute__ ((malloc (__VA_ARGS__), noipa)) + +typedef __SIZE_TYPE__ size_t; + +/* All functions with the same standard deallocator are associated + with each other. */ +void free (void*); +void* calloc (size_t, size_t); +void* malloc (size_t); +void* realloc (void*, size_t); + +A (__builtin_free) void* aligned_alloc (size_t, size_t); + +/* Like realloc(), reallocarray() is both an allocator and a deallocator. + It must be associated with both free() and with itself, but nothing + else. */ +A (__builtin_free) void* reallocarray (void*, size_t, size_t); +A (reallocarray) void* reallocarray (void*, size_t, size_t); + +A (__builtin_free) extern char *canonicalize_file_name (const char*); + + +void dealloc (void*); +A (dealloc) void* alloc (size_t); + + +void sink (void*); +void* source (void); + + +void test_builtin_aligned_alloc (void *p) +{ + { + void *q = __builtin_aligned_alloc (1, 2); + sink (q); + __builtin_free (q); + } + + { + void *q = __builtin_aligned_alloc (1, 2); + sink (q); + free (q); + } + + { + void *q = __builtin_aligned_alloc (1, 2); + q = __builtin_realloc (q, 3); + sink (q); + free (q); + } + + { + void *q = __builtin_aligned_alloc (1, 2); + q = realloc (q, 3); + sink (q); + free (q); + } + + { + void *q; + q = __builtin_aligned_alloc (1, 2); // { dg-message "returned from '__builtin_aligned_alloc'" } + sink (q); + dealloc (q); // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" } + } +} + + +void test_aligned_alloc (void *p) +{ + { + void *q = aligned_alloc (1, 2); + sink (q); + __builtin_free (q); + } + + { + void *q = aligned_alloc (1, 2); + sink (q); + free (q); + } + + { + void *q = aligned_alloc (1, 2); + q = __builtin_realloc (q, 3); + sink (q); + free (q); + } + + { + void *q = aligned_alloc (1, 2); + q = realloc (q, 3); + sink (q); + free (q); + } + + { + void *q = aligned_alloc (1, 2); // { dg-message "returned from 'aligned_alloc'" } + sink (q); + dealloc (q); // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" } + } +} + + +void test_reallocarray (void *p) +{ + { + void *q = __builtin_aligned_alloc (1, 2); + q = reallocarray (q, 2, 3); + sink (q); + free (q); + } + + { + void *q = aligned_alloc (1, 2); + q = reallocarray (q, 2, 3); + sink (q); + free (q); + } + + { + void *q = __builtin_calloc (1, 2); + q = reallocarray (q, 2, 3); + sink (q); + free (q); + } + + { + void *q = calloc (1, 2); + q = reallocarray (q, 2, 3); + sink (q); + free (q); + } + + { + void *q = __builtin_malloc (1); + q = reallocarray (q, 2, 3); + sink (q); + free (q); + } + + { + void *q = malloc (1); + q = reallocarray (q, 2, 3); + sink (q); + free (q); + } + + { + void *q = __builtin_realloc (p, 1); + q = reallocarray (q, 2, 3); + sink (q); + free (q); + } + + { + void *q = realloc (p, 1); + q = reallocarray (q, 2, 3); + sink (q); + free (q); + } + + { + void *q = __builtin_strdup ("abc"); + q = reallocarray (q, 3, 4); + sink (q); + free (q); + } + + { + void *q = __builtin_strndup ("abcd", 3); + q = reallocarray (q, 4, 5); + sink (q); + free (q); + } + + { + void *q = source (); + q = reallocarray (q, 5, 6); + sink (q); + free (q); + } + + { + void *q = alloc (1); // { dg-message "returned from 'alloc'" } + q = reallocarray (q, 6, 7); // { dg-warning "'reallocarray' called on pointer returned from a mismatched allocation function" } + sink (q); + free (q); + } + + { + void *q = reallocarray (p, 7, 8); + q = __builtin_realloc (q, 9); + sink (q); + free (q); + } + + { + void *q = reallocarray (p, 7, 8); + q = realloc (q, 9); + sink (q); + free (q); + } + + { + void *q = reallocarray (p, 8, 9); + q = reallocarray (q, 3, 4); + sink (q); + free (q); + } + + { + void *q = reallocarray (p, 9, 10); + q = reallocarray (q, 3, 4); + sink (q); + dealloc (q); // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" } + } +} + + +void test_canonicalize_filename (void *p) +{ + { + void *q = canonicalize_file_name ("a"); + sink (q); + __builtin_free (q); + } + + { + void *q = canonicalize_file_name ("b"); + sink (q); + free (q); + } + + { + void *q = canonicalize_file_name ("c"); + q = __builtin_realloc (q, 2); + sink (q); + free (q); + } + + { + void *q = canonicalize_file_name ("d"); + q = realloc (q, 3); + sink (q); + free (q); + } + + { + void *q = canonicalize_file_name ("e"); + q = reallocarray (q, 4, 5); + sink (q); + free (q); + } + + { + void *q; + q = canonicalize_file_name ("f"); // { dg-message "returned from 'canonicalize_file_name'" } + sink (q); + dealloc (q); // { dg-warning "'dealloc' called on pointer returned from a mismatched allocation function" } + } +} diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c index 7c5d6acf4d6..6336efa5594 100644 --- a/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c +++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c @@ -13,28 +13,27 @@ void free (void*); void* malloc (size_t); void* realloc (void*, size_t); -int fclose (FILE*); -FILE* freopen (const char*, const char*, FILE*); -int pclose (FILE*); - -A (fclose) A (freopen, 3) - FILE* fdopen (int); -A (fclose) A (freopen, 3) - FILE* fopen (const char*, const char*); -A (fclose) A (freopen, 3) - FILE* fmemopen(void *, size_t, const char *); -A (fclose) A (freopen, 3) - FILE* freopen (const char*, const char*, FILE*); -A (pclose) A (freopen, 3) - FILE* popen (const char*, const char*); -A (fclose) A (freopen, 3) - FILE* tmpfile (void); +/* Declare functions with the minimum attributes malloc how they're + likely going to be declared in . */ + int fclose (FILE*); +A (fclose) FILE* fdopen (int); +A (fclose) FILE* fopen (const char*, const char*); +A (fclose) FILE* fmemopen(void *, size_t, const char *); +A (fclose) FILE* freopen (const char*, const char*, FILE*); +A (freopen, 3) FILE* freopen (const char*, const char*, FILE*); +A (fclose) FILE* tmpfile (void); -void sink (FILE*); +A (fclose) FILE* open_memstream (char**, size_t*); +A (fclose) FILE* open_wmemstream (char**, size_t*); + + int pclose (FILE*); +A (pclose) FILE* popen (const char*, const char*); + void release (void*); +A (release) FILE* acquire (void); + +void sink (FILE*); - void release (void*); -A (release) FILE* acquire (void); void nowarn_fdopen (void) { @@ -68,18 +67,18 @@ void nowarn_fdopen (void) void warn_fdopen (void) { { - FILE *q = fdopen (0); // { dg-message "returned from a call to 'fdopen'" "note" } + FILE *q = fdopen (0); // { dg-message "returned from 'fdopen'" "note" } sink (q); release (q); // { dg-warning "'release' called on pointer returned from a mismatched allocation function" } } { - FILE *q = fdopen (0); // { dg-message "returned from a call to 'fdopen'" "note" } + FILE *q = fdopen (0); // { dg-message "returned from 'fdopen'" "note" } sink (q); free (q); // { dg-warning "'free' called on pointer returned from a mismatched allocation function" } } { - FILE *q = fdopen (0); // { dg-message "returned from a call to 'fdopen'" "note" } + FILE *q = fdopen (0); // { dg-message "returned from 'fdopen'" "note" } sink (q); q = realloc (q, 7); // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" } sink (q); @@ -132,43 +131,104 @@ void warn_fopen (void) } -void test_popen (void) +void test_freopen (FILE *p[]) { { - FILE *p = popen ("1", "r"); + FILE *q = freopen ("1", "r", p[0]); + sink (q); + fclose (q); + } + { + FILE *q = freopen ("2", "r", p[1]); + sink (q); + q = freopen ("3", "r", q); + sink (q); + fclose (q); + } + + { + FILE *q; + q = freopen ("3", "r", p[2]); // { dg-message "returned from 'freopen'" } + sink (q); + q = realloc (q, 7); // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" } + sink (q); + } +} + + +void test_tmpfile (void) +{ + { + FILE *p = tmpfile (); sink (p); - pclose (p); + fclose (p); } { - FILE *p; - p = popen ("2", "r"); // { dg-message "returned from a call to 'popen'" "note" } + FILE *p = tmpfile (); sink (p); - fclose (p); // { dg-warning "'fclose' called on pointer returned from a mismatched allocation function" } + p = freopen ("1", "r", p); + sink (p); + fclose (p); } { - /* freopen() can close a stream open by popen() but pclose() can't - close the stream returned from freopen(). */ - FILE *p = popen ("2", "r"); + FILE *p = tmpfile (); // { dg-message "returned from 'tmpfile'" "note" } sink (p); - p = freopen ("3", "r", p); // { dg-message "returned from a call to 'freopen'" "note" } + pclose (p); // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" } + } +} + + +void test_open_memstream (char **bufp, size_t *sizep) +{ + { + FILE *p = open_memstream (bufp, sizep); + sink (p); + fclose (p); + } + + { + FILE *p = open_memstream (bufp, sizep); + sink (p); + p = freopen ("1", "r", p); + sink (p); + fclose (p); + } + + { + FILE *p; + p = open_memstream (bufp, sizep); // { dg-message "returned from 'open_memstream'" "note" } sink (p); pclose (p); // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" } } + + { + FILE *p; + p = open_memstream (bufp, sizep); // { dg-message "returned from 'open_memstream'" "note" } + sink (p); + free (p); // { dg-warning "'free' called on pointer returned from a mismatched allocation function" } + } + + { + FILE *p; + p = open_memstream (bufp, sizep); // { dg-message "returned from 'open_memstream'" "note" } + sink (p); + release (p); // { dg-warning "'release' called on pointer returned from a mismatched allocation function" } + } } -void test_tmpfile (void) +void test_open_wmemstream (char **bufp, size_t *sizep) { { - FILE *p = tmpfile (); + FILE *p = open_wmemstream (bufp, sizep); sink (p); fclose (p); } { - FILE *p = tmpfile (); + FILE *p = open_wmemstream (bufp, sizep); sink (p); p = freopen ("1", "r", p); sink (p); @@ -176,29 +236,44 @@ void test_tmpfile (void) } { - FILE *p = tmpfile (); // { dg-message "returned from a call to 'tmpfile'" "note" } + FILE *p; + p = open_wmemstream (bufp, sizep); // { dg-message "returned from 'open_wmemstream'" "note" } sink (p); pclose (p); // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" } } + + { + FILE *p; + p = open_wmemstream (bufp, sizep); // { dg-message "returned from 'open_wmemstream'" "note" } + sink (p); + free (p); // { dg-warning "'free' called on pointer returned from a mismatched allocation function" } + } + + { + FILE *p; + p = open_wmemstream (bufp, sizep); // { dg-message "returned from 'open_wmemstream'" "note" } + sink (p); + release (p); // { dg-warning "'release' called on pointer returned from a mismatched allocation function" } + } } void warn_malloc (void) { { - FILE *p = malloc (100); // { dg-message "returned from a call to 'malloc'" "note" } + FILE *p = malloc (100); // { dg-message "returned from 'malloc'" "note" } sink (p); fclose (p); // { dg-warning "'fclose' called on pointer returned from a mismatched allocation function" } } { - FILE *p = malloc (100); // { dg-message "returned from a call to 'malloc'" "note" } + FILE *p = malloc (100); // { dg-message "returned from 'malloc'" "note" } sink (p); p = freopen ("1", "r", p);// { dg-warning "'freopen' called on pointer returned from a mismatched allocation function" } } { - FILE *p = malloc (100); // { dg-message "returned from a call to 'malloc'" "note" } + FILE *p = malloc (100); // { dg-message "returned from 'malloc'" "note" } sink (p); pclose (p); // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" } } @@ -219,32 +294,32 @@ void test_acquire (void) } { - FILE *p = acquire (); // { dg-message "returned from a call to 'acquire'" "note" } + FILE *p = acquire (); // { dg-message "returned from 'acquire'" "note" } sink (p); fclose (p); // { dg-warning "'fclose' called on pointer returned from a mismatched allocation function" } } { - FILE *p = acquire (); // { dg-message "returned from a call to 'acquire'" "note" } + FILE *p = acquire (); // { dg-message "returned from 'acquire'" "note" } sink (p); pclose (p); // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" } } { - FILE *p = acquire (); // { dg-message "returned from a call to 'acquire'" "note" } + FILE *p = acquire (); // { dg-message "returned from 'acquire'" "note" } sink (p); p = freopen ("1", "r", p); // { dg-warning "'freopen' called on pointer returned from a mismatched allocation function" } sink (p); } { - FILE *p = acquire (); // { dg-message "returned from a call to 'acquire'" "note" } + FILE *p = acquire (); // { dg-message "returned from 'acquire'" "note" } sink (p); free (p); // { dg-warning "'free' called on pointer returned from a mismatched allocation function" } } { - FILE *p = acquire (); // { dg-message "returned from a call to 'acquire'" "note" } + FILE *p = acquire (); // { dg-message "returned from 'acquire'" "note" } sink (p); p = realloc (p, 123); // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" } sink (p); diff --git a/gcc/tree-ssa-dce.c b/gcc/tree-ssa-dce.c index 9fb156c120d..5ec872967b7 100644 --- a/gcc/tree-ssa-dce.c +++ b/gcc/tree-ssa-dce.c @@ -656,67 +656,7 @@ valid_new_delete_pair_p (gimple *new_call, gimple *delete_call) { tree new_asm = DECL_ASSEMBLER_NAME (gimple_call_fndecl (new_call)); tree delete_asm = DECL_ASSEMBLER_NAME (gimple_call_fndecl (delete_call)); - const char *new_name = IDENTIFIER_POINTER (new_asm); - const char *delete_name = IDENTIFIER_POINTER (delete_asm); - unsigned int new_len = IDENTIFIER_LENGTH (new_asm); - unsigned int delete_len = IDENTIFIER_LENGTH (delete_asm); - - if (new_len < 5 || delete_len < 6) - return false; - if (new_name[0] == '_') - ++new_name, --new_len; - if (new_name[0] == '_') - ++new_name, --new_len; - if (delete_name[0] == '_') - ++delete_name, --delete_len; - if (delete_name[0] == '_') - ++delete_name, --delete_len; - if (new_len < 4 || delete_len < 5) - return false; - /* *_len is now just the length after initial underscores. */ - if (new_name[0] != 'Z' || new_name[1] != 'n') - return false; - if (delete_name[0] != 'Z' || delete_name[1] != 'd') - return false; - /* _Znw must match _Zdl, _Zna must match _Zda. */ - if ((new_name[2] != 'w' || delete_name[2] != 'l') - && (new_name[2] != 'a' || delete_name[2] != 'a')) - return false; - /* 'j', 'm' and 'y' correspond to size_t. */ - if (new_name[3] != 'j' && new_name[3] != 'm' && new_name[3] != 'y') - return false; - if (delete_name[3] != 'P' || delete_name[4] != 'v') - return false; - if (new_len == 4 - || (new_len == 18 && !memcmp (new_name + 4, "RKSt9nothrow_t", 14))) - { - /* _ZnXY or _ZnXYRKSt9nothrow_t matches - _ZdXPv, _ZdXPvY and _ZdXPvRKSt9nothrow_t. */ - if (delete_len == 5) - return true; - if (delete_len == 6 && delete_name[5] == new_name[3]) - return true; - if (delete_len == 19 && !memcmp (delete_name + 5, "RKSt9nothrow_t", 14)) - return true; - } - else if ((new_len == 19 && !memcmp (new_name + 4, "St11align_val_t", 15)) - || (new_len == 33 - && !memcmp (new_name + 4, "St11align_val_tRKSt9nothrow_t", 29))) - { - /* _ZnXYSt11align_val_t or _ZnXYSt11align_val_tRKSt9nothrow_t matches - _ZdXPvSt11align_val_t or _ZdXPvYSt11align_val_t or or - _ZdXPvSt11align_val_tRKSt9nothrow_t. */ - if (delete_len == 20 && !memcmp (delete_name + 5, "St11align_val_t", 15)) - return true; - if (delete_len == 21 - && delete_name[5] == new_name[3] - && !memcmp (delete_name + 6, "St11align_val_t", 15)) - return true; - if (delete_len == 34 - && !memcmp (delete_name + 5, "St11align_val_tRKSt9nothrow_t", 29)) - return true; - } - return false; + return valid_new_delete_pair_p (new_asm, delete_asm); } /* Propagate necessity using the operands of necessary statements. diff --git a/gcc/tree.c b/gcc/tree.c index 9b2ecb34256..5fd9da3ab96 100644 --- a/gcc/tree.c +++ b/gcc/tree.c @@ -12610,8 +12610,40 @@ tree_nonartificial_location (tree exp) return EXPR_LOCATION (exp); } +/* Return the location into which EXP has been inlined. Analogous + to tree_nonartificial_location() above but not limited to artificial + functions declared inline. If SYSTEM_HEADER is true, return + the macro expansion point of the location if it's in a system header */ -/* These are the hash table functions for the hash table of OPTIMIZATION_NODEq +location_t +tree_inlined_location (tree exp, bool system_header /* = true */) +{ + location_t loc = UNKNOWN_LOCATION; + + tree block = TREE_BLOCK (exp); + + while (block && TREE_CODE (block) == BLOCK + && BLOCK_ABSTRACT_ORIGIN (block)) + { + tree ao = BLOCK_ABSTRACT_ORIGIN (block); + if (TREE_CODE (ao) == FUNCTION_DECL) + loc = BLOCK_SOURCE_LOCATION (block); + else if (TREE_CODE (ao) != BLOCK) + break; + + block = BLOCK_SUPERCONTEXT (block); + } + + if (loc == UNKNOWN_LOCATION) + loc = EXPR_LOCATION (exp); + + if (system_header) + return expansion_point_location_if_in_system_header (loc); + + return loc; +} + +/* These are the hash table functions for the hash table of OPTIMIZATION_NODE nodes. */ /* Return the hash code X, an OPTIMIZATION_NODE or TARGET_OPTION code. */ @@ -15386,6 +15418,75 @@ verify_type_context (location_t loc, type_context_kind context, || targetm.verify_type_context (loc, context, type, silent_p)); } +/* Return that NEW_ASM and DELETE_ASM name a valid pair of new and + delete operators. */ + +bool +valid_new_delete_pair_p (tree new_asm, tree delete_asm) +{ + const char *new_name = IDENTIFIER_POINTER (new_asm); + const char *delete_name = IDENTIFIER_POINTER (delete_asm); + unsigned int new_len = IDENTIFIER_LENGTH (new_asm); + unsigned int delete_len = IDENTIFIER_LENGTH (delete_asm); + + if (new_len < 5 || delete_len < 6) + return false; + if (new_name[0] == '_') + ++new_name, --new_len; + if (new_name[0] == '_') + ++new_name, --new_len; + if (delete_name[0] == '_') + ++delete_name, --delete_len; + if (delete_name[0] == '_') + ++delete_name, --delete_len; + if (new_len < 4 || delete_len < 5) + return false; + /* *_len is now just the length after initial underscores. */ + if (new_name[0] != 'Z' || new_name[1] != 'n') + return false; + if (delete_name[0] != 'Z' || delete_name[1] != 'd') + return false; + /* _Znw must match _Zdl, _Zna must match _Zda. */ + if ((new_name[2] != 'w' || delete_name[2] != 'l') + && (new_name[2] != 'a' || delete_name[2] != 'a')) + return false; + /* 'j', 'm' and 'y' correspond to size_t. */ + if (new_name[3] != 'j' && new_name[3] != 'm' && new_name[3] != 'y') + return false; + if (delete_name[3] != 'P' || delete_name[4] != 'v') + return false; + if (new_len == 4 + || (new_len == 18 && !memcmp (new_name + 4, "RKSt9nothrow_t", 14))) + { + /* _ZnXY or _ZnXYRKSt9nothrow_t matches + _ZdXPv, _ZdXPvY and _ZdXPvRKSt9nothrow_t. */ + if (delete_len == 5) + return true; + if (delete_len == 6 && delete_name[5] == new_name[3]) + return true; + if (delete_len == 19 && !memcmp (delete_name + 5, "RKSt9nothrow_t", 14)) + return true; + } + else if ((new_len == 19 && !memcmp (new_name + 4, "St11align_val_t", 15)) + || (new_len == 33 + && !memcmp (new_name + 4, "St11align_val_tRKSt9nothrow_t", 29))) + { + /* _ZnXYSt11align_val_t or _ZnXYSt11align_val_tRKSt9nothrow_t matches + _ZdXPvSt11align_val_t or _ZdXPvYSt11align_val_t or or + _ZdXPvSt11align_val_tRKSt9nothrow_t. */ + if (delete_len == 20 && !memcmp (delete_name + 5, "St11align_val_t", 15)) + return true; + if (delete_len == 21 + && delete_name[5] == new_name[3] + && !memcmp (delete_name + 6, "St11align_val_t", 15)) + return true; + if (delete_len == 34 + && !memcmp (delete_name + 5, "St11align_val_tRKSt9nothrow_t", 29)) + return true; + } + return false; +} + #if CHECKING_P namespace selftest { diff --git a/gcc/tree.h b/gcc/tree.h index b44039f61ff..d366ffd8a51 100644 --- a/gcc/tree.h +++ b/gcc/tree.h @@ -5278,6 +5278,7 @@ extern tree tree_block (tree); extern void tree_set_block (tree, tree); extern location_t *block_nonartificial_location (tree); extern location_t tree_nonartificial_location (tree); +extern location_t tree_inlined_location (tree, bool = true); extern tree block_ultimate_origin (const_tree); extern tree get_binfo_at_offset (tree, poly_int64, tree); extern bool virtual_method_call_p (const_tree, bool = false); @@ -5355,6 +5356,7 @@ extern bool gimple_canonical_types_compatible_p (const_tree, const_tree, extern bool type_with_interoperable_signedness (const_tree); extern bitmap get_nonnull_args (const_tree); extern int get_range_pos_neg (tree); +extern bool valid_new_delete_pair_p (tree, tree); /* Return simplified tree code of type that is used for canonical type merging. */ diff --git a/libstdc++-v3/testsuite/ext/vstring/requirements/exception/basic.cc b/libstdc++-v3/testsuite/ext/vstring/requirements/exception/basic.cc index 1f4ba020b3b..5036be50724 100644 --- a/libstdc++-v3/testsuite/ext/vstring/requirements/exception/basic.cc +++ b/libstdc++-v3/testsuite/ext/vstring/requirements/exception/basic.cc @@ -48,3 +48,7 @@ int main() value(); return 0; } + +// The __versa_string destructor triggers a bogus -Wfree-nonheap-object +// due to pr54202. +// { dg-prune-output "\\\[-Wfree-nonheap-object" } diff --git a/libstdc++-v3/testsuite/ext/vstring/requirements/exception/propagation_consistent.cc b/libstdc++-v3/testsuite/ext/vstring/requirements/exception/propagation_consistent.cc index add7a922cdf..61e9aedb7e6 100644 --- a/libstdc++-v3/testsuite/ext/vstring/requirements/exception/propagation_consistent.cc +++ b/libstdc++-v3/testsuite/ext/vstring/requirements/exception/propagation_consistent.cc @@ -48,3 +48,7 @@ int main() value(); return 0; } + +// The __versa_string destructor triggers a bogus -Wfree-nonheap-object +// due to pr54202. +// { dg-prune-output "\\\[-Wfree-nonheap-object" }