Skip to content

Commit 691f5e8

Browse files
committed
Store and report basic block source lines as a structure
Do not serialise the information to a string early on, but instead leave this to the user interface. This will make it possible to further process the information in CBMC Viewer even when function identifiers also contain semicolons, as is the case with Kani.
1 parent 092328c commit 691f5e8

File tree

10 files changed

+173
-28
lines changed

10 files changed

+173
-28
lines changed

doc/assets/xml_spec.xsd

+32
Original file line numberDiff line numberDiff line change
@@ -173,6 +173,37 @@
173173
</xs:complexType>
174174
</xs:element>
175175

176+
<xs:element name="line">
177+
<xs:complexType>
178+
<xs:simpleContent>
179+
<xs:extension base="xs:string">
180+
<xs:attribute name="file" type="xs:string"/>
181+
<xs:attribute name="function" type="xs:string"/>
182+
</xs:extension>
183+
</xs:simpleContent>
184+
</xs:complexType>
185+
</xs:element>
186+
187+
<xs:element name="basic_block_lines">
188+
<xs:complexType>
189+
<xs:sequence>
190+
<xs:element ref="line" minOccurs="0"/>
191+
</xs:sequence>
192+
</xs:complexType>
193+
</xs:element>
194+
195+
<xs:element name="goal">
196+
<xs:complexType>
197+
<xs:sequence>
198+
<xs:element ref="location" minOccurs="0"/>
199+
<xs:element ref="basic_block_lines" minOccurs="0"/>
200+
</xs:sequence>
201+
<xs:attribute name="description" type="xs:string"/>
202+
<xs:attribute name="id" type="xs:string"/>
203+
<xs:attribute name="status" type="xs:string"/>
204+
</xs:complexType>
205+
</xs:element>
206+
176207
<xs:element name="message">
177208
<xs:complexType>
178209
<xs:sequence>
@@ -217,6 +248,7 @@
217248
<xs:element ref="result"/>
218249
<xs:element ref="refinement-iteration"/>
219250
<xs:element ref="fault-localization"/>
251+
<xs:element ref="goal"/>
220252
</xs:choice>
221253
<xs:element ref="cprover-status" minOccurs="0"/>
222254
</xs:sequence>

