Skip to content

Commit 65c42a3

Browse files
authored
Merge pull request #6818 from remi-delmas-3000/assigns-clause-local-arrays
CONTRACTS: Handle locals with composite types when checking assigns clauses
2 parents 1d608d2 + d0b5fb4 commit 65c42a3

File tree

7 files changed

+296
-41
lines changed

7 files changed

+296
-41
lines changed
Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,104 @@
1+
struct st1
2+
{
3+
int a;
4+
int arr[10];
5+
};
6+
7+
struct st2
8+
{
9+
int a;
10+
struct st1 arr[10];
11+
};
12+
13+
struct st3
14+
{
15+
struct st1 *s1;
16+
struct st2 *s2;
17+
struct st1 arr1[10];
18+
struct st2 arr2[10];
19+
};
20+
21+
enum tagt
22+
{
23+
CHAR,
24+
INT,
25+
CHAR_PTR,
26+
INT_ARR,
27+
STRUCT_ST1_ARR
28+
};
29+
30+
// clang-format off
31+
struct taggedt {
32+
enum tagt tag;
33+
union {
34+
struct{ char a; };
35+
struct{ int b; };
36+
struct{ char *ptr; };
37+
struct{ int arr[10]; };
38+
struct{ struct st1 arr1[10]; };
39+
};
40+
};
41+
// clang-format on
42+
43+
int foo(int i) __CPROVER_assigns()
44+
{
45+
// all accesses to locals should pass
46+
int arr[10];
47+
struct st1 s1;
48+
struct st2 s2;
49+
struct st1 dirty_s1;
50+
struct st3 s3;
51+
s3.s1 = &dirty_s1;
52+
s3.s2 = malloc(sizeof(struct st2));
53+
54+
if(0 <= i && i < 10)
55+
{
56+
arr[i] = 0;
57+
s1.a = 0;
58+
s1.arr[i] = 0;
59+
s2.a = 0;
60+
s2.arr[i].a = 0;
61+
s2.arr[i].arr[i] = 0;
62+
s3.s1->a = 0;
63+
s3.s1->arr[i] = 0;
64+
s3.s2->a = 0;
65+
s3.s2->arr[i].a = 0;
66+
s3.s2->arr[i].arr[i] = 0;
67+
*(&(s3.s2->arr[i].arr[0]) + i) = 0;
68+
(&(s3.arr1[0]) + i)->arr[i] = 0;
69+
(&((&(s3.arr2[0]) + i)->arr[i]))->a = 0;
70+
}
71+
72+
struct taggedt tagged;
73+
switch(tagged.tag)
74+
{
75+
case CHAR:
76+
tagged.a = 0;
77+
break;
78+
case INT:
79+
tagged.b = 0;
80+
break;
81+
case CHAR_PTR:
82+
tagged.ptr = 0;
83+
break;
84+
case INT_ARR:
85+
if(0 <= i && i < 10)
86+
tagged.arr[i] = 0;
87+
break;
88+
case STRUCT_ST1_ARR:
89+
if(0 <= i && i < 10)
90+
{
91+
tagged.arr1[i].a = 0;
92+
tagged.arr1[i].arr[i] = 0;
93+
}
94+
break;
95+
}
96+
97+
return 0;
98+
}
99+
100+
void main()
101+
{
102+
int i;
103+
foo(i);
104+
}
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
main.c
3+
--enforce-contract foo
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
--
8+
--
9+
Checks that assigns clause checking explicitly checks assignments to locally
10+
declared symbols with composite types, when they are dirty.
11+
Out of bounds accesses to locally declared arrays, structs, etc.
12+
will be detected by assigns clause checking.

regression/contracts/assigns_type_checking_valid_cases/main.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,8 @@ void foo4(int a, int *b, int *c) __CPROVER_requires(c != NULL)
4141

4242
void foo5(struct buf buffer) __CPROVER_assigns()
4343
{
44+
// these are assignments to the function parameter which is a local symbol
45+
// and should not generate checks
4446
buffer.data = NULL;
4547
buffer.len = 0;
4648
}

