+/* Pass to detect and issue warnings for invalid accesses, including
+ invalid or mismatched allocation/deallocation calls.
+
+ Copyright (C) 2020-2021 Free Software Foundation, Inc.
+ Contributed by Martin Sebor <msebor@redhat.com>.
+
+ This file is part of GCC.
+
+ GCC is free software; you can redistribute it and/or modify it under
+ the terms of the GNU General Public License as published by the Free
+ Software Foundation; either version 3, or (at your option) any later
+ version.
+
+ GCC is distributed in the hope that it will be useful, but WITHOUT ANY
+ WARRANTY; without even the implied warranty of MERCHANTABILITY or
+ FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
+ for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with GCC; see the file COPYING3. If not see
+ <http://www.gnu.org/licenses/>. */
+
+#include "config.h"
+#include "system.h"
+#include "coretypes.h"
+#include "backend.h"
+#include "tree.h"
+#include "gimple.h"
+#include "tree-pass.h"
+#include "builtins.h"
+#include "ssa.h"
+#include "gimple-pretty-print.h"
+#include "gimple-ssa-warn-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 "tree-dfa.h"
+#include "tree-ssa.h"
+#include "tree-cfg.h"
+#include "tree-object-size.h"
+#include "calls.h"
+#include "cfgloop.h"
+#include "intl.h"
+#include "gimple-range.h"
+#include "stringpool.h"
+#include "attribs.h"
+#include "demangle.h"
+#include "pointer-query.h"
+
+/* 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. */
+
+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 */)
+{
+ 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 (DECL_SOURCE_LOCATION (decl),
+ "referenced argument declared here");
+ suppress_warning (arg, opt);
+ if (expr)
+ suppress_warning (expr, opt);
+ }
+}
+
+/* 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. RDONLY is true for read-only
+ accesses such as strcmp, false for read-write such as strcpy. When
+ EXPR is also issues a warning. */
+
+bool
+check_nul_terminated_array (tree expr, tree src,
+ tree bound /* = NULL_TREE */)
+{
+ /* 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 (EXPR_LOCATION (expr), expr, NULL, src, nonstr,
+ size, exact, bound ? bndrng : NULL);
+
+ return false;
+}
+
+/* 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 == NULL_TREE && lendata.minlen && lendata.decl)
+ {
+ if (size)
+ {
+ 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;
+ }
+
+ return NULL_TREE;
+}
+
+/* Issue a warning OPT for a bounded call EXP with a bound in RANGE
+ accessing an object with SIZE. */
+
+bool
+maybe_warn_for_bound (opt_code opt, location_t loc, tree exp, tree func,
+ tree bndrng[2], tree size,
+ const access_data *pad /* = NULL */)
+{
+ 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 (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)
+ {
+ if (DECL_P (pad->src.ref))
+ inform (DECL_SOURCE_LOCATION (pad->src.ref),
+ "source object declared here");
+ else if (EXPR_HAS_LOCATION (pad->src.ref))
+ inform (EXPR_LOCATION (pad->src.ref),
+ "source object allocated here");
+ }
+ suppress_warning (exp, opt);
+ }
+
+ return warned;
+ }
+
+ bool maybe = pad && pad->dst.phi ();
+ 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)
+ {
+ if (DECL_P (pad->dst.ref))
+ inform (DECL_SOURCE_LOCATION (pad->dst.ref),
+ "destination object declared here");
+ else if (EXPR_HAS_LOCATION (pad->dst.ref))
+ inform (EXPR_LOCATION (pad->dst.ref),
+ "destination object allocated here");
+ }
+ suppress_warning (exp, opt);
+ }
+
+ return warned;
+}
+
+/* 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. */
+
+static bool
+warn_for_access (location_t loc, tree func, tree 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;
+}
+
+/* Helper to set RANGE to the range of BOUND if it's nonnull, bounded
+ by BNDRNG if nonnull and valid. */
+
+void
+get_size_range (tree bound, tree range[2], const offset_int bndrng[2])
+{
+ if (bound)
+ get_size_range (bound, 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. */
+
+bool
+check_access (tree exp, tree dstwrite,
+ tree maxread, tree srcstr, tree dstsize,
+ access_mode mode, const access_data *pad /* = NULL */)
+{
+ /* 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 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. */
+ get_size_range (dstwrite, 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 = EXPR_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 = EXPR_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 (maxread, range, pad ? pad->src.bndrng : NULL);
+
+ location_t loc = EXPR_LOCATION (exp);
+ tree size = dstsize;
+ if (pad && pad->mode == access_read_only)
+ size = wide_int_to_tree (sizetype, pad->src.sizrng[1]);
+
+ 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 (maxread, 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 = EXPR_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;
+}
+
+/* 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. */
+ if (valid_new_delete_pair_p (new_name, delete_name))
+ return false;
+
+ /* 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 (DECL_SOURCE_LOCATION (aref.ref), "declared here");
+ else if (TREE_CODE (aref.ref) == SSA_NAME)
+ {
+ gimple *def_stmt = SSA_NAME_DEF_STMT (aref.ref);
+ if (is_gimple_call (def_stmt))
+ {
+ location_t def_loc = gimple_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;
+}
+
+/* Issue a warning if a deallocation function such as free, realloc,
+ or C++ operator delete is called with an argument not returned by
+ a matching allocation function such as malloc or the corresponding
+ form of C++ operatorn new. */
+
+void
+maybe_emit_free_warning (gcall *call)
+{
+ tree fndecl = gimple_call_fndecl (call);
+ if (!fndecl)
+ return;
+
+ unsigned argno = fndecl_dealloc_argno (fndecl);
+ if ((unsigned) gimple_call_num_args (call) <= argno)
+ return;
+
+ tree ptr = gimple_call_arg (call, argno);
+ if (integer_zerop (ptr))
+ return;
+
+ access_ref aref;
+ if (!compute_objsize (ptr, 0, &aref))
+ 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))
+ {
+ loc = (DECL_P (ref)
+ ? DECL_SOURCE_LOCATION (ref)
+ : EXPR_LOCATION (ref));
+ inform (loc, "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 (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;
+ }
+ }
+}
+
+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 *ctxt)
+ : gimple_opt_pass (pass_data_waccess, ctxt), m_ranger ()
+ { }
+
+ opt_pass *clone () { return new pass_waccess (m_ctxt); }
+
+ virtual bool gate (function *);
+ virtual unsigned int execute (function *);
+
+ void check (basic_block);
+ void check (gcall *);
+
+private:
+ gimple_ranger *m_ranger;
+};
+
+/* 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);
+}
+
+/* Check call STMT for invalid accesses. */
+
+void
+pass_waccess::check (gcall *stmt)
+{
+ maybe_emit_free_warning (stmt);
+}
+
+/* Check basic block BB for invalid accesses. */
+
+void
+pass_waccess::check (basic_block bb)
+{
+ /* Iterate over statements, looking for function calls. */
+ for (auto si = gsi_start_bb (bb); !gsi_end_p (si); gsi_next (&si))
+ {
+ if (gcall *call = dyn_cast <gcall *> (gsi_stmt (si)))
+ check (call);
+ }
+}
+
+/* Check function FUN for invalid accesses. */
+
+unsigned
+pass_waccess::execute (function *fun)
+{
+ basic_block bb;
+ FOR_EACH_BB_FN (bb, fun)
+ check (bb);
+
+ 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);
+}