jbmc/regression/jdiff/java-properties/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,6 @@ old.jar --json-ui --show-properties --cover location
55
activate-multi-line-match
66
^EXIT=0$
77
^SIGNAL=0$
8-
"deletedFunctions": \[\n \{\n "name": "java::Test.obsolete:\(\)V",\n "properties": \[\n \{\n "class": "coverage",\n "coveredLines": "Test.java:java::Test.obsolete:\(\)V:18",\n "description": "block 1 \(lines Test.java:java::Test.obsolete:\(\)V:18\)",\n "expression": "false",\n "name": "java::Test.obsolete:\(\)V.coverage.1",\n "sourceLocation": \{\n "bytecodeIndex": "0",\n "file": "Test.java",\n "function": "java::Test.obsolete:\(\)V",\n "line": "18"\n }\n }\n ],\n "sourceLocation": \{\n "file": "Test.java",\n "function": "java::Test.obsolete:\(\)V",\n "line": "18"\n }\n }\n ],\n "modifiedFunctions": \[\n \{\n "name": "java::Test.<init>:\(\)V",\n "properties": \[\n \{\n "class": "coverage",\n "coveredLines": "Test.java:java::Test.<init>:\(\)V:6",\n "description": "block 2 \(lines Test.java:java::Test.<init>:\(\)V:6\)",\n "expression": "false",\n "name": "java::Test.<init>:\(\)V.coverage.1",\n "sourceLocation": \{\n "bytecodeIndex": "1",\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "6"\n }\n },\n \{\n "class": "coverage",\n "coveredLines": "Test.java:java::Test.<init>:\(\)V:4",\n "description": "block 3 \(lines Test.java:java::Test.<init>:\(\)V:4\)",\n "expression": "false",\n "name": "java::Test.<init>:\(\)V.coverage.2",\n "sourceLocation": \{\n "bytecodeIndex": "3",\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "4"\n }\n },\n \{\n "class": "coverage",\n "coveredLines": "Test.java:java::Test.<init>:\(\)V:4",\n "description": "block 4 \(lines Test.java:java::Test.<init>:\(\)V:4\)",\n "expression": "false",\n "name": "java::Test.<init>:\(\)V.coverage.3",\n "sourceLocation": \{\n "bytecodeIndex": "5",\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "4"\n }\n },\n \{\n "class": "coverage",\n "coveredLines": "Test.java:java::Test.<init>:\(\)V:4",\n "description": "block 5 \(lines Test.java:java::Test.<init>:\(\)V:4\)",\n "expression": "false",\n "name": "java::Test.<init>:\(\)V.coverage.4",\n "sourceLocation": \{\n "bytecodeIndex": "6",\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "4"\n }\n },\n \{\n "class": "coverage",\n "coveredLines": "Test.java:java::Test.<init>:\(\)V:7",\n "description": "block 6 \(lines Test.java:java::Test.<init>:\(\)V:7\)",\n "expression": "false",\n "name": "java::Test.<init>:\(\)V.coverage.5",\n "sourceLocation": \{\n "bytecodeIndex": "7",\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "7"\n }\n }\n ],\n "sourceLocation": \{\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "6"\n }\n },\n \{\n "name": "java::Test.<clinit>:\(\)V",\n "properties": \[\n \{\n "class": "coverage",\n "coveredLines": "Test.java:java::Test.<clinit>:\(\)V:3",\n "description": "block 1 \(lines Test.java:java::Test.<clinit>:\(\)V:3\)",\n "expression": "false",\n "name": "java::Test.<clinit>:\(\)V.coverage.1",\n "sourceLocation": \{\n "bytecodeIndex": "1",\n "file": "Test.java",\n "function": "java::Test.<clinit>:\(\)V",\n "line": "3"\n }\n }\n ],\n "sourceLocation": \{\n "file": "Test.java",\n "function": "java::Test.<clinit>:\(\)V",\n "line": "3"\n }\n }\n ],\n "newFunctions": \[\n \{\n "name": "java::Test.newfun:\(\)V",\n "properties": \[\n \{\n "class": "coverage",\n "coveredLines": "Test.java:java::Test.newfun:\(\)V:18",\n "description": "block 1 \(lines Test.java:java::Test.newfun:\(\)V:18\)",\n "expression": "false",\n "name": "java::Test.newfun:\(\)V.coverage.1",\n "sourceLocation": \{\n "bytecodeIndex": "0",\n "file": "Test.java",\n "function": "java::Test.newfun:\(\)V",\n "line": "18"\n }\n }\n ],\n "sourceLocation": \{\n "file": "Test.java",\n "function": "java::Test.newfun:\(\)V",\n "line": "18"\n }\n }\n ],\n
8+
"deletedFunctions": \[\n \{\n "name": "java::Test.obsolete:\(\)V",\n "properties": \[\n \{\n "basicBlockLines": \{\n "Test.java": \{\n "java::Test.obsolete:\(\)V": "18"\n \}\n \},\n "class": "coverage",\n "description": "block 1 \(lines Test.java:java::Test.obsolete:\(\)V:18\)",\n "expression": "false",\n "name": "java::Test.obsolete:\(\)V.coverage.1",\n "sourceLocation": \{\n "bytecodeIndex": "0",\n "file": "Test.java",\n "function": "java::Test.obsolete:\(\)V",\n "line": "18"\n }\n }\n ],\n "sourceLocation": \{\n "file": "Test.java",\n "function": "java::Test.obsolete:\(\)V",\n "line": "18"\n }\n }\n ],\n "modifiedFunctions": \[\n \{\n "name": "java::Test.<init>:\(\)V",\n "properties": \[\n \{\n "basicBlockLines": \{\n "Test.java": \{\n "java::Test.<init>:\(\)V": "6"\n \}\n \},\n "class": "coverage",\n "description": "block 2 \(lines Test.java:java::Test.<init>:\(\)V:6\)",\n "expression": "false",\n "name": "java::Test.<init>:\(\)V.coverage.1",\n "sourceLocation": \{\n "bytecodeIndex": "1",\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "6"\n }\n },\n \{\n "basicBlockLines": \{\n "Test.java": \{\n "java::Test.<init>:\(\)V": "4"\n \}\n \},\n "class": "coverage",\n "description": "block 3 \(lines Test.java:java::Test.<init>:\(\)V:4\)",\n "expression": "false",\n "name": "java::Test.<init>:\(\)V.coverage.2",\n "sourceLocation": \{\n "bytecodeIndex": "3",\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "4"\n }\n },\n \{\n "basicBlockLines": \{\n "Test.java": \{\n "java::Test.<init>:\(\)V": "4"\n \}\n \},\n "class": "coverage",\n "description": "block 4 \(lines Test.java:java::Test.<init>:\(\)V:4\)",\n "expression": "false",\n "name": "java::Test.<init>:\(\)V.coverage.3",\n "sourceLocation": \{\n "bytecodeIndex": "5",\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "4"\n }\n },\n \{\n "basicBlockLines": \{\n "Test.java": \{\n "java::Test.<init>:\(\)V": "4"\n \}\n \},\n "class": "coverage",\n "description": "block 5 \(lines Test.java:java::Test.<init>:\(\)V:4\)",\n "expression": "false",\n "name": "java::Test.<init>:\(\)V.coverage.4",\n "sourceLocation": \{\n "bytecodeIndex": "6",\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "4"\n }\n },\n \{\n "basicBlockLines": \{\n "Test.java": \{\n "java::Test.<init>:\(\)V": "7"\n \}\n \},\n "class": "coverage",\n "description": "block 6 \(lines Test.java:java::Test.<init>:\(\)V:7\)",\n "expression": "false",\n "name": "java::Test.<init>:\(\)V.coverage.5",\n "sourceLocation": \{\n "bytecodeIndex": "7",\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "7"\n }\n }\n ],\n "sourceLocation": \{\n "file": "Test.java",\n "function": "java::Test.<init>:\(\)V",\n "line": "6"\n }\n },\n \{\n "name": "java::Test.<clinit>:\(\)V",\n "properties": \[\n \{\n "basicBlockLines": \{\n "Test.java": \{\n "java::Test.<clinit>:\(\)V": "3"\n \}\n \},\n "class": "coverage",\n "description": "block 1 \(lines Test.java:java::Test.<clinit>:\(\)V:3\)",\n "expression": "false",\n "name": "java::Test.<clinit>:\(\)V.coverage.1",\n "sourceLocation": \{\n "bytecodeIndex": "1",\n "file": "Test.java",\n "function": "java::Test.<clinit>:\(\)V",\n "line": "3"\n }\n }\n ],\n "sourceLocation": \{\n "file": "Test.java",\n "function": "java::Test.<clinit>:\(\)V",\n "line": "3"\n }\n }\n ],\n "newFunctions": \[\n \{\n "name": "java::Test.newfun:\(\)V",\n "properties": \[\n \{\n "basicBlockLines": \{\n "Test.java": \{\n "java::Test.newfun:\(\)V": "18"\n \}\n \},\n "class": "coverage",\n "description": "block 1 \(lines Test.java:java::Test.newfun:\(\)V:18\)",\n "expression": "false",\n "name": "java::Test.newfun:\(\)V.coverage.1",\n "sourceLocation": \{\n "bytecodeIndex": "0",\n "file": "Test.java",\n "function": "java::Test.newfun:\(\)V",\n "line": "18"\n }\n }\n ],\n "sourceLocation": \{\n "file": "Test.java",\n "function": "java::Test.newfun:\(\)V",\n "line": "18"\n }\n }\n ],\n
99
--
1010
^warning: ignoring

