Skip to content

Commit 1cb10ad

Browse files
authored
Merge pull request #4958 from JohnDumbell/jd/feature/symex_complexity
Symex complexity heuristic
2 parents 5c88d5e + a35c29e commit 1cb10ad

26 files changed

+855
-100
lines changed
2.42 KB
Binary file not shown.
Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
import java.util.*;
2+
3+
public class ComplexClass {
4+
5+
public void loopBlacklist(List<String> prop, int increment, int size) {
6+
while (size <= 1000)
7+
{
8+
if (size > 500)
9+
branchCancel(prop, increment, size);
10+
11+
size++;
12+
}
13+
}
14+
15+
public void branchCancel(List<String> prop, int increment, int size) {
16+
StringBuilder sb = new StringBuilder();
17+
for (String str : prop)
18+
{
19+
if (size >= 50)
20+
sb.append(str);
21+
22+
size+=increment;
23+
}
24+
}
25+
26+
private static Properties properties = new Properties();
27+
28+
public void process(Properties prop) {
29+
30+
properties = prop;
31+
32+
for (Enumeration<?> pe = properties.propertyNames(); pe.hasMoreElements(); )
33+
{
34+
String key = (String)pe.nextElement();
35+
String value = recurse(key, properties.getProperty(key), 1);
36+
if (value != null)
37+
{
38+
properties.setProperty(key, value);
39+
}
40+
}
41+
}
42+
43+
private static String recurse(String key, String value, int level)
44+
{
45+
if (level > 9)
46+
throw new IllegalArgumentException("Recursion level too deep.");
47+
48+
int from = 0;
49+
StringBuffer result = null;
50+
while (from < value.length()) {
51+
while (from < value.length()) {
52+
int start = value.indexOf("[", from);
53+
if (start >= 0) {
54+
int end = value.indexOf(']', start);
55+
if (end < 0) {
56+
break;
57+
}
58+
String var = value.substring(start + 1, end);
59+
if (result == null) {
60+
result = new StringBuffer(value.substring(from, start));
61+
} else {
62+
result.append(value.substring(from, start));
63+
}
64+
if (properties.containsKey(var)) {
65+
String val = recurse(var, properties.getProperty(var), level + 1);
66+
if (val != null) {
67+
result.append(val);
68+
properties.setProperty(var, val);
69+
} else {
70+
result.append((properties.getProperty(var)).trim());
71+
}
72+
}
73+
74+
from = end + 1;
75+
} else {
76+
break;
77+
}
78+
}
79+
}
80+
81+
if (result != null && from < value.length())
82+
{
83+
result.append(value.substring(from));
84+
}
85+
return (result == null) ? null : result.toString();
86+
}
87+
}
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
CORE
2+
ComplexClass.class
3+
--function ComplexClass.branchCancel --symex-complexity-limit 1 --verbosity 9 --unwind 100 --cp `../../../../scripts/format_classpath.sh . ../../../lib/java-models-library/target/core-models.jar`
4+
^(\[symex-complexity\] Branch considered too complex|\[symex-complexity\] Loop operations considered too complex)
5+
^EXIT=10$
6+
^SIGNAL=0$
7+
^VERIFICATION FAILED$
8+
--
9+
--
10+
This tests that we cancel out of a loop with huge unwind and low complexity limit.
11+
12+
The basis here is that the undeterministic incrementor and list mean that symex can't
13+
work out statically what's going on, and so both pieces of unknowability cause the
14+
guard to grow. Once that guard has reached a certain number, it'll be killed via
15+
the complexity module.
16+
17+
If this is broken it means that the heuristics used to generate a 'complexity'
18+
measure of code is broken and will need to be re-written. See complexity_limitert
19+
for the main block of code.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
CORE
2+
ComplexClass.class
3+
--function ComplexClass.loopBlacklist --symex-complexity-limit 1 --symex-complexity-failed-child-loops-limit 1 --verbosity 9 --unwind 9 --cp `../../../../scripts/format_classpath.sh . ../../../lib/java-models-library/target/core-models.jar`
4+
^(\[symex-complexity\] Trying to enter blacklisted loop|\[symex-complexity\] Loop operations considered too complex)
5+
^EXIT=10$
6+
^SIGNAL=0$
7+
^VERIFICATION FAILED$
8+
--
9+
--
10+
This tests that we cancel out of a loop with huge unwind, low complexity limit and low branch failure limit.
11+
12+
Same basis as branchCancel with nondeterministic values, but in this case we have an
13+
outer loop that we're also running 100 times that will cause the inner to be blacklisted.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
CORE
2+
ComplexClass.class
3+
--function ComplexClass.process --symex-complexity-limit 2 --verbosity 9 --unwind 10 --cp `../../../../scripts/format_classpath.sh . ../../../lib/java-models-library/target/core-models.jar`
4+
^\[symex-complexity\] Branch considered too complex|\[symex-complexity\] Trying to enter blacklisted loop|\[symex-complexity\] Loop operations considered too complex$
5+
^EXIT=10$
6+
^SIGNAL=0$
7+
^VERIFICATION FAILED$
8+
--
9+
--
10+
This tests that the complexity limits apply correctly and that each level of them are detected correctly:
11+
12+
Branch cancelling.
13+
Loop cancelling.
14+
Loop blacklisting.
15+
16+
When these don't work this test may take some time to run (and then fail), which is hard to
17+
restrict because this is the problem this feature is meant to solve. If this test is running
18+
slowly, high chance something has gone wrong.

