Skip to content

Commit 716c90c

Browse files
committed
[coverage] MCDC reports unrechable and uncoverable conditions
1 parent 733acf4 commit 716c90c

File tree

11 files changed

+216
-73
lines changed

11 files changed

+216
-73
lines changed

clang/docs/SourceBasedCodeCoverage.rst

+12
Original file line numberDiff line numberDiff line change
@@ -517,6 +517,18 @@ starts a new boolean expression that is separated from the other conditions by
517517
the operator ``func()``. When this is encountered, a warning will be generated
518518
and the boolean expression will not be instrumented.
519519

520+
Similar as branch coverage, MCDC also is not tracked for constant folded conditions.
521+
Moreover it's even not tracked for conditions that can not effect outcomes of decisions
522+
due to other constants. These conditions will be shown as ``uncoverable`` or
523+
``unreachable``, determined by whether they can be visited. For instance, in
524+
``a || true || b``, value of the decision is always ``true`` by reason of the constant
525+
condition. Thus ``a`` can not lead the decision to ``false`` even though it could branch,
526+
while ``b`` is always short-circuited. Hence ``a`` is shown as ``uncoverable``
527+
and ``b`` is marked as ``unreachable``. Statistics of MCDC does not count constant
528+
conditions which do not vary or such conditions which make no difference on value of
529+
decisions. If a decision is proved to no branch theoretically, it shows mark ``Folded``
530+
rather than coverage percent.
531+
520532
Switch statements
521533
-----------------
522534

llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h

