diff options
author | Martin Sebor <msebor@redhat.com> | 2017-06-16 03:48:59 +0000 |
---|---|---|
committer | Martin Sebor <msebor@gcc.gnu.org> | 2017-06-15 21:48:59 -0600 |
commit | c3684b7b86da9b6b01f6fb274227fc6401df053e (patch) | |
tree | 921877ef9f69c6bac6285495a3eec052a4fad2a3 /gcc/cp | |
parent | 6a382041ddccd829db190f7c112ecfff2e7d369a (diff) | |
download | gcc-c3684b7b86da9b6b01f6fb274227fc6401df053e.zip gcc-c3684b7b86da9b6b01f6fb274227fc6401df053e.tar.gz gcc-c3684b7b86da9b6b01f6fb274227fc6401df053e.tar.bz2 |
PR c++/80560 - warn on undefined memory operations involving non-trivial types
gcc/c-family/ChangeLog:
PR c++/80560
* c.opt (-Wclass-memaccess): New option.
gcc/cp/ChangeLog:
PR c++/80560
* call.c (first_non_public_field, maybe_warn_class_memaccess): New
functions.
(has_trivial_copy_assign_p, has_trivial_copy_p): Ditto.
(build_cxx_call): Call maybe_warn_class_memaccess.
gcc/ChangeLog:
PR c++/80560
* dumpfile.c (dump_register): Avoid calling memset to initialize
a class with a default ctor.
* gcc.c (struct compiler): Remove const qualification.
* genattrtab.c (gen_insn_reserv): Replace memset with initialization.
* hash-table.h: Ditto.
* ipa-cp.c (allocate_and_init_ipcp_value): Replace memset with
assignment.
* ipa-prop.c (ipa_free_edge_args_substructures): Ditto.
* omp-low.c (lower_omp_ordered_clauses): Replace memset with
default ctor.
* params.h (struct param_info): Make struct members non-const.
* tree-switch-conversion.c (emit_case_bit_tests): Replace memset
with default initialization.
* vec.h (vec_copy_construct, vec_default_construct): New helper
functions.
(vec<T>::copy, vec<T>::splice, vec<T>::reserve): Replace memcpy
with vec_copy_construct.
(vect<T>::quick_grow_cleared): Replace memset with default ctor.
(vect<T>::vec_safe_grow_cleared, vec_safe_grow_cleared): Same.
* doc/invoke.texi (-Wclass-memaccess): Document.
libcpp/ChangeLog:
PR c++/80560
* line-map.c (line_maps::~line_maps): Avoid calling htab_delete
with a null pointer.
(linemap_init): Avoid calling memset on an object of a non-trivial
type.
libitm/ChangeLog:
PR c++/80560
* beginend.cc (GTM::gtm_thread::rollback): Avoid calling memset
on an object of a non-trivial type.
(GTM::gtm_transaction_cp::commit): Use assignment instead of memcpy
to copy an object.
* method-ml.cc (orec_iterator::reinit): Avoid -Wclass-memaccess.
gcc/testsuite/ChangeLog:
PR c++/80560
* g++.dg/Wclass-memaccess.C: New test.
From-SVN: r249234
Diffstat (limited to 'gcc/cp')
-rw-r--r-- | gcc/cp/ChangeLog | 8 | ||||
-rw-r--r-- | gcc/cp/call.c | 391 |
2 files changed, 399 insertions, 0 deletions
diff --git a/gcc/cp/ChangeLog b/gcc/cp/ChangeLog index b933392..ed307b6 100644 --- a/gcc/cp/ChangeLog +++ b/gcc/cp/ChangeLog @@ -1,3 +1,11 @@ +2017-06-15 Martin Sebor <msebor@redhat.com> + + PR c++/80560 + * call.c (first_non_public_field, maybe_warn_class_memaccess): New + functions. + (has_trivial_copy_assign_p, has_trivial_copy_p): Ditto. + (build_cxx_call): Call maybe_warn_class_memaccess. + 2017-06-14 Jakub Jelinek <jakub@redhat.com> * cp-gimplify.c (cp_genericize_r): Turn most of the function diff --git a/gcc/cp/call.c b/gcc/cp/call.c index ef99683..9c3f1eb 100644 --- a/gcc/cp/call.c +++ b/gcc/cp/call.c @@ -8184,6 +8184,393 @@ build_over_call (struct z_candidate *cand, int flags, tsubst_flags_t complain) return call; } +/* Return the DECL of the first non-public data member of class TYPE + or null if none can be found. */ + +static tree +first_non_public_field (tree type) +{ + if (!CLASS_TYPE_P (type)) + return NULL_TREE; + + for (tree field = TYPE_FIELDS (type); field; field = DECL_CHAIN (field)) + { + if (TREE_CODE (field) != FIELD_DECL) + continue; + if (TREE_STATIC (field)) + continue; + if (TREE_PRIVATE (field) || TREE_PROTECTED (field)) + return field; + } + + int i = 0; + + for (tree base_binfo, binfo = TYPE_BINFO (type); + BINFO_BASE_ITERATE (binfo, i, base_binfo); i++) + { + tree base = TREE_TYPE (base_binfo); + + if (tree field = first_non_public_field (base)) + return field; + } + + return NULL_TREE; +} + +/* Return true if all copy and move assignment operator overloads for + class TYPE are trivial and at least one of them is not deleted and, + when ACCESS is set, accessible. Return false otherwise. Set + HASASSIGN to true when the TYPE has a (not necessarily trivial) + copy or move assignment. */ + +static bool +has_trivial_copy_assign_p (tree type, bool access, bool *hasassign) +{ + tree fns = cp_assignment_operator_id (NOP_EXPR); + fns = lookup_fnfields_slot (type, fns); + + bool all_trivial = true; + + /* Iterate over overloads of the assignment operator, checking + accessible copy assignments for triviality. */ + + for (ovl_iterator oi (fns); oi; ++oi) + { + tree f = *oi; + + /* Skip operators that aren't copy assignments. */ + if (!copy_fn_p (f)) + continue; + + bool accessible = (!access || !(TREE_PRIVATE (f) || TREE_PROTECTED (f)) + || accessible_p (TYPE_BINFO (type), f, true)); + + /* Skip template assignment operators and deleted functions. */ + if (TREE_CODE (f) != FUNCTION_DECL || DECL_DELETED_FN (f)) + continue; + + if (accessible) + *hasassign = true; + + if (!accessible || !trivial_fn_p (f)) + all_trivial = false; + + /* Break early when both properties have been determined. */ + if (*hasassign && !all_trivial) + break; + } + + /* Return true if they're all trivial and one of the expressions + TYPE() = TYPE() or TYPE() = (TYPE&)() is valid. */ + tree ref = cp_build_reference_type (type, false); + return (all_trivial + && (is_trivially_xible (MODIFY_EXPR, type, type) + || is_trivially_xible (MODIFY_EXPR, type, ref))); +} + +/* Return true if all copy and move ctor overloads for class TYPE are + trivial and at least one of them is not deleted and, when ACCESS is + set, accessible. Return false otherwise. Set each element of HASCTOR[] + to true when the TYPE has a (not necessarily trivial) default and copy + (or move) ctor, respectively. */ + +static bool +has_trivial_copy_p (tree type, bool access, bool hasctor[2]) +{ + tree fns = lookup_fnfields_slot (type, complete_ctor_identifier); + + bool all_trivial = true; + + for (ovl_iterator oi (fns); oi; ++oi) + { + tree f = *oi; + + /* Skip template constructors. */ + if (TREE_CODE (f) != FUNCTION_DECL) + continue; + + bool cpy_or_move_ctor_p = copy_fn_p (f); + + /* Skip ctors other than default, copy, and move. */ + if (!cpy_or_move_ctor_p && !default_ctor_p (f)) + continue; + + if (DECL_DELETED_FN (f)) + continue; + + bool accessible = (!access || !(TREE_PRIVATE (f) || TREE_PROTECTED (f)) + || accessible_p (TYPE_BINFO (type), f, true)); + + if (accessible) + hasctor[cpy_or_move_ctor_p] = true; + + if (cpy_or_move_ctor_p && (!accessible || !trivial_fn_p (f))) + all_trivial = false; + + /* Break early when both properties have been determined. */ + if (hasctor[0] && hasctor[1] && !all_trivial) + break; + } + + return all_trivial; +} + +/* Issue a warning on a call to the built-in function FNDECL if it is + a raw memory write whose destination is not an object of (something + like) trivial or standard layout type with a non-deleted assignment + and copy ctor. Detects const correctness violations, corrupting + references, virtual table pointers, and bypassing non-trivial + assignments. */ + +static void +maybe_warn_class_memaccess (location_t loc, tree fndecl, tree *args) +{ + /* Except for bcopy where it's second, the destination pointer is + the first argument for all functions handled here. Compute + the index of the destination and source arguments. */ + unsigned dstidx = DECL_FUNCTION_CODE (fndecl) == BUILT_IN_BCOPY; + unsigned srcidx = !dstidx; + + tree dest = args[dstidx]; + if (!dest || !TREE_TYPE (dest) || !POINTER_TYPE_P (TREE_TYPE (dest))) + return; + + STRIP_NOPS (dest); + + tree srctype = NULL_TREE; + + /* Determine the type of the pointed-to object and whether it's + a complete class type. */ + tree desttype = TREE_TYPE (TREE_TYPE (dest)); + + if (!desttype || !COMPLETE_TYPE_P (desttype) || !CLASS_TYPE_P (desttype)) + return; + + /* Check to see if the raw memory call is made by a ctor or dtor + with this as the destination argument for the destination type. + If so, be more permissive. */ + if (current_function_decl + && (DECL_CONSTRUCTOR_P (current_function_decl) + || DECL_DESTRUCTOR_P (current_function_decl)) + && is_this_parameter (dest)) + { + tree ctx = DECL_CONTEXT (current_function_decl); + bool special = same_type_ignoring_top_level_qualifiers_p (ctx, desttype); + + tree binfo = TYPE_BINFO (ctx); + + /* A ctor and dtor for a class with no bases and no virtual functions + can do whatever they want. Bail early with no further checking. */ + if (special && !BINFO_VTABLE (binfo) && !BINFO_N_BASE_BINFOS (binfo)) + return; + } + + /* True if the class is trivial. */ + bool trivial = trivial_type_p (desttype); + + /* Set to true if DESTYPE has an accessible copy assignment. */ + bool hasassign = false; + /* True if all of the class' overloaded copy assignment operators + are all trivial (and not deleted) and at least one of them is + accessible. */ + bool trivassign = has_trivial_copy_assign_p (desttype, true, &hasassign); + + /* Set to true if DESTTYPE has an accessible default and copy ctor, + respectively. */ + bool hasctors[2] = { false, false }; + + /* True if all of the class' overloaded copy constructors are all + trivial (and not deleted) and at least one of them is accessible. */ + bool trivcopy = has_trivial_copy_p (desttype, true, hasctors); + + /* Set FLD to the first private/protected member of the class. */ + tree fld = trivial ? first_non_public_field (desttype) : NULL_TREE; + + /* The warning format string. */ + const char *warnfmt = NULL; + /* A suggested alternative to offer instead of the raw memory call. + Empty string when none can be come up with. */ + const char *suggest = ""; + bool warned = false; + + switch (DECL_FUNCTION_CODE (fndecl)) + { + case BUILT_IN_MEMSET: + if (!integer_zerop (args[1])) + { + /* Diagnose setting non-copy-assignable or non-trivial types, + or types with a private member, to (potentially) non-zero + bytes. Since the value of the bytes being written is unknown, + suggest using assignment instead (if one exists). Also warn + for writes into objects for which zero-initialization doesn't + mean all bits clear (pointer-to-member data, where null is all + bits set). Since the value being written is (most likely) + non-zero, simply suggest assignment (but not copy assignment). */ + suggest = "; use assignment instead"; + if (!trivassign) + warnfmt = G_("%qD writing to an object of type %#qT with " + "no trivial copy-assignment"); + else if (!trivial) + warnfmt = G_("%qD writing to an object of non-trivial type %#qT%s"); + else if (fld) + { + const char *access = TREE_PRIVATE (fld) ? "private" : "protected"; + warned = warning_at (loc, OPT_Wclass_memaccess, + "%qD writing to an object of type %#qT with " + "%qs member %qD", + fndecl, desttype, access, fld); + } + else if (!zero_init_p (desttype)) + warnfmt = G_("%qD writing to an object of type %#qT containing " + "a pointer to data member%s"); + + break; + } + /* Fall through. */ + + case BUILT_IN_BZERO: + /* Similarly to the above, diagnose clearing non-trivial or non- + standard layout objects, or objects of types with no assignmenmt. + Since the value being written is known to be zero, suggest either + copy assignment, copy ctor, or default ctor as an alternative, + depending on what's available. */ + + if (hasassign && hasctors[0]) + suggest = G_("; use assignment or value-initialization instead"); + else if (hasassign) + suggest = G_("; use assignment instead"); + else if (hasctors[0]) + suggest = G_("; use value-initialization instead"); + + if (!trivassign) + warnfmt = G_("%qD clearing an object of type %#qT with " + "no trivial copy-assignment%s"); + else if (!trivial) + warnfmt = G_("%qD clearing an object of non-trivial type %#qT%s"); + else if (!zero_init_p (desttype)) + warnfmt = G_("%qD clearing an object of type %#qT containing " + "a pointer-to-member%s"); + break; + + case BUILT_IN_BCOPY: + case BUILT_IN_MEMCPY: + case BUILT_IN_MEMMOVE: + case BUILT_IN_MEMPCPY: + /* Determine the type of the source object. */ + srctype = STRIP_NOPS (args[srcidx]); + srctype = TREE_TYPE (TREE_TYPE (srctype)); + + /* Since it's impossible to determine wheter the byte copy is + being used in place of assignment to an existing object or + as a substitute for initialization, assume it's the former. + Determine the best alternative to use instead depending on + what's not deleted. */ + if (hasassign && hasctors[1]) + suggest = G_("; use copy-assignment or copy-initialization instead"); + else if (hasassign) + suggest = G_("; use copy-assignment instead"); + else if (hasctors[1]) + suggest = G_("; use copy-initialization instead"); + + if (!trivassign) + warnfmt = G_("%qD writing to an object of type %#qT with no trivial " + "copy-assignment%s"); + else if (!trivially_copyable_p (desttype)) + warnfmt = G_("%qD writing to an object of non-trivially copyable " + "type %#qT%s"); + else if (!trivcopy) + warnfmt = G_("%qD writing to an object with a deleted copy constructor"); + + else if (!trivial + && !VOID_TYPE_P (srctype) + && !char_type_p (TYPE_MAIN_VARIANT (srctype)) + && !same_type_ignoring_top_level_qualifiers_p (desttype, + srctype)) + { + /* Warn when copying into a non-trivial object from an object + of a different type other than void or char. */ + warned = warning_at (loc, OPT_Wclass_memaccess, + "%qD copying an object of non-trivial type " + "%#qT from an array of %#qT", + fndecl, desttype, srctype); + } + else if (fld + && !VOID_TYPE_P (srctype) + && !char_type_p (TYPE_MAIN_VARIANT (srctype)) + && !same_type_ignoring_top_level_qualifiers_p (desttype, + srctype)) + { + const char *access = TREE_PRIVATE (fld) ? "private" : "protected"; + warned = warning_at (loc, OPT_Wclass_memaccess, + "%qD copying an object of type %#qT with " + "%qs member %qD from an array of %#qT; use " + "assignment or copy-initialization instead", + fndecl, desttype, access, fld, srctype); + } + else if (!trivial && TREE_CODE (args[2]) == INTEGER_CST) + { + /* Finally, warn on partial copies. */ + unsigned HOST_WIDE_INT typesize + = tree_to_uhwi (TYPE_SIZE_UNIT (desttype)); + if (unsigned HOST_WIDE_INT partial + = tree_to_uhwi (args[2]) % typesize) + warned = warning_at (loc, OPT_Wclass_memaccess, + (typesize - partial > 1 + ? G_("%qD writing to an object of " + "a non-trivial type %#qT leaves %wu " + "bytes unchanged") + : G_("%qD writing to an object of " + "a non-trivial type %#qT leaves %wu " + "byte unchanged")), + fndecl, desttype, typesize - partial); + } + break; + + case BUILT_IN_REALLOC: + + if (!trivially_copyable_p (desttype)) + warnfmt = G_("%qD moving an object of non-trivially copyable type " + "%#qT; use %<new%> and %<delete%> instead"); + else if (!trivcopy) + warnfmt = G_("%qD moving an object of type %#qT with deleted copy " + "constructor; use %<new%> and %<delete%> instead"); + else if (!get_dtor (desttype, tf_none)) + warnfmt = G_("%qD moving an object of type %#qT with deleted " + "destructor"); + else if (!trivial + && TREE_CODE (args[1]) == INTEGER_CST + && tree_int_cst_lt (args[1], TYPE_SIZE_UNIT (desttype))) + { + /* Finally, warn on reallocation into insufficient space. */ + warned = warning_at (loc, OPT_Wclass_memaccess, + "%qD moving an object of non-trivial type " + "%#qT and size %E into a region of size %E", + fndecl, desttype, TYPE_SIZE_UNIT (desttype), + args[1]); + } + break; + + default: + return; + } + + if (!warned && !warnfmt) + return; + + if (warnfmt) + { + if (suggest) + warned = warning_at (loc, OPT_Wclass_memaccess, + warnfmt, fndecl, desttype, suggest); + else + warned = warning_at (loc, OPT_Wclass_memaccess, + warnfmt, fndecl, desttype); + } + + if (warned) + inform (location_of (desttype), "%#qT declared here", desttype); +} + /* Build and return a call to FN, using NARGS arguments in ARGARRAY. This function performs no overload resolution, conversion, or other high-level operations. */ @@ -8216,6 +8603,10 @@ build_cxx_call (tree fn, int nargs, tree *argarray, if (!check_builtin_function_arguments (EXPR_LOCATION (fn), vNULL, fndecl, nargs, argarray)) return error_mark_node; + + /* Warn if the built-in writes to an object of a non-trivial type. */ + if (nargs) + maybe_warn_class_memaccess (loc, fndecl, argarray); } /* If it is a built-in array notation function, then the return type of |