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>
This commit is contained in:
David Malcolm
2026-02-24 18:47:35 -05:00
parent eb67d9885b
commit 5b0937f037
11 changed files with 132 additions and 26 deletions

View File

@@ -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.

View File

@@ -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)

View File

@@ -855,6 +855,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. */
@@ -1073,24 +1113,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;
}
}

View File

@@ -891,9 +891,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.

View File

@@ -11555,6 +11555,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
@@ -11646,6 +11647,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

View File

@@ -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\\\]" } */
}

View File

@@ -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;
}

View File

@@ -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" } */

View File

@@ -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\\\]" } */
}

View File

@@ -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);
}

View File

@@ -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;
}