From ea3c9cfd384a2c2221cedb1bea75f98fa730ae43 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 24 Feb 2026 18:47:35 -0500
Subject: [PATCH 20/30] FIXME: WIP: add -Wanalyzer-div-by-zero (PR
 analyzer/124217)

---
 gcc/analyzer/analyzer.opt                     |  4 ++
 gcc/analyzer/analyzer.opt.urls                |  3 +
 gcc/analyzer/region-model.cc                  | 72 +++++++++++++++----
 gcc/doc/invoke.texi                           | 13 ++++
 .../c-c++-common/analyzer/divide-by-zero-1.c  |  4 +-
 .../analyzer/divide-by-zero-pr124195-2.c      |  3 +-
 .../analyzer/divide-by-zero-pr124195-1.c      |  2 +-
 7 files changed, 83 insertions(+), 18 deletions(-)

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 3c5dd0849c6..1e55003dd2e 100644
--- a/gcc/analyzer/analyzer.opt
+++ b/gcc/analyzer/analyzer.opt
@@ -78,6 +78,10 @@ Wanalyzer-deref-before-check
 Common Var(warn_analyzer_deref_before_check) Init(1) Warning
 Warn about code paths in which a pointer is checked for NULL after it has already been dereferenced.
 
+Wanalyzer-div-by-zero
+Common Var(warn_analyzer_div_by_zero) Init(1) Warning
+Warn about code paths which attempt integer division by zero.
+
 Wanalyzer-double-fclose
 Common Var(warn_analyzer_double_fclose) Init(1) Warning
 Warn about code paths in which a stdio FILE can be closed more than once.
diff --git a/gcc/analyzer/analyzer.opt.urls b/gcc/analyzer/analyzer.opt.urls
index 1a698f9c6d9..a0789445d30 100644
--- a/gcc/analyzer/analyzer.opt.urls
+++ b/gcc/analyzer/analyzer.opt.urls
@@ -6,6 +6,9 @@ UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-allocation-size)
 Wanalyzer-deref-before-check
 UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-deref-before-check)
 
+Wanalyzer-div-by-zero
+UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-div-by-zero)
+
 Wanalyzer-double-fclose
 UrlSuffix(gcc/Static-Analyzer-Options.html#index-Wanalyzer-double-fclose)
 
diff --git a/gcc/analyzer/region-model.cc b/gcc/analyzer/region-model.cc
index a85c6958838..df9d5b5b86e 100644
--- a/gcc/analyzer/region-model.cc
+++ b/gcc/analyzer/region-model.cc
@@ -1133,6 +1133,46 @@ private:
   const region *m_base_reg_b;
 };
 
+class div_by_zero_diagnostic
+: public pending_diagnostic_subclass<div_by_zero_diagnostic>
+{
+public:
+  div_by_zero_diagnostic (const gassign *assign)
+  : m_assign (assign)
+  {}
+
+  const char *get_kind () const final override
+  {
+    return "div_by_zero_diagnostic";
+  }
+
+  bool operator== (const div_by_zero_diagnostic &other) const
+  {
+    return m_assign == other.m_assign;
+  }
+
+  int get_controlling_option () const final override
+  {
+    return OPT_Wanalyzer_div_by_zero;
+  }
+
+  bool emit (diagnostic_emission_context &ctxt) final override
+  {
+    return ctxt.warn ("division by zero");
+  }
+
+  bool
+  describe_final_event (pretty_printer &pp,
+			const evdesc::final_event &) final override
+  {
+    pp_printf (&pp, "division by zero");
+    return true;
+  }
+
+private:
+  const gassign *m_assign;
+};
+
 /* 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.  */
 
@@ -1353,23 +1393,27 @@ region_model::get_gassign_result (const gassign *assign,
 		}
 	  }
 