jbmc/unit/java_bytecode/java_virtual_functions/virtual_functions.cpp

+2-2
Original file line numberDiff line numberDiff line change
@@ -159,12 +159,12 @@ SCENARIO(
159159
// instruction with a new bytecode index begins with ASSERT
160160
REQUIRE(it->type() == goto_program_instruction_typet::ASSERT);
161161
// the assertion corresponds to a line coverage goal
162-
REQUIRE_FALSE(loc.get_basic_block_source_lines().empty());
162+
REQUIRE_FALSE(loc.get_basic_block_source_lines().is_nil());
163163
}
164164
else
165165
{
166166
// there is no line coverage goal in the middle of a block
167-
REQUIRE(loc.get_basic_block_source_lines().empty());
167+
REQUIRE(loc.get_basic_block_source_lines().is_nil());
168168
}
169169

170170
++it;

regression/cbmc-cover/json-goals1/test.desc

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,6 @@ main.c
44
activate-multi-line-match
55
^EXIT=0$
66
^SIGNAL=0$
7-
\{\n\s*"goals": \[\n\s*\{\n\s*"description": ".*",\n\s*"goal": "main\.coverage\.[1-4]",\n\s*"sourceLocation": \{(\n.+){5}\},\n\s*"status": "satisfied"\n\s*\},\n\s*\{\n\s*"description": ".*",\n\s*"goal": "main\.coverage\.[1-4]",\n\s*"sourceLocation": \{(\n.+){5}\},\n\s*"status": "satisfied"\n\s*\},\n\s*\{\n\s*"description": ".*",\n\s*"goal": "main\.coverage\.[1-4]",\n\s*"sourceLocation": \{(\n.+){5}\},\n\s*"status": "satisfied"\n\s*\},\n\s*\{\n\s*"description": ".*",\n\s*"goal": "main\.coverage\.[1-4]",\n\s*"sourceLocation": \{(\n.+){5}\},\n\s*"status": "satisfied"\n\s*\}\n\s*\],\n\s*"goalsCovered": 4,\n\s*"totalGoals": 4\n\s*\}
7+
\{\n\s*"goals": \[\n\s*\{\n\s*"basicBlockLines": \{\n\s*"main.c": \{\n\s*"main": "\d+"\n\s*\}\n\s*\},\n\s*"description": "block \d+ \(lines main.c:main:\d+\)",\n\s*"goal": "main\.coverage\.[1-4]",\n\s*"sourceLocation": \{(\n.+){5}\},\n\s*"status": "satisfied"\n\s*\},\n\s*\{\n\s*"basicBlockLines": \{\n\s*"main.c": \{\n\s*"main": "\d+"\n\s*\}\n\s*\},\n\s*"description": "block \d+ \(lines main.c:main:\d+\)",\n\s*"goal": "main\.coverage\.[1-4]",\n\s*"sourceLocation": \{(\n.+){5}\},\n\s*"status": "satisfied"\n\s*\},\n\s*\{\n\s*"basicBlockLines": \{\n\s*"main.c": \{\n\s*"main": "\d+"\n\s*\}\n\s*\},\n\s*"description": "block \d+ \(lines main.c:main:\d+\)",\n\s*"goal": "main\.coverage\.[1-4]",\n\s*"sourceLocation": \{(\n.+){5}\},\n\s*"status": "satisfied"\n\s*\},\n\s*\{\n\s*"basicBlockLines": \{\n\s*"main.c": \{\n\s*"main": "\d+"\n\s*\}\n\s*\},\n\s*"description": "block \d+ \(lines main.c:main:\d+\)",\n\s*"goal": "main\.coverage\.[1-4]",\n\s*"sourceLocation": \{(\n.+){5}\},\n\s*"status": "satisfied"\n\s*\}\n\s*\],\n\s*"goalsCovered": 4,\n\s*"totalGoals": 4\n\s*\}
88
--
99
^warning: ignoring

