Skip to content

Commit 865e4a1

Browse files
hanickadotcor3ntin
andauthored
[coverage] skipping code coverage for 'if constexpr' and 'if consteval' (#78033)
`if constexpr` and `if consteval` conditional statements code coverage should behave more like a preprocesor `#if`-s than normal ConditionalStmt. This PR should fix that. --------- Co-authored-by: cor3ntin <[email protected]>
1 parent 1a5eead commit 865e4a1

File tree

7 files changed

+325
-80
lines changed

7 files changed

+325
-80
lines changed

clang/docs/ReleaseNotes.rst

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -811,7 +811,7 @@ Bug Fixes in This Version
811811
invalidation by invalid initializer Expr.
812812
Fixes (`#30908 <https://github.com/llvm/llvm-project/issues/30908>`_)
813813
- Clang now emits correct source location for code-coverage regions in `if constexpr`
814-
and `if consteval` branches.
814+
and `if consteval` branches. Untaken branches are now skipped.
815815
Fixes (`#54419 <https://github.com/llvm/llvm-project/issues/54419>`_)
816816
- Fix assertion failure when declaring a template friend function with
817817
a constrained parameter in a template class that declares a class method

clang/lib/CodeGen/CoverageMappingGen.cpp

Lines changed: 182 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -119,26 +119,31 @@ class SourceMappingRegion {
119119
/// as the line execution count if there are no other regions on the line.
120120
bool GapRegion;
121121

122+
/// Whetever this region is skipped ('if constexpr' or 'if consteval' untaken
123+
/// branch, or anything skipped but not empty line / comments)
124+
bool SkippedRegion;
125+
122126
public:
123127
SourceMappingRegion(Counter Count, std::optional<SourceLocation> LocStart,
124128
std::optional<SourceLocation> LocEnd,
125129
bool GapRegion = false)
126-
: Count(Count), LocStart(LocStart), LocEnd(LocEnd), GapRegion(GapRegion) {
127-
}
130+
: Count(Count), LocStart(LocStart), LocEnd(LocEnd), GapRegion(GapRegion),
131+
SkippedRegion(false) {}
128132

129133
SourceMappingRegion(Counter Count, std::optional<Counter> FalseCount,
130134
MCDCParameters MCDCParams,
131135
std::optional<SourceLocation> LocStart,
132136
std::optional<SourceLocation> LocEnd,
133137
bool GapRegion = false)
134138
: Count(Count), FalseCount(FalseCount), MCDCParams(MCDCParams),
135-
LocStart(LocStart), LocEnd(LocEnd), GapRegion(GapRegion) {}
139+
LocStart(LocStart), LocEnd(LocEnd), GapRegion(GapRegion),
140+
SkippedRegion(false) {}
136141

137142
SourceMappingRegion(MCDCParameters MCDCParams,
138143
std::optional<SourceLocation> LocStart,
139144
std::optional<SourceLocation> LocEnd)
140145
: MCDCParams(MCDCParams), LocStart(LocStart), LocEnd(LocEnd),
141-
GapRegion(false) {}
146+
GapRegion(false), SkippedRegion(false) {}
142147

143148
const Counter &getCounter() const { return Count; }
144149

@@ -174,6 +179,10 @@ class SourceMappingRegion {
174179

175180
void setGap(bool Gap) { GapRegion = Gap; }
176181

182+
bool isSkipped() const { return SkippedRegion; }
183+
184+
void setSkipped(bool Skipped) { SkippedRegion = Skipped; }
185+
177186
bool isBranch() const { return FalseCount.has_value(); }
178187

179188
bool isMCDCDecision() const { return MCDCParams.NumConditions != 0; }
@@ -468,6 +477,10 @@ class CoverageMappingBuilder {
468477
MappingRegions.push_back(CounterMappingRegion::makeGapRegion(
469478
Region.getCounter(), *CovFileID, SR.LineStart, SR.ColumnStart,
470479
SR.LineEnd, SR.ColumnEnd));
480+
} else if (Region.isSkipped()) {
481+
MappingRegions.push_back(CounterMappingRegion::makeSkipped(
482+
*CovFileID, SR.LineStart, SR.ColumnStart, SR.LineEnd,
483+
SR.ColumnEnd));
471484
} else if (Region.isBranch()) {
472485
MappingRegions.push_back(CounterMappingRegion::makeBranchRegion(
473486
Region.getCounter(), Region.getFalseCounter(),
@@ -1251,6 +1264,69 @@ struct CounterCoverageMappingBuilder
12511264
popRegions(Index);
12521265
}
12531266

1267+
/// Find a valid range starting with \p StartingLoc and ending before \p
1268+
/// BeforeLoc.
1269+
std::optional<SourceRange> findAreaStartingFromTo(SourceLocation StartingLoc,
1270+
SourceLocation BeforeLoc) {
1271+
// If StartingLoc is in function-like macro, use its start location.
1272+
if (StartingLoc.isMacroID()) {
1273+
FileID FID = SM.getFileID(StartingLoc);
1274+
const SrcMgr::ExpansionInfo *EI = &SM.getSLocEntry(FID).getExpansion();
1275+
if (EI->isFunctionMacroExpansion())
1276+
StartingLoc = EI->getExpansionLocStart();
1277+
}
1278+
1279+
size_t StartDepth = locationDepth(StartingLoc);
1280+
size_t EndDepth = locationDepth(BeforeLoc);
1281+
while (!SM.isWrittenInSameFile(StartingLoc, BeforeLoc)) {
1282+
bool UnnestStart = StartDepth >= EndDepth;
1283+
bool UnnestEnd = EndDepth >= StartDepth;
1284+
if (UnnestEnd) {
1285+
assert(SM.isWrittenInSameFile(getStartOfFileOrMacro(BeforeLoc),
1286+
BeforeLoc));
1287+
1288+
BeforeLoc = getIncludeOrExpansionLoc(BeforeLoc);
1289+
assert(BeforeLoc.isValid());
1290+
EndDepth--;
1291+
}
1292+
if (UnnestStart) {
1293+
assert(SM.isWrittenInSameFile(StartingLoc,
1294+
getStartOfFileOrMacro(StartingLoc)));
1295+
1296+
StartingLoc = getIncludeOrExpansionLoc(StartingLoc);
1297+
assert(StartingLoc.isValid());
1298+
StartDepth--;
1299+
}
1300+
}
1301+
// If the start and end locations of the gap are both within the same macro
1302+
// file, the range may not be in source order.
1303+
if (StartingLoc.isMacroID() || BeforeLoc.isMacroID())
1304+
return std::nullopt;
1305+
if (!SM.isWrittenInSameFile(StartingLoc, BeforeLoc) ||
1306+
!SpellingRegion(SM, StartingLoc, BeforeLoc).isInSourceOrder())
1307+
return std::nullopt;
1308+
return {{StartingLoc, BeforeLoc}};
1309+
}
1310+
1311+
void markSkipped(SourceLocation StartLoc, SourceLocation BeforeLoc) {
1312+
const auto Skipped = findAreaStartingFromTo(StartLoc, BeforeLoc);
1313+
1314+
if (!Skipped)
1315+
return;
1316+
1317+
const auto NewStartLoc = Skipped->getBegin();
1318+
const auto EndLoc = Skipped->getEnd();
1319+
1320+
if (NewStartLoc == EndLoc)
1321+
return;
1322+
assert(SpellingRegion(SM, NewStartLoc, EndLoc).isInSourceOrder());
1323+
handleFileExit(NewStartLoc);
1324+
size_t Index = pushRegion({}, NewStartLoc, EndLoc);
1325+
getRegion().setSkipped(true);
1326+
handleFileExit(EndLoc);
1327+
popRegions(Index);
1328+
}
1329+
12541330
/// Keep counts of breaks and continues inside loops.
12551331
struct BreakContinue {
12561332
Counter BreakCount;
@@ -1700,43 +1776,119 @@ struct CounterCoverageMappingBuilder
17001776
Visit(S->getSubStmt());
17011777
}
17021778

1779+
void coverIfConsteval(const IfStmt *S) {
1780+
assert(S->isConsteval());
1781+
1782+
const auto *Then = S->getThen();
1783+
const auto *Else = S->getElse();
1784+
1785+
// It's better for llvm-cov to create a new region with same counter
1786+
// so line-coverage can be properly calculated for lines containing
1787+
// a skipped region (without it the line is marked uncovered)
1788+
const Counter ParentCount = getRegion().getCounter();
1789+
1790+
extendRegion(S);
1791+
1792+
if (S->isNegatedConsteval()) {
1793+
// ignore 'if consteval'
1794+
markSkipped(S->getIfLoc(), getStart(Then));
1795+
propagateCounts(ParentCount, Then);
1796+
1797+
if (Else) {
1798+
// ignore 'else <else>'
1799+
markSkipped(getEnd(Then), getEnd(Else));
1800+
}
1801+
} else {
1802+
assert(S->isNonNegatedConsteval());
1803+
// ignore 'if consteval <then> [else]'
1804+
markSkipped(S->getIfLoc(), Else ? getStart(Else) : getEnd(Then));
1805+
1806+
if (Else)
1807+
propagateCounts(ParentCount, Else);
1808+
}
1809+
}
1810+
1811+
void coverIfConstexpr(const IfStmt *S) {
1812+
assert(S->isConstexpr());
1813+
1814+
// evaluate constant condition...
1815+
const auto *E = cast<ConstantExpr>(S->getCond());
1816+
const bool isTrue = E->getResultAsAPSInt().getExtValue();
1817+
1818+
extendRegion(S);
1819+
1820+
// I'm using 'propagateCounts' later as new region is better and allows me
1821+
// to properly calculate line coverage in llvm-cov utility
1822+
const Counter ParentCount = getRegion().getCounter();
1823+
1824+
// ignore 'if constexpr ('
1825+
SourceLocation startOfSkipped = S->getIfLoc();
1826+
1827+
if (const auto *Init = S->getInit()) {
1828+
const auto start = getStart(Init);
1829+
const auto end = getEnd(Init);
1830+
1831+
// this check is to make sure typedef here which doesn't have valid source
1832+
// location won't crash it
1833+
if (start.isValid() && end.isValid()) {
1834+
markSkipped(startOfSkipped, start);
1835+
propagateCounts(ParentCount, Init);
1836+
startOfSkipped = getEnd(Init);
1837+
}
1838+
}
1839+
1840+
const auto *Then = S->getThen();
1841+
const auto *Else = S->getElse();
1842+
1843+
if (isTrue) {
1844+
// ignore '<condition>)'
1845+
markSkipped(startOfSkipped, getStart(Then));
1846+
propagateCounts(ParentCount, Then);
1847+
1848+
if (Else)
1849+
// ignore 'else <else>'
1850+
markSkipped(getEnd(Then), getEnd(Else));
1851+
} else {
1852+
// ignore '<condition>) <then> [else]'
1853+
markSkipped(startOfSkipped, Else ? getStart(Else) : getEnd(Then));
1854+
1855+
if (Else)
1856+
propagateCounts(ParentCount, Else);
1857+
}
1858+
}
1859+
17031860
void VisitIfStmt(const IfStmt *S) {
1861+
// "if constexpr" and "if consteval" are not normal conditional statements,
1862+
// their discarded statement should be skipped
1863+
if (S->isConsteval())
1864+
return coverIfConsteval(S);
1865+
else if (S->isConstexpr())
1866+
return coverIfConstexpr(S);
1867+
17041868
extendRegion(S);
17051869
if (S->getInit())
17061870
Visit(S->getInit());
17071871

17081872
// Extend into the condition before we propagate through it below - this is
17091873
// needed to handle macros that generate the "if" but not the condition.
1710-
if (!S->isConsteval())
1711-
extendRegion(S->getCond());
1874+
extendRegion(S->getCond());
17121875

17131876
Counter ParentCount = getRegion().getCounter();
1877+
Counter ThenCount = getRegionCounter(S);
17141878

1715-
// If this is "if !consteval" the then-branch will never be taken, we don't
1716-
// need to change counter
1717-
Counter ThenCount =
1718-
S->isNegatedConsteval() ? ParentCount : getRegionCounter(S);
1879+
// Emitting a counter for the condition makes it easier to interpret the
1880+
// counter for the body when looking at the coverage.
1881+
propagateCounts(ParentCount, S->getCond());
17191882

1720-
if (!S->isConsteval()) {
1721-
// Emitting a counter for the condition makes it easier to interpret the
1722-
// counter for the body when looking at the coverage.
1723-
propagateCounts(ParentCount, S->getCond());
1724-
1725-
// The 'then' count applies to the area immediately after the condition.
1726-
std::optional<SourceRange> Gap =
1727-
findGapAreaBetween(S->getRParenLoc(), getStart(S->getThen()));
1728-
if (Gap)
1729-
fillGapAreaWithCount(Gap->getBegin(), Gap->getEnd(), ThenCount);
1730-
}
1883+
// The 'then' count applies to the area immediately after the condition.
1884+
std::optional<SourceRange> Gap =
1885+
findGapAreaBetween(S->getRParenLoc(), getStart(S->getThen()));
1886+
if (Gap)
1887+
fillGapAreaWithCount(Gap->getBegin(), Gap->getEnd(), ThenCount);
17311888

17321889
extendRegion(S->getThen());
17331890
Counter OutCount = propagateCounts(ThenCount, S->getThen());
1734-
1735-
// If this is "if consteval" the else-branch will never be taken, we don't
1736-
// need to change counter
1737-
Counter ElseCount = S->isNonNegatedConsteval()
1738-
? ParentCount
1739-
: subtractCounters(ParentCount, ThenCount);
1891+
Counter ElseCount = subtractCounters(ParentCount, ThenCount);
17401892

17411893
if (const Stmt *Else = S->getElse()) {
17421894
bool ThenHasTerminateStmt = HasTerminateStmt;
@@ -1759,11 +1911,9 @@ struct CounterCoverageMappingBuilder
17591911
GapRegionCounter = OutCount;
17601912
}
17611913

1762-
if (!S->isConsteval()) {
1763-
// Create Branch Region around condition.
1764-
createBranchRegion(S->getCond(), ThenCount,
1765-
subtractCounters(ParentCount, ThenCount));
1766-
}
1914+
// Create Branch Region around condition.
1915+
createBranchRegion(S->getCond(), ThenCount,
1916+
subtractCounters(ParentCount, ThenCount));
17671917
}
17681918

17691919
void VisitCXXTryStmt(const CXXTryStmt *S) {

clang/test/CoverageMapping/branch-constfolded.cpp

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -90,10 +90,10 @@ bool for_7(bool a) { // MCDC: Decision,File 0, [[@LINE+1]]:10 -> [[@LINE+1
9090
} // CHECK: Branch,File 0, [[@LINE-1]]:15 -> [[@LINE-1]]:19 = 0, 0
9191

9292
// CHECK-LABEL: _Z5for_8b:
93-
bool for_8(bool a) { // MCDC: Decision,File 0, [[@LINE+3]]:17 -> [[@LINE+3]]:30 = M:0, C:2
94-
// CHECK: Branch,File 0, [[@LINE+2]]:17 -> [[@LINE+2]]:21 = 0, 0
95-
// CHECK: Branch,File 0, [[@LINE+1]]:25 -> [[@LINE+1]]:30 = 0, 0
96-
if constexpr (true && false)
93+
bool for_8(bool a) { // MCDC: Decision,File 0, [[@LINE+3]]:7 -> [[@LINE+3]]:20 = M:0, C:2
94+
// CHECK: Branch,File 0, [[@LINE+2]]:7 -> [[@LINE+2]]:11 = 0, 0
95+
// CHECK: Branch,File 0, [[@LINE+1]]:15 -> [[@LINE+1]]:20 = 0, 0
96+
if (true && false)
9797
return true;
9898
else
9999
return false;

0 commit comments

Comments
 (0)