Skip to content

Commit 998d141

Browse files
committed
Fix invalidation of CARs on free and DEAD
Previously, corresponding CARs were completely removed on DEAD instructions. While this is "okay" for unconditionally dead symbols, it resulted in spurious non-assignable errors when symbols were only conditionally DEAD, e.g. RETURN within a conditional block. Previously, invalid CARs from free calls were simply left behind, because (in theory) the CAR validation condition should automatically guard against conditions. However, in both these cases, we need to check dead/deallocated status of individual objects to avoid spurious errors from CBMC's internal pointer checks, which only track a single dead/deallocated object nondeterministically.
1 parent 8da66da commit 998d141

File tree

5 files changed

+221
-12
lines changed

5 files changed

+221
-12
lines changed
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
#include <assert.h>
2+
#include <stdlib.h>
3+
4+
int foo(int *x, int **p) __CPROVER_assigns(*x, *p, **p)
5+
{
6+
if(p && *p)
7+
**p = 0;
8+
9+
{
10+
int y;
11+
y = 1;
12+
*x = 0;
13+
14+
if(nondet_bool())
15+
return 0;
16+
17+
if(p)
18+
*p = &y;
19+
20+
// y goes DEAD here, unconditionally
21+
}
22+
23+
if(p && *p)
24+
**p = 0;
25+
26+
int a = 1;
27+
28+
if(x == NULL)
29+
{
30+
return 1;
31+
// a goes DEAD here, conditionally
32+
}
33+
34+
int *z = malloc(100 * sizeof(int));
35+
int *w = NULL;
36+
37+
if(z)
38+
{
39+
w = &(z[10]);
40+
*w = 0;
41+
42+
int *q = &(z[20]);
43+
*q = 1;
44+
// q goes DEAD here, unconditionally
45+
}
46+
47+
free(z);
48+
49+
z = malloc(sizeof(int));
50+
if(nondet_bool())
51+
{
52+
free(z);
53+
// here z is deallocated, conditionally
54+
}
55+
56+
*x = 1;
57+
x = 0;
58+
}
59+
60+
int main()
61+
{
62+
int z;
63+
int *x = malloc(sizeof(int));
64+
foo(&z, &x);
65+
}
Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo _ --malloc-may-fail --malloc-fail-null --pointer-primitive-check
4+
^\[foo.\d+\] line \d+ Check that \*\(\*p\) is assignable: SUCCESS$
5+
^\[foo.\d+\] line \d+ Check that \*\(\*p\) is assignable: FAILURE$
6+
^\[foo.\d+\] line \d+ Check that \*p is assignable: SUCCESS$
7+
^\[foo.\d+\] line \d+ Check that \*q is assignable: SUCCESS$
8+
^\[foo.\d+\] line \d+ Check that \*w is assignable: SUCCESS$
9+
^\[foo.\d+\] line \d+ Check that \*x is assignable: SUCCESS$
10+
^\[foo.\d+\] line \d+ Check that a is assignable: SUCCESS$
11+
^\[foo.\d+\] line \d+ Check that q is assignable: SUCCESS$
12+
^\[foo.\d+\] line \d+ Check that w is assignable: SUCCESS$
13+
^\[foo.\d+\] line \d+ Check that x is assignable: SUCCESS$
14+
^\[foo.\d+\] line \d+ Check that y is assignable: SUCCESS$
15+
^\[foo.\d+\] line \d+ Check that z is assignable: SUCCESS$
16+
^\[foo.\d+\] line \d+ Check that POINTER_OBJECT\(\(void \*\)z\) is assignable: SUCCESS$
17+
^EXIT=10$
18+
^SIGNAL=0$
19+
^VERIFICATION FAILED$
20+
--
21+
^\[foo.\d+\] line \d+ Check that \*p is assignable: FAILURE$
22+
^\[foo.\d+\] line \d+ Check that \*q is assignable: FAILURE$
23+
^\[foo.\d+\] line \d+ Check that \*w is assignable: FAILURE$
24+
^\[foo.\d+\] line \d+ Check that \*x is assignable: FAILURE$
25+
^\[foo.\d+\] line \d+ Check that a is assignable: FAILURE$
26+
^\[foo.\d+\] line \d+ Check that q is assignable: FAILURE$
27+
^\[foo.\d+\] line \d+ Check that w is assignable: FAILURE$
28+
^\[foo.\d+\] line \d+ Check that x is assignable: FAILURE$
29+
^\[foo.\d+\] line \d+ Check that y is assignable: FAILURE$
30+
^\[foo.\d+\] line \d+ Check that z is assignable: FAILURE$
31+
^\[foo.\d+\] line \d+ Check that POINTER_OBJECT\(\(void \*\)z\) is assignable: FAILURE$
32+
^.*tmp_cc\$\d+.*: FAILURE$
33+
--
34+
Checks that invalidated CARs are correctly tracked on `free` and `DEAD`.
35+
36+
Since several variables are assigned multiple times,
37+
we rule out all failures using the negative regex matches above.
38+
39+
We also check (using positive regex matches above) that
40+
`**p` should be assignable in one case and should not be assignable in the other.
41+
42+
Finally, we check that there should be no "internal" assertion failures
43+
on `tmp_cc` variables used to track CARs.

