Skip to content

Commit 0003223

Browse files
author
Norbert Manthey
committed
Bitand/bitor promotion with ~ may have unexpected results
The combination of bitand and bitnot on variables/expressions with different bit width might lead to unexpected computations. Hence, goto-cc produces a warning when the combination is spotted. The check also works for the &= operator. When constants are used, no warning is produced.
1 parent 478dc8a commit 0003223

File tree

7 files changed

+248
-0
lines changed

7 files changed

+248
-0
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
#include <inttypes.h>
2+
#include <stdio.h>
3+
4+
#define G (1024L*1024L*1024L*1024L)
5+
6+
void broken(unsigned int a, unsigned long b, unsigned long c ) {
7+
if( a != 2 && a != 4 && a != 8 ) return;
8+
if ( a == 2 )
9+
b = (int16_t)b;
10+
else if ( a == 4 )
11+
b = (int32_t)b;
12+
if( (long)b < 0 ) {
13+
unsigned long d;
14+
// here, we would like to have some warning like
15+
// increasing bit width automatically before applying "~" operator
16+
d = a + (((-b-1) >> 3) & ~(a-1));
17+
c -= d;
18+
b = (d << 3) + b;
19+
} else {
20+
// increasing bit width automatically before applying "~" operator
21+
c += (b >> 3) & ~(a - 1);
22+
b &= (a << 3) - 1;
23+
}
24+
printf("b: %ld c: %ld\n", b, c);
25+
}
26+
27+
void fixed(unsigned int a, unsigned long b, unsigned long c ) {
28+
if( a != 2 && a != 4 && a != 8 ) return;
29+
if ( a == 2 )
30+
b = (int16_t)b;
31+
else if ( a == 4 )
32+
b = (int32_t)b;
33+
if( (long)b < 0 ) {
34+
unsigned long d;
35+
// the problem is that the 1 (i.e. not 1L) turned the later operand
36+
// into a 32bit value first, and then the ~ operator turns this
37+
// back into 64, with many leading 0bits, which are then flipped
38+
// to 1, and successfully pass the & operator.
39+
// combinations we might want to look for are hence:
40+
// (bigger_t) & ~(bigger_t vs smaller_t)
41+
// but basically, any ~ together with a silent cast looks weird
42+
d = a + (((-b-1) >> 3) & ~(a-1L));
43+
c -= d;
44+
b = (d << 3) + b;
45+
} else {
46+
c += (b >> 3) & ~(a - 1L);
47+
b &= (a << 3) - 1;
48+
}
49+
printf("b: %ld c: %ld\n", b, c);
50+
}
51+
52+
void test(unsigned int a, unsigned long b, unsigned long c) {
53+
printf("broken for %u,%lu,%lu\n",a,b,c);
54+
broken(a, b, c);
55+
printf(" and fixed:\n");
56+
fixed(a,b,c);
57+
}
58+
59+
int main() {
60+
test(8, G+1023L, 0);
61+
test(8, -G-1023L, 0);
62+
return 0;
63+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
CORE
2+
main.c
3+
-Wall
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
Implicit zero extension of bitwise operand in combination with bitwise negation may cause unexpected results$
7+
--
8+
^warning: ignoring
9+
^CONVERSION ERROR$
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
#include <inttypes.h>
2+
#include <stdio.h>
3+
4+
int main() {
5+
unsigned int u=1023;
6+
unsigned char c=3;
7+
unsigned long l=1024L*1024L*1024L*1024L*1024L - 1L;
8+
unsigned long d=l;
9+
10+
d&=u; // fine, no negation
11+
12+
d&=c; // fine, no negation
13+
14+
d&=~u; // dangerous, u<d, negation
15+
16+
d&=~c; // dangerous, c<d, negation
17+
18+
u&=d; // fine, u<d
19+
20+
c&=d; // fine, c<d
21+
22+
c&=1024L*1024L*1024L*1024L*1024L - 1L; // fine, c < constant
23+
24+
c&=~d; // fine, c < d
25+
26+
c&=~1024L*1024L*1024L*1024L*1024L - 1L; // fine, c < constant
27+
28+
d&=1; // fine, constant
29+
30+
d=l & 1; // fine, no negation
31+
32+
d=l & c; // fine, no negation
33+
34+
d=d & ~1; // dangerous, d>1 and negation
35+
36+
d=~d & 1; // fine, d>1, but negation on greater bit width
37+
38+
d=(l&=~u); // dangerous, l>u and negation
39+
40+
d=(u&=~l); // fine, u < l
41+
42+
d=(l & ~u); // dangerous, l>u and negation
43+
44+
d=(u & ~l); // fine, u < l, i.e. ~ on greater bit width
45+
46+
d=(l&=u); // fine, no negation
47+
48+
d=(u&=l); // fine, no negation
49+
50+
d=(~5 & l); // dangerous, negation and 5<l
51+
52+
d=(~5L & u); // fine, negation on constant, 5L > u, and negation on higher bit width
53+
54+
return 0;
55+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
CORE
2+
main.c
3+
-Wall
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
main.c:14:
7+
main.c:16:
8+
main.c:34:
9+
main.c:38:
10+
main.c:42:
11+
main.c:50:
12+
--
13+
main.c:10:
14+
main.c:12:
15+
main.c:18:
16+
main.c:20:
17+
main.c:22:
18+
main.c:24:
19+
main.c:26:
20+
main.c:28:
21+
main.c:30:
22+
main.c:32:
23+
main.c:36:
24+
main.c:40:
25+
main.c:44:
26+
main.c:46:
27+
main.c:48:
28+
main.c:52:
29+
^warning: ignoring
30+
^CONVERSION ERROR$
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,32 @@
1+
#include <stdio.h>
2+
3+
#include <inttypes.h>
4+
5+
6+
int main() {
7+
8+
uint32_t a=0;
9+
uint64_t b=1024L;
10+
b=b * b * b * b + 1023;
11+
12+
printf("a=%u b=%lu\n", a, b);
13+
b=b & ~a;
14+
printf("a=%u b=%lu\n", a, b);
15+
16+
uint16_t c=0;
17+
uint32_t d=256 * 256 + 127;
18+
19+
printf("c=%u d=%u\n", c, d);
20+
d=d & ~c;
21+
printf("c=%u d=%u\n", c, d);
22+
23+
uint64_t e=3;
24+
unsigned __int128 f=1024;
25+
f=f * f * f * f; // >32bit
26+
f=f * f; // >64bit
27+
28+
printf("e=%lu f=hi:%lu lo:%lu\n", e, (uint64_t)(f >> 64), (uint64_t)f);
29+
f=f & ~e;
30+
printf("e=%lu f=hi:%lu lo:%lu\n", e, (uint64_t)(f >> 64), (uint64_t)f);
31+
return 0;
32+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
CORE
2+
main.c
3+
-Wall
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
main.c:13:
7+
main.c:29:
8+
--
9+
main.c:20:
10+
^warning: ignoring
11+
^CONVERSION ERROR$

src/ansi-c/c_typecheck_expr.cpp

+48
Original file line numberDiff line numberDiff line change
@@ -16,11 +16,13 @@ Author: Daniel Kroening, [email protected]
1616
#include <util/arith_tools.h>
1717
#include <util/c_types.h>
1818
#include <util/config.h>
19+
#include <util/expr_util.h>
1920
#include <util/std_types.h>
2021
#include <util/prefix.h>
2122
#include <util/cprover_prefix.h>
2223
#include <util/simplify_expr.h>
2324
#include <util/base_type.h>
25+
#include <util/file_util.h>
2426
#include <util/std_expr.h>
2527
#include <util/pointer_offset_size.h>
2628
#include <util/pointer_predicates.h>
@@ -2813,6 +2815,36 @@ void c_typecheck_baset::typecheck_expr_binary_arithmetic(exprt &expr)
28132815

28142816
// promote!
28152817

2818+
// warn about an implicit 0-extension of bitand operands
2819+
// expect an explicit cast to avoid this warning
2820+
if((expr.id()==ID_bitand) &&
2821+
(op0.type().id()==ID_signedbv || op0.type().id()==ID_unsignedbv) &&
2822+
(op1.type().id()==ID_signedbv || op1.type().id()==ID_unsignedbv))
2823+
{
2824+
bool unintentional=false;
2825+
// do not apply, if we are working with constants and the & operator
2826+
if(op0.id()!=ID_constant && op1.id()!=ID_constant)
2827+
{
2828+
// mismatch occurs only, if we have "&" with two different width,
2829+
// where the side with the smaller width has to use the "~" operator
2830+
const size_t width0=to_bitvector_type(op0.type()).get_width();
2831+
const size_t width1=to_bitvector_type(op1.type()).get_width();
2832+
unintentional=(width0<width1 && has_subexpr(op0, ID_bitnot)) ||
2833+
(width0>width1 && has_subexpr(op1, ID_bitnot));
2834+
}
2835+
2836+
// is all conditions are met for an unintended combination
2837+
// of the operators ~ and &, print a warning
2838+
if(unintentional)
2839+
{
2840+
warning().source_location=expr.find_source_location();
2841+
warning() << "Implicit zero extension of bitwise operand"
2842+
<< " in combination with bitwise negation may"
2843+
<< " cause unexpected results"
2844+
<< eom;
2845+
}
2846+
}
2847+
28162848
implicit_typecast_arithmetic(op0, op1);
28172849

28182850
const typet &type0=follow(op0.type());
@@ -3205,6 +3237,22 @@ void c_typecheck_baset::typecheck_side_effect_assignment(
32053237
o_type0.id()==ID_signedbv ||
32063238
o_type0.id()==ID_c_bit_field)
32073239
{
3240+
// check whether an implicit cast might be dangerous,
3241+
// i.e. it involves the & and ~ operator
3242+
if((o_type0.id()==ID_unsignedbv || o_type0.id()==ID_signedbv) &&
3243+
(o_type1.id()==ID_unsignedbv || o_type1.id()==ID_signedbv) &&
3244+
op1.id()!=ID_constant &&
3245+
(to_bitvector_type(o_type0).get_width()>
3246+
to_bitvector_type(o_type1).get_width()) &&
3247+
has_subexpr(op1, ID_bitnot))
3248+
{
3249+
warning().source_location=expr.find_source_location();
3250+
warning() << "Implicit zero extension of bitwise operand"
3251+
<< " in combination with bitwise negation may"
3252+
<< " cause unexpected results"
3253+
<< eom;
3254+
}
3255+
// perform the cast
32083256
implicit_typecast(op1, o_type0);
32093257
return;
32103258
}

0 commit comments

Comments
 (0)