regression/contracts/assigns_type_checking_valid_cases/test.desc

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@ main.c
88
^\[foo3.assigns.\d+\] line \d+ Check that y is assignable: SUCCESS$
99
^\[foo4.assigns.\d+\] line \d+ Check that \*c is assignable: SUCCESS$
1010
^\[foo4.assigns.\d+\] line \d+ Check that \*x is assignable: SUCCESS$
11-
^\[foo5.assigns.\d+\] line \d+ Check that buffer.data is assignable: SUCCESS$
12-
^\[foo5.assigns.\d+\] line \d+ Check that buffer.len is assignable: SUCCESS$
1311
^\[foo6.assigns.\d+\] line \d+ Check that \*buffer->data is assignable: SUCCESS$
1412
^\[foo6.assigns.\d+\] line \d+ Check that buffer->len is assignable: SUCCESS$
1513
^\[foo7.assigns.\d+\] line \d+ Check that \*buffer->data is assignable: SUCCESS$

src/goto-instrument/contracts/instrument_spec_assigns.cpp

Lines changed: 99 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ Date: January 2022
2525

2626
#include "utils.h"
2727

28+
/// header for log messages
29+
static const char LOG_HEADER[] = "assigns clause checking: ";
30+
2831
/// Pragma used to mark assignments to static locals that need to be propagated
2932
static const char PROPAGATE_STATIC_LOCAL_PRAGMA[] =
3033
"contracts:propagate-static-local";
@@ -694,8 +697,9 @@ exprt instrument_spec_assignst::inclusion_check_full(
694697
}
695698