regression/contracts/test_aliasing_ensure_indirect/test.desc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
CORE
22
main.c
3-
--enforce-contract foo --enforce-contract bar
3+
--enforce-contract foo
44
^EXIT=0$
55
^SIGNAL=0$
66
\[postcondition.\d+\] file main.c line \d+ Check ensures clause: SUCCESS

src/goto-instrument/contracts/contracts.cpp

Lines changed: 109 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,6 @@ Date: February 2016
3737
#include <util/pointer_predicates.h>
3838
#include <util/replace_symbol.h>
3939

40-
#include "assigns.h"
4140
#include "memory_predicates.h"
4241
#include "utils.h"
4342

@@ -669,21 +668,98 @@ void code_contractst::instrument_call_statement(
669668
auto snapshot_instructions = car->generate_snapshot_instructions();
670669
insert_before_swap_and_advance(
671670
body, instruction_it, snapshot_instructions);
672-
// since CAR was inserted *after* the malloc,
671+
// since CAR was inserted *after* the malloc call,
673672
// move the instruction pointer backward,
674673
// because the caller increments it in a `for` loop
675674
instruction_it--;
676675
}
677-
return;
678676
}
679677
else if(callee_name == "free")
680678
{
681-
add_inclusion_check(
679+
const auto free_car = add_inclusion_check(
682680
body,
683681
assigns,
684682
instruction_it,
685683
pointer_object(instruction_it->call_arguments().front()));
686-
return;
684+
685+
// skip all invalidation business if we're freeing invalid memory
686+
goto_programt alias_checking_instructions, skip_program;
687+
alias_checking_instructions.add(goto_programt::make_goto(
688+
skip_program.add(
689+
goto_programt::make_skip(instruction_it->source_location)),
690+
not_exprt{free_car.validity_condition_var},
691+
instruction_it->source_location));
692+
693+
// Since the argument to free may be an "alias" (but not identical)
694+
// to existing CARs' source_expr, structural equality wouldn't work.
695+
// Moreover, multiple CARs in the writeset might be aliased to the
696+
// same underlying object.
697+
// So, we first find the corresponding CAR using `same_object` checks.
698+
std::unordered_set<exprt, irep_hash> write_set_validity_addrs;
699+
for(const auto &w_car : assigns.get_write_set())
700+
{
701+
const auto object_validity_var_addr =
702+
new_tmp_symbol(
703+
pointer_type(bool_typet{}),
704+
instruction_it->source_location,
705+
symbol_table.lookup_ref(function).mode,
706+
symbol_table)
707+
.symbol_expr();
708+
write_set_validity_addrs.insert(object_validity_var_addr);
709+
710+
alias_checking_instructions.add(goto_programt::make_decl(
711+
object_validity_var_addr, instruction_it->source_location));
712+
// if the CAR was defined on the same_object as the one being `free`d,
713+
// record its validity variable's address, otherwise record NULL
714+
alias_checking_instructions.add(goto_programt::make_assignment(
715+
object_validity_var_addr,
716+
if_exprt{
717+
and_exprt{
718+
w_car.validity_condition_var,
719+
same_object(
720+
free_car.lower_bound_address_var, w_car.lower_bound_address_var)},
721+
address_of_exprt{w_car.validity_condition_var},
722+
null_pointer_exprt{to_pointer_type(object_validity_var_addr.type())}},
723+
instruction_it->source_location));
724+
}
725+
726+
alias_checking_instructions.destructive_append(skip_program);
727+
insert_before_swap_and_advance(
728+
body, instruction_it, alias_checking_instructions);
729+
730+
// move past the call and then insert the invalidation instructions
731+
instruction_it++;
732+
733+
// skip all invalidation business if we're freeing invalid memory
734+
goto_programt invalidation_instructions;
735+
skip_program.clear();
736+
invalidation_instructions.add(goto_programt::make_goto(
737+
skip_program.add(
738+
goto_programt::make_skip(instruction_it->source_location)),
739+
not_exprt{free_car.validity_condition_var},
740+
instruction_it->source_location));
741+
742+
// invalidate all recorded CAR validity variables
743+
for(const auto &w_car_validity_addr : write_set_validity_addrs)
744+
{
745+
goto_programt w_car_skip_program;
746+
invalidation_instructions.add(goto_programt::make_goto(
747+
w_car_skip_program.add(
748+
goto_programt::make_skip(instruction_it->source_location)),
749+
null_pointer(w_car_validity_addr),
750+
instruction_it->source_location));
751+
invalidation_instructions.add(goto_programt::make_assignment(
752+
dereference_exprt{w_car_validity_addr},
753+
false_exprt{},
754+
instruction_it->source_location));
755+
invalidation_instructions.destructive_append(w_car_skip_program);
756+
}
757+
758+
invalidation_instructions.destructive_append(skip_program);
759+
insert_before_swap_and_advance(
760+
body, instruction_it, invalidation_instructions);
761+
762+
instruction_it--;
687763
}
688764
}
689765

