| /* Pass to detect and issue warnings for violations of the restrict |
| qualifier. |
| Copyright (C) 2017-2021 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 "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 "tree-cfg.h" |
| #include "tree-object-size.h" |
| #include "calls.h" |
| #include "cfgloop.h" |
| #include "intl.h" |
| #include "gimple-range.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 || warn_restrict || warn_stringop_overflow; |
| } |
| |
| static void check_call (range_query *, gimple *); |
| |
| static void |
| wrestrict_walk (range_query *query, 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; |
| |
| check_call (query, stmt); |
| } |
| } |
| |
| unsigned |
| pass_wrestrict::execute (function *fun) |
| { |
| gimple_ranger ranger; |
| basic_block bb; |
| FOR_EACH_BB_FN (bb, fun) |
| wrestrict_walk (&ranger, bb); |
| |
| 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. */ |
| class builtin_memref |
| { |
| public: |
| /* 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; |
| /* Same for the subobject. */ |
| offset_int refsize; |
| |
| /* 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]; |
| |
| /* Cached result of get_max_objsize(). */ |
| const offset_int maxobjsize; |
| |
| /* 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 (range_query *, gimple *, tree, tree); |
| |
| tree offset_out_of_bounds (int, offset_int[3]) const; |
| |
| private: |
| /* Call statement to the built-in. */ |
| gimple *stmt; |
| |
| range_query *query; |
| |
| /* Ctor helper to set or extend OFFRANGE based on argument. */ |
| void extend_offset_range (tree); |
| |
| /* Ctor helper to determine BASE and OFFRANGE from argument. */ |
| void set_base_and_offset (tree); |
| }; |
| |
| /* 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 |
| && detect_overlap != &builtin_access::no_overlap); |
| } |
| |
| builtin_access (range_query *, gimple *, builtin_memref &, builtin_memref &); |
| |
| /* Entry point to determine overlap. */ |
| bool overlap (); |
| |
| offset_int write_off (tree) const; |
| |
| void dump (FILE *) const; |
| |
| 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. STMT is the statement in which expr appears in. */ |
| |
| builtin_memref::builtin_memref (range_query *query, gimple *stmt, tree expr, |
| tree size) |
| : ptr (expr), |
| ref (), |
| base (), |
| basesize (-1), |
| refsize (-1), |
| refoff (HOST_WIDE_INT_MIN), |
| offrange (), |
| sizrange (), |
| maxobjsize (tree_to_shwi (max_object_size ())), |
| strbounded_p (), |
| stmt (stmt), |
| query (query) |
| { |
| /* 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; |
| |
| if (!expr) |
| return; |
| |
| /* Find the BASE object or pointer referenced by EXPR and set |
| the offset range OFFRANGE in the process. */ |
| set_base_and_offset (expr); |
| |
| 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 (query, size, stmt, range, SR_ALLOW_ZERO); |
| 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[0] <= maxobjsize && sizrange[1] > maxobjsize) |
| sizrange[1] = maxobjsize; |
| } |
| else |
| sizrange[1] = maxobjsize; |
| |
| if (!DECL_P (base)) |
| return; |
| |
| /* If the offset could be in the range of the referenced object |
| constrain its bounds so neither exceeds those of the object. */ |
| if (offrange[0] < 0 && offrange[1] > 0) |
| offrange[0] = 0; |
| |
| offset_int maxoff = maxobjsize; |
| tree basetype = TREE_TYPE (base); |
| if (TREE_CODE (basetype) == ARRAY_TYPE) |
| { |
| if (ref && array_at_struct_end_p (ref)) |
| ; /* Use the maximum possible offset for last member arrays. */ |
| else if (tree basesize = TYPE_SIZE_UNIT (basetype)) |
| if (TREE_CODE (basesize) == INTEGER_CST) |
| /* Size could be non-constant for a variable-length type such |
| as a struct with a VLA member (a GCC extension). */ |
| maxoff = wi::to_offset (basesize); |
| } |
| |
| if (offrange[0] >= 0) |
| { |
| if (offrange[1] < 0) |
| offrange[1] = offrange[0] <= maxoff ? maxoff : maxobjsize; |
| else if (offrange[0] <= maxoff && offrange[1] > maxoff) |
| offrange[1] = maxoff; |
| } |
| } |
| |
| /* Based on the initial length of the destination STARTLEN, returns |
| the offset of the first write access from the beginning of |
| the destination. Nonzero only for strcat-type of calls. */ |
| |
| offset_int builtin_access::write_off (tree startlen) const |
| { |
| if (detect_overlap != &builtin_access::strcat_overlap |
| || !startlen || TREE_CODE (startlen) != INTEGER_CST) |
| return 0; |
| |
| return wi::to_offset (startlen); |
| } |
| |
| /* Ctor helper to set or extend OFFRANGE based on the OFFSET argument. |
| Pointer offsets are represented as unsigned sizetype but must be |
| treated as signed. */ |
| |
| void |
| builtin_memref::extend_offset_range (tree offset) |
| { |
| if (TREE_CODE (offset) == INTEGER_CST) |
| { |
| offset_int off = int_cst_value (offset); |
| if (off != 0) |
| { |
| offrange[0] += off; |
| offrange[1] += off; |
| } |
| return; |
| } |
| |
| if (TREE_CODE (offset) == SSA_NAME) |
| { |
| /* A pointer offset is represented as sizetype but treated |
| as signed. */ |
| wide_int min, max; |
| value_range_kind rng; |
| value_range vr; |
| if (query && query->range_of_expr (vr, offset, stmt)) |
| { |
| rng = vr.kind (); |
| if (!vr.undefined_p ()) |
| { |
| min = wi::to_wide (vr.min ()); |
| max = wi::to_wide (vr.max ()); |
| } |
| } |
| else |
| { |
| /* There is a global version here because |
| check_bounds_or_overlap may be called from gimple |
| fold during gimple lowering. */ |
| rng = get_range_info (offset, &min, &max); |
| } |
| if (rng == VR_ANTI_RANGE && wi::lts_p (max, min)) |
| { |
| /* Convert an anti-range whose upper bound is less than |
| its lower bound to a signed range. */ |
| offrange[0] += offset_int::from (max + 1, SIGNED); |
| offrange[1] += offset_int::from (min - 1, SIGNED); |
| return; |
| } |
| |
| if (rng == VR_RANGE |
| && (DECL_P (base) || wi::lts_p (min, max))) |
| { |
| /* Preserve the bounds of the range for an offset into |
| a known object (it may be adjusted later relative to |
| a constant offset from its beginning). Otherwise use |
| the bounds only when they are ascending when treated |
| as signed. */ |
| offrange[0] += offset_int::from (min, SIGNED); |
| offrange[1] += offset_int::from (max, SIGNED); |
| return; |
| } |
| |
| /* Handle an anti-range the same as no range at all. */ |
| gimple *stmt = SSA_NAME_DEF_STMT (offset); |
| tree type; |
| if (is_gimple_assign (stmt) |
| && (type = TREE_TYPE (gimple_assign_rhs1 (stmt))) |
| && INTEGRAL_TYPE_P (type)) |
| { |
| tree_code code = gimple_assign_rhs_code (stmt); |
| if (code == 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. */ |
| offrange[0] += wi::to_offset (TYPE_MIN_VALUE (type)); |
| offrange[1] += wi::to_offset (TYPE_MAX_VALUE (type)); |
| return; |
| } |
| } |
| } |
| |
| const offset_int maxoff = tree_to_shwi (max_object_size ()) >> 1; |
| const offset_int minoff = -maxoff - 1; |
| |
| offrange[0] += minoff; |
| offrange[1] += maxoff; |
| } |
| |
| /* Determines the base object or pointer of the reference EXPR |
| and the offset range from the beginning of the base. */ |
| |
| void |
| builtin_memref::set_base_and_offset (tree expr) |
| { |
| tree offset = NULL_TREE; |
| |
| if (TREE_CODE (expr) == SSA_NAME) |
| { |
| /* Try to tease the offset out of the pointer. */ |
| gimple *stmt = SSA_NAME_DEF_STMT (expr); |
| if (!base |
| && 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 |
| { |
| base = expr; |
| return; |
| } |
| } |
| else if (code == POINTER_PLUS_EXPR) |
| { |
| expr = gimple_assign_rhs1 (stmt); |
| offset = gimple_assign_rhs2 (stmt); |
| } |
| else |
| { |
| base = expr; |
| return; |
| } |
| } |
| else |
| { |
| /* FIXME: Handle PHI nodes in case like: |
| _12 = &MEM[(void *)&a + 2B] + _10; |
| |
| <bb> [local count: 1073741824]: |
| # prephitmp_13 = PHI <_12, &MEM[(void *)&a + 2B]> |
| memcpy (prephitmp_13, p_7(D), 6); */ |
| base = expr; |
| return; |
| } |
| } |
| |
| if (TREE_CODE (expr) == ADDR_EXPR) |
| expr = TREE_OPERAND (expr, 0); |
| |
| /* Stash the reference for offset validation. */ |
| ref = expr; |
| |
| poly_int64 bitsize, bitpos; |
| tree var_off; |
| machine_mode mode; |
| int sign, reverse, vol; |
| |
| /* Determine the base object or pointer of the reference and |
| the constant bit offset from the beginning of the base. |
| If the offset has a non-constant component, it will be in |
| VAR_OFF. MODE, SIGN, REVERSE, and VOL are write only and |
| unused here. */ |
| base = get_inner_reference (expr, &bitsize, &bitpos, &var_off, |
| &mode, &sign, &reverse, &vol); |
| |
| /* get_inner_reference is not expected to return null. */ |
| gcc_assert (base != NULL); |
| |
| if (offset) |
| extend_offset_range (offset); |
| |
| poly_int64 bytepos = exact_div (bitpos, BITS_PER_UNIT); |
| |
| /* Convert the poly_int64 offset to offset_int. The offset |
| should be constant but be prepared for it not to be just in |
| case. */ |
| offset_int cstoff; |
| if (bytepos.is_constant (&cstoff)) |
| { |
| offrange[0] += cstoff; |
| offrange[1] += cstoff; |
| |
| /* Besides the reference saved above, also stash the offset |
| for validation. */ |
| if (TREE_CODE (expr) == COMPONENT_REF) |
| refoff = cstoff; |
| } |
| else |
| offrange[1] += maxobjsize; |
| |
| if (var_off) |
| { |
| if (TREE_CODE (var_off) == INTEGER_CST) |
| { |
| cstoff = wi::to_offset (var_off); |
| offrange[0] += cstoff; |
| offrange[1] += cstoff; |
| } |
| else |
| offrange[1] += maxobjsize; |
| } |
| |
| if (TREE_CODE (base) == MEM_REF) |
| { |
| tree memrefoff = fold_convert (ptrdiff_type_node, TREE_OPERAND (base, 1)); |
| extend_offset_range (memrefoff); |
| base = TREE_OPERAND (base, 0); |
| |
| if (refoff != HOST_WIDE_INT_MIN |
| && TREE_CODE (expr) == COMPONENT_REF) |
| { |
| /* Bump up the offset of the referenced subobject to reflect |
| the offset to the enclosing object. For example, so that |
| in |
| struct S { char a, b[3]; } s[2]; |
| strcpy (s[1].b, "1234"); |
| REFOFF is set to s[1].b - (char*)s. */ |
| offset_int off = tree_to_shwi (memrefoff); |
| refoff += off; |
| } |
| |
| if (!integer_zerop (memrefoff)) |
| /* A non-zero offset into an array of struct with flexible array |
| members implies that the array is empty because there is no |
| way to initialize such a member when it belongs to an array. |
| This must be some sort of a bug. */ |
| refsize = 0; |
| } |
| |
| if (TREE_CODE (ref) == COMPONENT_REF) |
| if (tree size = component_ref_size (ref)) |
| if (TREE_CODE (size) == INTEGER_CST) |
| refsize = wi::to_offset (size); |
| |
| if (TREE_CODE (base) == SSA_NAME) |
| set_base_and_offset (base); |
| } |
| |
| /* 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. OOBOFF is initially setto the range of offsets, |
| and OOBOFF[2] to the offset of the first write access (nonzero for |
| the strcat family). When STRICT is nonzero 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[3]) const |
| { |
| if (!ptr) |
| return NULL_TREE; |
| |
| /* The offset of the first write access or zero. */ |
| offset_int wroff = ooboff[2]; |
| |
| /* A temporary, possibly adjusted, copy of the offset range. */ |
| offset_int offrng[2] = { ooboff[0], ooboff[1] }; |
| |
| if (DECL_P (base) && TREE_CODE (TREE_TYPE (base)) == ARRAY_TYPE) |
| { |
| /* Check for offset in an anti-range with a negative lower bound. |
| For such a range, consider only the non-negative subrange. */ |
| if (offrng[1] < offrng[0] && offrng[1] < 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; |
| |
| /* Set to the size remaining in the object after subtracting |
| REFOFF. It may become negative as a result of negative indices |
| into the enclosing object, such as in: |
| extern struct S { char a[4], b[3], c[1]; } *p; |
| strcpy (p[-3].b, "123"); */ |
| offset_int size = basesize; |
| tree obj = base; |
| |
| const bool decl_p = DECL_P (obj); |
| |
| if (basesize < 0) |
| { |
| endoff = offrng[lob] + (sizrange[0] - wroff); |
| |
| /* 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; |
| |
| /* When the referenced subobject is known, the end offset must be |
| within its bounds. Otherwise there is nothing to do. */ |
| if (strict |
| && !decl_p |
| && ref |
| && refsize >= 0 |
| && TREE_CODE (ref) == COMPONENT_REF) |
| { |
| /* If REFOFF is negative, SIZE will become negative here. */ |
| size = refoff + refsize; |
| obj = ref; |
| } |
| else |
| return NULL_TREE; |
| } |
| |
| /* A reference to an object of known size must be within the bounds |
| of either the base object or the subobject (see above for when |
| a subobject can be used). */ |
| if ((decl_p && offrng[hib] < 0) || offrng[lob] > size) |
| return obj; |
| |
| /* The extent of the reference must also be within the bounds of |
| the base object (if known) or the subobject or the maximum object |
| size otherwise. */ |
| endoff = offrng[lob] + sizrange[0]; |
| if (endoff > maxobjsize) |
| return error_mark_node; |
| |
| if (strict |
| && decl_p |
| && ref |
| && refsize >= 0 |
| && TREE_CODE (ref) == COMPONENT_REF) |
| { |
| /* If the reference is to a member subobject of a declared object, |
| the offset must be within the bounds of the subobject. */ |
| size = refoff + refsize; |
| 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; |
| |
| if (endoff > ooboff[lob]) |
| ooboff[hib] = endoff - 1; |
| else |
| ooboff[hib] = offrng[lob] + 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 (range_query *query, gimple *call, |
| builtin_memref &dst, |
| builtin_memref &src) |
| : dstref (&dst), srcref (&src), sizrange (), ovloff (), ovlsiz (), |
| dstoff (), srcoff (), dstsiz (), srcsiz () |
| { |
| dstoff[0] = dst.offrange[0]; |
| dstoff[1] = dst.offrange[1]; |
| |
| /* Zero out since the offset_int ctors invoked above are no-op. */ |
| 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; |
| |
| tree func = gimple_call_fndecl (call); |
| switch (DECL_FUNCTION_CODE (func)) |
| { |
| case BUILT_IN_MEMCPY: |
| case BUILT_IN_MEMCPY_CHK: |
| case BUILT_IN_MEMPCPY: |
| case BUILT_IN_MEMPCPY_CHK: |
| ostype = 0; |
| depends_p = false; |
| detect_overlap = &builtin_access::generic_overlap; |
| break; |
| |
| case BUILT_IN_MEMMOVE: |
| case BUILT_IN_MEMMOVE_CHK: |
| /* 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_MEMSET: |
| case BUILT_IN_MEMSET_CHK: |
| /* For memset 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_STRCPY: |
| case BUILT_IN_STRCPY_CHK: |
| detect_overlap = &builtin_access::strcpy_overlap; |
| break; |
| |
| case BUILT_IN_STRCAT: |
| case BUILT_IN_STRCAT_CHK: |
| 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. */ |
| return; |
| } |
| |
| const offset_int maxobjsize = dst.maxobjsize; |
| |
| /* 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.base && 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; |
| } |
| |
| /* 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) |
| { |
| unsigned nargs = gimple_call_num_args (call); |
| if (nargs <= sizeargno) |
| return; |
| |
| tree size = gimple_call_arg (call, sizeargno); |
| tree range[2]; |
| if (get_size_range (query, size, call, 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]; |
| } |
| } |
| |
| bool dstsize_set = false; |
| /* 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]; |
| dstsize_set = true; |
| } |
| else if (srcref->sizrange[0] == 0 && srcref->sizrange[1] == maxobjsize) |
| { |
| /* When the size of the source access is unknown set it to the size |
| of the destination first and adjust it later if necessary. */ |
| srcref->sizrange[0] = dstref->sizrange[0]; |
| srcref->sizrange[1] = dstref->sizrange[1]; |
| |
| if (depends_p) |
| { |
| if (dstref->strbounded_p) |
| { |
| /* Read access by strncpy is constrained by the third |
| argument but except for a zero bound is at least one. */ |
| srcref->sizrange[0] = bounds[1] > 0 ? 1 : 0; |
| offset_int bound = wi::umin (srcref->basesize, bounds[1]); |
| if (bound < srcref->sizrange[1]) |
| srcref->sizrange[1] = bound; |
| } |
| /* 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] >= 0 |
| && srcref->offrange[1] < srcref->sizrange[0]) |
| srcref->sizrange[0] -= srcref->offrange[1]; |
| else |
| srcref->sizrange[0] = 1; |
| |
| 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]; |
| } |
| } |
| } |
| else if (!dstsize_set && detect_overlap == &builtin_access::strcat_overlap) |
| { |
| dstref->sizrange[0] += srcref->sizrange[0] - 1; |
| dstref->sizrange[1] += srcref->sizrange[1] - 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 = acs.dstref->maxobjsize; |
| |
| offset_int maxsize = dstref->basesize < 0 ? maxobjsize : dstref->basesize; |
| |
| /* 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) |
| { |
| if (!dstref->strbounded_p && !depends_p) |
| /* Memcpy only considers certain overlap. */ |
| return false; |
| |
| /* There's no way to distinguish an access to the same member |
| of a structure from one to two distinct members of the same |
| structure. Give up to avoid excessive false positives. */ |
| tree basetype = TREE_TYPE (dstref->base); |
| |
| if (POINTER_TYPE_P (basetype)) |
| basetype = TREE_TYPE (basetype); |
| else |
| while (TREE_CODE (basetype) == ARRAY_TYPE) |
| basetype = TREE_TYPE (basetype); |
| |
| if (RECORD_OR_UNION_TYPE_P (basetype)) |
| 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; |
| |
| if (stxcpy_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 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 |
| { |
| /* Iterate over the extreme locations (on the horizontal axis |
| formed by their offsets) and sizes of the 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) |
| { |
| 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 (); |
| |
| /* Adjust the overlap offset range to reflect the overlap size range. */ |
| if (ovlsiz[0] == 0 && ovlsiz[1] > 1) |
| ovloff[1] = ovloff[0] + ovlsiz[1] - 1; |
| |
| 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 = acs.dstref->maxobjsize; |
| |
| 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 destination string since the function starts writing over |
| its terminating nul, and set the destination size to 1 for the length |
| of the nul. */ |
| acs.dstoff[0] += dstsiz[0] - srcref->sizrange[0]; |
| acs.dstoff[1] += dstsiz[1] - srcref->sizrange[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; |
| |
| /* 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 terminating 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; |
| |
| offset_int endoff |
| = dstref->offrange[0] + (dstref->sizrange[0] - srcref->sizrange[0]); |
| if (endoff <= srcref->offrange[0]) |
| acs.ovloff[0] = wi::smin (maxobjsize, srcref->offrange[0]).to_shwi (); |
| else |
| acs.ovloff[0] = wi::smin (maxobjsize, endoff).to_shwi (); |
| |
| acs.sizrange[0] = wi::smax (wi::abs (endoff - srcref->offrange[0]) + 1, |
| srcref->sizrange[0]).to_shwi (); |
| if (dstref->offrange[0] == dstref->offrange[1]) |
| { |
| if (srcref->offrange[0] == srcref->offrange[1]) |
| acs.ovloff[1] = acs.ovloff[0]; |
| else |
| acs.ovloff[1] |
| = wi::smin (maxobjsize, |
| srcref->offrange[1] + srcref->sizrange[1]).to_shwi (); |
| } |
| else |
| acs.ovloff[1] |
| = wi::smin (maxobjsize, |
| dstref->offrange[1] + dstref->sizrange[1]).to_shwi (); |
| |
| if (acs.sizrange[0] == 0) |
| acs.sizrange[0] = 1; |
| 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 (); |
| } |
| |
| /* For a BASE of array type, clamp REFOFF to at most [0, BASE_SIZE] |
| if known, or [0, MAXOBJSIZE] otherwise. */ |
| |
| static void |
| clamp_offset (tree base, offset_int refoff[2], offset_int maxobjsize) |
| { |
| if (!base || TREE_CODE (TREE_TYPE (base)) != ARRAY_TYPE) |
| return; |
| |
| if (refoff[0] < 0 && refoff[1] >= 0) |
| refoff[0] = 0; |
| |
| if (refoff[1] < refoff[0]) |
| { |
| offset_int maxsize = maxobjsize; |
| if (tree size = TYPE_SIZE_UNIT (TREE_TYPE (base))) |
| maxsize = wi::to_offset (size); |
| |
| refoff[1] = wi::umin (refoff[1], maxsize); |
| } |
| } |
| |
| /* 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 = dstref->maxobjsize; |
| |
| 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 bounds of the offset |
| to be non-negative and within the bounds of the array if possible. */ |
| clamp_offset (dstref->base, acs.dstoff, maxobjsize); |
| |
| acs.srcoff[0] = srcref->offrange[0]; |
| acs.srcoff[1] = srcref->offrange[1]; |
| |
| clamp_offset (srcref->base, acs.srcoff, maxobjsize); |
| |
| /* When the upper bound of the offset is less than the lower bound |
| the former is the result of a negative offset being represented |
| as a large positive value or vice versa. The resulting range is |
| a union of two subranges: [MIN, UB] and [LB, MAX]. Since such |
| a union is not representable using the current data structure |
| replace it with the full range of offsets. */ |
| if (acs.dstoff[1] < acs.dstoff[0]) |
| { |
| acs.dstoff[0] = -maxobjsize - 1; |
| acs.dstoff[1] = 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 - 1; |
| acs.srcoff[1] = 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) ()) |
| { |
| if (!sizrange[1]) |
| { |
| /* Unless the access size range has already been set, do so here. */ |
| 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 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, gimple *call, builtin_access &acs) |
| { |
| if (!acs.overlap ()) |
| return false; |
| |
| if (gimple_no_warning_p (call)) |
| return true; |
| |
| /* 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], HOST_WIDE_INT_PRINT_DEC, |
| dstref.offrange[0].to_shwi ()); |
| else |
| sprintf (offstr[0], |
| "[" HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC "]", |
| dstref.offrange[0].to_shwi (), |
| dstref.offrange[1].to_shwi ()); |
| |
| if (srcref.offrange[0] == srcref.offrange[1] |
| || srcref.offrange[1] > HOST_WIDE_INT_MAX) |
| sprintf (offstr[1], |
| HOST_WIDE_INT_PRINT_DEC, |
| srcref.offrange[0].to_shwi ()); |
| else |
| sprintf (offstr[1], |
| "[" HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC "]", |
| srcref.offrange[0].to_shwi (), |
| srcref.offrange[1].to_shwi ()); |
| |
| if (ovloff[0] == ovloff[1] || !ovloff[1]) |
| sprintf (offstr[2], HOST_WIDE_INT_PRINT_DEC, ovloff[0]); |
| else |
| sprintf (offstr[2], |
| "[" HOST_WIDE_INT_PRINT_DEC ", " HOST_WIDE_INT_PRINT_DEC "]", |
| ovloff[0], ovloff[1]); |
| |
| const offset_int maxobjsize = dstref.maxobjsize; |
| 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_n (loc, OPT_Wrestrict, sizrange[0], |
| "%G%qD accessing %wu byte at offsets %s " |
| "and %s overlaps between %wu and %wu bytes " |
| "at offset %s", |
| "%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_n (loc, OPT_Wrestrict, sizrange[0], |
| "%G%qD accessing %wu byte at offsets %s and " |
| "%s overlaps %wu or more bytes at offset %s", |
| "%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_n (loc, OPT_Wrestrict, ovlsiz[0], |
| "%G%qD accessing between %wu and %wu bytes " |
| "at offsets %s and %s overlaps %wu byte at " |
| "offset %s", |
| "%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_n (loc, OPT_Wrestrict, ovlsiz[0], |
| "%G%qD accessing %wu or more bytes at offsets " |
| "%s and %s overlaps %wu byte at offset %s", |
| "%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; |
| } |
| |
| /* 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; |
| if (DECL_P (dstref.base) && TREE_CODE (TREE_TYPE (dstref.base)) == ARRAY_TYPE) |
| open_range = ((dstref.offrange[0] == 0 |
| && dstref.offrange[1] == maxobjsize) |
| || (srcref.offrange[0] == 0 |
| && srcref.offrange[1] == maxobjsize)); |
| else |
| 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_n (loc, OPT_Wrestrict, sizrange[1], |
| "%G%qD accessing %wu byte may overlap " |
| "%wu byte", |
| "%G%qD accessing %wu bytes may overlap " |
| "%wu byte", |
| call, func, sizrange[1], ovlsiz[1]); |
| else |
| warning_n (loc, OPT_Wrestrict, sizrange[1], |
| "%G%qD accessing %wu byte at offsets %s " |
| "and %s may overlap %wu byte at offset %s", |
| "%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_n (loc, OPT_Wrestrict, sizrange[1], |
| "%G%qD accessing %wu byte may overlap " |
| "up to %wu bytes", |
| "%G%qD accessing %wu bytes may overlap " |
| "up to %wu bytes", |
| call, func, sizrange[1], ovlsiz[1]); |
| else |
| warning_n (loc, OPT_Wrestrict, sizrange[1], |
| "%G%qD accessing %wu byte at offsets %s and " |
| "%s may overlap up to %wu bytes at offset %s", |
| "%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_n (loc, OPT_Wrestrict, ovlsiz[1], |
| "%G%qD accessing between %wu and %wu bytes " |
| "may overlap %wu byte", |
| "%G%qD accessing between %wu and %wu bytes " |
| "may overlap up to %wu bytes", |
| call, func, sizrange[0], sizrange[1], ovlsiz[1]); |
| else |
| warning_n (loc, OPT_Wrestrict, ovlsiz[1], |
| "%G%qD accessing between %wu and %wu bytes " |
| "at offsets %s and %s may overlap %wu byte " |
| "at offset %s", |
| "%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_n (loc, OPT_Wrestrict, ovlsiz[1], |
| "%G%qD accessing %wu or more bytes at offsets %s " |
| "and %s may overlap %wu byte at offset %s", |
| "%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 size and 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. DO_WARN is true when a diagnostic should |
| be issued, false 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, or would have been issued if DO_WARN had been true. */ |
| |
| static bool |
| maybe_diag_access_bounds (gimple *call, tree func, int strict, |
| const builtin_memref &ref, offset_int wroff, |
| bool do_warn) |
| { |
| location_t loc = gimple_or_expr_nonartificial_location (call, ref.ptr); |
| const offset_int maxobjsize = ref.maxobjsize; |
| |
| /* Check for excessive size first and regardless of warning options |
| since the result is used to make codegen decisions. */ |
| if (ref.sizrange[0] > maxobjsize) |
| { |
| /* Return true without issuing a warning. */ |
| if (!do_warn) |
| return true; |
| |
| if (ref.ref && TREE_NO_WARNING (ref.ref)) |
| return false; |
| |
| if (warn_stringop_overflow) |
| { |
| if (ref.sizrange[0] == ref.sizrange[1]) |
| return warning_at (loc, OPT_Wstringop_overflow_, |
| "%G%qD specified bound %wu " |
| "exceeds maximum object size %wu", |
| call, func, ref.sizrange[0].to_uhwi (), |
| maxobjsize.to_uhwi ()); |
| |
| return warning_at (loc, OPT_Wstringop_overflow_, |
| "%G%qD specified bound between %wu and %wu " |
| "exceeds maximum object size %wu", |
| call, func, ref.sizrange[0].to_uhwi (), |
| ref.sizrange[1].to_uhwi (), |
| maxobjsize.to_uhwi ()); |
| } |
| } |
| |
| /* Check for out-bounds pointers regardless of warning options since |
| the result is used to make codegen decisions. An excessive WROFF |
| can only come up as a result of an invalid strncat bound and is |
| diagnosed separately using a more meaningful warning. */ |
| if (maxobjsize < wroff) |
| wroff = 0; |
| offset_int ooboff[] = { ref.offrange[0], ref.offrange[1], wroff }; |
| tree oobref = ref.offset_out_of_bounds (strict, ooboff); |
| if (!oobref) |
| return false; |
| |
| /* Return true without issuing a warning. */ |
| if (!do_warn) |
| return true; |
| |
| if (!warn_array_bounds) |
| return false; |
| |
| if (TREE_NO_WARNING (ref.ptr) |
| || (ref.ref && TREE_NO_WARNING (ref.ref))) |
| return false; |
| |
| 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 ()); |
| |
| bool warned = false; |
| |
| if (oobref == error_mark_node) |
| { |
| if (ref.sizrange[0] == ref.sizrange[1]) |
| sprintf (rangestr[1], "%llu", |
| (unsigned long long) ref.sizrange[0].to_shwi ()); |
| else |
| sprintf (rangestr[1], "[%lli, %lli]", |
| (unsigned long long) ref.sizrange[0].to_uhwi (), |
| (unsigned long long) ref.sizrange[1].to_uhwi ()); |
| |
| tree type; |
| |
| if (DECL_P (ref.base) |
| && TREE_CODE (type = TREE_TYPE (ref.base)) == ARRAY_TYPE) |
| { |
| auto_diagnostic_group d; |
| 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); |
| warned = true; |
| } |
| else |
| warned = warning_at (loc, OPT_Warray_bounds, |
| "%G%qD pointer overflow between offset %s " |
| "and size %s", |
| call, func, rangestr[0], rangestr[1]); |
| } |
| else |
| warned = 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) |
| { |
| /* 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)) |
| { |
| auto_diagnostic_group d; |
| 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); |
| warned = true; |
| } |
| } |
| else if (ref.basesize < maxobjsize) |
| warned = 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 |
| warned = 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 refop = TREE_OPERAND (ref.ref, 0); |
| tree type = TREE_TYPE (refop); |
| if (POINTER_TYPE_P (type)) |
| type = TREE_TYPE (type); |
| type = TYPE_MAIN_VARIANT (type); |
| |
| if (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)) |
| { |
| if (TREE_CODE (ref.ref) == COMPONENT_REF) |
| refop = TREE_OPERAND (ref.ref, 1); |
| if (DECL_P (refop)) |
| inform (DECL_SOURCE_LOCATION (refop), |
| "subobject %qD declared here", refop); |
| warned = true; |
| } |
| } |
| else |
| { |
| tree refop = TREE_OPERAND (ref.ref, 0); |
| tree type = TYPE_MAIN_VARIANT (TREE_TYPE (ref.ref)); |
| |
| if (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 %wi", |
| call, func, rangestr[0], ref.base, |
| TREE_OPERAND (ref.ref, 1), type, |
| ref.refoff.to_shwi ())) |
| { |
| if (TREE_CODE (ref.ref) == COMPONENT_REF) |
| refop = TREE_OPERAND (ref.ref, 1); |
| if (DECL_P (refop)) |
| inform (DECL_SOURCE_LOCATION (refop), |
| "subobject %qD declared here", refop); |
| warned = true; |
| } |
| } |
| |
| return warned; |
| } |
| |
| /* Check a CALL statement for restrict-violations and issue warnings |
| if/when appropriate. */ |
| |
| static void |
| check_call (range_query *query, gimple *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 || !fndecl_built_in_p (func, BUILT_IN_NORMAL)) |
| return; |
| |
| /* 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_MEMPCPY: |
| case BUILT_IN_MEMPCPY_CHK: |
| case BUILT_IN_MEMMOVE: |
| case BUILT_IN_MEMMOVE_CHK: |
| 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; |
| bnd_idx = 2; |
| break; |
| |
| case BUILT_IN_MEMSET: |
| case BUILT_IN_MEMSET_CHK: |
| dst_idx = 0; |
| bnd_idx = 2; |
| break; |
| |
| case BUILT_IN_STPCPY: |
| case BUILT_IN_STPCPY_CHK: |
| case BUILT_IN_STRCPY: |
| case BUILT_IN_STRCPY_CHK: |
| case BUILT_IN_STRCAT: |
| case BUILT_IN_STRCAT_CHK: |
| dst_idx = 0; |
| src_idx = 1; |
| break; |
| |
| default: |
| /* Handle other string functions here whose access may need |
| to be validated for in-bounds offsets and non-overlapping |
| copies. */ |
| 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; |
| |
| /* DST and SRC can be null for a call with an insufficient number |
| of arguments to a built-in function declared without a protype. */ |
| if (!dst || (src_idx < nargs && !src)) |
| return; |
| |
| /* DST, SRC, or DSTWR can also have the wrong type in a call to |
| a function declared without a prototype. Avoid checking such |
| invalid calls. */ |
| if (TREE_CODE (TREE_TYPE (dst)) != POINTER_TYPE |
| || (src && TREE_CODE (TREE_TYPE (src)) != POINTER_TYPE) |
| || (dstwr && !INTEGRAL_TYPE_P (TREE_TYPE (dstwr)))) |
| return; |
| |
| if (!check_bounds_or_overlap (query, 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. DO_WARN is false to detect either problem |
| without issue a warning. Return the OPT_Wxxx constant corresponding |
| to the warning if one has been detected and zero otherwise. */ |
| |
| int |
| check_bounds_or_overlap (gimple *call, tree dst, tree src, tree dstsize, |
| tree srcsize, bool bounds_only /* = false */, |
| bool do_warn /* = true */) |
| { |
| return check_bounds_or_overlap (/*range_query=*/NULL, |
| call, dst, src, dstsize, srcsize, |
| bounds_only, do_warn); |
| } |
| |
| int |
| check_bounds_or_overlap (range_query *query, |
| gimple *call, tree dst, tree src, tree dstsize, |
| tree srcsize, bool bounds_only /* = false */, |
| bool do_warn /* = true */) |
| { |
| tree func = gimple_call_fndecl (call); |
| |
| builtin_memref dstref (query, call, dst, dstsize); |
| builtin_memref srcref (query, call, src, srcsize); |
| |
| /* Create a descriptor of the access. This may adjust both DSTREF |
| and SRCREF based on one another and the kind of the access. */ |
| builtin_access acs (query, 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); |
| |
| /* The starting offset of the destination write access. Nonzero only |
| for the strcat family of functions. */ |
| offset_int wroff = acs.write_off (dstsize); |
| |
| /* Validate offsets to each reference before the access 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_access_bounds (call, func, strict, dstref, wroff, do_warn) |
| || maybe_diag_access_bounds (call, func, strict, srcref, 0, do_warn)) |
| { |
| if (do_warn) |
| gimple_set_no_warning (call, true); |
| return OPT_Warray_bounds; |
| } |
| |
| if (!warn_restrict || bounds_only || !src) |
| return 0; |
| |
| if (!bounds_only) |
| { |
| switch (DECL_FUNCTION_CODE (func)) |
| { |
| case BUILT_IN_MEMMOVE: |
| case BUILT_IN_MEMMOVE_CHK: |
| case BUILT_IN_MEMSET: |
| case BUILT_IN_MEMSET_CHK: |
| return 0; |
| default: |
| break; |
| } |
| } |
| |
| location_t loc = gimple_or_expr_nonartificial_location (call, dst); |
| if (operand_equal_p (dst, src, 0)) |
| { |
| /* Issue -Wrestrict unless the pointers are null (those do |
| not point to objects and so do not indicate an overlap; |
| such calls could be the result of sanitization and jump |
| threading). */ |
| if (!integer_zerop (dst) && !gimple_no_warning_p (call)) |
| { |
| warning_at (loc, OPT_Wrestrict, |
| "%G%qD source argument is the same as destination", |
| call, func); |
| gimple_set_no_warning (call, true); |
| return OPT_Wrestrict; |
| } |
| |
| return 0; |
| } |
| |
| /* Return false when overlap has been detected. */ |
| if (maybe_diag_overlap (loc, call, acs)) |
| { |
| gimple_set_no_warning (call, true); |
| return OPT_Wrestrict; |
| } |
| |
| return 0; |
| } |
| |
| gimple_opt_pass * |
| make_pass_warn_restrict (gcc::context *ctxt) |
| { |
| return new pass_wrestrict (ctxt); |
| } |
| |
| DEBUG_FUNCTION void |
| dump_builtin_memref (FILE *fp, const builtin_memref &ref) |
| { |
| fprintf (fp, "\n ptr = "); |
| print_generic_expr (fp, ref.ptr, TDF_LINENO); |
| fprintf (fp, "\n ref = "); |
| if (ref.ref) |
| print_generic_expr (fp, ref.ref, TDF_LINENO); |
| else |
| fputs ("null", fp); |
| fprintf (fp, "\n base = "); |
| print_generic_expr (fp, ref.base, TDF_LINENO); |
| fprintf (fp, |
| "\n basesize = %lli" |
| "\n refsize = %lli" |
| "\n refoff = %lli" |
| "\n offrange = [%lli, %lli]" |
| "\n sizrange = [%lli, %lli]" |
| "\n strbounded_p = %s\n", |
| (long long)ref.basesize.to_shwi (), |
| (long long)ref.refsize.to_shwi (), |
| (long long)ref.refoff.to_shwi (), |
| (long long)ref.offrange[0].to_shwi (), |
| (long long)ref.offrange[1].to_shwi (), |
| (long long)ref.sizrange[0].to_shwi (), |
| (long long)ref.sizrange[1].to_shwi (), |
| ref.strbounded_p ? "true" : "false"); |
| } |
| |
| void |
| builtin_access::dump (FILE *fp) const |
| { |
| fprintf (fp, " dstref:"); |
| dump_builtin_memref (fp, *dstref); |
| fprintf (fp, "\n srcref:"); |
| dump_builtin_memref (fp, *srcref); |
| |
| fprintf (fp, |
| " sizrange = [%lli, %lli]\n" |
| " ovloff = [%lli, %lli]\n" |
| " ovlsiz = [%lli, %lli]\n" |
| " dstoff = [%lli, %lli]\n" |
| " dstsiz = [%lli, %lli]\n" |
| " srcoff = [%lli, %lli]\n" |
| " srcsiz = [%lli, %lli]\n", |
| (long long)sizrange[0], (long long)sizrange[1], |
| (long long)ovloff[0], (long long)ovloff[1], |
| (long long)ovlsiz[0], (long long)ovlsiz[1], |
| (long long)dstoff[0].to_shwi (), (long long)dstoff[1].to_shwi (), |
| (long long)dstsiz[0].to_shwi (), (long long)dstsiz[1].to_shwi (), |
| (long long)srcoff[0].to_shwi (), (long long)srcoff[1].to_shwi (), |
| (long long)srcsiz[0].to_shwi (), (long long)srcsiz[1].to_shwi ()); |
| } |
| |
| DEBUG_FUNCTION void |
| dump_builtin_access (FILE *fp, gimple *stmt, const builtin_access &acs) |
| { |
| if (stmt) |
| { |
| fprintf (fp, "\nDumping builtin_access for "); |
| print_gimple_expr (fp, stmt, TDF_LINENO); |
| fputs (":\n", fp); |
| } |
| |
| acs.dump (fp); |
| } |
| |
| DEBUG_FUNCTION void |
| debug (gimple *stmt, const builtin_access &acs) |
| { |
| dump_builtin_access (stdout, stmt, acs); |
| } |