696699
if(allow_null_lhs)
697-
return or_exprt{null_pointer(car.target_start_address()),
698-
and_exprt{car.valid_var(), disjunction(disjuncts)}};
700+
return or_exprt{
701+
null_pointer(car.target_start_address()),
702+
and_exprt{car.valid_var(), disjunction(disjuncts)}};
699703
else
700704
return and_exprt{car.valid_var(), disjunction(disjuncts)};
701705
}
@@ -716,6 +720,8 @@ const car_exprt &instrument_spec_assignst::create_car_from_spec_assigns(
716720
}
717721
else
718722
{
723+
log.debug() << LOG_HEADER << "creating CAR for assigns clause target "
724+
<< format(condition) << ": " << format(target) << messaget::eom;
719725
from_spec_assigns.insert({key, create_car_expr(condition, target)});
720726
return from_spec_assigns.find(key)->second;
721727
}
@@ -734,6 +740,8 @@ const car_exprt &instrument_spec_assignst::create_car_from_stack_alloc(
734740
}
735741
else
736742
{
743+
log.debug() << LOG_HEADER << "creating CAR for stack-allocated target "
744+
<< format(target) << messaget::eom;
737745
from_stack_alloc.insert({target, create_car_expr(true_exprt{}, target)});
738746
return from_stack_alloc.find(target)->second;
739747
}
@@ -752,6 +760,8 @@ instrument_spec_assignst::create_car_from_heap_alloc(const exprt &target)
752760
}
753761
else
754762
{
763+
log.debug() << LOG_HEADER << "creating CAR for heap-allocated target "
764+
<< format(target) << messaget::eom;
755765
from_heap_alloc.insert({target, create_car_expr(true_exprt{}, target)});
756766
return from_heap_alloc.find(target)->second;
757767
}
@@ -770,6 +780,8 @@ const car_exprt &instrument_spec_assignst::create_car_from_static_local(
770780
}
771781
else
772782
{
783+
log.debug() << LOG_HEADER << "creating CAR for static local target "
784+
<< format(target) << messaget::eom;
773785
from_static_local.insert({target, create_car_expr(true_exprt{}, target)});
774786
return from_static_local.find(target)->second;
775787
}
@@ -809,44 +821,107 @@ bool instrument_spec_assignst::must_check_assign(
809821
skip_function_paramst skip_function_params,
810822
const optionalt<cfg_infot> cfg_info_opt)
811823
{
812-
if(
813-
const auto &symbol_expr =
814-
expr_try_dynamic_cast<symbol_exprt>(target->assign_lhs()))
824+
log.debug().source_location = target->source_location();
825+
826+
if(can_cast_expr<symbol_exprt>(target->assign_lhs()))
815827
{
828+
const auto &symbol_expr = to_symbol_expr(target->assign_lhs());
816829
if(
817830
skip_function_params == skip_function_paramst::NO &&
818-
ns.lookup(symbol_expr->get_identifier()).is_parameter)
831+
ns.lookup(symbol_expr.get_identifier()).is_parameter)
819832
{
833+
log.debug() << LOG_HEADER << "checking assignment to function parameter "
834+
<< format(symbol_expr) << messaget::eom;
820835
return true;
821836
}
822837

823838
if(cfg_info_opt.has_value())
824-
return !cfg_info_opt.value().is_local(symbol_expr->get_identifier());
839+
{
840+
if(cfg_info_opt.value().is_local(symbol_expr.get_identifier()))
841+
{
842+
log.debug() << LOG_HEADER
843+
<< "skipping checking on assignment to local symbol "
844+
<< format(symbol_expr) << messaget::eom;
845+
return false;
846+
}
847+
else
848+
{
849+
log.debug() << LOG_HEADER << "checking assignment to non-local symbol "
850+
<< format(symbol_expr) << messaget::eom;
851+
return true;
852+
}
853+
}
854+
log.debug() << LOG_HEADER << "checking assignment to symbol "
855+
<< format(symbol_expr) << messaget::eom;
856+
return true;
857+
}
858+
else
859+
{
860+
// This is not a mere symbol.
861+
// Since non-dirty locals are not tracked explicitly in the write set,
862+
// we need to skip the check if we can verify that the expression describes
863+
// an access to a non-dirty local symbol or an input parameter,
864+
// otherwise the check will fail.
865+
// In addition, the expression shall not contain address_of or dereference
866+
// operators, regardless of the base symbol/object on which they apply.
867+
// If the expression contains an address_of operation, the assignment gets
868+
// checked. If the base object is a local or a parameter, it will also be
869+
// flagged as dirty and will be tracked explicitly, and the check will pass.
870+
// If the expression contains a dereference operation, the assignment gets
871+
// checked. If the dereferenced address was computed from a local object,
872+
// from a function parameter or returned by a local malloc,
873+
// then the object will be tracked explicitly and the check will pass.
874+
// In all other cases (address of a non-local object, or dereference of
875+
// a non-locally computed address) the location must be given explicitly
876+
// in the assigns clause to be tracked and we must check the assignment.
877+
if(
878+
cfg_info_opt.has_value() &&
879+
cfg_info_opt.value().is_local_composite_access(target->assign_lhs()))
880+
{
881+
log.debug()
882+
<< LOG_HEADER
883+
<< "skipping check on assignment to local composite member expression "
884+
<< format(target->assign_lhs()) << messaget::eom;
885+
return false;
886+
}
887+
log.debug() << LOG_HEADER << "checking assignment to expression "
888+
<< format(target->assign_lhs()) << messaget::eom;
889+
return true;
825890
}
891+
}
826892

827-
return true;
893+
/// Track the symbol iff we have no cfg_infot, or we have a cfg_infot and the
894+
/// symbol is not a local or is a dirty local.
895+
bool instrument_spec_assignst::must_track_decl_or_dead(
896+
const irep_idt &ident,
897+
const optionalt<cfg_infot> &cfg_info_opt) const
898+
{
899+
return !cfg_info_opt.has_value() ||
900+
(cfg_info_opt.has_value() &&
901+
cfg_info_opt.value().is_not_local_or_dirty_local(ident));
828902
}
829903