jbmc/src/jbmc/jbmc_parse_options.cpp

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,6 +197,19 @@ void jbmc_parse_optionst::get_command_line_options(optionst &options)
197197
if(cmdline.isset("localize-faults"))
198198
options.set_option("localize-faults", true);
199199

200+
if(cmdline.isset("symex-complexity-limit"))
201+
{
202+
options.set_option(
203+
"symex-complexity-limit", cmdline.get_value("symex-complexity-limit"));
204+
}
205+
206+
if(cmdline.isset("symex-complexity-failed-child-loops-limit"))
207+
{
208+
options.set_option(
209+
"symex-complexity-failed-child-loops-limit",
210+
cmdline.get_value("symex-complexity-failed-child-loops-limit"));
211+
}
212+
200213
if(cmdline.isset("unwind"))
201214
options.set_option("unwind", cmdline.get_value("unwind"));
202215

src/analyses/lexical_loops.h

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -61,9 +61,9 @@ Author: Diffblue Ltd
6161
/// * [function] get_target() which returns an object that needs:
6262
/// * [field] location_number which is an unsigned int.
6363
template <class P, class T>
64-
class lexical_loops_templatet : public loop_analysist<P, T>
64+
class lexical_loops_templatet : public loop_analysist<T>
6565
{
66-
typedef loop_analysist<P, T> parentt;
66+
typedef loop_analysist<T> parentt;
6767

6868
public:
6969
typedef typename parentt::loopt lexical_loopt;
@@ -92,11 +92,13 @@ class lexical_loops_templatet : public loop_analysist<P, T>
9292
out << "Note not all loops were in lexical loop form\n";
9393
}
9494

95+
virtual ~lexical_loops_templatet() = default;
96+
9597
protected:
9698
void compute(P &program);
9799
bool compute_lexical_loop(T, T);
98100

99-
bool all_in_lexical_loop_form;
101+
bool all_in_lexical_loop_form = false;
100102
};
101103

102104
typedef lexical_loops_templatet<
@@ -178,7 +180,7 @@ bool lexical_loops_templatet<P, T>::compute_lexical_loop(
178180
}
179181

180182
auto insert_result = parentt::loop_map.emplace(
181-
loop_head, lexical_loopt{*this, std::move(loop_instructions)});
183+
loop_head, lexical_loopt{std::move(loop_instructions)});
182184

183185
// If this isn't a new loop head (i.e. return_result.second is false) then we
184186
// have multiple backedges targeting one loop header: this is not in simple

src/analyses/loop_analysis.h

Lines changed: 77 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -13,47 +13,33 @@ Author: Diffblue Ltd
1313
#ifndef CPROVER_ANALYSES_LOOP_ANALYSIS_H
1414
#define CPROVER_ANALYSES_LOOP_ANALYSIS_H
1515

16-
template <class P, class T>
16+
#include <memory>
17+
18+
template <class T>
1719
class loop_analysist;
1820

1921
/// A loop, specified as a set of instructions
20-
template <class P, class T>
22+
template <class T>
2123
class loop_templatet
2224
{
23-
typedef loop_analysist<P, T> parent_analysist;
2425
typedef std::set<T> loop_instructionst;
2526
loop_instructionst loop_instructions;
2627

27-
friend loop_analysist<P, T>;
28+
friend loop_analysist<T>;
2829

2930
public:
30-
explicit loop_templatet(parent_analysist &loop_analysis)
31-
: loop_analysis(loop_analysis)
32-
{
33-
}
31+
loop_templatet() = default;
3432

3533
template <typename InstructionSet>
36-
loop_templatet(parent_analysist &loop_analysis, InstructionSet &&instructions)
37-
: loop_instructions(std::forward<InstructionSet>(instructions)),
38-
loop_analysis(loop_analysis)
34+
explicit loop_templatet(InstructionSet &&instructions)
35+
: loop_instructions(std::forward<InstructionSet>(instructions))
3936
{
4037
}
4138

4239
/// Returns true if \p instruction is in this loop
43-
bool contains(const T instruction) const
44-
{
45-
return loop_analysis.loop_contains(*this, instruction);
46-
}
47-
48-
/// Get the \ref parent_analysist analysis this loop relates to
49-
const parent_analysist &get_loop_analysis() const
50-
{
51-
return loop_analysis;
52-
}
53-
/// Get the \ref parent_analysist analysis this loop relates to
54-
parent_analysist &get_loop_analysis()
40+
bool virtual contains(const T instruction) const
5541
{
56-
return loop_analysis;
42+
return !loop_instructions.empty() && loop_instructions.count(instruction);
5743
}
5844

5945
// NOLINTNEXTLINE(readability/identifiers)
@@ -83,58 +69,105 @@ class loop_templatet
8369
return loop_instructions.empty();
8470
}
8571