+51-19
Original file line numberDiff line numberDiff line change
@@ -384,6 +384,13 @@ struct MCDCRecord {
384384
/// are effectively ignored.
385385
enum CondState { MCDC_DontCare = -1, MCDC_False = 0, MCDC_True = 1 };
386386

387+
enum CondResult {
388+
MCDC_Normal,
389+
MCDC_Constant,
390+
MCDC_Uncoverable,
391+
MCDC_Unreachable
392+
};
393+
387394
/// Emulate SmallVector<CondState> with a pair of BitVector.
388395
///
389396
/// True False DontCare (Impossible)
@@ -442,30 +449,39 @@ struct MCDCRecord {
442449
using TVPairMap = llvm::DenseMap<unsigned, TVRowPair>;
443450
using CondIDMap = llvm::DenseMap<unsigned, unsigned>;
444451
using LineColPairMap = llvm::DenseMap<unsigned, LineColPair>;
452+
using ResultVector = llvm::SmallVector<CondResult>;
445453

446454
private:
447455
CounterMappingRegion Region;
448456
TestVectors TV;
449457
TVPairMap IndependencePairs;
450-
BoolVector Folded;
458+
ResultVector CondResults;
451459
CondIDMap PosToID;
452460
LineColPairMap CondLoc;
453461

454462
public:
455463
MCDCRecord(const CounterMappingRegion &Region, TestVectors &&TV,
456-
TVPairMap &&IndependencePairs, BoolVector &&Folded,
464+
TVPairMap &&IndependencePairs, ResultVector &&CondResults,
457465
CondIDMap &&PosToID, LineColPairMap &&CondLoc)
458466
: Region(Region), TV(std::move(TV)),
459467
IndependencePairs(std::move(IndependencePairs)),
460-
Folded(std::move(Folded)), PosToID(std::move(PosToID)),
468+
CondResults(std::move(CondResults)), PosToID(std::move(PosToID)),
461469
CondLoc(std::move(CondLoc)){};
462470

463471
CounterMappingRegion getDecisionRegion() const { return Region; }
464472
unsigned getNumConditions() const {
465473
return Region.getDecisionParams().NumConditions;
466474
}
467475
unsigned getNumTestVectors() const { return TV.size(); }
468-
bool isCondFolded(unsigned Condition) const { return Folded[Condition]; }
476+
bool isCondCoverable(unsigned Condition) const {
477+
return getCondResult(Condition) == CondResult::MCDC_Normal;
478+
}
479+
bool isCondConstant(unsigned Condition) const {
480+
return getCondResult(Condition) == CondResult::MCDC_Constant;
481+
}
482+
CondResult getCondResult(unsigned Condition) const {
483+
return CondResults[Condition];
484+
}
469485

470486
/// Return the evaluation of a condition (indicated by Condition) in an
471487
/// executed test vector (indicated by TestVectorIndex), which will be True,
@@ -505,20 +521,25 @@ struct MCDCRecord {
505521
return IndependencePairs[PosToID[Condition]];
506522
}
507523

508-
float getPercentCovered() const {
509-
unsigned Folded = 0;
524+
/// Return if the decision is coverable and percent of covered conditions.
525+
/// Only coverable conditions are counted as denominator.
526+
std::pair<bool, float> getPercentCovered() const {
527+
unsigned Excluded = 0;
510528
unsigned Covered = 0;
511529
for (unsigned C = 0; C < getNumConditions(); C++) {
512-
if (isCondFolded(C))
513-
Folded++;
530+
if (isCondConstant(C))
531+
Excluded++;
514532
else if (isConditionIndependencePairCovered(C))
515533
Covered++;
516534
}
517535

518-
unsigned Total = getNumConditions() - Folded;
536+
unsigned Total = getNumConditions() - Excluded;
519537
if (Total == 0)
520-
return 0.0;
521-
return (static_cast<double>(Covered) / static_cast<double>(Total)) * 100.0;
538+
return {false, 0.0};
539+
return {
540+
true,
541+
(static_cast<double>(Covered) / static_cast<double>(Total)) * 100.0,
542+
};
522543
}
523544

524545
std::string getConditionHeaderString(unsigned Condition) {
@@ -553,7 +574,7 @@ struct MCDCRecord {
553574
// Add individual condition values to the string.
554575
OS << " " << TestVectorIndex + 1 << " { ";
555576
for (unsigned Condition = 0; Condition < NumConditions; Condition++) {
556-
if (isCondFolded(Condition))
577+
if (isCondConstant(Condition))
557578
OS << "C";
558579
else {
559580
switch (getTVCondition(TestVectorIndex, Condition)) {
@@ -589,14 +610,25 @@ struct MCDCRecord {
589610
std::ostringstream OS;
590611

591612
OS << " C" << Condition + 1 << "-Pair: ";
592-
if (isCondFolded(Condition)) {
613+
switch (getCondResult(Condition)) {
614+
case CondResult::MCDC_Normal:
615+
if (isConditionIndependencePairCovered(Condition)) {
616+
TVRowPair rows = getConditionIndependencePair(Condition);
617+
OS << "covered: (" << rows.first << ",";
618+
OS << rows.second << ")\n";
619+
} else
620+
OS << "not covered\n";
621+
break;
622+
case CondResult::MCDC_Constant:
593623
OS << "constant folded\n";
594-
} else if (isConditionIndependencePairCovered(Condition)) {
595-
TVRowPair rows = getConditionIndependencePair(Condition);
596-
OS << "covered: (" << rows.first << ",";
597-
OS << rows.second << ")\n";
598-
} else
599-
OS << "not covered\n";
624+
break;
625+
case CondResult::MCDC_Uncoverable:
626+
OS << "uncoverable\n";
627+
break;
628+
case CondResult::MCDC_Unreachable:
629+
OS << "unreachable\n";
630+
break;
631+
}
600632

601633
return OS.str();
602634
}

llvm/lib/ProfileData/Coverage/CoverageMapping.cpp

+112-26
Original file line numberDiff line numberDiff line change
@@ -365,11 +365,15 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder {
365365
unsigned NumConditions;
366366

367367
/// Vector used to track whether a condition is constant folded.
368-
MCDCRecord::BoolVector Folded;
368+
MCDCRecord::ResultVector CondResults;
369369

370370
/// Mapping of calculated MC/DC Independence Pairs for each condition.
371371
MCDCRecord::TVPairMap IndependencePairs;
372372

373+
/// All possible Test Vectors for the boolean expression derived from
374+
/// binary decision diagran of the expression.
375+
MCDCRecord::TestVectors TestVectors;
376+
373377
/// Storage for ExecVectors
374378
/// ExecVectors is the alias of its 0th element.
375379
std::array<MCDCRecord::TestVectors, 2> ExecVectorsByCond;
@@ -395,8 +399,9 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder {
395399
: NextIDsBuilder(Branches), TVIdxBuilder(this->NextIDs), Bitmap(Bitmap),
396400
Region(Region), DecisionParams(Region.getDecisionParams()),
397401
Branches(Branches), NumConditions(DecisionParams.NumConditions),
398-
Folded(NumConditions, false), IndependencePairs(NumConditions),
399-
ExecVectors(ExecVectorsByCond[false]), IsVersion11(IsVersion11) {}
402+
CondResults(NumConditions, MCDCRecord::CondResult::MCDC_Normal),
403+
IndependencePairs(NumConditions), ExecVectors(ExecVectorsByCond[false]),
404+
IsVersion11(IsVersion11) {}
400405

401406
private:
402407
// Walk the binary decision diagram and try assigning both false and true to
@@ -418,6 +423,7 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder {
418423

419424
assert(TVIdx < SavedNodes[ID].Width);
420425
assert(TVIdxs.insert(NextTVIdx).second && "Duplicate TVIdx");
426+
TestVectors.push_back({TV, MCDCCond});
421427

422428
if (!Bitmap[IsVersion11
423429
? DecisionParams.BitmapIdx * CHAR_BIT + TV.getIndex()
@@ -445,7 +451,6 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder {
445451
buildTestVector(TV, 0, 0);
446452
assert(TVIdxs.size() == unsigned(NumTestVectors) &&
447453
"TVIdxs wasn't fulfilled");
448-
449454
// Fill ExecVectors order by False items and True items.
450455
// ExecVectors is the alias of ExecVectorsByCond[false], so
451456
// Append ExecVectorsByCond[true] on it.
@@ -477,48 +482,130 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder {
477482
}
478483
}
479484

485+
void findCoverablePairs(const MCDCRecord::CondIDMap &PosToID) {
486+
llvm::SmallVector<unsigned> FoldedCondPos;
487+
for (unsigned I = 0; I < CondResults.size(); ++I) {
488+
if (CondResults[I] == MCDCRecord::MCDC_Constant ||
489+
CondResults[I] == MCDCRecord::MCDC_Unreachable) {
490+
FoldedCondPos.push_back(I);
491+
}
492+
}
493+
if (FoldedCondPos.empty()) {
494+
return;
495+
}
496+
std::array<MCDCRecord::TestVectors, 2> PracticalTestVectorsByCond;
497+
for (const auto &TVWithCond : TestVectors) {
498+
const bool Practical =
499+
llvm::all_of(FoldedCondPos, [&](const unsigned &Pos) {
500+
const auto &[TV, Cond] = TVWithCond;
501+
const auto ID = PosToID.at(Pos);
502+
if (TV[ID] == MCDCRecord::MCDC_DontCare) {
503+
return true;
504+
}
505+
if (CondResults[Pos] == MCDCRecord::MCDC_Constant) {
506+
const auto ConstantValue = Branches[Pos]->Count.isZero()
507+
? MCDCRecord::MCDC_False
508+
: MCDCRecord::MCDC_True;
509+
if (TV[ID] == ConstantValue) {
510+
return true;
511+
}
512+
}
513+
return false;
514+
});
515+
516+
if (Practical) {
517+
PracticalTestVectorsByCond[TVWithCond.second].push_back(TVWithCond);
518+
}
519+
}
520+
521+
// If a condition:
522+
// - is uncoverable, all test vectors in exact one element of
523+
// `PracticalTestVectorsByCond` show it is `DontCare`;
524+
// - is unreachable, all test vectors in both elements of
525+
// `PracticalTestVectorsByCond` show it is `DontCare`;
526+
//
527+
// Otherwise, the condition is coverable as long as it has not been marked
528+
// as constant or unreachable before.
529+
for (unsigned Pos = 0; Pos < Branches.size(); ++Pos) {
530+
if (CondResults[Pos] != MCDCRecord::MCDC_Normal) {
531+
continue;
532+
}
533+
const auto ID = PosToID.at(Pos);
534+
unsigned InaccessibleCondCount =
535+
llvm::count_if(PracticalTestVectorsByCond,
536+
[=](const MCDCRecord::TestVectors &TestVectors) {
537+
for (const auto &[TV, Cond] : TestVectors) {
538+
if (TV[ID] != MCDCRecord::MCDC_DontCare) {
539+
return false;
540+
}
541+
}
542+
return true;
543+
});
544+
switch (InaccessibleCondCount) {
545+
case 1:
546+
CondResults[Pos] = MCDCRecord::CondResult::MCDC_Uncoverable;
547+
break;
548+
case 2:
549+
CondResults[Pos] = MCDCRecord::CondResult::MCDC_Unreachable;
550+
break;
551+
default:
552+
break;
553+
}
554+
}
555+
}
556+
480557
public:
481558
/// Process the MC/DC Record in order to produce a result for a boolean
482559
/// expression. This process includes tracking the conditions that comprise
483560
/// the decision region, calculating the list of all possible test vectors,
484561
/// marking the executed test vectors, and then finding an Independence Pair
485562
/// out of the executed test vectors for each condition in the boolean
486-
/// expression. A condition is tracked to ensure that its ID can be mapped to
487-
/// its ordinal position in the boolean expression. The condition's source
488-
/// location is also tracked, as well as whether it is constant folded (in
489-
/// which case it is excuded from the metric).
563+
/// expression. A condition is tracked to ensure that its ID can be mapped
564+
/// to its ordinal position in the boolean expression. The condition's
565+
/// source location is also tracked, as well as whether it is constant
566+
/// folded (in which case it is excuded from the metric).
490567
MCDCRecord processMCDCRecord() {
491568
unsigned I = 0;
492569
MCDCRecord::CondIDMap PosToID;
493570
MCDCRecord::LineColPairMap CondLoc;
494571

495572
// Walk the Record's BranchRegions (representing Conditions) in order to:
496-
// - Hash the condition based on its corresponding ID. This will be used to
573+
// - Hash the condition based on its corresponding ID. This will be used
574+
// to
497575
// calculate the test vectors.
498576
// - Keep a map of the condition's ordinal position (1, 2, 3, 4) to its
499577
// actual ID. This will be used to visualize the conditions in the
500578
// correct order.
501579
// - Keep track of the condition source location. This will be used to
502580
// visualize where the condition is.
503-
// - Record whether the condition is constant folded so that we exclude it
581+
// - Record whether the condition is folded so that we exclude it
504582
// from being measured.
505583
for (const auto *B : Branches) {
506584
const auto &BranchParams = B->getBranchParams();
507585
PosToID[I] = BranchParams.ID;
508586
CondLoc[I] = B->startLoc();
509-
Folded[I++] = (B->Count.isZero() || B->FalseCount.isZero());
587+
if (B->Count.isZero() && B->FalseCount.isZero()) {
588+
CondResults[I] = MCDCRecord::CondResult::MCDC_Unreachable;
589+
} else if (B->Count.isZero() || B->FalseCount.isZero()) {
590+
CondResults[I] = MCDCRecord::CondResult::MCDC_Constant;
591+
}
592+
++I;
510593
}
511594

512595
// Using Profile Bitmap from runtime, mark the executed test vectors.
513596
findExecutedTestVectors();
514597

515-
// Compare executed test vectors against each other to find an independence
516-
// pairs for each condition. This processing takes the most time.
598+
// Compare executed test vectors against each other to find an
599+
// independence pairs for each condition. This processing takes the most
600+
// time.
517601
findIndependencePairs();
518602

603+
// Identify all conditions making no difference on outcome of the decision.
604+
findCoverablePairs(PosToID);
605+
519606
// Record Test vectors, executed vectors, and independence pairs.
520607
return MCDCRecord(Region, std::move(ExecVectors),
521-
std::move(IndependencePairs), std::move(Folded),
608+
std::move(IndependencePairs), std::move(CondResults),
522609
std::move(PosToID), std::move(CondLoc));
523610
}
524611
};
@@ -910,8 +997,8 @@ Error CoverageMapping::loadFunctionRecord(
910997
}
911998

912999
// Don't create records for (filenames, function) pairs we've already seen.
913-
auto FilenamesHash = hash_combine_range(Record.Filenames.begin(),
914-
Record.Filenames.end());
1000+
auto FilenamesHash =
1001+
hash_combine_range(Record.Filenames.begin(), Record.Filenames.end());
9151002
if (!RecordProvenance[FilenamesHash].insert(hash_value(OrigFuncName)).second)
9161003
return Error::success();
9171004

@@ -961,12 +1048,11 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
9611048

9621049
// If E is a no_data_found error, returns success. Otherwise returns E.
9631050
static Error handleMaybeNoDataFoundError(Error E) {
964-
return handleErrors(
965-
std::move(E), [](const CoverageMapError &CME) {
966-
if (CME.get() == coveragemap_error::no_data_found)
967-
return static_cast<Error>(Error::success());
968-
return make_error<CoverageMapError>(CME.get(), CME.getMessage());
969-
});
1051+
return handleErrors(std::move(E), [](const CoverageMapError &CME) {
1052+
if (CME.get() == coveragemap_error::no_data_found)
1053+
return static_cast<Error>(Error::success());
1054+
return make_error<CoverageMapError>(CME.get(), CME.getMessage());
1055+
});
9701056
}
9711057

9721058
Error CoverageMapping::loadFromFile(
@@ -1058,7 +1144,7 @@ Expected<std::unique_ptr<CoverageMapping>> CoverageMapping::load(
10581144
std::string Path = std::move(*PathOpt);
10591145
StringRef Arch = Arches.size() == 1 ? Arches.front() : StringRef();
10601146
if (Error E = loadFromFile(Path, Arch, CompilationDir, *ProfileReader,
1061-
*Coverage, DataFound))
1147+
*Coverage, DataFound))
10621148
return std::move(E);
10631149
} else if (CheckBinaryIDs) {
10641150
return createFileError(
@@ -1152,9 +1238,9 @@ class SegmentBuilder {
11521238
// emit closing segments in sorted order.
11531239
auto CompletedRegionsIt = ActiveRegions.begin() + FirstCompletedRegion;
11541240
std::stable_sort(CompletedRegionsIt, ActiveRegions.end(),
1155-
[](const CountedRegion *L, const CountedRegion *R) {
1156-
return L->endLoc() < R->endLoc();
1157-
});
1241+
[](const CountedRegion *L, const CountedRegion *R) {
1242+
return L->endLoc() < R->endLoc();
1243+
});
11581244

11591245
// Emit segments for all completed regions.
11601246
for (unsigned I = FirstCompletedRegion + 1, E = ActiveRegions.size(); I < E;
Binary file not shown.
88 Bytes
Binary file not shown.
80 Bytes
Binary file not shown.

0 commit comments

Comments
 (0)