830-
/// Returns true iff a `DECL x` must be added to the local write set.
831-
///
832-
/// A variable is called 'dirty' if its address gets taken at some point in
833-
/// the program.
834-
///
835-
/// Assuming the goto program is obtained from a structured C program that
836-
/// passed C compiler checks, non-dirty variables can only be assigned to
837-
/// directly by name, cannot escape their lexical scope, and are always safe
838-
/// to assign. Hence, we only track dirty variables in the write set.
904+
/// Returns true iff a `DECL x` must be explicitly tracked in the write set.
839905
bool instrument_spec_assignst::must_track_decl(
840906
const goto_programt::const_targett &target,
841907
const optionalt<cfg_infot> &cfg_info_opt) const
842908
{
843-
if(cfg_info_opt.has_value())
909+
log.debug().source_location = target->source_location();
910+
if(must_track_decl_or_dead(
911+
target->decl_symbol().get_identifier(), cfg_info_opt))
844912
{
845-
return cfg_info_opt.value().is_not_local_or_dirty_local(
846-
target->decl_symbol().get_identifier());
913+
log.debug() << LOG_HEADER << "explicitly tracking "
914+
<< format(target->decl_symbol()) << " as assignable"
915+
<< messaget::eom;
916+
return true;
917+
}
918+
else
919+
{
920+
log.debug() << LOG_HEADER << "implicitly tracking "
921+
<< format(target->decl_symbol())
922+
<< " as assignable (non-dirty local)" << messaget::eom;
923+
return false;
847924
}
848-
// Unless proved non-dirty by the CFG analysis we assume it is dirty.
849-
return true;
850925
}
851926

852927
/// Returns true iff a `DEAD x` must be processed to upate the local write set.
@@ -855,12 +930,8 @@ bool instrument_spec_assignst::must_track_dead(
855930
const goto_programt::const_targett &target,
856931
const optionalt<cfg_infot> &cfg_info_opt) const
857932
{
858-
// Unless proved non-dirty by the CFG analysis we assume it is dirty.
859-
if(!cfg_info_opt.has_value())
860-
return true;
861-
862-
return cfg_info_opt.value().is_not_local_or_dirty_local(
863-
target->dead_symbol().get_identifier());
933+
return must_track_decl_or_dead(
934+
target->dead_symbol().get_identifier(), cfg_info_opt);
864935
}
865936

866937
void instrument_spec_assignst::instrument_assign_statement(

src/goto-instrument/contracts/instrument_spec_assigns.h

Lines changed: 17 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -574,25 +574,31 @@ class instrument_spec_assignst
574574
const car_exprt &freed_car,
575575
goto_programt &dest) const;
576576

577-
/// Returns true iff a `DECL x` must be added to the local write set.
578-
///
579-
/// A variable is called 'dirty' if its address gets taken at some point in
580-
/// the program.
581-
///
582-
/// Assuming the goto program is obtained from a structured C program that
583-
/// passed C compiler checks, non-dirty variables can only be assigned to
584-
/// directly by name, cannot escape their lexical scope, and are always safe
585-
/// to assign. Hence, we only track dirty variables in the write set.
577+
/// Returns true iff a `DECL x` must be explicitly added to the write set.
578+
/// @see must_track_decl_or_dead.
586579
bool must_track_decl(
587580
const goto_programt::const_targett &target,
588581
const optionalt<cfg_infot> &cfg_info_opt) const;
589582

590-
/// Returns true iff a `DEAD x` must be processed to update the local write
591-
/// set. The conditions are the same than for tracking a `DECL x` instruction.
583+
/// Returns true iff a `DEAD x` must be processed to update the write set.
584+
/// @see must_track_decl_or_dead.
592585
bool must_track_dead(
593586
const goto_programt::const_targett &target,
594587
const optionalt<cfg_infot> &cfg_info_opt) const;
595588

589+
/// \brief Returns true iff a function-local symbol must be tracked.
590+
///
591+
/// A local is called 'dirty' if its address gets taken at some point in
592+
/// the program.
593+
///
594+
/// Assuming the goto program is obtained from a structured C program that
595+
/// passed C compiler checks, non-dirty locals can only be assigned to
596+
/// directly by name, cannot escape their lexical scope, and are always safe
597+
/// to assign. Hence, we only track dirty locals in the write set.
598+
bool must_track_decl_or_dead(
599+
const irep_idt &ident,
600+
const optionalt<cfg_infot> &cfg_info_opt) const;
601+
596602
/// Returns true iff an `ASSIGN lhs := rhs` instruction must be instrumented.
597603
bool must_check_assign(
598604
const goto_programt::const_targett &target,

0 commit comments

Comments
 (0)