Skip to content

Commit 7b33ea2

Browse files
authored
Refactor 'llvm2lcov' utility (#418)
- Improve the algorithm for collecting line coverage data using segments: previously, only segment start lines were used for instrumentation, but lines between these start lines were not. - Add separate 'True' and 'False' branches as it is done for gcov. - Add branches defined in expansions (like macros). The entries were placed to expansions call sites. - Fixed some problems with MC/DC branch expressions. - Add branch expressions for multiline MC/DC entries. - Add support for JSON data format version '3.0.1', which adds 'fileId' to MC/DC entries. This allows using branches from expansions for placing MC/DC from expansions to the expansions call sites. It also helps to add correct expressions for some MC/DC branches belonging to groups that contain branches from expansions. Signed-off-by: Roman Beliaev <[email protected]>
1 parent cfce43e commit 7b33ea2

File tree

6 files changed

+565
-83
lines changed

6 files changed

+565
-83
lines changed

bin/llvm2lcov

Lines changed: 189 additions & 82 deletions
Original file line numberDiff line numberDiff line change
@@ -156,10 +156,6 @@ sub parse
156156
if (defined($version) && $version ne "");
157157

158158
my $lineData = $fileInfo->test($testname);
159-
# use branch data to derive MC/DC expression - so need
160-
# it, even if user didn't ask
161-
my $branchData = $fileInfo->testbr($testname)
162-
if $lcovutil::br_coverage || $lcovutil::mcdc_coverage;
163159
my $mcdcData = $fileInfo->testcase_mcdc($testname)
164160
if $lcovutil::mcdc_coverage;
165161

@@ -170,129 +166,239 @@ sub parse
170166
my $mcdc = $f->{mcdc_records}
171167
if $lcovutil::mcdc_coverage && exists($f->{mcdc_records});
172168

173-
foreach my $s (@$segments) {
174-
die("unexpected segment data") unless scalar(@$s) == 6;
175-
my ($line, $col, $count, $hasCount, $isRegion, $isGap) =
176-
@$s;
177-
next unless $hasCount;
178-
$lineData->append($line, $count);
169+
my $index = 0;
170+
my $currentLine = 0;
171+
172+
while ($index < $#$segments) {
173+
my $segment = $segments->[$index];
174+
die("unexpected segment data")
175+
unless scalar(@$segment) == 6;
176+
my ($line, $col, $count, $hasCount, $isRegionEntry, $isGap)
177+
= @$segment;
178+
$currentLine = $line if !$currentLine;
179+
if ($hasCount) {
180+
$segment = $segments->[$index + 1];
181+
die("unexpected segment data")
182+
unless scalar(@$segment) == 6;
183+
my ($next_line, $next_col, $next_count, $next_hasCount,
184+
$next_isRegionEntry, $next_isGap)
185+
= @$segment;
186+
if ($currentLine == $next_line && !$next_isRegionEntry)
187+
{
188+
while ($next_line == $currentLine &&
189+
++$index < $#$segments) {
190+
$segment = $segments->[$index + 1];
191+
die("unexpected segment data")
192+
unless scalar(@$segment) == 6;
193+
$next_line = $segment->[0];
194+
$count = $next_count
195+
if ($count &&
196+
$next_count > $count &&
197+
$currentLine == $next_line);
198+
$next_count = $segment->[2];
199+
}
200+
$lineData->append($currentLine, $count);
201+
++$currentLine;
202+
} else {
203+
my $bound = $next_line;
204+
my $i = $index;
205+
while (!$next_isRegionEntry &&
206+
$next_line == $bound &&
207+
++$i < $#$segments) {
208+
$segment = $segments->[$i + 1];
209+
die("unexpected segment data")
210+
unless scalar(@$segment) == 6;
211+
$next_line = $segment->[0];
212+
$next_isRegionEntry = $segment->[4];
213+
}
214+
--$bound
215+
if ($next_isRegionEntry &&
216+
$next_line == $bound &&
217+
!($isRegionEntry && $line == $next_line));
218+
$count = $next_count
219+
if $next_count > $count && $line == $next_line;
220+
while ($currentLine <= $bound) {
221+
$lineData->append($currentLine, $count);
222+
++$currentLine;
223+
}
224+
++$index;
225+
}
226+
} else {
227+
do {
228+
++$index;
229+
$segment = $segments->[$index];
230+
die("unexpected segment data")
231+
unless scalar(@$segment) == 6;
232+
($line, $col, $count, $hasCount,
233+
$isRegionEntry, $isGap) = @$segment;
234+
} while (!$hasCount && $index < $#$segments);
235+
$currentLine = $isRegionEntry ? $line : $line + 1;
236+
}
179237
}
180-
181-
if ($branchData) {
182-
my $currentLine = -1;
183-
my $branchIdx;
238+
if ($mcdc) {
239+
my @mcdcBranches; # array (start line, start column, expression)
184240
foreach my $branch (@$branches) {
185241
die("unexpected branch data")
186242
unless scalar(@$branch) == 9;
243+
# Consider only branches of "MCDCBranchRegion" kind.
244+
next if ($branch->[-1] != 6);
187245
my ($line, $startCol, $endline,
188246
$endcol, $trueCount, $falseCount,
189247
$fileId, $expandedId, $kind) = @$branch;
190-
if ($line != $currentLine &&
191-
defined($lineData->value($line))) {
192-
$branchIdx = 0; # restart counter
193-
$currentLine = $line;
194-
} else {
195-
# this branch is part of the current group
196-
++$branchIdx;
197-
}
198248
my $expr =
199249
$srcReader->getExpr($line, $startCol, $endline,
200250
$endcol)
201251
if $srcReader->notEmpty();
202-
203-
my $br =
204-
BranchBlock->new($branchIdx, $trueCount, $expr);
205-
$branchData->append($line, 0, $br, $filename);
252+
push(@mcdcBranches, [$line, $startCol, $expr]);
206253
}
207-
}
208-
if ($mcdc) {
209254
foreach my $m (@$mcdc) {
210-
# what are fileID and kind?
211255
die("unexpected MC/DC data") unless scalar(@$m) == 7;
212-
my ($line, $startCol, $endLine, $endcol, $fileId,
256+
my ($line, $startCol, $endLine, $endCol, $expandedId,
213257
$kind, $cov)
214258
= @$m;
215259
die("unexpected MC/DC cov")
216260
unless 'ARRAY' eq ref($cov);
217-
my $groupSize = scalar(@$cov);
218261

219262
# read the source line and extract the expression...
220263
my $expr =
221264
$srcReader->getExpr($line, $startCol, $endLine,
222-
$endcol)
265+
$endCol)
223266
if ($srcReader->notEmpty());
224-
267+
my @brExprs;
268+
foreach my $branch (@mcdcBranches) {
269+
my ($brLine, $brCol, $brExpr) = @$branch;
270+
if (($brLine > $line ||
271+
($brLine == $line && $brCol >= $startCol))
272+
&&
273+
($brLine < $endLine ||
274+
($brLine == $endLine && $brCol <= $endCol))
275+
) {
276+
push(@brExprs, [$brLine, $brCol, $brExpr]);
277+
}
278+
}
279+
@brExprs =
280+
sort { $a->[0] <=> $b->[0] || $a->[1] <=> $b->[1] }
281+
@brExprs;
225282
my $current_mcdc =
226283
$mcdcData->new_mcdc($mcdcData, $line);
227-
my $branch = $branchData->value($line);
228-
my $idx = 0;
284+
my $groupSize = scalar(@$cov);
285+
my $idx = 0;
229286
foreach my $c (@$cov) {
230-
my $branchExpr =
231-
$branch->getBlock(0)->[$idx]->expr()
232-
if $branch &&
233-
(scalar(@{$branch->getBlock(0)}) > $idx);
234-
$branchExpr =
235-
defined($branchExpr) ?
236-
"'$branchExpr' in '$expr'" :
287+
my $branchExpr = $brExprs[$idx]->[2]
288+
if $groupSize == scalar(@brExprs);
289+
my $fullExpr =
290+
defined($branchExpr) &&
291+
defined($expr) ? "'$branchExpr' in '$expr'" :
237292
$idx;
238-
239293
$current_mcdc->insertExpr($filename, $groupSize, 0,
240-
$c, $idx, $branchExpr);
294+
$c, $idx, $fullExpr);
241295
$current_mcdc->insertExpr($filename, $groupSize, 1,
242-
$c, $idx, $branchExpr);
296+
$c, $idx, $fullExpr);
243297
++$idx;
244298
}
245299
$mcdcData->close_mcdcBlock($current_mcdc);
246300
}
247-
} # MCDC
248-
$fileInfo->testbr()->remove($testname)
249-
if $lcovutil::mcdc_coverage && !$lcovutil::br_coverage;
301+
}
250302
lcovutil::info(2, "finished parsing $filename\n");
251303
}
252304

