Skip to content

Commit b7b0059

Browse files
committed
Properly report branch coverage
1 parent 08162b6 commit b7b0059

File tree

4 files changed

+170
-59
lines changed

4 files changed

+170
-59
lines changed

src/cbmc/symex_bmc.cpp

Lines changed: 44 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,11 +63,52 @@ void symex_bmct::symex_step(
6363
last_source_location=source_location;
6464
}
6565

66-
if(record_coverage &&
67-
!state.guard.is_false())
68-
symex_coverage.covered(state.source.pc);
66+
const goto_programt::const_targett cur_pc=state.source.pc;
6967

7068
goto_symext::symex_step(goto_functions, state);
69+
70+
if(record_coverage &&
71+
// is the instruction being executed
72+
!state.guard.is_false() &&
73+
// avoid an invalid iterator in state.source.pc
74+
(!cur_pc->is_end_function() ||
75+
cur_pc->function!=ID__start) &&
76+
// ignore transition to next instruction when goto points elsewhere
77+
(!cur_pc->is_goto() ||
78+
cur_pc->get_target()==state.source.pc ||
79+
!cur_pc->guard.is_true()))
80+
symex_coverage.covered(cur_pc, state.source.pc);
81+
}
82+
83+
/*******************************************************************\
84+
85+
Function: symex_bmct::merge_goto
86+
87+
Inputs:
88+
89+
Outputs:
90+
91+
Purpose:
92+
93+
\*******************************************************************/
94+
95+
void symex_bmct::merge_goto(
96+
const statet::goto_statet &goto_state,
97+
statet &state)
98+
{
99+
const goto_programt::const_targett prev_pc=goto_state.source.pc;
100+
const guardt prev_guard=goto_state.guard;
101+
102+
goto_symext::merge_goto(goto_state, state);
103+
104+
assert(prev_pc->is_goto());
105+
if(record_coverage &&
106+
// could the branch possibly be taken?
107+
!prev_guard.is_false() &&
108+
!state.guard.is_false() &&
109+
// branches only, no single-successor goto
110+
!prev_pc->guard.is_true())
111+
symex_coverage.covered(prev_pc, state.source.pc);
71112
}
72113

73114
/*******************************************************************\

src/cbmc/symex_bmc.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,10 @@ class symex_bmct:
8383
const goto_functionst &goto_functions,
8484
statet &state);
8585

86+
virtual void merge_goto(
87+
const statet::goto_statet &goto_state,
88+
statet &state);
89+
8690
// for loop unwinding
8791
virtual bool get_unwind(
8892
const symex_targett::sourcet &source,

src/cbmc/symex_coverage.cpp

Lines changed: 110 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -58,26 +58,37 @@ class goto_program_coverage_recordt:public coverage_recordt
5858
protected:
5959
irep_idt file_name;
6060

61-
struct line_coverage_recordt
61+
struct coverage_conditiont
6262
{
63-
line_coverage_recordt():
64-
hits(0), is_branch(false), branch_covered(false)
63+
coverage_conditiont():
64+
false_taken(false), true_taken(false)
65+
{
66+
}
67+
68+
bool false_taken;
69+
bool true_taken;
70+
};
71+
72+
struct coverage_linet
73+
{
74+
coverage_linet():
75+
hits(0)
6576
{
6677
}
6778

6879
unsigned hits;
69-
bool is_branch;
70-
bool branch_covered;
80+
std::map<goto_programt::const_targett, coverage_conditiont>
81+
conditions;
7182
};
7283

73-
typedef std::map<unsigned, line_coverage_recordt>
74-
line_coverage_mapt;
84+
typedef std::map<unsigned, coverage_linet>
85+
coverage_lines_mapt;
7586

76-
void compute_line_coverage(
87+
void compute_coverage_lines(
7788
const goto_programt &goto_program,
7889
const irep_idt &file_name,
7990
const symex_coveraget::coveraget &coverage,
80-
line_coverage_mapt &dest);
91+
coverage_lines_mapt &dest);
8192
};
8293

8394
/*******************************************************************\
@@ -92,17 +103,26 @@ Function: rate
92103
93104
\*******************************************************************/
94105

