Skip to content

Commit 17fa596

Browse files
authored
Merge pull request diffblue#4386 from smowton/smowton/feature/symex-condition-driving
Symex: propagate assumptions and conditions
2 parents 391738d + e0d4d78 commit 17fa596

File tree

20 files changed

+397
-69
lines changed

20 files changed

+397
-69
lines changed
1010 Bytes
Binary file not shown.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
public class Test {
2+
3+
public static void main(int unknown) {
4+
5+
try {
6+
mayThrow(unknown % 7 == 5);
7+
}
8+
catch(Exception e) {
9+
}
10+
11+
// This test checks that symex can tell there is no exception in flight
12+
// (i.e. @inflight_exception is null) at this point, requiring it to
13+
// converge the `if @inflight_exception == null` test on mayThrow's return
14+
// with the explicit `@inflight_exception = null;` assignment that concludes
15+
// the catch block.
16+
17+
// If it knows that, it will also know the following catch block is
18+
// unreachable; if not it will pursue both paths.
19+
try {
20+
mayThrow(false);
21+
}
22+
catch(Exception e) {
23+
unreachable();
24+
}
25+
26+
// Try it once more, to check we also know after an unreachable catch that
27+
// there is still no inflight exception
28+
try {
29+
mayThrow(false);
30+
}
31+
catch(Exception e) {
32+
unreachable();
33+
}
34+
35+
}
36+
37+
public static void mayThrow(boolean shouldThrow) throws Exception {
38+
if(shouldThrow)
39+
throw new Exception();
40+
}
41+
42+
public static void unreachable() { assert false; }
43+
44+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
CORE
2+
Test.class
3+
--function Test.main
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
1 remaining after simplification$
8+
--
9+
^warning: ignoring
10+
--
11+
The function "unreachable" should be successfully noted as unreachable by symex,
12+
but the final uncaught-exception test in __CPROVER__start is not yet decidable
13+
in symex, as it requires symex to note that within a catch block
14+
@inflight_exception is definitely *not* null, which it can't just yet.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
CORE
2+
Test.class
3+
--function Test.main --show-vcc
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
file Test\.java line 6 function java::Test.main:\(I\)V
7+
no uncaught exception
8+
--
9+
file Test\.java line 42 function java::Test\.unreachable:\(\)V bytecode-index 5
10+
assertion at file Test\.java line 42 function java::Test\.unreachable:\(\)V bytecode-index 5
11+
java::Test\.unreachable:\(\)V
12+
^warning: ignoring
13+
--
14+
Supplemental to test.desc, check that the right condition remains to be tested by the solver,
15+
and the unreachable function indeed does not occur in the VCCs.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
int main(int argc, char **argv)
2+
{
3+
if(argc == 1)
4+
assert(argc == 1);
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
test.c
3+
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
0 remaining after simplification$
8+
--
9+
^warning: ignoring
10+
--
11+
Assumption and GOTO condition propagation means this test should be
12+
entirely decided by symex.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
int main(int argc, char **argv)
2+
{
3+
__CPROVER_assume(argc == 1);
4+
assert(argc == 1);
5+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
test.c
3+
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
0 remaining after simplification$
8+
--
9+
^warning: ignoring
10+
--
11+
Assumption and GOTO condition propagation means this test should be
12+
entirely decided by symex.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
int main(int argc, char **argv)
2+
{
3+
if(argc != 1)
4+
{
5+
}
6+
else
7+
{
8+
assert(argc == 1);
9+
}
10+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
test.c
3+
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
0 remaining after simplification$
8+
--
9+
^warning: ignoring
10+
--
11+
Assumption and GOTO condition propagation means this test should be
12+
entirely decided by symex.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
int main(int argc, char **argv)
2+
{
3+
int unknown;
4+
5+
if(argc != 1)
6+
{
7+
}
8+
else
9+
{
10+
if(unknown == argc)
11+
{
12+
assert(unknown == 1);
13+
}
14+
}
15+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
CORE
2+
test.c
3+
4+
^EXIT=0$
5+
^SIGNAL=0$
6+
^VERIFICATION SUCCESSFUL$
7+
0 remaining after simplification$
8+
--
9+
^warning: ignoring
10+
--
11+
Assumption and GOTO condition propagation means this test should be
12+
entirely decided by symex.

src/goto-symex/goto_state.cpp

+73
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ Author: Romain Brenguier, [email protected]
77
\*******************************************************************/
88

99
#include "goto_state.h"
10+
#include "goto_symex_is_constant.h"
11+
#include "goto_symex_state.h"
1012

1113
#include <util/format_expr.h>
1214

@@ -20,3 +22,74 @@ void goto_statet::output_propagation_map(std::ostream &out)
2022
out << name_value.first << " <- " << format(name_value.second) << "\n";
2123
}
2224
}
25+
26+
std::size_t goto_statet::increase_generation(
27+
const irep_idt l1_identifier,
28+
const ssa_exprt &lhs,
29+
std::function<std::size_t(const irep_idt &)> fresh_l2_name_provider)
30+
{
31+
auto current_emplace_res =
32+
level2.current_names.emplace(l1_identifier, std::make_pair(lhs, 0));
33+
34+
current_emplace_res.first->second.second =
35+
fresh_l2_name_provider(l1_identifier);
36+
37+
return current_emplace_res.first->second.second;
38+
}
39+
40+
/// Given a condition that must hold on this path, propagate as much knowledge
41+
/// as possible. For example, if the condition is (x == 5), whether that's an
42+
/// assumption or a GOTO condition that we just passed through, we can propagate
43+
/// the constant '5' for future 'x' references. If the condition is (y == &o1)
44+
/// then we can use that to populate the value set.
45+
/// \param condition: condition that must hold on this path. Expected to already
46+
/// be L2-renamed.
47+
/// \param previous_state: currently active state, not necessarily the same as
48+
/// *this. For a GOTO instruction this is the state immediately preceding the
49+
/// branch while *this is the state that will be used on the taken- or
50+
/// not-taken path. For an ASSUME instruction \p previous_state and *this are
51+
/// equal.
52+
/// \param ns: global namespace
53+
void goto_statet::apply_condition(
54+
const exprt &condition,
55+
const goto_symex_statet &previous_state,
56+
const namespacet &ns)
57+
{
58+
if(condition.id() == ID_and)
59+
{
60+
for(const auto &op : condition.operands())
61+
apply_condition(op, previous_state, ns);
62+
}
63+
else if(condition.id() == ID_equal)
64+
{
65+
exprt lhs = condition.op0();
66+
exprt rhs = condition.op1();
67+
if(is_ssa_expr(rhs))
68+
std::swap(lhs, rhs);
69+
70+
if(is_ssa_expr(lhs) && goto_symex_is_constantt()(rhs))
71+
{
72+
const ssa_exprt &ssa_lhs = to_ssa_expr(lhs);
73+
INVARIANT(
74+
!ssa_lhs.get_level_2().empty(),
75+
"apply_condition operand should be L2 renamed");
76+
77+
if(
78+
previous_state.threads.size() == 1 ||
79+
previous_state.write_is_shared(ssa_lhs, ns) !=
80+
goto_symex_statet::write_is_shared_resultt::SHARED)
81+
{
82+
ssa_exprt l1_lhs = ssa_lhs;
83+
l1_lhs.remove_level_2();
84+
const irep_idt &l1_identifier = l1_lhs.get_identifier();
85+
86+
increase_generation(
87+
l1_identifier, l1_lhs, previous_state.get_l2_name_provider());
88+
89+
propagation[l1_identifier] = rhs;
90+
91+
value_set.assign(l1_lhs, rhs, ns, true, false);
92+
}
93+
}
94+
}
95+
}

src/goto-symex/goto_state.h

+10
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,16 @@ class goto_statet
7474
: guard(true_exprt(), guard_manager)
7575
{
7676
}
77+
78+
void apply_condition(
79+
const exprt &condition, // L2
80+
const goto_symex_statet &previous_state,
81+
const namespacet &ns);
82+
83+
std::size_t increase_generation(
84+
const irep_idt l1_identifier,
85+
const ssa_exprt &lhs,
86+
std::function<std::size_t(const irep_idt &)> fresh_l2_name_provider);
7787
};
7888

7989
#endif // CPROVER_GOTO_SYMEX_GOTO_STATE_H
+53
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*******************************************************************\
2+
3+
Module: Symbolic Execution Constant Propagation
4+
5+
Author: Michael Tautschig, [email protected]
6+
7+
\*******************************************************************/
8+
9+
/// \file
10+
/// GOTO Symex constant propagation
11+
12+
#ifndef CPROVER_GOTO_SYMEX_GOTO_SYMEX_IS_CONSTANT_H
13+
#define CPROVER_GOTO_SYMEX_GOTO_SYMEX_IS_CONSTANT_H
14+
15+
#include <util/expr.h>
16+
#include <util/expr_util.h>
17+
18+
class goto_symex_is_constantt : public is_constantt
19+
{
20+
protected:
21+
bool is_constant(const exprt &expr) const override
22+
{
23+
if(expr.id() == ID_mult)
24+
{
25+
// propagate stuff with sizeof in it
26+
forall_operands(it, expr)
27+
{
28+
if(it->find(ID_C_c_sizeof_type).is_not_nil())
29+
return true;
30+
else if(!is_constant(*it))
31+
return false;
32+
}
33+
34+
return true;
35+
}
36+
else if(expr.id() == ID_with)
37+
{
38+
// this is bad
39+
/*
40+
forall_operands(it, expr)
41+
if(!is_constant(expr.op0()))
42+
return false;
43+
44+
return true;
45+
*/
46+
return false;
47+
}
48+
49+
return is_constantt::is_constant(expr);
50+
}
51+
};
52+
53+
#endif // CPROVER_GOTO_SYMEX_GOTO_SYMEX_IS_CONSTANT_H

0 commit comments

Comments
 (0)