253-
next unless $lcovutil::func_coverage;
254305
foreach my $f (@{$k->{functions}}) {
255306
my $name = $f->{name};
256307
my $filenames = $f->{filenames}; # array
257-
if ($#$filenames != 0) {
258-
lcovutil::ignorable_error($lcovutil::ERROR_USAGE,
259-
"unsupported: function $name associated with multiple files"
260-
);
261-
next;
262-
}
263308
my $filename =
264309
ReadCurrentSource::resolve_path($filenames->[0], 1);
265-
if (TraceFile::skipCurrentFile($filename)) {
266-
if (!exists($lcovutil::excluded_files{$filename})) {
267-
$lcovutil::excluded_files{$filename} = 1;
268-
lcovutil::info("Excluding $filename\n");
269-
}
270-
next;
271-
}
310+
next if (TraceFile::skipCurrentFile($filename));
272311
die('unexpected unknown file \'' . $filenames->[0] . '\'')
273312
unless $top->file_exists($filename);
274-
my $info = $top->data($filename);
275-
my $count = $f->{count};
276-
my $regions = $f->{regions}; # startline/col, endline/col/
313+
$srcReader->open($filename);
314+
315+
my $info = $top->data($filename);
316+
my $count = $f->{count};
317+
my $regions = $f->{regions}; # startline/col, endline/col/
318+
my $branches = $f->{branches};
277319

278320
my $functionMap = $info->testfnc($testname);
321+
# use branch data to derive MC/DC expression - so need
322+
# it, even if user didn't ask
323+
my $branchData = $info->testbr($testname)
324+
if $lcovutil::br_coverage;
279325
my $startLine = $regions->[0]->[0]; # startline of first region
280-
# NOTE: might be a mistake to grab the end line of the last region -
281-
# LCOV follows GCC behaviour and associates lines with where they
282-
# start - not where they end...
283-
my $endline = $regions->[-1]->[2]; # endline of last region
284-
my $func =
285-
$functionMap->define_function($name, $startLine, $endline)
286-
unless defined($functionMap->findName($name));
287-
$functionMap->add_count($name, $count);
288-
289-
# for the moment - don't worry about the coverpoints in the function
290-
#my $branches = $f->{branches};
291-
#my $mcdc = $f->{mcdc_records} if exists($f->{mcdc_records});
292-
#foreach my $r (@$regions) {
293-
# my ($startLine, $startCol, $endLine, $endCol, $count, $fileId,
294-
# $expandedId, $kind) = @$r;
295-
#}
326+
my $endline = $regions->[0]->[2]; # endline of last region
327+
if ($lcovutil::func_coverage) {
328+
my $func =
329+
$functionMap->define_function($name, $startLine,
330+
$endline)
331+
unless defined($functionMap->findName($name));
332+
$functionMap->add_count($name, $count);
333+
}
334+
if ($branchData) {
335+
my $funcBranchData = BranchData->new();
336+
my $regionIdx = 0;
337+
foreach my $b (@$branches) {
338+
die("unexpected branch data") unless scalar(@$b) == 9;
339+
my ($brStartLine, $brStartCol, $endLine,
340+
$endCol, $trueCount, $falseCount,
341+
$fileId, $expandedId, $kind) = @$b;
342+
my ($line, $col) = ($brStartLine, $brStartCol);
343+
my $expr;
344+
345+
if ($fileId == 0) {
346+
$expr =
347+
$srcReader->getExpr($line, $col, $endLine,
348+
$endCol)
349+
if $srcReader->notEmpty();
350+
} else {
351+
# Find a source range, which contains the branch.
352+
while ($regionIdx < scalar(@$regions)) {
353+
my ($rStartLine, $rStartCol, $rEndLine,
354+
$rEndCol, $rCount, $rFileId,
355+
$rExpandedId, $rKind
356+
) = @{$regions->[$regionIdx]};
357+
if ($rExpandedId == $fileId && $rKind == 1) {
358+
if ($rFileId != 0) {
359+
# Check previous regions to find one
360+
# that describes lines of the function's
361+
# source file.
362+
my $rIdx = $regionIdx - 1;
363+
$fileId = $rFileId;
364+
while ($fileId != 0 && $rIdx >= 0) {
365+
($rStartLine, $rStartCol,
366+
$rEndLine, $rEndCol,
367+
$rCount, $rFileId,
368+
$rExpandedId, $rKind
369+
) = @{$regions->[$rIdx]};
370+
$fileId = $rFileId
371+
if ($rExpandedId == $fileId &&
372+
$rKind == 1);
373+
--$rIdx;
374+
}
375+
}
376+
($line, $col) = ($rStartLine, $rStartCol);
377+
last;
378+
}
379+
++$regionIdx;
380+
}
381+
}
382+
# Processed branch on the same line doesn't have to be the previous.
383+
my $brEntry = $funcBranchData->value($line);
384+
my $branchIdx =
385+
!defined($brEntry) ? 0 :
386+
scalar(@{$brEntry->getBlock(0)});
387+
my $br =
388+
BranchBlock->new($branchIdx, $trueCount,
389+
!defined($expr) ? $branchIdx :
390+
"(" . $expr . ") == True");
391+
$funcBranchData->append($line, 0, $br, $filename);
392+
393+
++$branchIdx;
394+
$br =
395+
BranchBlock->new($branchIdx, $falseCount,
396+
!defined($expr) ? $branchIdx :
397+
"(" . $expr . ") == False");
398+
$funcBranchData->append($line, 0, $br, $filename);
399+
}
400+
$branchData->union($funcBranchData);
401+
}
296402
}
297403
}
298404
lcovutil::info(2, "finished $jsonFile\n");
@@ -324,7 +430,8 @@ my %opts = ('test-name|t=s' => \$testname,
324430
'output-filename|o=s' => \$output_filename,);
325431
my %rc_opts;
326432
if (!lcovutil::parseOptions(\%rc_opts, \%opts, \$output_filename)) {
327-
print(STDERR "argparse failed");
433+
print(STDERR "argparse failed\n");
434+
exit(1);
328435
}
329436

330437
my $info = parse($testname, @ARGV);

tests/Makefile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ all: check report
1212

1313
include common.mak
1414

15-
TESTS := genhtml lcov gendiffcov py2lcov perl2lcov xml2lcov
15+
TESTS := genhtml lcov gendiffcov llvm2lcov py2lcov perl2lcov xml2lcov
1616

1717
# there may or may not be some .info files generated for exported
1818
# tools - py2lcov, perl2lcov, etc. We want them included in the

tests/llvm2lcov/Makefile

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
include ../common.mak
2+
3+
TESTS := llvm2lcov.sh
4+
5+
clean:
6+
$(shell ./llvm2lcov.sh --clean)

0 commit comments

Comments
 (0)