86-
/// Adds \p instruction to this loop. The caller must verify that the added
87-
/// instruction does not alter loop structure; if it does they must discard
88-
/// and recompute the related \ref parent_analysist instance.
72+
/// Adds \p instruction to this loop.
8973
/// \return true if the instruction is new
9074
bool insert_instruction(const T instruction)
9175
{
9276
return loop_instructions.insert(instruction).second;
9377
}
94-
95-
private:
96-
parent_analysist &loop_analysis;
9778
};
9879

99-
template <class P, class T>
80+
template <class T>
10081
class loop_analysist
10182
{
10283
public:
103-
typedef loop_templatet<P, T> loopt;
84+
typedef loop_templatet<T> loopt;
10485
// map loop headers to loops
10586
typedef std::map<T, loopt> loop_mapt;
10687

10788
loop_mapt loop_map;
10889

109-
void output(std::ostream &) const;
90+
virtual void output(std::ostream &) const;
91+
92+
/// Returns true if \p instruction is the header of any loop
93+
bool is_loop_header(const T instruction) const
94+
{
95+
return loop_map.count(instruction);
96+
}
97+
98+
loop_analysist() = default;
99+
};
100+
101+
template <typename T>
102+
class loop_with_parent_analysis_templatet : loop_templatet<T>
103+
{
104+
typedef loop_analysist<T> parent_analysist;
105+
106+
public:
107+
explicit loop_with_parent_analysis_templatet(parent_analysist &loop_analysis)
108+
: loop_analysis(loop_analysis)
109+
{
110+
}
111+
112+
template <typename InstructionSet>
113+
explicit loop_with_parent_analysis_templatet(
114+
parent_analysist &loop_analysis,
115+
InstructionSet &&instructions)
116+
: loop_templatet<T>(std::forward<InstructionSet>(instructions)),
117+
loop_analysis(loop_analysis)
118+
{
119+
}
110120

111121
/// Returns true if \p instruction is in \p loop
112-
bool loop_contains(const loopt &loop, const T instruction) const
122+
bool loop_contains(
123+
const typename loop_analysist<T>::loopt &loop,
124+
const T instruction) const
113125
{
114126
return loop.loop_instructions.count(instruction);
115127
}
116128

117-
/// Returns true if \p instruction is the header of any loop
118-
bool is_loop_header(const T instruction) const
129+
/// Get the \ref parent_analysist analysis this loop relates to
130+
const parent_analysist &get_loop_analysis() const
119131
{
120-
return loop_map.count(instruction);
132+
return loop_analysis;
133+
}
134+
/// Get the \ref parent_analysist analysis this loop relates to
135+
parent_analysist &get_loop_analysis()
136+
{
137+
return loop_analysis;
121138
}
122139

123-
loop_analysist() = default;
140+
private:
141+
parent_analysist &loop_analysis;
142+
};
143+
144+
template <class T>
145+
class linked_loop_analysist : loop_analysist<T>
146+
{
147+
public:
148+
linked_loop_analysist() = default;
149+
150+
/// Returns true if \p instruction is in \p loop
151+
bool loop_contains(
152+
const typename loop_analysist<T>::loopt &loop,
153+
const T instruction) const
154+
{
155+
return loop.loop_instructions.count(instruction);
156+
}
124157

125158
// The loop structures stored in `loop_map` contain back-pointers to this
126159
// class, so we forbid copying or moving the analysis struct. If this becomes
127160
// necessary then either add a layer of indirection or update the loop_map
128161
// back-pointers on copy/move.
129-
loop_analysist(const loop_analysist &) = delete;
130-
loop_analysist(loop_analysist &&) = delete;
131-
loop_analysist &operator=(const loop_analysist &) = delete;
132-
loop_analysist &operator=(loop_analysist &&) = delete;
162+
linked_loop_analysist(const linked_loop_analysist &) = delete;
163+
linked_loop_analysist(linked_loop_analysist &&) = delete;
164+
linked_loop_analysist &operator=(const linked_loop_analysist &) = delete;
165+
linked_loop_analysist &operator=(linked_loop_analysist &&) = delete;
133166
};
134167

135168
/// Print all natural loops that were found
136-
template <class P, class T>
137-
void loop_analysist<P, T>::output(std::ostream &out) const
169+
template <class T>
170+
void loop_analysist<T>::output(std::ostream &out) const
138171
{
139172
for(const auto &loop : loop_map)
140173
{

0 commit comments

Comments
 (0)