[pushed] analyzer: new warning: -Wanalyzer-overlapping-buffers [PR99860]

Message ID 20230829221757.3870381-1-dmalcolm@redhat.com
State Unresolved
Headers
Series [pushed] analyzer: new warning: -Wanalyzer-overlapping-buffers [PR99860] |

Checks

Context Check Description
snail/gcc-patch-check warning Git am fail log

Commit Message

David Malcolm Aug. 29, 2023, 10:17 p.m. UTC
  Successfully bootstrapped & regrtested on x86_64-pc-linux-gnu.
Pushed to trunk as r14-3556-g034d99e81484fb.

gcc/ChangeLog:
	PR analyzer/99860
	* Makefile.in (ANALYZER_OBJS): Add analyzer/ranges.o.

gcc/analyzer/ChangeLog:
	PR analyzer/99860
	* analyzer-selftests.cc (selftest::run_analyzer_selftests): Call
	selftest::analyzer_ranges_cc_tests.
	* analyzer-selftests.h (selftest::run_analyzer_selftests): New
	decl.
	* analyzer.opt (Wanalyzer-overlapping-buffers): New option.
	* call-details.cc: Include "analyzer/ranges.h" and "make-unique.h".
	(class overlapping_buffers): New.
	(call_details::complain_about_overlap): New.
	* call-details.h (call_details::complain_about_overlap): New decl.
	* kf.cc (kf_memcpy_memmove::impl_call_pre): Call
	cd.complain_about_overlap for memcpy and memcpy_chk.
	(kf_strcat::impl_call_pre): Call cd.complain_about_overlap.
	(kf_strcpy::impl_call_pre): Likewise.
	* ranges.cc: New file.
	* ranges.h: New file.

gcc/ChangeLog:
	PR analyzer/99860
	* doc/invoke.texi: Add -Wanalyzer-overlapping-buffers.

gcc/testsuite/ChangeLog:
	PR analyzer/99860
	* c-c++-common/analyzer/overlapping-buffers.c: New test.
---
 gcc/Makefile.in                               |   1 +
 gcc/analyzer/analyzer-selftests.cc            |   1 +
 gcc/analyzer/analyzer-selftests.h             |   1 +
 gcc/analyzer/analyzer.opt                     |   4 +
 gcc/analyzer/call-details.cc                  | 106 ++++++
 gcc/analyzer/call-details.h                   |   5 +
 gcc/analyzer/kf.cc                            |  19 +-
 gcc/analyzer/ranges.cc                        | 324 ++++++++++++++++++
 gcc/analyzer/ranges.h                         |  96 ++++++
 gcc/doc/invoke.texi                           |  20 ++
 .../analyzer/overlapping-buffers.c            | 147 ++++++++
 11 files changed, 722 insertions(+), 2 deletions(-)
 create mode 100644 gcc/analyzer/ranges.cc
 create mode 100644 gcc/analyzer/ranges.h
 create mode 100644 gcc/testsuite/c-c++-common/analyzer/overlapping-buffers.c
  

Patch

diff --git a/gcc/Makefile.in b/gcc/Makefile.in
index 78779546459f..5930b52462aa 100644
--- a/gcc/Makefile.in
+++ b/gcc/Makefile.in
@@ -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 \
diff --git a/gcc/analyzer/analyzer-selftests.cc b/gcc/analyzer/analyzer-selftests.cc
index 63b8cdfa1369..d06b4c374430 100644
--- a/gcc/analyzer/analyzer-selftests.cc
+++ b/gcc/analyzer/analyzer-selftests.cc
@@ -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 ();
diff --git a/gcc/analyzer/analyzer-selftests.h b/gcc/analyzer/analyzer-selftests.h
index d848ed9bc941..de494bfceae8 100644
--- a/gcc/analyzer/analyzer-selftests.h
+++ b/gcc/analyzer/analyzer-selftests.h
@@ -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 ();
diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 7917473d1223..25df89d9c06b 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -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.
diff --git a/gcc/analyzer/call-details.cc b/gcc/analyzer/call-details.cc
index ce1f859c9996..66fb0fe871e2 100644
--- a/gcc/analyzer/call-details.cc
+++ b/gcc/analyzer/call-details.cc
@@ -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 */
diff --git a/gcc/analyzer/call-details.h b/gcc/analyzer/call-details.h
index ae528e4ab116..57b9d6e40ab1 100644
--- a/gcc/analyzer/call-details.h
+++ b/gcc/analyzer/call-details.h
@@ -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;
diff --git a/gcc/analyzer/kf.cc b/gcc/analyzer/kf.cc
index 37792aed909c..219421005c16 100644
--- a/gcc/analyzer/kf.cc
+++ b/gcc/analyzer/kf.cc
@@ -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
diff --git a/gcc/analyzer/ranges.cc b/gcc/analyzer/ranges.cc
new file mode 100644
index 000000000000..8b1613e6b204
--- /dev/null
+++ b/gcc/analyzer/ranges.cc
@@ -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 */
diff --git a/gcc/analyzer/ranges.h b/gcc/analyzer/ranges.h
new file mode 100644
index 000000000000..cc72469a9023
--- /dev/null
+++ b/gcc/analyzer/ranges.h
@@ -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 */
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index a32dabf0405f..703aaaed13c6 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -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
diff --git a/gcc/testsuite/c-c++-common/analyzer/overlapping-buffers.c b/gcc/testsuite/c-c++-common/analyzer/overlapping-buffers.c
new file mode 100644
index 000000000000..5808b3304cfc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/overlapping-buffers.c
@@ -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
+ */