95-
static std::string rate(std::size_t covered, std::size_t total)
106+
static std::string rate(
107+
std::size_t covered,
108+
std::size_t total,
109+
bool per_cent=false)
96110
{
111+
std::ostringstream oss;
112+
97113
#if 1
98-
if(total==0)
99-
return "1.0";
114+
float fraction;
100115

101-
std::ostringstream oss;
116+
if(total==0)
117+
fraction=1.0;
118+
else
119+
fraction=static_cast<float>(covered)/static_cast<float>(total);
102120

103-
oss << static_cast<float>(covered)/static_cast<float>(total);
121+
if(per_cent)
122+
oss << fraction*100.0 << '%';
123+
else
124+
oss << fraction;
104125
#else
105-
std::ostringstream oss;
106126
oss << covered << " of " << total;
107127
#endif
108128

@@ -138,12 +158,12 @@ goto_program_coverage_recordt::goto_program_coverage_recordt(
138158
assert(!file_name.empty());
139159

140160
// compute the maximum coverage of individual source-code lines
141-
line_coverage_mapt line_coverage_map;
142-
compute_line_coverage(
161+
coverage_lines_mapt coverage_lines_map;
162+
compute_coverage_lines(
143163
gf_it->second.body,
144164
file_name,
145165
coverage,
146-
line_coverage_map);
166+
coverage_lines_map);
147167

148168
// <method name="foo" signature="int(int)" line-rate="1.0" branch-rate="1.0">
149169
// <lines>
@@ -173,28 +193,43 @@ goto_program_coverage_recordt::goto_program_coverage_recordt(
173193

174194
xmlt &lines=xml.new_element("lines");
175195

176-
for(line_coverage_mapt::const_iterator
177-
it=line_coverage_map.begin();
178-
it!=line_coverage_map.end();
179-
++it)
196+
for(const auto &cov_line : coverage_lines_map)
180197
{
181198
xmlt &line=lines.new_element("line");
182199

183-
line.set_attribute("number", std::to_string(it->first));
184-
line.set_attribute("hits", std::to_string(it->second.hits));
185-
if(!it->second.is_branch)
200+
line.set_attribute("number", std::to_string(cov_line.first));
201+
line.set_attribute("hits", std::to_string(cov_line.second.hits));
202+
if(cov_line.second.conditions.empty())
186203
line.set_attribute("branch", "false");
187204
else
188205
{
189-
// TODO: conditions
190206
line.set_attribute("branch", "true");
207+
208+
xmlt &conditions=line.new_element("conditions");
209+
210+
std::size_t number=0, total_taken=0;
211+
for(const auto &c : cov_line.second.conditions)
212+
{
213+
// <condition number="0" type="jump" coverage="50%"/>
214+
xmlt &condition=conditions.new_element("condition");
215+
condition.set_attribute("number", std::to_string(number++));
216+
condition.set_attribute("type", "jump");
217+
unsigned taken=c.second.false_taken+c.second.true_taken;
218+
total_taken+=taken;
219+
condition.set_attribute("coverage", rate(taken, 2, true));
220+
}
221+
222+
std::ostringstream oss;
223+
oss << rate(total_taken, number*2, true)
224+
<< " (" << total_taken << '/' << number*2 << ')';
225+
line.set_attribute("condition-coverage", oss.str());
191226
}
192227
}
193228
}
194229

195230
/*******************************************************************\
196231
197-
Function: goto_program_coverage_recordt::compute_line_coverage
232+
Function: goto_program_coverage_recordt::compute_coverage_lines
198233
199234
Inputs:
200235
@@ -204,58 +239,83 @@ Function: goto_program_coverage_recordt::compute_line_coverage
204239
205240
\*******************************************************************/
206241

207-
void goto_program_coverage_recordt::compute_line_coverage(
242+
void goto_program_coverage_recordt::compute_coverage_lines(
208243
const goto_programt &goto_program,
209244
const irep_idt &file_name,
210245
const symex_coveraget::coveraget &coverage,
211-
line_coverage_mapt &dest)
246+
coverage_lines_mapt &dest)
212247
{
213248
forall_goto_program_instructions(it, goto_program)
214249
{
215250
if(it->source_location.is_nil() ||
216-
it->source_location.get_file()!=file_name)
251+
it->source_location.get_file()!=file_name ||
252+
it->is_dead() ||
253+
it->is_end_function())
217254
continue;
218255

219256
const bool is_branch=it->is_goto() && !it->guard.is_constant();
220257

221258
unsigned l=
222259
safe_string2unsigned(id2string(it->source_location.get_line()));
223-
std::pair<line_coverage_mapt::iterator, bool> entry=
224-
dest.insert(std::make_pair(l, line_coverage_recordt()));
260+
std::pair<coverage_lines_mapt::iterator, bool> entry=
261+
dest.insert(std::make_pair(l, coverage_linet()));
225262

226263
if(entry.second)
227-
{
228264
++lines_total;
229-
if(is_branch)
230-
++branches_total;
231-
}
232265

233266
// mark as branch if any instruction in this source code line is
234267
// a branching instruction
235-
if(is_branch &&
236-
!entry.first->second.is_branch)
268+
if(is_branch)
237269
{
238-
++branches_total;
239-
entry.first->second.is_branch=true;
270+
branches_total+=2;
271+
if(!entry.first->second.conditions.insert(
272+
{it, coverage_conditiont()}).second)
273+
assert(false);
240274
}
241275

242276
symex_coveraget::coveraget::const_iterator c_entry=
243277
coverage.find(it);
244-
if(c_entry!=coverage.end() &&
245-
c_entry->second.num_executions>0)
278+
if(c_entry!=coverage.end())
246279
{
247-
// maximum over all instructions in this source code line
248-
if(c_entry->second.num_executions>entry.first->second.hits)
280+
if(!(c_entry->second.size()==1 || is_branch))
249281
{
250-
if(entry.first->second.hits==0)
251-
++lines_covered;
252-
entry.first->second.hits=c_entry->second.num_executions;
282+
std::cerr << it->location_number << std::endl;
283+
for(const auto &cov : c_entry->second)
284+
std::cerr << cov.second.succ->location_number << std::endl;
253285
}
286+
assert(c_entry->second.size()==1 || is_branch);
254287

255-
if(is_branch && !entry.first->second.branch_covered)
288+
for(const auto &cov : c_entry->second)
256289
{
257-
++branches_covered;
258-
entry.first->second.branch_covered=true;
290+
assert(cov.second.num_executions>0);
291+
292+
if(entry.first->second.hits==0)
293+
++lines_covered;
294+
295+
entry.first->second.hits+=cov.second.num_executions;
296+
297+
if(is_branch)
298+
{
299+
auto cond_entry=entry.first->second.conditions.find(it);
300+
assert(cond_entry!=entry.first->second.conditions.end());
301+
302+
if(it->get_target()==cov.second.succ)
303+
{
304+
if(!cond_entry->second.false_taken)
305+
{
306+
cond_entry->second.false_taken=true;
307+
++branches_covered;
308+
}
309+
}
310+
else
311+
{
312+
if(!cond_entry->second.true_taken)
313+
{
314+
cond_entry->second.true_taken=true;
315+
++branches_covered;
316+
}
317+
}
318+
}
259319
}
260320
}
261321
}

src/cbmc/symex_coverage.h

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -29,11 +29,12 @@ class symex_coveraget
2929
{
3030
}
3131

32-
void covered(goto_programt::const_targett location)
32+
void covered(
33+
goto_programt::const_targett from,
34+
goto_programt::const_targett to)
3335
{
34-
std::pair<coveraget::iterator, bool> entry=
35-
coverage.insert(std::make_pair(location,
36-
coverage_infot(location, 1)));
36+
std::pair<coverage_innert::iterator, bool> entry=
37+
coverage[from].insert({to, coverage_infot(from, to, 1)});
3738

3839
if(!entry.second)
3940
++(entry.first->second.num_executions);
@@ -49,17 +50,22 @@ class symex_coveraget
4950
struct coverage_infot
5051
{
5152
coverage_infot(
52-
goto_programt::const_targett _location,
53+
goto_programt::const_targett _from,
54+
goto_programt::const_targett _to,
5355
unsigned _num_executions):
54-
location(_location), num_executions(_num_executions)
56+
location(_from), num_executions(_num_executions),
57+
succ(_to)
5558
{
5659
}
5760

5861
goto_programt::const_targett location;
5962
unsigned num_executions;
63+
goto_programt::const_targett succ;
6064
};
6165

6266
typedef std::map<goto_programt::const_targett, coverage_infot>
67+
coverage_innert;
68+
typedef std::map<goto_programt::const_targett, coverage_innert>
6369
coveraget;
6470
coveraget coverage;
6571

0 commit comments

Comments
 (0)