-	if (ctxt
-	    && (op == TRUNC_DIV_EXPR
-		|| op == CEIL_DIV_EXPR
-		|| op == FLOOR_DIV_EXPR
-		|| op == ROUND_DIV_EXPR
-		|| op == TRUNC_MOD_EXPR
-		|| op == CEIL_MOD_EXPR
-		|| op == FLOOR_MOD_EXPR
-		|| op == ROUND_MOD_EXPR
-		|| op == RDIV_EXPR
-		|| op == EXACT_DIV_EXPR))
-	  {
+	if (op == TRUNC_DIV_EXPR
+	    || op == CEIL_DIV_EXPR
+	    || op == FLOOR_DIV_EXPR
+	    || op == ROUND_DIV_EXPR
+	    || op == TRUNC_MOD_EXPR
+	    || op == CEIL_MOD_EXPR
+	    || op == FLOOR_MOD_EXPR
+	    || op == ROUND_MOD_EXPR
+	    || op == RDIV_EXPR
+	    || op == EXACT_DIV_EXPR)
+	{
 	    if (const tree rhs2_cst = rhs2_sval->maybe_get_constant ())
+	      // FIXME: only on integer division
 	      if (zerop (rhs2_cst))
 		{
-		  /* Ideally we should issue a warning here;
-		     see PR analyzer/124217.  */
+		  if (ctxt)
+		    {
+		      ctxt->warn
+			(std::make_unique<div_by_zero_diagnostic> (assign));
+		      ctxt->terminate_path ();
+		    }
 		  return nullptr;
 		}
 	  }
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 6440e7cd369..dcb6cfbab23 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -11552,6 +11552,7 @@ Enabling this option effectively enables the following warnings:
 @gccoptlist{
 -Wanalyzer-allocation-size
 -Wanalyzer-deref-before-check
+-Wanalyzer-div-by-zero
 -Wanalyzer-double-fclose
 -Wanalyzer-double-free
 -Wanalyzer-exposure-through-output-file
@@ -11640,6 +11641,18 @@ multiple of @code{sizeof (*pointer)}.
 
 See @uref{https://cwe.mitre.org/data/definitions/131.html, CWE-131: Incorrect Calculation of Buffer Size}.
 
+@opindex Wanalyzer-div-by-zero
+@opindex Wno-analyzer-div-by-zero
+@item -Wno-analyzer-div-by-zero
+This warning requires @option{-fanalyzer}, which enables it;
+to disable it, use @option{-Wno-analyzer-div-by-zero}.
+
+This diagnostic warns for paths through the code which attempt
+integer division by zero.  It is analogous to @option{-Wdiv-by-zero}, but
+implemented in a different way.
+
+See @uref{https://cwe.mitre.org/data/definitions/369.html,  CWE-369: Divide By Zero}.
+
 @opindex Wanalyzer-deref-before-check
 @opindex Wno-analyzer-deref-before-check
 @item -Wno-analyzer-deref-before-check
diff --git a/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-1.c b/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-1.c
index 5fbfe9c52ea..0d0b8e01157 100644
--- a/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-1.c
+++ b/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-1.c
@@ -9,11 +9,11 @@ return_zero (void)
 void
 test_div (int a)
 {
-  __analyzer_eval (a / return_zero () == 0); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (a / return_zero () == 0); /* { dg-warning "division by zero \\\[-Wanalyzer-div-by-zero\\\]" } */
 }
 
 void
 test_mod (int a)
 {
-  __analyzer_eval (a % return_zero () == 0); /* { dg-warning "UNKNOWN" } */
+  __analyzer_eval (a % return_zero () == 0); /* { dg-warning "division by zero \\\[-Wanalyzer-div-by-zero\\\]" } */
 }
diff --git a/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-pr124195-2.c b/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-pr124195-2.c
index cac91e57d3c..dc2edc9958e 100644
--- a/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-pr124195-2.c
+++ b/gcc/testsuite/c-c++-common/analyzer/divide-by-zero-pr124195-2.c
@@ -3,6 +3,7 @@ short s;
 int
 foo()
 {
-  s %= 0; /* { dg-warning "division by zero" } */
+  s %= 0; /* { dg-warning "division by zero \\\[-Wdiv-by-zero\\\]" } */
+  /* { dg-warning "division by zero \\\[-Wanalyzer-div-by-zero\\\]" "" { target *-*-* } .-1 } */
   return s > 0;
 }
diff --git a/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-pr124195-1.c b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-pr124195-1.c
index 6be17ed61d4..d11cfeb4f97 100644
--- a/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-pr124195-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-pr124195-1.c
@@ -3,6 +3,6 @@ short s;
 int
 foo()
 {
-  s %= (0, 0);
+  s %= (0, 0); /* { dg-warning "division by zero \\\[-Wanalyzer-div-by-zero\\\]" } */
   return s > 0;
 }
-- 
2.49.0

