Skip to content

Commit e32148b

Browse files
authored
Merge pull request #9081 from rod-chapman/set-char-signedness-in-contracts
Set signedness of "char *" casts to avoid cross-platform differences.
2 parents a777c7b + 60947cb commit e32148b

6 files changed

Lines changed: 107 additions & 30 deletions

File tree

regression/contracts-dfcc/chain.sh

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,17 @@ else
2121
args_cbmc="${args#*" _ "}"
2222
fi
2323

24+
# When the first instrumentation argument is --char-signedness-stability,
25+
# compile and instrument the test a second time with the opposite signedness
26+
# of plain char, and require the resulting goto functions to be identical.
27+
check_char_signedness_stability=0
28+
if [[ "$args_inst" == "--char-signedness-stability"* ]]
29+
then
30+
check_char_signedness_stability=1
31+
args_inst="${args_inst#--char-signedness-stability}"
32+
args_inst="${args_inst# }"
33+
fi
34+
2435
# An optional pre-processing pass with goto-instrument can be requested by
2536
# separating its arguments from the main instrumentation arguments using
2637
# " __ ", as in: --drop-unused-functions __ --dfcc main
@@ -50,7 +61,11 @@ fi
5061
if [[ "${is_windows}" == "true" ]]; then
5162
$goto_cc "${name}.c" "/Fe${name}${dfcc_suffix}.gb"
5263
else
53-
$goto_cc -o "${name}${dfcc_suffix}.gb" "${name}.c"
64+
signedness_flag=""
65+
if [[ "$check_char_signedness_stability" == "1" ]]; then
66+
signedness_flag="-fsigned-char"
67+
fi
68+
$goto_cc ${signedness_flag} -o "${name}${dfcc_suffix}.gb" "${name}.c"
5469
fi
5570

5671
if [[ -n "$args_pre" ]]; then
@@ -77,5 +92,19 @@ fi
7792
if ! echo "${args_cbmc}" | grep -q -- --function ; then
7893
$goto_instrument --drop-unused-functions "${name}${dfcc_suffix}-mod.gb" "${name}${dfcc_suffix}-mod.gb"
7994
fi
95+
if [[ "$check_char_signedness_stability" == "1" ]]
96+
then
97+
$goto_cc -funsigned-char -o "${name}-uchar.gb" "${name}.c"
98+
$goto_instrument ${args_inst} "${name}-uchar.gb" "${name}-uchar-mod.gb"
99+
$goto_instrument --show-goto-functions "${name}${dfcc_suffix}-mod.gb" | \
100+
grep -v '^Reading' > "${name}-default.txt"
101+
$goto_instrument --show-goto-functions "${name}-uchar-mod.gb" | \
102+
grep -v '^Reading' > "${name}-uchar.txt"
103+
if diff "${name}-default.txt" "${name}-uchar.txt"
104+
then
105+
echo "GOTO FUNCTIONS IDENTICAL ACROSS CHAR SIGNEDNESS"
106+
fi
107+
rm "${name}-default.txt" "${name}-uchar.txt"
108+
fi
80109
$goto_instrument --show-goto-functions "${name}${dfcc_suffix}-mod.gb"
81110
$cbmc --sat-solver cadical "${name}${dfcc_suffix}-mod.gb" ${args_cbmc}
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
#include <stdlib.h>
2+
#include <string.h>
3+
4+
int x;
5+
6+
void foo(int *p) __CPROVER_requires(__CPROVER_is_fresh(p, sizeof(int)))
7+
__CPROVER_assigns(*p, x) __CPROVER_ensures(*p == 42)
8+
{
9+
*p = 42;
10+
x = 1;
11+
}
12+
13+
int main()
14+
{
15+
int a[4], b[4];
16+
memset(a, 0, sizeof(a));
17+
memcpy(b, a, sizeof(a));
18+
memmove(a, b, sizeof(a));
19+
__CPROVER_havoc_slice(a, 2 * sizeof(int));
20+
21+
int *q = malloc(sizeof(int));
22+
if(q)
23+
foo(q);
24+
return 0;
25+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
CORE dfcc-only gcc-only
2+
main.c
3+
--char-signedness-stability --dfcc main --enforce-contract foo
4+
^GOTO FUNCTIONS IDENTICAL ACROSS CHAR SIGNEDNESS$
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
^VERIFICATION SUCCESSFUL$
8+
--
9+
^warning: ignoring
10+
--
11+
This test checks that the goto functions generated by DFCC instrumentation do
12+
not depend on the signedness of plain char, which is implementation-defined
13+
and differs between platforms (e.g. between Linux/aarch64 and macOS). It
14+
covers the contracts library (write set construction), the models of memset,
15+
memcpy and memmove, and the expansion of __CPROVER_havoc_slice.
16+
See: https://github.com/diffblue/cbmc/issues/8991

src/ansi-c/goto-conversion/builtin_functions.cpp

Lines changed: 9 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -676,9 +676,13 @@ void goto_convertt::do_havoc_slice(
676676
}
677677

678678
// insert instructions
679-
// assert(rw_ok(argument[0], argument[1]));
680-
// char nondet_contents[argument[1]];
681-
// __CPROVER_array_replace(p, nondet_contents);
679+
// assert(rw_ok(argument[0], argument[1]));
680+
// unsigned char nondet_contents[argument[1]];
681+
// __CPROVER_array_replace(p, nondet_contents);
682+
//
683+
// Note that we use "unsigned char" explicitly here to force stable
684+
// output on platforms where plain "char" is implementation-defined and signed
685+
// by default
682686

683687
r_or_w_ok_exprt ok_expr(ID_w_ok, arguments[0], arguments[1]);
684688
ok_expr.add_source_location() = source_location;
@@ -689,7 +693,8 @@ void goto_convertt::do_havoc_slice(
689693
"assertion havoc_slice " + from_expr(ns, identifier, ok_expr));
690694
dest.add(goto_programt::make_assertion(ok_expr, annotated_location));
691695

692-
const array_typet array_type(char_type(), simplify_expr(arguments[1], ns));
696+
const array_typet array_type(
697+
unsigned_char_type(), simplify_expr(arguments[1], ns));
693698

694699
const symbolt &nondet_contents =
695700
new_tmp_symbol(array_type, "nondet_contents", dest, source_location, mode);

src/ansi-c/library/cprover_contracts.c

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -138,7 +138,7 @@ __CPROVER_HIDE:;
138138
!(offset > 0) | (offset + size <= __CPROVER_max_malloc_size),
139139
"no offset bits overflow on CAR upper bound computation");
140140
return (__CPROVER_contracts_car_t){
141-
.is_writable = ptr != 0, .size = size, .lb = ptr, .ub = (char *)ptr + size};
141+
ptr != 0, size, ptr, (unsigned char *)ptr + size};
142142
}
143143

