@@ -1313,6 +1313,7 @@ ANALYZER_OBJS = \
analyzer/pending-diagnostic.o \
analyzer/program-point.o \
analyzer/program-state.o \
+ analyzer/ranges.o \
analyzer/region.o \
analyzer/region-model.o \
analyzer/region-model-asm.o \
@@ -55,6 +55,7 @@ run_analyzer_selftests ()
analyzer_function_set_cc_tests ();
analyzer_program_point_cc_tests ();
analyzer_program_state_cc_tests ();
+ analyzer_ranges_cc_tests ();
analyzer_region_model_cc_tests ();
analyzer_sm_file_cc_tests ();
analyzer_sm_signal_cc_tests ();
@@ -38,6 +38,7 @@ extern void analyzer_constraint_manager_cc_tests ();
extern void analyzer_function_set_cc_tests ();
extern void analyzer_program_point_cc_tests ();
extern void analyzer_program_state_cc_tests ();
+extern void analyzer_ranges_cc_tests ();
extern void analyzer_region_model_cc_tests ();
extern void analyzer_sm_file_cc_tests ();
extern void analyzer_sm_signal_cc_tests ();
@@ -154,6 +154,10 @@ Wanalyzer-out-of-bounds
Common Var(warn_analyzer_out_of_bounds) Init(1) Warning
Warn about code paths in which a write or read to a buffer is out-of-bounds.
+Wanalyzer-overlapping-buffers
+Common Var(warn_analyzer_overlapping_buffers) Init(1) Warning
+Warn about code paths in which undefined behavior would occur due to overlapping buffers.
+
Wanalyzer-possible-null-argument
Common Var(warn_analyzer_possible_null_argument) Init(1) Warning
Warn about code paths in which a possibly-NULL value is passed to a must-not-be-NULL function argument.
@@ -34,8 +34,10 @@ along with GCC; see the file COPYING3. If not see
#include "gimple-pretty-print.h"
#include "analyzer/region-model.h"
#include "analyzer/call-details.h"
+#include "analyzer/ranges.h"
#include "stringpool.h"
#include "attribs.h"
+#include "make-unique.h"
#if ENABLE_ANALYZER
@@ -405,6 +407,110 @@ check_for_null_terminated_string_arg (unsigned arg_idx,
out_sval);
}
+/* A subclass of pending_diagnostic for complaining about overlapping
+ buffers. */
+
+class overlapping_buffers
+: public pending_diagnostic_subclass<overlapping_buffers>
+{
+public:
+ overlapping_buffers (tree fndecl)
+ : m_fndecl (fndecl)
+ {
+ }
+
+ const char *get_kind () const final override
+ {
+ return "overlapping_buffers";
+ }
+
+ bool operator== (const overlapping_buffers &other) const
+ {
+ return m_fndecl == other.m_fndecl;
+ }
+
+ int get_controlling_option () const final override
+ {
+ return OPT_Wanalyzer_overlapping_buffers;
+ }
+
+ bool emit (rich_location *rich_loc, logger *) final override
+ {
+ auto_diagnostic_group d;
+
+ bool warned;
+ warned = warning_at (rich_loc, get_controlling_option (),
+ "overlapping buffers passed as arguments to %qD",
+ m_fndecl);
+
+ // TODO: draw a picture?
+
+ if (warned)
+ inform (DECL_SOURCE_LOCATION (m_fndecl),
+ "the behavior of %qD is undefined for overlapping buffers",
+ m_fndecl);
+
+ return warned;
+ }
+
+ label_text describe_final_event (const evdesc::final_event &ev) final override
+ {
+ return ev.formatted_print
+ ("overlapping buffers passed as arguments to %qD",
+ m_fndecl);
+ }
+
+private:
+ tree m_fndecl;
+};
+
+
+/* Check if the buffers pointed to by arguments ARG_IDX_A and ARG_IDX_B
+ (zero-based) overlap, when considering them both to be of size
+ NUM_BYTES_READ_SVAL.
+
+ If they do overlap, complain to the context. */
+
+void
+call_details::complain_about_overlap (unsigned arg_idx_a,
+ unsigned arg_idx_b,
+ const svalue *num_bytes_read_sval) const
+{
+ region_model_context *ctxt = get_ctxt ();
+ if (!ctxt)
+ return;
+
+ region_model *model = get_model ();
+ region_model_manager *mgr = model->get_manager ();
+
+ const svalue *arg_a_ptr_sval = get_arg_svalue (arg_idx_a);
+ if (arg_a_ptr_sval->get_kind () == SK_UNKNOWN)
+ return;
+ const region *arg_a_reg = model->deref_rvalue (arg_a_ptr_sval,
+ get_arg_tree (arg_idx_a),
+ ctxt);
+ const svalue *arg_b_ptr_sval = get_arg_svalue (arg_idx_b);
+ if (arg_b_ptr_sval->get_kind () == SK_UNKNOWN)
+ return;
+ const region *arg_b_reg = model->deref_rvalue (arg_b_ptr_sval,
+ get_arg_tree (arg_idx_b),
+ ctxt);
+ if (arg_a_reg->get_base_region () != arg_b_reg->get_base_region ())
+ return;
+
+ /* Are they within NUM_BYTES_READ_SVAL of each other? */
+ symbolic_byte_range byte_range_a (arg_a_reg->get_offset (mgr),
+ num_bytes_read_sval,
+ *mgr);
+ symbolic_byte_range byte_range_b (arg_b_reg->get_offset (mgr),
+ num_bytes_read_sval,
+ *mgr);
+ if (!byte_range_a.intersection (byte_range_b, *model).is_true ())
+ return;
+
+ ctxt->warn (make_unique<overlapping_buffers> (get_fndecl_for_call ()));
+}
+
} // namespace ana
#endif /* #if ENABLE_ANALYZER */
@@ -79,6 +79,11 @@ public:
bool include_terminator,
const svalue **out_sval) const;
+ void
+ complain_about_overlap (unsigned arg_idx_a,
+ unsigned arg_idx_b,
+ const svalue *num_bytes_read_sval) const;
+
private:
const gcall *m_call;
region_model *m_model;
@@ -534,8 +534,6 @@ kf_malloc::impl_call_pre (const call_details &cd) const
/* Handler for "memcpy" and "__builtin_memcpy",
"memmove", and "__builtin_memmove". */
-/* TODO: complain about overlapping src and dest for the memcpy
- variants. */
class kf_memcpy_memmove : public builtin_known_function
{
@@ -592,7 +590,22 @@ kf_memcpy_memmove::impl_call_pre (const call_details &cd) const
= model->deref_rvalue (src_ptr_sval, cd.get_arg_tree (1), cd.get_ctxt ());
cd.maybe_set_lhs (dest_ptr_sval);
+ /* Check for overlap. */
+ switch (m_variant)
+ {
+ case KF_MEMCPY:
+ case KF_MEMCPY_CHK:
+ cd.complain_about_overlap (0, 1, num_bytes_sval);
+ break;
+
+ case KF_MEMMOVE:
+ case KF_MEMMOVE_CHK:
+ /* It's OK for memmove's arguments to overlap. */
+ break;
+ default:
+ gcc_unreachable ();
+ }
model->copy_bytes (dest_reg,
src_reg, cd.get_arg_tree (1),
num_bytes_sval,
@@ -1221,6 +1234,7 @@ public:
}
cd.maybe_set_lhs (dest_sval);
+ cd.complain_about_overlap (0, 1, num_src_bytes_read_sval);
const region *offset_reg
= mgr->get_offset_region (dest_reg, NULL_TREE, dst_strlen_sval);
@@ -1276,6 +1290,7 @@ kf_strcpy::impl_call_pre (const call_details &cd) const
if (const svalue *num_bytes_read_sval
= cd.check_for_null_terminated_string_arg (1, true, &bytes_to_copy))
{
+ cd.complain_about_overlap (0, 1, num_bytes_read_sval);
model->write_bytes (dest_reg, num_bytes_read_sval, bytes_to_copy, ctxt);
}
else
new file mode 100644
@@ -0,0 +1,324 @@
+/* Symbolic offsets and ranges.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@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"
+#define INCLUDE_MEMORY
+#include "system.h"
+#include "coretypes.h"
+#include "tree.h"
+#include "diagnostic-core.h"
+#include "gimple-pretty-print.h"
+#include "function.h"
+#include "basic-block.h"
+#include "gimple.h"
+#include "gimple-iterator.h"
+#include "diagnostic-core.h"
+#include "graphviz.h"
+#include "options.h"
+#include "cgraph.h"
+#include "tree-dfa.h"
+#include "stringpool.h"
+#include "convert.h"
+#include "target.h"
+#include "fold-const.h"
+#include "tree-pretty-print.h"
+#include "bitmap.h"
+#include "analyzer/analyzer.h"
+#include "analyzer/analyzer-logging.h"
+#include "ordered-hash-map.h"
+#include "options.h"
+#include "analyzer/supergraph.h"
+#include "sbitmap.h"
+#include "analyzer/call-string.h"
+#include "analyzer/program-point.h"
+#include "analyzer/store.h"
+#include "analyzer/region-model.h"
+#include "analyzer/constraint-manager.h"
+#include "analyzer/analyzer-selftests.h"
+#include "analyzer/ranges.h"
+
+#if ENABLE_ANALYZER
+
+namespace ana {
+
+/* class symbolic_byte_offset. */
+
+symbolic_byte_offset::symbolic_byte_offset (int i, region_model_manager &mgr)
+: m_num_bytes_sval (mgr.get_or_create_int_cst (size_type_node, i))
+{
+}
+
+symbolic_byte_offset::symbolic_byte_offset (const svalue *num_bytes_sval)
+: m_num_bytes_sval (num_bytes_sval)
+{
+}
+
+symbolic_byte_offset::symbolic_byte_offset (region_offset offset,
+ region_model_manager &mgr)
+{
+ if (offset.concrete_p ())
+ {
+ bit_offset_t num_bits = offset.get_bit_offset ();
+ gcc_assert (num_bits % BITS_PER_UNIT == 0);
+ byte_offset_t num_bytes = num_bits / BITS_PER_UNIT;
+ m_num_bytes_sval = mgr.get_or_create_int_cst (size_type_node, num_bytes);
+ }
+ else
+ m_num_bytes_sval = offset.get_symbolic_byte_offset ();
+}
+
+void
+symbolic_byte_offset::dump_to_pp (pretty_printer *pp, bool simple) const
+{
+ pp_string (pp, "byte ");
+ m_num_bytes_sval->dump_to_pp (pp, simple);
+}
+
+void
+symbolic_byte_offset::dump (bool simple) const
+{
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = stderr;
+ dump_to_pp (&pp, simple);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+tree
+symbolic_byte_offset::maybe_get_constant () const
+{
+ return m_num_bytes_sval->maybe_get_constant ();
+}
+
+/* class symbolic_byte_range. */
+
+symbolic_byte_range::symbolic_byte_range (region_offset start,
+ const svalue *num_bytes,
+ region_model_manager &mgr)
+: m_start (start, mgr),
+ m_size (num_bytes)
+{
+}
+
+void
+symbolic_byte_range::dump_to_pp (pretty_printer *pp,
+ bool simple,
+ region_model_manager &mgr) const
+{
+ if (empty_p ())
+ {
+ pp_string (pp, "empty");
+ return;
+ }
+
+ if (tree size_cst = m_size.maybe_get_constant ())
+ if (integer_onep (size_cst))
+ {
+ pp_string (pp, "byte ");
+ m_start.get_svalue ()->dump_to_pp (pp, simple);
+ return;
+ }
+
+ pp_string (pp, "bytes ");
+ m_start.get_svalue ()->dump_to_pp (pp, simple);
+ pp_string (pp, " to ");
+ get_last_byte_offset (mgr).get_svalue ()->dump_to_pp (pp, simple);
+}
+
+void
+symbolic_byte_range::dump (bool simple, region_model_manager &mgr) const
+{
+ pretty_printer pp;
+ pp_format_decoder (&pp) = default_tree_printer;
+ pp_show_color (&pp) = pp_show_color (global_dc->printer);
+ pp.buffer->stream = stderr;
+ dump_to_pp (&pp, simple, mgr);
+ pp_newline (&pp);
+ pp_flush (&pp);
+}
+
+bool
+symbolic_byte_range::empty_p () const
+{
+ tree cst = m_size.maybe_get_constant ();
+ if (!cst)
+ return false;
+ return zerop (cst);
+}
+
+symbolic_byte_offset
+symbolic_byte_range::get_last_byte_offset (region_model_manager &mgr) const
+{
+ gcc_assert (!empty_p ());
+ const symbolic_byte_offset one (1, mgr);
+ return symbolic_byte_offset
+ (mgr.get_or_create_binop (size_type_node,
+ MINUS_EXPR,
+ get_next_byte_offset (mgr).get_svalue (),
+ one.get_svalue ()));
+}
+
+symbolic_byte_offset
+symbolic_byte_range::get_next_byte_offset (region_model_manager &mgr) const
+{
+ return symbolic_byte_offset (mgr.get_or_create_binop (size_type_node,
+ PLUS_EXPR,
+ m_start.get_svalue (),
+ m_size.get_svalue ()));
+}
+
+/* Attempt to determine if THIS range intersects OTHER,
+ using constraints from MODEL. */
+
+tristate
+symbolic_byte_range::intersection (const symbolic_byte_range &other,
+ const region_model &model) const
+{
+ /* For brevity, consider THIS to be "range A", and OTHER to be "range B". */
+
+ region_model_manager *mgr = model.get_manager ();
+
+ const svalue *first_sval_a = m_start.get_svalue ();
+ const svalue *first_sval_b = other.m_start.get_svalue ();
+ const svalue *last_sval_a = get_last_byte_offset (*mgr).get_svalue ();
+ const svalue *last_sval_b = other.get_last_byte_offset (*mgr).get_svalue ();
+
+ if (m_size.get_svalue ()->get_kind () == SK_UNKNOWN
+ || other.m_size.get_svalue ()->get_kind () == SK_UNKNOWN)
+ {
+ if (first_sval_a == first_sval_b)
+ return tristate::TS_TRUE;
+ else
+ return tristate::TS_UNKNOWN;
+ }
+
+ if (first_sval_a == first_sval_b)
+ return tristate::TS_TRUE;
+
+ /* Is B fully before A? */
+ tristate b_fully_before_a = model.eval_condition (last_sval_b,
+ LT_EXPR,
+ first_sval_a);
+ /* Is B fully after A? */
+ tristate b_fully_after_a = model.eval_condition (first_sval_b,
+ GT_EXPR,
+ last_sval_a);
+
+ if (b_fully_before_a.is_true ()
+ || b_fully_after_a.is_true ())
+ return tristate::TS_FALSE;
+
+ if (b_fully_before_a.is_unknown ()
+ || b_fully_after_a.is_unknown ())
+ return tristate::TS_UNKNOWN;
+
+ return tristate::TS_TRUE;
+}
+
+#if CHECKING_P
+
+namespace selftest {
+
+static void test_intersects (void)
+{
+ region_model_manager mgr;
+ region_model m (&mgr);
+
+ /* Test various concrete ranges. */
+ symbolic_byte_offset zero (0, mgr);
+ symbolic_byte_offset one (1, mgr);
+ symbolic_byte_offset five (5, mgr);
+ symbolic_byte_offset nine (9, mgr);
+ symbolic_byte_offset ten (10, mgr);
+
+ symbolic_byte_range r0_9 (zero, ten);
+ symbolic_byte_range r0 (zero, one);
+ symbolic_byte_range r5_9 (five, five);
+ symbolic_byte_range r9 (nine, one);
+ symbolic_byte_range r10 (ten, one);
+ symbolic_byte_range r10_19 (ten, ten);
+
+ ASSERT_EQ (r0_9.get_start_byte_offset (), zero);
+ ASSERT_EQ (r0_9.get_size_in_bytes (), ten);
+ ASSERT_EQ (r0_9.get_next_byte_offset (mgr), ten);
+ ASSERT_EQ (r0_9.get_last_byte_offset (mgr), nine);
+
+ ASSERT_EQ (r0_9.intersection (r0, m), tristate::TS_TRUE);
+ ASSERT_EQ (r0.intersection (r0_9, m), tristate::TS_TRUE);
+ ASSERT_EQ (r0_9.intersection (r9, m), tristate::TS_TRUE);
+ ASSERT_EQ (r9.intersection (r0_9, m), tristate::TS_TRUE);
+ ASSERT_EQ (r0_9.intersection (r10, m), tristate::TS_FALSE);
+ ASSERT_EQ (r10.intersection (r0_9, m), tristate::TS_FALSE);
+
+ ASSERT_EQ (r5_9.intersection (r0, m), tristate::TS_FALSE);
+ ASSERT_EQ (r0.intersection (r5_9, m), tristate::TS_FALSE);
+ ASSERT_EQ (r9.intersection (r5_9, m), tristate::TS_TRUE);
+ ASSERT_EQ (r10.intersection (r5_9, m), tristate::TS_FALSE);
+
+ /* Test various symbolic ranges. */
+ tree x = build_global_decl ("x", size_type_node);
+ const svalue *x_init_sval = m.get_rvalue (x, nullptr);
+ tree y = build_global_decl ("y", size_type_node);
+ const svalue *y_init_sval = m.get_rvalue (y, nullptr);
+
+ symbolic_byte_range r0_x_minus_1 (zero, x_init_sval);
+ symbolic_byte_range rx (x_init_sval, one);
+ symbolic_byte_range r0_y_minus_1 (zero, y_init_sval);
+ symbolic_byte_range ry (y_init_sval, one);
+ symbolic_byte_range rx_x_plus_y_minus_1 (x_init_sval, y_init_sval);
+
+ ASSERT_EQ (rx_x_plus_y_minus_1.get_start_byte_offset (), x_init_sval);
+ ASSERT_EQ (rx_x_plus_y_minus_1.get_size_in_bytes (), y_init_sval);
+ ASSERT_EQ
+ (rx_x_plus_y_minus_1.get_next_byte_offset (mgr).get_svalue ()->get_kind (),
+ SK_BINOP);
+ ASSERT_EQ
+ (rx_x_plus_y_minus_1.get_last_byte_offset (mgr).get_svalue ()->get_kind (),
+ SK_BINOP);
+
+ ASSERT_EQ (rx.intersection (ry, m), tristate::TS_UNKNOWN);
+ ASSERT_EQ (r0_x_minus_1.intersection (r0, m), tristate::TS_TRUE);
+#if 0
+ ASSERT_EQ (r0_x_minus_1.intersection (rx, m), tristate::TS_FALSE);
+ /* Fails (with UNKNOWN): b_fully_after_a is UNKNOWN, when it could
+ be TRUE: last of A is (x - 1), but it's not necessarily true that
+ X > (x - 1), for the case where x is (unsigned)0. */
+#endif
+ ASSERT_EQ (r0_x_minus_1.intersection (r0_y_minus_1, m), tristate::TS_TRUE);
+ // TODO: etc
+}
+
+/* Run all of the selftests within this file. */
+
+void
+analyzer_ranges_cc_tests ()
+{
+ test_intersects ();
+}
+
+} // namespace selftest
+
+#endif /* CHECKING_P */
+
+} // namespace ana
+
+#endif /* #if ENABLE_ANALYZER */
new file mode 100644
@@ -0,0 +1,96 @@
+/* Symbolic offsets and ranges.
+ Copyright (C) 2023 Free Software Foundation, Inc.
+ Contributed by David Malcolm <dmalcolm@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/>. */
+
+#ifndef GCC_ANALYZER_RANGES_H
+#define GCC_ANALYZER_RANGES_H
+
+namespace ana {
+
+/* Wrapper around an svalue for a value measured in bytes. */
+
+class symbolic_byte_offset
+{
+public:
+ explicit symbolic_byte_offset (int i, region_model_manager &mgr);
+ symbolic_byte_offset (const svalue *num_bytes_sval);
+ explicit symbolic_byte_offset (region_offset offset,
+ region_model_manager &mgr);
+
+ const svalue *get_svalue () const { return m_num_bytes_sval; }
+ tree maybe_get_constant () const;
+
+ void dump_to_pp (pretty_printer *pp, bool) const;
+ void dump (bool) const;
+
+ bool operator== (const symbolic_byte_offset &other) const
+ {
+ return m_num_bytes_sval == other.m_num_bytes_sval;
+ }
+
+private:
+ const svalue *m_num_bytes_sval;
+};
+
+/* A range of byte offsets, where both the start and size of the
+ range can be symbolic. */
+
+class symbolic_byte_range
+{
+public:
+ symbolic_byte_range (symbolic_byte_offset start,
+ symbolic_byte_offset size)
+ : m_start (start),
+ m_size (size)
+ {
+ }
+
+ symbolic_byte_range (region_offset start,
+ const svalue *num_bytes,
+ region_model_manager &mgr);
+
+ void dump_to_pp (pretty_printer *pp,
+ bool simple,
+ region_model_manager &mgr) const;
+ void dump (bool, region_model_manager &mgr) const;
+
+ bool empty_p () const;
+
+ symbolic_byte_offset get_start_byte_offset () const
+ {
+ return m_start;
+ }
+ symbolic_byte_offset get_last_byte_offset (region_model_manager &mgr) const;
+ symbolic_byte_offset get_size_in_bytes () const
+ {
+ return m_size;
+ }
+ symbolic_byte_offset get_next_byte_offset (region_model_manager &mgr) const;
+
+ tristate intersection (const symbolic_byte_range &other,
+ const region_model &model) const;
+
+private:
+ symbolic_byte_offset m_start;
+ symbolic_byte_offset m_size;
+};
+
+} // namespace ana
+
+#endif /* GCC_ANALYZER_RANGES_H */
@@ -474,6 +474,7 @@ Objective-C and Objective-C++ Dialects}.
-Wno-analyzer-null-argument
-Wno-analyzer-null-dereference
-Wno-analyzer-out-of-bounds
+-Wno-analyzer-overlapping-buffers
-Wno-analyzer-possible-null-argument
-Wno-analyzer-possible-null-dereference
-Wno-analyzer-putenv-of-auto-var
@@ -10319,6 +10320,7 @@ Enabling this option effectively enables the following warnings:
-Wanalyzer-null-argument
-Wanalyzer-null-dereference
-Wanalyzer-out-of-bounds
+-Wanalyzer-overlapping-buffers
-Wanalyzer-possible-null-argument
-Wanalyzer-possible-null-dereference
-Wanalyzer-putenv-of-auto-var
@@ -10664,6 +10666,24 @@ involved, the direction of the access (read vs write), and, in some
cases, the values of data involved. This diagram can be suppressed
using @option{-fdiagnostics-text-art-charset=none}.
+@opindex Wanalyzer-overlapping-buffers
+@opindex Wno-analyzer-overlapping-buffers
+@item -Wno-analyzer-overlapping-buffers
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-overlapping-buffers} to disable it.
+
+This diagnostic warns for paths through the code in which overlapping
+buffers are passed to an API for which the behavior on such buffers
+is undefined.
+
+Specifically, the diagnostic occurs on calls to the following functions
+@itemize @bullet
+@item @code{memcpy}
+@item @code{strcat}
+@item @code{strcpy}
+@end itemize
+for cases where the buffers are known to overlap.
+
@opindex Wanalyzer-possible-null-argument
@opindex Wno-analyzer-possible-null-argument
@item -Wno-analyzer-possible-null-argument
new file mode 100644
@@ -0,0 +1,147 @@
+/* Test of -Wanalyzer-overlapping-buffers. */
+
+#include <string.h>
+
+/* Use noinline functions to hide these calls from the optimizer, to avoid
+ undefined behavior being optimized away to GIMPLE_NOP before the analyzer
+ sees it. */
+
+char * __attribute__((noinline))
+call_strcat_symbolic_1 (char *dest, const char *src)
+{
+ return strcat (dest, src); /* { dg-warning "overlapping buffers" } */
+}
+
+void test_strcat_symbolic_1 (char *p)
+{
+ call_strcat_symbolic_1 (p, p);
+}
+
+char * __attribute__((noinline))
+call_strcpy_symbolic_1 (char *dest, const char *src)
+{
+ return strcpy (dest, src); /* { dg-warning "overlapping buffers" } */
+}
+
+void test_strcpy_symbolic_1 (char *p)
+{
+ call_strcpy_symbolic_1 (p, p);
+}
+
+void * __attribute__((noinline))
+call_memcpy_concrete_1 (void *dest, const void *src, size_t n)
+{
+ return memcpy (dest, src, n); /* { dg-warning "overlapping buffers" } */
+}
+
+void test_memcpy_concrete_1 (void *p)
+{
+ call_memcpy_concrete_1 (p, p, 10);
+}
+
+void * __attribute__((noinline))
+call_memcpy_symbolic_1 (void *dest, const void *src, size_t n)
+{
+ return memcpy (dest, src, n); /* { dg-warning "overlapping buffers" } */
+}
+
+void test_memcpy_symbolic_1 (void *p, size_t n)
+{
+ call_memcpy_symbolic_1 (p, p, n);
+}
+
+/* Intersecting vs non-intersecting parts of the same buffer. */
+
+void * __attribute__((noinline))
+call_memcpy_nonintersecting_concrete_1 (void *dest, const void *src, size_t n)
+{
+ return memcpy (dest, src, n); /* { dg-bogus "overlapping buffers" } */
+}
+
+void test_memcpy_nonintersecting_concrete_1 (char *p)
+{
+ call_memcpy_nonintersecting_concrete_1 (p, p + 10, 10);
+}
+
+void * __attribute__((noinline))
+call_memcpy_nonintersecting_concrete_2 (void *dest, const void *src, size_t n)
+{
+ return memcpy (dest, src, n); /* { dg-bogus "overlapping buffers" } */
+}
+
+void test_memcpy_nonintersecting_concrete_2 (char *p)
+{
+ call_memcpy_nonintersecting_concrete_2 (p + 10, p, 10);
+}
+
+void * __attribute__((noinline))
+call_memcpy_intersecting_concrete_1 (void *dest, const void *src, size_t n)
+{
+ return memcpy (dest, src, n); /* { dg-warning "overlapping buffers" } */
+}
+
+void test_memcpy_intersecting_concrete_1 (char *p)
+{
+ call_memcpy_intersecting_concrete_1 (p, p + 9, 10);
+}
+
+void * __attribute__((noinline))
+call_memcpy_intersecting_concrete_2 (void *dest, const void *src, size_t n)
+{
+ return memcpy (dest, src, n); /* { dg-warning "overlapping buffers" } */
+}
+
+void test_memcpy_intersecting_concrete_2 (char *p)
+{
+ call_memcpy_intersecting_concrete_2 (p + 9, p, 10);
+}
+
+void * __attribute__((noinline))
+call_memcpy_intersecting_symbolic_1 (void *dest, const void *src, size_t n)
+{
+ return memcpy (dest, src, n); /* { dg-warning "overlapping buffers" "" { xfail *-*-* } } */
+ // TODO(xfail)
+}
+
+void test_memcpy_intersecting_symbolic_1 (char *p, size_t n)
+{
+ call_memcpy_intersecting_symbolic_1 (p, p + 1, n);
+}
+
+void * __attribute__((noinline))
+call_memcpy_nonintersecting_symbolic_1 (void *dest, const void *src, size_t n)
+{
+ return memcpy (dest, src, n); /* { dg-bogus "overlapping buffers" } */
+}
+
+void test_memcpy_nonintersecting_symbolic_1 (char *p, size_t n)
+{
+ call_memcpy_nonintersecting_symbolic_1 (p, p + n, n);
+}
+
+void * __attribute__((noinline))
+call_memcpy_nonintersecting_symbolic_2 (void *dest, const void *src, size_t n)
+{
+ return memcpy (dest, src, n); /* { dg-bogus "overlapping buffers" } */
+}
+
+void test_memcpy_nonintersecting_symbolic_2 (char *p, size_t n)
+{
+ call_memcpy_nonintersecting_symbolic_2 (p + n, p, n);
+}
+/* It's OK for memmove's arguments to overlap. */
+
+void * __attribute__((noinline))
+call_memmove_symbolic_1 (void *dest, const void *src, size_t n)
+{
+ return memmove (dest, src, n); /* { dg-bogus "overlapping buffers" } */
+}
+
+void test_memmove_symbolic_1 (void *p, size_t n)
+{
+ call_memmove_symbolic_1 (p, p, n);
+}
+
+/* TODO:
+ - strncpy
+ */