@@ -823,9 +899,9 @@ void code_contractst::check_frame_conditions(
823899
auto snapshot_instructions = car->generate_snapshot_instructions();
824900
insert_before_swap_and_advance(
825901
body, instruction_it, snapshot_instructions);
826-
// since CAR was inserted *after* the DECL,
902+
// since CAR was inserted *after* the DECL instruction,
827903
// move the instruction pointer backward,
828-
// because the caller increments it in a `for` loop
904+
// because the `for` loop takes care of the increment
829905
instruction_it--;
830906
}
831907
else if(instruction_it->is_assign())
@@ -838,7 +914,28 @@ void code_contractst::check_frame_conditions(
838914
}
839915
else if(instruction_it->is_dead())
840916
{
841-
assigns.remove_from_write_set(instruction_it->dead_symbol());
917+
const auto &symbol = instruction_it->dead_symbol();
918+
// CAR equality and hash are defined on source_expr alone,
919+
// therefore this temporary CAR should be "found"
920+
const auto &symbol_car = assigns.get_write_set().find(
921+
assigns_clauset::conditional_address_ranget{assigns, symbol});
922+
if(symbol_car != assigns.get_write_set().end())
923+
{
924+
instruction_it++;
925+
auto invalidation_assignment = goto_programt::make_assignment(
926+
symbol_car->validity_condition_var,
927+
false_exprt{},
928+
instruction_it->source_location);
929+
// note that instruction_it is not advanced by this call,
930+
// so no need to move it backwards
931+
body.insert_before_swap(instruction_it, invalidation_assignment);
932+
}
933+
else
934+
{
935+
throw incorrect_goto_program_exceptiont(
936+
"Found a `DEAD` variable without corresponding `DECL`!",
937+
instruction_it->source_location);
938+
}
842939
}
843940
else if(
844941
instruction_it->is_other() &&
@@ -851,7 +948,8 @@ void code_contractst::check_frame_conditions(
851948
}
852949
}
853950

854-
void code_contractst::add_inclusion_check(
951+
const assigns_clauset::conditional_address_ranget
952+
code_contractst::add_inclusion_check(
855953
goto_programt &program,
856954
const assigns_clauset &assigns,
857955
goto_programt::instructionst::iterator &instruction_it,
@@ -868,6 +966,8 @@ void code_contractst::add_inclusion_check(
868966
assertion.instructions.back().source_location.set_comment(
869967
"Check that " + from_expr(ns, expr.id(), expr) + " is assignable");
870968
insert_before_swap_and_advance(program, instruction_it, assertion);
969+
970+
return car;
871971
}
872972

873973
bool code_contractst::enforce_contract(const irep_idt &function)

src/goto-instrument/contracts/contracts.h

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,8 @@ Date: February 2016
3030
#include <util/namespace.h>
3131
#include <util/pointer_expr.h>
3232

33+
#include "assigns.h"
34+
3335
#define FLAG_LOOP_CONTRACTS "apply-loop-contracts"
3436
#define HELP_LOOP_CONTRACTS \
3537
" --apply-loop-contracts\n" \
@@ -44,7 +46,6 @@ Date: February 2016
4446
#define HELP_ENFORCE_CONTRACT \
4547
" --enforce-contract <fun> wrap fun with an assertion of its contract\n"
4648

47-
class assigns_clauset;
4849
class local_may_aliast;
4950
class replace_symbolt;
5051

@@ -134,7 +135,7 @@ class code_contractst
134135

135136
/// Inserts an assertion into the goto program to ensure that
136137
/// an expression is within the assignable memory frame.
137-
void add_inclusion_check(
138+
const assigns_clauset::conditional_address_ranget add_inclusion_check(
138139
goto_programt &,
139140
const assigns_clauset &,
140141
goto_programt::instructionst::iterator &,

0 commit comments

Comments
 (0)