4710 lines
138 KiB
C++
4710 lines
138 KiB
C++
/* Pass to detect and issue warnings for invalid accesses, including
|
|
invalid or mismatched allocation/deallocation calls.
|
|
|
|
Copyright (C) 2020-2022 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/>. */
|
|
|
|
#define INCLUDE_STRING
|
|
#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 "diagnostic.h"
|
|
#include "ssa.h"
|
|
#include "gimple-pretty-print.h"
|
|
#include "gimple-ssa-warn-access.h"
|
|
#include "gimple-ssa-warn-restrict.h"
|
|
#include "diagnostic-core.h"
|
|
#include "fold-const.h"
|
|
#include "gimple-fold.h"
|
|
#include "gimple-iterator.h"
|
|
#include "langhooks.h"
|
|
#include "memmodel.h"
|
|
#include "target.h"
|
|
#include "tree-dfa.h"
|
|
#include "tree-ssa.h"
|
|
#include "tree-cfg.h"
|
|
#include "tree-object-size.h"
|
|
#include "tree-ssa-strlen.h"
|
|
#include "calls.h"
|
|
#include "cfgloop.h"
|
|
#include "intl.h"
|
|
#include "gimple-range.h"
|
|
#include "stringpool.h"
|
|
#include "attribs.h"
|
|
#include "demangle.h"
|
|
#include "attr-fnspec.h"
|
|
#include "pointer-query.h"
|
|
|
|
/* Return true if tree node X has an associated location. */
|
|
|
|
static inline location_t
|
|
has_location (const_tree x)
|
|
{
|
|
if (DECL_P (x))
|
|
return DECL_SOURCE_LOCATION (x) != UNKNOWN_LOCATION;
|
|
|
|
if (EXPR_P (x))
|
|
return EXPR_HAS_LOCATION (x);
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Return the associated location of STMT. */
|
|
|
|
static inline location_t
|
|
get_location (const gimple *stmt)
|
|
{
|
|
return gimple_location (stmt);
|
|
}
|
|
|
|
/* Return the associated location of tree node X. */
|
|
|
|
static inline location_t
|
|
get_location (tree x)
|
|
{
|
|
if (DECL_P (x))
|
|
return DECL_SOURCE_LOCATION (x);
|
|
|
|
if (EXPR_P (x))
|
|
return EXPR_LOCATION (x);
|
|
|
|
return UNKNOWN_LOCATION;
|
|
}
|
|
|
|
/* Overload of the nascent tree function for GIMPLE STMT. */
|
|
|
|
static inline tree
|
|
get_callee_fndecl (const gimple *stmt)
|
|
{
|
|
return gimple_call_fndecl (stmt);
|
|
}
|
|
|
|
static inline unsigned
|
|
call_nargs (const gimple *stmt)
|
|
{
|
|
return gimple_call_num_args (stmt);
|
|
}
|
|
|
|
static inline unsigned
|
|
call_nargs (const_tree expr)
|
|
{
|
|
return call_expr_nargs (expr);
|
|
}
|
|
|
|
|
|
static inline tree
|
|
call_arg (const gimple *stmt, unsigned argno)
|
|
{
|
|
return gimple_call_arg (stmt, argno);
|
|
}
|
|
|
|
static inline tree
|
|
call_arg (tree expr, unsigned argno)
|
|
{
|
|
return CALL_EXPR_ARG (expr, argno);
|
|
}
|
|
|
|
/* For a call EXPR at LOC to a function FNAME that expects a string
|
|
in the argument ARG, issue a diagnostic due to it being a called
|
|
with an argument that is a character array with no terminating
|
|
NUL. SIZE is the EXACT size of the array, and BNDRNG the number
|
|
of characters in which the NUL is expected. Either EXPR or FNAME
|
|
may be null but noth both. SIZE may be null when BNDRNG is null. */
|
|
|
|
template <class GimpleOrTree>
|
|
static void
|
|
warn_string_no_nul (location_t loc, GimpleOrTree expr, const char *fname,
|
|
tree arg, tree decl, tree size, bool exact,
|
|
const wide_int bndrng[2] /* = NULL */)
|
|
{
|
|
const opt_code opt = OPT_Wstringop_overread;
|
|
if ((expr && warning_suppressed_p (expr, opt))
|
|
|| warning_suppressed_p (arg, opt))
|
|
return;
|
|
|
|
loc = expansion_point_location_if_in_system_header (loc);
|
|
bool warned;
|
|
|
|
/* Format the bound range as a string to keep the nuber of messages
|
|
from exploding. */
|
|
char bndstr[80];
|
|
*bndstr = 0;
|
|
if (bndrng)
|
|
{
|
|
if (bndrng[0] == bndrng[1])
|
|
sprintf (bndstr, "%llu", (unsigned long long) bndrng[0].to_uhwi ());
|
|
else
|
|
sprintf (bndstr, "[%llu, %llu]",
|
|
(unsigned long long) bndrng[0].to_uhwi (),
|
|
(unsigned long long) bndrng[1].to_uhwi ());
|
|
}
|
|
|
|
const tree maxobjsize = max_object_size ();
|
|
const wide_int maxsiz = wi::to_wide (maxobjsize);
|
|
if (expr)
|
|
{
|
|
tree func = get_callee_fndecl (expr);
|
|
if (bndrng)
|
|
{
|
|
if (wi::ltu_p (maxsiz, bndrng[0]))
|
|
warned = warning_at (loc, opt,
|
|
"%qD specified bound %s exceeds "
|
|
"maximum object size %E",
|
|
func, bndstr, maxobjsize);
|
|
else
|
|
{
|
|
bool maybe = wi::to_wide (size) == bndrng[0];
|
|
warned = warning_at (loc, opt,
|
|
exact
|
|
? G_("%qD specified bound %s exceeds "
|
|
"the size %E of unterminated array")
|
|
: (maybe
|
|
? G_("%qD specified bound %s may "
|
|
"exceed the size of at most %E "
|
|
"of unterminated array")
|
|
: G_("%qD specified bound %s exceeds "
|
|
"the size of at most %E "
|
|
"of unterminated array")),
|
|
func, bndstr, size);
|
|
}
|
|
}
|
|
else
|
|
warned = warning_at (loc, opt,
|
|
"%qD argument missing terminating nul",
|
|
func);
|
|
}
|
|
else
|
|
{
|
|
if (bndrng)
|
|
{
|
|
if (wi::ltu_p (maxsiz, bndrng[0]))
|
|
warned = warning_at (loc, opt,
|
|
"%qs specified bound %s exceeds "
|
|
"maximum object size %E",
|
|
fname, bndstr, maxobjsize);
|
|
else
|
|
{
|
|
bool maybe = wi::to_wide (size) == bndrng[0];
|
|
warned = warning_at (loc, opt,
|
|
exact
|
|
? G_("%qs specified bound %s exceeds "
|
|
"the size %E of unterminated array")
|
|
: (maybe
|
|
? G_("%qs specified bound %s may "
|
|
"exceed the size of at most %E "
|
|
"of unterminated array")
|
|
: G_("%qs specified bound %s exceeds "
|
|
"the size of at most %E "
|
|
"of unterminated array")),
|
|
fname, bndstr, size);
|
|
}
|
|
}
|
|
else
|
|
warned = warning_at (loc, opt,
|
|
"%qs argument missing terminating nul",
|
|
fname);
|
|
}
|
|
|
|
if (warned)
|
|
{
|
|
inform (get_location (decl),
|
|
"referenced argument declared here");
|
|
suppress_warning (arg, opt);
|
|
if (expr)
|
|
suppress_warning (expr, opt);
|
|
}
|
|
}
|
|
|
|
void
|
|
warn_string_no_nul (location_t loc, gimple *stmt, const char *fname,
|
|
tree arg, tree decl, tree size /* = NULL_TREE */,
|
|
bool exact /* = false */,
|
|
const wide_int bndrng[2] /* = NULL */)
|
|
{
|
|
return warn_string_no_nul<gimple *> (loc, stmt, fname,
|
|
arg, decl, size, exact, bndrng);
|
|
}
|
|
|
|
void
|
|
warn_string_no_nul (location_t loc, tree expr, const char *fname,
|
|
tree arg, tree decl, tree size /* = NULL_TREE */,
|
|
bool exact /* = false */,
|
|
const wide_int bndrng[2] /* = NULL */)
|
|
{
|
|
return warn_string_no_nul<tree> (loc, expr, fname,
|
|
arg, decl, size, exact, bndrng);
|
|
}
|
|
|
|
/* If EXP refers to an unterminated constant character array return
|
|
the declaration of the object of which the array is a member or
|
|
element and if SIZE is not null, set *SIZE to the size of
|
|
the unterminated array and set *EXACT if the size is exact or
|
|
clear it otherwise. Otherwise return null. */
|
|
|
|
tree
|
|
unterminated_array (tree exp, tree *size /* = NULL */, bool *exact /* = NULL */)
|
|
{
|
|
/* C_STRLEN will return NULL and set DECL in the info
|
|
structure if EXP references a unterminated array. */
|
|
c_strlen_data lendata = { };
|
|
tree len = c_strlen (exp, 1, &lendata);
|
|
if (len || !lendata.minlen || !lendata.decl)
|
|
return NULL_TREE;
|
|
|
|
if (!size)
|
|
return lendata.decl;
|
|
|
|
len = lendata.minlen;
|
|
if (lendata.off)
|
|
{
|
|
/* Constant offsets are already accounted for in LENDATA.MINLEN,
|
|
but not in a SSA_NAME + CST expression. */
|
|
if (TREE_CODE (lendata.off) == INTEGER_CST)
|
|
*exact = true;
|
|
else if (TREE_CODE (lendata.off) == PLUS_EXPR
|
|
&& TREE_CODE (TREE_OPERAND (lendata.off, 1)) == INTEGER_CST)
|
|
{
|
|
/* Subtract the offset from the size of the array. */
|
|
*exact = false;
|
|
tree temp = TREE_OPERAND (lendata.off, 1);
|
|
temp = fold_convert (ssizetype, temp);
|
|
len = fold_build2 (MINUS_EXPR, ssizetype, len, temp);
|
|
}
|
|
else
|
|
*exact = false;
|
|
}
|
|
else
|
|
*exact = true;
|
|
|
|
*size = len;
|
|
return lendata.decl;
|
|
}
|
|
|
|
/* For a call EXPR (which may be null) that expects a string argument
|
|
SRC as an argument, returns false if SRC is a character array with
|
|
no terminating NUL. When nonnull, BOUND is the number of characters
|
|
in which to expect the terminating NUL. When EXPR is nonnull also
|
|
issues a warning. */
|
|
|
|
template <class GimpleOrTree>
|
|
static bool
|
|
check_nul_terminated_array (GimpleOrTree expr, tree src, tree bound)
|
|
{
|
|
/* The constant size of the array SRC points to. The actual size
|
|
may be less of EXACT is true, but not more. */
|
|
tree size;
|
|
/* True if SRC involves a non-constant offset into the array. */
|
|
bool exact;
|
|
/* The unterminated constant array SRC points to. */
|
|
tree nonstr = unterminated_array (src, &size, &exact);
|
|
if (!nonstr)
|
|
return true;
|
|
|
|
/* NONSTR refers to the non-nul terminated constant array and SIZE
|
|
is the constant size of the array in bytes. EXACT is true when
|
|
SIZE is exact. */
|
|
|
|
wide_int bndrng[2];
|
|
if (bound)
|
|
{
|
|
value_range r;
|
|
|
|
get_global_range_query ()->range_of_expr (r, bound);
|
|
|
|
if (r.kind () != VR_RANGE)
|
|
return true;
|
|
|
|
bndrng[0] = r.lower_bound ();
|
|
bndrng[1] = r.upper_bound ();
|
|
|
|
if (exact)
|
|
{
|
|
if (wi::leu_p (bndrng[0], wi::to_wide (size)))
|
|
return true;
|
|
}
|
|
else if (wi::lt_p (bndrng[0], wi::to_wide (size), UNSIGNED))
|
|
return true;
|
|
}
|
|
|
|
if (expr)
|
|
warn_string_no_nul (get_location (expr), expr, NULL, src, nonstr,
|
|
size, exact, bound ? bndrng : NULL);
|
|
|
|
return false;
|
|
}
|
|
|
|
bool
|
|
check_nul_terminated_array (gimple *stmt, tree src, tree bound /* = NULL_TREE */)
|
|
{
|
|
return check_nul_terminated_array<gimple *>(stmt, src, bound);
|
|
}
|
|
|
|
bool
|
|
check_nul_terminated_array (tree expr, tree src, tree bound /* = NULL_TREE */)
|
|
{
|
|
return check_nul_terminated_array<tree>(expr, src, bound);
|
|
}
|
|
|
|
/* Warn about passing a non-string array/pointer to a built-in function
|
|
that expects a nul-terminated string argument. Returns true if
|
|
a warning has been issued.*/
|
|
|
|
template <class GimpleOrTree>
|
|
static bool
|
|
maybe_warn_nonstring_arg (tree fndecl, GimpleOrTree exp)
|
|
{
|
|
if (!fndecl || !fndecl_built_in_p (fndecl, BUILT_IN_NORMAL))
|
|
return false;
|
|
|
|
if (!warn_stringop_overread
|
|
|| warning_suppressed_p (exp, OPT_Wstringop_overread))
|
|
return false;
|
|
|
|
/* Avoid clearly invalid calls (more checking done below). */
|
|
unsigned nargs = call_nargs (exp);
|
|
if (!nargs)
|
|
return false;
|
|
|
|
/* The bound argument to a bounded string function like strncpy. */
|
|
tree bound = NULL_TREE;
|
|
|
|
/* The longest known or possible string argument to one of the comparison
|
|
functions. If the length is less than the bound it is used instead.
|
|
Since the length is only used for warning and not for code generation
|
|
disable strict mode in the calls to get_range_strlen below. */
|
|
tree maxlen = NULL_TREE;
|
|
|
|
/* It's safe to call "bounded" string functions with a non-string
|
|
argument since the functions provide an explicit bound for this
|
|
purpose. The exception is strncat where the bound may refer to
|
|
either the destination or the source. */
|
|
int fncode = DECL_FUNCTION_CODE (fndecl);
|
|
switch (fncode)
|
|
{
|
|
case BUILT_IN_STRCMP:
|
|
case BUILT_IN_STRNCMP:
|
|
case BUILT_IN_STRNCASECMP:
|
|
{
|
|
/* For these, if one argument refers to one or more of a set
|
|
of string constants or arrays of known size, determine
|
|
the range of their known or possible lengths and use it
|
|
conservatively as the bound for the unbounded function,
|
|
and to adjust the range of the bound of the bounded ones. */
|
|
for (unsigned argno = 0;
|
|
argno < MIN (nargs, 2)
|
|
&& !(maxlen && TREE_CODE (maxlen) == INTEGER_CST); argno++)
|
|
{
|
|
tree arg = call_arg (exp, argno);
|
|
if (!get_attr_nonstring_decl (arg))
|
|
{
|
|
c_strlen_data lendata = { };
|
|
/* Set MAXBOUND to an arbitrary non-null non-integer
|
|
node as a request to have it set to the length of
|
|
the longest string in a PHI. */
|
|
lendata.maxbound = arg;
|
|
get_range_strlen (arg, &lendata, /* eltsize = */ 1);
|
|
maxlen = lendata.maxbound;
|
|
}
|
|
}
|
|
}
|
|
/* Fall through. */
|
|
|
|
case BUILT_IN_STRNCAT:
|
|
case BUILT_IN_STPNCPY:
|
|
case BUILT_IN_STRNCPY:
|
|
if (nargs > 2)
|
|
bound = call_arg (exp, 2);
|
|
break;
|
|
|
|
case BUILT_IN_STRNDUP:
|
|
if (nargs < 2)
|
|
return false;
|
|
bound = call_arg (exp, 1);
|
|
break;
|
|
|
|
case BUILT_IN_STRNLEN:
|
|
{
|
|
tree arg = call_arg (exp, 0);
|
|
if (!get_attr_nonstring_decl (arg))
|
|
{
|
|
c_strlen_data lendata = { };
|
|
/* Set MAXBOUND to an arbitrary non-null non-integer
|
|
node as a request to have it set to the length of
|
|
the longest string in a PHI. */
|
|
lendata.maxbound = arg;
|
|
get_range_strlen (arg, &lendata, /* eltsize = */ 1);
|
|
maxlen = lendata.maxbound;
|
|
}
|
|
if (nargs > 1)
|
|
bound = call_arg (exp, 1);
|
|
break;
|
|
}
|
|
|
|
default:
|
|
break;
|
|
}
|
|
|
|
/* Determine the range of the bound argument (if specified). */
|
|
tree bndrng[2] = { NULL_TREE, NULL_TREE };
|
|
if (bound)
|
|
{
|
|
STRIP_NOPS (bound);
|
|
get_size_range (bound, bndrng);
|
|
}
|
|
|
|
location_t loc = get_location (exp);
|
|
|
|
if (bndrng[0])
|
|
{
|
|
/* Diagnose excessive bound prior to the adjustment below and
|
|
regardless of attribute nonstring. */
|
|
tree maxobjsize = max_object_size ();
|
|
if (tree_int_cst_lt (maxobjsize, bndrng[0]))
|
|
{
|
|
bool warned = false;
|
|
if (tree_int_cst_equal (bndrng[0], bndrng[1]))
|
|
warned = warning_at (loc, OPT_Wstringop_overread,
|
|
"%qD specified bound %E "
|
|
"exceeds maximum object size %E",
|
|
fndecl, bndrng[0], maxobjsize);
|
|
else
|
|
warned = warning_at (loc, OPT_Wstringop_overread,
|
|
"%qD specified bound [%E, %E] "
|
|
"exceeds maximum object size %E",
|
|
fndecl, bndrng[0], bndrng[1],
|
|
maxobjsize);
|
|
if (warned)
|
|
suppress_warning (exp, OPT_Wstringop_overread);
|
|
|
|
return warned;
|
|
}
|
|
}
|
|
|
|
if (maxlen && !integer_all_onesp (maxlen))
|
|
{
|
|
/* Add one for the nul. */
|
|
maxlen = const_binop (PLUS_EXPR, TREE_TYPE (maxlen), maxlen,
|
|
size_one_node);
|
|
|
|
if (!bndrng[0])
|
|
{
|
|
/* Conservatively use the upper bound of the lengths for
|
|
both the lower and the upper bound of the operation. */
|
|
bndrng[0] = maxlen;
|
|
bndrng[1] = maxlen;
|
|
bound = void_type_node;
|
|
}
|
|
else if (maxlen)
|
|
{
|
|
/* Replace the bound on the operation with the upper bound
|
|
of the length of the string if the latter is smaller. */
|
|
if (tree_int_cst_lt (maxlen, bndrng[0]))
|
|
bndrng[0] = maxlen;
|
|
else if (tree_int_cst_lt (maxlen, bndrng[1]))
|
|
bndrng[1] = maxlen;
|
|
}
|
|
}
|
|
|
|
bool any_arg_warned = false;
|
|
/* Iterate over the built-in function's formal arguments and check
|
|
each const char* against the actual argument. If the actual
|
|
argument is declared attribute non-string issue a warning unless
|
|
the argument's maximum length is bounded. */
|
|
function_args_iterator it;
|
|
function_args_iter_init (&it, TREE_TYPE (fndecl));
|
|
|
|
for (unsigned argno = 0; ; ++argno, function_args_iter_next (&it))
|
|
{
|
|
/* Avoid iterating past the declared argument in a call
|
|
to function declared without a prototype. */
|
|
if (argno >= nargs)
|
|
break;
|
|
|
|
tree argtype = function_args_iter_cond (&it);
|
|
if (!argtype)
|
|
break;
|
|
|
|
if (TREE_CODE (argtype) != POINTER_TYPE)
|
|
continue;
|
|
|
|
argtype = TREE_TYPE (argtype);
|
|
|
|
if (TREE_CODE (argtype) != INTEGER_TYPE
|
|
|| !TYPE_READONLY (argtype))
|
|
continue;
|
|
|
|
argtype = TYPE_MAIN_VARIANT (argtype);
|
|
if (argtype != char_type_node)
|
|
continue;
|
|
|
|
tree callarg = call_arg (exp, argno);
|
|
if (TREE_CODE (callarg) == ADDR_EXPR)
|
|
callarg = TREE_OPERAND (callarg, 0);
|
|
|
|
/* See if the destination is declared with attribute "nonstring". */
|
|
tree decl = get_attr_nonstring_decl (callarg);
|
|
if (!decl)
|
|
continue;
|
|
|
|
/* The maximum number of array elements accessed. */
|
|
offset_int wibnd = 0;
|
|
|
|
if (argno && fncode == BUILT_IN_STRNCAT)
|
|
{
|
|
/* See if the bound in strncat is derived from the length
|
|
of the strlen of the destination (as it's expected to be).
|
|
If so, reset BOUND and FNCODE to trigger a warning. */
|
|
tree dstarg = call_arg (exp, 0);
|
|
if (is_strlen_related_p (dstarg, bound))
|
|
{
|
|
/* The bound applies to the destination, not to the source,
|
|
so reset these to trigger a warning without mentioning
|
|
the bound. */
|
|
bound = NULL;
|
|
fncode = 0;
|
|
}
|
|
else if (bndrng[1])
|
|
/* Use the upper bound of the range for strncat. */
|
|
wibnd = wi::to_offset (bndrng[1]);
|
|
}
|
|
else if (bndrng[0])
|
|
/* Use the lower bound of the range for functions other than
|
|
strncat. */
|
|
wibnd = wi::to_offset (bndrng[0]);
|
|
|
|
/* Determine the size of the argument array if it is one. */
|
|
offset_int asize = wibnd;
|
|
bool known_size = false;
|
|
tree type = TREE_TYPE (decl);
|
|
|
|
/* Determine the array size. For arrays of unknown bound and
|
|
pointers reset BOUND to trigger the appropriate warning. */
|
|
if (TREE_CODE (type) == ARRAY_TYPE)
|
|
{
|
|
if (tree arrbnd = TYPE_DOMAIN (type))
|
|
{
|
|
if ((arrbnd = TYPE_MAX_VALUE (arrbnd)))
|
|
{
|
|
asize = wi::to_offset (arrbnd) + 1;
|
|
known_size = true;
|
|
}
|
|
}
|
|
else if (bound == void_type_node)
|
|
bound = NULL_TREE;
|
|
}
|
|
else if (bound == void_type_node)
|
|
bound = NULL_TREE;
|
|
|
|
/* In a call to strncat with a bound in a range whose lower but
|
|
not upper bound is less than the array size, reset ASIZE to
|
|
be the same as the bound and the other variable to trigger
|
|
the apprpriate warning below. */
|
|
if (fncode == BUILT_IN_STRNCAT
|
|
&& bndrng[0] != bndrng[1]
|
|
&& wi::ltu_p (wi::to_offset (bndrng[0]), asize)
|
|
&& (!known_size
|
|
|| wi::ltu_p (asize, wibnd)))
|
|
{
|
|
asize = wibnd;
|
|
bound = NULL_TREE;
|
|
fncode = 0;
|
|
}
|
|
|
|
bool warned = false;
|
|
|
|
auto_diagnostic_group d;
|
|
if (wi::ltu_p (asize, wibnd))
|
|
{
|
|
if (bndrng[0] == bndrng[1])
|
|
warned = warning_at (loc, OPT_Wstringop_overread,
|
|
"%qD argument %i declared attribute "
|
|
"%<nonstring%> is smaller than the specified "
|
|
"bound %wu",
|
|
fndecl, argno + 1, wibnd.to_uhwi ());
|
|
else if (wi::ltu_p (asize, wi::to_offset (bndrng[0])))
|
|
warned = warning_at (loc, OPT_Wstringop_overread,
|
|
"%qD argument %i declared attribute "
|
|
"%<nonstring%> is smaller than "
|
|
"the specified bound [%E, %E]",
|
|
fndecl, argno + 1, bndrng[0], bndrng[1]);
|
|
else
|
|
warned = warning_at (loc, OPT_Wstringop_overread,
|
|
"%qD argument %i declared attribute "
|
|
"%<nonstring%> may be smaller than "
|
|
"the specified bound [%E, %E]",
|
|
fndecl, argno + 1, bndrng[0], bndrng[1]);
|
|
}
|
|
else if (fncode == BUILT_IN_STRNCAT)
|
|
; /* Avoid warning for calls to strncat() when the bound
|
|
is equal to the size of the non-string argument. */
|
|
else if (!bound)
|
|
warned = warning_at (loc, OPT_Wstringop_overread,
|
|
"%qD argument %i declared attribute %<nonstring%>",
|
|
fndecl, argno + 1);
|
|
|
|
if (warned)
|
|
{
|
|
inform (DECL_SOURCE_LOCATION (decl),
|
|
"argument %qD declared here", decl);
|
|
any_arg_warned = true;
|
|
}
|
|
}
|
|
|
|
if (any_arg_warned)
|
|
suppress_warning (exp, OPT_Wstringop_overread);
|
|
|
|
return any_arg_warned;
|
|
}
|
|
|
|
bool
|
|
maybe_warn_nonstring_arg (tree fndecl, gimple *stmt)
|
|
{
|
|
return maybe_warn_nonstring_arg<gimple *>(fndecl, stmt);
|
|
}
|
|
|
|
|
|
bool
|
|
maybe_warn_nonstring_arg (tree fndecl, tree expr)
|
|
{
|
|
return maybe_warn_nonstring_arg<tree>(fndecl, expr);
|
|
}
|
|
|
|
/* Issue a warning OPT for a bounded call EXP with a bound in RANGE
|
|
accessing an object with SIZE. */
|
|
|
|
template <class GimpleOrTree>
|
|
static bool
|
|
maybe_warn_for_bound (opt_code opt, location_t loc, GimpleOrTree exp, tree func,
|
|
tree bndrng[2], tree size, const access_data *pad)
|
|
{
|
|
if (!bndrng[0] || warning_suppressed_p (exp, opt))
|
|
return false;
|
|
|
|
tree maxobjsize = max_object_size ();
|
|
|
|
bool warned = false;
|
|
|
|
if (opt == OPT_Wstringop_overread)
|
|
{
|
|
bool maybe = pad && pad->src.phi ();
|
|
if (maybe)
|
|
{
|
|
/* Issue a "maybe" warning only if the PHI refers to objects
|
|
at least one of which has more space remaining than the bound.
|
|
Otherwise, if the bound is greater, use the definitive form. */
|
|
offset_int remmax = pad->src.size_remaining ();
|
|
if (remmax < wi::to_offset (bndrng[0]))
|
|
maybe = false;
|
|
}
|
|
|
|
if (tree_int_cst_lt (maxobjsize, bndrng[0]))
|
|
{
|
|
if (bndrng[0] == bndrng[1])
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD specified bound %E may "
|
|
"exceed maximum object size %E")
|
|
: G_("%qD specified bound %E "
|
|
"exceeds maximum object size %E")),
|
|
func, bndrng[0], maxobjsize)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("specified bound %E may "
|
|
"exceed maximum object size %E")
|
|
: G_("specified bound %E "
|
|
"exceeds maximum object size %E")),
|
|
bndrng[0], maxobjsize));
|
|
else
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD specified bound [%E, %E] may "
|
|
"exceed maximum object size %E")
|
|
: G_("%qD specified bound [%E, %E] "
|
|
"exceeds maximum object size %E")),
|
|
func,
|
|
bndrng[0], bndrng[1], maxobjsize)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("specified bound [%E, %E] may "
|
|
"exceed maximum object size %E")
|
|
: G_("specified bound [%E, %E] "
|
|
"exceeds maximum object size %E")),
|
|
bndrng[0], bndrng[1], maxobjsize));
|
|
}
|
|
else if (!size || tree_int_cst_le (bndrng[0], size))
|
|
return false;
|
|
else if (tree_int_cst_equal (bndrng[0], bndrng[1]))
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD specified bound %E may exceed "
|
|
"source size %E")
|
|
: G_("%qD specified bound %E exceeds "
|
|
"source size %E")),
|
|
func, bndrng[0], size)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("specified bound %E may exceed "
|
|
"source size %E")
|
|
: G_("specified bound %E exceeds "
|
|
"source size %E")),
|
|
bndrng[0], size));
|
|
else
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD specified bound [%E, %E] may "
|
|
"exceed source size %E")
|
|
: G_("%qD specified bound [%E, %E] exceeds "
|
|
"source size %E")),
|
|
func, bndrng[0], bndrng[1], size)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("specified bound [%E, %E] may exceed "
|
|
"source size %E")
|
|
: G_("specified bound [%E, %E] exceeds "
|
|
"source size %E")),
|
|
bndrng[0], bndrng[1], size));
|
|
if (warned)
|
|
{
|
|
if (pad && pad->src.ref
|
|
&& has_location (pad->src.ref))
|
|
inform (get_location (pad->src.ref),
|
|
"source object allocated here");
|
|
suppress_warning (exp, opt);
|
|
}
|
|
|
|
return warned;
|
|
}
|
|
|
|
bool maybe = pad && pad->dst.phi ();
|
|
if (maybe)
|
|
{
|
|
/* Issue a "maybe" warning only if the PHI refers to objects
|
|
at least one of which has more space remaining than the bound.
|
|
Otherwise, if the bound is greater, use the definitive form. */
|
|
offset_int remmax = pad->dst.size_remaining ();
|
|
if (remmax < wi::to_offset (bndrng[0]))
|
|
maybe = false;
|
|
}
|
|
if (tree_int_cst_lt (maxobjsize, bndrng[0]))
|
|
{
|
|
if (bndrng[0] == bndrng[1])
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD specified size %E may "
|
|
"exceed maximum object size %E")
|
|
: G_("%qD specified size %E "
|
|
"exceeds maximum object size %E")),
|
|
func, bndrng[0], maxobjsize)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("specified size %E may exceed "
|
|
"maximum object size %E")
|
|
: G_("specified size %E exceeds "
|
|
"maximum object size %E")),
|
|
bndrng[0], maxobjsize));
|
|
else
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD specified size between %E and %E "
|
|
"may exceed maximum object size %E")
|
|
: G_("%qD specified size between %E and %E "
|
|
"exceeds maximum object size %E")),
|
|
func, bndrng[0], bndrng[1], maxobjsize)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("specified size between %E and %E "
|
|
"may exceed maximum object size %E")
|
|
: G_("specified size between %E and %E "
|
|
"exceeds maximum object size %E")),
|
|
bndrng[0], bndrng[1], maxobjsize));
|
|
}
|
|
else if (!size || tree_int_cst_le (bndrng[0], size))
|
|
return false;
|
|
else if (tree_int_cst_equal (bndrng[0], bndrng[1]))
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD specified bound %E may exceed "
|
|
"destination size %E")
|
|
: G_("%qD specified bound %E exceeds "
|
|
"destination size %E")),
|
|
func, bndrng[0], size)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("specified bound %E may exceed "
|
|
"destination size %E")
|
|
: G_("specified bound %E exceeds "
|
|
"destination size %E")),
|
|
bndrng[0], size));
|
|
else
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD specified bound [%E, %E] may exceed "
|
|
"destination size %E")
|
|
: G_("%qD specified bound [%E, %E] exceeds "
|
|
"destination size %E")),
|
|
func, bndrng[0], bndrng[1], size)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("specified bound [%E, %E] exceeds "
|
|
"destination size %E")
|
|
: G_("specified bound [%E, %E] exceeds "
|
|
"destination size %E")),
|
|
bndrng[0], bndrng[1], size));
|
|
|
|
if (warned)
|
|
{
|
|
if (pad && pad->dst.ref
|
|
&& has_location (pad->dst.ref))
|
|
inform (get_location (pad->dst.ref),
|
|
"destination object allocated here");
|
|
suppress_warning (exp, opt);
|
|
}
|
|
|
|
return warned;
|
|
}
|
|
|
|
bool
|
|
maybe_warn_for_bound (opt_code opt, location_t loc, gimple *stmt, tree func,
|
|
tree bndrng[2], tree size,
|
|
const access_data *pad /* = NULL */)
|
|
{
|
|
return maybe_warn_for_bound<gimple *> (opt, loc, stmt, func, bndrng, size,
|
|
pad);
|
|
}
|
|
|
|
bool
|
|
maybe_warn_for_bound (opt_code opt, location_t loc, tree expr, tree func,
|
|
tree bndrng[2], tree size,
|
|
const access_data *pad /* = NULL */)
|
|
{
|
|
return maybe_warn_for_bound<tree> (opt, loc, expr, func, bndrng, size, pad);
|
|
}
|
|
|
|
/* For an expression EXP issue an access warning controlled by option OPT
|
|
with access to a region SIZE bytes in size in the RANGE of sizes.
|
|
WRITE is true for a write access, READ for a read access, neither for
|
|
call that may or may not perform an access but for which the range
|
|
is expected to valid.
|
|
Returns true when a warning has been issued. */
|
|
|
|
template <class GimpleOrTree>
|
|
static bool
|
|
warn_for_access (location_t loc, tree func, GimpleOrTree exp, int opt,
|
|
tree range[2], tree size, bool write, bool read, bool maybe)
|
|
{
|
|
bool warned = false;
|
|
|
|
if (write && read)
|
|
{
|
|
if (tree_int_cst_equal (range[0], range[1]))
|
|
warned = (func
|
|
? warning_n (loc, opt, tree_to_uhwi (range[0]),
|
|
(maybe
|
|
? G_("%qD may access %E byte in a region "
|
|
"of size %E")
|
|
: G_("%qD accessing %E byte in a region "
|
|
"of size %E")),
|
|
(maybe
|
|
? G_ ("%qD may access %E bytes in a region "
|
|
"of size %E")
|
|
: G_ ("%qD accessing %E bytes in a region "
|
|
"of size %E")),
|
|
func, range[0], size)
|
|
: warning_n (loc, opt, tree_to_uhwi (range[0]),
|
|
(maybe
|
|
? G_("may access %E byte in a region "
|
|
"of size %E")
|
|
: G_("accessing %E byte in a region "
|
|
"of size %E")),
|
|
(maybe
|
|
? G_("may access %E bytes in a region "
|
|
"of size %E")
|
|
: G_("accessing %E bytes in a region "
|
|
"of size %E")),
|
|
range[0], size));
|
|
else if (tree_int_cst_sign_bit (range[1]))
|
|
{
|
|
/* Avoid printing the upper bound if it's invalid. */
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD may access %E or more bytes "
|
|
"in a region of size %E")
|
|
: G_("%qD accessing %E or more bytes "
|
|
"in a region of size %E")),
|
|
func, range[0], size)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("may access %E or more bytes "
|
|
"in a region of size %E")
|
|
: G_("accessing %E or more bytes "
|
|
"in a region of size %E")),
|
|
range[0], size));
|
|
}
|
|
else
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD may access between %E and %E "
|
|
"bytes in a region of size %E")
|
|
: G_("%qD accessing between %E and %E "
|
|
"bytes in a region of size %E")),
|
|
func, range[0], range[1], size)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("may access between %E and %E bytes "
|
|
"in a region of size %E")
|
|
: G_("accessing between %E and %E bytes "
|
|
"in a region of size %E")),
|
|
range[0], range[1], size));
|
|
return warned;
|
|
}
|
|
|
|
if (write)
|
|
{
|
|
if (tree_int_cst_equal (range[0], range[1]))
|
|
warned = (func
|
|
? warning_n (loc, opt, tree_to_uhwi (range[0]),
|
|
(maybe
|
|
? G_("%qD may write %E byte into a region "
|
|
"of size %E")
|
|
: G_("%qD writing %E byte into a region "
|
|
"of size %E overflows the destination")),
|
|
(maybe
|
|
? G_("%qD may write %E bytes into a region "
|
|
"of size %E")
|
|
: G_("%qD writing %E bytes into a region "
|
|
"of size %E overflows the destination")),
|
|
func, range[0], size)
|
|
: warning_n (loc, opt, tree_to_uhwi (range[0]),
|
|
(maybe
|
|
? G_("may write %E byte into a region "
|
|
"of size %E")
|
|
: G_("writing %E byte into a region "
|
|
"of size %E overflows the destination")),
|
|
(maybe
|
|
? G_("may write %E bytes into a region "
|
|
"of size %E")
|
|
: G_("writing %E bytes into a region "
|
|
"of size %E overflows the destination")),
|
|
range[0], size));
|
|
else if (tree_int_cst_sign_bit (range[1]))
|
|
{
|
|
/* Avoid printing the upper bound if it's invalid. */
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD may write %E or more bytes "
|
|
"into a region of size %E")
|
|
: G_("%qD writing %E or more bytes "
|
|
"into a region of size %E overflows "
|
|
"the destination")),
|
|
func, range[0], size)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("may write %E or more bytes into "
|
|
"a region of size %E")
|
|
: G_("writing %E or more bytes into "
|
|
"a region of size %E overflows "
|
|
"the destination")),
|
|
range[0], size));
|
|
}
|
|
else
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
(maybe
|
|
? G_("%qD may write between %E and %E bytes "
|
|
"into a region of size %E")
|
|
: G_("%qD writing between %E and %E bytes "
|
|
"into a region of size %E overflows "
|
|
"the destination")),
|
|
func, range[0], range[1], size)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("may write between %E and %E bytes "
|
|
"into a region of size %E")
|
|
: G_("writing between %E and %E bytes "
|
|
"into a region of size %E overflows "
|
|
"the destination")),
|
|
range[0], range[1], size));
|
|
return warned;
|
|
}
|
|
|
|
if (read)
|
|
{
|
|
if (tree_int_cst_equal (range[0], range[1]))
|
|
warned = (func
|
|
? warning_n (loc, OPT_Wstringop_overread,
|
|
tree_to_uhwi (range[0]),
|
|
(maybe
|
|
? G_("%qD may read %E byte from a region "
|
|
"of size %E")
|
|
: G_("%qD reading %E byte from a region "
|
|
"of size %E")),
|
|
(maybe
|
|
? G_("%qD may read %E bytes from a region "
|
|
"of size %E")
|
|
: G_("%qD reading %E bytes from a region "
|
|
"of size %E")),
|
|
func, range[0], size)
|
|
: warning_n (loc, OPT_Wstringop_overread,
|
|
tree_to_uhwi (range[0]),
|
|
(maybe
|
|
? G_("may read %E byte from a region "
|
|
"of size %E")
|
|
: G_("reading %E byte from a region "
|
|
"of size %E")),
|
|
(maybe
|
|
? G_("may read %E bytes from a region "
|
|
"of size %E")
|
|
: G_("reading %E bytes from a region "
|
|
"of size %E")),
|
|
range[0], size));
|
|
else if (tree_int_cst_sign_bit (range[1]))
|
|
{
|
|
/* Avoid printing the upper bound if it's invalid. */
|
|
warned = (func
|
|
? warning_at (loc, OPT_Wstringop_overread,
|
|
(maybe
|
|
? G_("%qD may read %E or more bytes "
|
|
"from a region of size %E")
|
|
: G_("%qD reading %E or more bytes "
|
|
"from a region of size %E")),
|
|
func, range[0], size)
|
|
: warning_at (loc, OPT_Wstringop_overread,
|
|
(maybe
|
|
? G_("may read %E or more bytes "
|
|
"from a region of size %E")
|
|
: G_("reading %E or more bytes "
|
|
"from a region of size %E")),
|
|
range[0], size));
|
|
}
|
|
else
|
|
warned = (func
|
|
? warning_at (loc, OPT_Wstringop_overread,
|
|
(maybe
|
|
? G_("%qD may read between %E and %E bytes "
|
|
"from a region of size %E")
|
|
: G_("%qD reading between %E and %E bytes "
|
|
"from a region of size %E")),
|
|
func, range[0], range[1], size)
|
|
: warning_at (loc, opt,
|
|
(maybe
|
|
? G_("may read between %E and %E bytes "
|
|
"from a region of size %E")
|
|
: G_("reading between %E and %E bytes "
|
|
"from a region of size %E")),
|
|
range[0], range[1], size));
|
|
|
|
if (warned)
|
|
suppress_warning (exp, OPT_Wstringop_overread);
|
|
|
|
return warned;
|
|
}
|
|
|
|
if (tree_int_cst_equal (range[0], range[1])
|
|
|| tree_int_cst_sign_bit (range[1]))
|
|
warned = (func
|
|
? warning_n (loc, OPT_Wstringop_overread,
|
|
tree_to_uhwi (range[0]),
|
|
"%qD expecting %E byte in a region of size %E",
|
|
"%qD expecting %E bytes in a region of size %E",
|
|
func, range[0], size)
|
|
: warning_n (loc, OPT_Wstringop_overread,
|
|
tree_to_uhwi (range[0]),
|
|
"expecting %E byte in a region of size %E",
|
|
"expecting %E bytes in a region of size %E",
|
|
range[0], size));
|
|
else if (tree_int_cst_sign_bit (range[1]))
|
|
{
|
|
/* Avoid printing the upper bound if it's invalid. */
|
|
warned = (func
|
|
? warning_at (loc, OPT_Wstringop_overread,
|
|
"%qD expecting %E or more bytes in a region "
|
|
"of size %E",
|
|
func, range[0], size)
|
|
: warning_at (loc, OPT_Wstringop_overread,
|
|
"expecting %E or more bytes in a region "
|
|
"of size %E",
|
|
range[0], size));
|
|
}
|
|
else
|
|
warned = (func
|
|
? warning_at (loc, OPT_Wstringop_overread,
|
|
"%qD expecting between %E and %E bytes in "
|
|
"a region of size %E",
|
|
func, range[0], range[1], size)
|
|
: warning_at (loc, OPT_Wstringop_overread,
|
|
"expecting between %E and %E bytes in "
|
|
"a region of size %E",
|
|
range[0], range[1], size));
|
|
|
|
if (warned)
|
|
suppress_warning (exp, OPT_Wstringop_overread);
|
|
|
|
return warned;
|
|
}
|
|
|
|
static bool
|
|
warn_for_access (location_t loc, tree func, gimple *stmt, int opt,
|
|
tree range[2], tree size, bool write, bool read, bool maybe)
|
|
{
|
|
return warn_for_access<gimple *>(loc, func, stmt, opt, range, size,
|
|
write, read, maybe);
|
|
}
|
|
|
|
static bool
|
|
warn_for_access (location_t loc, tree func, tree expr, int opt,
|
|
tree range[2], tree size, bool write, bool read, bool maybe)
|
|
{
|
|
return warn_for_access<tree>(loc, func, expr, opt, range, size,
|
|
write, read, maybe);
|
|
}
|
|
|
|
/* Helper to set RANGE to the range of BOUND if it's nonnull, bounded
|
|
by BNDRNG if nonnull and valid. */
|
|
|
|
static void
|
|
get_size_range (range_query *query, tree bound, gimple *stmt, tree range[2],
|
|
const offset_int bndrng[2])
|
|
{
|
|
if (bound)
|
|
get_size_range (query, bound, stmt, range);
|
|
|
|
if (!bndrng || (bndrng[0] == 0 && bndrng[1] == HOST_WIDE_INT_M1U))
|
|
return;
|
|
|
|
if (range[0] && TREE_CODE (range[0]) == INTEGER_CST)
|
|
{
|
|
offset_int r[] =
|
|
{ wi::to_offset (range[0]), wi::to_offset (range[1]) };
|
|
if (r[0] < bndrng[0])
|
|
range[0] = wide_int_to_tree (sizetype, bndrng[0]);
|
|
if (bndrng[1] < r[1])
|
|
range[1] = wide_int_to_tree (sizetype, bndrng[1]);
|
|
}
|
|
else
|
|
{
|
|
range[0] = wide_int_to_tree (sizetype, bndrng[0]);
|
|
range[1] = wide_int_to_tree (sizetype, bndrng[1]);
|
|
}
|
|
}
|
|
|
|
/* Try to verify that the sizes and lengths of the arguments to a string
|
|
manipulation function given by EXP are within valid bounds and that
|
|
the operation does not lead to buffer overflow or read past the end.
|
|
Arguments other than EXP may be null. When non-null, the arguments
|
|
have the following meaning:
|
|
DST is the destination of a copy call or NULL otherwise.
|
|
SRC is the source of a copy call or NULL otherwise.
|
|
DSTWRITE is the number of bytes written into the destination obtained
|
|
from the user-supplied size argument to the function (such as in
|
|
memcpy(DST, SRCs, DSTWRITE) or strncpy(DST, DRC, DSTWRITE).
|
|
MAXREAD is the user-supplied bound on the length of the source sequence
|
|
(such as in strncat(d, s, N). It specifies the upper limit on the number
|
|
of bytes to write. If NULL, it's taken to be the same as DSTWRITE.
|
|
SRCSTR is the source string (such as in strcpy(DST, SRC)) when the
|
|
expression EXP is a string function call (as opposed to a memory call
|
|
like memcpy). As an exception, SRCSTR can also be an integer denoting
|
|
the precomputed size of the source string or object (for functions like
|
|
memcpy).
|
|
DSTSIZE is the size of the destination object.
|
|
|
|
When DSTWRITE is null LEN is checked to verify that it doesn't exceed
|
|
SIZE_MAX.
|
|
|
|
WRITE is true for write accesses, READ is true for reads. Both are
|
|
false for simple size checks in calls to functions that neither read
|
|
from nor write to the region.
|
|
|
|
When nonnull, PAD points to a more detailed description of the access.
|
|
|
|
If the call is successfully verified as safe return true, otherwise
|
|
return false. */
|
|
|
|
template <class GimpleOrTree>
|
|
static bool
|
|
check_access (GimpleOrTree exp, tree dstwrite,
|
|
tree maxread, tree srcstr, tree dstsize,
|
|
access_mode mode, const access_data *pad,
|
|
range_query *rvals)
|
|
{
|
|
/* The size of the largest object is half the address space, or
|
|
PTRDIFF_MAX. (This is way too permissive.) */
|
|
tree maxobjsize = max_object_size ();
|
|
|
|
/* Either an approximate/minimum the length of the source string for
|
|
string functions or the size of the source object for raw memory
|
|
functions. */
|
|
tree slen = NULL_TREE;
|
|
|
|
/* The range of the access in bytes; first set to the write access
|
|
for functions that write and then read for those that also (or
|
|
just) read. */
|
|
tree range[2] = { NULL_TREE, NULL_TREE };
|
|
|
|
/* Set to true when the exact number of bytes written by a string
|
|
function like strcpy is not known and the only thing that is
|
|
known is that it must be at least one (for the terminating nul). */
|
|
bool at_least_one = false;
|
|
if (srcstr)
|
|
{
|
|
/* SRCSTR is normally a pointer to string but as a special case
|
|
it can be an integer denoting the length of a string. */
|
|
if (POINTER_TYPE_P (TREE_TYPE (srcstr)))
|
|
{
|
|
if (!check_nul_terminated_array (exp, srcstr, maxread))
|
|
/* Return if the array is not nul-terminated and a warning
|
|
has been issued. */
|
|
return false;
|
|
|
|
/* Try to determine the range of lengths the source string
|
|
refers to. If it can be determined and is less than
|
|
the upper bound given by MAXREAD add one to it for
|
|
the terminating nul. Otherwise, set it to one for
|
|
the same reason, or to MAXREAD as appropriate. */
|
|
c_strlen_data lendata = { };
|
|
get_range_strlen (srcstr, &lendata, /* eltsize = */ 1);
|
|
range[0] = lendata.minlen;
|
|
range[1] = lendata.maxbound ? lendata.maxbound : lendata.maxlen;
|
|
if (range[0]
|
|
&& TREE_CODE (range[0]) == INTEGER_CST
|
|
&& TREE_CODE (range[1]) == INTEGER_CST
|
|
&& (!maxread || TREE_CODE (maxread) == INTEGER_CST))
|
|
{
|
|
if (maxread && tree_int_cst_le (maxread, range[0]))
|
|
range[0] = range[1] = maxread;
|
|
else
|
|
range[0] = fold_build2 (PLUS_EXPR, size_type_node,
|
|
range[0], size_one_node);
|
|
|
|
if (maxread && tree_int_cst_le (maxread, range[1]))
|
|
range[1] = maxread;
|
|
else if (!integer_all_onesp (range[1]))
|
|
range[1] = fold_build2 (PLUS_EXPR, size_type_node,
|
|
range[1], size_one_node);
|
|
|
|
slen = range[0];
|
|
}
|
|
else
|
|
{
|
|
at_least_one = true;
|
|
slen = size_one_node;
|
|
}
|
|
}
|
|
else
|
|
slen = srcstr;
|
|
}
|
|
|
|
if (!dstwrite && !maxread)
|
|
{
|
|
/* When the only available piece of data is the object size
|
|
there is nothing to do. */
|
|
if (!slen)
|
|
return true;
|
|
|
|
/* Otherwise, when the length of the source sequence is known
|
|
(as with strlen), set DSTWRITE to it. */
|
|
if (!range[0])
|
|
dstwrite = slen;
|
|
}
|
|
|
|
if (!dstsize)
|
|
dstsize = maxobjsize;
|
|
|
|
/* Set RANGE to that of DSTWRITE if non-null, bounded by PAD->DST_BNDRNG
|
|
if valid. */
|
|
gimple *stmt = pad ? pad->stmt : nullptr;
|
|
get_size_range (rvals, dstwrite, stmt, range, pad ? pad->dst_bndrng : NULL);
|
|
|
|
tree func = get_callee_fndecl (exp);
|
|
/* Read vs write access by built-ins can be determined from the const
|
|
qualifiers on the pointer argument. In the absence of attribute
|
|
access, non-const qualified pointer arguments to user-defined
|
|
functions are assumed to both read and write the objects. */
|
|
const bool builtin = func ? fndecl_built_in_p (func) : false;
|
|
|
|
/* First check the number of bytes to be written against the maximum
|
|
object size. */
|
|
if (range[0]
|
|
&& TREE_CODE (range[0]) == INTEGER_CST
|
|
&& tree_int_cst_lt (maxobjsize, range[0]))
|
|
{
|
|
location_t loc = get_location (exp);
|
|
maybe_warn_for_bound (OPT_Wstringop_overflow_, loc, exp, func, range,
|
|
NULL_TREE, pad);
|
|
return false;
|
|
}
|
|
|
|
/* The number of bytes to write is "exact" if DSTWRITE is non-null,
|
|
constant, and in range of unsigned HOST_WIDE_INT. */
|
|
bool exactwrite = dstwrite && tree_fits_uhwi_p (dstwrite);
|
|
|
|
/* Next check the number of bytes to be written against the destination
|
|
object size. */
|
|
if (range[0] || !exactwrite || integer_all_onesp (dstwrite))
|
|
{
|
|
if (range[0]
|
|
&& TREE_CODE (range[0]) == INTEGER_CST
|
|
&& ((tree_fits_uhwi_p (dstsize)
|
|
&& tree_int_cst_lt (dstsize, range[0]))
|
|
|| (dstwrite
|
|
&& tree_fits_uhwi_p (dstwrite)
|
|
&& tree_int_cst_lt (dstwrite, range[0]))))
|
|
{
|
|
const opt_code opt = OPT_Wstringop_overflow_;
|
|
if (warning_suppressed_p (exp, opt)
|
|
|| (pad && pad->dst.ref
|
|
&& warning_suppressed_p (pad->dst.ref, opt)))
|
|
return false;
|
|
|
|
location_t loc = get_location (exp);
|
|
bool warned = false;
|
|
if (dstwrite == slen && at_least_one)
|
|
{
|
|
/* This is a call to strcpy with a destination of 0 size
|
|
and a source of unknown length. The call will write
|
|
at least one byte past the end of the destination. */
|
|
warned = (func
|
|
? warning_at (loc, opt,
|
|
"%qD writing %E or more bytes into "
|
|
"a region of size %E overflows "
|
|
"the destination",
|
|
func, range[0], dstsize)
|
|
: warning_at (loc, opt,
|
|
"writing %E or more bytes into "
|
|
"a region of size %E overflows "
|
|
"the destination",
|
|
range[0], dstsize));
|
|
}
|
|
else
|
|
{
|
|
const bool read
|
|
= mode == access_read_only || mode == access_read_write;
|
|
const bool write
|
|
= mode == access_write_only || mode == access_read_write;
|
|
const bool maybe = pad && pad->dst.parmarray;
|
|
warned = warn_for_access (loc, func, exp,
|
|
OPT_Wstringop_overflow_,
|
|
range, dstsize,
|
|
write, read && !builtin, maybe);
|
|
}
|
|
|
|
if (warned)
|
|
{
|
|
suppress_warning (exp, OPT_Wstringop_overflow_);
|
|
if (pad)
|
|
pad->dst.inform_access (pad->mode);
|
|
}
|
|
|
|
/* Return error when an overflow has been detected. */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/* Check the maximum length of the source sequence against the size
|
|
of the destination object if known, or against the maximum size
|
|
of an object. */
|
|
if (maxread)
|
|
{
|
|
/* Set RANGE to that of MAXREAD, bounded by PAD->SRC_BNDRNG if
|
|
PAD is nonnull and BNDRNG is valid. */
|
|
get_size_range (rvals, maxread, stmt, range, pad ? pad->src_bndrng : NULL);
|
|
|
|
location_t loc = get_location (exp);
|
|
tree size = dstsize;
|
|
if (pad && pad->mode == access_read_only)
|
|
size = wide_int_to_tree (sizetype, pad->src.size_remaining ());
|
|
|
|
if (range[0] && maxread && tree_fits_uhwi_p (size))
|
|
{
|
|
if (tree_int_cst_lt (maxobjsize, range[0]))
|
|
{
|
|
maybe_warn_for_bound (OPT_Wstringop_overread, loc, exp, func,
|
|
range, size, pad);
|
|
return false;
|
|
}
|
|
|
|
if (size != maxobjsize && tree_int_cst_lt (size, range[0]))
|
|
{
|
|
opt_code opt = (dstwrite || mode != access_read_only
|
|
? OPT_Wstringop_overflow_
|
|
: OPT_Wstringop_overread);
|
|
maybe_warn_for_bound (opt, loc, exp, func, range, size, pad);
|
|
return false;
|
|
}
|
|
}
|
|
|
|
maybe_warn_nonstring_arg (func, exp);
|
|
}
|
|
|
|
/* Check for reading past the end of SRC. */
|
|
bool overread = (slen
|
|
&& slen == srcstr
|
|
&& dstwrite
|
|
&& range[0]
|
|
&& TREE_CODE (slen) == INTEGER_CST
|
|
&& tree_int_cst_lt (slen, range[0]));
|
|
/* If none is determined try to get a better answer based on the details
|
|
in PAD. */
|
|
if (!overread
|
|
&& pad
|
|
&& pad->src.sizrng[1] >= 0
|
|
&& pad->src.offrng[0] >= 0
|
|
&& (pad->src.offrng[1] < 0
|
|
|| pad->src.offrng[0] <= pad->src.offrng[1]))
|
|
{
|
|
/* Set RANGE to that of MAXREAD, bounded by PAD->SRC_BNDRNG if
|
|
PAD is nonnull and BNDRNG is valid. */
|
|
get_size_range (rvals, maxread, stmt, range, pad ? pad->src_bndrng : NULL);
|
|
/* Set OVERREAD for reads starting just past the end of an object. */
|
|
overread = pad->src.sizrng[1] - pad->src.offrng[0] < pad->src_bndrng[0];
|
|
range[0] = wide_int_to_tree (sizetype, pad->src_bndrng[0]);
|
|
slen = size_zero_node;
|
|
}
|
|
|
|
if (overread)
|
|
{
|
|
const opt_code opt = OPT_Wstringop_overread;
|
|
if (warning_suppressed_p (exp, opt)
|
|
|| (srcstr && warning_suppressed_p (srcstr, opt))
|
|
|| (pad && pad->src.ref
|
|
&& warning_suppressed_p (pad->src.ref, opt)))
|
|
return false;
|
|
|
|
location_t loc = get_location (exp);
|
|
const bool read
|
|
= mode == access_read_only || mode == access_read_write;
|
|
const bool maybe = pad && pad->dst.parmarray;
|
|
if (warn_for_access (loc, func, exp, opt, range, slen, false, read,
|
|
maybe))
|
|
{
|
|
suppress_warning (exp, opt);
|
|
if (pad)
|
|
pad->src.inform_access (access_read_only);
|
|
}
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
static bool
|
|
check_access (gimple *stmt, tree dstwrite,
|
|
tree maxread, tree srcstr, tree dstsize,
|
|
access_mode mode, const access_data *pad,
|
|
range_query *rvals)
|
|
{
|
|
return check_access<gimple *> (stmt, dstwrite, maxread, srcstr, dstsize,
|
|
mode, pad, rvals);
|
|
}
|
|
|
|
bool
|
|
check_access (tree expr, tree dstwrite,
|
|
tree maxread, tree srcstr, tree dstsize,
|
|
access_mode mode, const access_data *pad /* = NULL */)
|
|
{
|
|
return check_access<tree> (expr, dstwrite, maxread, srcstr, dstsize,
|
|
mode, pad, nullptr);
|
|
}
|
|
|
|
/* Return true if STMT is a call to an allocation function. Unless
|
|
ALL_ALLOC is set, consider only functions that return dynmamically
|
|
allocated objects. Otherwise return true even for all forms of
|
|
alloca (including VLA). */
|
|
|
|
static bool
|
|
fndecl_alloc_p (tree fndecl, bool all_alloc)
|
|
{
|
|
if (!fndecl)
|
|
return false;
|
|
|
|
/* A call to operator new isn't recognized as one to a built-in. */
|
|
if (DECL_IS_OPERATOR_NEW_P (fndecl))
|
|
return true;
|
|
|
|
if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL))
|
|
{
|
|
switch (DECL_FUNCTION_CODE (fndecl))
|
|
{
|
|
case BUILT_IN_ALLOCA:
|
|
case BUILT_IN_ALLOCA_WITH_ALIGN:
|
|
return all_alloc;
|
|
case BUILT_IN_ALIGNED_ALLOC:
|
|
case BUILT_IN_CALLOC:
|
|
case BUILT_IN_GOMP_ALLOC:
|
|
case BUILT_IN_MALLOC:
|
|
case BUILT_IN_REALLOC:
|
|
case BUILT_IN_STRDUP:
|
|
case BUILT_IN_STRNDUP:
|
|
return true;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* A function is considered an allocation function if it's declared
|
|
with attribute malloc with an argument naming its associated
|
|
deallocation function. */
|
|
tree attrs = DECL_ATTRIBUTES (fndecl);
|
|
if (!attrs)
|
|
return false;
|
|
|
|
for (tree allocs = attrs;
|
|
(allocs = lookup_attribute ("malloc", allocs));
|
|
allocs = TREE_CHAIN (allocs))
|
|
{
|
|
tree args = TREE_VALUE (allocs);
|
|
if (!args)
|
|
continue;
|
|
|
|
if (TREE_VALUE (args))
|
|
return true;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Return true if STMT is a call to an allocation function. A wrapper
|
|
around fndecl_alloc_p. */
|
|
|
|
static bool
|
|
gimple_call_alloc_p (gimple *stmt, bool all_alloc = false)
|
|
{
|
|
return fndecl_alloc_p (gimple_call_fndecl (stmt), all_alloc);
|
|
}
|
|
|
|
/* Return true if DELC doesn't refer to an operator delete that's
|
|
suitable to call with a pointer returned from the operator new
|
|
described by NEWC. */
|
|
|
|
static bool
|
|
new_delete_mismatch_p (const demangle_component &newc,
|
|
const demangle_component &delc)
|
|
{
|
|
if (newc.type != delc.type)
|
|
return true;
|
|
|
|
switch (newc.type)
|
|
{
|
|
case DEMANGLE_COMPONENT_NAME:
|
|
{
|
|
int len = newc.u.s_name.len;
|
|
const char *news = newc.u.s_name.s;
|
|
const char *dels = delc.u.s_name.s;
|
|
if (len != delc.u.s_name.len || memcmp (news, dels, len))
|
|
return true;
|
|
|
|
if (news[len] == 'n')
|
|
{
|
|
if (news[len + 1] == 'a')
|
|
return dels[len] != 'd' || dels[len + 1] != 'a';
|
|
if (news[len + 1] == 'w')
|
|
return dels[len] != 'd' || dels[len + 1] != 'l';
|
|
}
|
|
return false;
|
|
}
|
|
|
|
case DEMANGLE_COMPONENT_OPERATOR:
|
|
/* Operator mismatches are handled above. */
|
|
return false;
|
|
|
|
case DEMANGLE_COMPONENT_EXTENDED_OPERATOR:
|
|
if (newc.u.s_extended_operator.args != delc.u.s_extended_operator.args)
|
|
return true;
|
|
return new_delete_mismatch_p (*newc.u.s_extended_operator.name,
|
|
*delc.u.s_extended_operator.name);
|
|
|
|
case DEMANGLE_COMPONENT_FIXED_TYPE:
|
|
if (newc.u.s_fixed.accum != delc.u.s_fixed.accum
|
|
|| newc.u.s_fixed.sat != delc.u.s_fixed.sat)
|
|
return true;
|
|
return new_delete_mismatch_p (*newc.u.s_fixed.length,
|
|
*delc.u.s_fixed.length);
|
|
|
|
case DEMANGLE_COMPONENT_CTOR:
|
|
if (newc.u.s_ctor.kind != delc.u.s_ctor.kind)
|
|
return true;
|
|
return new_delete_mismatch_p (*newc.u.s_ctor.name,
|
|
*delc.u.s_ctor.name);
|
|
|
|
case DEMANGLE_COMPONENT_DTOR:
|
|
if (newc.u.s_dtor.kind != delc.u.s_dtor.kind)
|
|
return true;
|
|
return new_delete_mismatch_p (*newc.u.s_dtor.name,
|
|
*delc.u.s_dtor.name);
|
|
|
|
case DEMANGLE_COMPONENT_BUILTIN_TYPE:
|
|
{
|
|
/* The demangler API provides no better way to compare built-in
|
|
types except to by comparing their demangled names. */
|
|
size_t nsz, dsz;
|
|
demangle_component *pnc = const_cast<demangle_component *>(&newc);
|
|
demangle_component *pdc = const_cast<demangle_component *>(&delc);
|
|
char *nts = cplus_demangle_print (0, pnc, 16, &nsz);
|
|
char *dts = cplus_demangle_print (0, pdc, 16, &dsz);
|
|
if (!nts != !dts)
|
|
return true;
|
|
bool mismatch = strcmp (nts, dts);
|
|
free (nts);
|
|
free (dts);
|
|
return mismatch;
|
|
}
|
|
|
|
case DEMANGLE_COMPONENT_SUB_STD:
|
|
if (newc.u.s_string.len != delc.u.s_string.len)
|
|
return true;
|
|
return memcmp (newc.u.s_string.string, delc.u.s_string.string,
|
|
newc.u.s_string.len);
|
|
|
|
case DEMANGLE_COMPONENT_FUNCTION_PARAM:
|
|
case DEMANGLE_COMPONENT_TEMPLATE_PARAM:
|
|
return newc.u.s_number.number != delc.u.s_number.number;
|
|
|
|
case DEMANGLE_COMPONENT_CHARACTER:
|
|
return newc.u.s_character.character != delc.u.s_character.character;
|
|
|
|
case DEMANGLE_COMPONENT_DEFAULT_ARG:
|
|
case DEMANGLE_COMPONENT_LAMBDA:
|
|
if (newc.u.s_unary_num.num != delc.u.s_unary_num.num)
|
|
return true;
|
|
return new_delete_mismatch_p (*newc.u.s_unary_num.sub,
|
|
*delc.u.s_unary_num.sub);
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!newc.u.s_binary.left != !delc.u.s_binary.left)
|
|
return true;
|
|
|
|
if (!newc.u.s_binary.left)
|
|
return false;
|
|
|
|
if (new_delete_mismatch_p (*newc.u.s_binary.left, *delc.u.s_binary.left)
|
|
|| !newc.u.s_binary.right != !delc.u.s_binary.right)
|
|
return true;
|
|
|
|
if (newc.u.s_binary.right)
|
|
return new_delete_mismatch_p (*newc.u.s_binary.right,
|
|
*delc.u.s_binary.right);
|
|
return false;
|
|
}
|
|
|
|
/* Return true if DELETE_DECL is an operator delete that's not suitable
|
|
to call with a pointer returned fron NEW_DECL. */
|
|
|
|
static bool
|
|
new_delete_mismatch_p (tree new_decl, tree delete_decl)
|
|
{
|
|
tree new_name = DECL_ASSEMBLER_NAME (new_decl);
|
|
tree delete_name = DECL_ASSEMBLER_NAME (delete_decl);
|
|
|
|
/* valid_new_delete_pair_p() returns a conservative result (currently
|
|
it only handles global operators). A true result is reliable but
|
|
a false result doesn't necessarily mean the operators don't match
|
|
unless CERTAIN is set. */
|
|
bool certain;
|
|
if (valid_new_delete_pair_p (new_name, delete_name, &certain))
|
|
return false;
|
|
/* CERTAIN is set when the negative result is certain. */
|
|
if (certain)
|
|
return true;
|
|
|
|
/* For anything not handled by valid_new_delete_pair_p() such as member
|
|
operators compare the individual demangled components of the mangled
|
|
name. */
|
|
const char *new_str = IDENTIFIER_POINTER (new_name);
|
|
const char *del_str = IDENTIFIER_POINTER (delete_name);
|
|
|
|
void *np = NULL, *dp = NULL;
|
|
demangle_component *ndc = cplus_demangle_v3_components (new_str, 0, &np);
|
|
demangle_component *ddc = cplus_demangle_v3_components (del_str, 0, &dp);
|
|
bool mismatch = new_delete_mismatch_p (*ndc, *ddc);
|
|
free (np);
|
|
free (dp);
|
|
return mismatch;
|
|
}
|
|
|
|
/* ALLOC_DECL and DEALLOC_DECL are pair of allocation and deallocation
|
|
functions. Return true if the latter is suitable to deallocate objects
|
|
allocated by calls to the former. */
|
|
|
|
static bool
|
|
matching_alloc_calls_p (tree alloc_decl, tree dealloc_decl)
|
|
{
|
|
/* Set to alloc_kind_t::builtin if ALLOC_DECL is associated with
|
|
a built-in deallocator. */
|
|
enum class alloc_kind_t { none, builtin, user }
|
|
alloc_dealloc_kind = alloc_kind_t::none;
|
|
|
|
if (DECL_IS_OPERATOR_NEW_P (alloc_decl))
|
|
{
|
|
if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl))
|
|
/* Return true iff both functions are of the same array or
|
|
singleton form and false otherwise. */
|
|
return !new_delete_mismatch_p (alloc_decl, dealloc_decl);
|
|
|
|
/* Return false for deallocation functions that are known not
|
|
to match. */
|
|
if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE)
|
|
|| fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC))
|
|
return false;
|
|
/* Otherwise proceed below to check the deallocation function's
|
|
"*dealloc" attributes to look for one that mentions this operator
|
|
new. */
|
|
}
|
|
else if (fndecl_built_in_p (alloc_decl, BUILT_IN_NORMAL))
|
|
{
|
|
switch (DECL_FUNCTION_CODE (alloc_decl))
|
|
{
|
|
case BUILT_IN_ALLOCA:
|
|
case BUILT_IN_ALLOCA_WITH_ALIGN:
|
|
return false;
|
|
|
|
case BUILT_IN_ALIGNED_ALLOC:
|
|
case BUILT_IN_CALLOC:
|
|
case BUILT_IN_GOMP_ALLOC:
|
|
case BUILT_IN_MALLOC:
|
|
case BUILT_IN_REALLOC:
|
|
case BUILT_IN_STRDUP:
|
|
case BUILT_IN_STRNDUP:
|
|
if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl))
|
|
return false;
|
|
|
|
if (fndecl_built_in_p (dealloc_decl, BUILT_IN_FREE)
|
|
|| fndecl_built_in_p (dealloc_decl, BUILT_IN_REALLOC))
|
|
return true;
|
|
|
|
alloc_dealloc_kind = alloc_kind_t::builtin;
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Set if DEALLOC_DECL both allocates and deallocates. */
|
|
alloc_kind_t realloc_kind = alloc_kind_t::none;
|
|
|
|
if (fndecl_built_in_p (dealloc_decl, BUILT_IN_NORMAL))
|
|
{
|
|
built_in_function dealloc_code = DECL_FUNCTION_CODE (dealloc_decl);
|
|
if (dealloc_code == BUILT_IN_REALLOC)
|
|
realloc_kind = alloc_kind_t::builtin;
|
|
|
|
for (tree amats = DECL_ATTRIBUTES (alloc_decl);
|
|
(amats = lookup_attribute ("malloc", amats));
|
|
amats = TREE_CHAIN (amats))
|
|
{
|
|
tree args = TREE_VALUE (amats);
|
|
if (!args)
|
|
continue;
|
|
|
|
tree fndecl = TREE_VALUE (args);
|
|
if (!fndecl || !DECL_P (fndecl))
|
|
continue;
|
|
|
|
if (fndecl_built_in_p (fndecl, BUILT_IN_NORMAL)
|
|
&& dealloc_code == DECL_FUNCTION_CODE (fndecl))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
const bool alloc_builtin = fndecl_built_in_p (alloc_decl, BUILT_IN_NORMAL);
|
|
alloc_kind_t realloc_dealloc_kind = alloc_kind_t::none;
|
|
|
|
/* If DEALLOC_DECL has an internal "*dealloc" attribute scan the list
|
|
of its associated allocation functions for ALLOC_DECL.
|
|
If the corresponding ALLOC_DECL is found they're a matching pair,
|
|
otherwise they're not.
|
|
With DDATS set to the Deallocator's *Dealloc ATtributes... */
|
|
for (tree ddats = DECL_ATTRIBUTES (dealloc_decl);
|
|
(ddats = lookup_attribute ("*dealloc", ddats));
|
|
ddats = TREE_CHAIN (ddats))
|
|
{
|
|
tree args = TREE_VALUE (ddats);
|
|
if (!args)
|
|
continue;
|
|
|
|
tree alloc = TREE_VALUE (args);
|
|
if (!alloc)
|
|
continue;
|
|
|
|
if (alloc == DECL_NAME (dealloc_decl))
|
|
realloc_kind = alloc_kind_t::user;
|
|
|
|
if (DECL_P (alloc))
|
|
{
|
|
gcc_checking_assert (fndecl_built_in_p (alloc, BUILT_IN_NORMAL));
|
|
|
|
switch (DECL_FUNCTION_CODE (alloc))
|
|
{
|
|
case BUILT_IN_ALIGNED_ALLOC:
|
|
case BUILT_IN_CALLOC:
|
|
case BUILT_IN_GOMP_ALLOC:
|
|
case BUILT_IN_MALLOC:
|
|
case BUILT_IN_REALLOC:
|
|
case BUILT_IN_STRDUP:
|
|
case BUILT_IN_STRNDUP:
|
|
realloc_dealloc_kind = alloc_kind_t::builtin;
|
|
break;
|
|
default:
|
|
break;
|
|
}
|
|
|
|
if (!alloc_builtin)
|
|
continue;
|
|
|
|
if (DECL_FUNCTION_CODE (alloc) != DECL_FUNCTION_CODE (alloc_decl))
|
|
continue;
|
|
|
|
return true;
|
|
}
|
|
|
|
if (alloc == DECL_NAME (alloc_decl))
|
|
return true;
|
|
}
|
|
|
|
if (realloc_kind == alloc_kind_t::none)
|
|
return false;
|
|
|
|
hash_set<tree> common_deallocs;
|
|
/* Special handling for deallocators. Iterate over both the allocator's
|
|
and the reallocator's associated deallocator functions looking for
|
|
the first one in common. If one is found, the de/reallocator is
|
|
a match for the allocator even though the latter isn't directly
|
|
associated with the former. This simplifies declarations in system
|
|
headers.
|
|
With AMATS set to the Allocator's Malloc ATtributes,
|
|
and RMATS set to Reallocator's Malloc ATtributes... */
|
|
for (tree amats = DECL_ATTRIBUTES (alloc_decl),
|
|
rmats = DECL_ATTRIBUTES (dealloc_decl);
|
|
(amats = lookup_attribute ("malloc", amats))
|
|
|| (rmats = lookup_attribute ("malloc", rmats));
|
|
amats = amats ? TREE_CHAIN (amats) : NULL_TREE,
|
|
rmats = rmats ? TREE_CHAIN (rmats) : NULL_TREE)
|
|
{
|
|
if (tree args = amats ? TREE_VALUE (amats) : NULL_TREE)
|
|
if (tree adealloc = TREE_VALUE (args))
|
|
{
|
|
if (DECL_P (adealloc)
|
|
&& fndecl_built_in_p (adealloc, BUILT_IN_NORMAL))
|
|
{
|
|
built_in_function fncode = DECL_FUNCTION_CODE (adealloc);
|
|
if (fncode == BUILT_IN_FREE || fncode == BUILT_IN_REALLOC)
|
|
{
|
|
if (realloc_kind == alloc_kind_t::builtin)
|
|
return true;
|
|
alloc_dealloc_kind = alloc_kind_t::builtin;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
common_deallocs.add (adealloc);
|
|
}
|
|
|
|
if (tree args = rmats ? TREE_VALUE (rmats) : NULL_TREE)
|
|
if (tree ddealloc = TREE_VALUE (args))
|
|
{
|
|
if (DECL_P (ddealloc)
|
|
&& fndecl_built_in_p (ddealloc, BUILT_IN_NORMAL))
|
|
{
|
|
built_in_function fncode = DECL_FUNCTION_CODE (ddealloc);
|
|
if (fncode == BUILT_IN_FREE || fncode == BUILT_IN_REALLOC)
|
|
{
|
|
if (alloc_dealloc_kind == alloc_kind_t::builtin)
|
|
return true;
|
|
realloc_dealloc_kind = alloc_kind_t::builtin;
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (common_deallocs.add (ddealloc))
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/* Succeed only if ALLOC_DECL and the reallocator DEALLOC_DECL share
|
|
a built-in deallocator. */
|
|
return (alloc_dealloc_kind == alloc_kind_t::builtin
|
|
&& realloc_dealloc_kind == alloc_kind_t::builtin);
|
|
}
|
|
|
|
/* Return true if DEALLOC_DECL is a function suitable to deallocate
|
|
objectes allocated by the ALLOC call. */
|
|
|
|
static bool
|
|
matching_alloc_calls_p (gimple *alloc, tree dealloc_decl)
|
|
{
|
|
tree alloc_decl = gimple_call_fndecl (alloc);
|
|
if (!alloc_decl)
|
|
return true;
|
|
|
|
return matching_alloc_calls_p (alloc_decl, dealloc_decl);
|
|
}
|
|
|
|
/* Diagnose a call EXP to deallocate a pointer referenced by AREF if it
|
|
includes a nonzero offset. Such a pointer cannot refer to the beginning
|
|
of an allocated object. A negative offset may refer to it only if
|
|
the target pointer is unknown. */
|
|
|
|
static bool
|
|
warn_dealloc_offset (location_t loc, gimple *call, const access_ref &aref)
|
|
{
|
|
if (aref.deref || aref.offrng[0] <= 0 || aref.offrng[1] <= 0)
|
|
return false;
|
|
|
|
tree dealloc_decl = gimple_call_fndecl (call);
|
|
if (!dealloc_decl)
|
|
return false;
|
|
|
|
if (DECL_IS_OPERATOR_DELETE_P (dealloc_decl)
|
|
&& !DECL_IS_REPLACEABLE_OPERATOR (dealloc_decl))
|
|
{
|
|
/* A call to a user-defined operator delete with a pointer plus offset
|
|
may be valid if it's returned from an unknown function (i.e., one
|
|
that's not operator new). */
|
|
if (TREE_CODE (aref.ref) == SSA_NAME)
|
|
{
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref);
|
|
if (is_gimple_call (def_stmt))
|
|
{
|
|
tree alloc_decl = gimple_call_fndecl (def_stmt);
|
|
if (!alloc_decl || !DECL_IS_OPERATOR_NEW_P (alloc_decl))
|
|
return false;
|
|
}
|
|
}
|
|
}
|
|
|
|
char offstr[80];
|
|
offstr[0] = '\0';
|
|
if (wi::fits_shwi_p (aref.offrng[0]))
|
|
{
|
|
if (aref.offrng[0] == aref.offrng[1]
|
|
|| !wi::fits_shwi_p (aref.offrng[1]))
|
|
sprintf (offstr, " %lli",
|
|
(long long)aref.offrng[0].to_shwi ());
|
|
else
|
|
sprintf (offstr, " [%lli, %lli]",
|
|
(long long)aref.offrng[0].to_shwi (),
|
|
(long long)aref.offrng[1].to_shwi ());
|
|
}
|
|
|
|
if (!warning_at (loc, OPT_Wfree_nonheap_object,
|
|
"%qD called on pointer %qE with nonzero offset%s",
|
|
dealloc_decl, aref.ref, offstr))
|
|
return false;
|
|
|
|
if (DECL_P (aref.ref))
|
|
inform (get_location (aref.ref), "declared here");
|
|
else if (TREE_CODE (aref.ref) == SSA_NAME)
|
|
{
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref);
|
|
if (is_gimple_call (def_stmt))
|
|
{
|
|
location_t def_loc = get_location (def_stmt);
|
|
tree alloc_decl = gimple_call_fndecl (def_stmt);
|
|
if (alloc_decl)
|
|
inform (def_loc,
|
|
"returned from %qD", alloc_decl);
|
|
else if (tree alloc_fntype = gimple_call_fntype (def_stmt))
|
|
inform (def_loc,
|
|
"returned from %qT", alloc_fntype);
|
|
else
|
|
inform (def_loc, "obtained here");
|
|
}
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
namespace {
|
|
|
|
const pass_data pass_data_waccess = {
|
|
GIMPLE_PASS,
|
|
"waccess",
|
|
OPTGROUP_NONE,
|
|
TV_NONE,
|
|
PROP_cfg, /* properties_required */
|
|
0, /* properties_provided */
|
|
0, /* properties_destroyed */
|
|
0, /* properties_start */
|
|
0, /* properties_finish */
|
|
};
|
|
|
|
/* Pass to detect invalid accesses. */
|
|
class pass_waccess : public gimple_opt_pass
|
|
{
|
|
public:
|
|
pass_waccess (gcc::context *);
|
|
|
|
~pass_waccess ();
|
|
|
|
opt_pass *clone ();
|
|
|
|
virtual bool gate (function *);
|
|
|
|
void set_pass_param (unsigned, bool);
|
|
|
|
virtual unsigned int execute (function *);
|
|
|
|
private:
|
|
/* Not copyable or assignable. */
|
|
pass_waccess (pass_waccess &) = delete;
|
|
void operator= (pass_waccess &) = delete;
|
|
|
|
/* Check a call to an atomic built-in function. */
|
|
bool check_atomic_builtin (gcall *);
|
|
|
|
/* Check a call to a built-in function. */
|
|
bool check_builtin (gcall *);
|
|
|
|
/* Check a call to an ordinary function for invalid accesses. */
|
|
bool check_call_access (gcall *);
|
|
|
|
/* Check a non-call statement. */
|
|
void check_stmt (gimple *);
|
|
|
|
/* Check statements in a basic block. */
|
|
void check_block (basic_block);
|
|
|
|
/* Check a call to a function. */
|
|
void check_call (gcall *);
|
|
|
|
/* Check a call to the named built-in function. */
|
|
void check_alloca (gcall *);
|
|
void check_alloc_size_call (gcall *);
|
|
void check_strcat (gcall *);
|
|
void check_strncat (gcall *);
|
|
void check_stxcpy (gcall *);
|
|
void check_stxncpy (gcall *);
|
|
void check_strncmp (gcall *);
|
|
void check_memop_access (gimple *, tree, tree, tree);
|
|
void check_read_access (gimple *, tree, tree = NULL_TREE, int = 1);
|
|
|
|
void maybe_check_dealloc_call (gcall *);
|
|
void maybe_check_access_sizes (rdwr_map *, tree, tree, gimple *);
|
|
bool maybe_warn_memmodel (gimple *, tree, tree, const unsigned char *);
|
|
void check_atomic_memmodel (gimple *, tree, tree, const unsigned char *);
|
|
|
|
/* Check for uses of indeterminate pointers. */
|
|
void check_pointer_uses (gimple *, tree, tree = NULL_TREE, bool = false);
|
|
|
|
/* Return the argument that a call returns. */
|
|
tree gimple_call_return_arg (gcall *);
|
|
tree gimple_call_return_arg_ref (gcall *);
|
|
|
|
/* Check a call for uses of a dangling pointer arguments. */
|
|
void check_call_dangling (gcall *);
|
|
|
|
/* Check uses of a dangling pointer or those derived from it. */
|
|
void check_dangling_uses (tree, tree, bool = false, bool = false);
|
|
void check_dangling_uses ();
|
|
void check_dangling_stores ();
|
|
void check_dangling_stores (basic_block, hash_set<tree> &, auto_bitmap &);
|
|
|
|
void warn_invalid_pointer (tree, gimple *, gimple *, tree, bool, bool = false);
|
|
|
|
/* Return true if use follows an invalidating statement. */
|
|
bool use_after_inval_p (gimple *, gimple *, bool = false);
|
|
|
|
/* A pointer_query object and its cache to store information about
|
|
pointers and their targets in. */
|
|
pointer_query m_ptr_qry;
|
|
pointer_query::cache_type m_var_cache;
|
|
/* Mapping from DECLs and their clobber statements in the function. */
|
|
hash_map<tree, gimple *> m_clobbers;
|
|
/* A bit is set for each basic block whose statements have been assigned
|
|
valid UIDs. */
|
|
bitmap m_bb_uids_set;
|
|
/* The current function. */
|
|
function *m_func;
|
|
/* True to run checks for uses of dangling pointers. */
|
|
bool m_check_dangling_p;
|
|
/* True to run checks early on in the optimization pipeline. */
|
|
bool m_early_checks_p;
|
|
};
|
|
|
|
/* Construct the pass. */
|
|
|
|
pass_waccess::pass_waccess (gcc::context *ctxt)
|
|
: gimple_opt_pass (pass_data_waccess, ctxt),
|
|
m_ptr_qry (NULL, &m_var_cache),
|
|
m_var_cache (),
|
|
m_clobbers (),
|
|
m_bb_uids_set (),
|
|
m_func (),
|
|
m_check_dangling_p (),
|
|
m_early_checks_p ()
|
|
{
|
|
}
|
|
|
|
/* Return a copy of the pass with RUN_NUMBER one greater than THIS. */
|
|
|
|
opt_pass*
|
|
pass_waccess::clone ()
|
|
{
|
|
return new pass_waccess (m_ctxt);
|
|
}
|
|
|
|
/* Release pointer_query cache. */
|
|
|
|
pass_waccess::~pass_waccess ()
|
|
{
|
|
m_ptr_qry.flush_cache ();
|
|
}
|
|
|
|
void
|
|
pass_waccess::set_pass_param (unsigned int n, bool early)
|
|
{
|
|
gcc_assert (n == 0);
|
|
|
|
m_early_checks_p = early;
|
|
}
|
|
|
|
/* Return true when any checks performed by the pass are enabled. */
|
|
|
|
bool
|
|
pass_waccess::gate (function *)
|
|
{
|
|
return (warn_free_nonheap_object
|
|
|| warn_mismatched_alloc
|
|
|| warn_mismatched_new_delete);
|
|
}
|
|
|
|
/* Initialize ALLOC_OBJECT_SIZE_LIMIT based on the -Walloc-size-larger-than=
|
|
setting if the option is specified, or to the maximum object size if it
|
|
is not. Return the initialized value. */
|
|
|
|
static tree
|
|
alloc_max_size (void)
|
|
{
|
|
HOST_WIDE_INT limit = warn_alloc_size_limit;
|
|
if (limit == HOST_WIDE_INT_MAX)
|
|
limit = tree_to_shwi (TYPE_MAX_VALUE (ptrdiff_type_node));
|
|
|
|
return build_int_cst (size_type_node, limit);
|
|
}
|
|
|
|
/* Diagnose a call EXP to function FN decorated with attribute alloc_size
|
|
whose argument numbers given by IDX with values given by ARGS exceed
|
|
the maximum object size or cause an unsigned oveflow (wrapping) when
|
|
multiplied. FN is null when EXP is a call via a function pointer.
|
|
When ARGS[0] is null the function does nothing. ARGS[1] may be null
|
|
for functions like malloc, and non-null for those like calloc that
|
|
are decorated with a two-argument attribute alloc_size. */
|
|
|
|
void
|
|
maybe_warn_alloc_args_overflow (gimple *stmt, const tree args[2],
|
|
const int idx[2])
|
|
{
|
|
/* The range each of the (up to) two arguments is known to be in. */
|
|
tree argrange[2][2] = { { NULL_TREE, NULL_TREE }, { NULL_TREE, NULL_TREE } };
|
|
|
|
/* Maximum object size set by -Walloc-size-larger-than= or SIZE_MAX / 2. */
|
|
tree maxobjsize = alloc_max_size ();
|
|
|
|
location_t loc = get_location (stmt);
|
|
|
|
tree fn = gimple_call_fndecl (stmt);
|
|
tree fntype = fn ? TREE_TYPE (fn) : gimple_call_fntype (stmt);
|
|
bool warned = false;
|
|
|
|
/* Validate each argument individually. */
|
|
for (unsigned i = 0; i != 2 && args[i]; ++i)
|
|
{
|
|
if (TREE_CODE (args[i]) == INTEGER_CST)
|
|
{
|
|
argrange[i][0] = args[i];
|
|
argrange[i][1] = args[i];
|
|
|
|
if (tree_int_cst_lt (args[i], integer_zero_node))
|
|
{
|
|
warned = warning_at (loc, OPT_Walloc_size_larger_than_,
|
|
"argument %i value %qE is negative",
|
|
idx[i] + 1, args[i]);
|
|
}
|
|
else if (integer_zerop (args[i]))
|
|
{
|
|
/* Avoid issuing -Walloc-zero for allocation functions other
|
|
than __builtin_alloca that are declared with attribute
|
|
returns_nonnull because there's no portability risk. This
|
|
avoids warning for such calls to libiberty's xmalloc and
|
|
friends.
|
|
Also avoid issuing the warning for calls to function named
|
|
"alloca". */
|
|
if (fn && fndecl_built_in_p (fn, BUILT_IN_ALLOCA)
|
|
? IDENTIFIER_LENGTH (DECL_NAME (fn)) != 6
|
|
: !lookup_attribute ("returns_nonnull",
|
|
TYPE_ATTRIBUTES (fntype)))
|
|
warned = warning_at (loc, OPT_Walloc_zero,
|
|
"argument %i value is zero",
|
|
idx[i] + 1);
|
|
}
|
|
else if (tree_int_cst_lt (maxobjsize, args[i]))
|
|
{
|
|
/* G++ emits calls to ::operator new[](SIZE_MAX) in C++98
|
|
mode and with -fno-exceptions as a way to indicate array
|
|
size overflow. There's no good way to detect C++98 here
|
|
so avoid diagnosing these calls for all C++ modes. */
|
|
if (i == 0
|
|
&& fn
|
|
&& !args[1]
|
|
&& lang_GNU_CXX ()
|
|
&& DECL_IS_OPERATOR_NEW_P (fn)
|
|
&& integer_all_onesp (args[i]))
|
|
continue;
|
|
|
|
warned = warning_at (loc, OPT_Walloc_size_larger_than_,
|
|
"argument %i value %qE exceeds "
|
|
"maximum object size %E",
|
|
idx[i] + 1, args[i], maxobjsize);
|
|
}
|
|
}
|
|
else if (TREE_CODE (args[i]) == SSA_NAME
|
|
&& get_size_range (args[i], argrange[i]))
|
|
{
|
|
/* Verify that the argument's range is not negative (including
|
|
upper bound of zero). */
|
|
if (tree_int_cst_lt (argrange[i][0], integer_zero_node)
|
|
&& tree_int_cst_le (argrange[i][1], integer_zero_node))
|
|
{
|
|
warned = warning_at (loc, OPT_Walloc_size_larger_than_,
|
|
"argument %i range [%E, %E] is negative",
|
|
idx[i] + 1,
|
|
argrange[i][0], argrange[i][1]);
|
|
}
|
|
else if (tree_int_cst_lt (maxobjsize, argrange[i][0]))
|
|
{
|
|
warned = warning_at (loc, OPT_Walloc_size_larger_than_,
|
|
"argument %i range [%E, %E] exceeds "
|
|
"maximum object size %E",
|
|
idx[i] + 1,
|
|
argrange[i][0], argrange[i][1],
|
|
maxobjsize);
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!argrange[0][0])
|
|
return;
|
|
|
|
/* For a two-argument alloc_size, validate the product of the two
|
|
arguments if both of their values or ranges are known. */
|
|
if (!warned && tree_fits_uhwi_p (argrange[0][0])
|
|
&& argrange[1][0] && tree_fits_uhwi_p (argrange[1][0])
|
|
&& !integer_onep (argrange[0][0])
|
|
&& !integer_onep (argrange[1][0]))
|
|
{
|
|
/* Check for overflow in the product of a function decorated with
|
|
attribute alloc_size (X, Y). */
|
|
unsigned szprec = TYPE_PRECISION (size_type_node);
|
|
wide_int x = wi::to_wide (argrange[0][0], szprec);
|
|
wide_int y = wi::to_wide (argrange[1][0], szprec);
|
|
|
|
wi::overflow_type vflow;
|
|
wide_int prod = wi::umul (x, y, &vflow);
|
|
|
|
if (vflow)
|
|
warned = warning_at (loc, OPT_Walloc_size_larger_than_,
|
|
"product %<%E * %E%> of arguments %i and %i "
|
|
"exceeds %<SIZE_MAX%>",
|
|
argrange[0][0], argrange[1][0],
|
|
idx[0] + 1, idx[1] + 1);
|
|
else if (wi::ltu_p (wi::to_wide (maxobjsize, szprec), prod))
|
|
warned = warning_at (loc, OPT_Walloc_size_larger_than_,
|
|
"product %<%E * %E%> of arguments %i and %i "
|
|
"exceeds maximum object size %E",
|
|
argrange[0][0], argrange[1][0],
|
|
idx[0] + 1, idx[1] + 1,
|
|
maxobjsize);
|
|
|
|
if (warned)
|
|
{
|
|
/* Print the full range of each of the two arguments to make
|
|
it clear when it is, in fact, in a range and not constant. */
|
|
if (argrange[0][0] != argrange [0][1])
|
|
inform (loc, "argument %i in the range [%E, %E]",
|
|
idx[0] + 1, argrange[0][0], argrange[0][1]);
|
|
if (argrange[1][0] != argrange [1][1])
|
|
inform (loc, "argument %i in the range [%E, %E]",
|
|
idx[1] + 1, argrange[1][0], argrange[1][1]);
|
|
}
|
|
}
|
|
|
|
if (warned && fn)
|
|
{
|
|
location_t fnloc = DECL_SOURCE_LOCATION (fn);
|
|
|
|
if (DECL_IS_UNDECLARED_BUILTIN (fn))
|
|
inform (loc,
|
|
"in a call to built-in allocation function %qD", fn);
|
|
else
|
|
inform (fnloc,
|
|
"in a call to allocation function %qD declared here", fn);
|
|
}
|
|
}
|
|
|
|
/* Check a call to an alloca function for an excessive size. */
|
|
|
|
void
|
|
pass_waccess::check_alloca (gcall *stmt)
|
|
{
|
|
if (m_early_checks_p)
|
|
return;
|
|
|
|
if ((warn_vla_limit >= HOST_WIDE_INT_MAX
|
|
&& warn_alloc_size_limit < warn_vla_limit)
|
|
|| (warn_alloca_limit >= HOST_WIDE_INT_MAX
|
|
&& warn_alloc_size_limit < warn_alloca_limit))
|
|
{
|
|
/* -Walloca-larger-than and -Wvla-larger-than settings of less
|
|
than HWI_MAX override the more general -Walloc-size-larger-than
|
|
so unless either of the former options is smaller than the last
|
|
one (wchich would imply that the call was already checked), check
|
|
the alloca arguments for overflow. */
|
|
const tree alloc_args[] = { call_arg (stmt, 0), NULL_TREE };
|
|
const int idx[] = { 0, -1 };
|
|
maybe_warn_alloc_args_overflow (stmt, alloc_args, idx);
|
|
}
|
|
}
|
|
|
|
/* Check a call to an allocation function for an excessive size. */
|
|
|
|
void
|
|
pass_waccess::check_alloc_size_call (gcall *stmt)
|
|
{
|
|
if (m_early_checks_p)
|
|
return;
|
|
|
|
if (gimple_call_num_args (stmt) < 1)
|
|
/* Avoid invalid calls to functions without a prototype. */
|
|
return;
|
|
|
|
tree fndecl = gimple_call_fndecl (stmt);
|
|
if (fndecl && gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
|
|
{
|
|
/* Alloca is handled separately. */
|
|
switch (DECL_FUNCTION_CODE (fndecl))
|
|
{
|
|
case BUILT_IN_ALLOCA:
|
|
case BUILT_IN_ALLOCA_WITH_ALIGN:
|
|
case BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX:
|
|
return;
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
|
|
tree fntype = gimple_call_fntype (stmt);
|
|
tree fntypeattrs = TYPE_ATTRIBUTES (fntype);
|
|
|
|
tree alloc_size = lookup_attribute ("alloc_size", fntypeattrs);
|
|
if (!alloc_size)
|
|
return;
|
|
|
|
/* Extract attribute alloc_size from the type of the called expression
|
|
(which could be a function or a function pointer) and if set, store
|
|
the indices of the corresponding arguments in ALLOC_IDX, and then
|
|
the actual argument(s) at those indices in ALLOC_ARGS. */
|
|
int idx[2] = { -1, -1 };
|
|
tree alloc_args[] = { NULL_TREE, NULL_TREE };
|
|
unsigned nargs = gimple_call_num_args (stmt);
|
|
|
|
tree args = TREE_VALUE (alloc_size);
|
|
idx[0] = TREE_INT_CST_LOW (TREE_VALUE (args)) - 1;
|
|
/* Avoid invalid calls to functions without a prototype. */
|
|
if ((unsigned) idx[0] >= nargs)
|
|
return;
|
|
alloc_args[0] = call_arg (stmt, idx[0]);
|
|
if (TREE_CHAIN (args))
|
|
{
|
|
idx[1] = TREE_INT_CST_LOW (TREE_VALUE (TREE_CHAIN (args))) - 1;
|
|
if ((unsigned) idx[1] >= nargs)
|
|
return;
|
|
alloc_args[1] = call_arg (stmt, idx[1]);
|
|
}
|
|
|
|
maybe_warn_alloc_args_overflow (stmt, alloc_args, idx);
|
|
}
|
|
|
|
/* Check a call STMT to strcat() for overflow and warn if it does. */
|
|
|
|
void
|
|
pass_waccess::check_strcat (gcall *stmt)
|
|
{
|
|
if (m_early_checks_p)
|
|
return;
|
|
|
|
if (!warn_stringop_overflow && !warn_stringop_overread)
|
|
return;
|
|
|
|
tree dest = call_arg (stmt, 0);
|
|
tree src = call_arg (stmt, 1);
|
|
|
|
/* There is no way here to determine the length of the string in
|
|
the destination to which the SRC string is being appended so
|
|
just diagnose cases when the souce string is longer than
|
|
the destination object. */
|
|
access_data data (m_ptr_qry.rvals, stmt, access_read_write, NULL_TREE,
|
|
true, NULL_TREE, true);
|
|
const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1;
|
|
compute_objsize (src, stmt, ost, &data.src, &m_ptr_qry);
|
|
tree destsize = compute_objsize (dest, stmt, ost, &data.dst, &m_ptr_qry);
|
|
|
|
check_access (stmt, /*dstwrite=*/NULL_TREE, /*maxread=*/NULL_TREE,
|
|
src, destsize, data.mode, &data, m_ptr_qry.rvals);
|
|
}
|
|
|
|
/* Check a call STMT to strcat() for overflow and warn if it does. */
|
|
|
|
void
|
|
pass_waccess::check_strncat (gcall *stmt)
|
|
{
|
|
if (m_early_checks_p)
|
|
return;
|
|
|
|
if (!warn_stringop_overflow && !warn_stringop_overread)
|
|
return;
|
|
|
|
tree dest = call_arg (stmt, 0);
|
|
tree src = call_arg (stmt, 1);
|
|
/* The upper bound on the number of bytes to write. */
|
|
tree maxread = call_arg (stmt, 2);
|
|
|
|
/* Detect unterminated source (only). */
|
|
if (!check_nul_terminated_array (stmt, src, maxread))
|
|
return;
|
|
|
|
/* The length of the source sequence. */
|
|
tree slen = c_strlen (src, 1);
|
|
|
|
/* Try to determine the range of lengths that the source expression
|
|
refers to. Since the lengths are only used for warning and not
|
|
for code generation disable strict mode below. */
|
|
tree maxlen = slen;
|
|
if (!maxlen)
|
|
{
|
|
c_strlen_data lendata = { };
|
|
get_range_strlen (src, &lendata, /* eltsize = */ 1);
|
|
maxlen = lendata.maxbound;
|
|
}
|
|
|
|
access_data data (m_ptr_qry.rvals, stmt, access_read_write);
|
|
/* Try to verify that the destination is big enough for the shortest
|
|
string. First try to determine the size of the destination object
|
|
into which the source is being copied. */
|
|
const int ost = warn_stringop_overflow - 1;
|
|
tree destsize = compute_objsize (dest, stmt, ost, &data.dst, &m_ptr_qry);
|
|
|
|
/* Add one for the terminating nul. */
|
|
tree srclen = (maxlen
|
|
? fold_build2 (PLUS_EXPR, size_type_node, maxlen,
|
|
size_one_node)
|
|
: NULL_TREE);
|
|
|
|
/* The strncat function copies at most MAXREAD bytes and always appends
|
|
the terminating nul so the specified upper bound should never be equal
|
|
to (or greater than) the size of the destination. */
|
|
if (tree_fits_uhwi_p (maxread) && tree_fits_uhwi_p (destsize)
|
|
&& tree_int_cst_equal (destsize, maxread))
|
|
{
|
|
location_t loc = get_location (stmt);
|
|
warning_at (loc, OPT_Wstringop_overflow_,
|
|
"%qD specified bound %E equals destination size",
|
|
get_callee_fndecl (stmt), maxread);
|
|
|
|
return;
|
|
}
|
|
|
|
if (!srclen
|
|
|| (maxread && tree_fits_uhwi_p (maxread)
|
|
&& tree_fits_uhwi_p (srclen)
|
|
&& tree_int_cst_lt (maxread, srclen)))
|
|
srclen = maxread;
|
|
|
|
check_access (stmt, /*dstwrite=*/NULL_TREE, maxread, srclen,
|
|
destsize, data.mode, &data, m_ptr_qry.rvals);
|
|
}
|
|
|
|
/* Check a call STMT to stpcpy() or strcpy() for overflow and warn
|
|
if it does. */
|
|
|
|
void
|
|
pass_waccess::check_stxcpy (gcall *stmt)
|
|
{
|
|
if (m_early_checks_p)
|
|
return;
|
|
|
|
tree dst = call_arg (stmt, 0);
|
|
tree src = call_arg (stmt, 1);
|
|
|
|
tree size;
|
|
bool exact;
|
|
if (tree nonstr = unterminated_array (src, &size, &exact))
|
|
{
|
|
/* NONSTR refers to the non-nul terminated constant array. */
|
|
warn_string_no_nul (get_location (stmt), stmt, NULL, src, nonstr,
|
|
size, exact);
|
|
return;
|
|
}
|
|
|
|
if (warn_stringop_overflow)
|
|
{
|
|
access_data data (m_ptr_qry.rvals, stmt, access_read_write, NULL_TREE,
|
|
true, NULL_TREE, true);
|
|
const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1;
|
|
compute_objsize (src, stmt, ost, &data.src, &m_ptr_qry);
|
|
tree dstsize = compute_objsize (dst, stmt, ost, &data.dst, &m_ptr_qry);
|
|
check_access (stmt, /*dstwrite=*/ NULL_TREE,
|
|
/*maxread=*/ NULL_TREE, /*srcstr=*/ src,
|
|
dstsize, data.mode, &data, m_ptr_qry.rvals);
|
|
}
|
|
|
|
/* Check to see if the argument was declared attribute nonstring
|
|
and if so, issue a warning since at this point it's not known
|
|
to be nul-terminated. */
|
|
tree fndecl = get_callee_fndecl (stmt);
|
|
maybe_warn_nonstring_arg (fndecl, stmt);
|
|
}
|
|
|
|
/* Check a call STMT to stpncpy() or strncpy() for overflow and warn
|
|
if it does. */
|
|
|
|
void
|
|
pass_waccess::check_stxncpy (gcall *stmt)
|
|
{
|
|
if (m_early_checks_p || !warn_stringop_overflow)
|
|
return;
|
|
|
|
tree dst = call_arg (stmt, 0);
|
|
tree src = call_arg (stmt, 1);
|
|
/* The number of bytes to write (not the maximum). */
|
|
tree len = call_arg (stmt, 2);
|
|
|
|
access_data data (m_ptr_qry.rvals, stmt, access_read_write, len, true, len,
|
|
true);
|
|
const int ost = warn_stringop_overflow ? warn_stringop_overflow - 1 : 1;
|
|
compute_objsize (src, stmt, ost, &data.src, &m_ptr_qry);
|
|
tree dstsize = compute_objsize (dst, stmt, ost, &data.dst, &m_ptr_qry);
|
|
|
|
check_access (stmt, /*dstwrite=*/len, /*maxread=*/len, src, dstsize,
|
|
data.mode, &data, m_ptr_qry.rvals);
|
|
}
|
|
|
|
/* Check a call STMT to stpncpy() or strncpy() for overflow and warn
|
|
if it does. */
|
|
|
|
void
|
|
pass_waccess::check_strncmp (gcall *stmt)
|
|
{
|
|
if (m_early_checks_p || !warn_stringop_overread)
|
|
return;
|
|
|
|
tree arg1 = call_arg (stmt, 0);
|
|
tree arg2 = call_arg (stmt, 1);
|
|
tree bound = call_arg (stmt, 2);
|
|
|
|
/* First check each argument separately, considering the bound. */
|
|
if (!check_nul_terminated_array (stmt, arg1, bound)
|
|
|| !check_nul_terminated_array (stmt, arg2, bound))
|
|
return;
|
|
|
|
/* A strncmp read from each argument is constrained not just by
|
|
the bound but also by the length of the shorter string. Specifying
|
|
a bound that's larger than the size of either array makes no sense
|
|
and is likely a bug. When the length of neither of the two strings
|
|
is known but the sizes of both of the arrays they are stored in is,
|
|
issue a warning if the bound is larger than than the size of
|
|
the larger of the two arrays. */
|
|
|
|
c_strlen_data lendata1{ }, lendata2{ };
|
|
tree len1 = c_strlen (arg1, 1, &lendata1);
|
|
tree len2 = c_strlen (arg2, 1, &lendata2);
|
|
|
|
if (len1 && TREE_CODE (len1) != INTEGER_CST)
|
|
len1 = NULL_TREE;
|
|
if (len2 && TREE_CODE (len2) != INTEGER_CST)
|
|
len2 = NULL_TREE;
|
|
|
|
if (len1 && len2)
|
|
/* If the length of both arguments was computed they must both be
|
|
nul-terminated and no further checking is necessary regardless
|
|
of the bound. */
|
|
return;
|
|
|
|
/* Check to see if the argument was declared with attribute nonstring
|
|
and if so, issue a warning since at this point it's not known to be
|
|
nul-terminated. */
|
|
if (maybe_warn_nonstring_arg (get_callee_fndecl (stmt), stmt))
|
|
return;
|
|
|
|
access_data adata1 (m_ptr_qry.rvals, stmt, access_read_only, NULL_TREE, false,
|
|
bound, true);
|
|
access_data adata2 (m_ptr_qry.rvals, stmt, access_read_only, NULL_TREE, false,
|
|
bound, true);
|
|
|
|
/* Determine the range of the bound first and bail if it fails; it's
|
|
cheaper than computing the size of the objects. */
|
|
tree bndrng[2] = { NULL_TREE, NULL_TREE };
|
|
get_size_range (m_ptr_qry.rvals, bound, stmt, bndrng, adata1.src_bndrng);
|
|
if (!bndrng[0] || integer_zerop (bndrng[0]))
|
|
return;
|
|
|
|
if (len1 && tree_int_cst_lt (len1, bndrng[0]))
|
|
bndrng[0] = len1;
|
|
if (len2 && tree_int_cst_lt (len2, bndrng[0]))
|
|
bndrng[0] = len2;
|
|
|
|
/* compute_objsize almost never fails (and ultimately should never
|
|
fail). Don't bother to handle the rare case when it does. */
|
|
if (!compute_objsize (arg1, stmt, 1, &adata1.src, &m_ptr_qry)
|
|
|| !compute_objsize (arg2, stmt, 1, &adata2.src, &m_ptr_qry))
|
|
return;
|
|
|
|
/* Compute the size of the remaining space in each array after
|
|
subtracting any offset into it. */
|
|
offset_int rem1 = adata1.src.size_remaining ();
|
|
offset_int rem2 = adata2.src.size_remaining ();
|
|
|
|
/* Cap REM1 and REM2 at the other if the other's argument is known
|
|
to be an unterminated array, either because there's no space
|
|
left in it after adding its offset or because it's constant and
|
|
has no nul. */
|
|
if (rem1 == 0 || (rem1 < rem2 && lendata1.decl))
|
|
rem2 = rem1;
|
|
else if (rem2 == 0 || (rem2 < rem1 && lendata2.decl))
|
|
rem1 = rem2;
|
|
|
|
/* Point PAD at the array to reference in the note if a warning
|
|
is issued. */
|
|
access_data *pad = len1 ? &adata2 : &adata1;
|
|
offset_int maxrem = wi::max (rem1, rem2, UNSIGNED);
|
|
if (lendata1.decl || lendata2.decl
|
|
|| maxrem < wi::to_offset (bndrng[0]))
|
|
{
|
|
/* Warn when either argument isn't nul-terminated or the maximum
|
|
remaining space in the two arrays is less than the bound. */
|
|
tree func = get_callee_fndecl (stmt);
|
|
location_t loc = gimple_location (stmt);
|
|
maybe_warn_for_bound (OPT_Wstringop_overread, loc, stmt, func,
|
|
bndrng, wide_int_to_tree (sizetype, maxrem),
|
|
pad);
|
|
}
|
|
}
|
|
|
|
/* Determine and check the sizes of the source and the destination
|
|
of calls to __builtin_{bzero,memcpy,mempcpy,memset} calls. STMT is
|
|
the call statement, DEST is the destination argument, SRC is the source
|
|
argument or null, and SIZE is the number of bytes being accessed. Use
|
|
Object Size type-0 regardless of the OPT_Wstringop_overflow_ setting.
|
|
Return true on success (no overflow or invalid sizes), false otherwise. */
|
|
|
|
void
|
|
pass_waccess::check_memop_access (gimple *stmt, tree dest, tree src, tree size)
|
|
{
|
|
if (m_early_checks_p)
|
|
return;
|
|
|
|
/* For functions like memset and memcpy that operate on raw memory
|
|
try to determine the size of the largest source and destination
|
|
object using type-0 Object Size regardless of the object size
|
|
type specified by the option. */
|
|
access_data data (m_ptr_qry.rvals, stmt, access_read_write);
|
|
tree srcsize
|
|
= src ? compute_objsize (src, stmt, 0, &data.src, &m_ptr_qry) : NULL_TREE;
|
|
tree dstsize = compute_objsize (dest, stmt, 0, &data.dst, &m_ptr_qry);
|
|
|
|
check_access (stmt, size, /*maxread=*/NULL_TREE, srcsize, dstsize,
|
|
data.mode, &data, m_ptr_qry.rvals);
|
|
}
|
|
|
|
/* A convenience wrapper for check_access to check access by a read-only
|
|
function like puts or strcmp. */
|
|
|
|
void
|
|
pass_waccess::check_read_access (gimple *stmt, tree src,
|
|
tree bound /* = NULL_TREE */,
|
|
int ost /* = 1 */)
|
|
{
|
|
if (m_early_checks_p || !warn_stringop_overread)
|
|
return;
|
|
|
|
if (bound && !useless_type_conversion_p (size_type_node, TREE_TYPE (bound)))
|
|
bound = fold_convert (size_type_node, bound);
|
|
|
|
tree fndecl = get_callee_fndecl (stmt);
|
|
maybe_warn_nonstring_arg (fndecl, stmt);
|
|
|
|
access_data data (m_ptr_qry.rvals, stmt, access_read_only, NULL_TREE,
|
|
false, bound, true);
|
|
compute_objsize (src, stmt, ost, &data.src, &m_ptr_qry);
|
|
check_access (stmt, /*dstwrite=*/ NULL_TREE, /*maxread=*/ bound,
|
|
/*srcstr=*/ src, /*dstsize=*/ NULL_TREE, data.mode,
|
|
&data, m_ptr_qry.rvals);
|
|
}
|
|
|
|
/* Return true if memory model ORD is constant in the context of STMT and
|
|
set *CSTVAL to the constant value. Otherwise return false. Warn for
|
|
invalid ORD. */
|
|
|
|
bool
|
|
memmodel_to_uhwi (tree ord, gimple *stmt, unsigned HOST_WIDE_INT *cstval)
|
|
{
|
|
unsigned HOST_WIDE_INT val;
|
|
|
|
if (TREE_CODE (ord) == INTEGER_CST)
|
|
{
|
|
if (!tree_fits_uhwi_p (ord))
|
|
return false;
|
|
val = tree_to_uhwi (ord);
|
|
}
|
|
else
|
|
{
|
|
/* Use the range query to determine constant values in the absence
|
|
of constant proppagation (such as at -O0). */
|
|
value_range rng;
|
|
if (!get_range_query (cfun)->range_of_expr (rng, ord, stmt)
|
|
|| !rng.constant_p ()
|
|
|| !rng.singleton_p (&ord))
|
|
return false;
|
|
|
|
wide_int lob = rng.lower_bound ();
|
|
if (!wi::fits_uhwi_p (lob))
|
|
return false;
|
|
|
|
val = lob.to_shwi ();
|
|
}
|
|
|
|
if (targetm.memmodel_check)
|
|
/* This might warn for an invalid VAL but return a conservatively
|
|
valid result. */
|
|
val = targetm.memmodel_check (val);
|
|
else if (val & ~MEMMODEL_MASK)
|
|
{
|
|
tree fndecl = gimple_call_fndecl (stmt);
|
|
location_t loc = gimple_location (stmt);
|
|
loc = expansion_point_location_if_in_system_header (loc);
|
|
|
|
warning_at (loc, OPT_Winvalid_memory_model,
|
|
"unknown architecture specifier in memory model "
|
|
"%wi for %qD", val, fndecl);
|
|
return false;
|
|
}
|
|
|
|
*cstval = val;
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Valid memory model for each set of atomic built-in functions. */
|
|
|
|
struct memmodel_pair
|
|
{
|
|
memmodel modval;
|
|
const char* modname;
|
|
|
|
#define MEMMODEL_PAIR(val, str) \
|
|
{ MEMMODEL_ ## val, "memory_order_" str }
|
|
};
|
|
|
|
/* Valid memory models in the order of increasing strength. */
|
|
|
|
static const memmodel_pair memory_models[] =
|
|
{ MEMMODEL_PAIR (RELAXED, "relaxed"),
|
|
MEMMODEL_PAIR (SEQ_CST, "seq_cst"),
|
|
MEMMODEL_PAIR (ACQUIRE, "acquire"),
|
|
MEMMODEL_PAIR (CONSUME, "consume"),
|
|
MEMMODEL_PAIR (RELEASE, "release"),
|
|
MEMMODEL_PAIR (ACQ_REL, "acq_rel")
|
|
};
|
|
|
|
/* Return the name of the memory model VAL. */
|
|
|
|
static const char*
|
|
memmodel_name (unsigned HOST_WIDE_INT val)
|
|
{
|
|
val = memmodel_base (val);
|
|
|
|
for (unsigned i = 0; i != sizeof memory_models / sizeof *memory_models; ++i)
|
|
{
|
|
if (val == memory_models[i].modval)
|
|
return memory_models[i].modname;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Indices of valid MEMORY_MODELS above for corresponding atomic operations. */
|
|
static const unsigned char load_models[] = { 0, 1, 2, 3, UCHAR_MAX };
|
|
static const unsigned char store_models[] = { 0, 1, 4, UCHAR_MAX };
|
|
static const unsigned char xchg_models[] = { 0, 1, 3, 4, 5, UCHAR_MAX };
|
|
static const unsigned char flag_clr_models[] = { 0, 1, 4, UCHAR_MAX };
|
|
static const unsigned char all_models[] = { 0, 1, 2, 3, 4, 5, UCHAR_MAX };
|
|
|
|
/* Check the success memory model argument ORD_SUCS to the call STMT to
|
|
an atomic function and warn if it's invalid. If nonnull, also check
|
|
the failure memory model ORD_FAIL and warn if it's invalid. Return
|
|
true if a warning has been issued. */
|
|
|
|
bool
|
|
pass_waccess::maybe_warn_memmodel (gimple *stmt, tree ord_sucs,
|
|
tree ord_fail, const unsigned char *valid)
|
|
{
|
|
unsigned HOST_WIDE_INT sucs, fail = 0;
|
|
if (!memmodel_to_uhwi (ord_sucs, stmt, &sucs)
|
|
|| (ord_fail && !memmodel_to_uhwi (ord_fail, stmt, &fail)))
|
|
return false;
|
|
|
|
bool is_valid = false;
|
|
if (valid)
|
|
for (unsigned i = 0; valid[i] != UCHAR_MAX; ++i)
|
|
{
|
|
memmodel model = memory_models[valid[i]].modval;
|
|
if (memmodel_base (sucs) == model)
|
|
{
|
|
is_valid = true;
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
is_valid = true;
|
|
|
|
tree fndecl = gimple_call_fndecl (stmt);
|
|
location_t loc = gimple_location (stmt);
|
|
loc = expansion_point_location_if_in_system_header (loc);
|
|
|
|
if (!is_valid)
|
|
{
|
|
bool warned = false;
|
|
if (const char *modname = memmodel_name (sucs))
|
|
warned = warning_at (loc, OPT_Winvalid_memory_model,
|
|
"invalid memory model %qs for %qD",
|
|
modname, fndecl);
|
|
else
|
|
warned = warning_at (loc, OPT_Winvalid_memory_model,
|
|
"invalid memory model %wi for %qD",
|
|
sucs, fndecl);
|
|
|
|
if (!warned)
|
|
return false;
|
|
|
|
/* Print a note with the valid memory models. */
|
|
pretty_printer pp;
|
|
pp_show_color (&pp) = pp_show_color (global_dc->printer);
|
|
for (unsigned i = 0; valid[i] != UCHAR_MAX; ++i)
|
|
{
|
|
const char *modname = memory_models[valid[i]].modname;
|
|
pp_printf (&pp, "%s%qs", i ? ", " : "", modname);
|
|
}
|
|
|
|
inform (loc, "valid models are %s", pp_formatted_text (&pp));
|
|
return true;
|
|
}
|
|
|
|
if (!ord_fail)
|
|
return false;
|
|
|
|
if (fail == MEMMODEL_RELEASE || fail == MEMMODEL_ACQ_REL)
|
|
if (const char *failname = memmodel_name (fail))
|
|
{
|
|
/* If both memory model arguments are valid but their combination
|
|
is not, use their names in the warning. */
|
|
if (!warning_at (loc, OPT_Winvalid_memory_model,
|
|
"invalid failure memory model %qs for %qD",
|
|
failname, fndecl))
|
|
return false;
|
|
|
|
inform (loc,
|
|
"valid failure models are %qs, %qs, %qs, %qs",
|
|
"memory_order_relaxed", "memory_order_seq_cst",
|
|
"memory_order_acquire", "memory_order_consume");
|
|
return true;
|
|
}
|
|
|
|
if (memmodel_base (fail) <= memmodel_base (sucs))
|
|
return false;
|
|
|
|
if (const char *sucsname = memmodel_name (sucs))
|
|
if (const char *failname = memmodel_name (fail))
|
|
{
|
|
/* If both memory model arguments are valid but their combination
|
|
is not, use their names in the warning. */
|
|
if (!warning_at (loc, OPT_Winvalid_memory_model,
|
|
"failure memory model %qs cannot be stronger "
|
|
"than success memory model %qs for %qD",
|
|
failname, sucsname, fndecl))
|
|
return false;
|
|
|
|
/* Print a note with the valid failure memory models which are
|
|
those with a value less than or equal to the success mode. */
|
|
char buf[120];
|
|
*buf = '\0';
|
|
for (unsigned i = 0;
|
|
memory_models[i].modval <= memmodel_base (sucs); ++i)
|
|
{
|
|
if (*buf)
|
|
strcat (buf, ", ");
|
|
|
|
const char *modname = memory_models[valid[i]].modname;
|
|
sprintf (buf + strlen (buf), "'%s'", modname);
|
|
}
|
|
|
|
inform (loc, "valid models are %s", buf);
|
|
return true;
|
|
}
|
|
|
|
/* If either memory model argument value is invalid use the numerical
|
|
value of both in the message. */
|
|
return warning_at (loc, OPT_Winvalid_memory_model,
|
|
"failure memory model %wi cannot be stronger "
|
|
"than success memory model %wi for %qD",
|
|
fail, sucs, fndecl);
|
|
}
|
|
|
|
/* Wrapper for the above. */
|
|
|
|
void
|
|
pass_waccess::check_atomic_memmodel (gimple *stmt, tree ord_sucs,
|
|
tree ord_fail, const unsigned char *valid)
|
|
{
|
|
if (warning_suppressed_p (stmt, OPT_Winvalid_memory_model))
|
|
return;
|
|
|
|
if (!maybe_warn_memmodel (stmt, ord_sucs, ord_fail, valid))
|
|
return;
|
|
|
|
suppress_warning (stmt, OPT_Winvalid_memory_model);
|
|
}
|
|
|
|
/* Check a call STMT to an atomic or sync built-in. */
|
|
|
|
bool
|
|
pass_waccess::check_atomic_builtin (gcall *stmt)
|
|
{
|
|
tree callee = gimple_call_fndecl (stmt);
|
|
if (!callee)
|
|
return false;
|
|
|
|
/* The size in bytes of the access by the function, and the number
|
|
of the second argument to check (if any). */
|
|
unsigned bytes = 0, arg2 = UINT_MAX;
|
|
unsigned sucs_arg = UINT_MAX, fail_arg = UINT_MAX;
|
|
/* Points to the array of indices of valid memory models. */
|
|
const unsigned char *pvalid_models = NULL;
|
|
|
|
switch (DECL_FUNCTION_CODE (callee))
|
|
{
|
|
#define BUILTIN_ACCESS_SIZE_FNSPEC(N) \
|
|
BUILT_IN_SYNC_FETCH_AND_ADD_ ## N: \
|
|
case BUILT_IN_SYNC_FETCH_AND_SUB_ ## N: \
|
|
case BUILT_IN_SYNC_FETCH_AND_OR_ ## N: \
|
|
case BUILT_IN_SYNC_FETCH_AND_AND_ ## N: \
|
|
case BUILT_IN_SYNC_FETCH_AND_XOR_ ## N: \
|
|
case BUILT_IN_SYNC_FETCH_AND_NAND_ ## N: \
|
|
case BUILT_IN_SYNC_ADD_AND_FETCH_ ## N: \
|
|
case BUILT_IN_SYNC_SUB_AND_FETCH_ ## N: \
|
|
case BUILT_IN_SYNC_OR_AND_FETCH_ ## N: \
|
|
case BUILT_IN_SYNC_AND_AND_FETCH_ ## N: \
|
|
case BUILT_IN_SYNC_XOR_AND_FETCH_ ## N: \
|
|
case BUILT_IN_SYNC_NAND_AND_FETCH_ ## N: \
|
|
case BUILT_IN_SYNC_LOCK_TEST_AND_SET_ ## N: \
|
|
case BUILT_IN_SYNC_BOOL_COMPARE_AND_SWAP_ ## N: \
|
|
case BUILT_IN_SYNC_VAL_COMPARE_AND_SWAP_ ## N: \
|
|
case BUILT_IN_SYNC_LOCK_RELEASE_ ## N: \
|
|
bytes = N; \
|
|
break; \
|
|
case BUILT_IN_ATOMIC_LOAD_ ## N: \
|
|
pvalid_models = load_models; \
|
|
sucs_arg = 1; \
|
|
/* FALLTHROUGH */ \
|
|
case BUILT_IN_ATOMIC_STORE_ ## N: \
|
|
if (!pvalid_models) \
|
|
pvalid_models = store_models; \
|
|
/* FALLTHROUGH */ \
|
|
case BUILT_IN_ATOMIC_ADD_FETCH_ ## N: \
|
|
case BUILT_IN_ATOMIC_SUB_FETCH_ ## N: \
|
|
case BUILT_IN_ATOMIC_AND_FETCH_ ## N: \
|
|
case BUILT_IN_ATOMIC_NAND_FETCH_ ## N: \
|
|
case BUILT_IN_ATOMIC_XOR_FETCH_ ## N: \
|
|
case BUILT_IN_ATOMIC_OR_FETCH_ ## N: \
|
|
case BUILT_IN_ATOMIC_FETCH_ADD_ ## N: \
|
|
case BUILT_IN_ATOMIC_FETCH_SUB_ ## N: \
|
|
case BUILT_IN_ATOMIC_FETCH_AND_ ## N: \
|
|
case BUILT_IN_ATOMIC_FETCH_NAND_ ## N: \
|
|
case BUILT_IN_ATOMIC_FETCH_OR_ ## N: \
|
|
case BUILT_IN_ATOMIC_FETCH_XOR_ ## N: \
|
|
bytes = N; \
|
|
if (sucs_arg == UINT_MAX) \
|
|
sucs_arg = 2; \
|
|
if (!pvalid_models) \
|
|
pvalid_models = all_models; \
|
|
break; \
|
|
case BUILT_IN_ATOMIC_EXCHANGE_ ## N: \
|
|
bytes = N; \
|
|
sucs_arg = 3; \
|
|
pvalid_models = xchg_models; \
|
|
break; \
|
|
case BUILT_IN_ATOMIC_COMPARE_EXCHANGE_ ## N: \
|
|
bytes = N; \
|
|
sucs_arg = 4; \
|
|
fail_arg = 5; \
|
|
pvalid_models = all_models; \
|
|
arg2 = 1
|
|
|
|
case BUILTIN_ACCESS_SIZE_FNSPEC (1);
|
|
break;
|
|
case BUILTIN_ACCESS_SIZE_FNSPEC (2);
|
|
break;
|
|
case BUILTIN_ACCESS_SIZE_FNSPEC (4);
|
|
break;
|
|
case BUILTIN_ACCESS_SIZE_FNSPEC (8);
|
|
break;
|
|
case BUILTIN_ACCESS_SIZE_FNSPEC (16);
|
|
break;
|
|
|
|
case BUILT_IN_ATOMIC_CLEAR:
|
|
sucs_arg = 1;
|
|
pvalid_models = flag_clr_models;
|
|
break;
|
|
|
|
default:
|
|
return false;
|
|
}
|
|
|
|
unsigned nargs = gimple_call_num_args (stmt);
|
|
if (sucs_arg < nargs)
|
|
{
|
|
tree ord_sucs = gimple_call_arg (stmt, sucs_arg);
|
|
tree ord_fail = NULL_TREE;
|
|
if (fail_arg < nargs)
|
|
ord_fail = gimple_call_arg (stmt, fail_arg);
|
|
check_atomic_memmodel (stmt, ord_sucs, ord_fail, pvalid_models);
|
|
}
|
|
|
|
if (!bytes)
|
|
return true;
|
|
|
|
tree size = build_int_cstu (sizetype, bytes);
|
|
tree dst = gimple_call_arg (stmt, 0);
|
|
check_memop_access (stmt, dst, NULL_TREE, size);
|
|
|
|
if (arg2 != UINT_MAX)
|
|
{
|
|
tree dst = gimple_call_arg (stmt, arg2);
|
|
check_memop_access (stmt, dst, NULL_TREE, size);
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/* Check call STMT to a built-in function for invalid accesses. Return
|
|
true if a call has been handled. */
|
|
|
|
bool
|
|
pass_waccess::check_builtin (gcall *stmt)
|
|
{
|
|
tree callee = gimple_call_fndecl (stmt);
|
|
if (!callee)
|
|
return false;
|
|
|
|
switch (DECL_FUNCTION_CODE (callee))
|
|
{
|
|
case BUILT_IN_ALLOCA:
|
|
case BUILT_IN_ALLOCA_WITH_ALIGN:
|
|
case BUILT_IN_ALLOCA_WITH_ALIGN_AND_MAX:
|
|
check_alloca (stmt);
|
|
return true;
|
|
|
|
case BUILT_IN_EXECL:
|
|
case BUILT_IN_EXECLE:
|
|
case BUILT_IN_EXECLP:
|
|
case BUILT_IN_EXECV:
|
|
case BUILT_IN_EXECVE:
|
|
case BUILT_IN_EXECVP:
|
|
check_read_access (stmt, call_arg (stmt, 0));
|
|
return true;
|
|
|
|
case BUILT_IN_FREE:
|
|
case BUILT_IN_REALLOC:
|
|
if (!m_early_checks_p)
|
|
{
|
|
tree arg = call_arg (stmt, 0);
|
|
if (TREE_CODE (arg) == SSA_NAME)
|
|
check_pointer_uses (stmt, arg);
|
|
}
|
|
return true;
|
|
|
|
case BUILT_IN_GETTEXT:
|
|
case BUILT_IN_PUTS:
|
|
case BUILT_IN_PUTS_UNLOCKED:
|
|
case BUILT_IN_STRDUP:
|
|
check_read_access (stmt, call_arg (stmt, 0));
|
|
return true;
|
|
|
|
case BUILT_IN_INDEX:
|
|
case BUILT_IN_RINDEX:
|
|
case BUILT_IN_STRCHR:
|
|
case BUILT_IN_STRRCHR:
|
|
case BUILT_IN_STRLEN:
|
|
check_read_access (stmt, call_arg (stmt, 0));
|
|
return true;
|
|
|
|
case BUILT_IN_FPUTS:
|
|
case BUILT_IN_FPUTS_UNLOCKED:
|
|
check_read_access (stmt, call_arg (stmt, 0));
|
|
return true;
|
|
|
|
case BUILT_IN_STRNDUP:
|
|
case BUILT_IN_STRNLEN:
|
|
{
|
|
tree str = call_arg (stmt, 0);
|
|
tree len = call_arg (stmt, 1);
|
|
check_read_access (stmt, str, len);
|
|
return true;
|
|
}
|
|
|
|
case BUILT_IN_STRCAT:
|
|
check_strcat (stmt);
|
|
return true;
|
|
|
|
case BUILT_IN_STRNCAT:
|
|
check_strncat (stmt);
|
|
return true;
|
|
|
|
case BUILT_IN_STPCPY:
|
|
case BUILT_IN_STRCPY:
|
|
check_stxcpy (stmt);
|
|
return true;
|
|
|
|
case BUILT_IN_STPNCPY:
|
|
case BUILT_IN_STRNCPY:
|
|
check_stxncpy (stmt);
|
|
return true;
|
|
|
|
case BUILT_IN_STRCASECMP:
|
|
case BUILT_IN_STRCMP:
|
|
case BUILT_IN_STRPBRK:
|
|
case BUILT_IN_STRSPN:
|
|
case BUILT_IN_STRCSPN:
|
|
case BUILT_IN_STRSTR:
|
|
check_read_access (stmt, call_arg (stmt, 0));
|
|
check_read_access (stmt, call_arg (stmt, 1));
|
|
return true;
|
|
|
|
case BUILT_IN_STRNCASECMP:
|
|
case BUILT_IN_STRNCMP:
|
|
check_strncmp (stmt);
|
|
return true;
|
|
|
|
case BUILT_IN_MEMCMP:
|
|
{
|
|
tree a1 = call_arg (stmt, 0);
|
|
tree a2 = call_arg (stmt, 1);
|
|
tree len = call_arg (stmt, 2);
|
|
check_read_access (stmt, a1, len, 0);
|
|
check_read_access (stmt, a2, len, 0);
|
|
return true;
|
|
}
|
|
|
|
case BUILT_IN_MEMCPY:
|
|
case BUILT_IN_MEMPCPY:
|
|
case BUILT_IN_MEMMOVE:
|
|
{
|
|
tree dst = call_arg (stmt, 0);
|
|
tree src = call_arg (stmt, 1);
|
|
tree len = call_arg (stmt, 2);
|
|
check_memop_access (stmt, dst, src, len);
|
|
return true;
|
|
}
|
|
|
|
case BUILT_IN_MEMCHR:
|
|
{
|
|
tree src = call_arg (stmt, 0);
|
|
tree len = call_arg (stmt, 2);
|
|
check_read_access (stmt, src, len, 0);
|
|
return true;
|
|
}
|
|
|
|
case BUILT_IN_MEMSET:
|
|
{
|
|
tree dst = call_arg (stmt, 0);
|
|
tree len = call_arg (stmt, 2);
|
|
check_memop_access (stmt, dst, NULL_TREE, len);
|
|
return true;
|
|
}
|
|
|
|
default:
|
|
if (check_atomic_builtin (stmt))
|
|
return true;
|
|
break;
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/* Returns the type of the argument ARGNO to function with type FNTYPE
|
|
or null when the typoe cannot be determined or no such argument exists. */
|
|
|
|
static tree
|
|
fntype_argno_type (tree fntype, unsigned argno)
|
|
{
|
|
if (!prototype_p (fntype))
|
|
return NULL_TREE;
|
|
|
|
tree argtype;
|
|
function_args_iterator it;
|
|
FOREACH_FUNCTION_ARGS (fntype, argtype, it)
|
|
if (argno-- == 0)
|
|
return argtype;
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
/* Helper to append the "human readable" attribute access specification
|
|
described by ACCESS to the array ATTRSTR with size STRSIZE. Used in
|
|
diagnostics. */
|
|
|
|
static inline void
|
|
append_attrname (const std::pair<int, attr_access> &access,
|
|
char *attrstr, size_t strsize)
|
|
{
|
|
if (access.second.internal_p)
|
|
return;
|
|
|
|
tree str = access.second.to_external_string ();
|
|
gcc_assert (strsize >= (size_t) TREE_STRING_LENGTH (str));
|
|
strcpy (attrstr, TREE_STRING_POINTER (str));
|
|
}
|
|
|
|
/* Iterate over attribute access read-only, read-write, and write-only
|
|
arguments and diagnose past-the-end accesses and related problems
|
|
in the function call EXP. */
|
|
|
|
void
|
|
pass_waccess::maybe_check_access_sizes (rdwr_map *rwm, tree fndecl, tree fntype,
|
|
gimple *stmt)
|
|
{
|
|
auto_diagnostic_group adg;
|
|
|
|
/* Set if a warning has been issued for any argument (used to decide
|
|
whether to emit an informational note at the end). */
|
|
opt_code opt_warned = no_warning;
|
|
|
|
/* A string describing the attributes that the warnings issued by this
|
|
function apply to. Used to print one informational note per function
|
|
call, rather than one per warning. That reduces clutter. */
|
|
char attrstr[80];
|
|
attrstr[0] = 0;
|
|
|
|
for (rdwr_map::iterator it = rwm->begin (); it != rwm->end (); ++it)
|
|
{
|
|
std::pair<int, attr_access> access = *it;
|
|
|
|
/* Get the function call arguments corresponding to the attribute's
|
|
positional arguments. When both arguments have been specified
|
|
there will be two entries in *RWM, one for each. They are
|
|
cross-referenced by their respective argument numbers in
|
|
ACCESS.PTRARG and ACCESS.SIZARG. */
|
|
const int ptridx = access.second.ptrarg;
|
|
const int sizidx = access.second.sizarg;
|
|
|
|
gcc_assert (ptridx != -1);
|
|
gcc_assert (access.first == ptridx || access.first == sizidx);
|
|
|
|
/* The pointer is set to null for the entry corresponding to
|
|
the size argument. Skip it. It's handled when the entry
|
|
corresponding to the pointer argument comes up. */
|
|
if (!access.second.ptr)
|
|
continue;
|
|
|
|
tree ptrtype = fntype_argno_type (fntype, ptridx);
|
|
if (!ptrtype)
|
|
/* A function with a prototype was redeclared without one and
|
|
the protype has been lost. See pr102759. Avoid dealing
|
|
with this pathological case. */
|
|
return;
|
|
|
|
tree argtype = TREE_TYPE (ptrtype);
|
|
|
|
/* The size of the access by the call in elements. */
|
|
tree access_nelts;
|
|
if (sizidx == -1)
|
|
{
|
|
/* If only the pointer attribute operand was specified and
|
|
not size, set SIZE to the greater of MINSIZE or size of
|
|
one element of the pointed to type to detect smaller
|
|
objects (null pointers are diagnosed in this case only
|
|
if the pointer is also declared with attribute nonnull. */
|
|
if (access.second.minsize
|
|
&& access.second.minsize != HOST_WIDE_INT_M1U)
|
|
access_nelts = build_int_cstu (sizetype, access.second.minsize);
|
|
else if (VOID_TYPE_P (argtype) && access.second.mode == access_none)
|
|
/* Treat access mode none on a void* argument as expecting
|
|
as little as zero bytes. */
|
|
access_nelts = size_zero_node;
|
|
else
|
|
access_nelts = size_one_node;
|
|
}
|
|
else
|
|
access_nelts = rwm->get (sizidx)->size;
|
|
|
|
/* Format the value or range to avoid an explosion of messages. */
|
|
char sizstr[80];
|
|
tree sizrng[2] = { size_zero_node, build_all_ones_cst (sizetype) };
|
|
if (get_size_range (m_ptr_qry.rvals, access_nelts, stmt, sizrng, 1))
|
|
{
|
|
char *s0 = print_generic_expr_to_str (sizrng[0]);
|
|
if (tree_int_cst_equal (sizrng[0], sizrng[1]))
|
|
{
|
|
gcc_checking_assert (strlen (s0) < sizeof sizstr);
|
|
strcpy (sizstr, s0);
|
|
}
|
|
else
|
|
{
|
|
char *s1 = print_generic_expr_to_str (sizrng[1]);
|
|
gcc_checking_assert (strlen (s0) + strlen (s1)
|
|
< sizeof sizstr - 4);
|
|
sprintf (sizstr, "[%.37s, %.37s]", s0, s1);
|
|
free (s1);
|
|
}
|
|
free (s0);
|
|
}
|
|
else
|
|
*sizstr = '\0';
|
|
|
|
/* Set if a warning has been issued for the current argument. */
|
|
opt_code arg_warned = no_warning;
|
|
location_t loc = get_location (stmt);
|
|
tree ptr = access.second.ptr;
|
|
if (*sizstr
|
|
&& tree_int_cst_sgn (sizrng[0]) < 0
|
|
&& tree_int_cst_sgn (sizrng[1]) < 0)
|
|
{
|
|
/* Warn about negative sizes. */
|
|
if (access.second.internal_p)
|
|
{
|
|
const std::string argtypestr
|
|
= access.second.array_as_string (ptrtype);
|
|
|
|
if (warning_at (loc, OPT_Wstringop_overflow_,
|
|
"bound argument %i value %s is "
|
|
"negative for a variable length array "
|
|
"argument %i of type %s",
|
|
sizidx + 1, sizstr,
|
|
ptridx + 1, argtypestr.c_str ()))
|
|
arg_warned = OPT_Wstringop_overflow_;
|
|
}
|
|
else if (warning_at (loc, OPT_Wstringop_overflow_,
|
|
"argument %i value %s is negative",
|
|
sizidx + 1, sizstr))
|
|
arg_warned = OPT_Wstringop_overflow_;
|
|
|
|
if (arg_warned != no_warning)
|
|
{
|
|
append_attrname (access, attrstr, sizeof attrstr);
|
|
/* Remember a warning has been issued and avoid warning
|
|
again below for the same attribute. */
|
|
opt_warned = arg_warned;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
/* The size of the access by the call in bytes. */
|
|
tree access_size = NULL_TREE;
|
|
if (tree_int_cst_sgn (sizrng[0]) >= 0)
|
|
{
|
|
if (COMPLETE_TYPE_P (argtype))
|
|
{
|
|
/* Multiply ACCESS_SIZE by the size of the type the pointer
|
|
argument points to. If it's incomplete the size is used
|
|
as is. */
|
|
if (tree argsize = TYPE_SIZE_UNIT (argtype))
|
|
if (TREE_CODE (argsize) == INTEGER_CST)
|
|
{
|
|
const int prec = TYPE_PRECISION (sizetype);
|
|
wide_int minsize = wi::to_wide (sizrng[0], prec);
|
|
minsize *= wi::to_wide (argsize, prec);
|
|
access_size = wide_int_to_tree (sizetype, minsize);
|
|
}
|
|
}
|
|
else
|
|
access_size = access_nelts;
|
|
}
|
|
|
|
if (integer_zerop (ptr))
|
|
{
|
|
if (sizidx >= 0 && tree_int_cst_sgn (sizrng[0]) > 0)
|
|
{
|
|
/* Warn about null pointers with positive sizes. This is
|
|
different from also declaring the pointer argument with
|
|
attribute nonnull when the function accepts null pointers
|
|
only when the corresponding size is zero. */
|
|
if (access.second.internal_p)
|
|
{
|
|
const std::string argtypestr
|
|
= access.second.array_as_string (ptrtype);
|
|
|
|
if (warning_at (loc, OPT_Wnonnull,
|
|
"argument %i of variable length "
|
|
"array %s is null but "
|
|
"the corresponding bound argument "
|
|
"%i value is %s",
|
|
ptridx + 1, argtypestr.c_str (),
|
|
sizidx + 1, sizstr))
|
|
arg_warned = OPT_Wnonnull;
|
|
}
|
|
else if (warning_at (loc, OPT_Wnonnull,
|
|
"argument %i is null but "
|
|
"the corresponding size argument "
|
|
"%i value is %s",
|
|
ptridx + 1, sizidx + 1, sizstr))
|
|
arg_warned = OPT_Wnonnull;
|
|
}
|
|
else if (access_size && access.second.static_p)
|
|
{
|
|
/* Warn about null pointers for [static N] array arguments
|
|
but do not warn for ordinary (i.e., nonstatic) arrays. */
|
|
if (warning_at (loc, OPT_Wnonnull,
|
|
"argument %i to %<%T[static %E]%> "
|
|
"is null where non-null expected",
|
|
ptridx + 1, argtype, access_size))
|
|
arg_warned = OPT_Wnonnull;
|
|
}
|
|
|
|
if (arg_warned != no_warning)
|
|
{
|
|
append_attrname (access, attrstr, sizeof attrstr);
|
|
/* Remember a warning has been issued and avoid warning
|
|
again below for the same attribute. */
|
|
opt_warned = OPT_Wnonnull;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
access_data data (m_ptr_qry.rvals, stmt, access.second.mode,
|
|
NULL_TREE, false, NULL_TREE, false);
|
|
access_ref* const pobj = (access.second.mode == access_write_only
|
|
? &data.dst : &data.src);
|
|
tree objsize = compute_objsize (ptr, stmt, 1, pobj, &m_ptr_qry);
|
|
|
|
/* The size of the destination or source object. */
|
|
tree dstsize = NULL_TREE, srcsize = NULL_TREE;
|
|
if (access.second.mode == access_read_only
|
|
|| access.second.mode == access_none)
|
|
{
|
|
/* For a read-only argument there is no destination. For
|
|
no access, set the source as well and differentiate via
|
|
the access flag below. */
|
|
srcsize = objsize;
|
|
if (access.second.mode == access_read_only
|
|
|| access.second.mode == access_none)
|
|
{
|
|
/* For a read-only attribute there is no destination so
|
|
clear OBJSIZE. This emits "reading N bytes" kind of
|
|
diagnostics instead of the "writing N bytes" kind,
|
|
unless MODE is none. */
|
|
objsize = NULL_TREE;
|
|
}
|
|
}
|
|
else
|
|
dstsize = objsize;
|
|
|
|
/* Clear the no-warning bit in case it was set by check_access
|
|
in a prior iteration so that accesses via different arguments
|
|
are diagnosed. */
|
|
suppress_warning (stmt, OPT_Wstringop_overflow_, false);
|
|
access_mode mode = data.mode;
|
|
if (mode == access_deferred)
|
|
mode = TYPE_READONLY (argtype) ? access_read_only : access_read_write;
|
|
check_access (stmt, access_size, /*maxread=*/ NULL_TREE, srcsize,
|
|
dstsize, mode, &data, m_ptr_qry.rvals);
|
|
|
|
if (warning_suppressed_p (stmt, OPT_Wstringop_overflow_))
|
|
opt_warned = OPT_Wstringop_overflow_;
|
|
if (opt_warned != no_warning)
|
|
{
|
|
if (access.second.internal_p)
|
|
{
|
|
unsigned HOST_WIDE_INT nelts =
|
|
access_nelts ? access.second.minsize : HOST_WIDE_INT_M1U;
|
|
tree arrtype = build_printable_array_type (argtype, nelts);
|
|
inform (loc, "referencing argument %u of type %qT",
|
|
ptridx + 1, arrtype);
|
|
}
|
|
else
|
|
/* If check_access issued a warning above, append the relevant
|
|
attribute to the string. */
|
|
append_attrname (access, attrstr, sizeof attrstr);
|
|
}
|
|
}
|
|
|
|
if (*attrstr)
|
|
{
|
|
if (fndecl)
|
|
inform (get_location (fndecl),
|
|
"in a call to function %qD declared with attribute %qs",
|
|
fndecl, attrstr);
|
|
else
|
|
inform (get_location (stmt),
|
|
"in a call with type %qT and attribute %qs",
|
|
fntype, attrstr);
|
|
}
|
|
else if (opt_warned != no_warning)
|
|
{
|
|
if (fndecl)
|
|
inform (get_location (fndecl),
|
|
"in a call to function %qD", fndecl);
|
|
else
|
|
inform (get_location (stmt),
|
|
"in a call with type %qT", fntype);
|
|
}
|
|
|
|
/* Set the bit in case if was cleared and not set above. */
|
|
if (opt_warned != no_warning)
|
|
suppress_warning (stmt, opt_warned);
|
|
}
|
|
|
|
/* Check call STMT to an ordinary (non-built-in) function for invalid
|
|
accesses. Return true if a call has been handled. */
|
|
|
|
bool
|
|
pass_waccess::check_call_access (gcall *stmt)
|
|
{
|
|
tree fntype = gimple_call_fntype (stmt);
|
|
if (!fntype)
|
|
return false;
|
|
|
|
tree fntypeattrs = TYPE_ATTRIBUTES (fntype);
|
|
if (!fntypeattrs)
|
|
return false;
|
|
|
|
/* Map of attribute accewss specifications for function arguments. */
|
|
rdwr_map rdwr_idx;
|
|
init_attr_rdwr_indices (&rdwr_idx, fntypeattrs);
|
|
|
|
unsigned nargs = call_nargs (stmt);
|
|
for (unsigned i = 0; i != nargs; ++i)
|
|
{
|
|
tree arg = call_arg (stmt, i);
|
|
|
|
/* Save the actual argument that corresponds to the access attribute
|
|
operand for later processing. */
|
|
if (attr_access *access = rdwr_idx.get (i))
|
|
{
|
|
if (POINTER_TYPE_P (TREE_TYPE (arg)))
|
|
{
|
|
access->ptr = arg;
|
|
// A nonnull ACCESS->SIZE contains VLA bounds. */
|
|
}
|
|
else
|
|
{
|
|
access->size = arg;
|
|
gcc_assert (access->ptr == NULL_TREE);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check attribute access arguments. */
|
|
tree fndecl = gimple_call_fndecl (stmt);
|
|
maybe_check_access_sizes (&rdwr_idx, fndecl, fntype, stmt);
|
|
|
|
check_alloc_size_call (stmt);
|
|
return true;
|
|
}
|
|
|
|
/* Check arguments in a call STMT for attribute nonstring. */
|
|
|
|
static void
|
|
check_nonstring_args (gcall *stmt)
|
|
{
|
|
tree fndecl = gimple_call_fndecl (stmt);
|
|
|
|
/* Detect passing non-string arguments to functions expecting
|
|
nul-terminated strings. */
|
|
maybe_warn_nonstring_arg (fndecl, stmt);
|
|
}
|
|
|
|
/* Issue a warning if a deallocation function such as free, realloc,
|
|
or C++ operator delete is called with an argument not returned by
|
|
a matching allocation function such as malloc or the corresponding
|
|
form of C++ operatorn new. */
|
|
|
|
void
|
|
pass_waccess::maybe_check_dealloc_call (gcall *call)
|
|
{
|
|
tree fndecl = gimple_call_fndecl (call);
|
|
if (!fndecl)
|
|
return;
|
|
|
|
unsigned argno = fndecl_dealloc_argno (fndecl);
|
|
if ((unsigned) call_nargs (call) <= argno)
|
|
return;
|
|
|
|
tree ptr = gimple_call_arg (call, argno);
|
|
if (integer_zerop (ptr))
|
|
return;
|
|
|
|
access_ref aref;
|
|
if (!compute_objsize (ptr, call, 0, &aref, &m_ptr_qry))
|
|
return;
|
|
|
|
tree ref = aref.ref;
|
|
if (integer_zerop (ref))
|
|
return;
|
|
|
|
tree dealloc_decl = fndecl;
|
|
location_t loc = gimple_location (call);
|
|
|
|
if (DECL_P (ref) || EXPR_P (ref))
|
|
{
|
|
/* Diagnose freeing a declared object. */
|
|
if (aref.ref_declared ()
|
|
&& warning_at (loc, OPT_Wfree_nonheap_object,
|
|
"%qD called on unallocated object %qD",
|
|
dealloc_decl, ref))
|
|
{
|
|
inform (get_location (ref), "declared here");
|
|
return;
|
|
}
|
|
|
|
/* Diagnose freeing a pointer that includes a positive offset.
|
|
Such a pointer cannot refer to the beginning of an allocated
|
|
object. A negative offset may refer to it. */
|
|
if (aref.sizrng[0] != aref.sizrng[1]
|
|
&& warn_dealloc_offset (loc, call, aref))
|
|
return;
|
|
}
|
|
else if (CONSTANT_CLASS_P (ref))
|
|
{
|
|
if (warning_at (loc, OPT_Wfree_nonheap_object,
|
|
"%qD called on a pointer to an unallocated "
|
|
"object %qE", dealloc_decl, ref))
|
|
{
|
|
if (TREE_CODE (ptr) == SSA_NAME)
|
|
{
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (ptr);
|
|
if (is_gimple_assign (def_stmt))
|
|
{
|
|
location_t loc = gimple_location (def_stmt);
|
|
inform (loc, "assigned here");
|
|
}
|
|
}
|
|
return;
|
|
}
|
|
}
|
|
else if (TREE_CODE (ref) == SSA_NAME)
|
|
{
|
|
/* Also warn if the pointer argument refers to the result
|
|
of an allocation call like alloca or VLA. */
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (ref);
|
|
if (!def_stmt)
|
|
return;
|
|
|
|
if (is_gimple_call (def_stmt))
|
|
{
|
|
bool warned = false;
|
|
if (gimple_call_alloc_p (def_stmt))
|
|
{
|
|
if (matching_alloc_calls_p (def_stmt, dealloc_decl))
|
|
{
|
|
if (warn_dealloc_offset (loc, call, aref))
|
|
return;
|
|
}
|
|
else
|
|
{
|
|
tree alloc_decl = gimple_call_fndecl (def_stmt);
|
|
const opt_code opt =
|
|
(DECL_IS_OPERATOR_NEW_P (alloc_decl)
|
|
|| DECL_IS_OPERATOR_DELETE_P (dealloc_decl)
|
|
? OPT_Wmismatched_new_delete
|
|
: OPT_Wmismatched_dealloc);
|
|
warned = warning_at (loc, opt,
|
|
"%qD called on pointer returned "
|
|
"from a mismatched allocation "
|
|
"function", dealloc_decl);
|
|
}
|
|
}
|
|
else if (gimple_call_builtin_p (def_stmt, BUILT_IN_ALLOCA)
|
|
|| gimple_call_builtin_p (def_stmt,
|
|
BUILT_IN_ALLOCA_WITH_ALIGN))
|
|
warned = warning_at (loc, OPT_Wfree_nonheap_object,
|
|
"%qD called on pointer to "
|
|
"an unallocated object",
|
|
dealloc_decl);
|
|
else if (warn_dealloc_offset (loc, call, aref))
|
|
return;
|
|
|
|
if (warned)
|
|
{
|
|
tree fndecl = gimple_call_fndecl (def_stmt);
|
|
inform (gimple_location (def_stmt),
|
|
"returned from %qD", fndecl);
|
|
return;
|
|
}
|
|
}
|
|
else if (gimple_nop_p (def_stmt))
|
|
{
|
|
ref = SSA_NAME_VAR (ref);
|
|
/* Diagnose freeing a pointer that includes a positive offset. */
|
|
if (TREE_CODE (ref) == PARM_DECL
|
|
&& !aref.deref
|
|
&& aref.sizrng[0] != aref.sizrng[1]
|
|
&& aref.offrng[0] > 0 && aref.offrng[1] > 0
|
|
&& warn_dealloc_offset (loc, call, aref))
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Return true if either USE_STMT's basic block (that of a pointer's use)
|
|
is dominated by INVAL_STMT's (that of a pointer's invalidating statement,
|
|
which is either a clobber or a deallocation call), or if they're in
|
|
the same block, USE_STMT follows INVAL_STMT. */
|
|
|
|
bool
|
|
pass_waccess::use_after_inval_p (gimple *inval_stmt, gimple *use_stmt,
|
|
bool last_block /* = false */)
|
|
{
|
|
tree clobvar =
|
|
gimple_clobber_p (inval_stmt) ? gimple_assign_lhs (inval_stmt) : NULL_TREE;
|
|
|
|
basic_block inval_bb = gimple_bb (inval_stmt);
|
|
basic_block use_bb = gimple_bb (use_stmt);
|
|
|
|
if (!inval_bb || !use_bb)
|
|
return false;
|
|
|
|
if (inval_bb != use_bb)
|
|
{
|
|
if (dominated_by_p (CDI_DOMINATORS, use_bb, inval_bb))
|
|
return true;
|
|
|
|
if (!clobvar || !last_block)
|
|
return false;
|
|
|
|
/* Proceed only when looking for uses of dangling pointers. */
|
|
auto gsi = gsi_for_stmt (use_stmt);
|
|
|
|
auto_bitmap visited;
|
|
|
|
/* A use statement in the last basic block in a function or one that
|
|
falls through to it is after any other prior clobber of the used
|
|
variable unless it's followed by a clobber of the same variable. */
|
|
basic_block bb = use_bb;
|
|
while (bb != inval_bb
|
|
&& single_succ_p (bb)
|
|
&& !(single_succ_edge (bb)->flags & (EDGE_EH|EDGE_DFS_BACK)))
|
|
{
|
|
if (!bitmap_set_bit (visited, bb->index))
|
|
/* Avoid cycles. */
|
|
return true;
|
|
|
|
for (; !gsi_end_p (gsi); gsi_next_nondebug (&gsi))
|
|
{
|
|
gimple *stmt = gsi_stmt (gsi);
|
|
if (gimple_clobber_p (stmt))
|
|
{
|
|
if (clobvar == gimple_assign_lhs (stmt))
|
|
/* The use is followed by a clobber. */
|
|
return false;
|
|
}
|
|
}
|
|
|
|
bb = single_succ (bb);
|
|
gsi = gsi_start_bb (bb);
|
|
}
|
|
|
|
/* The use is one of a dangling pointer if a clobber of the variable
|
|
[the pointer points to] has not been found before the function exit
|
|
point. */
|
|
return bb == EXIT_BLOCK_PTR_FOR_FN (cfun);
|
|
}
|
|
|
|
if (bitmap_set_bit (m_bb_uids_set, inval_bb->index))
|
|
/* The first time this basic block is visited assign increasing ids
|
|
to consecutive statements in it. Use the ids to determine which
|
|
precedes which. This avoids the linear traversal on subsequent
|
|
visits to the same block. */
|
|
for (auto si = gsi_start_bb (inval_bb); !gsi_end_p (si);
|
|
gsi_next_nondebug (&si))
|
|
{
|
|
gimple *stmt = gsi_stmt (si);
|
|
unsigned uid = inc_gimple_stmt_max_uid (m_func);
|
|
gimple_set_uid (stmt, uid);
|
|
}
|
|
|
|
return gimple_uid (inval_stmt) < gimple_uid (use_stmt);
|
|
}
|
|
|
|
/* Issue a warning for the USE_STMT of pointer or reference REF rendered
|
|
invalid by INVAL_STMT. REF may be null when it's been optimized away.
|
|
When nonnull, INVAL_STMT is the deallocation function that rendered
|
|
the pointer or reference dangling. Otherwise, VAR is the auto variable
|
|
(including an unnamed temporary such as a compound literal) whose
|
|
lifetime's rended it dangling. MAYBE is true to issue the "maybe"
|
|
kind of warning. EQUALITY is true when the pointer is used in
|
|
an equality expression. */
|
|
|
|
void
|
|
pass_waccess::warn_invalid_pointer (tree ref, gimple *use_stmt,
|
|
gimple *inval_stmt, tree var,
|
|
bool maybe, bool equality /* = false */)
|
|
{
|
|
/* Avoid printing the unhelpful "<unknown>" in the diagnostics. */
|
|
if (ref && TREE_CODE (ref) == SSA_NAME
|
|
&& (!SSA_NAME_VAR (ref) || DECL_ARTIFICIAL (SSA_NAME_VAR (ref))))
|
|
ref = NULL_TREE;
|
|
|
|
location_t use_loc = gimple_location (use_stmt);
|
|
if (use_loc == UNKNOWN_LOCATION)
|
|
{
|
|
use_loc = m_func->function_end_locus;
|
|
if (!ref)
|
|
/* Avoid issuing a warning with no context other than
|
|
the function. That would make it difficult to debug
|
|
in any but very simple cases. */
|
|
return;
|
|
}
|
|
|
|
if (is_gimple_call (inval_stmt))
|
|
{
|
|
if ((equality && warn_use_after_free < 3)
|
|
|| (maybe && warn_use_after_free < 2)
|
|
|| warning_suppressed_p (use_stmt, OPT_Wuse_after_free))
|
|
return;
|
|
|
|
const tree inval_decl = gimple_call_fndecl (inval_stmt);
|
|
|
|
if ((ref && warning_at (use_loc, OPT_Wuse_after_free,
|
|
(maybe
|
|
? G_("pointer %qE may be used after %qD")
|
|
: G_("pointer %qE used after %qD")),
|
|
ref, inval_decl))
|
|
|| (!ref && warning_at (use_loc, OPT_Wuse_after_free,
|
|
(maybe
|
|
? G_("pointer may be used after %qD")
|
|
: G_("pointer used after %qD")),
|
|
inval_decl)))
|
|
{
|
|
location_t loc = gimple_location (inval_stmt);
|
|
inform (loc, "call to %qD here", inval_decl);
|
|
suppress_warning (use_stmt, OPT_Wuse_after_free);
|
|
}
|
|
return;
|
|
}
|
|
|
|
if ((maybe && warn_dangling_pointer < 2)
|
|
|| warning_suppressed_p (use_stmt, OPT_Wdangling_pointer_))
|
|
return;
|
|
|
|
if (DECL_NAME (var))
|
|
{
|
|
if ((ref
|
|
&& warning_at (use_loc, OPT_Wdangling_pointer_,
|
|
(maybe
|
|
? G_("dangling pointer %qE to %qD may be used")
|
|
: G_("using dangling pointer %qE to %qD")),
|
|
ref, var))
|
|
|| (!ref
|
|
&& warning_at (use_loc, OPT_Wdangling_pointer_,
|
|
(maybe
|
|
? G_("dangling pointer to %qD may be used")
|
|
: G_("using a dangling pointer to %qD")),
|
|
var)))
|
|
inform (DECL_SOURCE_LOCATION (var),
|
|
"%qD declared here", var);
|
|
suppress_warning (use_stmt, OPT_Wdangling_pointer_);
|
|
return;
|
|
}
|
|
|
|
if ((ref
|
|
&& warning_at (use_loc, OPT_Wdangling_pointer_,
|
|
(maybe
|
|
? G_("dangling pointer %qE to an unnamed temporary "
|
|
"may be used")
|
|
: G_("using dangling pointer %qE to an unnamed "
|
|
"temporary")),
|
|
ref, var))
|
|
|| (!ref
|
|
&& warning_at (use_loc, OPT_Wdangling_pointer_,
|
|
(maybe
|
|
? G_("dangling pointer to an unnamed temporary "
|
|
"may be used")
|
|
: G_("using a dangling pointer to an unnamed "
|
|
"temporary")),
|
|
var)))
|
|
{
|
|
inform (DECL_SOURCE_LOCATION (var),
|
|
"unnamed temporary defined here");
|
|
suppress_warning (use_stmt, OPT_Wdangling_pointer_);
|
|
}
|
|
}
|
|
|
|
/* If STMT is a call to either the standard realloc or to a user-defined
|
|
reallocation function returns its LHS and set *PTR to the reallocated
|
|
pointer. Otherwise return null. */
|
|
|
|
static tree
|
|
get_realloc_lhs (gimple *stmt, tree *ptr)
|
|
{
|
|
if (gimple_call_builtin_p (stmt, BUILT_IN_REALLOC))
|
|
{
|
|
*ptr = gimple_call_arg (stmt, 0);
|
|
return gimple_call_lhs (stmt);
|
|
}
|
|
|
|
gcall *call = dyn_cast<gcall *>(stmt);
|
|
if (!call)
|
|
return NULL_TREE;
|
|
|
|
tree fnattr = NULL_TREE;
|
|
tree fndecl = gimple_call_fndecl (call);
|
|
if (fndecl)
|
|
fnattr = DECL_ATTRIBUTES (fndecl);
|
|
else
|
|
{
|
|
tree fntype = gimple_call_fntype (stmt);
|
|
if (!fntype)
|
|
return NULL_TREE;
|
|
fnattr = TYPE_ATTRIBUTES (fntype);
|
|
}
|
|
|
|
if (!fnattr)
|
|
return NULL_TREE;
|
|
|
|
for (tree ats = fnattr; (ats = lookup_attribute ("*dealloc", ats));
|
|
ats = TREE_CHAIN (ats))
|
|
{
|
|
tree args = TREE_VALUE (ats);
|
|
if (!args)
|
|
continue;
|
|
|
|
tree alloc = TREE_VALUE (args);
|
|
if (!alloc)
|
|
continue;
|
|
|
|
if (alloc == DECL_NAME (fndecl))
|
|
{
|
|
unsigned argno = 0;
|
|
if (tree index = TREE_CHAIN (args))
|
|
argno = TREE_INT_CST_LOW (TREE_VALUE (index)) - 1;
|
|
*ptr = gimple_call_arg (stmt, argno);
|
|
return gimple_call_lhs (stmt);
|
|
}
|
|
}
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
/* Warn if STMT is a call to a deallocation function that's not a match
|
|
for the REALLOC_STMT call. Return true if warned. */
|
|
|
|
static bool
|
|
maybe_warn_mismatched_realloc (tree ptr, gimple *realloc_stmt, gimple *stmt)
|
|
{
|
|
if (!is_gimple_call (stmt))
|
|
return false;
|
|
|
|
tree fndecl = gimple_call_fndecl (stmt);
|
|
if (!fndecl)
|
|
return false;
|
|
|
|
unsigned argno = fndecl_dealloc_argno (fndecl);
|
|
if (call_nargs (stmt) <= argno)
|
|
return false;
|
|
|
|
if (matching_alloc_calls_p (realloc_stmt, fndecl))
|
|
return false;
|
|
|
|
/* Avoid printing the unhelpful "<unknown>" in the diagnostics. */
|
|
if (ptr && TREE_CODE (ptr) == SSA_NAME
|
|
&& (!SSA_NAME_VAR (ptr) || DECL_ARTIFICIAL (SSA_NAME_VAR (ptr))))
|
|
ptr = NULL_TREE;
|
|
|
|
location_t loc = gimple_location (stmt);
|
|
tree realloc_decl = gimple_call_fndecl (realloc_stmt);
|
|
tree dealloc_decl = gimple_call_fndecl (stmt);
|
|
if (ptr && !warning_at (loc, OPT_Wmismatched_dealloc,
|
|
"%qD called on pointer %qE passed to mismatched "
|
|
"allocation function %qD",
|
|
dealloc_decl, ptr, realloc_decl))
|
|
return false;
|
|
if (!ptr && !warning_at (loc, OPT_Wmismatched_dealloc,
|
|
"%qD called on a pointer passed to mismatched "
|
|
"reallocation function %qD",
|
|
dealloc_decl, realloc_decl))
|
|
return false;
|
|
|
|
inform (gimple_location (realloc_stmt),
|
|
"call to %qD", realloc_decl);
|
|
return true;
|
|
}
|
|
|
|
/* Return true if P and Q point to the same object, and false if they
|
|
either don't or their relationship cannot be determined. */
|
|
|
|
static bool
|
|
pointers_related_p (gimple *stmt, tree p, tree q, pointer_query &qry)
|
|
{
|
|
if (!ptr_derefs_may_alias_p (p, q))
|
|
return false;
|
|
|
|
/* TODO: Work harder to rule out relatedness. */
|
|
access_ref pref, qref;
|
|
if (!qry.get_ref (p, stmt, &pref, 0)
|
|
|| !qry.get_ref (q, stmt, &qref, 0))
|
|
return true;
|
|
|
|
return pref.ref == qref.ref;
|
|
}
|
|
|
|
/* For a STMT either a call to a deallocation function or a clobber, warn
|
|
for uses of the pointer PTR it was called with (including its copies
|
|
or others derived from it by pointer arithmetic). If STMT is a clobber,
|
|
VAR is the decl of the clobbered variable. When MAYBE is true use
|
|
a "maybe" form of diagnostic. */
|
|
|
|
void
|
|
pass_waccess::check_pointer_uses (gimple *stmt, tree ptr,
|
|
tree var /* = NULL_TREE */,
|
|
bool maybe /* = false */)
|
|
{
|
|
gcc_assert (TREE_CODE (ptr) == SSA_NAME);
|
|
|
|
const bool check_dangling = !is_gimple_call (stmt);
|
|
basic_block stmt_bb = gimple_bb (stmt);
|
|
|
|
/* If STMT is a reallocation function set to the reallocated pointer
|
|
and the LHS of the call, respectively. */
|
|
tree realloc_ptr = NULL_TREE;
|
|
tree realloc_lhs = get_realloc_lhs (stmt, &realloc_ptr);
|
|
|
|
auto_bitmap visited;
|
|
|
|
auto_vec<tree> pointers;
|
|
pointers.safe_push (ptr);
|
|
|
|
/* Starting with PTR, iterate over POINTERS added by the loop, and
|
|
either warn for their uses in basic blocks dominated by the STMT
|
|
or in statements that follow it in the same basic block, or add
|
|
them to POINTERS if they point into the same object as PTR (i.e.,
|
|
are obtained by pointer arithmetic on PTR). */
|
|
for (unsigned i = 0; i != pointers.length (); ++i)
|
|
{
|
|
tree ptr = pointers[i];
|
|
if (TREE_CODE (ptr) == SSA_NAME
|
|
&& !bitmap_set_bit (visited, SSA_NAME_VERSION (ptr)))
|
|
/* Avoid revisiting the same pointer. */
|
|
continue;
|
|
|
|
use_operand_p use_p;
|
|
imm_use_iterator iter;
|
|
FOR_EACH_IMM_USE_FAST (use_p, iter, ptr)
|
|
{
|
|
gimple *use_stmt = USE_STMT (use_p);
|
|
if (use_stmt == stmt || is_gimple_debug (use_stmt))
|
|
continue;
|
|
|
|
if (realloc_lhs)
|
|
{
|
|
/* Check to see if USE_STMT is a mismatched deallocation
|
|
call for the pointer passed to realloc. That's a bug
|
|
regardless of the pointer's value and so warn. */
|
|
if (maybe_warn_mismatched_realloc (*use_p->use, stmt, use_stmt))
|
|
continue;
|
|
|
|
/* Pointers passed to realloc that are used in basic blocks
|
|
where the realloc call is known to have failed are valid.
|
|
Ignore pointers that nothing is known about. Those could
|
|
have escaped along with their nullness. */
|
|
value_range vr;
|
|
if (m_ptr_qry.rvals->range_of_expr (vr, realloc_lhs, use_stmt))
|
|
{
|
|
if (vr.zero_p ())
|
|
continue;
|
|
|
|
if (!pointers_related_p (stmt, ptr, realloc_ptr, m_ptr_qry))
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (check_dangling
|
|
&& gimple_code (use_stmt) == GIMPLE_RETURN)
|
|
/* Avoid interfering with -Wreturn-local-addr (which runs only
|
|
with optimization enabled so it won't diagnose cases that
|
|
would be caught here when optimization is disabled). */
|
|
continue;
|
|
|
|
bool equality = false;
|
|
if (is_gimple_assign (use_stmt))
|
|
{
|
|
tree_code code = gimple_assign_rhs_code (use_stmt);
|
|
equality = code == EQ_EXPR || code == NE_EXPR;
|
|
}
|
|
else if (gcond *cond = dyn_cast<gcond *>(use_stmt))
|
|
{
|
|
tree_code code = gimple_cond_code (cond);
|
|
equality = code == EQ_EXPR || code == NE_EXPR;
|
|
}
|
|
|
|
/* Warn if USE_STMT is dominated by the deallocation STMT.
|
|
Otherwise, add the pointer to POINTERS so that the uses
|
|
of any other pointers derived from it can be checked. */
|
|
if (use_after_inval_p (stmt, use_stmt, check_dangling))
|
|
{
|
|
if (gimple_code (use_stmt) == GIMPLE_PHI)
|
|
{
|
|
tree lhs = gimple_phi_result (use_stmt);
|
|
if (TREE_CODE (lhs) == SSA_NAME)
|
|
{
|
|
pointers.safe_push (lhs);
|
|
continue;
|
|
}
|
|
}
|
|
|
|
basic_block use_bb = gimple_bb (use_stmt);
|
|
bool this_maybe
|
|
= (maybe
|
|
|| !dominated_by_p (CDI_POST_DOMINATORS, use_bb, stmt_bb));
|
|
warn_invalid_pointer (*use_p->use, use_stmt, stmt, var,
|
|
this_maybe, equality);
|
|
continue;
|
|
}
|
|
|
|
if (is_gimple_assign (use_stmt))
|
|
{
|
|
tree lhs = gimple_assign_lhs (use_stmt);
|
|
if (TREE_CODE (lhs) == SSA_NAME)
|
|
{
|
|
tree_code rhs_code = gimple_assign_rhs_code (use_stmt);
|
|
if (rhs_code == POINTER_PLUS_EXPR || rhs_code == SSA_NAME)
|
|
pointers.safe_push (lhs);
|
|
}
|
|
continue;
|
|
}
|
|
|
|
if (gcall *call = dyn_cast <gcall *>(use_stmt))
|
|
{
|
|
if (gimple_call_return_arg (call))
|
|
if (tree lhs = gimple_call_lhs (call))
|
|
if (TREE_CODE (lhs) == SSA_NAME)
|
|
pointers.safe_push (lhs);
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Check call STMT for invalid accesses. */
|
|
|
|
void
|
|
pass_waccess::check_call (gcall *stmt)
|
|
{
|
|
if (gimple_call_builtin_p (stmt, BUILT_IN_NORMAL))
|
|
check_builtin (stmt);
|
|
|
|
if (!m_early_checks_p)
|
|
if (tree callee = gimple_call_fndecl (stmt))
|
|
{
|
|
/* Check for uses of the pointer passed to either a standard
|
|
or a user-defined deallocation function. */
|
|
unsigned argno = fndecl_dealloc_argno (callee);
|
|
if (argno < (unsigned) call_nargs (stmt))
|
|
{
|
|
tree arg = call_arg (stmt, argno);
|
|
if (TREE_CODE (arg) == SSA_NAME)
|
|
check_pointer_uses (stmt, arg);
|
|
}
|
|
}
|
|
|
|
check_call_access (stmt);
|
|
check_call_dangling (stmt);
|
|
|
|
if (m_early_checks_p)
|
|
return;
|
|
|
|
maybe_check_dealloc_call (stmt);
|
|
check_nonstring_args (stmt);
|
|
}
|
|
|
|
|
|
/* Return true of X is a DECL with automatic storage duration. */
|
|
|
|
static inline bool
|
|
is_auto_decl (tree x)
|
|
{
|
|
return DECL_P (x) && !DECL_EXTERNAL (x) && !TREE_STATIC (x);
|
|
}
|
|
|
|
/* Check non-call STMT for invalid accesses. */
|
|
|
|
void
|
|
pass_waccess::check_stmt (gimple *stmt)
|
|
{
|
|
if (m_check_dangling_p && gimple_clobber_p (stmt))
|
|
{
|
|
/* Ignore clobber statemts in blocks with exceptional edges. */
|
|
basic_block bb = gimple_bb (stmt);
|
|
edge e = EDGE_PRED (bb, 0);
|
|
if (e->flags & EDGE_EH)
|
|
return;
|
|
|
|
tree var = gimple_assign_lhs (stmt);
|
|
m_clobbers.put (var, stmt);
|
|
return;
|
|
}
|
|
|
|
if (is_gimple_assign (stmt))
|
|
{
|
|
/* Clobbered unnamed temporaries such as compound literals can be
|
|
revived. Check for an assignment to one and remove it from
|
|
M_CLOBBERS. */
|
|
tree lhs = gimple_assign_lhs (stmt);
|
|
while (handled_component_p (lhs))
|
|
lhs = TREE_OPERAND (lhs, 0);
|
|
|
|
if (is_auto_decl (lhs))
|
|
m_clobbers.remove (lhs);
|
|
return;
|
|
}
|
|
|
|
if (greturn *ret = dyn_cast <greturn *> (stmt))
|
|
{
|
|
if (optimize && flag_isolate_erroneous_paths_dereference)
|
|
/* Avoid interfering with -Wreturn-local-addr (which runs only
|
|
with optimization enabled). */
|
|
return;
|
|
|
|
tree arg = gimple_return_retval (ret);
|
|
if (!arg || TREE_CODE (arg) != ADDR_EXPR)
|
|
return;
|
|
|
|
arg = TREE_OPERAND (arg, 0);
|
|
while (handled_component_p (arg))
|
|
arg = TREE_OPERAND (arg, 0);
|
|
|
|
if (!is_auto_decl (arg))
|
|
return;
|
|
|
|
gimple **pclobber = m_clobbers.get (arg);
|
|
if (!pclobber)
|
|
return;
|
|
|
|
if (!use_after_inval_p (*pclobber, stmt))
|
|
return;
|
|
|
|
warn_invalid_pointer (NULL_TREE, stmt, *pclobber, arg, false);
|
|
}
|
|
}
|
|
|
|
/* Check basic block BB for invalid accesses. */
|
|
|
|
void
|
|
pass_waccess::check_block (basic_block bb)
|
|
{
|
|
/* Iterate over statements, looking for function calls. */
|
|
for (auto si = gsi_start_bb (bb); !gsi_end_p (si);
|
|
gsi_next_nondebug (&si))
|
|
{
|
|
gimple *stmt = gsi_stmt (si);
|
|
if (gcall *call = dyn_cast <gcall *> (stmt))
|
|
check_call (call);
|
|
else
|
|
check_stmt (stmt);
|
|
}
|
|
}
|
|
|
|
/* Return the argument that the call STMT to a built-in function returns
|
|
(including with an offset) or null if it doesn't. */
|
|
|
|
tree
|
|
pass_waccess::gimple_call_return_arg (gcall *call)
|
|
{
|
|
/* Check for attribute fn spec to see if the function returns one
|
|
of its arguments. */
|
|
attr_fnspec fnspec = gimple_call_fnspec (call);
|
|
unsigned int argno;
|
|
if (!fnspec.returns_arg (&argno))
|
|
{
|
|
if (gimple_call_num_args (call) < 1)
|
|
return NULL_TREE;
|
|
|
|
if (!gimple_call_builtin_p (call, BUILT_IN_NORMAL))
|
|
return NULL_TREE;
|
|
|
|
tree fndecl = gimple_call_fndecl (call);
|
|
switch (DECL_FUNCTION_CODE (fndecl))
|
|
{
|
|
case BUILT_IN_MEMPCPY:
|
|
case BUILT_IN_MEMPCPY_CHK:
|
|
case BUILT_IN_MEMCHR:
|
|
case BUILT_IN_STRCHR:
|
|
case BUILT_IN_STRRCHR:
|
|
case BUILT_IN_STRSTR:
|
|
case BUILT_IN_STPCPY:
|
|
case BUILT_IN_STPCPY_CHK:
|
|
case BUILT_IN_STPNCPY:
|
|
case BUILT_IN_STPNCPY_CHK:
|
|
argno = 0;
|
|
break;
|
|
|
|
default:
|
|
return NULL_TREE;
|
|
}
|
|
}
|
|
|
|
if (gimple_call_num_args (call) <= argno)
|
|
return NULL_TREE;
|
|
|
|
return gimple_call_arg (call, argno);
|
|
}
|
|
|
|
/* Return the decl referenced by the argument that the call STMT to
|
|
a built-in function returns (including with an offset) or null if
|
|
it doesn't. */
|
|
|
|
tree
|
|
pass_waccess::gimple_call_return_arg_ref (gcall *call)
|
|
{
|
|
if (tree arg = gimple_call_return_arg (call))
|
|
{
|
|
access_ref aref;
|
|
if (m_ptr_qry.get_ref (arg, call, &aref, 0)
|
|
&& DECL_P (aref.ref))
|
|
return aref.ref;
|
|
}
|
|
|
|
return NULL_TREE;
|
|
}
|
|
|
|
/* Check for and diagnose all uses of the dangling pointer VAR to the auto
|
|
object DECL whose lifetime has ended. OBJREF is true when VAR denotes
|
|
an access to a DECL that may have been clobbered. */
|
|
|
|
void
|
|
pass_waccess::check_dangling_uses (tree var, tree decl, bool maybe /* = false */,
|
|
bool objref /* = false */)
|
|
{
|
|
if (!decl || !is_auto_decl (decl))
|
|
return;
|
|
|
|
gimple **pclob = m_clobbers.get (decl);
|
|
if (!pclob)
|
|
return;
|
|
|
|
if (!objref)
|
|
{
|
|
check_pointer_uses (*pclob, var, decl, maybe);
|
|
return;
|
|
}
|
|
|
|
gimple *use_stmt = SSA_NAME_DEF_STMT (var);
|
|
if (!use_after_inval_p (*pclob, use_stmt, true))
|
|
return;
|
|
|
|
basic_block use_bb = gimple_bb (use_stmt);
|
|
basic_block clob_bb = gimple_bb (*pclob);
|
|
maybe = maybe || !dominated_by_p (CDI_POST_DOMINATORS, use_bb, clob_bb);
|
|
warn_invalid_pointer (var, use_stmt, *pclob, decl, maybe, false);
|
|
}
|
|
|
|
/* Diagnose stores in BB and (recursively) its predecessors of the addresses
|
|
of local variables into nonlocal pointers that are left dangling after
|
|
the function returns. BBS is a bitmap of basic blocks visited. */
|
|
|
|
void
|
|
pass_waccess::check_dangling_stores (basic_block bb,
|
|
hash_set<tree> &stores,
|
|
auto_bitmap &bbs)
|
|
{
|
|
if (!bitmap_set_bit (bbs, bb->index))
|
|
/* Avoid cycles. */
|
|
return;
|
|
|
|
/* Iterate backwards over the statements looking for a store of
|
|
the address of a local variable into a nonlocal pointer. */
|
|
for (auto gsi = gsi_last_nondebug_bb (bb); ; gsi_prev_nondebug (&gsi))
|
|
{
|
|
gimple *stmt = gsi_stmt (gsi);
|
|
if (!stmt)
|
|
break;
|
|
|
|
if (is_gimple_call (stmt)
|
|
&& !(gimple_call_flags (stmt) & (ECF_CONST | ECF_PURE)))
|
|
/* Avoid looking before nonconst, nonpure calls since those might
|
|
use the escaped locals. */
|
|
return;
|
|
|
|
if (!is_gimple_assign (stmt) || gimple_clobber_p (stmt))
|
|
continue;
|
|
|
|
access_ref lhs_ref;
|
|
tree lhs = gimple_assign_lhs (stmt);
|
|
if (!m_ptr_qry.get_ref (lhs, stmt, &lhs_ref, 0))
|
|
continue;
|
|
|
|
if (is_auto_decl (lhs_ref.ref))
|
|
continue;
|
|
|
|
if (DECL_P (lhs_ref.ref))
|
|
{
|
|
if (!POINTER_TYPE_P (TREE_TYPE (lhs_ref.ref))
|
|
|| lhs_ref.deref > 0)
|
|
continue;
|
|
}
|
|
else if (TREE_CODE (lhs_ref.ref) == SSA_NAME)
|
|
{
|
|
/* Avoid looking at or before stores into unknown objects. */
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (lhs_ref.ref);
|
|
if (!gimple_nop_p (def_stmt))
|
|
return;
|
|
}
|
|
else if (TREE_CODE (lhs_ref.ref) == MEM_REF)
|
|
{
|
|
tree arg = TREE_OPERAND (lhs_ref.ref, 0);
|
|
if (TREE_CODE (arg) == SSA_NAME)
|
|
{
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (arg);
|
|
if (!gimple_nop_p (def_stmt))
|
|
return;
|
|
}
|
|
}
|
|
else
|
|
continue;
|
|
|
|
if (stores.add (lhs_ref.ref))
|
|
continue;
|
|
|
|
/* FIXME: Handle stores of alloca() and VLA. */
|
|
access_ref rhs_ref;
|
|
tree rhs = gimple_assign_rhs1 (stmt);
|
|
if (!m_ptr_qry.get_ref (rhs, stmt, &rhs_ref, 0)
|
|
|| rhs_ref.deref != -1)
|
|
continue;
|
|
|
|
if (!is_auto_decl (rhs_ref.ref))
|
|
continue;
|
|
|
|
location_t loc = gimple_location (stmt);
|
|
if (warning_at (loc, OPT_Wdangling_pointer_,
|
|
"storing the address of local variable %qD in %qE",
|
|
rhs_ref.ref, lhs))
|
|
{
|
|
location_t loc = DECL_SOURCE_LOCATION (rhs_ref.ref);
|
|
inform (loc, "%qD declared here", rhs_ref.ref);
|
|
|
|
if (DECL_P (lhs_ref.ref))
|
|
loc = DECL_SOURCE_LOCATION (lhs_ref.ref);
|
|
else if (EXPR_HAS_LOCATION (lhs_ref.ref))
|
|
loc = EXPR_LOCATION (lhs_ref.ref);
|
|
|
|
if (loc != UNKNOWN_LOCATION)
|
|
inform (loc, "%qE declared here", lhs_ref.ref);
|
|
}
|
|
}
|
|
|
|
edge e;
|
|
edge_iterator ei;
|
|
FOR_EACH_EDGE (e, ei, bb->preds)
|
|
{
|
|
basic_block pred = e->src;
|
|
check_dangling_stores (pred, stores, bbs);
|
|
}
|
|
}
|
|
|
|
/* Diagnose stores of the addresses of local variables into nonlocal
|
|
pointers that are left dangling after the function returns. */
|
|
|
|
void
|
|
pass_waccess::check_dangling_stores ()
|
|
{
|
|
auto_bitmap bbs;
|
|
hash_set<tree> stores;
|
|
check_dangling_stores (EXIT_BLOCK_PTR_FOR_FN (m_func), stores, bbs);
|
|
}
|
|
|
|
/* Check for and diagnose uses of dangling pointers to auto objects
|
|
whose lifetime has ended. */
|
|
|
|
void
|
|
pass_waccess::check_dangling_uses ()
|
|
{
|
|
tree var;
|
|
unsigned i;
|
|
FOR_EACH_SSA_NAME (i, var, m_func)
|
|
{
|
|
/* For each SSA_NAME pointer VAR find the DECL it points to.
|
|
If the DECL is a clobbered local variable, check to see
|
|
if any of VAR's uses (or those of other pointers derived
|
|
from VAR) happens after the clobber. If so, warn. */
|
|
tree decl = NULL_TREE;
|
|
|
|
gimple *def_stmt = SSA_NAME_DEF_STMT (var);
|
|
if (is_gimple_assign (def_stmt))
|
|
{
|
|
tree rhs = gimple_assign_rhs1 (def_stmt);
|
|
if (TREE_CODE (rhs) == ADDR_EXPR)
|
|
{
|
|
if (!POINTER_TYPE_P (TREE_TYPE (var)))
|
|
continue;
|
|
decl = TREE_OPERAND (rhs, 0);
|
|
}
|
|
else
|
|
{
|
|
/* For other expressions, check the base DECL to see
|
|
if it's been clobbered, most likely as a result of
|
|
inlining a reference to it. */
|
|
decl = get_base_address (rhs);
|
|
if (DECL_P (decl))
|
|
check_dangling_uses (var, decl, false, true);
|
|
continue;
|
|
}
|
|
}
|
|
else if (POINTER_TYPE_P (TREE_TYPE (var)))
|
|
{
|
|
if (gcall *call = dyn_cast<gcall *>(def_stmt))
|
|
decl = gimple_call_return_arg_ref (call);
|
|
else if (gphi *phi = dyn_cast <gphi *>(def_stmt))
|
|
{
|
|
unsigned nargs = gimple_phi_num_args (phi);
|
|
for (unsigned i = 0; i != nargs; ++i)
|
|
{
|
|
access_ref aref;
|
|
tree arg = gimple_phi_arg_def (phi, i);
|
|
if (!m_ptr_qry.get_ref (arg, phi, &aref, 0)
|
|
|| (aref.deref == 0
|
|
&& POINTER_TYPE_P (TREE_TYPE (aref.ref))))
|
|
continue;
|
|
check_dangling_uses (var, aref.ref, true);
|
|
}
|
|
continue;
|
|
}
|
|
else
|
|
continue;
|
|
}
|
|
|
|
check_dangling_uses (var, decl);
|
|
}
|
|
}
|
|
|
|
/* Check CALL arguments for dangling pointers (those that have been
|
|
clobbered) and warn if found. */
|
|
|
|
void
|
|
pass_waccess::check_call_dangling (gcall *call)
|
|
{
|
|
unsigned nargs = gimple_call_num_args (call);
|
|
for (unsigned i = 0; i != nargs; ++i)
|
|
{
|
|
tree arg = gimple_call_arg (call, i);
|
|
if (TREE_CODE (arg) != ADDR_EXPR)
|
|
continue;
|
|
|
|
arg = TREE_OPERAND (arg, 0);
|
|
if (!DECL_P (arg))
|
|
continue;
|
|
|
|
gimple **pclobber = m_clobbers.get (arg);
|
|
if (!pclobber)
|
|
continue;
|
|
|
|
if (!use_after_inval_p (*pclobber, call))
|
|
continue;
|
|
|
|
warn_invalid_pointer (NULL_TREE, call, *pclobber, arg, false);
|
|
}
|
|
}
|
|
|
|
/* Check function FUN for invalid accesses. */
|
|
|
|
unsigned
|
|
pass_waccess::execute (function *fun)
|
|
{
|
|
calculate_dominance_info (CDI_DOMINATORS);
|
|
calculate_dominance_info (CDI_POST_DOMINATORS);
|
|
|
|
/* Create a new ranger instance and associate it with FUN. */
|
|
m_ptr_qry.rvals = enable_ranger (fun);
|
|
m_func = fun;
|
|
|
|
/* Check for dangling pointers in the earliest run of the pass.
|
|
The latest point -Wdangling-pointer should run is just before
|
|
loop unrolling which introduces uses after clobbers. Most cases
|
|
can be detected without optimization; cases where the address of
|
|
the local variable is passed to and then returned from a user-
|
|
defined function before its lifetime ends and the returned pointer
|
|
becomes dangling depend on inlining. */
|
|
m_check_dangling_p = m_early_checks_p;
|
|
|
|
auto_bitmap bb_uids_set (&bitmap_default_obstack);
|
|
m_bb_uids_set = bb_uids_set;
|
|
|
|
set_gimple_stmt_max_uid (m_func, 0);
|
|
|
|
basic_block bb;
|
|
FOR_EACH_BB_FN (bb, fun)
|
|
check_block (bb);
|
|
|
|
if (m_check_dangling_p)
|
|
{
|
|
check_dangling_uses ();
|
|
check_dangling_stores ();
|
|
}
|
|
|
|
if (dump_file)
|
|
m_ptr_qry.dump (dump_file, (dump_flags & TDF_DETAILS) != 0);
|
|
|
|
m_ptr_qry.flush_cache ();
|
|
|
|
/* Release the ranger instance and replace it with a global ranger.
|
|
Also reset the pointer since calling disable_ranger() deletes it. */
|
|
disable_ranger (fun);
|
|
m_ptr_qry.rvals = NULL;
|
|
|
|
m_clobbers.empty ();
|
|
m_bb_uids_set = NULL;
|
|
|
|
free_dominance_info (CDI_POST_DOMINATORS);
|
|
free_dominance_info (CDI_DOMINATORS);
|
|
return 0;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
/* Return a new instance of the pass. */
|
|
|
|
gimple_opt_pass *
|
|
make_pass_warn_access (gcc::context *ctxt)
|
|
{
|
|
return new pass_waccess (ctxt);
|
|
}
|