https://gcc.gnu.org/g:13dcaf1bb6d4f15665a47b14ac0c12cf454e38a2

commit r15-1107-g13dcaf1bb6d4f15665a47b14ac0c12cf454e38a2
Author: David Malcolm <dmalc...@redhat.com>
Date:   Fri Jun 7 16:14:28 2024 -0400

    analyzer: new warning: -Wanalyzer-undefined-behavior-ptrdiff (PR 
analyzer/105892)
    
    Add a new warning to complain about pointer subtraction involving
    different chunks of memory.
    
    For example, given:
    
      #include <stddef.h>
    
      int arr[42];
      int sentinel;
    
      ptrdiff_t
      test_invalid_calc_of_array_size (void)
      {
        return &sentinel - arr;
      }
    
    this emits:
    
    demo.c: In function ‘test_invalid_calc_of_array_size’:
    demo.c:9:20: warning: undefined behavior when subtracting pointers 
[CWE-469] [-Wanalyzer-undefined-behavior-ptrdiff]
        9 |   return &sentinel - arr;
          |                    ^
      events 1-2
        │
        │    3 | int arr[42];
        │      |     ~~~
        │      |     |
        │      |     (2) underlying object for right-hand side of subtraction 
created here
        │    4 | int sentinel;
        │      |     ^~~~~~~~
        │      |     |
        │      |     (1) underlying object for left-hand side of subtraction 
created here
        │
        └──> ‘test_invalid_calc_of_array_size’: event 3
               │
               │    9 |   return &sentinel - arr;
               │      |                    ^
               │      |                    |
               │      |                    (3) ⚠️  subtraction of pointers has 
undefined behavior if they do not point into the same array object
               │
    
    gcc/analyzer/ChangeLog:
            PR analyzer/105892
            * analyzer.opt (Wanalyzer-undefined-behavior-ptrdiff): New option.
            * analyzer.opt.urls: Regenerate.
            * region-model.cc (class undefined_ptrdiff_diagnostic): New.
            (check_for_invalid_ptrdiff): New.
            (region_model::get_gassign_result): Call it for POINTER_DIFF_EXPR.
    
    gcc/ChangeLog:
            * doc/invoke.texi: Add -Wanalyzer-undefined-behavior-ptrdiff.
    
    gcc/testsuite/ChangeLog:
            PR analyzer/105892
            * c-c++-common/analyzer/out-of-bounds-pr110387.c: Add
            expected warnings about pointer subtraction.
            * c-c++-common/analyzer/ptr-subtraction-1.c: New test.
            * c-c++-common/analyzer/ptr-subtraction-CWE-469-example.c: New test.
    
    Signed-off-by: David Malcolm <dmalc...@redhat.com>

Diff:
---
 gcc/analyzer/analyzer.opt                          |   4 +
 gcc/analyzer/analyzer.opt.urls                     |   3 +
 gcc/analyzer/region-model.cc                       | 141 +++++++++++++++++++++
 gcc/doc/invoke.texi                                |  16 +++
 .../c-c++-common/analyzer/out-of-bounds-pr110387.c |   4 +-
 .../c-c++-common/analyzer/ptr-subtraction-1.c      |  46 +++++++
 .../analyzer/ptr-subtraction-CWE-469-example.c     |  81 ++++++++++++
 7 files changed, 293 insertions(+), 2 deletions(-)

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index bbf2ba670d8..5335f7e1999 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -222,6 +222,10 @@ Wanalyzer-tainted-size
 Common Var(warn_analyzer_tainted_size) Init(1) Warning
 Warn about code paths in which an unsanitized value is used as a size.
 
+Wanalyzer-undefined-behavior-ptrdiff
+Common Var(warn_analyzer_undefined_behavior_ptrdiff) Init(1) Warning
+Warn about code paths in which pointer subtraction involves undefined behavior.
+
 Wanalyzer-undefined-behavior-strtok
 Common Var(warn_analyzer_undefined_behavior_strtok) Init(1) Warning
 Warn about code paths in which a call is made to strtok with undefined 
