Skip to content

Commit 1934f07

Browse files
committed
Use bitvector types to implement __builtin_{add,sub,mul}_overflow
The use of mathematical integers is not supported by the SAT back-end. Although GCC's specification describes the operations over mathematical integers, the same precision can be achieved by using bitvector types of sufficient width. The implementation now also introduces a fresh symbol to store the result of the full-precision operation. This ensures that the arguments to __builtin_{add,sub,mul}_overflow are evaluated exactly once.
1 parent 66959cb commit 1934f07

File tree

3 files changed

+77
-27
lines changed

3 files changed

+77
-27
lines changed

regression/cbmc/gcc_builtin_add_overflow/main.c

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -110,6 +110,15 @@ void check_generic(void)
110110
assert(0 && "reachability");
111111
}
112112

113+
void check_non_const()
114+
{
115+
int a, b, c, d, r;
116+
// this may overflow (negated assertion fails)
117+
assert(!__builtin_add_overflow(a, b, &r));
118+
// but need not overflow (assertion fails)
119+
assert(__builtin_add_overflow(c, d, &c));
120+
}
121+
113122
int main(void)
114123
{
115124
check_int();
@@ -119,4 +128,5 @@ int main(void)
119128
check_unsigned_long();
120129
check_unsigned_long_long();
121130
check_generic();
131+
check_non_const();
122132
}

regression/cbmc/gcc_builtin_add_overflow/test.desc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,8 @@ main.c
5757
\[check_generic.assertion.9\] line \d+ assertion big_result == 2ll \* .*: SUCCESS
5858
\[check_generic.assertion.10\] line \d+ assertion __builtin_add_overflow\(.* / 2 \+ 1, .*/ 2 \+ 1, &big_result\): SUCCESS
5959
\[check_generic.assertion.11\] line \d+ assertion 0 && "reachability": FAILURE
60+
\[check_non_const\.assertion\.1\] line \d+ assertion !__builtin_add_overflow\(a, b, &r\): FAILURE
61+
\[check_non_const\.assertion\.2\] line \d+ assertion __builtin_add_overflow\(c, d, &c\): FAILURE
6062
VERIFICATION FAILED
6163
^EXIT=10$
6264
^SIGNAL=0$

src/ansi-c/c_typecheck_expr.cpp

Lines changed: 65 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ Author: Daniel Kroening, [email protected]
1919
#include <util/cprover_prefix.h>
2020
#include <util/expr_util.h>
2121
#include <util/floatbv_expr.h>
22+
#include <util/fresh_symbol.h>
2223
#include <util/ieee_float.h>
2324
#include <util/mathematical_expr.h>
2425
#include <util/mathematical_types.h>
@@ -3014,48 +3015,85 @@ exprt c_typecheck_baset::do_special_functions(
30143015
}
30153016
}
30163017

3017-
// actual logic implementing the operators
3018+
// actual logic implementing the operators: perform operations on signed
3019+
// bitvector types of sufficiently large size to hold the result
30183020
auto const make_operation = [&identifier](exprt lhs, exprt rhs) -> exprt {
3021+
std::size_t lhs_ssize = to_bitvector_type(lhs.type()).get_width();
3022+
if(lhs.type().id() == ID_unsignedbv)
3023+
++lhs_ssize;
3024+
std::size_t rhs_ssize = to_bitvector_type(rhs.type()).get_width();
3025+
if(rhs.type().id() == ID_unsignedbv)
3026+
++rhs_ssize;
3027+
30193028
if(identifier == "__builtin_add_overflow")
30203029
{
3021-
return plus_exprt{lhs, rhs};
3030+
std::size_t ssize = std::max(lhs_ssize, rhs_ssize) + 1;
3031+
integer_bitvector_typet ssize_type{ID_signedbv, ssize};
3032+
return plus_exprt{typecast_exprt{lhs, ssize_type},
3033+
typecast_exprt{rhs, ssize_type}};
30223034
}
30233035
else if(identifier == "__builtin_sub_overflow")
30243036
{
3025-
return minus_exprt{lhs, rhs};
3037+
std::size_t ssize = std::max(lhs_ssize, rhs_ssize) + 1;
3038+
integer_bitvector_typet ssize_type{ID_signedbv, ssize};
3039+
return minus_exprt{typecast_exprt{lhs, ssize_type},
3040+
typecast_exprt{rhs, ssize_type}};
30263041
}
30273042
else
30283043
{
30293044
INVARIANT(
30303045
identifier == "__builtin_mul_overflow",
30313046
"the three overflow operations are add, sub and mul");
3032-
return mult_exprt{lhs, rhs};
3047+
std::size_t ssize = lhs_ssize + rhs_ssize;
3048+
integer_bitvector_typet ssize_type{ID_signedbv, ssize};
3049+
return mult_exprt{typecast_exprt{lhs, ssize_type},
3050+
typecast_exprt{rhs, ssize_type}};
30333051
}
30343052
};
30353053

