diff options
author | Martin Sebor <msebor@redhat.com> | 2017-12-16 23:58:34 +0000 |
---|---|---|
committer | Martin Sebor <msebor@gcc.gnu.org> | 2017-12-16 16:58:34 -0700 |
commit | cc8bea0aeeeeb6ed8046ede1a577ee681da2ca6a (patch) | |
tree | 8ab48ffcd2c1abce152c9686d64db508a4f40a96 /gcc/gimple-ssa-warn-restrict.c | |
parent | d43568222a4564e22a6ffd370481e11ba031b318 (diff) | |
download | gcc-cc8bea0aeeeeb6ed8046ede1a577ee681da2ca6a.zip gcc-cc8bea0aeeeeb6ed8046ede1a577ee681da2ca6a.tar.gz gcc-cc8bea0aeeeeb6ed8046ede1a577ee681da2ca6a.tar.bz2 |
PR tree-optimization/78918 - missing -Wrestrict on memcpy copying over self
gcc/c-family/ChangeLog:
PR tree-optimization/78918
* c-common.c (check_function_restrict): Avoid checking built-ins.
* c.opt (-Wrestrict): Include in -Wall.
gcc/ChangeLog:
PR tree-optimization/78918
* Makefile.in (OBJS): Add gimple-ssa-warn-restrict.o.
* builtins.c (check_sizes): Rename...
(check_access): ...to this. Rename function arguments for clarity.
(check_memop_sizes): Adjust names.
(expand_builtin_memchr, expand_builtin_memcpy): Same.
(expand_builtin_memmove, expand_builtin_mempcpy): Same.
(expand_builtin_strcat, expand_builtin_stpncpy): Same.
(check_strncat_sizes, expand_builtin_strncat): Same.
(expand_builtin_strncpy, expand_builtin_memset): Same.
(expand_builtin_bzero, expand_builtin_memcmp): Same.
(expand_builtin_memory_chk, maybe_emit_chk_warning): Same.
(maybe_emit_sprintf_chk_warning): Same.
(expand_builtin_strcpy): Adjust.
(expand_builtin_stpcpy): Same.
(expand_builtin_with_bounds): Detect out-of-bounds accesses
in pointer-checking forms of memcpy, memmove, and mempcpy.
(gcall_to_tree_minimal, max_object_size): Define new functions.
* builtins.h (max_object_size): Declare.
* calls.c (alloc_max_size): Call max_object_size instead of
hardcoding ssizetype limit.
(get_size_range): Handle new argument.
* calls.h (get_size_range): Add a new argument.
* cfgexpand.c (expand_call_stmt): Propagate no-warning bit.
* doc/invoke.texi (-Wrestrict): Adjust, add example.
* gimple-fold.c (gimple_fold_builtin_memory_op): Detect overlapping
operations.
(gimple_fold_builtin_memory_chk): Same.
(gimple_fold_builtin_stxcpy_chk): New function.
* gimple-ssa-warn-restrict.c: New source.
* gimple-ssa-warn-restrict.h: New header.
* gimple.c (gimple_build_call_from_tree): Propagate location.
* passes.def (pass_warn_restrict): Add new pass.
* tree-pass.h (make_pass_warn_restrict): Declare.
* tree-ssa-strlen.c (handle_builtin_strcpy): Detect overlapping
operations.
(handle_builtin_strcat): Same.
(strlen_optimize_stmt): Rename...
(strlen_check_and_optimize_stmt): ...to this. Handle strncat,
stpncpy, strncpy, and their checking forms.
gcc/testsuite/ChangeLog:
PR tree-optimization/78918
* c-c++-common/Warray-bounds.c: New test.
* c-c++-common/Warray-bounds-2.c: New test.
* c-c++-common/Warray-bounds-3.c: New test.
* c-c++-common/Warray-bounds-4.c: New test.
* c-c++-common/Warray-bounds-5.c: New test.
* c-c++-common/Wrestrict-2.c: New test.
* c-c++-common/Wrestrict.c: New test.
* c-c++-common/Wrestrict.s: New test.
* c-c++-common/Wsizeof-pointer-memaccess1.c: Adjust
* c-c++-common/Wsizeof-pointer-memaccess2.c: Same.
* g++.dg/torture/Wsizeof-pointer-memaccess1.C: Same.
* g++.dg/torture/Wsizeof-pointer-memaccess2.C: Same.
* gcc.dg/range.h: New header.
* gcc.dg/memcpy-6.c: New test.
* gcc.dg/pr69172.c: Adjust.
* gcc.dg/pr79223.c: Same.
* gcc.dg/pr81345.c: Adjust.
* gcc.dg/Wobjsize-1.c: Same.
* gcc.dg/Wrestrict-2.c: New test.
* gcc.dg/Wrestrict.c: New test.
* gcc.dg/Wsizeof-pointer-memaccess1.c: Adjust.
* gcc.dg/builtin-stpncpy.c: Same.
* gcc.dg/builtin-stringop-chk-1.c: Same.
* gcc.target/i386/chkp-stropt-17.c: New test.
* gcc.dg/torture/Wsizeof-pointer-memaccess1.c: Adjust.
From-SVN: r255755
Diffstat (limited to 'gcc/gimple-ssa-warn-restrict.c')
-rw-r--r-- | gcc/gimple-ssa-warn-restrict.c | 1761 |
1 files changed, 1761 insertions, 0 deletions
diff --git a/gcc/gimple-ssa-warn-restrict.c b/gcc/gimple-ssa-warn-restrict.c new file mode 100644 index 0000000..f524e1d --- /dev/null +++ b/gcc/gimple-ssa-warn-restrict.c @@ -0,0 +1,1761 @@ +/* Pass to detect and issue warnings for violations of the restrict + qualifier. + Copyright (C) 2017 Free Software Foundation, Inc. + Contributed by Martin Sebor <msebor@redhat.com>. + + This file is part of GCC. + + GCC is free software; you can redistribute it and/or modify it under + the terms of the GNU General Public License as published by the Free + Software Foundation; either version 3, or (at your option) any later + version. + + GCC is distributed in the hope that it will be useful, but WITHOUT ANY + WARRANTY; without even the implied warranty of MERCHANTABILITY or + FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License + for more details. + + You should have received a copy of the GNU General Public License + along with GCC; see the file COPYING3. If not see + <http://www.gnu.org/licenses/>. */ + +#include "config.h" +#include "system.h" +#include "coretypes.h" +#include "backend.h" +#include "tree.h" +#include "gimple.h" +#include "domwalk.h" +#include "tree-pass.h" +#include "builtins.h" +#include "ssa.h" +#include "gimple-pretty-print.h" +#include "gimple-ssa-warn-restrict.h" +#include "diagnostic-core.h" +#include "fold-const.h" +#include "gimple-iterator.h" +#include "tree-dfa.h" +#include "tree-ssa.h" +#include "params.h" +#include "tree-cfg.h" +#include "tree-object-size.h" +#include "calls.h" +#include "cfgloop.h" +#include "intl.h" + +namespace { + +const pass_data pass_data_wrestrict = { + GIMPLE_PASS, + "wrestrict", + OPTGROUP_NONE, + TV_NONE, + PROP_cfg, /* Properties_required. */ + 0, /* properties_provided. */ + 0, /* properties_destroyed. */ + 0, /* properties_start */ + 0, /* properties_finish */ +}; + +/* Pass to detect violations of strict aliasing requirements in calls + to built-in string and raw memory functions. */ +class pass_wrestrict : public gimple_opt_pass +{ + public: + pass_wrestrict (gcc::context *ctxt) + : gimple_opt_pass (pass_data_wrestrict, ctxt) + { } + + opt_pass *clone () { return new pass_wrestrict (m_ctxt); } + + virtual bool gate (function *); + virtual unsigned int execute (function *); +}; + +bool +pass_wrestrict::gate (function *fun ATTRIBUTE_UNUSED) +{ + return warn_array_bounds != 0 || warn_restrict != 0; +} + +/* Class to walk the basic blocks of a function in dominator order. */ +class wrestrict_dom_walker : public dom_walker +{ + public: + wrestrict_dom_walker () : dom_walker (CDI_DOMINATORS) {} + + edge before_dom_children (basic_block) FINAL OVERRIDE; + bool handle_gimple_call (gimple_stmt_iterator *); + + private: + void check_call (gcall *); +}; + +edge +wrestrict_dom_walker::before_dom_children (basic_block bb) +{ + /* Iterate over statements, looking for function calls. */ + for (gimple_stmt_iterator si = gsi_start_bb (bb); !gsi_end_p (si); + gsi_next (&si)) + { + gimple *stmt = gsi_stmt (si); + if (!is_gimple_call (stmt)) + continue; + + if (gcall *call = as_a <gcall *> (stmt)) + check_call (call); + } + + return NULL; +} + +/* Execute the pass for function FUN, walking in dominator order. */ + +unsigned +pass_wrestrict::execute (function *fun) +{ + calculate_dominance_info (CDI_DOMINATORS); + + wrestrict_dom_walker walker; + walker.walk (ENTRY_BLOCK_PTR_FOR_FN (fun)); + + return 0; +} + +/* Description of a memory reference by a built-in function. This + is similar to ao_ref but made especially suitable for -Wrestrict + and not for optimization. */ +struct builtin_memref +{ + /* The original pointer argument to the built-in function. */ + tree ptr; + /* The referenced subobject or NULL if not available, and the base + object of the memory reference or NULL. */ + tree ref; + tree base; + + /* The size of the BASE object, PTRDIFF_MAX if indeterminate, + and negative until (possibly lazily) initialized. */ + offset_int basesize; + + /* The non-negative offset of the referenced subobject. Used to avoid + warnings for (apparently) possibly but not definitively overlapping + accesses to member arrays. Negative when unknown/invalid. */ + offset_int refoff; + + /* The offset range relative to the base. */ + offset_int offrange[2]; + /* The size range of the access to this reference. */ + offset_int sizrange[2]; + + /* True for "bounded" string functions like strncat, and strncpy + and their variants that specify either an exact or upper bound + on the size of the accesses they perform. For strncat both + the source and destination references are bounded. For strncpy + only the destination reference is. */ + bool strbounded_p; + + builtin_memref (tree, tree); + + tree offset_out_of_bounds (int, offset_int[2]) const; +}; + +/* Description of a memory access by a raw memory or string built-in + function involving a pair of builtin_memref's. */ +class builtin_access +{ + public: + /* Destination and source memory reference. */ + builtin_memref* const dstref; + builtin_memref* const srcref; + /* The size range of the access. It's the greater of the accesses + to the two references. */ + HOST_WIDE_INT sizrange[2]; + + /* The minimum and maximum offset of an overlap of the access + (if it does, in fact, overlap), and the size of the overlap. */ + HOST_WIDE_INT ovloff[2]; + HOST_WIDE_INT ovlsiz[2]; + + /* True to consider valid only accesses to the smallest subobject + and false for raw memory functions. */ + bool strict () const + { + return detect_overlap != &builtin_access::generic_overlap; + } + + builtin_access (gcall *, builtin_memref &, builtin_memref &); + + /* Entry point to determine overlap. */ + bool overlap (); + + private: + /* Implementation functions used to determine overlap. */ + bool generic_overlap (); + bool strcat_overlap (); + bool strcpy_overlap (); + + bool no_overlap () + { + return false; + } + + offset_int overlap_size (const offset_int [2], const offset_int[2], + offset_int [2]); + + private: + /* Temporaries used to compute the final result. */ + offset_int dstoff[2]; + offset_int srcoff[2]; + offset_int dstsiz[2]; + offset_int srcsiz[2]; + + /* Pointer to a member function to call to determine overlap. */ + bool (builtin_access::*detect_overlap) (); +}; + +/* Initialize a memory reference representation from a pointer EXPR and + a size SIZE in bytes. If SIZE is NULL_TREE then the size is assumed + to be unknown. */ + +builtin_memref::builtin_memref (tree expr, tree size) +: ptr (expr), + ref (), + base (), + basesize (-1), + refoff (HOST_WIDE_INT_MIN), + offrange (), + sizrange (), + strbounded_p () +{ + /* Unfortunately, wide_int default ctor is a no-op so array members + of the type must be set individually. */ + offrange[0] = offrange[1] = 0; + sizrange[0] = sizrange[1] = 0; + + const offset_int maxobjsize = tree_to_shwi (max_object_size ()); + + if (TREE_CODE (expr) == SSA_NAME) + { + /* Try to tease the offset out of the pointer. */ + gimple *stmt = SSA_NAME_DEF_STMT (expr); + if (gimple_assign_single_p (stmt) + && gimple_assign_rhs_code (stmt) == ADDR_EXPR) + expr = gimple_assign_rhs1 (stmt); + else if (is_gimple_assign (stmt)) + { + tree_code code = gimple_assign_rhs_code (stmt); + if (code == NOP_EXPR) + { + tree rhs = gimple_assign_rhs1 (stmt); + if (POINTER_TYPE_P (TREE_TYPE (rhs))) + expr = gimple_assign_rhs1 (stmt); + } + else if (code == POINTER_PLUS_EXPR) + { + expr = gimple_assign_rhs1 (stmt); + + tree offset = gimple_assign_rhs2 (stmt); + if (TREE_CODE (offset) == INTEGER_CST) + { + offset_int off = int_cst_value (offset); + offrange[0] = off; + offrange[1] = off; + + if (TREE_CODE (expr) == SSA_NAME) + { + gimple *stmt = SSA_NAME_DEF_STMT (expr); + if (gimple_assign_single_p (stmt) + && gimple_assign_rhs_code (stmt) == ADDR_EXPR) + expr = gimple_assign_rhs1 (stmt); + } + } + else if (TREE_CODE (offset) == SSA_NAME) + { + wide_int min, max; + value_range_type rng = get_range_info (offset, &min, &max); + if (rng == VR_RANGE) + { + offrange[0] = min.to_shwi (); + offrange[1] = max.to_shwi (); + } + else if (rng == VR_ANTI_RANGE) + { + offrange[0] = (max + 1).to_shwi (); + offrange[1] = (min - 1).to_shwi (); + } + else + { + gimple *stmt = SSA_NAME_DEF_STMT (offset); + if (is_gimple_assign (stmt) + && gimple_assign_rhs_code (stmt) == NOP_EXPR) + { + /* Use the bounds of the type of the NOP_EXPR operand + even if it's signed. The result doesn't trigger + warnings but makes their output more readable. */ + tree type = TREE_TYPE (gimple_assign_rhs1 (stmt)); + offrange[0] = wi::to_offset (TYPE_MIN_VALUE (type)); + offrange[1] = wi::to_offset (TYPE_MAX_VALUE (type)); + } + else + offrange[1] = maxobjsize; + } + } + else + offrange[1] = maxobjsize; + } + } + } + + if (TREE_CODE (expr) == ADDR_EXPR) + { + HOST_WIDE_INT off; + tree oper = TREE_OPERAND (expr, 0); + + /* Determine the base object or pointer of the reference + and its constant offset from the beginning of the base. */ + base = get_addr_base_and_unit_offset (oper, &off); + + if (base) + { + offrange[0] += off; + offrange[1] += off; + + /* Stash the reference for offset validation. */ + ref = oper; + + /* Also stash the constant offset for offset validation. */ + tree_code code = TREE_CODE (oper); + if (code == COMPONENT_REF) + { + tree field = TREE_OPERAND (ref, 1); + tree fldoff = DECL_FIELD_OFFSET (field); + if (TREE_CODE (fldoff) == INTEGER_CST) + refoff = off + wi::to_offset (fldoff); + } + } + else + { + size = NULL_TREE; + base = get_base_address (TREE_OPERAND (expr, 0)); + } + } + + if (!base) + base = build2 (MEM_REF, char_type_node, expr, null_pointer_node); + + if (TREE_CODE (base) == MEM_REF) + { + offset_int off = mem_ref_offset (base); + refoff += off; + offrange[0] += off; + offrange[1] += off; + base = TREE_OPERAND (base, 0); + } + + if (TREE_CODE (base) == SSA_NAME) + if (gimple *stmt = SSA_NAME_DEF_STMT (base)) + { + enum gimple_code code = gimple_code (stmt); + if (code == GIMPLE_ASSIGN) + if (gimple_assign_rhs_code (stmt) == POINTER_PLUS_EXPR) + { + base = gimple_assign_rhs1 (stmt); + + tree offset = gimple_assign_rhs2 (stmt); + if (TREE_CODE (offset) == INTEGER_CST) + { + offset_int off = int_cst_value (offset); + refoff += off; + offrange[0] += off; + offrange[1] += off; + } + } + + if (TREE_CODE (base) == SSA_NAME && SSA_NAME_VAR (base)) + base = SSA_NAME_VAR (base); + } + + if (size) + { + tree range[2]; + /* Determine the size range, allowing for the result to be [0, 0] + for SIZE in the anti-range ~[0, N] where N >= PTRDIFF_MAX. */ + get_size_range (size, range, true); + sizrange[0] = wi::to_offset (range[0]); + sizrange[1] = wi::to_offset (range[1]); + /* get_size_range returns SIZE_MAX for the maximum size. + Constrain it to the real maximum of PTRDIFF_MAX. */ + if (sizrange[1] > maxobjsize) + sizrange[1] = maxobjsize; + } + else + sizrange[1] = maxobjsize; +} + +/* Return error_mark_node if the signed offset exceeds the bounds + of the address space (PTRDIFF_MAX). Otherwise, return either + BASE or REF when the offset exceeds the bounds of the BASE or + REF object, and set OOBOFF to the past-the-end offset formed + by the reference, including its size. When STRICT is non-zero + use REF size, when available, otherwise use BASE size. When + STRICT is greater than 1, use the size of the last array member + as the bound, otherwise treat such a member as a flexible array + member. Return NULL when the offset is in bounds. */ + +tree +builtin_memref::offset_out_of_bounds (int strict, offset_int ooboff[2]) const +{ + const offset_int maxobjsize = tree_to_shwi (max_object_size ()); + + /* A temporary, possibly adjusted, copy of the offset range. */ + offset_int offrng[2] = { offrange[0], offrange[1] }; + + if (DECL_P (base) && TREE_CODE (TREE_TYPE (base)) == ARRAY_TYPE) + { + if (offrng[1] < offrng[0]) + offrng[1] = maxobjsize; + } + + /* Conservative offset of the last byte of the referenced object. */ + offset_int endoff; + + /* The bounds need not be ordered. Set HIB to use as the index + of the larger of the bounds and LOB as the opposite. */ + bool hib = wi::les_p (offrng[0], offrng[1]); + bool lob = !hib; + + if (basesize < 0) + { + endoff = offrng[lob] + sizrange[0]; + + /* For a reference through a pointer to an object of unknown size + all initial offsets are considered valid, positive as well as + negative, since the pointer itself can point past the beginning + of the object. However, the sum of the lower bound of the offset + and that of the size must be less than or equal than PTRDIFF_MAX. */ + if (endoff > maxobjsize) + return error_mark_node; + + return NULL_TREE; + } + + /* A reference to an object of known size must be within the bounds + of the base object. */ + if (offrng[hib] < 0 || offrng[lob] > basesize) + return base; + + /* The extent of the reference must also be within the bounds of + the base object (if known) or the maximum object size otherwise. */ + endoff = wi::smax (offrng[lob], 0) + sizrange[0]; + if (endoff > maxobjsize) + return error_mark_node; + + offset_int size = basesize; + tree obj = base; + + if (strict + && DECL_P (obj) + && ref + && refoff >= 0 + && TREE_CODE (ref) == COMPONENT_REF + && (strict > 1 + || !array_at_struct_end_p (ref))) + { + /* If the reference is to a member subobject, the offset must + be within the bounds of the subobject. */ + tree field = TREE_OPERAND (ref, 1); + tree type = TREE_TYPE (field); + if (tree sz = TYPE_SIZE_UNIT (type)) + if (TREE_CODE (sz) == INTEGER_CST) + { + size = refoff + wi::to_offset (sz); + obj = ref; + } + } + + if (endoff <= size) + return NULL_TREE; + + /* Set the out-of-bounds offset range to be one greater than + that delimited by the reference including its size. */ + ooboff[lob] = size + 1; + + if (endoff > ooboff[lob]) + ooboff[hib] = endoff; + else + ooboff[hib] = wi::smax (offrng[lob], 0) + sizrange[1]; + + return obj; +} + +/* Create an association between the memory references DST and SRC + for access by a call EXPR to a memory or string built-in funtion. */ + +builtin_access::builtin_access (gcall *call, builtin_memref &dst, + builtin_memref &src) +: dstref (&dst), srcref (&src), sizrange (), ovloff (), ovlsiz (), + dstoff (), srcoff (), dstsiz (), srcsiz () +{ + /* Zero out since the offset_int ctors invoked above are no-op. */ + dstoff[0] = dstoff[1] = 0; + srcoff[0] = srcoff[1] = 0; + dstsiz[0] = dstsiz[1] = 0; + srcsiz[0] = srcsiz[1] = 0; + + /* Object Size Type to use to determine the size of the destination + and source objects. Overridden below for raw memory functions. */ + int ostype = 1; + + /* True when the size of one reference depends on the offset of + itself or the other. */ + bool depends_p = true; + + /* True when the size of the destination reference DSTREF has been + determined from SRCREF and so needs to be adjusted by the latter's + offset. Only meaningful for bounded string functions like strncpy. */ + bool dstadjust_p = false; + + /* The size argument number (depends on the built-in). */ + unsigned sizeargno = 2; + if (gimple_call_with_bounds_p (call)) + sizeargno += 2; + + tree func = gimple_call_fndecl (call); + switch (DECL_FUNCTION_CODE (func)) + { + case BUILT_IN_MEMCPY: + case BUILT_IN_MEMCPY_CHK: + case BUILT_IN_MEMCPY_CHKP: + case BUILT_IN_MEMCPY_CHK_CHKP: + case BUILT_IN_MEMPCPY: + case BUILT_IN_MEMPCPY_CHK: + case BUILT_IN_MEMPCPY_CHKP: + case BUILT_IN_MEMPCPY_CHK_CHKP: + ostype = 0; + depends_p = false; + detect_overlap = &builtin_access::generic_overlap; + break; + + case BUILT_IN_MEMMOVE: + case BUILT_IN_MEMMOVE_CHK: + case BUILT_IN_MEMMOVE_CHKP: + case BUILT_IN_MEMMOVE_CHK_CHKP: + /* For memmove there is never any overlap to check for. */ + ostype = 0; + depends_p = false; + detect_overlap = &builtin_access::no_overlap; + break; + + case BUILT_IN_STPNCPY: + case BUILT_IN_STPNCPY_CHK: + case BUILT_IN_STRNCPY: + case BUILT_IN_STRNCPY_CHK: + dstref->strbounded_p = true; + detect_overlap = &builtin_access::strcpy_overlap; + break; + + case BUILT_IN_STPCPY: + case BUILT_IN_STPCPY_CHK: + case BUILT_IN_STPCPY_CHKP: + case BUILT_IN_STPCPY_CHK_CHKP: + case BUILT_IN_STRCPY: + case BUILT_IN_STRCPY_CHK: + case BUILT_IN_STRCPY_CHKP: + case BUILT_IN_STRCPY_CHK_CHKP: + detect_overlap = &builtin_access::strcpy_overlap; + break; + + case BUILT_IN_STRCAT: + case BUILT_IN_STRCAT_CHK: + case BUILT_IN_STRCAT_CHKP: + case BUILT_IN_STRCAT_CHK_CHKP: + detect_overlap = &builtin_access::strcat_overlap; + break; + + case BUILT_IN_STRNCAT: + case BUILT_IN_STRNCAT_CHK: + dstref->strbounded_p = true; + srcref->strbounded_p = true; + detect_overlap = &builtin_access::strcat_overlap; + break; + + default: + /* Handle other string functions here whose access may need + to be validated for in-bounds offsets and non-overlapping + copies. (Not all _chkp functions have BUILT_IN_XXX_CHKP + macros so they need to be handled here.) */ + return; + } + + const offset_int maxobjsize = tree_to_shwi (max_object_size ()); + + /* Try to determine the size of the base object. compute_objsize + expects a pointer so create one if BASE is a non-pointer object. */ + tree addr; + if (dst.basesize < 0) + { + addr = dst.base; + if (!POINTER_TYPE_P (TREE_TYPE (addr))) + addr = build1 (ADDR_EXPR, (TREE_TYPE (addr)), addr); + + if (tree dstsize = compute_objsize (addr, ostype)) + dst.basesize = wi::to_offset (dstsize); + else if (POINTER_TYPE_P (TREE_TYPE (addr))) + dst.basesize = HOST_WIDE_INT_MIN; + else + dst.basesize = maxobjsize; + } + + if (src.basesize < 0) + { + addr = src.base; + if (!POINTER_TYPE_P (TREE_TYPE (addr))) + addr = build1 (ADDR_EXPR, (TREE_TYPE (addr)), addr); + + if (tree srcsize = compute_objsize (addr, ostype)) + src.basesize = wi::to_offset (srcsize); + else if (POINTER_TYPE_P (TREE_TYPE (addr))) + src.basesize = HOST_WIDE_INT_MIN; + else + src.basesize = maxobjsize; + } + + /* If there is no dependency between the references or the base + objects of the two references aren't the same there's nothing + else to do. */ + if (depends_p && dstref->base != srcref->base) + return; + + /* ...otherwise, make adjustments for references to the same object + by string built-in functions to reflect the constraints imposed + by the function. */ + + /* For bounded string functions determine the range of the bound + on the access. For others, the range stays unbounded. */ + offset_int bounds[2] = { maxobjsize, maxobjsize }; + if (dstref->strbounded_p) + { + tree size = gimple_call_arg (call, sizeargno); + tree range[2]; + if (get_size_range (size, range, true)) + { + bounds[0] = wi::to_offset (range[0]); + bounds[1] = wi::to_offset (range[1]); + } + + /* If both references' size ranges are indeterminate use the last + (size) argument from the function call as a substitute. This + may only be necessary for strncpy (but not for memcpy where + the size range would have been already determined this way). */ + if (dstref->sizrange[0] == 0 && dstref->sizrange[1] == maxobjsize + && srcref->sizrange[0] == 0 && srcref->sizrange[1] == maxobjsize) + { + dstref->sizrange[0] = bounds[0]; + dstref->sizrange[1] = bounds[1]; + } + } + + /* The size range of one reference involving the same base object + can be determined from the size range of the other reference. + This makes it possible to compute accurate offsets for warnings + involving functions like strcpy where the length of just one of + the two arguments is known (determined by tree-ssa-strlen). */ + if (dstref->sizrange[0] == 0 && dstref->sizrange[1] == maxobjsize) + { + /* When the destination size is unknown set it to the size of + the source. */ + dstref->sizrange[0] = srcref->sizrange[0]; + dstref->sizrange[1] = srcref->sizrange[1]; + } + else if (srcref->sizrange[0] == 0 && srcref->sizrange[1] == maxobjsize) + { + /* When the source size is unknown set it to the size of + the destination. */ + srcref->sizrange[0] = dstref->sizrange[0]; + srcref->sizrange[1] = dstref->sizrange[1]; + + if (depends_p) + { + if (dstref->strbounded_p) + { + /* Read access by strncpy is bounded. */ + if (bounds[0] < srcref->sizrange[0]) + srcref->sizrange[0] = bounds[0]; + if (bounds[1] < srcref->sizrange[1]) + srcref->sizrange[1] = bounds[1]; + } + + /* For string functions, adjust the size range of the source + reference by the inverse boundaries of the offset (because + the higher the offset into the string the shorter its + length). */ + if (srcref->offrange[1] < srcref->sizrange[0]) + srcref->sizrange[0] -= srcref->offrange[1]; + else + srcref->sizrange[0] = 0; + + if (srcref->offrange[0] > 0) + { + if (srcref->offrange[0] < srcref->sizrange[1]) + srcref->sizrange[1] -= srcref->offrange[0]; + else + srcref->sizrange[1] = 0; + } + + dstadjust_p = true; + } + } + + if (detect_overlap == &builtin_access::generic_overlap) + { + if (dstref->strbounded_p) + { + dstref->sizrange[0] = bounds[0]; + dstref->sizrange[1] = bounds[1]; + + if (dstref->sizrange[0] < srcref->sizrange[0]) + srcref->sizrange[0] = dstref->sizrange[0]; + + if (dstref->sizrange[1] < srcref->sizrange[1]) + srcref->sizrange[1] = dstref->sizrange[1]; + } + } + else if (detect_overlap == &builtin_access::strcpy_overlap) + { + if (!dstref->strbounded_p) + { + /* For strcpy, adjust the destination size range to match that + of the source computed above. */ + if (depends_p && dstadjust_p) + { + dstref->sizrange[0] = srcref->sizrange[0]; + dstref->sizrange[1] = srcref->sizrange[1]; + } + } + } + + if (dstref->strbounded_p) + { + /* For strncpy, adjust the destination size range to match that + of the source computed above. */ + dstref->sizrange[0] = bounds[0]; + dstref->sizrange[1] = bounds[1]; + + if (bounds[0] < srcref->sizrange[0]) + srcref->sizrange[0] = bounds[0]; + + if (bounds[1] < srcref->sizrange[1]) + srcref->sizrange[1] = bounds[1]; + } +} + +offset_int +builtin_access::overlap_size (const offset_int a[2], const offset_int b[2], + offset_int *off) +{ + const offset_int *p = a; + const offset_int *q = b; + + /* Point P at the bigger of the two ranges and Q at the smaller. */ + if (wi::lts_p (a[1] - a[0], b[1] - b[0])) + { + p = b; + q = a; + } + + if (p[0] < q[0]) + { + if (p[1] < q[0]) + return 0; + + *off = q[0]; + return wi::smin (p[1], q[1]) - q[0]; + } + + if (q[1] < p[0]) + return 0; + + off[0] = p[0]; + return q[1] - p[0]; +} + +/* Return true if the bounded mempry (memcpy amd similar) or string function + access (strncpy and similar) ACS overlaps. */ + +bool +builtin_access::generic_overlap () +{ + builtin_access &acs = *this; + const builtin_memref *dstref = acs.dstref; + const builtin_memref *srcref = acs.srcref; + + gcc_assert (dstref->base == srcref->base); + + const offset_int maxobjsize = tree_to_shwi (max_object_size ()); + + offset_int maxsize = dstref->basesize < 0 ? maxobjsize : dstref->basesize; + gcc_assert (maxsize <= maxobjsize); + + /* Adjust the larger bounds of the offsets (which may be the first + element if the lower bound is larger than the upper bound) to + make them valid for the smallest access (if possible) but no smaller + than the smaller bounds. */ + gcc_assert (wi::les_p (acs.dstoff[0], acs.dstoff[1])); + + if (maxsize < acs.dstoff[1] + acs.dstsiz[0]) + acs.dstoff[1] = maxsize - acs.dstsiz[0]; + if (acs.dstoff[1] < acs.dstoff[0]) + acs.dstoff[1] = acs.dstoff[0]; + + gcc_assert (wi::les_p (acs.srcoff[0], acs.srcoff[1])); + + if (maxsize < acs.srcoff[1] + acs.srcsiz[0]) + acs.srcoff[1] = maxsize - acs.srcsiz[0]; + if (acs.srcoff[1] < acs.srcoff[0]) + acs.srcoff[1] = acs.srcoff[0]; + + /* Determine the minimum and maximum space for the access given + the offsets. */ + offset_int space[2]; + space[0] = wi::abs (acs.dstoff[0] - acs.srcoff[0]); + space[1] = space[0]; + + offset_int d = wi::abs (acs.dstoff[0] - acs.srcoff[1]); + if (acs.srcsiz[0] > 0) + { + if (d < space[0]) + space[0] = d; + + if (space[1] < d) + space[1] = d; + } + else + space[1] = acs.dstsiz[1]; + + d = wi::abs (acs.dstoff[1] - acs.srcoff[0]); + if (d < space[0]) + space[0] = d; + + if (space[1] < d) + space[1] = d; + + /* Treat raw memory functions both of whose references are bounded + as special and permit uncertain overlaps to go undetected. For + all kinds of constant offset and constant size accesses, if + overlap isn't certain it is not possible. */ + bool overlap_possible = space[0] < acs.dstsiz[1]; + if (!overlap_possible) + return false; + + bool overlap_certain = space[1] < acs.dstsiz[0]; + + /* True when the size of one reference depends on the offset of + the other. */ + bool depends_p = detect_overlap != &builtin_access::generic_overlap; + + if (!overlap_certain + && !dstref->strbounded_p + && !depends_p) + return false; + + /* True for stpcpy and strcpy. */ + bool stxcpy_p = (!dstref->strbounded_p + && detect_overlap == &builtin_access::strcpy_overlap); + + if (dstref->refoff >= 0 + && srcref->refoff >= 0 + && dstref->refoff != srcref->refoff + && (stxcpy_p || dstref->strbounded_p || srcref->strbounded_p)) + return false; + + offset_int siz[2] = { maxobjsize + 1, 0 }; + + ovloff[0] = HOST_WIDE_INT_MAX; + ovloff[1] = HOST_WIDE_INT_MIN; + + /* Adjustment to the lower bound of the offset of the overlap to + account for a subset of unbounded string calls where the size + of the destination string depends on the length of the source + which in turn depends on the offset into it. */ + bool sub1; + + if (stxcpy_p) + { + sub1 = acs.dstoff[0] <= acs.srcoff[0]; + + /* Iterate over the extreme locations (on the horizontal axis formed + by their offsets) and sizes of two regions and find their smallest + and largest overlap and the corresponding offsets. */ + for (unsigned i = 0; i != 2; ++i) + { + const offset_int a[2] = { + acs.dstoff[i], acs.dstoff[i] + acs.dstsiz[!i] + }; + + const offset_int b[2] = { + acs.srcoff[i], acs.srcoff[i] + acs.srcsiz[!i] + }; + + offset_int off; + offset_int sz = overlap_size (a, b, &off); + if (sz < siz[0]) + siz[0] = sz; + + if (siz[1] <= sz) + siz[1] = sz; + + if (sz != 0) + { + if (wi::lts_p (off, ovloff[0])) + ovloff[0] = off.to_shwi (); + if (wi::lts_p (ovloff[1], off)) + ovloff[1] = off.to_shwi (); + } + } + } + else + { + sub1 = !depends_p; + + /* Iterate over the extreme locations (on the horizontal axis + formed by their offsets) and sizes of two regions and find + their smallest and largest overlap and the corresponding + offsets. */ + + for (unsigned io = 0; io != 2; ++io) + for (unsigned is = 0; is != 2; ++is) + { + const offset_int a[2] = { + acs.dstoff[io], acs.dstoff[io] + acs.dstsiz[is] + }; + + for (unsigned jo = 0; jo != 2; ++jo) + for (unsigned js = 0; js != 2; ++js) + { + if (depends_p) + { + /* For st{p,r}ncpy the size of the source sequence + depends on the offset into it. */ + if (js) + break; + js = !jo; + } + + const offset_int b[2] = { + acs.srcoff[jo], acs.srcoff[jo] + acs.srcsiz[js] + }; + + offset_int off; + offset_int sz = overlap_size (a, b, &off); + if (sz < siz[0]) + siz[0] = sz; + + if (siz[1] <= sz) + siz[1] = sz; + + if (sz != 0) + { + if (wi::lts_p (off, ovloff[0])) + ovloff[0] = off.to_shwi (); + if (wi::lts_p (ovloff[1], off)) + ovloff[1] = off.to_shwi (); + } + } + } + } + + ovlsiz[0] = siz[0].to_shwi (); + ovlsiz[1] = siz[1].to_shwi (); + + if (ovlsiz[0] == 0 && ovlsiz[1] > 1) + ovloff[0] = ovloff[1] + ovlsiz[1] - 1 - sub1; + + return true; +} + +/* Return true if the strcat-like access overlaps. */ + +bool +builtin_access::strcat_overlap () +{ + builtin_access &acs = *this; + const builtin_memref *dstref = acs.dstref; + const builtin_memref *srcref = acs.srcref; + + gcc_assert (dstref->base == srcref->base); + + const offset_int maxobjsize = tree_to_shwi (max_object_size ()); + + gcc_assert (dstref->base && dstref->base == srcref->base); + + /* Adjust for strcat-like accesses. */ + + /* As a special case for strcat, set the DSTREF offsets to the length + of the source string since the function starts writing at the first + nul, and set the size to 1 for the length of the nul. */ + acs.dstoff[0] += acs.dstsiz[0]; + acs.dstoff[1] += acs.dstsiz[1]; + + bool strfunc_unknown_args = acs.dstsiz[0] == 0 && acs.dstsiz[1] != 0; + + /* The lower bound is zero when the size is unknown because then + overlap is not certain. */ + acs.dstsiz[0] = strfunc_unknown_args ? 0 : 1; + acs.dstsiz[1] = 1; + + offset_int maxsize = dstref->basesize < 0 ? maxobjsize : dstref->basesize; + gcc_assert (maxsize <= maxobjsize); + + /* For references to the same base object, determine if there's a pair + of valid offsets into the two references such that access between + them doesn't overlap. Adjust both upper bounds to be valid for + the smaller size (i.e., at most MAXSIZE - SIZE). */ + + if (maxsize < acs.dstoff[1] + acs.dstsiz[0]) + acs.dstoff[1] = maxsize - acs.dstsiz[0]; + + if (maxsize < acs.srcoff[1] + acs.srcsiz[0]) + acs.srcoff[1] = maxsize - acs.srcsiz[0]; + + /* Check to see if there's enough space for both accesses without + overlap. Determine the optimistic (maximum) amount of available + space. */ + offset_int space; + if (acs.dstoff[0] <= acs.srcoff[0]) + { + if (acs.dstoff[1] < acs.srcoff[1]) + space = acs.srcoff[1] + acs.srcsiz[0] - acs.dstoff[0]; + else + space = acs.dstoff[1] + acs.dstsiz[0] - acs.srcoff[0]; + } + else + space = acs.dstoff[1] + acs.dstsiz[0] - acs.srcoff[0]; + + /* Overlap is certain if the distance between the farthest offsets + of the opposite accesses is less than the sum of the lower bounds + of the sizes of the two accesses. */ + bool overlap_certain = space < acs.dstsiz[0] + acs.srcsiz[0]; + + /* For a constant-offset, constant size access, consider the largest + distance between the offset bounds and the lower bound of the access + size. If the overlap isn't certain return success. */ + if (!overlap_certain + && acs.dstoff[0] == acs.dstoff[1] + && acs.srcoff[0] == acs.srcoff[1] + && acs.dstsiz[0] == acs.dstsiz[1] + && acs.srcsiz[0] == acs.srcsiz[1]) + return false; + + /* Overlap is not certain but may be possible. */ + + offset_int access_min = acs.dstsiz[0] + acs.srcsiz[0]; + + /* Determine the conservative (minimum) amount of space. */ + space = wi::abs (acs.dstoff[0] - acs.srcoff[0]); + offset_int d = wi::abs (acs.dstoff[0] - acs.srcoff[1]); + if (d < space) + space = d; + d = wi::abs (acs.dstoff[1] - acs.srcoff[0]); + if (d < space) + space = d; + + /* For a strict test (used for strcpy and similar with unknown or + variable bounds or sizes), consider the smallest distance between + the offset bounds and either the upper bound of the access size + if known, or the lower bound otherwise. */ + if (access_min <= space && (access_min != 0 || !strfunc_unknown_args)) + return false; + + /* When strcat overlap is certain it is always a single byte: + the terminatinn NUL, regardless of offsets and sizes. When + overlap is only possible its range is [0, 1]. */ + acs.ovlsiz[0] = dstref->sizrange[0] == dstref->sizrange[1] ? 1 : 0; + acs.ovlsiz[1] = 1; + acs.ovloff[0] = (dstref->sizrange[0] + dstref->offrange[0]).to_shwi (); + acs.ovloff[1] = (dstref->sizrange[1] + dstref->offrange[1]).to_shwi (); + + acs.sizrange[0] = wi::smax (acs.dstsiz[0], srcref->sizrange[0]).to_shwi (); + acs.sizrange[1] = wi::smax (acs.dstsiz[1], srcref->sizrange[1]).to_shwi (); + return true; +} + +/* Return true if the strcpy-like access overlaps. */ + +bool +builtin_access::strcpy_overlap () +{ + return generic_overlap (); +} + + +/* Return true if DSTREF and SRCREF describe accesses that either overlap + one another or that, in order not to overlap, would imply that the size + of the referenced object(s) exceeds the maximum size of an object. Set + Otherwise, if DSTREF and SRCREF do not definitely overlap (even though + they may overlap in a way that's not apparent from the available data), + return false. */ + +bool +builtin_access::overlap () +{ + builtin_access &acs = *this; + + const offset_int maxobjsize = tree_to_shwi (max_object_size ()); + + acs.sizrange[0] = wi::smax (dstref->sizrange[0], + srcref->sizrange[0]).to_shwi (); + acs.sizrange[1] = wi::smax (dstref->sizrange[1], + srcref->sizrange[1]).to_shwi (); + + /* Check to see if the two references refer to regions that are + too large not to overlap in the address space (whose maximum + size is PTRDIFF_MAX). */ + offset_int size = dstref->sizrange[0] + srcref->sizrange[0]; + if (maxobjsize < size) + { + acs.ovloff[0] = (maxobjsize - dstref->sizrange[0]).to_shwi (); + acs.ovlsiz[0] = (size - maxobjsize).to_shwi (); + return true; + } + + /* If both base objects aren't known return the maximum possible + offset that would make them not overlap. */ + if (!dstref->base || !srcref->base) + return false; + + /* If the base object is an array adjust the lower bound of the offset + to be non-negative. */ + if (dstref->base + && TREE_CODE (TREE_TYPE (dstref->base)) == ARRAY_TYPE) + acs.dstoff[0] = wi::smax (dstref->offrange[0], 0); + else + acs.dstoff[0] = dstref->offrange[0]; + + acs.dstoff[1] = dstref->offrange[1]; + + if (srcref->base + && TREE_CODE (TREE_TYPE (srcref->base)) == ARRAY_TYPE) + acs.srcoff[0] = wi::smax (srcref->offrange[0], 0); + else + acs.srcoff[0] = srcref->offrange[0]; + + acs.srcoff[1] = srcref->offrange[1]; + + /* When the lower bound of the offset is less that the upper bound + disregard it and use the inverse of the maximum object size + instead. The upper bound is the result of a negative offset + being represented as a large positive value. */ + if (acs.dstoff[1] < acs.dstoff[0]) + acs.dstoff[0] = -maxobjsize; + + /* Validate the offset and size of each reference on its own first. + This is independent of whether or not the base objects are the + same. Normally, this would have already been detected and + diagnosed by -Warray-bounds, unless it has been disabled. */ + offset_int maxoff = acs.dstoff[0] + dstref->sizrange[0]; + if (maxobjsize < maxoff) + { + acs.ovlsiz[0] = (maxoff - maxobjsize).to_shwi (); + acs.ovloff[0] = acs.dstoff[0].to_shwi () - acs.ovlsiz[0]; + return true; + } + + /* Repeat the same as above but for the source offsets. */ + if (acs.srcoff[1] < acs.srcoff[0]) + acs.srcoff[0] = -maxobjsize; + + maxoff = acs.srcoff[0] + srcref->sizrange[0]; + if (maxobjsize < maxoff) + { + acs.ovlsiz[0] = (maxoff - maxobjsize).to_shwi (); + acs.ovlsiz[1] = (acs.srcoff[0] + srcref->sizrange[1] + - maxobjsize).to_shwi (); + acs.ovloff[0] = acs.srcoff[0].to_shwi () - acs.ovlsiz[0]; + return true; + } + + if (dstref->base != srcref->base) + return false; + + acs.dstsiz[0] = dstref->sizrange[0]; + acs.dstsiz[1] = dstref->sizrange[1]; + + acs.srcsiz[0] = srcref->sizrange[0]; + acs.srcsiz[1] = srcref->sizrange[1]; + + /* Call the appropriate function to determine the overlap. */ + if ((this->*detect_overlap) ()) + { + sizrange[0] = wi::smax (acs.dstsiz[0], srcref->sizrange[0]).to_shwi (); + sizrange[1] = wi::smax (acs.dstsiz[1], srcref->sizrange[1]).to_shwi (); + return true; + } + + return false; +} + +/* Attempt to detect and diagnose an overlapping copy in a call expression + EXPR involving an an access ACS to a built-in memory or string function. + Return true when one has been detected, false otherwise. */ + +static bool +maybe_diag_overlap (location_t loc, gcall *call, builtin_access &acs) +{ + if (!acs.overlap ()) + return false; + + /* For convenience. */ + const builtin_memref &dstref = *acs.dstref; + const builtin_memref &srcref = *acs.srcref; + + /* Determine the range of offsets and sizes of the overlap if it + exists and issue diagnostics. */ + HOST_WIDE_INT *ovloff = acs.ovloff; + HOST_WIDE_INT *ovlsiz = acs.ovlsiz; + HOST_WIDE_INT *sizrange = acs.sizrange; + + tree func = gimple_call_fndecl (call); + + /* To avoid a combinatorial explosion of diagnostics format the offsets + or their ranges as strings and use them in the warning calls below. */ + char offstr[3][64]; + + if (dstref.offrange[0] == dstref.offrange[1] + || dstref.offrange[1] > HOST_WIDE_INT_MAX) + sprintf (offstr[0], "%lli", (long long) dstref.offrange[0].to_shwi ()); + else + sprintf (offstr[0], "[%lli, %lli]", + (long long) dstref.offrange[0].to_shwi (), + (long long) dstref.offrange[1].to_shwi ()); + + if (srcref.offrange[0] == srcref.offrange[1] + || srcref.offrange[1] > HOST_WIDE_INT_MAX) + sprintf (offstr[1], "%lli", (long long) srcref.offrange[0].to_shwi ()); + else + sprintf (offstr[1], "[%lli, %lli]", + (long long) srcref.offrange[0].to_shwi (), + (long long) srcref.offrange[1].to_shwi ()); + + if (ovloff[0] == ovloff[1] || !ovloff[1]) + sprintf (offstr[2], "%lli", (long long) ovloff[0]); + else + sprintf (offstr[2], "[%lli, %lli]", + (long long) ovloff[0], (long long) ovloff[1]); + + const offset_int maxobjsize = tree_to_shwi (max_object_size ()); + bool must_overlap = ovlsiz[0] > 0; + + if (ovlsiz[1] == 0) + ovlsiz[1] = ovlsiz[0]; + + if (must_overlap) + { + /* Issue definitive "overlaps" diagnostic in this block. */ + + if (sizrange[0] == sizrange[1]) + { + if (ovlsiz[0] == ovlsiz[1]) + warning_at (loc, OPT_Wrestrict, + sizrange[0] == 1 + ? (ovlsiz[0] == 1 + ? G_("%G%qD accessing %wu byte at offsets %s " + "and %s overlaps %wu byte at offset %s") + : G_("%G%qD accessing %wu byte at offsets %s " + "and %s overlaps %wu bytes at offset " + "%s")) + : (ovlsiz[0] == 1 + ? G_("%G%qD accessing %wu bytes at offsets %s " + "and %s overlaps %wu byte at offset %s") + : G_("%G%qD accessing %wu bytes at offsets %s " + "and %s overlaps %wu bytes at offset " + "%s")), + call, func, sizrange[0], + offstr[0], offstr[1], ovlsiz[0], offstr[2]); + else if (ovlsiz[1] >= 0 && ovlsiz[1] < maxobjsize.to_shwi ()) + warning_at (loc, OPT_Wrestrict, + sizrange[0] == 1 + ? G_("%G%qD accessing %wu byte at offsets %s " + "and %s overlaps between %wu and %wu bytes " + "at offset %s") + : G_("%G%qD accessing %wu bytes at offsets %s " + "and %s overlaps between %wu and %wu bytes " + "at offset %s"), + call, func, sizrange[0], + offstr[0], offstr[1], ovlsiz[0], ovlsiz[1], + offstr[2]); + else + warning_at (loc, OPT_Wrestrict, + sizrange[0] == 1 + ? G_("%G%qD accessing %wu byte at offsets %s and " + "%s overlaps %wu or more bytes at offset %s") + : G_("%G%qD accessing %wu bytes at offsets %s and " + "%s overlaps %wu or more bytes at offset %s"), + call, func, sizrange[0], + offstr[0], offstr[1], ovlsiz[0], offstr[2]); + return true; + } + + if (sizrange[1] >= 0 && sizrange[1] < maxobjsize.to_shwi ()) + { + if (ovlsiz[0] == ovlsiz[1]) + warning_at (loc, OPT_Wrestrict, + ovlsiz[0] == 1 + ? G_("%G%qD accessing between %wu and %wu bytes " + "at offsets %s and %s overlaps %wu byte at " + "offset %s") + : G_("%G%qD accessing between %wu and %wu bytes " + "at offsets %s and %s overlaps %wu bytes " + "at offset %s"), + call, func, sizrange[0], sizrange[1], + offstr[0], offstr[1], ovlsiz[0], offstr[2]); + else if (ovlsiz[1] >= 0 && ovlsiz[1] < maxobjsize.to_shwi ()) + warning_at (loc, OPT_Wrestrict, + "%G%qD accessing between %wu and %wu bytes at " + "offsets %s and %s overlaps between %wu and %wu " + "bytes at offset %s", + call, func, sizrange[0], sizrange[1], + offstr[0], offstr[1], ovlsiz[0], ovlsiz[1], + offstr[2]); + else + warning_at (loc, OPT_Wrestrict, + "%G%qD accessing between %wu and %wu bytes at " + "offsets %s and %s overlaps %wu or more bytes " + "at offset %s", + call, func, sizrange[0], sizrange[1], + offstr[0], offstr[1], ovlsiz[0], offstr[2]); + return true; + } + + if (ovlsiz[0] != ovlsiz[1]) + ovlsiz[1] = maxobjsize.to_shwi (); + + if (ovlsiz[0] == ovlsiz[1]) + warning_at (loc, OPT_Wrestrict, + ovlsiz[0] == 1 + ? G_("%G%qD accessing %wu or more bytes at offsets " + "%s and %s overlaps %wu byte at offset %s") + : G_("%G%qD accessing %wu or more bytes at offsets " + "%s and %s overlaps %wu bytes at offset %s"), + call, func, sizrange[0], offstr[0], offstr[1], + ovlsiz[0], offstr[2]); + else if (ovlsiz[1] >= 0 && ovlsiz[1] < maxobjsize.to_shwi ()) + warning_at (loc, OPT_Wrestrict, + "%G%qD accessing %wu or more bytes at offsets %s " + "and %s overlaps between %wu and %wu bytes " + "at offset %s", + call, func, sizrange[0], offstr[0], offstr[1], + ovlsiz[0], ovlsiz[1], offstr[2]); + else + warning_at (loc, OPT_Wrestrict, + "%G%qD accessing %wu or more bytes at offsets %s " + "and %s overlaps %wu or more bytes at offset %s", + call, func, sizrange[0], offstr[0], offstr[1], + ovlsiz[0], offstr[2]); + return true; + } + + /* Issue "may overlap" diagnostics below. */ + gcc_assert (ovlsiz[0] == 0 + && ovlsiz[1] > 0 + && ovlsiz[1] <= maxobjsize.to_shwi ()); + + /* Use more concise wording when one of the offsets is unbounded + to avoid confusing the user with large and mostly meaningless + numbers. */ + bool open_range = ((dstref.offrange[0] == -maxobjsize - 1 + && dstref.offrange[1] == maxobjsize) + || (srcref.offrange[0] == -maxobjsize - 1 + && srcref.offrange[1] == maxobjsize)); + + if (sizrange[0] == sizrange[1] || sizrange[1] == 1) + { + if (ovlsiz[1] == 1) + { + if (open_range) + warning_at (loc, OPT_Wrestrict, + sizrange[1] == 1 + ? G_("%G%qD accessing %wu byte may overlap " + "%wu byte") + : G_("%G%qD accessing %wu bytes may overlap " + "%wu byte"), + call, func, sizrange[1], ovlsiz[1]); + else + warning_at (loc, OPT_Wrestrict, + sizrange[1] == 1 + ? G_("%G%qD accessing %wu byte at offsets %s " + "and %s may overlap %wu byte at offset %s") + : G_("%G%qD accessing %wu bytes at offsets %s " + "and %s may overlap %wu byte at offset %s"), + call, func, sizrange[1], offstr[0], offstr[1], + ovlsiz[1], offstr[2]); + return true; + } + + if (open_range) + warning_at (loc, OPT_Wrestrict, + sizrange[1] == 1 + ? G_("%G%qD accessing %wu byte may overlap " + "up to %wu bytes") + : G_("%G%qD accessing %wu bytes may overlap " + "up to %wu bytes"), + call, func, sizrange[1], ovlsiz[1]); + else + warning_at (loc, OPT_Wrestrict, + sizrange[1] == 1 + ? G_("%G%qD accessing %wu byte at offsets %s and " + "%s may overlap up to %wu bytes at offset %s") + : G_("%G%qD accessing %wu bytes at offsets %s and " + "%s may overlap up to %wu bytes at offset %s"), + call, func, sizrange[1], offstr[0], offstr[1], + ovlsiz[1], offstr[2]); + return true; + } + + if (sizrange[1] >= 0 && sizrange[1] < maxobjsize.to_shwi ()) + { + if (open_range) + warning_at (loc, OPT_Wrestrict, + ovlsiz[1] == 1 + ? G_("%G%qD accessing between %wu and %wu bytes " + "may overlap %wu byte") + : G_("%G%qD accessing between %wu and %wu bytes " + "may overlap up to %wu bytes"), + call, func, sizrange[0], sizrange[1], ovlsiz[1]); + else + warning_at (loc, OPT_Wrestrict, + ovlsiz[1] == 1 + ? G_("%G%qD accessing between %wu and %wu bytes " + "at offsets %s and %s may overlap %wu byte " + "at offset %s") + : G_("%G%qD accessing between %wu and %wu bytes " + "at offsets %s and %s may overlap up to %wu " + "bytes at offset %s"), + call, func, sizrange[0], sizrange[1], + offstr[0], offstr[1], ovlsiz[1], offstr[2]); + return true; + } + + warning_at (loc, OPT_Wrestrict, + ovlsiz[1] == 1 + ? G_("%G%qD accessing %wu or more bytes at offsets %s " + "and %s may overlap %wu byte at offset %s") + : G_("%G%qD accessing %wu or more bytes at offsets %s " + "and %s may overlap up to %wu bytes at offset %s"), + call, func, sizrange[0], offstr[0], offstr[1], + ovlsiz[1], offstr[2]); + + return true; +} + +/* Validate REF offsets in an EXPRession passed as an argument to a CALL + to a built-in function FUNC to make sure they are within the bounds + of the referenced object if its size is known, or PTRDIFF_MAX otherwise. + Both initial values of the offsets and their final value computed by + the function by incrementing the initial value by the size are + validated. Return true if the offsets are not valid and a diagnostic + has been issued. */ + +static bool +maybe_diag_offset_bounds (location_t loc, gcall *call, tree func, int strict, + tree expr, const builtin_memref &ref) +{ + if (!warn_array_bounds) + return false; + + offset_int ooboff[] = { ref.offrange[0], ref.offrange[1] }; + tree oobref = ref.offset_out_of_bounds (strict, ooboff); + if (!oobref) + return false; + + if (EXPR_HAS_LOCATION (expr)) + loc = EXPR_LOCATION (expr); + + loc = expansion_point_location_if_in_system_header (loc); + + tree type; + + char rangestr[2][64]; + if (ooboff[0] == ooboff[1] + || (ooboff[0] != ref.offrange[0] + && ooboff[0].to_shwi () >= ooboff[1].to_shwi ())) + sprintf (rangestr[0], "%lli", (long long) ooboff[0].to_shwi ()); + else + sprintf (rangestr[0], "[%lli, %lli]", + (long long) ooboff[0].to_shwi (), + (long long) ooboff[1].to_shwi ()); + + if (oobref == error_mark_node) + { + if (ref.sizrange[0] == ref.sizrange[1]) + sprintf (rangestr[1], "%lli", (long long) ref.sizrange[0].to_shwi ()); + else + sprintf (rangestr[1], "[%lli, %lli]", + (long long) ref.sizrange[0].to_shwi (), + (long long) ref.sizrange[1].to_shwi ()); + + if (DECL_P (ref.base) + && TREE_CODE (type = TREE_TYPE (ref.base)) == ARRAY_TYPE) + { + if (warning_at (loc, OPT_Warray_bounds, + "%G%qD pointer overflow between offset %s " + "and size %s accessing array %qD with type %qT", + call, func, rangestr[0], rangestr[1], ref.base, type)) + inform (DECL_SOURCE_LOCATION (ref.base), + "array %qD declared here", ref.base); + else + warning_at (loc, OPT_Warray_bounds, + "%G%qD pointer overflow between offset %s " + "and size %s", + call, func, rangestr[0], rangestr[1]); + } + else + warning_at (loc, OPT_Warray_bounds, + "%G%qD pointer overflow between offset %s " + "and size %s", + call, func, rangestr[0], rangestr[1]); + } + else if (oobref == ref.base) + { + const offset_int maxobjsize = tree_to_shwi (max_object_size ()); + + /* True when the offset formed by an access to the reference + is out of bounds, rather than the initial offset wich is + in bounds. This implies access past the end. */ + bool form = ooboff[0] != ref.offrange[0]; + + if (DECL_P (ref.base)) + { + if ((ref.basesize < maxobjsize + && warning_at (loc, OPT_Warray_bounds, + form + ? G_("%G%qD forming offset %s is out of " + "the bounds [0, %wu] of object %qD with " + "type %qT") + : G_("%G%qD offset %s is out of the bounds " + "[0, %wu] of object %qD with type %qT"), + call, func, rangestr[0], ref.basesize.to_uhwi (), + ref.base, TREE_TYPE (ref.base))) + || warning_at (loc, OPT_Warray_bounds, + form + ? G_("%G%qD forming offset %s is out of " + "the bounds of object %qD with type %qT") + : G_("%G%qD offset %s is out of the bounds " + "of object %qD with type %qT"), + call, func, rangestr[0], + ref.base, TREE_TYPE (ref.base))) + inform (DECL_SOURCE_LOCATION (ref.base), + "%qD declared here", ref.base); + } + else if (ref.basesize < maxobjsize) + warning_at (loc, OPT_Warray_bounds, + form + ? G_("%G%qD forming offset %s is out of the bounds " + "[0, %wu]") + : G_("%G%qD offset %s is out of the bounds [0, %wu]"), + call, func, rangestr[0], ref.basesize.to_uhwi ()); + else + warning_at (loc, OPT_Warray_bounds, + form + ? G_("%G%qD forming offset %s is out of bounds") + : G_("%G%qD offset %s is out of bounds"), + call, func, rangestr[0]); + } + else if (TREE_CODE (ref.ref) == MEM_REF) + { + tree type = TREE_TYPE (TREE_OPERAND (ref.ref, 0)); + if (POINTER_TYPE_P (type)) + type = TREE_TYPE (type); + type = TYPE_MAIN_VARIANT (type); + + warning_at (loc, OPT_Warray_bounds, + "%G%qD offset %s from the object at %qE is out " + "of the bounds of %qT", + call, func, rangestr[0], ref.base, type); + } + else + { + type = TYPE_MAIN_VARIANT (TREE_TYPE (ref.ref)); + + warning_at (loc, OPT_Warray_bounds, + "%G%qD offset %s from the object at %qE is out " + "of the bounds of referenced subobject %qD with type %qT " + "at offset %wu", + call, func, rangestr[0], ref.base, TREE_OPERAND (ref.ref, 1), + type, ref.refoff.to_uhwi ()); + } + + return true; +} + +/* Check a CALL statement for restrict-violations and issue warnings + if/when appropriate. */ + +void +wrestrict_dom_walker::check_call (gcall *call) +{ + /* Avoid checking the call if it has already been diagnosed for + some reason. */ + if (gimple_no_warning_p (call)) + return; + + tree func = gimple_call_fndecl (call); + if (!func || DECL_BUILT_IN_CLASS (func) != BUILT_IN_NORMAL) + return; + + bool with_bounds = gimple_call_with_bounds_p (call); + + /* Argument number to extract from the call (depends on the built-in + and its kind). */ + unsigned dst_idx = -1; + unsigned src_idx = -1; + unsigned bnd_idx = -1; + + /* Is this CALL to a string function (as opposed to one to a raw + memory function). */ + bool strfun = true; + + switch (DECL_FUNCTION_CODE (func)) + { + case BUILT_IN_MEMCPY: + case BUILT_IN_MEMCPY_CHK: + case BUILT_IN_MEMCPY_CHKP: + case BUILT_IN_MEMCPY_CHK_CHKP: + case BUILT_IN_MEMPCPY: + case BUILT_IN_MEMPCPY_CHK: + case BUILT_IN_MEMPCPY_CHKP: + case BUILT_IN_MEMPCPY_CHK_CHKP: + case BUILT_IN_MEMMOVE: + case BUILT_IN_MEMMOVE_CHK: + case BUILT_IN_MEMMOVE_CHKP: + case BUILT_IN_MEMMOVE_CHK_CHKP: + strfun = false; + /* Fall through. */ + + case BUILT_IN_STPNCPY: + case BUILT_IN_STPNCPY_CHK: + case BUILT_IN_STRNCAT: + case BUILT_IN_STRNCAT_CHK: + case BUILT_IN_STRNCPY: + case BUILT_IN_STRNCPY_CHK: + dst_idx = 0; + src_idx = 1 + with_bounds; + bnd_idx = 2 + 2 * with_bounds; + break; + + case BUILT_IN_STPCPY: + case BUILT_IN_STPCPY_CHK: + case BUILT_IN_STPCPY_CHKP: + case BUILT_IN_STPCPY_CHK_CHKP: + case BUILT_IN_STRCPY: + case BUILT_IN_STRCPY_CHK: + case BUILT_IN_STRCPY_CHKP: + case BUILT_IN_STRCPY_CHK_CHKP: + case BUILT_IN_STRCAT: + case BUILT_IN_STRCAT_CHK: + case BUILT_IN_STRCAT_CHKP: + case BUILT_IN_STRCAT_CHK_CHKP: + dst_idx = 0; + src_idx = 1 + with_bounds; + break; + + default: + /* Handle other string functions here whose access may need + to be validated for in-bounds offsets and non-overlapping + copies. (Not all _chkp functions have BUILT_IN_XXX_CHKP + macros so they need to be handled here.) */ + return; + } + + unsigned nargs = gimple_call_num_args (call); + + tree dst = dst_idx < nargs ? gimple_call_arg (call, dst_idx) : NULL_TREE; + tree src = src_idx < nargs ? gimple_call_arg (call, src_idx) : NULL_TREE; + tree dstwr = bnd_idx < nargs ? gimple_call_arg (call, bnd_idx) : NULL_TREE; + + /* For string functions with an unspecified or unknown bound, + assume the size of the access is one. */ + if (!dstwr && strfun) + dstwr = size_one_node; + + if (check_bounds_or_overlap (call, dst, src, dstwr, NULL_TREE)) + return; + + /* Avoid diagnosing the call again. */ + gimple_set_no_warning (call, true); +} + +} /* anonymous namespace */ + +/* Attempt to detect and diagnose invalid offset bounds and (except for + memmove) overlapping copy in a call expression EXPR from SRC to DST + and DSTSIZE and SRCSIZE bytes, respectively. Both DSTSIZE and + SRCSIZE may be NULL. Return false when one or the other has been + detected and diagnosed, true otherwise. */ + +bool +check_bounds_or_overlap (gcall *call, tree dst, tree src, tree dstsize, + tree srcsize, bool bounds_only /* = false */) +{ + location_t loc = gimple_location (call); + + if (tree block = gimple_block (call)) + if (location_t *pbloc = block_nonartificial_location (block)) + loc = *pbloc; + + loc = expansion_point_location_if_in_system_header (loc); + + tree func = gimple_call_fndecl (call); + + builtin_memref dstref (dst, dstsize); + builtin_memref srcref (src, srcsize); + + builtin_access acs (call, dstref, srcref); + + /* Set STRICT to the value of the -Warray-bounds=N argument for + string functions or when N > 1. */ + int strict = (acs.strict () || warn_array_bounds > 1 ? warn_array_bounds : 0); + + /* Validate offsets first to make sure they are within the bounds + of the destination object if its size is known, or PTRDIFF_MAX + otherwise. */ + if (maybe_diag_offset_bounds (loc, call, func, strict, dst, dstref) + || maybe_diag_offset_bounds (loc, call, func, strict, src, srcref)) + { + gimple_set_no_warning (call, true); + return false; + } + + bool check_overlap + = (warn_restrict + && (bounds_only + || (DECL_FUNCTION_CODE (func) != BUILT_IN_MEMMOVE + && DECL_FUNCTION_CODE (func) != BUILT_IN_MEMMOVE_CHK))); + + if (!check_overlap) + return true; + + if (operand_equal_p (dst, src, 0)) + { + warning_at (loc, OPT_Wrestrict, + "%G%qD source argument is the same as destination", + call, func); + gimple_set_no_warning (call, true); + return false; + } + + /* Return false when overlap has been detected. */ + if (maybe_diag_overlap (loc, call, acs)) + { + gimple_set_no_warning (call, true); + return false; + } + + return true; +} + +gimple_opt_pass * +make_pass_warn_restrict (gcc::context *ctxt) +{ + return new pass_wrestrict (ctxt); +} |