From fde3b928da5f2861d11796b5830153d805727cf3 Mon Sep 17 00:00:00 2001
From: David Malcolm <dmalcolm@redhat.com>
Date: Tue, 24 Feb 2026 18:47:35 -0500
Subject: [PATCH 22/98] analyzer: new warning: -Wanalyzer-div-by-zero (PR
 analyzer/124217)

gcc/analyzer/ChangeLog:
	PR analyzer/124217
	* analyzer.opt (Wanalyzer-div-by-zero): New.
	* analyzer.opt.urls: Regenerate.
	* region-model.cc (class div_by_zero_diagnostic): New.
	(region_model::get_gassign_result): Add warning for division by
	zero if ctxt is non-null.  Bail out on such cases even if ctxt
	is null.
	* svalue.cc (type_can_have_value_range_p): Also handle frange.

gcc/ChangeLog:
	PR analyzer/124217
	* doc/invoke.texi: Add -Wanalyzer-div-by-zero.

gcc/testsuite/ChangeLog:
	PR analyzer/124217
	* c-c++-common/analyzer/divide-by-zero-1.c: Update to expect
	-Wanalyzer-div-by-zero.
	* c-c++-common/analyzer/divide-by-zero-pr124195-2.c: Likewise.
	* gcc.dg/analyzer/data-model-1.c (test_21): Split out division by
	zero cases into...
	(test_21_division_by_zero): ...this, and...
	(test_21_modulus_by_zero): ...this, updating these to expect
	-Wanalyzer-div-by-zero warnings.
	* gcc.dg/analyzer/divide-by-zero-float.c: New test.
	* gcc.dg/analyzer/divide-by-zero-ice-pr124433.c: Update to expect
	-Wanalyzer-div-by-zero.
	* gcc.dg/analyzer/divide-by-zero-pr124195-1.c: Likewise.

Signed-off-by: David Malcolm <dmalcolm@redhat.com>
---
 gcc/analyzer/analyzer.opt                     |  4 ++
 gcc/analyzer/analyzer.opt.urls                |  3 +
 gcc/analyzer/region-model.cc                  | 69 +++++++++++++++----
 gcc/analyzer/svalue.cc                        |  8 ++-
 gcc/doc/invoke.texi                           | 13 ++++
 .../c-c++-common/analyzer/divide-by-zero-1.c  |  4 +-
 .../analyzer/divide-by-zero-pr124195-2.c      |  3 +-
 gcc/testsuite/gcc.dg/analyzer/data-model-1.c  | 27 ++++++--
 .../gcc.dg/analyzer/divide-by-zero-float.c    | 23 +++++++
 .../analyzer/divide-by-zero-ice-pr124433.c    |  2 +-
 .../analyzer/divide-by-zero-pr124195-1.c      |  2 +-
 11 files changed, 132 insertions(+), 26 deletions(-)
 create mode 100644 gcc/testsuite/gcc.dg/analyzer/divide-by-zero-float.c