144144
/// \brief Initialises a __CPROVER_contracts_car_set_ptr_t object
@@ -187,7 +187,7 @@ __CPROVER_HIDE:;
187187
"no offset bits overflow on CAR upper bound computation");
188188
__CPROVER_contracts_car_t *elem = set->elems + idx;
189189
*elem = (__CPROVER_contracts_car_t){
190-
.is_writable = ptr != 0, .size = size, .lb = ptr, .ub = (char *)ptr + size};
190+
ptr != 0, size, ptr, (unsigned char *)ptr + size};
191191
}
192192

193193
/// \brief Invalidates all cars in the \p set that point into the same object
@@ -547,7 +547,7 @@ __CPROVER_HIDE:;
547547
__CPROVER_contracts_car_set_insert(
548548
&(set->contract_assigns),
549549
idx,
550-
((char *)ptr) - __CPROVER_POINTER_OFFSET(ptr),
550+
((unsigned char *)ptr) - __CPROVER_POINTER_OFFSET(ptr),
551551
__CPROVER_OBJECT_SIZE(ptr));
552552
}
553553

@@ -832,7 +832,7 @@ __CPROVER_HIDE:;
832832
__CPROVER_assert(
833833
!(offset > 0) | (offset + size <= __CPROVER_max_malloc_size),
834834
"no offset bits overflow on CAR upper bound computation");
835-
void *ub = (void *)((char *)ptr + size);
835+
void *ub = (void *)((unsigned char *)ptr + size);
836836
__CPROVER_contracts_car_t *elem = set->contract_assigns.elems;
837837
__CPROVER_size_t idx = set->contract_assigns.max_elems;
838838
__CPROVER_bool incl = 0;
@@ -925,7 +925,7 @@ __CPROVER_HIDE:;
925925
/// \param[in] set The write set to check the operation against
926926
/// \param[in] ptr Pointer to the havoced object
927927
/// \return True iff the range of bytes starting at
928-
/// `(char *)ptr - __CPROVER_POINTER_OFFSET(ptr)` and of size
928+
/// `(unsigned char *)ptr - __CPROVER_POINTER_OFFSET(ptr)` and of size
929929
/// `__CPROVER_OBJECT_SIZE(ptr)` is contained in `set->contract_assigns` or
930930
/// `set->allocated`.
931931
__CPROVER_bool __CPROVER_contracts_write_set_check_havoc_object(
@@ -935,7 +935,7 @@ __CPROVER_bool __CPROVER_contracts_write_set_check_havoc_object(
935935
__CPROVER_HIDE:;
936936
return __CPROVER_contracts_write_set_check_assignment(
937937
set,
938-
(char *)ptr - __CPROVER_POINTER_OFFSET(ptr),
938+
(unsigned char *)ptr - __CPROVER_POINTER_OFFSET(ptr),
939939
__CPROVER_OBJECT_SIZE(ptr));
940940
}
941941

@@ -1527,7 +1527,7 @@ __CPROVER_HIDE:;
15271527
// this cast is safe because we prove that ub and lb are ordered
15281528
__CPROVER_size_t max_offset = ub_offset - lb_offset;
15291529
__CPROVER_assume(offset <= max_offset);
1530-
*ptr = (char *)lb + offset;
1530+
*ptr = (unsigned char *)lb + offset;
15311531
__CPROVER_assert(
15321532
write_set->linked_ptr_pred_ctx->ptr_pred != ptr,
15331533
"__CPROVER_pointer_in_range_dfcc does not conflict with other pointer "

src/ansi-c/library/string.c

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -634,8 +634,8 @@ __CPROVER_HIDE:;
634634
#else
635635
__CPROVER_precondition(
636636
__CPROVER_POINTER_OBJECT(dst) != __CPROVER_POINTER_OBJECT(src) ||
637-
((const char *)src >= (const char *)dst + n) ||
638-
((const char *)dst >= (const char *)src + n),
637+
((const unsigned char *)src >= (const unsigned char *)dst + n) ||
638+
((const unsigned char *)dst >= (const unsigned char *)src + n),
639639
"memcpy src/dst overlap");
640640
__CPROVER_precondition(
641641
__CPROVER_r_ok(src, n), "memcpy source region readable");
@@ -644,10 +644,11 @@ __CPROVER_HIDE:;
644644

645645
if(n > 0)
646646
{
647-
//for(__CPROVER_size_t i=0; i<n ; i++) ((char *)dst)[i]=((const char *)src)[i];
648-
char src_n[n];
649-
__CPROVER_array_copy(src_n, (char *)src);
650-
__CPROVER_array_replace((char *)dst, src_n);
647+
//for(__CPROVER_size_t i=0; i<n ; i++)
648+
// ((unsigned char *)dst)[i]=((const unsigned char *)src)[i];
649+
unsigned char src_n[n];
650+
__CPROVER_array_copy(src_n, (unsigned char *)src);
651+
__CPROVER_array_replace((unsigned char *)dst, src_n);
651652
}
652653
#endif
653654

@@ -681,8 +682,8 @@ void *__builtin___memcpy_chk(void *dst, const void *src, __CPROVER_size_t n, __C
681682
#else
682683
__CPROVER_precondition(
683684
__CPROVER_POINTER_OBJECT(dst) != __CPROVER_POINTER_OBJECT(src) ||
684-
((const char *)src >= (const char *)dst + n) ||
685-
((const char *)dst >= (const char *)src + n),
685+
((const unsigned char *)src >= (const unsigned char *)dst + n) ||
686+
((const unsigned char *)dst >= (const unsigned char *)src + n),
686687
"memcpy src/dst overlap");
687688
__CPROVER_precondition(
688689
__CPROVER_r_ok(src, n), "memcpy source region readable");
@@ -692,10 +693,11 @@ void *__builtin___memcpy_chk(void *dst, const void *src, __CPROVER_size_t n, __C
692693

693694
if(n > 0)
694695
{
695-
//for(__CPROVER_size_t i=0; i<n ; i++) ((char *)dst)[i]=((const char *)src)[i];
696-
char src_n[n];
697-
__CPROVER_array_copy(src_n, (char *)src);
698-
__CPROVER_array_replace((char *)dst, src_n);
696+
//for(__CPROVER_size_t i=0; i<n ; i++)
697+
// ((unsigned char *)dst)[i]=((const unsigned char *)src)[i];
698+
unsigned char src_n[n];
699+
__CPROVER_array_copy(src_n, (unsigned char *)src);
700+
__CPROVER_array_replace((unsigned char *)dst, src_n);
699701
}
700702
#endif
701703
return dst;
@@ -857,9 +859,9 @@ void *memmove(void *dest, const void *src, size_t n)
857859

858860
if(n > 0)
859861
{
860-
char src_n[n];
861-
__CPROVER_array_copy(src_n, (char *)src);
862-
__CPROVER_array_replace((char *)dest, src_n);
862+
unsigned char src_n[n];
863+
__CPROVER_array_copy(src_n, (unsigned char *)src);
864+
__CPROVER_array_replace((unsigned char *)dest, src_n);
863865
}
864866
#endif
865867
return dest;
@@ -903,9 +905,9 @@ void *__builtin___memmove_chk(void *dest, const void *src, size_t n, __CPROVER_s
903905

904906
if(n > 0)
905907
{
906-
char src_n[n];
907-
__CPROVER_array_copy(src_n, (char *)src);
908-
__CPROVER_array_replace((char *)dest, src_n);
908+
unsigned char src_n[n];
909+
__CPROVER_array_copy(src_n, (unsigned char *)src);
910+
__CPROVER_array_replace((unsigned char *)dest, src_n);
909911
}
910912
#endif
911913
return dest;

0 commit comments

Comments
 (0)