behavior.
diff --git a/gcc/analyzer/analyzer.opt.urls b/gcc/analyzer/analyzer.opt.urls
index 5fcab720582..18a0d6926de 100644
--- a/gcc/analyzer/analyzer.opt.urls
+++ b/gcc/analyzer/analyzer.opt.urls
@@ -114,6 +114,9 @@ 
UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-tainted-offset)
 Wanalyzer-tainted-size
 UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-tainted-size)
 
+Wanalyzer-undefined-behavior-ptrdiff
+UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-undefined-behavior-ptrdiff)
+
 Wanalyzer-undefined-behavior-strtok
 
UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-undefined-behavior-strtok)
 
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index d142d851a26..d6bcb8630cd 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -841,6 +841,144 @@ private:
   tree m_count_cst;
 };
 
+/* A subclass of pending_diagnostic for complaining about pointer
+   subtractions involving unrelated buffers.  */
+
+class undefined_ptrdiff_diagnostic
+: public pending_diagnostic_subclass<undefined_ptrdiff_diagnostic>
+{
+public:
+  /* Region_creation_event subclass to give a custom wording when
+     talking about creation of buffers for LHS and RHS of the
+     subtraction.  */
+  class ptrdiff_region_creation_event : public region_creation_event
+  {
+  public:
+    ptrdiff_region_creation_event (const event_loc_info &loc_info,
+                                  bool is_lhs)
+    : region_creation_event (loc_info),
+      m_is_lhs (is_lhs)
+    {
+    }
+
+    label_text get_desc (bool) const
+    {
+      if (m_is_lhs)
+       return label_text::borrow ("underlying object for left-hand side"
+                                  " of subtraction created here");
+      else
+       return label_text::borrow ("underlying object for right-hand side"
+                                  " of subtraction created here");
+    }
+
+  private:
+    bool m_is_lhs;
+  };
+
+  undefined_ptrdiff_diagnostic (const gassign *assign,
+                               const svalue *sval_a,
+                               const svalue *sval_b,
+                               const region *base_reg_a,
+                               const region *base_reg_b)
+  : m_assign (assign),
+    m_sval_a (sval_a),
+    m_sval_b (sval_b),
+    m_base_reg_a (base_reg_a),
+    m_base_reg_b (base_reg_b)
+  {
+    gcc_assert (m_base_reg_a != m_base_reg_b);
+  }
+
+  const char *get_kind () const final override
+  {
+    return "undefined_ptrdiff_diagnostic";
+  }
+
+  bool operator== (const undefined_ptrdiff_diagnostic &other) const
+  {
+    return (m_assign == other.m_assign
+           && m_sval_a == other.m_sval_a
+           && m_sval_b == other.m_sval_b
+           && m_base_reg_a == other.m_base_reg_a
+           && m_base_reg_b == other.m_base_reg_b);
+  }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_undefined_behavior_ptrdiff;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
+  {
+    /* CWE-469: Use of Pointer Subtraction to Determine Size.  */
+    ctxt.add_cwe (469);
+    return ctxt.warn ("undefined behavior when subtracting pointers");
+  }
+
+  void add_region_creation_events (const region *reg,
+                                  tree /*capacity*/,
+                                  const event_loc_info &loc_info,
+                                  checker_path &emission_path) final override
+  {
+    if (reg == m_base_reg_a)
+      emission_path.add_event
+       (make_unique<ptrdiff_region_creation_event> (loc_info, true));
+    else if (reg == m_base_reg_b)
+      emission_path.add_event
+       (make_unique<ptrdiff_region_creation_event> (loc_info, false));
+  }
+
+  label_text describe_final_event (const evdesc::final_event &ev) final 
override
+  {
+    return ev.formatted_print
+      ("subtraction of pointers has undefined behavior if"
+       " they do not point into the same array object");
+  }
+
+  void mark_interesting_stuff (interesting_t *interesting) final override
+  {
+    interesting->add_region_creation (m_base_reg_a);
+    interesting->add_region_creation (m_base_reg_b);
+  }
+
+private:
+  const gassign *m_assign;
+  const svalue *m_sval_a;
+  const svalue *m_sval_b;
+  const region *m_base_reg_a;
+  const region *m_base_reg_b;
+};
+
+/* Check the pointer subtraction SVAL_A - SVAL_B at ASSIGN and add
+   a warning to CTXT if they're not within the same base region.  */
+
+static void
+check_for_invalid_ptrdiff (const gassign *assign,
+                          region_model_context &ctxt,
+                          const svalue *sval_a, const svalue *sval_b)
+{
+  const region *base_reg_a = sval_a->maybe_get_deref_base_region ();
+  if (!base_reg_a)
+    return;
+  const region *base_reg_b = sval_b->maybe_get_deref_base_region ();
+  if (!base_reg_b)
+    return;
+
+  if (base_reg_a == base_reg_b)
+    return;
+
+  if (base_reg_a->get_kind () == RK_SYMBOLIC)
+    return;
+  if (base_reg_b->get_kind () == RK_SYMBOLIC)
+    return;
+
+  ctxt.warn (make_unique<undefined_ptrdiff_diagnostic> (assign,
+                                                       sval_a,
+                                                       sval_b,
+                                                       base_reg_a,
+                                                       base_reg_b));
+}
+
 /* If ASSIGN is a stmt that can be modelled via
      set_value (lhs_reg, SVALUE, CTXT)
    for some SVALUE, get the SVALUE.
@@ -897,6 +1035,9 @@ region_model::get_gassign_result (const gassign *assign,
 
        // TODO: perhaps fold to zero if they're known to be equal?
 
+       if (ctxt)
+         check_for_invalid_ptrdiff (assign, *ctxt, rhs1_sval, rhs2_sval);
+
        const svalue *sval_binop
          = m_mgr->get_or_create_binop (TREE_TYPE (lhs), op,
                                        rhs1_sval, rhs2_sval);
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index ca2591ce2c3..a0b37564646 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -496,6 +496,7 @@ Objective-C and Objective-C++ Dialects}.
 -Wno-analyzer-tainted-size
 -Wanalyzer-symbol-too-complex
 -Wanalyzer-too-complex
+-Wno-analyzer-undefined-behavior-ptrdiff
 -Wno-analyzer-undefined-behavior-strtok
 -Wno-analyzer-unsafe-call-within-signal-handler
 -Wno-analyzer-use-after-free
@@ -10766,6 +10767,7 @@ Enabling this option effectively enables the following 
warnings:
 -Wanalyzer-tainted-divisor
 -Wanalyzer-tainted-offset
 -Wanalyzer-tainted-size
+-Wanalyzer-undefined-behavior-ptrdiff
 -Wanalyzer-undefined-behavior-strtok
 -Wanalyzer-unsafe-call-within-signal-handler
 -Wanalyzer-use-after-free
@@ -11416,6 +11418,20 @@ attacker could inject an out-of-bounds access.
 
 See @uref{https://cwe.mitre.org/data/definitions/129.html, CWE-129: Improper 
Validation of Array Index}.
 
+@opindex Wanalyzer-undefined-behavior-ptrdiff
+@opindex Wno-analyzer-undefined-behavior-ptrdiff
+@item -Wno-analyzer-undefined-behavior-ptrdiff
+This warning requires @option{-fanalyzer}, which enables it; use
+@option{-Wno-analyzer-undefined-behavior-ptrdiff} to disable it.
+
+This diagnostic warns for paths through the code in which a pointer
+subtraction occurs where the pointers refer to different chunks of
+memory.  Such code relies on undefined behavior, as pointer subtraction
+is only defined for cases where both pointers point to within (or just
+after) the same array.
+
+See @uref{https://cwe.mitre.org/data/definitions/469.html, CWE-469: Use of 
Pointer Subtraction to Determine Size}.
+
 @opindex Wanalyzer-undefined-behavior-strtok
 @opindex Wno-analyzer-undefined-behavior-strtok
 @item -Wno-analyzer-undefined-behavior-strtok
diff --git a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-pr110387.c 
b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-pr110387.c
index a046659c83e..b4454805eb5 100644
--- a/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-pr110387.c
+++ b/gcc/testsuite/c-c++-common/analyzer/out-of-bounds-pr110387.c
@@ -12,8 +12,8 @@ _S_copy (long __n)
 void
 _M_construct ()
 {
-  x = &c - &b;
+  x = &c - &b; /* { dg-warning "undefined behavior when subtracting pointers" 
} */
   unsigned long __dnew = x;
   if (__dnew > 1)