diff --git a/gcc/analyzer/analyzer.opt b/gcc/analyzer/analyzer.opt
index 389f4ea946ac..992ef9f8a08f 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 db56a7209e93..833d0d76c76b 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 d46b130b00d9..3d3d5441aea6 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,24 +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)
 	  {
 	    value_range rhs_vr;
 	    if (rhs2_sval->maybe_get_value_range (rhs_vr))
 	      if (rhs_vr.zero_p ())
 		{
-		  /* 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/analyzer/svalue.cc b/gcc/analyzer/svalue.cc
index a62d85b1658e..05ba2212db6b 100644
--- a/gcc/analyzer/svalue.cc
+++ b/gcc/analyzer/svalue.cc
@@ -898,9 +898,11 @@ type_can_have_value_range_p (tree type)
 {
   if (!type)
     return false;
-  if (!irange::supports_p (type))
-    return false;
-  return true;
+  if (irange::supports_p (type))
+    return true;
+  if (frange::supports_p (type))
+    return true;
+  return false;
 }
 
 /* Base implementation of svalue::maybe_get_value_range_1 vfunc.
diff --git a/gcc/doc/invoke.texi b/gcc/doc/invoke.texi
index 81f62ccdda90..13e42cf140e7 100644
--- a/gcc/doc/invoke.texi
+++ b/gcc/doc/invoke.texi
@@ -11543,6 +11543,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
@@ -11634,6 +11635,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 5fbfe9c52ea4..0d0b8e01157c 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 cac91e57d3c7..dc2edc9958ef 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/data-model-1.c b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
index ce77d1254772..35699e0bb1e8 100644
--- a/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
+++ b/gcc/testsuite/gcc.dg/analyzer/data-model-1.c
@@ -421,11 +421,6 @@ void test_21 (void)
   __analyzer_eval (i / j == 1); /* { dg-warning "TRUE" } */
   __analyzer_eval (i % j == 2); /* { dg-warning "TRUE" } */
 
-  /* Division by zero.  */
-  // TODO: should we warn for this?
-  __analyzer_eval (i / zero); /* { dg-warning "UNKNOWN" } */
-  __analyzer_eval (i % zero); /* { dg-warning "UNKNOWN" } */
-
   __analyzer_eval ((i & 1) == (5 & 1)); /* { dg-warning "TRUE" } */
   __analyzer_eval ((i & j) == (5 & 3)); /* { dg-warning "TRUE" } */
   __analyzer_eval ((i | 1) == (5 | 1)); /* { dg-warning "TRUE" } */
@@ -449,6 +444,28 @@ void test_21 (void)
   __analyzer_eval (+i == +5); /* { dg-warning "TRUE" } */
 }
 
+void test_21_division_by_zero (void)
+{
+  int i, zero;
+  int *pi = &i;
+  int *pzero = &zero;
+  *pi = 5;
+  *pzero = 0;
+
+  __analyzer_eval (i / zero); /* { dg-warning "Wanalyzer-div-by-zero" } */
+}
+
+void test_21_modulus_by_zero (void)
+{
+  int i, zero;
+  int *pi = &i;
+  int *pzero = &zero;
+  *pi = 5;
+  *pzero = 0;
+
+  __analyzer_eval (i % zero); /* { dg-warning "Wanalyzer-div-by-zero" } */
+}
+
 void test_22 (int i, int j)
 {
   __analyzer_eval (i + j == i + j); /* { dg-warning "TRUE" } */
diff --git a/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-float.c b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-float.c
new file mode 100644
index 000000000000..3aaee568bc1c
--- /dev/null
+++ b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-float.c
@@ -0,0 +1,23 @@
+float
+test_1 ()
+{
+  return 42.f / 0.f; /* { dg-warning "division by zero \\\[-Wanalyzer-div-by-zero\\\]" } */
+}
+
+static float __attribute__((noinline))
+get_zero ()
+{
+  return 0.f;
+}
+
+float
+test_2 ()
+{
+  return 42.f / get_zero (); /* { dg-warning "division by zero \\\[-Wanalyzer-div-by-zero\\\]" } */
+}
+
+float
+test_3 (float x)
+{
+  return x / get_zero (); /* { dg-warning "division by zero \\\[-Wanalyzer-div-by-zero\\\]" } */
+}
diff --git a/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-ice-pr124433.c b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-ice-pr124433.c
index 9b52ac8cc5bf..0853939e5b1a 100644
--- a/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-ice-pr124433.c
+++ b/gcc/testsuite/gcc.dg/analyzer/divide-by-zero-ice-pr124433.c
@@ -4,6 +4,6 @@ int c;
 void
 foo()
 {
-  do c %= (5ull << 40) & m;
+  do c %= (5ull << 40) & m; /* { dg-warning "division by zero \\\[-Wanalyzer-div-by-zero\\\]" } */
   while (c);
 }
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 6be17ed61d44..d11cfeb4f97b 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

