From: Martin Sebor Date: Thu, 3 Dec 2020 22:41:25 +0000 (-0700) Subject: Add support for detecting mismatched allocation/deallocation calls. X-Git-Url: https://git.libre-soc.org/?a=commitdiff_plain;h=dce6c58db87ebf7f4477bd3126228e73e4eeee97;p=gcc.git Add support for detecting mismatched allocation/deallocation calls. PR c++/90629 - Support for -Wmismatched-new-delete PR middle-end/94527 - Add an __attribute__ that marks a function as freeing an object gcc/ChangeLog: PR c++/90629 PR middle-end/94527 * builtins.c (access_ref::access_ref): Initialize new member. (compute_objsize): Use access_ref::deref. Handle simple pointer assignment. (expand_builtin): Remove handling of the free built-in. (call_dealloc_argno): Same. (find_assignment_location): New function. (fndecl_alloc_p): Same. (gimple_call_alloc_p): Same. (call_dealloc_p): Same. (matching_alloc_calls_p): Same. (warn_dealloc_offset): Same. (maybe_emit_free_warning): Same. * builtins.h (struct access_ref): Declare new member. (maybe_emit_free_warning): Make extern. Make use of access_ref. Handle -Wmismatched-new-delete. * calls.c (initialize_argument_information): Call maybe_emit_free_warning. * doc/extend.texi (attribute malloc): Update. * doc/invoke.texi (-Wfree-nonheap-object): Expand documentation. (-Wmismatched-new-delete): Document new option. (-Wmismatched-dealloc): Document new option. gcc/c-family/ChangeLog: PR c++/90629 PR middle-end/94527 * c-attribs.c (handle_dealloc_attribute): New function. (handle_malloc_attribute): Handle argument forms of attribute. * c.opt (-Wmismatched-dealloc): New option. (-Wmismatched-new-delete): New option. gcc/testsuite/ChangeLog: PR c++/90629 PR middle-end/94527 * g++.dg/asan/asan_test.cc: Fix a bug. * g++.dg/warn/delete-array-1.C: Add expected warning. * g++.old-deja/g++.other/delete2.C: Add expected warning. * g++.dg/warn/Wfree-nonheap-object-2.C: New test. * g++.dg/warn/Wfree-nonheap-object.C: New test. * g++.dg/warn/Wmismatched-new-delete.C: New test. * g++.dg/warn/Wmismatched-dealloc-2.C: New test. * g++.dg/warn/Wmismatched-dealloc.C: New test. * gcc.dg/Wmismatched-dealloc.c: New test. * gcc.dg/analyzer/malloc-1.c: Prune out expected warning. * gcc.dg/attr-malloc.c: New test. * gcc.dg/free-1.c: Adjust text of expected warning. * gcc.dg/free-2.c: Same. * gcc.dg/torture/pr71816.c: Prune out expected warning. * gcc.dg/tree-ssa/pr19831-2.c: Add an expected warning. * gcc.dg/Wfree-nonheap-object-2.c: New test. * gcc.dg/Wfree-nonheap-object-3.c: New test. * gcc.dg/Wfree-nonheap-object.c: New test. libstdc++-v3/ChangeLog: * testsuite/ext/vstring/modifiers/clear/56166.cc: Suppress a false positive warning. --- diff --git a/gcc/builtins.c b/gcc/builtins.c index cd30de8bfb0..bd12659712f 100644 --- a/gcc/builtins.c +++ b/gcc/builtins.c @@ -73,6 +73,7 @@ along with GCC; see the file COPYING3. If not see #include "gomp-constants.h" #include "omp-general.h" #include "tree-dfa.h" +#include "gimple-iterator.h" #include "gimple-ssa.h" #include "tree-ssa-live.h" #include "tree-outof-ssa.h" @@ -182,7 +183,6 @@ static rtx expand_builtin_memory_chk (tree, rtx, machine_mode, enum built_in_function); static void maybe_emit_chk_warning (tree, enum built_in_function); static void maybe_emit_sprintf_chk_warning (tree, enum built_in_function); -static void maybe_emit_free_warning (tree); static tree fold_builtin_object_size (tree, tree); static bool check_read_access (tree, tree, tree = NULL_TREE, int = 1); static bool compute_objsize_r (tree, int, access_ref *, ssa_name_limit_t &, @@ -201,8 +201,8 @@ static void expand_builtin_sync_synchronize (void); access_ref::access_ref (tree bound /* = NULL_TREE */, bool minaccess /* = false */) -: ref (), eval ([](tree x){ return x; }), trail1special (true), base0 (true), - parmarray () +: ref (), eval ([](tree x){ return x; }), deref (), trail1special (true), + base0 (true), parmarray () { /* Set to valid. */ offrng[0] = offrng[1] = 0; @@ -5313,7 +5313,10 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref, const bool addr = TREE_CODE (ptr) == ADDR_EXPR; if (addr) - ptr = TREE_OPERAND (ptr, 0); + { + --pref->deref; + ptr = TREE_OPERAND (ptr, 0); + } if (DECL_P (ptr)) { @@ -5421,6 +5424,8 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref, if (code == ARRAY_REF || code == MEM_REF) { + ++pref->deref; + tree ref = TREE_OPERAND (ptr, 0); tree reftype = TREE_TYPE (ref); if (!addr && code == ARRAY_REF @@ -5544,6 +5549,10 @@ compute_objsize_r (tree ptr, int ostype, access_ref *pref, if (!compute_objsize_r (ref, ostype, pref, snlim, qry)) return false; + /* Clear DEREF since the offset is being applied to the target + of the dereference. */ + pref->deref = 0; + offset_int orng[2]; tree off = pref->eval (TREE_OPERAND (ptr, 1)); if (get_offset_range (off, NULL, orng, rvals)) @@ -10630,11 +10639,6 @@ expand_builtin (tree exp, rtx target, rtx subtarget, machine_mode mode, maybe_emit_sprintf_chk_warning (exp, fcode); break; - case BUILT_IN_FREE: - if (warn_free_nonheap_object) - maybe_emit_free_warning (exp); - break; - case BUILT_IN_THREAD_POINTER: return expand_builtin_thread_pointer (exp, target); @@ -12944,30 +12948,403 @@ maybe_emit_sprintf_chk_warning (tree exp, enum built_in_function fcode) access_write_only); } -/* Emit warning if a free is called with address of a variable. */ +/* Return true if STMT is a call to an allocation function. Unless + ALL_ALLOC is set, consider only functions that return dynmamically + allocated objects. Otherwise return true even for all forms of + alloca (including VLA). */ -static void +static bool +fndecl_alloc_p (tree fndecl, bool all_alloc) +{ + if (!fndecl) + return false; + + /* A call to operator new isn't recognized as one to a built-in. */ + if (DECL_IS_OPERATOR_NEW_P (fndecl)) + return true; + + if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)) + { + switch (DECL_FUNCTION_CODE (fndecl)) + { + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: + return all_alloc; + case BUILT_IN_CALLOC: + case BUILT_IN_MALLOC: + case BUILT_IN_REALLOC: + case BUILT_IN_STRDUP: + case BUILT_IN_STRNDUP: + return true; + default: + break; + } + } + + /* A function is considered an allocation function if it's declared + with attribute malloc with an argument naming its associated + deallocation function. */ + tree attrs = DECL_ATTRIBUTES (fndecl); + if (!attrs) + return false; + + for (tree allocs = attrs; + (allocs = lookup_attribute ("malloc", allocs)); + allocs = TREE_CHAIN (allocs)) + { + tree args = TREE_VALUE (allocs); + if (!args) + continue; + + if (TREE_VALUE (args)) + return true; + } + + return false; +} + +/* Return true if STMT is a call to an allocation function. A wrapper + around fndecl_alloc_p. */ + +static bool +gimple_call_alloc_p (gimple *stmt, bool all_alloc = false) +{ + return fndecl_alloc_p (gimple_call_fndecl (stmt), all_alloc); +} + +/* Return the zero-based number corresponding to the argument being + deallocated if STMT is a call to a deallocation function or UINT_MAX + if it isn't. */ + +static unsigned +call_dealloc_argno (tree exp) +{ + tree fndecl = get_callee_fndecl (exp); + if (!fndecl) + return UINT_MAX; + + /* A call to operator delete isn't recognized as one to a built-in. */ + if (DECL_IS_OPERATOR_DELETE_P (fndecl)) + return 0; + + /* TODO: Handle user-defined functions with attribute malloc? Handle + known non-built-ins like fopen? */ + if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)) + { + switch (DECL_FUNCTION_CODE (fndecl)) + { + case BUILT_IN_FREE: + case BUILT_IN_REALLOC: + return 0; + default: + break; + } + return UINT_MAX; + } + + tree attrs = DECL_ATTRIBUTES (fndecl); + if (!attrs) + return UINT_MAX; + + for (tree atfree = attrs; + (atfree = lookup_attribute ("*dealloc", atfree)); + atfree = TREE_CHAIN (atfree)) + { + tree alloc = TREE_VALUE (atfree); + if (!alloc) + continue; + + tree pos = TREE_CHAIN (alloc); + if (!pos) + return 0; + + pos = TREE_VALUE (pos); + return TREE_INT_CST_LOW (pos) - 1; + } + + return UINT_MAX; +} + +/* Return true if STMT is a call to a deallocation function. */ + +static inline bool +call_dealloc_p (tree exp) +{ + return call_dealloc_argno (exp) != UINT_MAX; +} + +/* ALLOC_DECL and DEALLOC_DECL are pair of allocation and deallocation + functions. Return true if the latter is suitable to deallocate objects + allocated by calls to the former. */ + +static bool +matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl) +{ + 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 false for deallocation functions that are known not + to match. */ + if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE) + || fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC)) + return false; + /* Otherwise proceed below to check the deallocation function's + "*dealloc" attributes to look for one that mentions this operator + new. */ + } + else if (fndecl_built_in_p (alloc_decl, BUILT_IN_NORMAL)) + { + switch (DECL_FUNCTION_CODE (alloc_decl)) + { + case BUILT_IN_ALLOCA: + case BUILT_IN_ALLOCA_WITH_ALIGN: + return false; + + case BUILT_IN_CALLOC: + case BUILT_IN_MALLOC: + case BUILT_IN_REALLOC: + case BUILT_IN_STRDUP: + case BUILT_IN_STRNDUP: + if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl)) + return false; + + if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE) + || fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC)) + return true; + break; + + default: + break; + } + } + + /* 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; + + for (tree funs = attrs; + (funs = lookup_attribute ("*dealloc", funs)); + funs = TREE_CHAIN (funs)) + { + tree args = TREE_VALUE (funs); + if (!args) + continue; + + tree fname = TREE_VALUE (args); + if (!fname) + continue; + + if (fname == DECL_NAME (alloc_decl)) + return true; + } + + return false; +} + +/* Return true if DEALLOC_DECL is a function suitable to deallocate + objectes allocated by the ALLOC call. */ + +static bool +matching_alloc_calls_p (gimple *alloc, tree dealloc_decl) +{ + tree alloc_decl = gimple_call_fndecl (alloc); + if (!alloc_decl) + return true; + + 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. */ + +static bool +warn_dealloc_offset (location_t loc, tree exp, tree fndecl, + const access_ref &aref) +{ + char offstr[80]; + offstr[0] = '\0'; + if (wi::fits_shwi_p (aref.offrng[0])) + { + if (aref.offrng[0] == aref.offrng[1] + || !wi::fits_shwi_p (aref.offrng[1])) + sprintf (offstr, " %lli", + (long long)aref.offrng[0].to_shwi ()); + else + sprintf (offstr, " [%lli, %lli]", + (long long)aref.offrng[0].to_shwi (), + (long long)aref.offrng[1].to_shwi ()); + } + + if (!warning_at (loc, OPT_Wfree_nonheap_object, + "%K%qD called on pointer %qE with nonzero offset%s", + exp, fndecl, aref.ref, offstr)) + return false; + + if (DECL_P (aref.ref)) + inform (DECL_SOURCE_LOCATION (aref.ref), "declared here"); + else 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); + inform (gimple_location (def_stmt), + "returned from a call to %qD", alloc_decl); + } + } + + return true; +} + +/* Issue a warning if a deallocation function such as free, realloc, + or C++ operator delete is called with an argument not returned by + a matching allocation function such as malloc or the corresponding + form of C++ operatorn new. */ + +void maybe_emit_free_warning (tree exp) { - if (call_expr_nargs (exp) != 1) + tree fndecl = get_callee_fndecl (exp); + if (!fndecl) return; - tree arg = CALL_EXPR_ARG (exp, 0); + unsigned argno = call_dealloc_argno (exp); + if ((unsigned) call_expr_nargs (exp) <= argno) + return; - STRIP_NOPS (arg); - if (TREE_CODE (arg) != ADDR_EXPR) + tree ptr = CALL_EXPR_ARG (exp, argno); + if (integer_zerop (ptr)) return; - arg = get_base_address (TREE_OPERAND (arg, 0)); - if (arg == NULL || INDIRECT_REF_P (arg) || TREE_CODE (arg) == MEM_REF) + access_ref aref; + if (!compute_objsize (ptr, 0, &aref)) return; - if (SSA_VAR_P (arg)) - warning_at (tree_nonartificial_location (exp), OPT_Wfree_nonheap_object, - "%Kattempt to free a non-heap object %qD", exp, arg); - else - warning_at (tree_nonartificial_location (exp), OPT_Wfree_nonheap_object, - "%Kattempt to free a non-heap object", exp); + tree ref = aref.ref; + if (integer_zerop (ref)) + return; + + tree dealloc_decl = get_callee_fndecl (exp); + location_t loc = tree_nonartificial_location (exp); + loc = expansion_point_location_if_in_system_header (loc); + + if (DECL_P (ref) || EXPR_P (ref)) + { + /* Diagnose freeing a declared object. */ + if (aref.ref_declared () + && warning_at (loc, OPT_Wfree_nonheap_object, + "%K%qD called on unallocated object %qD", + exp, dealloc_decl, ref)) + { + inform (DECL_SOURCE_LOCATION (ref), + "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)) + return; + } + else if (CONSTANT_CLASS_P (ref)) + { + if (warning_at (loc, OPT_Wfree_nonheap_object, + "%K%qD called on a pointer to an unallocated " + "object %qE", exp, dealloc_decl, ref)) + { + if (TREE_CODE (ptr) == SSA_NAME) + { + gimple *def_stmt = SSA_NAME_DEF_STMT (ptr); + if (is_gimple_assign (def_stmt)) + { + location_t loc = gimple_location (def_stmt); + inform (loc, "assigned here"); + } + } + return; + } + } + else if (TREE_CODE (ref) == SSA_NAME) + { + /* Also warn if the pointer argument refers to the result + of an allocation call like alloca or VLA. */ + gimple *def_stmt = SSA_NAME_DEF_STMT (ref); + if (is_gimple_call (def_stmt)) + { + bool warned = false; + if (gimple_call_alloc_p (def_stmt)) + { + 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)) + return; + } + else + { + tree alloc_decl = gimple_call_fndecl (def_stmt); + int opt = (DECL_IS_OPERATOR_NEW_P (alloc_decl) + || DECL_IS_OPERATOR_DELETE_P (dealloc_decl) + ? OPT_Wmismatched_new_delete + : OPT_Wmismatched_dealloc); + warned = warning_at (loc, opt, + "%K%qD called on pointer returned " + "from a mismatched allocation " + "function", exp, dealloc_decl); + } + } + else if (gimple_call_builtin_p (def_stmt, BUILT_IN_ALLOCA) + || gimple_call_builtin_p (def_stmt, + BUILT_IN_ALLOCA_WITH_ALIGN)) + warned = warning_at (loc, OPT_Wfree_nonheap_object, + "%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)) + return; + + if (warned) + { + tree fndecl = gimple_call_fndecl (def_stmt); + inform (gimple_location (def_stmt), + "returned from a call to %qD", fndecl); + return; + } + } + else if (gimple_nop_p (def_stmt)) + { + ref = SSA_NAME_VAR (ref); + /* Diagnose freeing a pointer that includes a positive offset. */ + if (TREE_CODE (ref) == PARM_DECL + && !aref.deref + && aref.sizrng[0] != aref.sizrng[1] + && aref.offrng[0] > 0 && aref.offrng[1] > 0 + && warn_dealloc_offset (loc, exp, dealloc_decl, aref)) + return; + } + } } /* Fold a call to __builtin_object_size with arguments PTR and OST, diff --git a/gcc/builtins.h b/gcc/builtins.h index 09379e85d45..642923281c1 100644 --- a/gcc/builtins.h +++ b/gcc/builtins.h @@ -220,6 +220,12 @@ struct access_ref argument to the minimum. */ offset_int size_remaining (offset_int * = NULL) const; + /* Return true if *THIS is an access to a declared object. */ + bool ref_declared () const + { + return DECL_P (ref) && base0 && deref < 1; + } + /* Set the size range to the maximum. */ void set_max_size_range () { @@ -261,6 +267,9 @@ struct access_ref /* Used to fold integer expressions when called from front ends. */ tree (*eval)(tree); + /* Positive when REF is dereferenced, negative when its address is + taken. */ + int deref; /* Set if trailing one-element arrays should be treated as flexible array members. */ bool trail1special; @@ -350,5 +359,6 @@ extern tree compute_objsize (tree, int, tree * = NULL, tree * = NULL, range_query * = NULL); extern bool check_access (tree, tree, tree, tree, tree, access_mode, const access_data * = NULL); +extern void maybe_emit_free_warning (tree); #endif /* GCC_BUILTINS_H */ diff --git a/gcc/c-family/c-attribs.c b/gcc/c-family/c-attribs.c index 99b663085f2..f7dad7a91d7 100644 --- a/gcc/c-family/c-attribs.c +++ b/gcc/c-family/c-attribs.c @@ -113,6 +113,7 @@ static tree handle_no_instrument_function_attribute (tree *, tree, static tree handle_no_profile_instrument_function_attribute (tree *, tree, tree, int, bool *); static tree handle_malloc_attribute (tree *, tree, tree, int, bool *); +static tree handle_dealloc_attribute (tree *, tree, tree, int, bool *); static tree handle_returns_twice_attribute (tree *, tree, tree, int, bool *); static tree handle_no_limit_stack_attribute (tree *, tree, tree, int, bool *); @@ -364,7 +365,7 @@ const struct attribute_spec c_common_attribute_table[] = { "no_profile_instrument_function", 0, 0, true, false, false, false, handle_no_profile_instrument_function_attribute, NULL }, - { "malloc", 0, 0, true, false, false, false, + { "malloc", 0, 2, true, false, false, false, handle_malloc_attribute, attr_alloc_exclusions }, { "returns_twice", 0, 0, true, false, false, false, handle_returns_twice_attribute, @@ -524,6 +525,8 @@ const struct attribute_spec c_common_attribute_table[] = handle_objc_root_class_attribute, NULL }, { "objc_nullability", 1, 1, true, false, false, false, handle_objc_nullability_attribute, NULL }, + { "*dealloc", 1, 2, true, false, false, false, + handle_dealloc_attribute, NULL }, { NULL, 0, 0, false, false, false, false, NULL, NULL } }; @@ -3127,20 +3130,179 @@ handle_no_profile_instrument_function_attribute (tree *node, tree name, tree, return NULL_TREE; } -/* Handle a "malloc" attribute; arguments as in - struct attribute_spec.handler. */ +/* Handle the "malloc" attribute. */ static tree -handle_malloc_attribute (tree *node, tree name, tree ARG_UNUSED (args), +handle_malloc_attribute (tree *node, tree name, tree args, int ARG_UNUSED (flags), bool *no_add_attrs) { - if (TREE_CODE (*node) == FUNCTION_DECL - && POINTER_TYPE_P (TREE_TYPE (TREE_TYPE (*node)))) - DECL_IS_MALLOC (*node) = 1; - else + tree fndecl = *node; + + if (TREE_CODE (*node) != FUNCTION_DECL) + { + warning (OPT_Wattributes, "%qE attribute ignored; valid only " + "for functions", + name); + *no_add_attrs = true; + return NULL_TREE; + } + + tree rettype = TREE_TYPE (TREE_TYPE (*node)); + if (!POINTER_TYPE_P (rettype)) + { + warning (OPT_Wattributes, "%qE attribute ignored on functions " + "returning %qT; valid only for pointer return types", + name, rettype); + *no_add_attrs = true; + return NULL_TREE; + } + + if (!args) + { + /* Only the form of the attribute with no arguments declares + a function malloc-like. */ + DECL_IS_MALLOC (*node) = 1; + return NULL_TREE; + } + + tree dealloc = TREE_VALUE (args); + if (error_operand_p (dealloc)) + { + /* If the argument is in error it will have already been diagnosed. + Avoid issuing redundant errors here. */ + *no_add_attrs = true; + 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); + + if (TREE_CODE (dealloc) != FUNCTION_DECL) + { + if (TREE_CODE (dealloc) == OVERLOAD) + { + /* Handle specially the common case of specifying one of a number + of overloads, such as operator delete. */ + error ("%qE attribute argument 1 is ambiguous", name); + inform (input_location, + "use a cast to the expected type to disambiguate"); + *no_add_attrs = true; + return NULL_TREE; + } + + error ("%qE attribute argument 1 does not name a function", name); + if (DECL_P (dealloc)) + inform (DECL_SOURCE_LOCATION (dealloc), + "argument references a symbol declared here"); + *no_add_attrs = true; + return NULL_TREE; + } + + /* Mentioning the deallocation function qualifies as its use. */ + TREE_USED (dealloc) = 1; + + tree fntype = TREE_TYPE (dealloc); + tree argpos = TREE_CHAIN (args) ? TREE_VALUE (TREE_CHAIN (args)) : NULL_TREE; + if (!argpos) + { + tree argtypes = TYPE_ARG_TYPES (fntype); + if (!argtypes) + { + /* Reject functions without a prototype. */ + error ("%qE attribute argument 1 must take a pointer " + "type as its first argument", name); + inform (DECL_SOURCE_LOCATION (dealloc), + "refernced symbol declared here" ); + *no_add_attrs = true; + return NULL_TREE; + } + + tree argtype = TREE_VALUE (argtypes); + if (TREE_CODE (argtype) != POINTER_TYPE) + { + /* Reject functions that don't take a pointer as their first + argument. */ + error ("%qE attribute argument 1 must take a pointer type " + "as its first argument; have %qT", name, argtype); + inform (DECL_SOURCE_LOCATION (dealloc), + "referenced symbol declared here" ); + *no_add_attrs = true; + 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); + return NULL_TREE; + } + + /* Validate the positional argument. */ + argpos = positional_argument (fntype, name, argpos, POINTER_TYPE); + if (!argpos) { - warning (OPT_Wattributes, "%qE attribute ignored", name); *no_add_attrs = true; + 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); + return NULL_TREE; +} + +/* Handle the internal "*dealloc" attribute added for functions declared + with the one- and two-argument forms of attribute malloc. Add it + to *NODE unless it's already there with the same arguments. */ + +static tree +handle_dealloc_attribute (tree *node, tree name, tree args, int, + bool *no_add_attrs) +{ + tree fndecl = *node; + + tree attrs = DECL_ATTRIBUTES (fndecl); + if (!attrs) + return NULL_TREE; + + tree arg_fname = TREE_VALUE (args); + args = TREE_CHAIN (args); + tree arg_pos = args ? TREE_VALUE (args) : NULL_TREE; + + gcc_checking_assert (TREE_CODE (arg_fname) == IDENTIFIER_NODE); + + const char* const namestr = IDENTIFIER_POINTER (name); + for (tree at = attrs; (at = lookup_attribute (namestr, at)); + at = TREE_CHAIN (at)) + { + tree alloc = TREE_VALUE (at); + if (!alloc) + continue; + + tree pos = TREE_CHAIN (alloc); + alloc = TREE_VALUE (alloc); + pos = pos ? TREE_VALUE (pos) : NULL_TREE; + gcc_checking_assert (TREE_CODE (alloc) == IDENTIFIER_NODE); + + if (alloc == arg_fname + && ((!pos && !arg_pos) + || (pos && arg_pos && 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 + being added. Return without adding another copy. */ + *no_add_attrs = true; + return NULL_TREE; + } } return NULL_TREE; diff --git a/gcc/c-family/c.opt b/gcc/c-family/c.opt index 059f6c38a40..79478285070 100644 --- a/gcc/c-family/c.opt +++ b/gcc/c-family/c.opt @@ -793,6 +793,16 @@ Wmisleading-indentation C C++ Common Var(warn_misleading_indentation) Warning LangEnabledBy(C C++,Wall) Warn when the indentation of the code does not reflect the block structure. +Wmismatched-dealloc +C ObjC C++ ObjC++ Var(warn_mismatched_alloc) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall) +Warn for deallocation calls with arguments returned from mismatched allocation +functions. + +Wmismatched-new-delete +C++ ObjC++ Var(warn_mismatched_new_delete) Warning LangEnabledBy(C ObjC C++ ObjC++,Wall) +Warn for mismatches between calls to operator new or delete and the corrsponding +call to the allocation or deallocation function. + Wmismatched-tags C++ ObjC++ Var(warn_mismatched_tags) Warning Warn when a class is redeclared or referenced using a mismatched class-key. diff --git a/gcc/calls.c b/gcc/calls.c index a93d4bf0787..4114bf5e5b2 100644 --- a/gcc/calls.c +++ b/gcc/calls.c @@ -2623,6 +2623,10 @@ initialize_argument_information (int num_actuals ATTRIBUTE_UNUSED, /* Check attribute access arguments. */ maybe_warn_rdwr_sizes (&rdwr_idx, fndecl, fntype, exp); + + /* Check calls to operator new for mismatched forms and attempts + to deallocate unallocated objects. */ + maybe_emit_free_warning (exp); } /* Update ARGS_SIZE to contain the total size for the argument block. diff --git a/gcc/doc/extend.texi b/gcc/doc/extend.texi index 195bb21068a..435761572cd 100644 --- a/gcc/doc/extend.texi +++ b/gcc/doc/extend.texi @@ -3233,20 +3233,63 @@ this reason the attribute is not allowed on types to annotate indirect calls. @item malloc +@item malloc (@var{deallocator}) +@item malloc (@var{deallocator}, @var{ptr-index}) @cindex @code{malloc} function attribute @cindex functions that behave like malloc -This tells the compiler that a function is @code{malloc}-like, i.e., -that the pointer @var{P} returned by the function cannot alias any +Attribute @code{malloc} indicates that a function is @code{malloc}-like, +i.e., that the pointer @var{P} returned by the function cannot alias any other pointer valid when the function returns, and moreover no pointers to valid objects occur in any storage addressed by @var{P}. -Using this attribute can improve optimization. Compiler predicts -that a function with the attribute returns non-null in most cases. -Functions like -@code{malloc} and @code{calloc} have this property because they return -a pointer to uninitialized or zeroed-out storage. However, functions -like @code{realloc} do not have this property, as they can return a -pointer to storage containing pointers. +Independently, the form of the attribute with one or two arguments +associates @code{deallocator} as a suitable deallocation function for +pointers returned from the @code{malloc}-like function. @var{ptr-index} +denotes the positional argument to which when the pointer is passed in +calls to @code{deallocator} has the effect of deallocating it. + +Using the attribute with no arguments is designed to improve optimization. +The compiler predicts that a function with the attribute returns non-null +in most cases. Functions like @code{malloc} and @code{calloc} have this +property because they return a pointer to uninitialized or zeroed-out +storage. However, functions like @code{realloc} do not have this property, +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. + +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. + +@smallexample +int fclose (FILE*); +FILE* freopen (const char*, const char*, FILE*); +int pclose (FILE*); + +__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3))) + FILE* fdopen (int); +__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3))) + FILE* fopen (const char*, const char*); +__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3))) + 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))) + FILE* popen (const char*, const char*); +__attribute__ ((malloc, malloc (fclose), malloc (freopen, 3))) + FILE* tmpfile (void); +@end smallexample @item no_icf @cindex @code{no_icf} function attribute diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi index 35cd3dcd317..615eae9a1c5 100644 --- a/gcc/doc/invoke.texi +++ b/gcc/doc/invoke.texi @@ -242,7 +242,8 @@ in the following sections. -Wno-deprecated-enum-enum-conversion -Wno-deprecated-enum-float-conversion @gol -Weffc++ -Wno-exceptions -Wextra-semi -Wno-inaccessible-base @gol -Wno-inherited-variadic-ctor -Wno-init-list-lifetime @gol --Wno-invalid-offsetof -Wno-literal-suffix -Wmismatched-tags @gol +-Wno-invalid-offsetof -Wno-literal-suffix @gol +-Wno-mismatched-new-delete -Wmismatched-tags @gol -Wmultiple-inheritance -Wnamespaces -Wnarrowing @gol -Wnoexcept -Wnoexcept-type -Wnon-virtual-dtor @gol -Wpessimizing-move -Wno-placement-new -Wplacement-new=@var{n} @gol @@ -3859,6 +3860,40 @@ The warning is inactive inside a system header file, such as the STL, so one can still use the STL. One may also instantiate or specialize templates. +@item -Wno-mismatched-new-delete @r{(C++ and Objective-C++ only)} +@opindex Wmismatched-new-delete +@opindex Wno-mismatched-new-delete +Warn for mismatches between calls to @code{operator new} or @code{operator +delete} and the corresponding call to the allocation or deallocation function. +This includes invocations of C++ @code{operator delete} with pointers +returned from either mismatched forms of @code{operator new}, or from other +functions that allocate objects for which the @code{operator delete} isn't +a suitable deallocator, as well as calls to other deallocation functions +with pointers returned from @code{operator new} for which the deallocation +function isn't suitable. + +For example, the @code{delete} expression in the function below is diagnosed +because it doesn't match the array form of the @code{new} expression +the pointer argument was returned from. Similarly, the call to @code{free} +is also diagnosed. + +@smallexample +void f () +@{ + int *a = new int[n]; + delete a; // warning: mismatch in array forms of expressions + + char *p = new char[n]; + free (p); // warning: mismatch between new and free +@} +@end smallexample + +The related option @option{-Wmismatched-dealloc} diagnoses mismatches +involving allocation and deallocation functions other than @code{operator +new} and @code{operator delete}. + +@option{-Wmismatched-new-delete} is enabled by default. + @item -Wmismatched-tags @r{(C++ and Objective-C++ only)} @opindex Wmismatched-tags @opindex Wno-mismatched-tags @@ -6287,6 +6322,41 @@ Ignoring the warning can result in poorly optimized code. disable the warning, but this is not recommended and should be done only when non-existent profile data is justified. +@item -Wno-mismatched-dealloc +@opindex Wmismatched-dealloc +@opindex Wno-mismatched-dealloc + +Warn for calls to deallocation functions with pointer arguments returned +from from allocations functions for which the former isn't a suitable +deallocator. A pair of functions can be associated as matching allocators +and deallocators by use of attribute @code{malloc}. Unless disabled by +the @option{-fno-builtin} option the standard functions @code{calloc}, +@code{malloc}, @code{realloc}, and @code{free}, as well as the corresponding +forms of C++ @code{operator new} and @code{operator delete} are implicitly +associated as matching allocators and deallocators. In the following +example @code{mydealloc} is the deallocator for pointers returned from +@code{myalloc}. + +@smallexample +void mydealloc (void*); + +__attribute__ ((malloc (mydealloc, 1))) void* +myalloc (size_t); + +void f (void) +@{ + void *p = myalloc (32); + // @dots{}use p@dots{} + free (p); // warning: not a matching deallocator for myalloc + mydealloc (p); // ok +@} +@end smallexample + +In C++, the related option @option{-Wmismatched-new-delete} diagnoses +mismatches involving either @code{operator new} or @code{operator delete}. + +Option @option{-Wmismatched-dealloc} is enabled by default. + @item -Wmultistatement-macros @opindex Wmultistatement-macros @opindex Wno-multistatement-macros @@ -7778,8 +7848,23 @@ to @option{-Wframe-larger-than=}@samp{SIZE_MAX} or larger. @item -Wno-free-nonheap-object @opindex Wno-free-nonheap-object @opindex Wfree-nonheap-object -Do not warn when attempting to free an object that was not allocated -on the heap. +Warn when attempting to deallocate an object that was either not allocated +on the heap, or by using a pointer that was not returned from a prior call +to the corresponding allocation function. For example, because the call +to @code{stpcpy} returns a pointer to the terminating nul character and +not to the begginning of the object, the call to @code{free} below is +diagnosed. + +@smallexample +void f (char *p) +@{ + p = stpcpy (p, "abc"); + // ... + free (p); // warning +@} +@end smallexample + +@option{-Wfree-nonheap-object} is enabled by default. @item -Wstack-usage=@var{byte-size} @opindex Wstack-usage diff --git a/gcc/testsuite/g++.dg/asan/asan_test.cc b/gcc/testsuite/g++.dg/asan/asan_test.cc index 5f2e2c244dd..dbf1a6ac0a5 100644 --- a/gcc/testsuite/g++.dg/asan/asan_test.cc +++ b/gcc/testsuite/g++.dg/asan/asan_test.cc @@ -829,7 +829,7 @@ NOINLINE static int LargeFunction(bool do_bad_access) { x[18]++; x[19]++; - delete x; + delete[] x; return res; } diff --git a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-2.C b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-2.C new file mode 100644 index 00000000000..9d4d2a393ea --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object-2.C @@ -0,0 +1,274 @@ +/* PR ????? - No warning on attempts to access free object + Verify that freeing unallocated objects referenced either directly + or through pointers is diagnosed. + { dg-do compile } + { dg-options "-O2 -Wall -Wfree-nonheap-object" } */ + +typedef __INTPTR_TYPE__ intptr_t; +typedef __SIZE_TYPE__ size_t; + +extern "C" +{ + void free (void*); + extern void* malloc (size_t); + extern void* realloc (void *p, size_t); +} + +void sink (void*, ...); +#define sink(...) sink (0, __VA_ARGS__) + +extern char ecarr[]; +extern void* eparr[]; + +extern char *eptr; + +void* source (void); + +void nowarn_free (void *p, void **pp, size_t n, intptr_t iptr) +{ + free (p); + + p = 0; + free (p); + + p = malloc (n); + sink (p); + free (p); + + p = malloc (n); + sink (p); + + p = realloc (p, n * 2); + sink (p); + free (p); + + free ((void*)iptr); + + p = source (); + free (p); + + p = source (); + p = (char*)p - 1; + free (p); + + free (*pp); +} + +void warn_free_extern_arr (void) +{ + free (ecarr); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + +void warn_free_extern_arr_offset (int i) +{ + char *p = ecarr + i; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +void warn_free_cstint (void) +{ + void *p = (void*)1; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +void warn_free_func (void) +{ + void *p = (void*)warn_free_func; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +void warn_free_string (int i) +{ + { + char *p = (char*)"123"; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + { + char *p = (char*)"234" + 1; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + { + char *p = (char*)"345" + i; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + + if (i >= 0) + { + char *p = (char*)"456" + i; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } +} + +void warn_free_local_arr (int i) +{ + { + char a[4]; + sink (a); + free (a); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + { + char b[5]; + sink (b); + + char *p = b + 1; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + { + char c[6]; + sink (c); + + char *p = c + i; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } +} + + +void warn_free_vla (int n, int i) +{ + { + int vla[n], *p = vla; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + + { + int vla[n + 1], *p = vla + 1; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + { + int vla[n + 2], *p = vla + i; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } +} + + +void nowarn_free_extern_ptrarr (void) +{ + free (*eparr); +} + +void nowarn_free_extern_ptrarr_offset (int i) +{ + void *p = eparr[i]; + free (p); +} + + +void warn_free_extern_ptrarr (void) +{ + free (eparr); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + +void warn_free_extern_ptrarr_offset (int i) +{ + void *p = &eparr[i]; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +void nowarn_free_local_ptrarr (int i) +{ + void* a[4]; + sink (a); + free (a[0]); + free (a[1]); + free (a[i]); +} + + +void nowarn_free_extern_ptr (void) +{ + free (eptr); +} + +void nowarn_free_extern_ptr_offset (int i) +{ + char *p = eptr + i; + free (p); +} + +void warn_free_extern_ptr_pos_offset (int i) +{ + if (i <= 0) + i = 1; + + char *q = eptr + i; + free (q); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +void nowarn_free_parm_offset (char *p, int i) +{ + char *q = p + i; + free (q); +} + +void nowarn_free_parm_neg_offset (char *p, int i) +{ + if (i >= 0) + i = -1; + + char *q = p + i; + free (q); +} + +void warn_free_parm_pos_offset (char *p, int i) +{ + if (i <= 0) + i = 1; + + char *q = p + i; + free (q); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +struct Members +{ + char a[4], *p, *q; +}; + +extern struct Members em; + +void nowarn_free_member_ptr (struct Members *pm, int i) +{ + char *p = em.p; + free (p); + p = em.q + i; + free (p); + + free (pm->q); + p = pm->p; + free (pm); + free (p); +} + +void nowarn_free_struct_cast (intptr_t *p) +{ + struct Members *q = (struct Members*)*p; + if (q->p == 0) + free (q); // { dg-bogus "\\\[-Wfree-nonheap-object" } +} + + +void warn_free_member_array (void) +{ + char *p = em.a; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + +void warn_free_member_array_off (int i) +{ + char *p = em.a + i; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} diff --git a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object.C b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object.C new file mode 100644 index 00000000000..82b081ad48f --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object.C @@ -0,0 +1,124 @@ +/* PR ????? - No warning on attempts to access free object + Verify that attempts to deallocate objects by pointers with nonzero + offsets is diagnosed. + { dg-do compile } + { dg-options "-O2 -Wall -Wfree-nonheap-object" } */ + +typedef __INTPTR_TYPE__ intptr_t; +typedef __SIZE_TYPE__ size_t; + +void sink (void*, ...); + +extern char ecarr[]; +extern void* eparr[]; + +extern char *eptr; + +char* source (void); + +void nowarn_op_delete (void *p, void ***ppp, size_t n, intptr_t iptr) +{ + operator delete (p); + + p = 0; + operator delete (p); + + p = operator new (n); + sink (p); + operator delete (p); + + p = operator new (n); + sink (p); + + operator delete ((void*)iptr); + + p = source (); + operator delete (p); + + p = source (); + p = (char*)p - 1; + operator delete (p); + + operator delete (**ppp); + operator delete (*ppp); + operator delete (ppp); +} + +void warn_op_delete_cstaddr (void *p) +{ + operator delete (p); + p = (void*)~0; + operator delete (p); // { dg-warning "called on a pointer to an unallocated object" } */ +} + +void warn_op_delete_funcaddr () +{ + void *p = (void*)&warn_op_delete_funcaddr; + operator delete (p); // { dg-warning "called on unallocated object 'void warn_op_delete_funcaddr()" } */ +} + +void warn_op_delete_string (void *p) +{ + operator delete (p); + p = (void*)""; + operator delete (p); // { dg-warning "called on a pointer to an unallocated object" } */ +} + +void warn_op_delete_ptr_to_self (void *p) +{ + operator delete (p); + p = &p; + operator delete (p); // { dg-warning "called on unallocated object 'p'" } */ +} + +void nowarn_op_new_delete (size_t n) +{ + void *p = operator new (n); + sink (p); + operator delete (p); +} + +void nowarn_op_new_delete_ptr_plus (size_t n) +{ + void *p0_1 = operator new (n); + void *p1 = (char*)p0_1 + 1; + sink (p0_1, p1); + void *p0_2 = (char*)p1 - 1; + sink (p0_1, p1, p0_2); + operator delete (p0_2); +} + +void warn_op_new_delete_cstoff (size_t n) +{ + void *p = operator new (n); + void *q = (char*)p + 1; + sink (p, q); + operator delete (q); // { dg-warning "'void operator delete\\\(void\\\*\\\)' called on pointer '\[^'\]+' with nonzero offset 1" } +} + +void warn_op_new_delete_ptr_plus (size_t n) +{ + char *p = (char*)operator new (n); + sink (++p); + operator delete (p); // { dg-warning "called on pointer '\[^']+' with nonzero offset 1" } +} + +void warn_op_delete_funcret_plus (size_t n) +{ + char *p = source (); + sink (++p); + operator delete (p); // { dg-warning "called on pointer '\[^']+' with nonzero offset 1" } +} + +void warn_op_delete_eptr_plus (int i) +{ + extern char *ecp; + + if (i < 1) + i = 1; + + char *p = ecp + i; + sink (p); + + operator delete (p); // { dg-warning "called on pointer '\[^']+' with nonzero offset \\\[1, \\d+]" } +} diff --git a/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object.s b/gcc/testsuite/g++.dg/warn/Wfree-nonheap-object.s new file mode 100644 index 00000000000..e69de29bb2d diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C new file mode 100644 index 00000000000..7ecc99a325c --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc-2.C @@ -0,0 +1,185 @@ +/* PR middle-end/94527 - Add an attribute that marks a function as freeing + an object + The detection doesn't require optimization. + { dg-do compile } + { dg-options "-Wall" } */ + +#define A(...) __attribute__ ((malloc (__VA_ARGS__))) + +typedef __SIZE_TYPE__ size_t; + +extern "C" { + void free (void *); + void* realloc (void *, size_t); +} + +void sink (void *); + +void mydealloc (int, void*); +void* A (mydealloc, 2) myalloc (void*); + + +void my_delete (const char*, void*); +void my_array_delete (const char*, void*); + +typedef void OpDelete1 (void*); +typedef void OpDelete2 (void*, size_t); + +A ((OpDelete1*)operator delete, 1) +#if __cplusplus >= 201402L +A ((OpDelete2*)operator delete, 1) +#endif +A (my_delete, 2) +int* my_new (size_t); + +A ((OpDelete1*)operator delete[], 1) +#if __cplusplus >= 201402L +A ((OpDelete2*)operator delete[], 1) +#endif +A (my_array_delete, 2) +int* my_array_new (size_t); + + +void test_my_new () +{ + { + void *p = my_new (1); + operator delete (p); + } + { + void *p = my_new (1); + sink (p); + operator delete (p); + } + { + int *p = my_new (1); + sink (p); + delete p; + } + + { + void *p = my_new (1); + // { dg-message "returned from a call to '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 } + 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 } + } + { + int *p = my_new (1); + sink (p); + 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); + my_delete ("1", p); + } + { + void *p = my_new (1); + sink (p); + my_delete ("2", p); + } + + { + void *p = my_new (1); + // { dg-message "returned from a call to '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 } + } + + { + void *p = my_new (1); + // { dg-message "returned from a call to '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 } + } + + { + void *p = my_new (1); + // { dg-message "returned from a call to '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 } + } +} + + +void test_my_array_new () +{ + { + void *p = my_array_new (1); + operator delete[] (p); + } + { + void *p = my_array_new (1); + sink (p); + operator delete[] (p); + } + { + int *p = my_array_new (1); + sink (p); + delete[] p; + } + + { + void *p = my_array_new (1); + // { dg-message "returned from a call to '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 } + 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 } + } + { + int *p = my_array_new (1); + sink (p); + 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); + my_array_delete ("1", p); + } + { + void *p = my_array_new (1); + sink (p); + my_array_delete ("2", p); + } + { + void *p = my_array_new (1); + // { dg-message "returned from a call to '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 } + } + + { + void *p = my_array_new (1); + // { dg-message "returned from a call to '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 } + } + + { + void *p = my_array_new (1); + // { dg-message "returned from a call to '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-dealloc.C b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc.C new file mode 100644 index 00000000000..682db6f02cb --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-dealloc.C @@ -0,0 +1,27 @@ +/* PR middle-end/94527 - Add an attribute that marks a function as freeing + an object + { dg-do compile { target c++11 } } + { dg-options "-Wall" } */ + +#define A(...) __attribute__ ((malloc (__VA_ARGS__))) + +typedef __SIZE_TYPE__ size_t; + +void mydealloc (int, void*); +void* A (mydealloc, 2) myalloc (void*); + + +void* A (operator delete, 1) + bad_new (size_t); // { dg-error "attribute argument 1 is ambiguous" } +void* A (operator delete[], 1) + bad_array_new (size_t); // { dg-error "attribute argument 1 is ambiguous" } + +void my_delete (const char*, void*); +void my_array_delete (const char*, void*); + +typedef void OpDelete (void*); + +int* A ((OpDelete*)operator delete, 1) A (my_delete, 2) + my_new (size_t); +int* A ((OpDelete*)operator delete[], 1) A (my_array_delete, 2) + my_array_new (size_t); diff --git a/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C new file mode 100644 index 00000000000..ed1090be5c5 --- /dev/null +++ b/gcc/testsuite/g++.dg/warn/Wmismatched-new-delete.C @@ -0,0 +1,212 @@ +/* PR c++/90629 - Support for -Wmismatched-new-delete + The detection doesn't require optimization. + { dg-do compile } + { dg-options "-Wall" } */ + +typedef __SIZE_TYPE__ size_t; + +extern "C" { + void free (void *); + void* malloc (size_t); + void* realloc (void *, size_t); + char* strdup (const char *); + char* strndup (const char *, size_t); +} + +void sink (void *); + +void nowarn_op_new_delete (int n) +{ + void *p = operator new (n); + sink (p); + operator delete (p); +} + +void nowarn_new_delete (int n) +{ + { + char *p = new char; + sink (p); + delete p; + } + + { + char *p = new char[n]; + sink (p); + delete[] p; + } +} + +/* Verify a warning for calls to free() with a pointer returned from + a call to operator new() or the new expressopm. */ + +void warn_new_free (int n) +{ + { + void *p = operator new (n); + // { dg-message "returned from a call to '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 } + sink (p); + free (p); + // { dg-warning "'void free\\\(void\\\*\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } + } +} + + +/* Verify a warning for calls to realloc() with a pointer returned from + a call to operator new() or the new expressopm. */ + +void warn_new_realloc (int n) +{ + { + void *p = operator new (n); + // { dg-message "returned from a call to '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 } + sink (p); + } + { + void *p = new char[n]; + // { dg-message "returned from a call to '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 } + sink (p); + } +} + + +/* Verify a warning for a call to operator_delete() with a pointer returned + from a call to malloc(). */ + +void warn_malloc_op_delete (int n) +{ + char *p = (char *)malloc (n); + // { dg-message "returned from a call to '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 } +} + + +/* Verify a warning for an invocation of either form of the delete + expression with a pointer returned from a call to malloc(). */ + +void warn_malloc_delete (int n) +{ + { + char *p = (char *)malloc (n); + // { dg-message "returned from a call to '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 + here so verify just that some operator delete is called. */ + delete p; + // { dg-warning "'void operator delete\\\(\[^)\]+\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } + } + + { + char *p = (char *)malloc (n); + // { dg-message "returned from a call to '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 } + } +} + + +/* Verify a warning for an invocation of either form of the delete + expression with a pointer returned from a call to 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 } + sink (q); + /* C++98 calls operator delete (void*) but later versions call + operator delete (void*, size_t). The difference doesn't matter + here so verify just that some operator delete is called. */ + delete q; + // { dg-warning "'void operator delete\\\(\[^)\]+\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } + } + + { + char *q = (char *)realloc (p2, n); + // { dg-message "returned from a call to '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 } + } +} + + +/* Verify a warning for an invocation of either form of the delete + expression with a pointer returned from a call to 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 } + sink (q); + /* C++98 calls operator delete (void*) but later versions call + operator delete (void*, size_t). The difference doesn't matter + here so verify just that some operator delete is called. */ + delete q; + // { dg-warning "'void operator delete\\\(\[^)\]+\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } + } + + { + char *q = strdup (s2); + // { dg-message "returned from a call to '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 } + } +} + + + +/* Verify a warning for an invocation of either form of the delete + expression with a pointer returned from a call to 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 } + sink (q); + /* C++98 calls operator delete (void*) but later versions call + operator delete (void*, size_t). The difference doesn't matter + here so verify just that some operator delete is called. */ + delete q; + // { dg-warning "'void operator delete\\\(\[^)\]+\\\)' called on pointer returned from a mismatched allocation function" "" { target *-*-* } .-1 } + } + + { + char *q = strndup (s2, n); + // { dg-message "returned from a call to '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 } + } +} + + +struct Base { virtual ~Base (); }; +struct Derived: Base { }; + +void warn_new_free_base_derived () +{ + Base *p = new Derived (); + sink (p); + free (p); // { dg-warning "\\\[-Wmismatched-new-delete" } +} diff --git a/gcc/testsuite/g++.dg/warn/delete-array-1.C b/gcc/testsuite/g++.dg/warn/delete-array-1.C index c3af71323ac..95fa7d445d4 100644 --- a/gcc/testsuite/g++.dg/warn/delete-array-1.C +++ b/gcc/testsuite/g++.dg/warn/delete-array-1.C @@ -5,7 +5,7 @@ struct S { int a [1]; } s; void foo (S *p) { - delete a; // { dg-warning "deleting array" } - delete s.a; // { dg-warning "deleting array" } - delete p->a; // { dg-warning "deleting array" } + delete a; // { dg-warning "deleting array|-Wfree-nonheap-object" } + delete s.a; // { dg-warning "deleting array|-Wfree-nonheap-object" } + delete p->a; // { dg-warning "deleting array|-Wfree-nonheap-object" } } diff --git a/gcc/testsuite/g++.old-deja/g++.other/delete2.C b/gcc/testsuite/g++.old-deja/g++.other/delete2.C index 1d0554f2d95..76ae3558a1d 100644 --- a/gcc/testsuite/g++.old-deja/g++.other/delete2.C +++ b/gcc/testsuite/g++.old-deja/g++.other/delete2.C @@ -9,5 +9,7 @@ void bar(foo a) { delete[] a; // should be accepted char b[1]; delete b; // { dg-warning "deleting array" } expecting pointer type + // { dg-warning "-Wfree-nonheap-object" "" { target *-*-* } .-1 } delete[] b; // { dg-warning "deleting array" } expecting pointer type + // { dg-warning "-Wfree-nonheap-object" "" { target *-*-* } .-1 } } diff --git a/gcc/testsuite/gcc.dg/Wfree-nonheap-object-2.c b/gcc/testsuite/gcc.dg/Wfree-nonheap-object-2.c new file mode 100644 index 00000000000..2b00d77e8b8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wfree-nonheap-object-2.c @@ -0,0 +1,279 @@ +/* PR ????? - No warning on attempts to access free object + Verify that attempting to reallocate unallocated objects referenced + either directly or through pointers is diagnosed. + { dg-do compile } + { dg-options "-O2 -Wall -Wfree-nonheap-object" } */ + +typedef __SIZE_TYPE__ size_t; + +extern void free (void*); +extern void* alloca (size_t); +extern void* realloc (void*, size_t); + +void sink (void*, ...); + +extern void* eparr[]; +extern char *eptr; + +extern size_t n; + + +void nowarn_realloc (void *p, size_t n) +{ + char *q = realloc (p, n); + sink (q); + + q = realloc (0, n); + sink (q); + + q = realloc (q, n * 2); + sink (q); +} + +/* Verify that calling realloc on a pointer to an unknown object minus + some nonzero offset isn't diagnosed, but a pointer plus a positive + offset is (a positive offset cannot point at the beginning). */ + +void test_realloc_offset (char *p1, char *p2, char *p3, size_t n, int i) +{ + char *q; + q = realloc (p1 - 1, n); + sink (q); + + q = realloc (p2 + 1, n); // { dg-warning "'realloc' called on pointer 'p2' with nonzero offset 1" } + sink (q); + + q = realloc (p3 + i, n); + sink (q); +} + +void warn_realloc_extern_arr (void) +{ + extern char ecarr[]; // { gg-message "declared here" } + char *p = ecarr; + char *q = realloc (p, n); // { dg-warning "'realloc' called on unallocated object 'ecarr'" } + sink (q); +} + +void warn_realloc_extern_arr_offset (int i) +{ + extern char ecarr[]; + char *p = ecarr + i; + char *q = realloc (p, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); +} + + +void warn_realloc_string (int i) +{ + char *p, *q; + { + p = "123"; + sink (p); + q = realloc (p, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } + { + p = "234" + 1; + sink (p); + q = realloc (p, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } + { + p = "123" + i; + sink (p); + q = realloc (p, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } +} + + +void warn_realloc_alloca (int n, int i) +{ + char *p, *q; + { + p = alloca (n); + sink (p); + q = realloc (p, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } + { + p = (char*)alloca (n + 1); + sink (p); + q = realloc (p, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } + { + p = (char*)alloca (n + 2) + i; + sink (p); + q = realloc (p, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } +} + + +void warn_realloc_local_arr (int i) +{ + char *q; + { + char a[4]; + sink (a); + q = realloc (a, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } + + { + char b[5]; + sink (b); + q = realloc (b + 1, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } + + { + char c[6]; + sink (c); + q = realloc (&c[2], n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } + + { + char d[7]; + sink (d); + q = realloc (&d[i], n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } +} + +void warn_realloc_vla (int n1, int n2, int i) +{ + char *q; + { + char vla[n1]; + sink (vla); + q = realloc (vla, n2); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } + + { + char vlb[n1 + 1]; + sink (vlb); + q = realloc (vlb + 1, n2);// { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } + + { + char vlc[n1 + 2]; + sink (vlc); + q = realloc (&vlc[2], n2);// { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } + + { + char vld[7]; + sink (vld); + q = realloc (&vld[i], n2);// { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); + } +} + +void nowarn_realloc_extern_ptrarr (void) +{ + char *q = realloc (*eparr, n); + sink (q); +} + +void nowarn_realloc_extern_ptrarr_offset (int i) +{ + char *p = eparr[i]; + char *q = realloc (p, n); + sink (q); +} + + +void warn_realloc_extern_ptrarr (void) +{ + char *q = realloc (eparr, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); +} + +void warn_realloc_extern_ptrarr_offset (int i) +{ + void *p = eparr + i; + void *q = realloc (p, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); +} + + +void nowarn_realloc_extern_ptr (void) +{ + char *q = realloc (eptr, n); + sink (q); +} + +void nowarn_realloc_extern_ptr_offset (int i) +{ + char *p = eptr + i; + char *q = realloc (p, n); + sink (q); +} + + +void warn_realloc_extern_ptr_pos_offset (int i) +{ + if (i <= 0) + i = 1; + + char *p = eptr + i; + char *q = realloc (p, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); +} + + +void nowarn_realloc_parm_offset (char *p, int i) +{ + char *q = p + i; + q = realloc (q, n); + sink (q); +} + +void nowarn_realloc_parm_neg_offset (char *p, int i) +{ + if (i >= 0) + i = -1; + + char *q = p + i; + q = realloc (q, n); + sink (q); +} + +void warn_realloc_parm_pos_offset (char *p, int i) +{ + if (i <= 0) + i = 1; + + char *q = p + i; + q = realloc (q, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); +} + +void nowarn_realloc_deref_parm_pos_offset (void **p, int i) +{ + if (i <= 0) + i = 1; + + // The offset is from p, not *p. + void *q = *(p + i); + q = realloc (q, n); + sink (q); +} + +void warn_realloc_deref_parm_pos_offset (void **p, int i) +{ + if (i <= 0) + i = 1; + + // Unlike in the function above the offset is from *p. + void *q = *p + i; + q = realloc (q, n); // { dg-warning "\\\[-Wfree-nonheap-object" } + sink (q); +} diff --git a/gcc/testsuite/gcc.dg/Wfree-nonheap-object-3.c b/gcc/testsuite/gcc.dg/Wfree-nonheap-object-3.c new file mode 100644 index 00000000000..a472b93fb87 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wfree-nonheap-object-3.c @@ -0,0 +1,57 @@ +/* PR ????? - No warning on attempts to access free object + Verify that freeing unallocated objects referenced indirectly through + pointers obtained from function calls is diagnosed. + { dg-do compile } + { dg-options "-O2 -Wall -Wfree-nonheap-object" } */ + +typedef __SIZE_TYPE__ size_t; + +extern void free (void*); +extern char* memchr (const void*, int, size_t); +extern char* strchr (const char*, int); + +void sink (void*, ...); + +extern char ecarr[]; +extern void* eparr[]; + +extern char *eptr; + + +void warn_free_memchr_ecarr (int x, size_t n) +{ + char *p = memchr (ecarr, x, n); + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + +void warn_free_memchr_ecarr_offset (int i, int j, int x, size_t n) +{ + char *p = memchr (ecarr + i, x, n); + char *q = p + j; + sink (p, q); + free (q); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +void warn_free_memchr_local_arr (int x, size_t n) +{ + char a[8]; + sink (a); + + char *p = memchr (a, x, n); + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + +void warn_free_memchr_local_arr_offset (int i, int j, int x, size_t n) +{ + char a[8]; + sink (a); + + char *p = memchr (a + i, x, n); + char *q = p + j; + sink (p, q); + free (q); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + diff --git a/gcc/testsuite/gcc.dg/Wfree-nonheap-object.c b/gcc/testsuite/gcc.dg/Wfree-nonheap-object.c new file mode 100644 index 00000000000..bb222ccf6ab --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wfree-nonheap-object.c @@ -0,0 +1,273 @@ +/* PR ????? - No warning on attempts to access free object + Verify that freeing unallocated objects referenced either directly + or through pointers is diagnosed. In most cases this doesn't require + optimization. + { dg-do compile } + { dg-options "-Wall -Wfree-nonheap-object" } */ + +typedef __INTPTR_TYPE__ intptr_t; +typedef __SIZE_TYPE__ size_t; + +extern void free (void*); +extern void* malloc (size_t); +extern void* realloc (void *p, size_t); + +void sink (void*, ...); + +extern char ecarr[]; +extern void* eparr[]; + +extern char *eptr; + +void* source (void); + +void nowarn_free (void *p, void **pp, size_t n, intptr_t iptr) +{ + free (p); + + p = 0; + free (p); + + p = malloc (n); + sink (p); + free (p); + + p = malloc (n); + sink (p); + + p = realloc (p, n * 2); + sink (p); + free (p); + + free ((void*)iptr); + + p = source (); + free (p); + + p = source (); + p = (char*)p - 1; + free (p); + + free (*pp); +} + +void warn_free_extern_arr (void) +{ + free (ecarr); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + +void warn_free_extern_arr_offset (int i) +{ + char *p = ecarr + i; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +void warn_free_cstint (void) +{ + void *p = (void*)1; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +void warn_free_func (void) +{ + void *p = warn_free_func; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +void warn_free_string (int i) +{ + { + char *p = "123"; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + { + char *p = "234" + 1; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + { + char *p = "345" + i; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + + if (i >= 0) + { + char *p = "456" + i; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } +} + +void warn_free_local_arr (int i) +{ + { + char a[4]; + sink (a); + free (a); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + { + char b[5]; + sink (b); + + char *p = b + 1; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + { + char c[6]; + sink (c); + + char *p = c + i; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } +} + + +void warn_free_vla (int n, int i) +{ + { + int vla[n], *p = vla; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + + { + int vla[n + 1], *p = vla + 1; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } + { + int vla[n + 2], *p = vla + i; + sink (p); + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } + } +} + + +void nowarn_free_extern_ptrarr (void) +{ + free (*eparr); +} + +void nowarn_free_extern_ptrarr_offset (int i) +{ + char *p = eparr[i]; + free (p); +} + + +void warn_free_extern_ptrarr (void) +{ + free (eparr); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + +void warn_free_extern_ptrarr_offset (int i) +{ + void *p = &eparr[i]; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +void nowarn_free_local_ptrarr (int i) +{ + void* a[4]; + sink (a); + free (a[0]); + free (a[1]); + free (a[i]); +} + + +void nowarn_free_extern_ptr (void) +{ + free (eptr); +} + +void nowarn_free_extern_ptr_offset (int i) +{ + char *p = eptr + i; + free (p); +} + +void nowarn_free_parm_offset (char *p, int i) +{ + char *q = p + i; + free (q); +} + +void nowarn_free_parm_neg_offset (char *p, int i) +{ + if (i >= 0) + i = -1; + + char *q = p + i; + free (q); +} + +struct Members +{ + char a[4], *p, *q; +}; + +extern struct Members em; + +void nowarn_free_member_ptr (struct Members *pm, int i) +{ + char *p = em.p; + free (p); + p = em.q + i; + free (p); + + free (pm->q); + p = pm->p; + free (pm); + free (p); +} + +void nowarn_free_struct_cast (intptr_t *p) +{ + struct Members *q = (struct Members*)*p; + if (q->p == 0) + free (q); // { dg-bogus "\\\[-Wfree-nonheap-object" } +} + + +void warn_free_member_array (void) +{ + char *p = em.a; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + +void warn_free_member_array_off (int i) +{ + char *p = em.a + i; + free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + + +// Range information requires optimization. +#pragma GCC optimize "1" + +void warn_free_extern_ptr_pos_offset (int i) +{ + if (i <= 0) + i = 1; + + char *q = eptr + i; + free (q); // { dg-warning "\\\[-Wfree-nonheap-object" } +} + +void warn_free_parm_pos_offset (char *p, int i) +{ + if (i <= 0) + i = 1; + + char *q = p + i; + free (q); // { dg-warning "\\\[-Wfree-nonheap-object" } +} diff --git a/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c b/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c new file mode 100644 index 00000000000..7c5d6acf4d6 --- /dev/null +++ b/gcc/testsuite/gcc.dg/Wmismatched-dealloc.c @@ -0,0 +1,252 @@ +/* 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__))) + +typedef struct FILE FILE; +typedef __SIZE_TYPE__ size_t; + +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); + +void sink (FILE*); + + + void release (void*); +A (release) FILE* acquire (void); + +void nowarn_fdopen (void) +{ + { + FILE *q = fdopen (0); + if (!q) + return; + + fclose (q); + } + + { + FILE *q = fdopen (0); + if (!q) + return; + + q = freopen ("1", "r", q); + fclose (q); + } + + { + FILE *q = fdopen (0); + if (!q) + return; + + sink (q); + } +} + + +void warn_fdopen (void) +{ + { + FILE *q = fdopen (0); // { dg-message "returned from a call to '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" } + 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" } + sink (q); + q = realloc (q, 7); // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" } + sink (q); + } +} + + +void nowarn_fopen (void) +{ + { + FILE *q = fopen ("1", "r"); + sink (q); + fclose (q); + } + + { + FILE *q = fopen ("2", "r"); + sink (q); + q = freopen ("3", "r", q); + sink (q); + fclose (q); + } + + { + FILE *q = fopen ("4", "r"); + sink (q); + } +} + + +void warn_fopen (void) +{ + { + FILE *q = fopen ("1", "r"); + sink (q); + release (q); // { dg-warning "'release' called on pointer returned from a mismatched allocation function" } + } + { + FILE *q = fdopen (0); + sink (q); + free (q); // { dg-warning "'free' called on pointer returned from a mismatched allocation function" } + } + + { + FILE *q = fdopen (0); + sink (q); + q = realloc (q, 7); // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" } + sink (q); + } +} + + +void test_popen (void) +{ + { + FILE *p = popen ("1", "r"); + sink (p); + pclose (p); + } + + { + FILE *p; + p = popen ("2", "r"); // { dg-message "returned from a call to 'popen'" "note" } + sink (p); + fclose (p); // { dg-warning "'fclose' called on pointer returned from a mismatched allocation function" } + } + + { + /* freopen() can close a stream open by popen() but pclose() can't + close the stream returned from freopen(). */ + FILE *p = popen ("2", "r"); + sink (p); + p = freopen ("3", "r", p); // { dg-message "returned from a call to 'freopen'" "note" } + sink (p); + pclose (p); // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" } + } +} + + +void test_tmpfile (void) +{ + { + FILE *p = tmpfile (); + sink (p); + fclose (p); + } + + { + FILE *p = tmpfile (); + sink (p); + p = freopen ("1", "r", p); + sink (p); + fclose (p); + } + + { + FILE *p = tmpfile (); // { dg-message "returned from a call to 'tmpfile'" "note" } + sink (p); + pclose (p); // { dg-warning "'pclose' 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" } + 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" } + 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" } + sink (p); + pclose (p); // { dg-warning "'pclose' called on pointer returned from a mismatched allocation function" } + } +} + + +void test_acquire (void) +{ + { + FILE *p = acquire (); + release (p); + } + + { + FILE *p = acquire (); + sink (p); + release (p); + } + + { + FILE *p = acquire (); // { dg-message "returned from a call to '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" } + 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" } + 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" } + 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" } + sink (p); + p = realloc (p, 123); // { dg-warning "'realloc' called on pointer returned from a mismatched allocation function" } + sink (p); + } +} diff --git a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c index c5bf1227c55..26d828848a2 100644 --- a/gcc/testsuite/gcc.dg/analyzer/malloc-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/malloc-1.c @@ -609,3 +609,5 @@ int test_49 (int i) *p = 1; /* { dg-warning "dereference of NULL 'p' \\\[CWE-476\\\]" } */ return x; } + +/* { dg-prune-output "\\\[-Wfree-nonheap-object" } */ diff --git a/gcc/testsuite/gcc.dg/attr-malloc.c b/gcc/testsuite/gcc.dg/attr-malloc.c new file mode 100644 index 00000000000..14f1980ed7f --- /dev/null +++ b/gcc/testsuite/gcc.dg/attr-malloc.c @@ -0,0 +1,75 @@ +/* PR middle-end/94527 - Add an attribute that marks a function as freeing + an object + Verify that attribute malloc with one or two arguments is accepted where + intended and rejected where it's invalid. + { dg-options "-Wall -ftrack-macro-expansion=0" } */ + +#define A(...) __attribute__ ((malloc (__VA_ARGS__))) + +A (0) void* alloc_zero (int); // { dg-error "'malloc' attribute argument 1 does not name a function" } + +A ("") void* alloc_string (int); // { dg-error "'malloc' attribute argument 1 does not name a function" } + +int var; +A (var) void* alloc_var (int); // { dg-error "'malloc' attribute argument 1 does not name a function" } + +typedef struct Type { int i; } Type; +A (Type) void* alloc_type (int); // { dg-error "expected expression|identifier" } + +A (unknown) void* alloc_unknown (int); // { dg-error "'unknown' undeclared" } + +void fv_ (); // { dg-message "declared here" } +A (fv_) void* alloc_fv_ (int); // { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument" } + +void fvi (int); // { dg-message "declared here" } +A (fvi) void* alloc_fvi (int); // { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument; have 'int'" } + +void fvv (void); // { dg-message "declared here" } +A (fvv) void* alloc_fvv (int); // { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument; have 'void'" } + +void fvi_ (int, ...); // { dg-message "declared here" } +A (fvi_) void* alloc_fvi_ (int); // { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument; have 'int'" } + +void fvi_vp (Type, void*); // { dg-message "declared here" } +A (fvi_vp) void* alloc_fvi_vp (int); // { dg-error "'malloc' attribute argument 1 must take a pointer type as its first argument; have 'Type'" } + + +void fpv (void*); +A (fpv) void* alloc_fpv (int); + +void fpv_i (void*, int); +A (fpv_i) void* alloc_fpv_i (int); + +void fpv_pv (void*, void*); +A (fpv_i) void* alloc_fpv_pv (int); + + +void gpc (char*); +void hpi (int*); +A (fpv) A (gpc) A (hpi) Type* alloc_fpv_gpv (int); + + +/* Verify that the attribute can be applied to functions. */ +typedef struct FILE FILE; +typedef __SIZE_TYPE__ size_t; + +int fclose (FILE*); +FILE* fdopen (int); +FILE* fopen (const char*, const char*); +FILE* freopen (const char*, const char*, FILE*); +int pclose (FILE*); +FILE* popen (const char*, const char*); +FILE* tmpfile (void); + +A (fclose) A (freopen, 3) A (pclose) + FILE* fdopen (int); +A (fclose) A (freopen, 3) A (pclose) + FILE* fopen (const char*, const char*); +A (fclose) A (freopen, 3) A (pclose) + FILE* fmemopen(void *, size_t, const char *); +A (fclose) A (freopen, 3) A (pclose) + FILE* freopen (const char*, const char*, FILE*); +A (fclose) A (freopen, 3) A (pclose) + FILE* popen (const char*, const char*); +A (fclose) A (freopen, 3) A (pclose) + FILE* tmpfile (void); diff --git a/gcc/testsuite/gcc.dg/free-1.c b/gcc/testsuite/gcc.dg/free-1.c index 5496c84fdb8..ad49d787fbe 100644 --- a/gcc/testsuite/gcc.dg/free-1.c +++ b/gcc/testsuite/gcc.dg/free-1.c @@ -13,14 +13,14 @@ void foo (void) static char buf4[10], e; char *q = buf; free (p); - free (q); /* { dg-warning "attempt to free a non-heap object" } */ - free (buf2); /* { dg-warning "attempt to free a non-heap object" } */ - free (&c); /* { dg-warning "attempt to free a non-heap object" } */ - free (buf3); /* { dg-warning "attempt to free a non-heap object" } */ - free (&d); /* { dg-warning "attempt to free a non-heap object" } */ - free (buf4); /* { dg-warning "attempt to free a non-heap object" } */ - free (&e); /* { dg-warning "attempt to free a non-heap object" } */ + free (q); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (buf2); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (&c); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (buf3); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (&d); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (buf4); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (&e); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ free (&r->a); - free ("abcd"); /* { dg-warning "attempt to free a non-heap object" } */ - free (L"abcd"); /* { dg-warning "attempt to free a non-heap object" } */ + free ("abcd"); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (L"abcd"); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ } diff --git a/gcc/testsuite/gcc.dg/free-2.c b/gcc/testsuite/gcc.dg/free-2.c index eb94651311b..edbcdc74a20 100644 --- a/gcc/testsuite/gcc.dg/free-2.c +++ b/gcc/testsuite/gcc.dg/free-2.c @@ -13,14 +13,14 @@ void foo (void) static char buf4[10], e; char *q = buf; free (p); - free (q); /* At -O0 no warning is reported here. */ - free (buf2); /* { dg-warning "attempt to free a non-heap object" } */ - free (&c); /* { dg-warning "attempt to free a non-heap object" } */ - free (buf3); /* { dg-warning "attempt to free a non-heap object" } */ - free (&d); /* { dg-warning "attempt to free a non-heap object" } */ - free (buf4); /* { dg-warning "attempt to free a non-heap object" } */ - free (&e); /* { dg-warning "attempt to free a non-heap object" } */ + free (q); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (buf2); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (&c); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (buf3); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (&d); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (buf4); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (&e); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ free (&r->a); - free ("abcd"); /* { dg-warning "attempt to free a non-heap object" } */ - free (L"abcd"); /* { dg-warning "attempt to free a non-heap object" } */ + free ("abcd"); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ + free (L"abcd"); /* { dg-warning "\\\[-Wfree-nonheap-object" } */ } diff --git a/gcc/testsuite/gcc.dg/torture/pr71816.c b/gcc/testsuite/gcc.dg/torture/pr71816.c index be37ad921bd..cc143fa6c7d 100644 --- a/gcc/testsuite/gcc.dg/torture/pr71816.c +++ b/gcc/testsuite/gcc.dg/torture/pr71816.c @@ -20,3 +20,7 @@ struct ext2_icount_el *insert_icount_el() { ext2fs_resize_mem(&insert_icount_el_icount_1); return 0; } + +/* Passing the address of a declared object to realloc triggers + -Wfree-nonheap-object unless -flto is used. + { dg-prune-output "\\\[-Wfree-nonheap-object" } */ diff --git a/gcc/testsuite/gcc.dg/tree-ssa/pr19831-2.c b/gcc/testsuite/gcc.dg/tree-ssa/pr19831-2.c index 55b4fd07580..df4120da582 100644 --- a/gcc/testsuite/gcc.dg/tree-ssa/pr19831-2.c +++ b/gcc/testsuite/gcc.dg/tree-ssa/pr19831-2.c @@ -5,7 +5,7 @@ void test1(void) { int *p = __builtin_malloc (sizeof (int) * 4); *p++ = 4; - __builtin_free (p); + __builtin_free (p); // { dg-warning "\\\[-Wfree-nonheap-object" } } /* Undefined. We can't do anything here. */ diff --git a/libstdc++-v3/testsuite/ext/vstring/modifiers/clear/56166.cc b/libstdc++-v3/testsuite/ext/vstring/modifiers/clear/56166.cc index 84dd79eef5e..b5ce78ddc7e 100644 --- a/libstdc++-v3/testsuite/ext/vstring/modifiers/clear/56166.cc +++ b/libstdc++-v3/testsuite/ext/vstring/modifiers/clear/56166.cc @@ -56,12 +56,12 @@ template throw std::bad_alloc(); } } - return (T*)new char[n * sizeof(T)]; + return (T*)operator new (n * sizeof(T)); } void deallocate(T* p, size_type) { - delete[] (char*)p; + operator delete (p); } }; @@ -94,3 +94,7 @@ int main() } } } + +// The __versa_string destructor triggers a bogus -Wfree-nonheap-object +// due to pr54202. +// { dg-prune-output "\\\[-Wfree-nonheap-object" }