3036-
// we’re basically generating this expression
3037-
// (*result = (result_type)((integer)lhs OP (integer)rhs)),
3038-
// ((integer)result == (integer)lhs OP (integer)rhs)
3039-
// i.e. perform the operation (+, -, *) on arbitrary length integer,
3040-
// cast to result type, check if the casted result is still equivalent
3041-
// to the arbitrary length result.
3042-
auto operation = make_operation(
3043-
typecast_exprt{lhs, integer_typet{}},
3044-
typecast_exprt{rhs, integer_typet{}});
3045-
3046-
auto operation_result =
3047-
typecast_exprt{operation, result_ptr.type().subtype()};
3048-
typecheck_expr_typecast(operation_result);
3049-
auto overflow_check = notequal_exprt{
3050-
typecast_exprt{dereference_exprt{result_ptr}, integer_typet{}},
3051-
operation};
3052-
typecheck_expr(overflow_check);
3053-
return exprt{ID_comma,
3054-
bool_typet{},
3055-
{side_effect_expr_assignt{dereference_exprt{result_ptr},
3056-
operation_result,
3057-
expr.source_location()},
3058-
overflow_check}};
3054+
// Generating the following statement expression:
3055+
// ({ large_signedbv tmp = (large_signedbv)lhs OP (large_signedbv)rhs;
3056+
// *result = (result_type)large_signedbv;
3057+
// (large_signedbv)*result != tmp; })
3058+
// This performs the operation (+, -, *) on a signed bitvector type of
3059+
// sufficiently large width to store the precise result, cast to result
3060+
// type, check if the casted result is not equivalent to the full-length
3061+
// result.
3062+
auto operation = make_operation(lhs, rhs);
3063+
// Disable overflow checks as the operation cannot overflow on the larger
3064+
// type
3065+
operation.add_source_location() = expr.source_location();
3066+
operation.add_source_location().add_pragma("disable:signed-overflow-check");
3067+
3068+
const symbolt &new_symbol = get_fresh_aux_symbol(
3069+
operation.type(),
3070+
id2string(current_symbol.name) + "$tmp",
3071+
id2string(current_symbol.base_name) + "$tmp",
3072+
expr.source_location(),
3073+
mode,
3074+
symbol_table);
3075+
3076+
code_declt decl{new_symbol.symbol_expr()};
3077+
decl.add_source_location() = expr.source_location();
3078+
3079+
code_assignt tmp_assignment{decl.symbol(), operation};
3080+
tmp_assignment.add_source_location() = expr.source_location();
3081+
3082+
const auto &result_type = to_pointer_type(result_ptr.type()).subtype();
3083+
code_assignt result_assignment{dereference_exprt{result_ptr},
3084+
typecast_exprt{decl.symbol(), result_type}};
3085+
result_assignment.add_source_location() = expr.source_location();
3086+
3087+
notequal_exprt overflow_check{
3088+
typecast_exprt{dereference_exprt{result_ptr}, operation.type()},
3089+
decl.symbol()};
3090+
overflow_check.add_source_location() = expr.source_location();
3091+
code_expressiont return_expr{overflow_check};
3092+
3093+
code_blockt statements{
3094+
{decl, tmp_assignment, result_assignment, return_expr}};
3095+
return side_effect_expr_statement_expressiont{
3096+
statements, overflow_check.type(), expr.source_location()};
30593097
}
30603098
else
30613099
return nil_exprt();

0 commit comments

Comments
 (0)