Skip to content

Commit 9335e6b

Browse files
committed
division-by-zero on float
This separates the division-by-zero check for floating-point operations from the division-by-zero check for integers. The new division-by-zero check for floating-point operations is off by default, and is enabled by --float-div-by-zero-check/ The case for doing so is weak. ISO/IEC 9899:2021 (C21) and predecessors clealy state (Sec 6.5.5 par 5) "In both operations, if the value of the second operand is zero, the behavior is undefined." However, Annex F (IEC 60559 floating-point arithmetic) states that implementations that define __STDC_IEC_559__ must implement IEC 60559 division, which clearly defines the behavior when dividing floating-point numbers by zero. This behavior can be observed on all architectures that we support.
1 parent 9411c77 commit 9335e6b

File tree

7 files changed

+130
-41
lines changed

7 files changed

+130
-41
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
#include <assert.h>
2+
#include <math.h>
3+
4+
int main()
5+
{
6+
assert(1.0 / 2 == 0.5);
7+
assert(1.0 / 0 == INFINITY);
8+
assert(-1.0 / 0 == -INFINITY);
9+
assert(isnan(NAN / 0));
10+
assert(1.0 / INFINITY == 0);
11+
assert(isnan(INFINITY / INFINITY));
12+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
CORE
2+
floating_point_division1.c
3+
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
^warning: ignoring
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
#include <assert.h>
2+
#include <math.h>
3+
4+
double nondet_double();
5+
6+
int main()
7+
{
8+
double y = nondet_double();
9+
10+
if(y == 0)
11+
{
12+
// we expect to catch this with --float-div-by-zero-check
13+
double x = 1.0 / y;
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
floating_point_division2.c
3+
--float-div-by-zero-check
4+
^EXIT=10$
5+
^SIGNAL=0$
6+
^\[main\.float-division-by-zero\.1\] line \d+ floating-point division by zero in 1\.0 / y: FAILURE$
7+
^VERIFICATION FAILED$
8+
--
9+
^warning: ignoring

src/ansi-c/goto_check_c.cpp

+41-1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ class goto_check_ct
5858
enable_memory_cleanup_check =
5959
_options.get_bool_option("memory-cleanup-check");
6060
enable_div_by_zero_check = _options.get_bool_option("div-by-zero-check");
61+
enable_float_div_by_zero_check =
62+
_options.get_bool_option("float-div-by-zero-check");
6163
enable_enum_range_check = _options.get_bool_option("enum-range-check");
6264
enable_signed_overflow_check =
6365
_options.get_bool_option("signed-overflow-check");
@@ -179,6 +181,7 @@ class goto_check_ct
179181
void bounds_check_index(const index_exprt &, const guardt &);
180182
void bounds_check_bit_count(const unary_exprt &, const guardt &);
181183
void div_by_zero_check(const div_exprt &, const guardt &);
184+
void float_div_by_zero_check(const div_exprt &, const guardt &);
182185
void mod_by_zero_check(const mod_exprt &, const guardt &);
183186
void mod_overflow_check(const mod_exprt &, const guardt &);
184187
void enum_range_check(const exprt &, const guardt &);
@@ -262,6 +265,7 @@ class goto_check_ct
262265
bool enable_memory_leak_check;
263266
bool enable_memory_cleanup_check;
264267
bool enable_div_by_zero_check;
268+
bool enable_float_div_by_zero_check;
265269
bool enable_enum_range_check;
266270
bool enable_signed_overflow_check;
267271
bool enable_unsigned_overflow_check;
@@ -282,6 +286,7 @@ class goto_check_ct
282286
{"memory-leak-check", &enable_memory_leak_check},
283287
{"memory-cleanup-check", &enable_memory_cleanup_check},
284288
{"div-by-zero-check", &enable_div_by_zero_check},
289+
{"float-div-by-zero-check", &enable_float_div_by_zero_check},
285290
{"enum-range-check", &enable_enum_range_check},
286291
{"signed-overflow-check", &enable_signed_overflow_check},
287292
{"unsigned-overflow-check", &enable_unsigned_overflow_check},
@@ -508,6 +513,27 @@ void goto_check_ct::div_by_zero_check(
508513
guard);
509514
}
510515

516+
void goto_check_ct::float_div_by_zero_check(
517+
const div_exprt &expr,
518+
const guardt &guard)
519+
{
520+
if(!enable_float_div_by_zero_check)
521+
return;
522+
523+
// add divison by zero subgoal
524+
525+
exprt zero = from_integer(0, expr.op1().type());
526+
const notequal_exprt inequality(expr.op1(), std::move(zero));
527+
528+
add_guarded_property(
529+
inequality,
530+
"floating-point division by zero",
531+
"float-division-by-zero",
532+
expr.find_source_location(),
533+
expr,
534+
guard);
535+
}
536+
511537
void goto_check_ct::enum_range_check(const exprt &expr, const guardt &guard)
512538
{
513539
if(!enable_enum_range_check)
@@ -1850,7 +1876,21 @@ void goto_check_ct::check_rec_div(
18501876
const div_exprt &div_expr,
18511877
const guardt &guard)
18521878
{
1853-
div_by_zero_check(to_div_expr(div_expr), guard);
1879+
if(
1880+
div_expr.type().id() == ID_signedbv ||
1881+
div_expr.type().id() == ID_unsignedbv || div_expr.type().id() == ID_c_bool)
1882+
{
1883+
// Division by zero is undefined behavior for all integer types.
1884+
div_by_zero_check(to_div_expr(div_expr), guard);
1885+
}
1886+
else if(div_expr.type().id() == ID_floatbv)
1887+
{
1888+
// Division by zero on floating-point numbers may be undefined behavior.
1889+
// Annex F of the ISO C21 suggests that implementations that
1890+
// define __STDC_IEC_559__ follow IEEE 754 semantics,
1891+
// which defines the outcome of division by zero.
1892+
float_div_by_zero_check(to_div_expr(div_expr), guard);
1893+
}
18541894

18551895
if(div_expr.type().id() == ID_signedbv)
18561896
integer_overflow_check(div_expr, guard);

src/ansi-c/goto_check_c.h

+7-2
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ void goto_check_c(
3939

4040
#define OPT_GOTO_CHECK \
4141
"(bounds-check)(pointer-check)(memory-leak-check)(memory-cleanup-check)" \
42-
"(div-by-zero-check)(enum-range-check)" \
42+
"(div-by-zero-check)(float-div-by-zero-check)" \
43+
"(enum-range-check)" \
4344
"(signed-overflow-check)(unsigned-overflow-check)" \
4445
"(pointer-overflow-check)(conversion-check)(undefined-shift-check)" \
4546
"(float-overflow-check)(nan-check)(no-built-in-assertions)" \
@@ -61,7 +62,10 @@ void goto_check_c(
6162
" {y--no-pointer-check} \t disable pointer checks\n" \
6263
" {y--memory-leak-check} \t enable memory leak checks\n" \
6364
" {y--memory-cleanup-check} \t enable memory cleanup checks\n" \
64-
" {y--div-by-zero-check} \t enable division by zero checks (default on)\n" \
65+
" {y--div-by-zero-check} \t " \
66+
"enable division by zero checks on integers (default on)\n" \
67+
" {y--div-by-zero-check} \t " \
68+
"enable division by zero checks on floating-point numbers (default off)\n" \
6569
" {y--no-div-by-zero-check} \t disable division by zero checks\n" \
6670
" {y--signed-overflow-check} \t " \
6771
"enable signed arithmetic over- and underflow checks (default on)\n" \
@@ -123,6 +127,7 @@ void goto_check_c(
123127
PARSE_OPTION_OVERRIDE(cmdline, options, "bounds-check"); \
124128
PARSE_OPTION_OVERRIDE(cmdline, options, "pointer-check"); \
125129
PARSE_OPTION_OVERRIDE(cmdline, options, "div-by-zero-check"); \
130+
PARSE_OPTION_OVERRIDE(cmdline, options, "float-div-by-zero-check"); \
126131
PARSE_OPTION_OVERRIDE(cmdline, options, "signed-overflow-check"); \
127132
PARSE_OPTION_OVERRIDE(cmdline, options, "undefined-shift-check"); \
128133
PARSE_OPTION_OVERRIDE(cmdline, options, "pointer-primitive-check"); \

0 commit comments

Comments
 (0)