-    _S_copy (&c - &b);
+    _S_copy (&c - &b); /* { dg-warning "undefined behavior when subtracting 
pointers" } */
 }
diff --git a/gcc/testsuite/c-c++-common/analyzer/ptr-subtraction-1.c 
b/gcc/testsuite/c-c++-common/analyzer/ptr-subtraction-1.c
new file mode 100644
index 00000000000..73684c115fc
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/ptr-subtraction-1.c
@@ -0,0 +1,46 @@
+#include <stddef.h>
+
+ptrdiff_t
+test_invalid_sub_addrs_of_locals (void)
+{
+  int a; /* { dg-message "underlying object for left-hand side of subtraction 
created here" } */
+  int b; /* { dg-message "underlying object for right-hand side of subtraction 
created here" } */
+  return &a - &b; /* { dg-warning "undefined behavior when subtracting 
pointers \\\[CWE-469\\\] \\\[-Wanalyzer-undefined-behavior-ptrdiff\\\]" } */
+  /* { dg-message "subtraction of pointers has undefined behavior if they do 
not point into the same array object" "final event" { target *-*-* } .-1 } */
+}
+
+ptrdiff_t
+test_valid_sub_addrs_within_array (void)
+{
+  int a[10];
+  return &a[7] - &a[3];
+}
+
+ptrdiff_t
+test_invalid_sub_addrs_within_arrays (void)
+{
+  int a[10]; /* { dg-message "left-hand side" } */
+  int b[10]; /* { dg-message "right-hand side" } */
+  return &a[7] - &b[3]; /* { dg-warning "undefined behavior when subtracting 
pointers" } */
+}
+
+
+ptrdiff_t
+test_invalid_sub_addrs_between_heap_allocs (size_t n)
+{
+  char *p = (char *)__builtin_malloc (n); /* { dg-message "left-hand side" } */
+  char *q = (char *)__builtin_malloc (n); /* { dg-message "right-hand side" } 
*/
+  ptrdiff_t d = p - q; /* { dg-warning "undefined behavior when subtracting 
pointers" } */
+  __builtin_free (p);
+  __builtin_free (q);
+  return d;
+}
+
+int arr[42]; /* { dg-message "right-hand side" } */
+int sentinel; /* { dg-message "left-hand side" } */
+
+ptrdiff_t
+test_invalid_calc_of_array_size (void)
+{
+  return &sentinel - arr; /* { dg-warning "undefined behavior when subtracting 
pointers" } */
+}
diff --git 
a/gcc/testsuite/c-c++-common/analyzer/ptr-subtraction-CWE-469-example.c 
b/gcc/testsuite/c-c++-common/analyzer/ptr-subtraction-CWE-469-example.c
new file mode 100644
index 00000000000..7f8bd342acb
--- /dev/null
+++ b/gcc/testsuite/c-c++-common/analyzer/ptr-subtraction-CWE-469-example.c
@@ -0,0 +1,81 @@
+/* Example heavily adapted from https://cwe.mitre.org/data/definitions/469.html
+   which states "Copyright © 2006–2024, The MITRE Corporation. CWE, CWSS, 
CWRAF, and the CWE logo are trademarks of The MITRE Corporation."
+   and which has this on:
+     https://cwe.mitre.org/about/termsofuse.html
+
+     Terms of Use
+
+     CWE™ is free to use by any organization or individual for any research, 
development, and/or commercial purposes, per these CWE Terms of Use. 
Accordingly, The MITRE Corporation hereby grants you a non-exclusive, 
royalty-free license to use CWE for research, development, and commercial 
purposes. Any copy you make for such purposes is authorized on the condition 
that you reproduce MITRE’s copyright designation and this license in any such 
copy. CWE is a trademark of The MITRE Corporation. Please contact 
c...@mitre.org if you require further clarification on this issue.
+
+     DISCLAIMERS
+
+     By accessing information through this site you (as “the user”) hereby 
agrees the site and the information is provided on an “as is” basis only 
without warranty of any kind, express or implied, including but not limited to 
implied warranties of merchantability, availability, accuracy, noninfringement, 
or fitness for a particular purpose. Use of this site and the information is at 
the user’s own risk. The user shall comply with all applicable laws, rules, and 
regulations, and the data source’s restrictions, when using the site.
+
+     By contributing information to this site you (as “the contributor”) 
hereby represents and warrants the contributor has obtained all necessary 
permissions from copyright holders and other third parties to allow the 
contributor to contribute, and this site to host and display, the information 
and any such contribution, hosting, and displaying will not violate any law, 
rule, or regulation. Additionally, the contributor hereby grants all users of 
such information a perpetual, worldwide, non-exclusive, no-charge, 
royalty-free, irrevocable license to reproduce, prepare derivative works of, 
publicly display, publicly perform, sublicense, and distribute such information 
and all derivative works.
+
+     The MITRE Corporation expressly disclaims any liability for any damages 
arising from the contributor’s contribution of such information, the user’s use 
of the site or such information, and The MITRE Corporation’s hosting the tool 
and displaying the information. The foregoing disclaimer specifically includes 
but is not limited to general, consequential, indirect, incidental, exemplary, 
or special or punitive damages (including but not limited to loss of income, 
program interruption, loss of information, or other pecuniary loss) arising out 
of use of this information, no matter the cause of action, even if The MITRE 
Corporation has been advised of the possibility of such damages.  */
+
+/* We need this for the issue to be found, or the loop analysis
+   is too simplistic.  */
+/* { dg-additional-options "-fno-analyzer-state-merge" } */
+/* { dg-additional-options "-Wno-analyzer-too-complex" } */
+/* { dg-additional-options "-Wno-analyzer-symbol-too-complex" } */
+
+#include <stddef.h>
+
+struct node {
+  int data;
+  struct node* next;
+};
+
+/* The example fails to initialize "tail" for the head == NULL case.  */
+
+int example_1_with_uninit (struct node* head) {
+  struct node* current = head;
+  struct node* tail;
+  while (current != NULL) {
+    tail = current;
+    current = current->next;
+  }
+  return tail - head; /* { dg-warning "use of uninitialized value 'tail'" } */
+}
+
+int example_1_bad (struct node* head) {
+  struct node* current = head;
+  struct node* tail = head; /* initialization added */
+  while (current != NULL) {
+    tail = current;
+    current = current->next;
+  }
+  return tail - head; /* { dg-warning "undefined behavior when subtracting 
pointers" } */
+}
+
+int example_1_good (struct node* head) {
+  struct node* current = head;
+  int count = 0;
+  while (current != NULL) {
+    count++;
+    current = current->next;
+  }
+  return count;
+}
+
+/* We need to add usage of the function to detect the issue.  */
+
+int usage_of_example_1_bad (void)
+{
+  struct node p; /* { dg-message "right-hand side" } */
+  struct node q; /* { dg-message "left-hand side" } */
+  p.next = &q;
+  q.next = NULL;
+  return example_1_bad (&p);
+}
+
+int usage_of_example_1_good (void)
+{
+  struct node p;
+  struct node q;
+  p.next = &q;
+  q.next = NULL;
+  return example_1_good (&p);
+}

Reply via email to