Skip to content

Commit 0b64870

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 60d683f commit 0b64870

File tree

4 files changed

+155
-12
lines changed

4 files changed

+155
-12
lines changed
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
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
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
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
45+
}
46+
47+
free(z);
48+
49+
*x = 1;
50+
x = 0;
51+
}
52+
53+
int main()
54+
{
55+
int z;
56+
int *x = malloc(sizeof(int));
57+
foo(&z, &x);
58+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo _ --malloc-may-fail --malloc-fail-null --pointer-primitive-check
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Checks whether CBMC properly tracks invalidated CARs
10+
on free and DEAD instructions.

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: 86 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -669,21 +669,75 @@ void code_contractst::instrument_call_statement(
669669
auto snapshot_instructions = car->generate_snapshot_instructions();
670670
insert_before_swap_and_advance(
671671
body, instruction_it, snapshot_instructions);
672-
// since CAR was inserted *after* the malloc,
672+
// since CAR was inserted *after* the malloc call,
673673
// move the instruction pointer backward,
674674
// because the caller increments it in a `for` loop
675675
instruction_it--;
676676
}
677-
return;
678677
}
679678
else if(callee_name == "free")
680679
{
681-
add_containment_check(
682-
body,
683-
assigns,
684-
instruction_it,
685-
pointer_object(instruction_it->call_arguments().front()));
686-
return;
680+
const auto &pointer = instruction_it->call_arguments().front();
681+
const auto object = pointer_object(pointer);
682+
add_containment_check(body, assigns, instruction_it, object);
683+
684+
// Since the argument to free would be an "alias" (but not identical)
685+
// to an existing CAR's source_expr, structural equality wouldn't work.
686+
// Moreover, multiple CARs in the writeset might be aliased to the
687+
// same underlying object.
688+
// So, we first find the corresponding CAR using `same_object` checks.
689+
goto_programt invalidation_instructions;
690+
std::unordered_set<exprt, irep_hash> temps;
691+
692+
for(const auto &car : assigns.get_write_set())
693+
{
694+
const auto object_validity_var_addr =
695+
new_tmp_symbol(
696+
pointer_type(bool_typet{}),
697+
instruction_it->source_location,
698+
symbol_table.lookup_ref(function).mode,
699+
symbol_table)
700+
.symbol_expr();
701+
temps.insert(object_validity_var_addr);
702+
703+
invalidation_instructions.add(goto_programt::make_decl(
704+
object_validity_var_addr, instruction_it->source_location));
705+
// if the CAR was defined on the same_object as the one being `free`d,
706+
// record its validity variable's address, otherwise record NULL
707+
invalidation_instructions.add(goto_programt::make_assignment(
708+
object_validity_var_addr,
709+
if_exprt{
710+
and_exprt{car.validity_condition_var,
711+
same_object(pointer, car.lower_bound_address_var)},
712+
address_of_exprt{car.validity_condition_var},
713+
null_pointer_exprt{to_pointer_type(object_validity_var_addr.type())}},
714+
instruction_it->source_location));
715+
}
716+
717+
insert_before_swap_and_advance(
718+
body, instruction_it, invalidation_instructions);
719+
720+
// move past the call and then insert the invalidation instructions
721+
instruction_it++;
722+
// invalidate all recorded CAR validity variables
723+
for(const auto &car_validity_addr : temps)
724+
{
725+
goto_programt skip_program;
726+
const auto skip_target = skip_program.add(
727+
goto_programt::make_skip(instruction_it->source_location));
728+
invalidation_instructions.add(goto_programt::make_goto(
729+
skip_target,
730+
null_pointer(car_validity_addr),
731+
instruction_it->source_location));
732+
invalidation_instructions.add(goto_programt::make_assignment(
733+
dereference_exprt{car_validity_addr},
734+
false_exprt{},
735+
instruction_it->source_location));
736+
invalidation_instructions.destructive_append(skip_program);
737+
}
738+
insert_before_swap_and_advance(
739+
body, instruction_it, invalidation_instructions);
740+
instruction_it--;
687741
}
688742
}
689743

@@ -823,9 +877,9 @@ void code_contractst::check_frame_conditions(
823877
auto snapshot_instructions = car->generate_snapshot_instructions();
824878
insert_before_swap_and_advance(
825879
body, instruction_it, snapshot_instructions);
826-
// since CAR was inserted *after* the DECL,
880+
// since CAR was inserted *after* the DECL instruction,
827881
// move the instruction pointer backward,
828-
// because the caller increments it in a `for` loop
882+
// because the `for` loop takes care of the increment
829883
instruction_it--;
830884
}
831885
else if(instruction_it->is_assign())
@@ -838,7 +892,28 @@ void code_contractst::check_frame_conditions(
838892
}
839893
else if(instruction_it->is_dead())
840894
{
841-
assigns.remove_from_write_set(instruction_it->dead_symbol());
895+
const auto &symbol = instruction_it->dead_symbol();
896+
// CAR equality and hash are defined on source_expr alone,
897+
// therefore this temporary CAR should be "found"
898+
const auto &symbol_car = assigns.get_write_set().find(
899+
assigns_clauset::conditional_address_ranget{assigns, symbol});
900+
if(symbol_car != assigns.get_write_set().end())
901+
{
902+
instruction_it++;
903+
auto invalidation_assignment = goto_programt::make_assignment(
904+
symbol_car->validity_condition_var,
905+
false_exprt{},
906+
instruction_it->source_location);
907+
// note that instruction_it is not advanced by this call,
908+
// so no need to move it backwards
909+
body.insert_before_swap(instruction_it, invalidation_assignment);
910+
}
911+
else
912+
{
913+
throw incorrect_goto_program_exceptiont(
914+
"Found a `DEAD` variable without corresponding `DECL`!",
915+
instruction_it->source_location);
916+
}
842917
}
843918
else if(
844919
instruction_it->is_other() &&

0 commit comments

Comments
 (0)