Skip to content

Commit 517c783

Browse files
authored
Merge pull request #4576 from smowton/smowton/feature/let-expr-for-derefd-pointers
Use let-expression for dereferenced pointers
2 parents 85c8fe0 + 87bcf4a commit 517c783

24 files changed

+456
-6
lines changed

regression/cbmc/double_deref/README

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
This is a batch of tests checking cbmc's behaviour against expressions with more than one deref
2+
operator, such as **p, *(int*)*p, p->field1->field2, etc. At present a directly nested dereference
3+
(**p) is handled by pushing the second deref inside the result of the first. Example:
4+
5+
**p ==>
6+
*(p == &o1 ? o1 : o2) ==>
7+
(p == &o1 ? *o1 : *o2) ==>
8+
(p == &o1 ? (o1 == &o3 ? o3 : o4) : (o2 == &o5 ? o5 : o6))
9+
10+
This deref operator push-in is performed (implicitly) by value_set_dereferencet::dereference, which special-cases
11+
the ternary conditional expression and typecasts of ternary conditionals.
12+
More complicated expressions are handled using a let-expression. Example:
13+
14+
p->field1->field2 ==>
15+
((p == &o1 ? o1 : o2).field1)->field2 ==>
16+
let derefd_pointer = ((p == &o1 ? o1 : o2).field1) in (derefd_pointer == &o3 ? o3 : derefd_pointer == &o4 ? o4 : derefd_pointer == &o5 ? o5 : o6)
17+
18+
Symex executes this let-expression as an auxiliary assignment, such as
19+
derefd_pointer = ((p == &o1 ? o1 : o2).field1)
20+
result = (derefd_pointer == &o3 ? o3 : derefd_pointer == &o4 ? o4 : derefd_pointer == &o5 ? o5 : o6)
21+
22+
The tests in this directory check that auxiliary let-expressions like this are only used when appropriate by inspecting formula VCCs:
23+
double_deref.desc -- a directly nested double-dereference, should not use a let-expression
24+
double_deref_with_cast.desc -- a double-deref with an intervening cast (*(int*)*p for example), should not use a let-expression
25+
double_deref_with_member.desc -- a double-deref with an intervening member expression (p->field1->field2), should use a let-expression
26+
double_deref_with_pointer_arithmetic.desc -- a double-deref with intervening pointer arithmetic (p[idx1][idx2]), should use a let-expression
27+
*_single_alias.desc -- variants of the above where the first dereference points to a single possible object, so no let-expression is necessary
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
#include <assert.h>
3+
#include <stdlib.h>
4+
5+
int main(int argc, char **argv)
6+
{
7+
int **pptr;
8+
int *ptr1 = (int *)malloc(sizeof(int));
9+
*ptr1 = 1;
10+
int *ptr2 = (int *)malloc(sizeof(int));
11+
*ptr2 = 2;
12+
13+
pptr = (argc == 5 ? &ptr1 : &ptr2);
14+
15+
assert(**pptr == argc);
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
double_deref.c
3+
--show-vcc
4+
\{1\} \(main::1::pptr!0@1#2 = address_of\(main::1::ptr[12]!0@1\) \? main::argc!0@1#1 = [12] : main::argc!0@1#1 = [12]\)
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
derefd_pointer::derefd_pointer
9+
--
10+
See README for details about these tests
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
#include <assert.h>
3+
#include <stdlib.h>
4+
5+
int main(int argc, char **argv)
6+
{
7+
int **pptr;
8+
int *ptr1 = (int *)malloc(sizeof(int));
9+
*ptr1 = 1;
10+
11+
pptr = &ptr1;
12+
13+
assert(**pptr == argc);
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
double_deref_single_alias.c
3+
--show-vcc
4+
\{1\} main::argc!0@1#1 = 1
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
derefd_pointer::derefd_pointer
9+
--
10+
See README for details about these tests
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
2+
#include <assert.h>
3+
#include <stdlib.h>
4+
5+
int main(int argc, char **argv)
6+
{
7+
void **pptr;
8+
int *ptr1 = (int *)malloc(sizeof(int));
9+
*ptr1 = 1;
10+
int *ptr2 = (int *)malloc(sizeof(int));
11+
*ptr2 = 2;
12+
13+
pptr = (argc == 1 ? &ptr1 : &ptr2);
14+
15+
assert(*(int *)*pptr == argc);
16+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
double_deref_with_cast.c
3+
--show-vcc
4+
\{1\} \(cast\(main::1::pptr!0@1#2, signedbv\[32\]\*\*\) = address_of\(main::1::ptr2!0@1\) \? main::argc!0@1#1 = 2 \: main::argc!0@1#1 = 1\)
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
derefd_pointer::derefd_pointer
9+
--
10+
See README for details about these tests
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
2+
#include <assert.h>
3+
#include <stdlib.h>
4+
5+
int main(int argc, char **argv)
6+
{
7+
void **pptr;
8+
int *ptr1 = (int *)malloc(sizeof(int));
9+
*ptr1 = 1;
10+
11+
pptr = &ptr1;
12+
13+
assert(*(int *)*pptr == argc);
14+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
double_deref_with_cast_single_alias.c
3+
--show-vcc
4+
\{1\} main::argc!0@1#1 = 1
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
derefd_pointer::derefd_pointer
9+
--
10+
See README for details about these tests
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
#include <assert.h>
3+
#include <stdlib.h>
4+
5+
struct container
6+
{
7+
int *iptr;
8+
};
9+
10+
int main(int argc, char **argv)
11+
{
12+
struct container *cptr;
13+
struct container container1, container2;
14+
container1.iptr = (int *)malloc(sizeof(int));
15+
*container1.iptr = 1;
16+
container2.iptr = (int *)malloc(sizeof(int));
17+
*container2.iptr = 2;
18+
19+
cptr = (argc == 1 ? &container1 : &container2);
20+
21+
assert(*(cptr->iptr) == argc);
22+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
double_deref_with_member.c
3+
--show-vcc
4+
^\{-[0-9]+\} derefd_pointer::derefd_pointer!0#1 = \(main::1::cptr!0@1#2 = address_of\(main::1::container[12]!0@1\) \? address_of\(symex_dynamic::dynamic_object[12]\) : address_of\(symex_dynamic::dynamic_object[12]\)\)
5+
^\{1\} \(derefd_pointer::derefd_pointer!0#1 = address_of\(symex_dynamic::dynamic_object[12]\) \? main::argc!0@1#1 = 2 : main::argc!0@1#1 = 1\)
6+
^EXIT=0$
7+
^SIGNAL=0$
8+
--
9+
--
10+
See README for details about these tests
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
2+
#include <assert.h>
3+
#include <stdlib.h>
4+
5+
struct container
6+
{
7+
int *iptr;
8+
};
9+
10+
int main(int argc, char **argv)
11+
{
12+
struct container *cptr;
13+
struct container container1;
14+
container1.iptr = (int *)malloc(sizeof(int));
15+
*container1.iptr = 1;
16+
17+
cptr = &container1;
18+
19+
assert(*(cptr->iptr) == argc);
20+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
double_deref_with_member_single_alias.c
3+
--show-vcc
4+
\{1\} main::argc!0@1#1 = 1
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
derefd_pointer::derefd_pointer
9+
--
10+
See README for details about these tests
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
2+
#include <assert.h>
3+
#include <stdlib.h>
4+
5+
struct container
6+
{
7+
int *iptr;
8+
};
9+
10+
int main(int argc, char **argv)
11+
{
12+
int **new_ptrs = malloc(2 * sizeof(int *));
13+
int *iptr1 = (int *)malloc(sizeof(int));
14+
*iptr1 = 1;
15+
int *iptr2 = (int *)malloc(sizeof(int));
16+
*iptr2 = 2;
17+
18+
new_ptrs[argc % 2] = iptr1;
19+
new_ptrs[1 - (argc % 2)] = iptr2;
20+
21+
assert(*(new_ptrs[argc % 2]) == argc);
22+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
double_deref_with_pointer_arithmetic.c
3+
--show-vcc
4+
^\{-[0-9]+\} derefd_pointer::derefd_pointer!0#1 = symex_dynamic::dynamic_object1#3\[cast\(mod #source_location=""\(main::argc!0@1#1, 2\), signedbv\[64\]\)\]
5+
^\{1\} \(derefd_pointer::derefd_pointer!0#1 = address_of\(symex_dynamic::dynamic_object[23]\) \? main::argc!0@1#1 = 2 : main::argc!0@1#1 = 1\)
6+
^EXIT=0$
7+
^SIGNAL=0$
8+
--
9+
--
10+
See README for details about these tests
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
2+
#include <assert.h>
3+
#include <stdlib.h>
4+
5+
struct container
6+
{
7+
int *iptr;
8+
};
9+
10+
int main(int argc, char **argv)
11+
{
12+
int **new_ptrs = malloc(2 * sizeof(int *));
13+
int *iptr1 = (int *)malloc(sizeof(int));
14+
*iptr1 = 1;
15+
16+
new_ptrs[argc % 2] = iptr1;
17+
18+
assert(*(new_ptrs[argc % 2]) == argc);
19+
}
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
CORE
2+
double_deref_with_pointer_arithmetic_single_alias.c
3+
--show-vcc
4+
\{1\} main::argc!0@1#1 = 1
5+
^EXIT=0$
6+
^SIGNAL=0$
7+
--
8+
derefd_pointer::derefd_pointer
9+
--
10+
See README for details about these tests

src/goto-symex/goto_symex.h

Lines changed: 44 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -218,15 +218,31 @@ class goto_symext
218218
const get_goto_functiont &get_goto_function);
219219

220220
/// \brief Called for each step in the symbolic execution
221+
/// This calls \ref print_symex_step to print symex's current instruction if
222+
/// required, then \ref execute_next_instruction to execute the actual
223+
/// instruction body.
224+
/// \param get_goto_function: The delegate to retrieve function bodies (see
225+
/// \ref get_goto_functiont)
226+
/// \param state: Symbolic execution state for current instruction
227+
virtual void
228+
symex_step(const get_goto_functiont &get_goto_function, statet &state);
229+
230+
/// \brief Executes the instruction `state.source.pc`
221231
/// Case-switches over the type of the instruction being executed and calls
222232
/// another function appropriate to the instruction type, for example
223233
/// \ref symex_function_call if the current instruction is a function call,
224234
/// \ref symex_goto if the current instruction is a goto, etc.
225235
/// \param get_goto_function: The delegate to retrieve function bodies (see
226236
/// \ref get_goto_functiont)
227237
/// \param state: Symbolic execution state for current instruction
228-
virtual void
229-
symex_step(const get_goto_functiont &get_goto_function, statet &state);
238+
void execute_next_instruction(
239+
const get_goto_functiont &get_goto_function,
240+
statet &state);
241+
242+
/// Kills any variables in \ref instruction_local_symbols (these are currently
243+
/// always let-bound variables defined in the course of executing the current
244+
/// instruction), then clears \ref instruction_local_symbols.
245+
void kill_instruction_local_symbols(statet &state);
230246

231247
/// Prints the route of symex as it walks through the code. Used for
232248
/// debugging.
@@ -270,6 +286,11 @@ class goto_symext
270286
/// instruction
271287
unsigned atomic_section_counter;
272288

289+
/// Variables that should be killed at the end of the current symex_step
290+
/// invocation. Currently this is used for let-bound variables executed during
291+
/// symex, whose lifetime is at most one instruction long.
292+
std::vector<symbol_exprt> instruction_local_symbols;
293+
273294
/// The messaget to write log messages to
274295
mutable messaget log;
275296

@@ -288,6 +309,11 @@ class goto_symext
288309

289310
void trigger_auto_object(const exprt &, statet &);
290311
void initialize_auto_object(const exprt &, statet &);
312+
313+
/// Given an expression, find the root object and the offset into it.
314+
///
315+
/// The extra complication to be considered here is that the expression may
316+
/// have any number of ternary expressions mixed with type casts.
291317
void process_array_expr(statet &, exprt &);
292318
exprt make_auto_object(const typet &, statet &);
293319
virtual void dereference(exprt &, statet &, bool write);
@@ -321,6 +347,10 @@ class goto_symext
321347
/// Symbolically execute a DEAD instruction
322348
/// \param state: Symbolic execution state for current instruction
323349
virtual void symex_dead(statet &state);
350+
/// Kill a symbol, as if it had been the subject of a DEAD instruction
351+
/// \param state: Symbolic execution state
352+
/// \param symbol_expr: Symbol to kill
353+
void symex_dead(statet &state, const symbol_exprt &symbol_expr);
324354
/// Symbolically execute an OTHER instruction
325355
/// \param state: Symbolic execution state for current instruction
326356
virtual void symex_other(statet &state);
@@ -503,6 +533,18 @@ class goto_symext
503533

504534
typedef symex_targett::assignment_typet assignment_typet;
505535

536+
/// Execute any let expressions in \p expr using \ref symex_assign_symbol.
537+
/// The assignments will be made in bottom-up topological but otherwise
538+
/// arbitrary order (i.e. in `(let x = let y = 0 in x + y) + (let z = 0 in z)
539+
/// we will define `y` before `x`, but `z` and `x` could come in either order)
540+
void lift_lets(statet &, exprt &);
541+
542+
/// Execute a single let expression, which should not have any nested let
543+
/// expressions (use \ref lift_lets instead if there might be).
544+
/// Records the newly-defined variable in \ref instruction_local_symbols,
545+
/// meaning it will be killed when \ref symex_step concludes.
546+
void lift_let(statet &state, const let_exprt &let_expr);
547+
506548
void symex_assign_rec(
507549
statet &,
508550
const exprt &lhs,

0 commit comments

Comments
 (0)