src/goto-checker/cover_goals_report_util.cpp

+47-5
Original file line numberDiff line numberDiff line change
@@ -75,9 +75,30 @@ static void output_goals_xml(const propertiest &properties, messaget &log)
7575
: "FAILED"}},
7676
{});
7777

78-
if(property_pair.second.pc->source_location().is_not_nil())
79-
xml_result.new_element() =
80-
xml(property_pair.second.pc->source_location());
78+
const auto &source_location = property_pair.second.pc->source_location();
79+
if(source_location.is_not_nil())
80+
{
81+
xml_result.new_element() = xml(source_location);
82+
83+
const irept &basic_block_lines =
84+
source_location.get_basic_block_source_lines();
85+
if(basic_block_lines.is_not_nil())
86+
{
87+
xmlt basic_block_lines_xml{"basic_block_lines"};
88+
for(const auto &file_entry : basic_block_lines.get_named_sub())
89+
{
90+
for(const auto &lines_entry : file_entry.second.get_named_sub())
91+
{
92+
xmlt line{"line"};
93+
line.set_attribute("file", id2string(file_entry.first));
94+
line.set_attribute("function", id2string(lines_entry.first));
95+
line.data = id2string(lines_entry.second.id());
96+
basic_block_lines_xml.new_element(line);
97+
}
98+
}
99+
xml_result.new_element(basic_block_lines_xml);
100+
}
101+
}
81102

