diff --git a/clang/docs/SourceBasedCodeCoverage.rst b/clang/docs/SourceBasedCodeCoverage.rst index 3e8642479a56d..1655535c97e39 100644 --- a/clang/docs/SourceBasedCodeCoverage.rst +++ b/clang/docs/SourceBasedCodeCoverage.rst @@ -522,6 +522,17 @@ starts a new boolean expression that is separated from the other conditions by the operator ``func()``. When this is encountered, a warning will be generated and the boolean expression will not be instrumented. +Besides, MC/DC may report conditions with three states: ``uncoverable``, ``constant`` and ``unreachable``. +``uncoverable`` means the condition could be evaluated but it cannot affect outcome of the decision. +``constant`` means the condition is always evaluated to the same value. +While ``unreachable`` means the condition is never evaluated. +For instance, in ``a || true || b``, value of the decision is always ``true``. +``a`` can not make the decision be ``false`` as it varies. And the second condition, ``true`` can not be evaluated to ``false``. +While ``b`` is always short-circuited. Hence ``a`` is ``uncoverable``, ``true`` is ``constant`` and ``b`` is ``unreachable``. +By default statistics of MCDC counts uncoverable and unreachable conditions but excludes constants. Users can pass option +``--mcdc-exclude`` to control this behavior. +If a decision is proved to no branch theoretically, it shows ``Folded`` rather than coverage percent. + Switch statements ----------------- diff --git a/clang/lib/CodeGen/CoverageMappingGen.cpp b/clang/lib/CodeGen/CoverageMappingGen.cpp index f09157771d2b5..298be8141af89 100644 --- a/clang/lib/CodeGen/CoverageMappingGen.cpp +++ b/clang/lib/CodeGen/CoverageMappingGen.cpp @@ -1159,8 +1159,8 @@ struct CounterCoverageMappingBuilder BranchParams = mcdc::BranchParameters{ID, Conds}; // If a condition can fold to true or false, the corresponding branch - // will be removed. Create a region with both counters hard-coded to - // zero. This allows us to visualize them in a special way. + // will be removed. Create a region with the relative counter hard-coded + // to zero. This allows us to visualize them in a special way. // Alternatively, we can prevent any optimization done via // constant-folding by ensuring that ConstantFoldsToSimpleInteger() in // CodeGenFunction.c always returns false, but that is very heavy-handed. diff --git a/llvm/docs/CommandGuide/llvm-cov.rst b/llvm/docs/CommandGuide/llvm-cov.rst index 968f3c452f558..26b881fc6a4ae 100644 --- a/llvm/docs/CommandGuide/llvm-cov.rst +++ b/llvm/docs/CommandGuide/llvm-cov.rst @@ -227,6 +227,12 @@ OPTIONS Show modified condition/decision coverage (MC/DC) for each applicable boolean expression. +.. option:: -mcdc-exclude + + Set which special states of conditions should be excluded from coverage (MC/DC). Possible + values are: "none", "uncoverable", "constant", "unreachable", separated by comma. Default + to "constant". + .. option:: -show-line-counts Show the execution counts for each line. Defaults to true, unless another @@ -435,6 +441,12 @@ OPTIONS Show MC/DC statistics. Defaults to false. +.. option:: -mcdc-exclude-uncoverable + + MC/DC does not count uncoverable conditions. Default to false. + Uncoverable conditions are conditions that may be evaluated but can not affect + the outcome of decisions due to constants. + .. option:: -show-functions Show coverage summaries for each function. Defaults to false. diff --git a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h index 4fc0133443192..fc0228d26992b 100644 --- a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h +++ b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h @@ -396,6 +396,13 @@ struct MCDCRecord { /// are effectively ignored. enum CondState { MCDC_DontCare = -1, MCDC_False = 0, MCDC_True = 1 }; + enum CondResult { + MCDC_Normal = 0x1, + MCDC_Constant = 0x2, + MCDC_Uncoverable = 0x4, + MCDC_Unreachable = 0x8 + }; + /// Emulate SmallVector with a pair of BitVector. /// /// True False DontCare (Impossible) @@ -454,19 +461,21 @@ struct MCDCRecord { using TVPairMap = llvm::DenseMap; using CondIDMap = llvm::DenseMap; using LineColPairMap = llvm::DenseMap; + using ResultVector = llvm::SmallVector; private: CounterMappingRegion Region; TestVectors TV; std::optional IndependencePairs; BoolVector Folded; + ResultVector CondResults; CondIDMap PosToID; LineColPairMap CondLoc; public: MCDCRecord(const CounterMappingRegion &Region, TestVectors &&TV, - BoolVector &&Folded, CondIDMap &&PosToID, LineColPairMap &&CondLoc) - : Region(Region), TV(std::move(TV)), Folded(std::move(Folded)), + BoolVector &&Folded,ResultVector &&CondResults, CondIDMap &&PosToID, LineColPairMap &&CondLoc) + : Region(Region), TV(std::move(TV)), Folded(std::move(Folded)), CondResults(std::move(CondResults)), PosToID(std::move(PosToID)), CondLoc(std::move(CondLoc)) { findIndependencePairs(); } @@ -483,6 +492,12 @@ struct MCDCRecord { bool isCondFolded(unsigned Condition) const { return Folded[false][Condition] || Folded[true][Condition]; } + bool isCondConstant(unsigned Condition) const { + return getCondResult(Condition) == CondResult::MCDC_Constant; + } + CondResult getCondResult(unsigned Condition) const { + return CondResults[Condition]; + } /// Return the evaluation of a condition (indicated by Condition) in an /// executed test vector (indicated by TestVectorIndex), which will be True, @@ -523,20 +538,26 @@ struct MCDCRecord { return (*IndependencePairs)[PosToID[Condition]]; } - float getPercentCovered() const { - unsigned Folded = 0; + /// Return if the decision is coverable and percent of covered conditions. + /// Only coverable conditions are counted as denominator. + std::pair getPercentCovered(int32_t CountedStates) const { + unsigned Excluded = 0; unsigned Covered = 0; + auto ExcludedStates = ~CountedStates; for (unsigned C = 0; C < getNumConditions(); C++) { - if (isCondFolded(C)) - Folded++; + if (getCondResult(C) & ExcludedStates) + Excluded++; else if (isConditionIndependencePairCovered(C)) Covered++; } - unsigned Total = getNumConditions() - Folded; + unsigned Total = getNumConditions() - Excluded; if (Total == 0) - return 0.0; - return (static_cast(Covered) / static_cast(Total)) * 100.0; + return {false, 0.0}; + return { + true, + (static_cast(Covered) / static_cast(Total)) * 100.0, + }; } std::string getConditionHeaderString(unsigned Condition) { @@ -571,7 +592,7 @@ struct MCDCRecord { // Add individual condition values to the string. OS << " " << TestVectorIndex + 1 << " { "; for (unsigned Condition = 0; Condition < NumConditions; Condition++) { - if (isCondFolded(Condition)) + if (isCondConstant(Condition)) OS << "C"; else { switch (getTVCondition(TestVectorIndex, Condition)) { @@ -607,14 +628,25 @@ struct MCDCRecord { std::ostringstream OS; OS << " C" << Condition + 1 << "-Pair: "; - if (isCondFolded(Condition)) { + switch (getCondResult(Condition)) { + case CondResult::MCDC_Normal: + if (isConditionIndependencePairCovered(Condition)) { + TVRowPair rows = getConditionIndependencePair(Condition); + OS << "covered: (" << rows.first << ","; + OS << rows.second << ")\n"; + } else + OS << "not covered\n"; + break; + case CondResult::MCDC_Constant: OS << "constant folded\n"; - } else if (isConditionIndependencePairCovered(Condition)) { - TVRowPair rows = getConditionIndependencePair(Condition); - OS << "covered: (" << rows.first << ","; - OS << rows.second << ")\n"; - } else - OS << "not covered\n"; + break; + case CondResult::MCDC_Uncoverable: + OS << "uncoverable\n"; + break; + case CondResult::MCDC_Unreachable: + OS << "unreachable\n"; + break; + } return OS.str(); } diff --git a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp index c39585681911a..262ccdd799cae 100644 --- a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp +++ b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp @@ -423,9 +423,16 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder { /// Vector used to track whether a condition is constant folded. MCDCRecord::BoolVector Folded; + /// Vector used to track whether a condition is constant folded. + MCDCRecord::ResultVector CondResults; + /// Mapping of calculated MC/DC Independence Pairs for each condition. MCDCRecord::TVPairMap IndependencePairs; + /// All possible Test Vectors for the boolean expression derived from + /// binary decision diagran of the expression. + MCDCRecord::TestVectors TestVectors; + /// Storage for ExecVectors /// ExecVectors is the alias of its 0th element. std::array ExecVectorsByCond; @@ -449,6 +456,7 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder { Region(Region), DecisionParams(Region.getDecisionParams()), Branches(Branches), NumConditions(DecisionParams.NumConditions), Folded{{BitVector(NumConditions), BitVector(NumConditions)}}, + CondResults(NumConditions, MCDCRecord::CondResult::MCDC_Normal), IndependencePairs(NumConditions), ExecVectors(ExecVectorsByCond[false]), IsVersion11(IsVersion11) {} @@ -472,6 +480,7 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder { assert(TVIdx < SavedNodes[ID].Width); assert(TVIdxs.insert(NextTVIdx).second && "Duplicate TVIdx"); + TestVectors.push_back({TV, MCDCCond}); if (!Bitmap[IsVersion11 ? DecisionParams.BitmapIdx * CHAR_BIT + TV.getIndex() @@ -499,7 +508,6 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder { buildTestVector(TV, 0, 0); assert(TVIdxs.size() == unsigned(NumTestVectors) && "TVIdxs wasn't fulfilled"); - // Fill ExecVectors order by False items and True items. // ExecVectors is the alias of ExecVectorsByCond[false], so // Append ExecVectorsByCond[true] on it. @@ -508,29 +516,102 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder { std::make_move_iterator(ExecVectorsT.end())); } + void findCoverablePairs(const MCDCRecord::CondIDMap &PosToID) { + llvm::SmallVector FoldedCondPos; + for (unsigned I = 0; I < CondResults.size(); ++I) { + if (CondResults[I] == MCDCRecord::MCDC_Constant || + CondResults[I] == MCDCRecord::MCDC_Unreachable) { + FoldedCondPos.push_back(I); + } + } + if (FoldedCondPos.empty()) { + return; + } + std::array PracticalTestVectorsByCond; + for (const auto &TVWithCond : TestVectors) { + const bool Practical = + llvm::all_of(FoldedCondPos, [&](const unsigned &Pos) { + const auto &[TV, Cond] = TVWithCond; + const auto ID = PosToID.at(Pos); + if (TV[ID] == MCDCRecord::MCDC_DontCare) { + return true; + } + if (CondResults[Pos] == MCDCRecord::MCDC_Constant) { + const auto ConstantValue = Branches[Pos]->Count.isZero() + ? MCDCRecord::MCDC_False + : MCDCRecord::MCDC_True; + if (TV[ID] == ConstantValue) { + return true; + } + } + return false; + }); + + if (Practical) { + PracticalTestVectorsByCond[TVWithCond.second].push_back(TVWithCond); + } + } + + // If a condition: + // - is uncoverable, all test vectors in exact one element of + // `PracticalTestVectorsByCond` show it is `DontCare`; + // - is unreachable, all test vectors in both elements of + // `PracticalTestVectorsByCond` show it is `DontCare`; + // + // Otherwise, the condition is coverable as long as it has not been marked + // as constant or unreachable before. + for (unsigned Pos = 0; Pos < Branches.size(); ++Pos) { + if (CondResults[Pos] != MCDCRecord::MCDC_Normal) { + continue; + } + const auto ID = PosToID.at(Pos); + unsigned InaccessibleCondCount = + llvm::count_if(PracticalTestVectorsByCond, + [=](const MCDCRecord::TestVectors &TestVectors) { + for (const auto &[TV, Cond] : TestVectors) { + if (TV[ID] != MCDCRecord::MCDC_DontCare) { + return false; + } + } + return true; + }); + switch (InaccessibleCondCount) { + case 1: + CondResults[Pos] = MCDCRecord::CondResult::MCDC_Uncoverable; + break; + case 2: + CondResults[Pos] = MCDCRecord::CondResult::MCDC_Unreachable; + break; + default: + break; + } + } + } + public: /// Process the MC/DC Record in order to produce a result for a boolean /// expression. This process includes tracking the conditions that comprise /// the decision region, calculating the list of all possible test vectors, /// marking the executed test vectors, and then finding an Independence Pair /// out of the executed test vectors for each condition in the boolean - /// expression. A condition is tracked to ensure that its ID can be mapped to - /// its ordinal position in the boolean expression. The condition's source - /// location is also tracked, as well as whether it is constant folded (in - /// which case it is excuded from the metric). + /// expression. A condition is tracked to ensure that its ID can be mapped + /// to its ordinal position in the boolean expression. The condition's + /// source location is also tracked, as well as whether it is constant + /// folded (in which case it is excuded from the metric). MCDCRecord processMCDCRecord() { MCDCRecord::CondIDMap PosToID; MCDCRecord::LineColPairMap CondLoc; // Walk the Record's BranchRegions (representing Conditions) in order to: - // - Hash the condition based on its corresponding ID. This will be used to + // - Hash the condition based on its corresponding ID. This will be used + // to // calculate the test vectors. // - Keep a map of the condition's ordinal position (1, 2, 3, 4) to its // actual ID. This will be used to visualize the conditions in the // correct order. // - Keep track of the condition source location. This will be used to // visualize where the condition is. - // - Record whether the condition is constant folded so that we exclude it + // - Record whether the condition is folded so that we exclude it // from being measured. for (auto [I, B] : enumerate(Branches)) { const auto &BranchParams = B->getBranchParams(); @@ -538,14 +619,25 @@ class MCDCRecordProcessor : NextIDsBuilder, mcdc::TVIdxBuilder { CondLoc[I] = B->startLoc(); Folded[false][I] = B->FalseCount.isZero(); Folded[true][I] = B->Count.isZero(); + if (B->Count.isZero() && B->FalseCount.isZero()) { + CondResults[I] = MCDCRecord::CondResult::MCDC_Unreachable; + } else if (B->Count.isZero() || B->FalseCount.isZero()) { + CondResults[I] = MCDCRecord::CondResult::MCDC_Constant; + } + ++I; } // Using Profile Bitmap from runtime, mark the executed test vectors. findExecutedTestVectors(); + // Identify all conditions making no difference on outcome of the decision. + findCoverablePairs(PosToID); + // Record Test vectors, executed vectors, and independence pairs. - return MCDCRecord(Region, std::move(ExecVectors), std::move(Folded), - std::move(PosToID), std::move(CondLoc)); + return MCDCRecord(Region, std::move(ExecVectors), + std::move(IndependencePairs), std::move(Folded), + std::move(CondResults), std::move(PosToID), + std::move(CondLoc)); } }; @@ -637,6 +729,8 @@ static unsigned getMaxCounterID(const CounterMappingContext &Ctx, unsigned MaxCounterID = 0; for (const auto &Region : Record.MappingRegions) { MaxCounterID = std::max(MaxCounterID, Ctx.getMaxCounterID(Region.Count)); + MaxCounterID = + std::max(MaxCounterID, Ctx.getMaxCounterID(Region.FalseCount)); } return MaxCounterID; } @@ -933,8 +1027,8 @@ Error CoverageMapping::loadFunctionRecord( } // Don't create records for (filenames, function) pairs we've already seen. - auto FilenamesHash = hash_combine_range(Record.Filenames.begin(), - Record.Filenames.end()); + auto FilenamesHash = + hash_combine_range(Record.Filenames.begin(), Record.Filenames.end()); if (!RecordProvenance[FilenamesHash].insert(hash_value(OrigFuncName)).second) return Error::success(); @@ -987,12 +1081,11 @@ Expected> CoverageMapping::load( // If E is a no_data_found error, returns success. Otherwise returns E. static Error handleMaybeNoDataFoundError(Error E) { - return handleErrors( - std::move(E), [](const CoverageMapError &CME) { - if (CME.get() == coveragemap_error::no_data_found) - return static_cast(Error::success()); - return make_error(CME.get(), CME.getMessage()); - }); + return handleErrors(std::move(E), [](const CoverageMapError &CME) { + if (CME.get() == coveragemap_error::no_data_found) + return static_cast(Error::success()); + return make_error(CME.get(), CME.getMessage()); + }); } Error CoverageMapping::loadFromFile( @@ -1084,7 +1177,7 @@ Expected> CoverageMapping::load( std::string Path = std::move(*PathOpt); StringRef Arch = Arches.size() == 1 ? Arches.front() : StringRef(); if (Error E = loadFromFile(Path, Arch, CompilationDir, *ProfileReader, - *Coverage, DataFound)) + *Coverage, DataFound)) return std::move(E); } else if (CheckBinaryIDs) { return createFileError( @@ -1178,9 +1271,9 @@ class SegmentBuilder { // emit closing segments in sorted order. auto CompletedRegionsIt = ActiveRegions.begin() + FirstCompletedRegion; std::stable_sort(CompletedRegionsIt, ActiveRegions.end(), - [](const CountedRegion *L, const CountedRegion *R) { - return L->endLoc() < R->endLoc(); - }); + [](const CountedRegion *L, const CountedRegion *R) { + return L->endLoc() < R->endLoc(); + }); // Emit segments for all completed regions. for (unsigned I = FirstCompletedRegion + 1, E = ActiveRegions.size(); I < E; diff --git a/llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.o b/llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.o index 7a16162f29c00..29aabadc9c6e5 100644 Binary files a/llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.o and b/llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.o differ diff --git a/llvm/test/tools/llvm-cov/Inputs/mcdc-const.o b/llvm/test/tools/llvm-cov/Inputs/mcdc-const.o index bc38b71b5de07..a204a1095745f 100644 Binary files a/llvm/test/tools/llvm-cov/Inputs/mcdc-const.o and b/llvm/test/tools/llvm-cov/Inputs/mcdc-const.o differ diff --git a/llvm/test/tools/llvm-cov/Inputs/mcdc-exclude.cpp b/llvm/test/tools/llvm-cov/Inputs/mcdc-exclude.cpp new file mode 100644 index 0000000000000..ebf4d5e1200d4 --- /dev/null +++ b/llvm/test/tools/llvm-cov/Inputs/mcdc-exclude.cpp @@ -0,0 +1,25 @@ +#include + + + + + + + + +void test(bool a,bool b, bool c, bool d) { + + if (a && (b || true || c) && ( true || d) && true) + printf("test 1 decision true\n"); + +} + +int main() +{ + test(true,false,true,false); + test(true,false,true,true); + test(true,true,false,false); + test(false,true,true,false); + test(true,false,false,false); + return 0; +} diff --git a/llvm/test/tools/llvm-cov/Inputs/mcdc-exclude.o b/llvm/test/tools/llvm-cov/Inputs/mcdc-exclude.o new file mode 100644 index 0000000000000..ef0cab25965fe Binary files /dev/null and b/llvm/test/tools/llvm-cov/Inputs/mcdc-exclude.o differ diff --git a/llvm/test/tools/llvm-cov/Inputs/mcdc-exclude.proftext b/llvm/test/tools/llvm-cov/Inputs/mcdc-exclude.proftext new file mode 100644 index 0000000000000..e1f88453360cf --- /dev/null +++ b/llvm/test/tools/llvm-cov/Inputs/mcdc-exclude.proftext @@ -0,0 +1,34 @@ +_Z4testbbbb +# Func Hash: +2877299778974865 +# Num Counters: +12 +# Counter Values: +5 +4 +4 +4 +4 +4 +0 +0 +3 +0 +0 +0 +# Num Bitmap Bytes: +$3 +# Bitmap Byte Values: +0xc0 +0x80 +0x0 + + +main +# Func Hash: +24 +# Num Counters: +1 +# Counter Values: +1 + diff --git a/llvm/test/tools/llvm-cov/Inputs/mcdc-macro.o b/llvm/test/tools/llvm-cov/Inputs/mcdc-macro.o index 5b3934816bd22..bda7b179db43f 100644 Binary files a/llvm/test/tools/llvm-cov/Inputs/mcdc-macro.o and b/llvm/test/tools/llvm-cov/Inputs/mcdc-macro.o differ diff --git a/llvm/test/tools/llvm-cov/mcdc-const.test b/llvm/test/tools/llvm-cov/mcdc-const.test index 5424625cf6a6b..9b6daebb36c6a 100644 --- a/llvm/test/tools/llvm-cov/mcdc-const.test +++ b/llvm/test/tools/llvm-cov/mcdc-const.test @@ -5,10 +5,10 @@ // CHECKGENERALCASE: ------------------ // CHECKGENERALCASE-NEXT: | Branch (12:8): [True: 4, False: 1] -// CHECKGENERALCASE-NEXT: | Branch (12:13): [Folded - Ignored] -// CHECKGENERALCASE-NEXT: | Branch (12:20): [Folded - Ignored] +// CHECKGENERALCASE-NEXT: | Branch (12:13): [True: 4, Folded] +// CHECKGENERALCASE-NEXT: | Branch (12:20): [Folded, False: 1] // CHECKGENERALCASE-NEXT: | Branch (12:25): [True: 0, False: 0] -// CHECKGENERALCASE-NEXT: | Branch (12:31): [Folded - Ignored] +// CHECKGENERALCASE-NEXT: | Branch (12:31): [Folded, False: 1] // CHECKGENERALCASE-NEXT: ------------------ // CHECKGENERALCASE-NEXT: |---> MC/DC Decision Region (12:7) to (12:32) // CHECKGENERALCASE-NEXT: | @@ -28,7 +28,7 @@ // CHECKGENERALCASE-NEXT: | C1-Pair: covered: (1,2) // CHECKGENERALCASE-NEXT: | C2-Pair: constant folded // CHECKGENERALCASE-NEXT: | C3-Pair: constant folded -// CHECKGENERALCASE-NEXT: | C4-Pair: not covered +// CHECKGENERALCASE-NEXT: | C4-Pair: unreachable // CHECKGENERALCASE-NEXT: | C5-Pair: constant folded // CHECKGENERALCASE-NEXT: | MC/DC Coverage for Decision: 50.00% // CHECKGENERALCASE-NEXT: | @@ -40,11 +40,11 @@ // CHECKFULLCASE: | 1 { C, - = F } // CHECKFULLCASE: | C1-Pair: constant folded -// CHECKFULLCASE-NEXT: | C2-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: unreachable // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { F, C = F } // CHECKFULLCASE-NEXT: | 2 { T, C = F } -// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C1-Pair: uncoverable // CHECKFULLCASE-NEXT: | C2-Pair: constant folded // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { C, F = F } @@ -59,11 +59,11 @@ // CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% // CHECKFULLCASE: | 1 { C, - = T } // CHECKFULLCASE: | C1-Pair: constant folded -// CHECKFULLCASE-NEXT: | C2-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: unreachable // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { F, C = T } // CHECKFULLCASE-NEXT: | 2 { T, C = T } -// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C1-Pair: uncoverable // CHECKFULLCASE-NEXT: | C2-Pair: constant folded // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { C, F = F } @@ -78,14 +78,14 @@ // CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% // CHECKFULLCASE: | 1 { C, -, - = F } // CHECKFULLCASE: | C1-Pair: constant folded -// CHECKFULLCASE-NEXT: | C2-Pair: not covered -// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: unreachable +// CHECKFULLCASE-NEXT: | C3-Pair: unreachable // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { F, C, - = F } // CHECKFULLCASE-NEXT: | 2 { T, C, - = F } -// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C1-Pair: uncoverable // CHECKFULLCASE-NEXT: | C2-Pair: constant folded -// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE-NEXT: | C3-Pair: unreachable // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { C, F, - = F } // CHECKFULLCASE-NEXT: | 2 { C, T, F = F } @@ -103,14 +103,14 @@ // CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% // CHECKFULLCASE: | 1 { C, -, - = T } // CHECKFULLCASE: | C1-Pair: constant folded -// CHECKFULLCASE-NEXT: | C2-Pair: not covered -// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: unreachable +// CHECKFULLCASE-NEXT: | C3-Pair: unreachable // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { F, C, - = T } // CHECKFULLCASE-NEXT: | 2 { T, C, - = T } -// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C1-Pair: uncoverable // CHECKFULLCASE-NEXT: | C2-Pair: constant folded -// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE-NEXT: | C3-Pair: unreachable // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { C, F, T = T } // CHECKFULLCASE-NEXT: | 2 { C, T, - = T } @@ -127,15 +127,15 @@ // CHECKFULLCASE: | 1 { F, -, C = F } // CHECKFULLCASE-NEXT: | 2 { T, F, C = F } // CHECKFULLCASE-NEXT: | 3 { T, T, C = F } -// CHECKFULLCASE: | C1-Pair: not covered -// CHECKFULLCASE-NEXT: | C2-Pair: not covered +// CHECKFULLCASE: | C1-Pair: uncoverable +// CHECKFULLCASE-NEXT: | C2-Pair: uncoverable // CHECKFULLCASE-NEXT: | C3-Pair: constant folded // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { F, C, - = F } // CHECKFULLCASE-NEXT: | 2 { T, C, - = F } -// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C1-Pair: uncoverable // CHECKFULLCASE-NEXT: | C2-Pair: constant folded -// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE-NEXT: | C3-Pair: unreachable // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { F, -, C = F } // CHECKFULLCASE-NEXT: | 2 { T, F, C = F } @@ -153,15 +153,15 @@ // CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% // CHECKFULLCASE: | 1 { F, T, C = T } // CHECKFULLCASE-NEXT: | 2 { T, -, C = T } -// CHECKFULLCASE: | C1-Pair: not covered -// CHECKFULLCASE-NEXT: | C2-Pair: not covered +// CHECKFULLCASE: | C1-Pair: uncoverable +// CHECKFULLCASE-NEXT: | C2-Pair: uncoverable // CHECKFULLCASE-NEXT: | C3-Pair: constant folded // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { F, C, - = T } // CHECKFULLCASE-NEXT: | 2 { T, C, - = T } -// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE: | C1-Pair: uncoverable // CHECKFULLCASE-NEXT: | C2-Pair: constant folded -// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE-NEXT: | C3-Pair: unreachable // CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% // CHECKFULLCASE: | 1 { F, T, C = T } // CHECKFULLCASE-NEXT: | 2 { T, -, C = T } diff --git a/llvm/test/tools/llvm-cov/mcdc-exclude.test b/llvm/test/tools/llvm-cov/mcdc-exclude.test new file mode 100644 index 0000000000000..11161cbda1864 --- /dev/null +++ b/llvm/test/tools/llvm-cov/mcdc-exclude.test @@ -0,0 +1,56 @@ +// Test visualization of MC/DC constructs for constant-folded condition masking with different counted states. + +// RUN: llvm-profdata merge %S/Inputs/mcdc-exclude.proftext -o %t.profdata +// RUN: llvm-cov show --show-mcdc %S/Inputs/mcdc-exclude.o -instr-profile %t.profdata -path-equivalence=.,%S/Inputs | FileCheck %s -check-prefix=DEFAULTCASE +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-exclude.o -instr-profile %t.profdata -show-functions -path-equivalence=.,%S/Inputs %S/Inputs/mcdc-exclude.cpp | FileCheck %s -check-prefix=REPORTDEFAULT + +// DEFAULTCASE: | MC/DC Coverage for Decision: 25.00% + +// REPORTDEFAULT: TOTAL {{.*}} 4 3 25.00% + +// RUN: llvm-cov show --show-mcdc --mcdc-exclude=uncoverable %S/Inputs/mcdc-exclude.o -instr-profile %t.profdata -path-equivalence=.,%S/Inputs | FileCheck %s -check-prefix=EXCLUDEUNCOVERABECASE +// RUN: llvm-cov report --show-mcdc-summary --mcdc-exclude=uncoverable %S/Inputs/mcdc-exclude.o -instr-profile %t.profdata -show-functions -path-equivalence=.,%S/Inputs %S/Inputs/mcdc-exclude.cpp | FileCheck %s -check-prefix=REPORTEXCLUDEUNCOVERABLE + +// EXCLUDEUNCOVERABECASE: | MC/DC Coverage for Decision: 16.67% + +// REPORTEXCLUDEUNCOVERABLE: TOTAL {{.*}} 6 5 16.67% + +// RUN: llvm-cov show --show-mcdc --mcdc-exclude=constant %S/Inputs/mcdc-exclude.o -instr-profile %t.profdata -path-equivalence=.,%S/Inputs | FileCheck %s -check-prefix=EXCLUDECONSTANTCASE +// RUN: llvm-cov report --show-mcdc-summary --mcdc-exclude=constant %S/Inputs/mcdc-exclude.o -instr-profile %t.profdata -show-functions -path-equivalence=.,%S/Inputs %S/Inputs/mcdc-exclude.cpp | FileCheck %s -check-prefix=REPORTEXCLUDECONSTANT + +// EXCLUDECONSTANTCASE: | MC/DC Coverage for Decision: 25.00% + +// REPORTEXCLUDECONSTANT: TOTAL {{.*}} 4 3 25.00% + +// RUN: llvm-cov show --show-mcdc --mcdc-exclude=unreachable %S/Inputs/mcdc-exclude.o -instr-profile %t.profdata -path-equivalence=.,%S/Inputs | FileCheck %s -check-prefix=EXCLUDEUNREACHABLECASE +// RUN: llvm-cov report --show-mcdc-summary --mcdc-exclude=unreachable %S/Inputs/mcdc-exclude.o -instr-profile %t.profdata -show-functions -path-equivalence=.,%S/Inputs %S/Inputs/mcdc-exclude.cpp | FileCheck %s -check-prefix=REPORTEXCLUDEUNREACHABLE + +// EXCLUDEUNREACHABLECASE: | MC/DC Coverage for Decision: 20.00% + +// REPORTEXCLUDEUNREACHABLE: TOTAL {{.*}} 5 4 20.00% + +// RUN: llvm-cov show --show-mcdc --mcdc-exclude=none %S/Inputs/mcdc-exclude.o -instr-profile %t.profdata -path-equivalence=.,%S/Inputs | FileCheck %s -check-prefix=INCLUDEALLCASE +// RUN: llvm-cov report --show-mcdc-summary --mcdc-exclude=none %S/Inputs/mcdc-exclude.o -instr-profile %t.profdata -show-functions -path-equivalence=.,%S/Inputs %S/Inputs/mcdc-exclude.cpp | FileCheck %s -check-prefix=REPORTALL + +// INCLUDEALLCASE: | MC/DC Coverage for Decision: 14.29% + +// REPORTALL: TOTAL {{.*}} 7 6 14.29% +Instructions for regenerating the test: + +cd %S/Inputs # Or copy files into the working directory + +clang++ -c -Os \ + -fcoverage-compilation-dir=. -mllvm -enable-name-compression=false \ + -fcoverage-mcdc -fprofile-instr-generate -fcoverage-mapping \ + mcdc-exclude.cpp + +# Instructions for regenerating proftext + +for x in mcdc-exclude; do ( + clang++ -fprofile-instr-generate $x.o -o $x + find -name '*.profraw' | xargs rm -f + export LLVM_PROFILE_FILE=$x-%p.profraw + ./$x 0 1 + llvm-profdata merge --sparse -o $x.profdata $(find -name '*.profraw') + llvm-profdata merge --text -o $x.proftext $x.profdata +); done diff --git a/llvm/test/tools/llvm-cov/mcdc-macro.test b/llvm/test/tools/llvm-cov/mcdc-macro.test index 14dd5ebd68eb1..9d0da4b0ad02c 100644 --- a/llvm/test/tools/llvm-cov/mcdc-macro.test +++ b/llvm/test/tools/llvm-cov/mcdc-macro.test @@ -3,7 +3,7 @@ // RUN: llvm-profdata merge %S/Inputs/mcdc-macro.proftext -o %t.profdata // RUN: llvm-cov show --show-expansions --show-branches=count --show-mcdc %S/Inputs/mcdc-macro.o -instr-profile %t.profdata --compilation-dir=%S/Inputs | FileCheck %s -// CHECK: | | | Branch (2:11): [Folded - Ignored] +// CHECK: | | | Branch (2:11): [True: 1, Folded] // CHECK: | | | Branch (3:11): [True: 1, False: 0] // CHECK: | | | Branch (3:23): [True: 1, False: 0] // CHECK: | Branch (9:7): [True: 1, False: 0] @@ -32,7 +32,7 @@ // CHECK-NEXT: | // CHECK-NEXT: ------------------ -// CHECK: | | | Branch (2:11): [Folded - Ignored] +// CHECK: | | | Branch (2:11): [True: 1, Folded] // CHECK: | Branch (11:7): [True: 1, False: 0] // CHECK-NEXT: ------------------ // CHECK-NEXT: |---> MC/DC Decision Region (11:7) to (11:13) @@ -53,7 +53,7 @@ // CHECK-NEXT: ------------------ // CHECK: | | | Branch (1:11): [True: 1, False: 0] -// CHECK: | | | Branch (2:11): [Folded - Ignored] +// CHECK: | | | Branch (2:11): [True: 0, Folded] // CHECK: | | | | | Branch (3:11): [True: 0, False: 0] // CHECK: | | | | | Branch (3:23): [True: 0, False: 0] // CHECK: | Branch (13:7): [True: 1, False: 0] diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp index 921f283deedc7..3e9a2ca05493a 100644 --- a/llvm/tools/llvm-cov/CodeCoverage.cpp +++ b/llvm/tools/llvm-cov/CodeCoverage.cpp @@ -773,6 +773,12 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) { cl::desc("Show MCDC statistics in summary table"), cl::init(false)); + cl::list MCDCExcludeStates( + "mcdc-exclude", cl::Optional, + cl::desc( + "Set which abnormal kinds of conditions are excluded from MC/DC"), + cl::CommaSeparated, cl::list_init({"constant"})); + cl::opt InstantiationSummary( "show-instantiation-summary", cl::Optional, cl::desc("Show instantiation statistics in summary table")); @@ -944,6 +950,24 @@ int CodeCoverageTool::run(Command Cmd, int argc, const char **argv) { ::exit(0); } + ViewOpts.MCDCCountedStates = + MCDCRecord::MCDC_Normal | MCDCRecord::MCDC_Uncoverable | + MCDCRecord::MCDC_Constant | MCDCRecord::MCDC_Unreachable; + for (const auto &State : MCDCExcludeStates) { + if (State == "uncoverable") { + ViewOpts.MCDCCountedStates &= ~MCDCRecord::MCDC_Uncoverable; + } else if (State == "constant") { + ViewOpts.MCDCCountedStates &= ~MCDCRecord::MCDC_Constant; + } else if (State == "unreachable") { + ViewOpts.MCDCCountedStates &= ~MCDCRecord::MCDC_Unreachable; + } else if (State != "none") { + error("invalid argument '" + State + + "', must be in one of 'uncoverable, constant, unreachable'", + "--mcdc-exclude"); + return 1; + } + } + ViewOpts.ShowMCDCSummary = MCDCSummary; ViewOpts.ShowBranchSummary = BranchSummary; ViewOpts.ShowRegionSummary = RegionSummary; diff --git a/llvm/tools/llvm-cov/CoverageReport.cpp b/llvm/tools/llvm-cov/CoverageReport.cpp index 00aea4039bfde..0d45a59e97fbc 100644 --- a/llvm/tools/llvm-cov/CoverageReport.cpp +++ b/llvm/tools/llvm-cov/CoverageReport.cpp @@ -379,7 +379,9 @@ void CoverageReport::render(const FunctionCoverageSummary &Function, (unsigned)(Function.MCDCCoverage.getNumPairs() - Function.MCDCCoverage.getCoveredPairs())); Options.colored_ostream( - OS, determineCoveragePercentageColor(Function.MCDCCoverage)) + OS, Function.MCDCCoverage.getNumPairs() == 0 + ? raw_ostream::GREEN + : determineCoveragePercentageColor(Function.MCDCCoverage)) << format("%*.2f", FunctionReportColumns[12] - 1, Function.MCDCCoverage.getPercentCovered()) << '%'; @@ -426,7 +428,8 @@ void CoverageReport::renderFunctionReports(ArrayRef Files, OS << "\n"; FunctionCoverageSummary Totals("TOTAL"); for (const auto &F : Functions) { - auto Function = FunctionCoverageSummary::get(Coverage, F); + auto Function = + FunctionCoverageSummary::get(Coverage, F, Options.MCDCCountedStates); ++Totals.ExecutionCount; Totals.RegionCoverage += Function.RegionCoverage; Totals.LineCoverage += Function.LineCoverage; @@ -451,7 +454,8 @@ void CoverageReport::prepareSingleFileReport(const StringRef Filename, for (const coverage::FunctionRecord *F : Group.getInstantiations()) { if (!Filters->matches(*Coverage, *F)) continue; - auto InstantiationSummary = FunctionCoverageSummary::get(*Coverage, *F); + auto InstantiationSummary = FunctionCoverageSummary::get( + *Coverage, *F, Options.MCDCCountedStates); FileReport->addInstantiation(InstantiationSummary); InstantiationSummaries.push_back(InstantiationSummary); } diff --git a/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp b/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp index be9aef8416e8d..ea84293f6c349 100644 --- a/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp +++ b/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp @@ -12,6 +12,7 @@ //===----------------------------------------------------------------------===// #include "CoverageSummaryInfo.h" +#include "llvm/ProfileData/Coverage/CoverageMapping.h" using namespace llvm; using namespace coverage; @@ -48,12 +49,13 @@ sumBranchExpansions(const CoverageMapping &CM, return BranchCoverage; } -auto sumMCDCPairs(const ArrayRef &Records) { +auto +sumMCDCPairs(const ArrayRef &Records, const int32_t CountFlags) { size_t NumPairs = 0, CoveredPairs = 0; for (const auto &Record : Records) { const auto NumConditions = Record.getNumConditions(); for (unsigned C = 0; C < NumConditions; C++) { - if (!Record.isCondFolded(C)) { + if(Record.getCondResult(C) & CountFlags){ ++NumPairs; if (Record.isConditionIndependencePairCovered(C)) ++CoveredPairs; @@ -86,25 +88,27 @@ sumRegions(ArrayRef CodeRegions, const CoverageData &CD) { } return {RegionCoverageInfo(CoveredRegions, NumCodeRegions), + LineCoverageInfo(CoveredLines, NumLines)}; } CoverageDataSummary::CoverageDataSummary(const CoverageData &CD, - ArrayRef CodeRegions) { + ArrayRef CodeRegions,const int32_t MCDCCountedFlags) { std::tie(RegionCoverage, LineCoverage) = sumRegions(CodeRegions, CD); BranchCoverage = sumBranches(CD.getBranches()); - MCDCCoverage = sumMCDCPairs(CD.getMCDCRecords()); + MCDCCoverage = sumMCDCPairs(CD.getMCDCRecords(), + MCDCCountedFlags); } FunctionCoverageSummary FunctionCoverageSummary::get(const CoverageMapping &CM, - const coverage::FunctionRecord &Function) { + const coverage::FunctionRecord &Function,const int32_t MCDCCountedFlags) { CoverageData CD = CM.getCoverageForFunction(Function); auto Summary = FunctionCoverageSummary(Function.Name, Function.ExecutionCount); - Summary += CoverageDataSummary(CD, Function.CountedRegions); + Summary += CoverageDataSummary(CD, Function.CountedRegions, MCDCCountedFlags); // Compute the branch coverage, including branches from expansions. Summary.BranchCoverage += sumBranchExpansions(CM, CD.getExpansions()); diff --git a/llvm/tools/llvm-cov/CoverageSummaryInfo.h b/llvm/tools/llvm-cov/CoverageSummaryInfo.h index d9210676c41bf..9885feb1f0b4a 100644 --- a/llvm/tools/llvm-cov/CoverageSummaryInfo.h +++ b/llvm/tools/llvm-cov/CoverageSummaryInfo.h @@ -16,6 +16,7 @@ #include "llvm/ProfileData/Coverage/CoverageMapping.h" #include "llvm/Support/raw_ostream.h" +#include namespace llvm { @@ -231,7 +232,8 @@ struct CoverageDataSummary { CoverageDataSummary() = default; CoverageDataSummary(const coverage::CoverageData &CD, - ArrayRef CodeRegions); + ArrayRef CodeRegions, + const int32_t MCDCCountedFlags); auto &operator+=(const CoverageDataSummary &RHS) { RegionCoverage += RHS.RegionCoverage; @@ -253,7 +255,8 @@ struct FunctionCoverageSummary : CoverageDataSummary { /// Compute the code coverage summary for the given function coverage /// mapping record. static FunctionCoverageSummary get(const coverage::CoverageMapping &CM, - const coverage::FunctionRecord &Function); + const coverage::FunctionRecord &Function, + int32_t MCDCCountedFlags = 0); /// Compute the code coverage summary for an instantiation group \p Group, /// given a list of summaries for each instantiation in \p Summaries. diff --git a/llvm/tools/llvm-cov/CoverageViewOptions.h b/llvm/tools/llvm-cov/CoverageViewOptions.h index 81e69c3814e30..d238f7949fc25 100644 --- a/llvm/tools/llvm-cov/CoverageViewOptions.h +++ b/llvm/tools/llvm-cov/CoverageViewOptions.h @@ -46,6 +46,7 @@ struct CoverageViewOptions { bool SkipFunctions; bool SkipBranches; bool BinaryCounters; + int32_t MCDCCountedStates; OutputFormat Format; BranchOutputType ShowBranches; std::string ShowOutputDirectory; diff --git a/llvm/tools/llvm-cov/SourceCoverageView.h b/llvm/tools/llvm-cov/SourceCoverageView.h index cff32b756ee32..0c46d96855983 100644 --- a/llvm/tools/llvm-cov/SourceCoverageView.h +++ b/llvm/tools/llvm-cov/SourceCoverageView.h @@ -161,9 +161,6 @@ class SourceCoverageView { /// A memory buffer backing the source on display. const MemoryBuffer &File; - /// Various options to guide the coverage renderer. - const CoverageViewOptions &Options; - /// Complete coverage information about the source on display. CoverageData CoverageInfo; @@ -195,6 +192,9 @@ class SourceCoverageView { using CoverageSegmentArray = ArrayRef; + /// Various options to guide the coverage renderer. + const CoverageViewOptions &Options; + /// @name Rendering Interface /// @{ diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp index c94d3853fc014..2200a7b7202ca 100644 --- a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp +++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp @@ -1187,7 +1187,13 @@ void SourceCoverageViewHTML::renderMCDCView(raw_ostream &OS, MCDCView &MRV, for (unsigned i = 0; i < Record.getNumConditions(); i++) OS << Record.getConditionCoverageString(i); OS << " MC/DC Coverage for Expression: "; - OS << format("%0.2f", Record.getPercentCovered()) << "%\n"; + const auto [Coverable, Percent] = + Record.getPercentCovered(Options.MCDCCountedStates); + if (Coverable) { + OS << format("%0.2f", Percent) << "%\n"; + } else { + OS << "Folded\n"; + } OS << EndPre; OS << EndExpansionDiv; } diff --git a/llvm/tools/llvm-cov/SourceCoverageViewText.cpp b/llvm/tools/llvm-cov/SourceCoverageViewText.cpp index 765f8bbbd8d1b..9506f7919bffc 100644 --- a/llvm/tools/llvm-cov/SourceCoverageViewText.cpp +++ b/llvm/tools/llvm-cov/SourceCoverageViewText.cpp @@ -377,10 +377,17 @@ void SourceCoverageViewText::renderMCDCView(raw_ostream &OS, MCDCView &MRV, } renderLinePrefix(OS, ViewDepth); OS << " MC/DC Coverage for Decision: "; - colored_ostream(OS, raw_ostream::RED, - getOptions().Colors && Record.getPercentCovered() < 100.0, - /*Bold=*/false, /*BG=*/true) - << format("%0.2f", Record.getPercentCovered()) << "%"; + const auto [Coverable, Percent] = + Record.getPercentCovered(Options.MCDCCountedStates); + if (Coverable) { + colored_ostream(OS, raw_ostream::RED, + getOptions().Colors && Percent < 100.0, + /*Bold=*/false, /*BG=*/true) + << format("%0.2f", Percent) << "%"; + } else { + OS << "Folded"; + } + OS << "\n"; renderLinePrefix(OS, ViewDepth); OS << "\n";