82103
log.result() << xml_result;
83104
}
@@ -103,8 +124,29 @@ static void output_goals_json(
103124
json_goal["goal"] = json_stringt(property_pair.first);
104125
json_goal["description"] = json_stringt(property_info.description);
105126

106-
if(property_info.pc->source_location().is_not_nil())
107-
json_goal["sourceLocation"] = json(property_info.pc->source_location());
127+
const auto &source_location = property_info.pc->source_location();
128+
if(source_location.is_not_nil())
129+
{
130+
json_goal["sourceLocation"] = json(source_location);
131+
132+
const irept &basic_block_lines =
133+
source_location.get_basic_block_source_lines();
134+
if(basic_block_lines.is_not_nil())
135+
{
136+
json_objectt basic_block_lines_json;
137+
for(const auto &file_entry : basic_block_lines.get_named_sub())
138+
{
139+
json_objectt file_lines_json;
140+
for(const auto &lines_entry : file_entry.second.get_named_sub())
141+
{
142+
file_lines_json[id2string(lines_entry.first)] =
143+
json_stringt{lines_entry.second.id()};
144+
}
145+
basic_block_lines_json[id2string(file_entry.first)] = file_lines_json;
146+
}
147+
json_goal["basicBlockLines"] = basic_block_lines_json;
148+
}
149+
}
108150

109151
goals_array.push_back(std::move(json_goal));
110152
}

src/goto-instrument/cover_instrument_location.cpp

+1-1
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,7 @@ void cover_location_instrumentert::instrument(
3939
const auto &source_lines = basic_blocks.source_lines_of(block_nr);
4040
const std::string comment =
4141
"block " + b + " (lines " + source_lines.to_string() + ")";
42-
source_location.set_basic_block_source_lines(source_lines.to_string());
42+
source_location.set_basic_block_source_lines(source_lines.to_irep());
4343
goto_program.insert_before_swap(i_it);
4444
*i_it = make_assertion(false_exprt(), source_location);
4545
initialize_source_location(i_it, comment, function_id);

src/goto-instrument/source_lines.cpp

+36-9
Original file line numberDiff line numberDiff line change
@@ -32,18 +32,45 @@ void source_linest::insert(const source_locationt &loc)
3232
return;
3333
std::size_t line = safe_string2size_t(id2string(loc.get_line()));
3434

35-
block_lines[file + ":" + func].insert(line);
35+
block_lines[file][func].insert(line);
3636
}
3737

3838
std::string source_linest::to_string() const
3939
{
40-
std::stringstream ss;
41-
const auto map =
42-
make_range(block_lines).map([&](const block_linest::value_type &pair) {
40+
std::stringstream result;
41+
const auto full_map =
42+
make_range(block_lines)
43+
.map([&](const block_linest::value_type &file_entry) {
44+
std::stringstream ss;
45+
const auto map = make_range(file_entry.second)
46+
.map([&](const function_linest::value_type &pair) {
47+
std::vector<unsigned> line_numbers(
48+
pair.second.begin(), pair.second.end());
49+
return file_entry.first + ':' + pair.first + ':' +
50+
format_number_range(line_numbers);
51+
});
52+
join_strings(ss, map.begin(), map.end(), "; ");
53+
return ss.str();
54+
});
55+
join_strings(result, full_map.begin(), full_map.end(), "; ");
56+
return result.str();
57+
}
58+
59+
irept source_linest::to_irep() const
60+
{
61+
irept result;
62+
63+
for(const auto &file_entry : block_lines)
64+
{
65+
irept file_result;
66+
for(const auto &lines_entry : file_entry.second)
67+
{
4368
std::vector<unsigned> line_numbers(
44-
pair.second.begin(), pair.second.end());
45-
return pair.first + ':' + format_number_range(line_numbers);
46-
});
47-
join_strings(ss, map.begin(), map.end(), "; ");
48-
return ss.str();
69+
lines_entry.second.begin(), lines_entry.second.end());
70+
file_result.set(lines_entry.first, format_number_range(line_numbers));
71+
}
72+
result.add(file_entry.first, std::move(file_result));
73+
}
74+
75+
return result;
4976
}

0 commit comments

Comments
 (0)