diff --git a/clang/lib/CodeGen/CoverageMappingGen.cpp b/clang/lib/CodeGen/CoverageMappingGen.cpp --- a/clang/lib/CodeGen/CoverageMappingGen.cpp +++ b/clang/lib/CodeGen/CoverageMappingGen.cpp @@ -1623,8 +1623,12 @@ OS << "Gap,"; break; case CounterMappingRegion::BranchRegion: + case CounterMappingRegion::MCDCBranchRegion: OS << "Branch,"; break; + case CounterMappingRegion::MCDCDecisionRegion: + OS << "Decision,"; + break; } OS << "File " << R.FileID << ", " << R.LineStart << ":" << R.ColumnStart diff --git a/llvm/docs/CommandGuide/llvm-cov.rst b/llvm/docs/CommandGuide/llvm-cov.rst --- a/llvm/docs/CommandGuide/llvm-cov.rst +++ b/llvm/docs/CommandGuide/llvm-cov.rst @@ -222,6 +222,11 @@ Show coverage for branch conditions in terms of either count or percentage. The supported views are: "count", "percent". +.. option:: -show-mcdc + + Show modified condition/decision coverage (MC/DC) for each applicable boolean + expression. + .. option:: -show-line-counts Show the execution counts for each line. Defaults to true, unless another @@ -426,6 +431,10 @@ Show statistics for all branch conditions. Defaults to true. +.. option:: -show-mcdc-summary + + Show MC/DC statistics. Defaults to false. + .. 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 --- a/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h +++ b/llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h @@ -15,6 +15,7 @@ #define LLVM_PROFILEDATA_COVERAGE_COVERAGEMAPPING_H #include "llvm/ADT/ArrayRef.h" +#include "llvm/ADT/BitVector.h" #include "llvm/ADT/DenseMap.h" #include "llvm/ADT/DenseSet.h" #include "llvm/ADT/Hashing.h" @@ -33,6 +34,7 @@ #include #include #include +#include #include #include #include @@ -237,7 +239,27 @@ /// A BranchRegion represents leaf-level boolean expressions and is /// associated with two counters, each representing the number of times the /// expression evaluates to true or false. - BranchRegion + BranchRegion, + + /// A DecisionRegion represents a top-level boolean expression and is + /// associated with a variable length bitmap index and condition number. + MCDCDecisionRegion, + + /// A Branch Region can be extended to include IDs to facilitate MC/DC. + MCDCBranchRegion + }; + + using MCDCConditionID = unsigned int; + struct MCDCParameters { + /// Byte Index of Bitmap Coverage Object for a Decision Region. + unsigned BitmapIdx = 0; + + /// Number of Conditions used for a Decision Region. + unsigned NumConditions = 0; + + /// IDs used to represent a branch region and other branch regions + /// evaluated based on True and False branches. + MCDCConditionID ID = 0, TrueID = 0, FalseID = 0; }; /// Primary Counter that is also used for Branch Regions (TrueCount). @@ -246,8 +268,13 @@ /// Secondary Counter used for Branch Regions (FalseCount). Counter FalseCount; - unsigned FileID, ExpandedFileID; + /// Parameters used for Modified Condition/Decision Coverage + MCDCParameters MCDCParams; + + unsigned FileID = 0; + unsigned ExpandedFileID = 0; unsigned LineStart, ColumnStart, LineEnd, ColumnEnd; + RegionKind Kind; CounterMappingRegion(Counter Count, unsigned FileID, unsigned ExpandedFileID, @@ -257,15 +284,24 @@ LineStart(LineStart), ColumnStart(ColumnStart), LineEnd(LineEnd), ColumnEnd(ColumnEnd), Kind(Kind) {} - CounterMappingRegion(Counter Count, Counter FalseCount, unsigned FileID, + CounterMappingRegion(Counter Count, Counter FalseCount, + MCDCParameters MCDCParams, unsigned FileID, unsigned ExpandedFileID, unsigned LineStart, unsigned ColumnStart, unsigned LineEnd, unsigned ColumnEnd, RegionKind Kind) - : Count(Count), FalseCount(FalseCount), FileID(FileID), - ExpandedFileID(ExpandedFileID), LineStart(LineStart), + : Count(Count), FalseCount(FalseCount), MCDCParams(MCDCParams), + FileID(FileID), ExpandedFileID(ExpandedFileID), LineStart(LineStart), ColumnStart(ColumnStart), LineEnd(LineEnd), ColumnEnd(ColumnEnd), Kind(Kind) {} + CounterMappingRegion(MCDCParameters MCDCParams, unsigned FileID, + unsigned ExpandedFileID, unsigned LineStart, + unsigned ColumnStart, unsigned LineEnd, + unsigned ColumnEnd, RegionKind Kind) + : MCDCParams(MCDCParams), ExpandedFileID(ExpandedFileID), + LineStart(LineStart), ColumnStart(ColumnStart), LineEnd(LineEnd), + ColumnEnd(ColumnEnd), Kind(Kind) {} + static CounterMappingRegion makeRegion(Counter Count, unsigned FileID, unsigned LineStart, unsigned ColumnStart, unsigned LineEnd, unsigned ColumnEnd) { @@ -299,8 +335,27 @@ makeBranchRegion(Counter Count, Counter FalseCount, unsigned FileID, unsigned LineStart, unsigned ColumnStart, unsigned LineEnd, unsigned ColumnEnd) { - return CounterMappingRegion(Count, FalseCount, FileID, 0, LineStart, - ColumnStart, LineEnd, ColumnEnd, BranchRegion); + return CounterMappingRegion(Count, FalseCount, MCDCParameters(), FileID, 0, + LineStart, ColumnStart, LineEnd, ColumnEnd, + BranchRegion); + } + + static CounterMappingRegion + makeBranchRegion(Counter Count, Counter FalseCount, MCDCParameters MCDCParams, + unsigned FileID, unsigned LineStart, unsigned ColumnStart, + unsigned LineEnd, unsigned ColumnEnd) { + return CounterMappingRegion(Count, FalseCount, MCDCParams, FileID, 0, + LineStart, ColumnStart, LineEnd, ColumnEnd, + MCDCParams.ID == 0 ? BranchRegion + : MCDCBranchRegion); + } + + static CounterMappingRegion + makeDecisionRegion(MCDCParameters MCDCParams, unsigned FileID, + unsigned LineStart, unsigned ColumnStart, unsigned LineEnd, + unsigned ColumnEnd) { + return CounterMappingRegion(MCDCParams, FileID, 0, LineStart, ColumnStart, + LineEnd, ColumnEnd, MCDCDecisionRegion); } inline LineColPair startLoc() const { @@ -326,11 +381,189 @@ FalseExecutionCount(FalseExecutionCount), Folded(false) {} }; +/// MCDC Record grouping all information together. +struct MCDCRecord { + /// CondState represents the evaluation of a condition in an executed test + /// vector, which can be True or False. A DontCare is used to mask an + /// unevaluatable condition resulting from short-circuit behavior of logical + /// operators in languages like C/C++. When comparing the evaluation of a + /// condition across executed test vectors, comparisons against a DontCare + /// are effectively ignored. + enum CondState { MCDC_DontCare = -1, MCDC_False = 0, MCDC_True = 1 }; + + using TestVector = llvm::SmallVector; + using TestVectors = llvm::SmallVector; + using BoolVector = llvm::SmallVector; + using TVRowPair = std::pair; + using TVPairMap = llvm::DenseMap; + using CondIDMap = llvm::DenseMap; + using LineColPairMap = llvm::DenseMap; + +private: + CounterMappingRegion Region; + TestVectors TV; + TVPairMap IndependencePairs; + BoolVector Folded; + CondIDMap PosToID; + LineColPairMap CondLoc; + +public: + MCDCRecord(CounterMappingRegion Region, TestVectors TV, + TVPairMap IndependencePairs, BoolVector Folded, CondIDMap PosToID, + LineColPairMap CondLoc) + : Region(Region), TV(TV), IndependencePairs(IndependencePairs), + Folded(Folded), PosToID(PosToID), CondLoc(CondLoc){}; + + CounterMappingRegion getDecisionRegion() const { return Region; } + unsigned getNumConditions() const { + assert(Region.MCDCParams.NumConditions != 0 && + "In MC/DC, NumConditions should never be zero!"); + return Region.MCDCParams.NumConditions; + } + unsigned getNumTestVectors() const { return TV.size(); } + bool isCondFolded(unsigned Condition) const { return Folded[Condition]; } + + /// Return the evaluation of a condition (indicated by Condition) in an + /// executed test vector (indicated by TestVectorIndex), which will be True, + /// False, or DontCare if the condition is unevaluatable. Because condition + /// IDs are not associated based on their position in the expression, + /// accessing conditions in the TestVectors requires a translation from a + /// ordinal position to actual condition ID. This is done via PosToID[]. + CondState getTVCondition(unsigned TestVectorIndex, unsigned Condition) { + return TV[TestVectorIndex][PosToID[Condition]]; + } + + /// Return the Result evaluation for an executed test vector. + /// See MCDCRecordProcessor::RecordTestVector(). + CondState getTVResult(unsigned TestVectorIndex) { + return TV[TestVectorIndex][getNumConditions()]; + } + + /// Determine whether a given condition (indicated by Condition) is covered + /// by an Independence Pair. Because condition IDs are not associated based + /// on their position in the expression, accessing conditions in the + /// TestVectors requires a translation from a ordinal position to actual + /// condition ID. This is done via PosToID[]. + bool isConditionIndependencePairCovered(unsigned Condition) const { + auto It = PosToID.find(Condition); + if (It != PosToID.end()) + return (IndependencePairs.find(It->second) != IndependencePairs.end()); + llvm_unreachable("Condition ID without an Ordinal mapping"); + } + + /// Return the Independence Pair that covers the given condition. Because + /// condition IDs are not associated based on their position in the + /// expression, accessing conditions in the TestVectors requires a + /// translation from a ordinal position to actual condition ID. This is done + /// via PosToID[]. + TVRowPair getConditionIndependencePair(unsigned Condition) { + assert(isConditionIndependencePairCovered(Condition)); + return IndependencePairs[PosToID[Condition]]; + } + + float getPercentCovered() const { + unsigned Folded = 0; + unsigned Covered = 0; + for (unsigned C = 0; C < getNumConditions(); C++) { + if (isCondFolded(C)) + Folded++; + else if (isConditionIndependencePairCovered(C)) + Covered++; + } + + unsigned Total = getNumConditions() - Folded; + if (Total == 0) + return 0.0; + return (static_cast(Covered) / static_cast(Total)) * 100.0; + } + + std::string getConditionHeaderString(unsigned Condition) { + std::ostringstream OS; + OS << "Condition C" << Condition + 1 << " --> ("; + OS << CondLoc[Condition].first << ":" << CondLoc[Condition].second; + OS << ")\n"; + return OS.str(); + } + + std::string getTestVectorHeaderString() const { + std::ostringstream OS; + if (getNumTestVectors() == 0) { + OS << "None.\n"; + return OS.str(); + } + const auto NumConditions = getNumConditions(); + for (unsigned I = 0; I < NumConditions; I++) { + OS << "C" << I + 1; + if (I != NumConditions - 1) + OS << ", "; + } + OS << " Result\n"; + return OS.str(); + } + + std::string getTestVectorString(unsigned TestVectorIndex) { + assert(TestVectorIndex < getNumTestVectors() && + "TestVector index out of bounds!"); + std::ostringstream OS; + const auto NumConditions = getNumConditions(); + // Add individual condition values to the string. + OS << " " << TestVectorIndex + 1 << " { "; + for (unsigned Condition = 0; Condition < NumConditions; Condition++) { + if (isCondFolded(Condition)) + OS << "C"; + else { + switch (getTVCondition(TestVectorIndex, Condition)) { + case MCDCRecord::MCDC_DontCare: + OS << "-"; + break; + case MCDCRecord::MCDC_True: + OS << "T"; + break; + case MCDCRecord::MCDC_False: + OS << "F"; + break; + } + } + if (Condition != NumConditions - 1) + OS << ", "; + } + + // Add result value to the string. + OS << " = "; + if (getTVResult(TestVectorIndex) == MCDC_True) + OS << "T"; + else + OS << "F"; + OS << " }\n"; + + return OS.str(); + } + + std::string getConditionCoverageString(unsigned Condition) { + assert(Condition < getNumConditions() && + "Condition index is out of bounds!"); + std::ostringstream OS; + + OS << " C" << Condition + 1 << "-Pair: "; + if (isCondFolded(Condition)) { + 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"; + + return OS.str(); + } +}; + /// A Counter mapping context is used to connect the counters, expressions /// and the obtained counter values. class CounterMappingContext { ArrayRef Expressions; ArrayRef CounterValues; + ArrayRef BitmapBytes; public: CounterMappingContext(ArrayRef Expressions, @@ -338,6 +571,7 @@ : Expressions(Expressions), CounterValues(CounterValues) {} void setCounts(ArrayRef Counts) { CounterValues = Counts; } + void setBitmapBytes(ArrayRef Bytes) { BitmapBytes = Bytes; } void dump(const Counter &C, raw_ostream &OS) const; void dump(const Counter &C) const { dump(C, dbgs()); } @@ -346,6 +580,17 @@ /// counter was executed. Expected evaluate(const Counter &C) const; + /// Return the number of times that a region of code associated with this + /// counter was executed. + Expected + evaluateBitmap(const CounterMappingRegion *MCDCDecision) const; + + /// Return an MCDC record that indicates executed test vectors and condition + /// pairs. + Expected + evaluateMCDCRegion(CounterMappingRegion Region, BitVector Bitmap, + ArrayRef Branches); + unsigned getMaxCounterID(const Counter &C) const; }; @@ -364,6 +609,8 @@ std::vector CountedRegions; /// Branch Regions in the function along with their counts. std::vector CountedBranchRegions; + /// MCDC Records record a DecisionRegion and associated BranchRegions. + std::vector MCDCRecords; /// The number of times this function was executed. uint64_t ExecutionCount = 0; @@ -373,9 +620,12 @@ FunctionRecord(FunctionRecord &&FR) = default; FunctionRecord &operator=(FunctionRecord &&) = default; + void pushMCDCRecord(MCDCRecord Record) { MCDCRecords.push_back(Record); } + void pushRegion(CounterMappingRegion Region, uint64_t Count, uint64_t FalseCount) { - if (Region.Kind == CounterMappingRegion::BranchRegion) { + if (Region.Kind == CounterMappingRegion::BranchRegion || + Region.Kind == CounterMappingRegion::MCDCBranchRegion) { CountedBranchRegions.emplace_back(Region, Count, FalseCount); // If both counters are hard-coded to zero, then this region represents a // constant-folded branch. @@ -546,6 +796,7 @@ std::vector Segments; std::vector Expansions; std::vector BranchRegions; + std::vector MCDCRecords; public: CoverageData() = default; @@ -572,6 +823,9 @@ /// Branches that can be further processed. ArrayRef getBranches() const { return BranchRegions; } + + /// MCDC Records that can be further processed. + ArrayRef getMCDCRecords() const { return MCDCRecords; } }; /// The mapping of profile information to coverage data. diff --git a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp --- a/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp +++ b/llvm/lib/ProfileData/Coverage/CoverageMapping.cpp @@ -30,6 +30,7 @@ #include "llvm/Support/raw_ostream.h" #include #include +#include #include #include #include @@ -221,6 +222,264 @@ return LastPoppedValue; } +Expected CounterMappingContext::evaluateBitmap( + const CounterMappingRegion *MCDCDecision) const { + unsigned ID = MCDCDecision->MCDCParams.BitmapIdx; + unsigned NC = MCDCDecision->MCDCParams.NumConditions; + unsigned SizeInBits = llvm::alignTo(uint64_t(1) << NC, CHAR_BIT); + unsigned SizeInBytes = SizeInBits / CHAR_BIT; + + ArrayRef Bytes(&BitmapBytes[ID], SizeInBytes); + + // Mask each bitmap byte into the BitVector. Go in reverse so that the + // bitvector can just be shifted over by one byte on each iteration. + BitVector Result(SizeInBits, false); + for (auto Byte = std::rbegin(Bytes); Byte != std::rend(Bytes); ++Byte) { + uint32_t Data = *Byte; + Result <<= CHAR_BIT; + Result.setBitsInMask(&Data, 1); + } + return Result; +} + +class MCDCRecordProcessor { + /// A bitmap representing the executed test vectors for a boolean expression. + /// Each index of the bitmap corresponds to a possible test vector. An index + /// with a bit value of '1' indicates that the corresponding Test Vector + /// identified by that index was executed. + BitVector &ExecutedTestVectorBitmap; + + /// Decision Region to which the ExecutedTestVectorBitmap applies. + CounterMappingRegion &Region; + + /// Array of branch regions corresponding each conditions in the boolean + /// expression. + ArrayRef Branches; + + /// Total number of conditions in the boolean expression. + unsigned NumConditions; + + /// Mapping of a condition ID to its corresponding branch region. + llvm::DenseMap Map; + + /// Vector used to track whether a condition is constant folded. + MCDCRecord::BoolVector Folded; + + /// Mapping of calculated MC/DC Independence Pairs for each condition. + MCDCRecord::TVPairMap IndependencePairs; + + /// Total number of possible Test Vectors for the boolean expression. + MCDCRecord::TestVectors TestVectors; + + /// Actual executed Test Vectors for the boolean expression, based on + /// ExecutedTestVectorBitmap. + MCDCRecord::TestVectors ExecVectors; + +public: + MCDCRecordProcessor(BitVector &Bitmap, CounterMappingRegion &Region, + ArrayRef Branches) + : ExecutedTestVectorBitmap(Bitmap), Region(Region), Branches(Branches), + NumConditions(Region.MCDCParams.NumConditions), + Folded(NumConditions, false), IndependencePairs(NumConditions), + TestVectors(pow(2, NumConditions)) {} + +private: + void recordTestVector(MCDCRecord::TestVector &TV, + MCDCRecord::CondState Result) { + // Calculate an index that is used to identify the test vector in a vector + // of test vectors. This index also corresponds to the index values of an + // MCDC Region's bitmap (see findExecutedTestVectors()). + unsigned Index = 0; + for (auto Cond = std::rbegin(TV); Cond != std::rend(TV); ++Cond) { + Index <<= 1; + Index |= (*Cond == MCDCRecord::MCDC_True) ? 0x1 : 0x0; + } + + // Copy the completed test vector to the vector of testvectors. + TestVectors[Index] = TV; + + // The final value (T,F) is equal to the last non-dontcare state on the + // path (in a short-circuiting system). + TestVectors[Index].push_back(Result); + } + + void shouldCopyOffTestVectorForTruePath(MCDCRecord::TestVector &TV, + unsigned ID) { + // Branch regions are hashed based on an ID. + const CounterMappingRegion *Branch = Map[ID]; + + TV[ID - 1] = MCDCRecord::MCDC_True; + if (Branch->MCDCParams.TrueID > 0) + buildTestVector(TV, Branch->MCDCParams.TrueID); + else + recordTestVector(TV, MCDCRecord::MCDC_True); + } + + void shouldCopyOffTestVectorForFalsePath(MCDCRecord::TestVector &TV, + unsigned ID) { + // Branch regions are hashed based on an ID. + const CounterMappingRegion *Branch = Map[ID]; + + TV[ID - 1] = MCDCRecord::MCDC_False; + if (Branch->MCDCParams.FalseID > 0) + buildTestVector(TV, Branch->MCDCParams.FalseID); + else + recordTestVector(TV, MCDCRecord::MCDC_False); + } + + /// Starting with the base test vector, build a comprehensive list of + /// possible test vectors by recursively walking the branch condition IDs + /// provided. Once an end node is reached, record the test vector in a vector + /// of test vectors that can be matched against during MC/DC analysis, and + /// then reset the positions to 'DontCare'. + void buildTestVector(MCDCRecord::TestVector &TV, unsigned ID = 1) { + shouldCopyOffTestVectorForTruePath(TV, ID); + shouldCopyOffTestVectorForFalsePath(TV, ID); + + // Reset back to DontCare. + TV[ID - 1] = MCDCRecord::MCDC_DontCare; + } + + /// Walk the bits in the bitmap. A bit set to '1' indicates that the test + /// vector at the corresponding index was executed during a test run. + void findExecutedTestVectors(BitVector &ExecutedTestVectorBitmap) { + for (unsigned Idx = 0; Idx < ExecutedTestVectorBitmap.size(); ++Idx) { + if (ExecutedTestVectorBitmap[Idx] == 0) + continue; + assert(!TestVectors[Idx].empty() && "Test Vector doesn't exist."); + ExecVectors.push_back(TestVectors[Idx]); + } + } + + /// For a given condition and two executed Test Vectors, A and B, see if the + /// two test vectors match forming an Independence Pair for the condition. + /// For two test vectors to match, the following must be satisfied: + /// - The condition's value in each test vector must be opposite. + /// - The result's value in each test vector must be opposite. + /// - All other conditions' values must be equal or marked as "don't care". + bool matchTestVectors(unsigned Aidx, unsigned Bidx, unsigned ConditionIdx) { + const MCDCRecord::TestVector &A = ExecVectors[Aidx]; + const MCDCRecord::TestVector &B = ExecVectors[Bidx]; + + // If condition values in both A and B aren't opposites, no match. + // Because a value can be 0 (false), 1 (true), or -1 (DontCare), a check + // that "XOR != 1" will ensure that the values are opposites and that + // neither of them is a DontCare. + // 1 XOR 0 == 1 | 0 XOR 0 == 0 | -1 XOR 0 == -1 + // 1 XOR 1 == 0 | 0 XOR 1 == 1 | -1 XOR 1 == -2 + // 1 XOR -1 == -2 | 0 XOR -1 == -1 | -1 XOR -1 == 0 + if ((A[ConditionIdx] ^ B[ConditionIdx]) != 1) + return false; + + // If the results of both A and B aren't opposites, no match. + if ((A[NumConditions] ^ B[NumConditions]) != 1) + return false; + + for (unsigned Idx = 0; Idx < NumConditions; ++Idx) { + // Look for other conditions that don't match. Skip over the given + // Condition as well as any conditions marked as "don't care". + const auto ARecordTyForCond = A[Idx]; + const auto BRecordTyForCond = B[Idx]; + if (Idx == ConditionIdx || + ARecordTyForCond == MCDCRecord::MCDC_DontCare || + BRecordTyForCond == MCDCRecord::MCDC_DontCare) + continue; + + // If there is a condition mismatch with any of the other conditions, + // there is no match for the test vectors. + if (ARecordTyForCond != BRecordTyForCond) + return false; + } + + // Otherwise, match. + return true; + } + + /// Find all possible Independence Pairs for a boolean expression given its + /// executed Test Vectors. This process involves looking at each condition + /// and attempting to find two Test Vectors that "match", giving us a pair. + void findIndependencePairs() { + unsigned NumTVs = ExecVectors.size(); + + // For each condition. + for (unsigned C = 0; C < NumConditions; ++C) { + bool PairFound = false; + + // For each executed test vector. + for (unsigned I = 0; !PairFound && I < NumTVs; ++I) { + // Compared to every other executed test vector. + for (unsigned J = 0; !PairFound && J < NumTVs; ++J) { + if (I == J) + continue; + + // If a matching pair of vectors is found, record them. + if ((PairFound = matchTestVectors(I, J, C))) + IndependencePairs[C] = std::make_pair(I + 1, J + 1); + } + } + } + } + +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). + MCDCRecord processMCDCRecord() { + unsigned I = 0; + 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 + // 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 + // from being measured. + for (const auto &B : Branches) { + Map[B.MCDCParams.ID] = &B; + PosToID[I] = B.MCDCParams.ID - 1; + CondLoc[I] = B.startLoc(); + Folded[I++] = (B.Count.isZero() && B.FalseCount.isZero()); + } + + // Initialize a base test vector as 'DontCare'. + MCDCRecord::TestVector TV(NumConditions, MCDCRecord::MCDC_DontCare); + + // Use the base test vector to build the list of all possible test vectors. + buildTestVector(TV); + + // Using Profile Bitmap from runtime, mark the executed test vectors. + findExecutedTestVectors(ExecutedTestVectorBitmap); + + // Compare executed test vectors against each other to find an independence + // pairs for each condition. This processing takes the most time. + findIndependencePairs(); + + // Record Test vectors, executed vectors, and independence pairs. + MCDCRecord Res(Region, ExecVectors, IndependencePairs, Folded, PosToID, + CondLoc); + return Res; + } +}; + +Expected CounterMappingContext::evaluateMCDCRegion( + CounterMappingRegion Region, BitVector ExecutedTestVectorBitmap, + ArrayRef Branches) { + + MCDCRecordProcessor MCDCProcessor(ExecutedTestVectorBitmap, Region, Branches); + return MCDCProcessor.processMCDCRecord(); +} + unsigned CounterMappingContext::getMaxCounterID(const Counter &C) const { struct StackElem { Counter ICounter; @@ -303,6 +562,24 @@ return MaxCounterID; } +static unsigned getMaxBitmapSize(const CounterMappingContext &Ctx, + const CoverageMappingRecord &Record) { + unsigned MaxBitmapID = 0; + unsigned NumConditions = 0; + // The last DecisionRegion has the highest bitmap byte index used in the + // function, which when combined with its number of conditions, yields the + // full bitmap size. + for (const auto &Region : reverse(Record.MappingRegions)) { + if (Region.Kind == CounterMappingRegion::MCDCDecisionRegion) { + MaxBitmapID = Region.MCDCParams.BitmapIdx; + NumConditions = Region.MCDCParams.NumConditions; + break; + } + } + unsigned SizeInBits = llvm::alignTo(uint64_t(1) << NumConditions, CHAR_BIT); + return MaxBitmapID + (SizeInBits / CHAR_BIT); +} + Error CoverageMapping::loadFunctionRecord( const CoverageMappingRecord &Record, IndexedInstrProfReader &ProfileReader) { @@ -326,12 +603,28 @@ FuncHashMismatches.emplace_back(std::string(Record.FunctionName), Record.FunctionHash); return Error::success(); - } else if (IPE != instrprof_error::unknown_function) + } + if (IPE != instrprof_error::unknown_function) return make_error(IPE); Counts.assign(getMaxCounterID(Ctx, Record) + 1, 0); } Ctx.setCounts(Counts); + std::vector BitmapBytes; + if (Error E = ProfileReader.getFunctionBitmapBytes( + Record.FunctionName, Record.FunctionHash, BitmapBytes)) { + instrprof_error IPE = std::get<0>(InstrProfError::take(std::move(E))); + if (IPE == instrprof_error::hash_mismatch) { + FuncHashMismatches.emplace_back(std::string(Record.FunctionName), + Record.FunctionHash); + return Error::success(); + } + if (IPE != instrprof_error::unknown_function) + return make_error(IPE); + BitmapBytes.assign(getMaxBitmapSize(Ctx, Record) + 1, 0); + } + Ctx.setBitmapBytes(BitmapBytes); + assert(!Record.MappingRegions.empty() && "Function has no regions"); // This coverage record is a zero region for a function that's unused in @@ -343,8 +636,20 @@ Record.MappingRegions[0].Count.isZero() && Counts[0] > 0) return Error::success(); + unsigned NumConds = 0; + const CounterMappingRegion *MCDCDecision; + std::vector MCDCBranches; + FunctionRecord Function(OrigFuncName, Record.Filenames); for (const auto &Region : Record.MappingRegions) { + // If an MCDCDecisionRegion is seen, track the BranchRegions that follow + // it according to Region.NumConditions. + if (Region.Kind == CounterMappingRegion::MCDCDecisionRegion) { + assert(NumConds == 0); + MCDCDecision = &Region; + NumConds = Region.MCDCParams.NumConditions; + continue; + } Expected ExecutionCount = Ctx.evaluate(Region.Count); if (auto E = ExecutionCount.takeError()) { consumeError(std::move(E)); @@ -356,6 +661,44 @@ return Error::success(); } Function.pushRegion(Region, *ExecutionCount, *AltExecutionCount); + + // If a MCDCDecisionRegion was seen, store the BranchRegions that + // correspond to it in a vector, according to the number of conditions + // recorded for the region (tracked by NumConds). + if (NumConds > 0 && Region.Kind == CounterMappingRegion::MCDCBranchRegion) { + MCDCBranches.push_back(Region); + + // As we move through all of the MCDCBranchRegions that follow the + // MCDCDecisionRegion, decrement NumConds to make sure we account for + // them all before we calculate the bitmap of executed test vectors. + if (--NumConds == 0) { + // Evaluating the test vector bitmap for the decision region entails + // calculating precisely what bits are pertinent to this region alone. + // This is calculated based on the recorded offset into the global + // profile bitmap; the length is calculated based on the recorded + // number of conditions. + Expected ExecutedTestVectorBitmap = + Ctx.evaluateBitmap(MCDCDecision); + if (auto E = ExecutedTestVectorBitmap.takeError()) { + consumeError(std::move(E)); + return Error::success(); + } + + // Since the bitmap identifies the executed test vectors for an MC/DC + // DecisionRegion, all of the information is now available to process. + // This is where the bulk of the MC/DC progressing takes place. + Expected Record = Ctx.evaluateMCDCRegion( + *MCDCDecision, *ExecutedTestVectorBitmap, MCDCBranches); + if (auto E = Record.takeError()) { + consumeError(std::move(E)); + return Error::success(); + } + + // Save the MC/DC Record so that it can be visualized later. + Function.pushMCDCRecord(*Record); + MCDCBranches.clear(); + } + } } // Don't create records for (filenames, function) pairs we've already seen. @@ -862,6 +1205,10 @@ for (const auto &CR : Function.CountedBranchRegions) if (FileIDs.test(CR.FileID) && (CR.FileID == CR.ExpandedFileID)) FileCoverage.BranchRegions.push_back(CR); + // Capture MCDC records specific to the function. + for (const auto &MR : Function.MCDCRecords) + if (FileIDs.test(MR.getDecisionRegion().FileID)) + FileCoverage.MCDCRecords.push_back(MR); } LLVM_DEBUG(dbgs() << "Emitting segments for file: " << Filename << "\n"); @@ -914,6 +1261,11 @@ if (CR.FileID == *MainFileID) FunctionCoverage.BranchRegions.push_back(CR); + // Capture MCDC records specific to the function. + for (const auto &MR : Function.MCDCRecords) + if (MR.getDecisionRegion().FileID == *MainFileID) + FunctionCoverage.MCDCRecords.push_back(MR); + LLVM_DEBUG(dbgs() << "Emitting segments for function: " << Function.Name << "\n"); FunctionCoverage.Segments = SegmentBuilder::buildSegments(Regions); diff --git a/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp b/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp --- a/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp +++ b/llvm/lib/ProfileData/Coverage/CoverageMappingReader.cpp @@ -244,6 +244,7 @@ unsigned LineStart = 0; for (size_t I = 0; I < NumRegions; ++I) { Counter C, C2; + uint64_t BIDX = 0, NC = 0, ID = 0, TID = 0, FID = 0; CounterMappingRegion::RegionKind Kind = CounterMappingRegion::CodeRegion; // Read the combined counter + region kind. @@ -294,6 +295,27 @@ if (auto Err = readCounter(C2)) return Err; break; + case CounterMappingRegion::MCDCBranchRegion: + // For a MCDC Branch Region, read two successive counters and 3 IDs. + Kind = CounterMappingRegion::MCDCBranchRegion; + if (auto Err = readCounter(C)) + return Err; + if (auto Err = readCounter(C2)) + return Err; + if (auto Err = readIntMax(ID, std::numeric_limits::max())) + return Err; + if (auto Err = readIntMax(TID, std::numeric_limits::max())) + return Err; + if (auto Err = readIntMax(FID, std::numeric_limits::max())) + return Err; + break; + case CounterMappingRegion::MCDCDecisionRegion: + Kind = CounterMappingRegion::MCDCDecisionRegion; + if (auto Err = readIntMax(BIDX, std::numeric_limits::max())) + return Err; + if (auto Err = readIntMax(NC, std::numeric_limits::max())) + return Err; + break; default: return make_error(coveragemap_error::malformed, "region kind is incorrect"); @@ -347,9 +369,14 @@ dbgs() << "\n"; }); - auto CMR = CounterMappingRegion(C, C2, InferredFileID, ExpandedFileID, - LineStart, ColumnStart, - LineStart + NumLines, ColumnEnd, Kind); + auto CMR = CounterMappingRegion( + C, C2, + CounterMappingRegion::MCDCParameters{ + static_cast(BIDX), static_cast(NC), + static_cast(ID), static_cast(TID), + static_cast(FID)}, + InferredFileID, ExpandedFileID, LineStart, ColumnStart, + LineStart + NumLines, ColumnEnd, Kind); if (CMR.startLoc() > CMR.endLoc()) return make_error( coveragemap_error::malformed, diff --git a/llvm/lib/ProfileData/Coverage/CoverageMappingWriter.cpp b/llvm/lib/ProfileData/Coverage/CoverageMappingWriter.cpp --- a/llvm/lib/ProfileData/Coverage/CoverageMappingWriter.cpp +++ b/llvm/lib/ProfileData/Coverage/CoverageMappingWriter.cpp @@ -237,6 +237,23 @@ writeCounter(MinExpressions, Count, OS); writeCounter(MinExpressions, FalseCount, OS); break; + case CounterMappingRegion::MCDCBranchRegion: + encodeULEB128(unsigned(I->Kind) + << Counter::EncodingCounterTagAndExpansionRegionTagBits, + OS); + writeCounter(MinExpressions, Count, OS); + writeCounter(MinExpressions, FalseCount, OS); + encodeULEB128(unsigned(I->MCDCParams.ID), OS); + encodeULEB128(unsigned(I->MCDCParams.TrueID), OS); + encodeULEB128(unsigned(I->MCDCParams.FalseID), OS); + break; + case CounterMappingRegion::MCDCDecisionRegion: + encodeULEB128(unsigned(I->Kind) + << Counter::EncodingCounterTagAndExpansionRegionTagBits, + OS); + encodeULEB128(unsigned(I->MCDCParams.BitmapIdx), OS); + encodeULEB128(unsigned(I->MCDCParams.NumConditions), OS); + break; } assert(I->LineStart >= PrevLineStart); encodeULEB128(I->LineStart - PrevLineStart, OS); diff --git a/llvm/test/tools/llvm-cov/Inputs/binary-formats.canonical.json b/llvm/test/tools/llvm-cov/Inputs/binary-formats.canonical.json --- a/llvm/test/tools/llvm-cov/Inputs/binary-formats.canonical.json +++ b/llvm/test/tools/llvm-cov/Inputs/binary-formats.canonical.json @@ -1,9 +1,11 @@ -CHECK: {"data": -CHECK-SAME: [{ +CHECK: { + "data": CHECK - SAME: [ + { CHECK-SAME: "files":[ CHECK-SAME: {"branches":[], CHECK-SAME: "expansions":[], CHECK-SAME: "filename":"/tmp/binary-formats.c", +CHECK-SAME: "mcdc_records":[], CHECK-SAME: "segments": CHECK-SAME: 4,40,100,true,true,false CHECK-SAME: 4,42,0,false,false,false @@ -11,11 +13,13 @@ CHECK-SAME: "functions":{"count":1,"covered":1,"percent":100}, CHECK-SAME: "instantiations":{"count":1,"covered":1,"percent":100}, CHECK-SAME: "lines":{"count":1,"covered":1,"percent":100}, +CHECK-SAME: "mcdc":{"count":0,"covered":0,"notcovered":0,"percent":0}, CHECK-SAME: "regions":{"count":1,"covered":1,"notcovered":0,"percent":100}}} CHECK-SAME: ], CHECK-SAME: "functions":[ CHECK-SAME: {"branches":[], -CHECK-SAME: "count":100,"filenames":["/tmp/binary-formats.c"],"name":"main", +CHECK-SAME: "count":100,"filenames":["/tmp/binary-formats.c"], +CHECK-SAME: "mcdc_records":[],"name":"main", CHECK-SAME: "regions": CHECK-SAME: 4,40,4,42,100,0,0,0 CHECK-SAME: } @@ -25,6 +29,7 @@ CHECK-SAME: "functions":{"count":1,"covered":1,"percent":100}, CHECK-SAME: "instantiations":{"count":1,"covered":1,"percent":100}, CHECK-SAME: "lines":{"count":1,"covered":1,"percent":100}, +CHECk-SAME: "mcdc":{"count":0,"covered":0,"notcovered":0,"percent":0}, CHECK-SAME: "regions":{"count":1,"covered":1,"notcovered":0,"percent":100}}} CHECK-SAME: ], CHECK-SAME: "type":"llvm.coverage.json.export" diff --git a/llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.cpp b/llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.cpp new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.cpp @@ -0,0 +1,117 @@ +#include + +bool case0(bool a) { + return 0 && a; +} +bool case1(bool a) { + return a && 0; +} +bool case2(bool a) { + return 1 && a; +} +bool case3(bool a) { + return a && 1; +} +bool case4(bool a) { + return 1 || a; +} +bool case5(bool a) { + return a || 1; +} +bool case6(bool a) { + return 0 || a; +} +bool case7(bool a) { + return a || 0; +} + +bool case8(bool a, bool b) { + return 0 && a && b; +} +bool case9(bool a, bool b) { + return a && 0 && b; +} +bool casea(bool a, bool b) { + return 1 && a && b; +} +bool caseb(bool a, bool b) { + return a && 1 && b; +} +bool casec(bool a, bool b) { + return 1 || a || b; +} +bool cased(bool a, bool b) { + return a || 1 || b; +} +bool casee(bool a, bool b) { + return 0 || a || b; +} +bool casef(bool a, bool b) { + return a || 0 || b; +} + +bool caseg(bool a, bool b) { + return b && a && 0; +} +bool caseh(bool a, bool b) { + return b && 0 && a; +} +bool casei(bool a, bool b) { + return b && a && 1; +} +bool casej(bool a, bool b) { + return b && 1 && a; +} +bool casek(bool a, bool b) { + return b || a || 1; +} +bool casel(bool a, bool b) { + return b || 1 || a; +} +bool casem(bool a, bool b) { + return b || a || 0; +} +bool casen(bool a, bool b) { + return b || 0 || a; +} + +extern "C" { + extern void __llvm_profile_write_file(void); +} + +int main(int argc, char *argv[]) +{ + bool a = atoi(argv[1]); + bool b = atoi(argv[2]); + volatile bool c; + + c = case0(a); + c = case1(a); + c = case2(a); + c = case3(a); + c = case4(a); + c = case5(a); + c = case6(a); + c = case7(a); + + c = case8(a, b); + c = case9(a, b); + c = casea(a, b); + c = caseb(a, b); + c = casec(a, b); + c = cased(a, b); + c = casee(a, b); + c = casef(a, b); + + c = caseg(a, b); + c = caseh(a, b); + c = casei(a, b); + c = casej(a, b); + c = casek(a, b); + c = casel(a, b); + c = casem(a, b); + c = casen(a, b); + + __llvm_profile_write_file(); + return 0; +} diff --git a/llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.o b/llvm/test/tools/llvm-cov/Inputs/mcdc-const-folding.o new file mode 100755 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ + +extern "C" { +extern void __llvm_profile_write_file(void); +} + +extern int foo(); + +void test(bool a, bool b, bool c, bool d) { + + if ((a && 1) || (0 && d) || 0) + printf("test1 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); + + __llvm_profile_write_file(); + return 0; +} diff --git a/llvm/test/tools/llvm-cov/Inputs/mcdc-const.o b/llvm/test/tools/llvm-cov/Inputs/mcdc-const.o new file mode 100755 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ + +extern "C" { +extern void __llvm_profile_write_file(void); +} + +extern int foo(); + +void test(bool a, bool b, bool c, bool d) { + + if ((a && b) || (c && d)) + printf("test1 decision true\n"); + + if (b && c) if (a && d) + printf("test2 decision true\n"); + + if ((c && d) && + (a && b)) + printf("test3 decision true\n"); +} + +int main() +{ + test(false,false,false,false); + test(true,false,true,false); + test(true,false,true,true); + test(true,true,false,false); + + test(true,false,false,false); + test(true,true,true,true); + test(false,true,true,false); + + __llvm_profile_write_file(); + return 0; +} diff --git a/llvm/test/tools/llvm-cov/Inputs/mcdc-general.o b/llvm/test/tools/llvm-cov/Inputs/mcdc-general.o new file mode 100755 index 0000000000000000000000000000000000000000..0000000000000000000000000000000000000000 GIT binary patch literal 0 Hc$@ MC/DC Decision Region (12:7) to (12:32) +// CHECKGENERALCASE-NEXT: | +// CHECKGENERALCASE-NEXT: | Number of Conditions: 5 +// CHECKGENERALCASE-NEXT: | Condition C1 --> (12:8) +// CHECKGENERALCASE-NEXT: | Condition C2 --> (12:13) +// CHECKGENERALCASE-NEXT: | Condition C3 --> (12:20) +// CHECKGENERALCASE-NEXT: | Condition C4 --> (12:25) +// CHECKGENERALCASE-NEXT: | Condition C5 --> (12:31) +// CHECKGENERALCASE-NEXT: | +// CHECKGENERALCASE-NEXT: | Executed MC/DC Test Vectors: +// CHECKGENERALCASE-NEXT: | +// CHECKGENERALCASE-NEXT: | C1, C2, C3, C4, C5 Result +// CHECKGENERALCASE-NEXT: | 1 { F, C, C, -, C = F } +// CHECKGENERALCASE-NEXT: | 2 { T, C, C, -, C = T } +// CHECKGENERALCASE-NEXT: | +// 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: | C5-Pair: constant folded +// CHECKGENERALCASE-NEXT: | MC/DC Coverage for Decision: 50.00% +// CHECKGENERALCASE-NEXT: | +// CHECKGENERALCASE-NEXT: ------------------ + +// RUN: llvm-profdata merge %S/Inputs/mcdc-const-folding.proftext -o %t.profdata +// RUN: llvm-cov show --show-mcdc %S/Inputs/mcdc-const-folding.o -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-const-folding.cpp | FileCheck %s -check-prefix=CHECKFULLCASE +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-const-folding.o -instr-profile %t.profdata -show-functions -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-const-folding.cpp | FileCheck %s -check-prefix=REPORT + +// CHECKFULLCASE: | 1 { C, - = F } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE-NEXT: | C2-Pair: not covered +// 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-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { C, F = F } +// CHECKFULLCASE-NEXT: | 2 { C, T = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE-NEXT: | C2-Pair: covered: (1,2) +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { F, C = F } +// CHECKFULLCASE-NEXT: | 2 { T, C = T } +// CHECKFULLCASE: | C1-Pair: covered: (1,2) +// CHECKFULLCASE-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { C, - = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE-NEXT: | C2-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, C = T } +// CHECKFULLCASE-NEXT: | 2 { F, C = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { C, F = F } +// CHECKFULLCASE-NEXT: | 2 { C, T = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE-NEXT: | C2-Pair: covered: (1,2) +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { F, C = F } +// CHECKFULLCASE-NEXT: | 2 { T, C = T } +// CHECKFULLCASE: | C1-Pair: covered: (1,2) +// CHECKFULLCASE-NEXT: | C2-Pair: constant folded +// 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: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { F, C, - = F } +// CHECKFULLCASE-NEXT: | 2 { T, C, - = F } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { C, F, - = F } +// CHECKFULLCASE-NEXT: | 2 { C, T, F = F } +// CHECKFULLCASE-NEXT: | 3 { C, T, T = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE-NEXT: | C2-Pair: covered: (1,3) +// CHECKFULLCASE-NEXT: | C3-Pair: covered: (2,3) +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { F, C, - = F } +// CHECKFULLCASE-NEXT: | 2 { T, C, F = F } +// CHECKFULLCASE-NEXT: | 3 { T, C, T = T } +// CHECKFULLCASE: | C1-Pair: covered: (1,3) +// CHECKFULLCASE-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE-NEXT: | C3-Pair: covered: (2,3) +// 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: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, C, - = T } +// CHECKFULLCASE-NEXT: | 2 { F, C, - = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { C, F, T = T } +// CHECKFULLCASE-NEXT: | 2 { C, T, - = T } +// CHECKFULLCASE: | C1-Pair: constant folded +// CHECKFULLCASE-NEXT: | C2-Pair: not covered +// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, C, - = T } +// CHECKFULLCASE-NEXT: | 2 { F, C, T = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// 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-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-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { F, -, C = F } +// CHECKFULLCASE-NEXT: | 2 { T, F, C = F } +// CHECKFULLCASE-NEXT: | 3 { T, T, C = T } +// CHECKFULLCASE: | C1-Pair: covered: (1,3) +// CHECKFULLCASE-NEXT: | C2-Pair: covered: (2,3) +// CHECKFULLCASE-NEXT: | C3-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { F, C, - = F } +// CHECKFULLCASE-NEXT: | 2 { T, C, F = F } +// CHECKFULLCASE-NEXT: | 3 { T, C, T = T } +// CHECKFULLCASE: | C1-Pair: covered: (1,3) +// CHECKFULLCASE-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE-NEXT: | C3-Pair: covered: (2,3) +// CHECKFULLCASE: | MC/DC Coverage for Decision: 100.00% +// CHECKFULLCASE: | 1 { T, -, C = T } +// CHECKFULLCASE-NEXT: | 2 { F, T, C = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: not covered +// CHECKFULLCASE-NEXT: | C3-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, C, - = T } +// CHECKFULLCASE-NEXT: | 2 { F, C, - = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, -, C = T } +// CHECKFULLCASE-NEXT: | 2 { F, T, C = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: not covered +// CHECKFULLCASE-NEXT: | C3-Pair: constant folded +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% +// CHECKFULLCASE: | 1 { T, C, - = T } +// CHECKFULLCASE-NEXT: | 2 { F, C, T = T } +// CHECKFULLCASE: | C1-Pair: not covered +// CHECKFULLCASE-NEXT: | C2-Pair: constant folded +// CHECKFULLCASE-NEXT: | C3-Pair: not covered +// CHECKFULLCASE: | MC/DC Coverage for Decision: 0.00% + +// REPORT: _Z5case0b {{.*}} 1 1 0.00% +// REPORT-NEXT: _Z5case1b {{.*}} 1 1 0.00% +// REPORT-NEXT: _Z5case2b {{.*}} 1 0 100.00% +// REPORT-NEXT: _Z5case3b {{.*}} 1 0 100.00% +// REPORT-NEXT: _Z5case4b {{.*}} 1 1 0.00% +// REPORT-NEXT: _Z5case5b {{.*}} 1 1 0.00% +// REPORT-NEXT: _Z5case6b {{.*}} 1 0 100.00% +// REPORT-NEXT: _Z5case7b {{.*}} 1 0 100.00% +// REPORT-NEXT: _Z5case8bb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5case9bb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5caseabb {{.*}} 2 0 100.00% +// REPORT-NEXT: _Z5casebbb {{.*}} 2 0 100.00% +// REPORT-NEXT: _Z5casecbb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5casedbb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5caseebb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5casefbb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5casegbb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5casehbb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5caseibb {{.*}} 2 0 100.00% +// REPORT-NEXT: _Z5casejbb {{.*}} 2 0 100.00% +// REPORT-NEXT: _Z5casekbb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5caselbb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5casembb {{.*}} 2 2 0.00% +// REPORT-NEXT: _Z5casenbb {{.*}} 2 2 0.00% +// REPORT: TOTAL {{.*}} 40 28 30.00% + +Instructions for regenerating the test: + +# cd %S/Inputs +cp mcdc-const.cpp /tmp +cp mcdc-const-folding.cpp /tmp + +clang -fcoverage-mcdc -fprofile-instr-generate -fcoverage-compilation-dir=. \ + -fcoverage-mapping /tmp/mcdc-const.cpp -o /tmp/mcdc-const.o + +clang -fcoverage-mcdc -fprofile-instr-generate -fcoverage-compilation-dir=. \ + -fcoverage-mapping /tmp/mcdc-const-folding.cpp -o /tmp/mcdc-const-folding.o + +mv /tmp/mcdc-const.o %S/Inputs +mv /tmp/mcdc-const-folding.o %S/Inputs diff --git a/llvm/test/tools/llvm-cov/mcdc-export-json.test b/llvm/test/tools/llvm-cov/mcdc-export-json.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-cov/mcdc-export-json.test @@ -0,0 +1,18 @@ +// RUN: llvm-profdata merge %S/Inputs/mcdc-general.proftext -o %t.profdata +// RUN: llvm-cov export --format=text %S/Inputs/mcdc-general.o -instr-profile %t.profdata | FileCheck %s + +// CHECK: 12,7,12,27,0,5,[true,true,true,true] +// CHECK: 15,7,15,13,0,5,[true,true] +// CHECK: 15,19,15,25,0,5,[true,false] +// CHECK: 18,7,19,15,0,5,[true,true,false,true] +// CHECK: "mcdc":{"count":12,"covered":10,"notcovered":2,"percent":83.333333333333343} + +Instructions for regenerating the test: + +# cd %S/Inputs +cp mcdc-general.cpp /tmp + +clang -fcoverage-mcdc -fprofile-instr-generate -fcoverage-compilation-dir=. \ + -fcoverage-mapping /tmp/mcdc-general.cpp -o /tmp/mcdc-const.o + +mv /tmp/mcdc-general.o %S/Inputs diff --git a/llvm/test/tools/llvm-cov/mcdc-general-none.test b/llvm/test/tools/llvm-cov/mcdc-general-none.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-cov/mcdc-general-none.test @@ -0,0 +1,82 @@ +// Test visualization of general MC/DC constructs with 0 executed test vectors. + +// RUN: llvm-profdata merge %S/Inputs/mcdc-general-none.proftext -o %t.profdata +// RUN: llvm-cov show --show-mcdc %S/Inputs/mcdc-general.o -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-general.o -instr-profile %t.profdata -show-functions -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=REPORT + +// CHECK: test(bool + +// CHECK: ------------------ +// CHECK-NEXT: |---> MC/DC Decision Region (12:7) to (12:27) +// CHECK-NEXT: | +// CHECK-NEXT: | Number of Conditions: 4 +// CHECK-NEXT: | Condition C1 --> (12:8) +// CHECK-NEXT: | Condition C2 --> (12:13) +// CHECK-NEXT: | Condition C3 --> (12:20) +// CHECK-NEXT: | Condition C4 --> (12:25) +// CHECK-NEXT: | +// CHECK-NEXT: | Executed MC/DC Test Vectors: +// CHECK-NEXT: | +// CHECK-NEXT: | None. +// CHECK-NEXT: | +// CHECK-NEXT: | C1-Pair: not covered +// CHECK-NEXT: | C2-Pair: not covered +// CHECK-NEXT: | C3-Pair: not covered +// CHECK-NEXT: | C4-Pair: not covered +// CHECK-NEXT: | MC/DC Coverage for Decision: 0.00% +// CHECK-NEXT: | +// CHECK-NEXT: ------------------ + + +// Turn off MC/DC visualization. +// RUN: llvm-cov show %S/Inputs/mcdc-general.o -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=NOMCDC +// NOMCDC-NOT: MC/DC Decision Region + +// REPORT: Name Regions Miss Cover Lines Miss Cover Branches Miss Cover MC/DC Conditions Miss Cover +// REPORT-NEXT: ------------------------------------------------------------------------------------------------------------------------------------------- +// REPORT-NEXT: _Z4testbbbb 25 0 100.00% 9 0 100.00% 24 2 91.67% 12 12 0.00% +// REPORT-NEXT: main 1 0 100.00% 11 0 100.00% 0 0 0.00% 0 0 0.00% +// REPORT-NEXT: --- +// REPORT-NEXT: TOTAL 26 0 100.00% 20 0 100.00% 24 2 91.67% 12 12 0.00% + +// Turn off MC/DC summary. +// RUN: llvm-cov report %S/Inputs/mcdc-general.o -instr-profile %t.profdata -show-functions -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=REPORT_NOMCDC +// REPORT_NOMCDC-NOT: TOTAL{{.*}}12 12 0.00% + + +// Test file-level report. +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-general.o -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=FILEREPORT +// FILEREPORT: TOTAL{{.*}}12 12 0.00% + + +// Test html output. +// RUN: llvm-cov show --show-mcdc-summary --show-mcdc %S/Inputs/mcdc-general.o -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp -format html -o %t.html.dir +// RUN: FileCheck -check-prefix=HTML -input-file=%t.html.dir/coverage/tmp/mcdc-general.cpp.html %s +// HTML-COUNT-4: MC/DC Decision Region ( + +// RUN: FileCheck -check-prefix HTML-INDEX -input-file %t.html.dir/index.html %s +// HTML-INDEX-LABEL: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX:
FilenameFunction CoverageLine CoverageRegion CoverageBranch CoverageMC/DC +// HTML-INDEX: 100.00% (2/2) +// HTML-INDEX: 100.00% (20/20) +// HTML-INDEX: 100.00% (26/26) +// HTML-INDEX: 91.67% (22/24) +// HTML-INDEX: 0.00% (0/12) +// HTML-INDEX: Totals + +Instructions for regenerating the test: + +# cd %S/Inputs +cp mcdc-general.cpp /tmp + +clang -fcoverage-mcdc -fprofile-instr-generate -fcoverage-compilation-dir=. \ + -fcoverage-mapping /tmp/mcdc-general.cpp -o /tmp/mcdc-general.o + +mv /tmp/mcdc-general.o %S/Inputs diff --git a/llvm/test/tools/llvm-cov/mcdc-general.test b/llvm/test/tools/llvm-cov/mcdc-general.test new file mode 100644 --- /dev/null +++ b/llvm/test/tools/llvm-cov/mcdc-general.test @@ -0,0 +1,148 @@ +// Test visualization of general MC/DC constructs. + +// RUN: llvm-profdata merge %S/Inputs/mcdc-general.proftext -o %t.profdata +// RUN: llvm-cov show --show-mcdc %S/Inputs/mcdc-general.o -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-general.o -instr-profile %t.profdata -show-functions -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=REPORT + +// CHECK: test(bool + +// CHECK: ------------------ +// CHECK-NEXT: |---> MC/DC Decision Region (12:7) to (12:27) +// CHECK-NEXT: | +// CHECK-NEXT: | Number of Conditions: 4 +// CHECK-NEXT: | Condition C1 --> (12:8) +// CHECK-NEXT: | Condition C2 --> (12:13) +// CHECK-NEXT: | Condition C3 --> (12:20) +// CHECK-NEXT: | Condition C4 --> (12:25) +// CHECK-NEXT: | +// CHECK-NEXT: | Executed MC/DC Test Vectors: +// CHECK-NEXT: | +// CHECK-NEXT: | C1, C2, C3, C4 Result +// CHECK-NEXT: | 1 { F, -, F, - = F } +// CHECK-NEXT: | 2 { T, F, F, - = F } +// CHECK-NEXT: | 3 { F, -, T, F = F } +// CHECK-NEXT: | 4 { T, F, T, F = F } +// CHECK-NEXT: | 5 { T, T, -, - = T } +// CHECK-NEXT: | 6 { T, F, T, T = T } +// CHECK-NEXT: | +// CHECK-NEXT: | C1-Pair: covered: (1,5) +// CHECK-NEXT: | C2-Pair: covered: (2,5) +// CHECK-NEXT: | C3-Pair: covered: (2,6) +// CHECK-NEXT: | C4-Pair: covered: (4,6) +// CHECK-NEXT: | MC/DC Coverage for Decision: 100.00% +// CHECK-NEXT: | +// CHECK-NEXT: ------------------ + +// CHECK: ------------------ +// CHECK-NEXT: |---> MC/DC Decision Region (15:7) to (15:13) +// CHECK-NEXT: | +// CHECK-NEXT: | Number of Conditions: 2 +// CHECK-NEXT: | Condition C1 --> (15:7) +// CHECK-NEXT: | Condition C2 --> (15:12) +// CHECK-NEXT: | +// CHECK-NEXT: | Executed MC/DC Test Vectors: +// CHECK-NEXT: | +// CHECK-NEXT: | C1, C2 Result +// CHECK-NEXT: | 1 { F, - = F } +// CHECK-NEXT: | 2 { T, F = F } +// CHECK-NEXT: | 3 { T, T = T } +// CHECK-NEXT: | +// CHECK-NEXT: | C1-Pair: covered: (1,3) +// CHECK-NEXT: | C2-Pair: covered: (2,3) +// CHECK-NEXT: | MC/DC Coverage for Decision: 100.00% +// CHECK-NEXT: | +// CHECK-NEXT: |---> MC/DC Decision Region (15:19) to (15:25) +// CHECK-NEXT: | +// CHECK-NEXT: | Number of Conditions: 2 +// CHECK-NEXT: | Condition C1 --> (15:19) +// CHECK-NEXT: | Condition C2 --> (15:24) +// CHECK-NEXT: | +// CHECK-NEXT: | Executed MC/DC Test Vectors: +// CHECK-NEXT: | +// CHECK-NEXT: | C1, C2 Result +// CHECK-NEXT: | 1 { F, - = F } +// CHECK-NEXT: | 2 { T, T = T } +// CHECK-NEXT: | +// CHECK-NEXT: | C1-Pair: covered: (1,2) +// CHECK-NEXT: | C2-Pair: not covered +// CHECK-NEXT: | MC/DC Coverage for Decision: 50.00% +// CHECK-NEXT: | +// CHECK-NEXT: ------------------ + +// CHECK: ------------------ +// CHECK-NEXT: |---> MC/DC Decision Region (18:7) to (19:15) +// CHECK-NEXT: | +// CHECK-NEXT: | Number of Conditions: 4 +// CHECK-NEXT: | Condition C1 --> (18:8) +// CHECK-NEXT: | Condition C2 --> (18:13) +// CHECK-NEXT: | Condition C3 --> (19:8) +// CHECK-NEXT: | Condition C4 --> (19:13) +// CHECK-NEXT: | +// CHECK-NEXT: | Executed MC/DC Test Vectors: +// CHECK-NEXT: | +// CHECK-NEXT: | C1, C2, C3, C4 Result +// CHECK-NEXT: | 1 { F, -, -, - = F } +// CHECK-NEXT: | 2 { T, F, -, - = F } +// CHECK-NEXT: | 3 { T, T, T, F = F } +// CHECK-NEXT: | 4 { T, T, T, T = T } +// CHECK-NEXT: | +// CHECK-NEXT: | C1-Pair: covered: (1,4) +// CHECK-NEXT: | C2-Pair: covered: (2,4) +// CHECK-NEXT: | C3-Pair: not covered +// CHECK-NEXT: | C4-Pair: covered: (3,4) +// CHECK-NEXT: | MC/DC Coverage for Decision: 75.00% +// CHECK-NEXT: | +// CHECK-NEXT: ------------------ + +// Turn off MC/DC visualization. +// RUN: llvm-cov show %S/Inputs/mcdc-general.o -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=NOMCDC +// NOMCDC-NOT: MC/DC Decision Region + +// REPORT: Name Regions Miss Cover Lines Miss Cover Branches Miss Cover MC/DC Conditions Miss Cover +// REPORT-NEXT: ------------------------------------------------------------------------------------------------------------------------------------------- +// REPORT-NEXT: _Z4testbbbb 25 0 100.00% 9 0 100.00% 24 2 91.67% 12 2 83.33% +// REPORT-NEXT: main 1 0 100.00% 11 0 100.00% 0 0 0.00% 0 0 0.00% +// REPORT-NEXT: --- +// REPORT-NEXT: TOTAL 26 0 100.00% 20 0 100.00% 24 2 91.67% 12 2 83.33% + +// Turn off MC/DC summary. +// RUN: llvm-cov report %S/Inputs/mcdc-general.o -instr-profile %t.profdata -show-functions -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=REPORT_NOMCDC +// REPORT_NOMCDC-NOT: TOTAL{{.*}}12 2 83.33% + + +// Test file-level report. +// RUN: llvm-cov report --show-mcdc-summary %S/Inputs/mcdc-general.o -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp | FileCheck %s -check-prefix=FILEREPORT +// FILEREPORT: TOTAL{{.*}}12 2 83.33% + + +// Test html output. +// RUN: llvm-cov show --show-mcdc-summary --show-mcdc %S/Inputs/mcdc-general.o -instr-profile %t.profdata -path-equivalence=/tmp,%S/Inputs %S/Inputs/mcdc-general.cpp -format html -o %t.html.dir +// RUN: FileCheck -check-prefix=HTML -input-file=%t.html.dir/coverage/tmp/mcdc-general.cpp.html %s +// HTML-COUNT-4: MC/DC Decision Region ( + +// RUN: FileCheck -check-prefix HTML-INDEX -input-file %t.html.dir/index.html %s +// HTML-INDEX-LABEL: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX: +// HTML-INDEX:
FilenameFunction CoverageLine CoverageRegion CoverageBranch CoverageMC/DC +// HTML-INDEX: 100.00% (2/2) +// HTML-INDEX: 100.00% (20/20) +// HTML-INDEX: 100.00% (26/26) +// HTML-INDEX: 91.67% (22/24) +// HTML-INDEX: 83.33% (10/12) +// HTML-INDEX: Totals + +Instructions for regenerating the test: + +# cd %S/Inputs +cp mcdc-general.cpp /tmp + +clang -fcoverage-mcdc -fprofile-instr-generate -fcoverage-compilation-dir=. \ + -fcoverage-mapping /tmp/mcdc-general.cpp -o /tmp/mcdc-general.o + +mv /tmp/mcdc-general.o %S/Inputs diff --git a/llvm/tools/llvm-cov/CodeCoverage.cpp b/llvm/tools/llvm-cov/CodeCoverage.cpp --- a/llvm/tools/llvm-cov/CodeCoverage.cpp +++ b/llvm/tools/llvm-cov/CodeCoverage.cpp @@ -105,6 +105,11 @@ const MemoryBuffer &File, CoverageData &CoverageInfo); + /// Create source views for the MCDC records. + void attachMCDCSubViews(SourceCoverageView &View, StringRef SourceName, + ArrayRef MCDCRecords, + const MemoryBuffer &File, CoverageData &CoverageInfo); + /// Create the source view of a particular function. std::unique_ptr createFunctionView(const FunctionRecord &Function, @@ -352,6 +357,37 @@ } } +void CodeCoverageTool::attachMCDCSubViews(SourceCoverageView &View, + StringRef SourceName, + ArrayRef MCDCRecords, + const MemoryBuffer &File, + CoverageData &CoverageInfo) { + if (!ViewOpts.ShowMCDC) + return; + + const auto *NextRecord = MCDCRecords.begin(); + const auto *EndRecord = MCDCRecords.end(); + + // Group and process MCDC records that have the same line number into the + // same subview. + while (NextRecord != EndRecord) { + std::vector ViewMCDCRecords; + unsigned CurrentLine = NextRecord->getDecisionRegion().LineEnd; + + while (NextRecord != EndRecord && + CurrentLine == NextRecord->getDecisionRegion().LineEnd) { + ViewMCDCRecords.push_back(*NextRecord++); + } + + if (ViewMCDCRecords.empty()) + continue; + + auto SubView = SourceCoverageView::create(SourceName, File, ViewOpts, + std::move(CoverageInfo)); + View.addMCDCRecord(CurrentLine, ViewMCDCRecords, std::move(SubView)); + } +} + std::unique_ptr CodeCoverageTool::createFunctionView(const FunctionRecord &Function, const CoverageMapping &Coverage) { @@ -364,12 +400,15 @@ auto Branches = FunctionCoverage.getBranches(); auto Expansions = FunctionCoverage.getExpansions(); + auto MCDCRecords = FunctionCoverage.getMCDCRecords(); auto View = SourceCoverageView::create(DC.demangle(Function.Name), SourceBuffer.get(), ViewOpts, std::move(FunctionCoverage)); attachExpansionSubViews(*View, Expansions, Coverage); attachBranchSubViews(*View, DC.demangle(Function.Name), Branches, SourceBuffer.get(), FunctionCoverage); + attachMCDCSubViews(*View, DC.demangle(Function.Name), MCDCRecords, + SourceBuffer.get(), FunctionCoverage); return View; } @@ -386,11 +425,14 @@ auto Branches = FileCoverage.getBranches(); auto Expansions = FileCoverage.getExpansions(); + auto MCDCRecords = FileCoverage.getMCDCRecords(); auto View = SourceCoverageView::create(SourceFile, SourceBuffer.get(), ViewOpts, std::move(FileCoverage)); attachExpansionSubViews(*View, Expansions, Coverage); attachBranchSubViews(*View, SourceFile, Branches, SourceBuffer.get(), FileCoverage); + attachMCDCSubViews(*View, SourceFile, MCDCRecords, SourceBuffer.get(), + FileCoverage); if (!ViewOpts.ShowFunctionInstantiations) return View; @@ -408,11 +450,14 @@ auto SubViewCoverage = Coverage.getCoverageForFunction(*Function); auto SubViewExpansions = SubViewCoverage.getExpansions(); auto SubViewBranches = SubViewCoverage.getBranches(); + auto SubViewMCDCRecords = SubViewCoverage.getMCDCRecords(); SubView = SourceCoverageView::create( Funcname, SourceBuffer.get(), ViewOpts, std::move(SubViewCoverage)); attachExpansionSubViews(*SubView, SubViewExpansions, Coverage); attachBranchSubViews(*SubView, SourceFile, SubViewBranches, SourceBuffer.get(), SubViewCoverage); + attachMCDCSubViews(*SubView, SourceFile, SubViewMCDCRecords, + SourceBuffer.get(), SubViewCoverage); } unsigned FileID = Function->CountedRegions.front().FileID; @@ -752,6 +797,10 @@ cl::desc("Show branch condition statistics in summary table"), cl::init(true)); + cl::opt MCDCSummary("show-mcdc-summary", cl::Optional, + cl::desc("Show MCDC statistics in summary table"), + cl::init(false)); + cl::opt InstantiationSummary( "show-instantiation-summary", cl::Optional, cl::desc("Show instantiation statistics in summary table")); @@ -923,6 +972,7 @@ ::exit(0); } + ViewOpts.ShowMCDCSummary = MCDCSummary; ViewOpts.ShowBranchSummary = BranchSummary; ViewOpts.ShowRegionSummary = RegionSummary; ViewOpts.ShowInstantiationSummary = InstantiationSummary; @@ -968,6 +1018,11 @@ "percent", "Show True/False percent")), cl::init(CoverageViewOptions::BranchOutputType::Off)); + cl::opt ShowMCDC( + "show-mcdc", cl::Optional, + cl::desc("Show the MCDC Coverage for each applicable boolean expression"), + cl::cat(ViewCategory)); + cl::opt ShowBestLineRegionsCounts( "show-line-counts-or-regions", cl::Optional, cl::desc("Show the execution counts for each line, or the execution " @@ -1063,6 +1118,7 @@ ViewOpts.ShowExpandedRegions = ShowExpansions; ViewOpts.ShowBranchCounts = ShowBranches == CoverageViewOptions::BranchOutputType::Count; + ViewOpts.ShowMCDC = ShowMCDC; ViewOpts.ShowBranchPercents = ShowBranches == CoverageViewOptions::BranchOutputType::Percent; ViewOpts.ShowFunctionInstantiations = ShowInstantiations; diff --git a/llvm/tools/llvm-cov/CoverageExporterJson.cpp b/llvm/tools/llvm-cov/CoverageExporterJson.cpp --- a/llvm/tools/llvm-cov/CoverageExporterJson.cpp +++ b/llvm/tools/llvm-cov/CoverageExporterJson.cpp @@ -20,6 +20,8 @@ // -- File: dict => Coverage for a single file // -- Branches: array => List of Branches in the file // -- Branch: dict => Describes a branch of the file with counters +// -- MCDC Records: array => List of MCDC records in the file +// -- MCDC Values: array => List of T/F covered condition values // -- Segments: array => List of Segments contained in the file // -- Segment: dict => Describes a segment of the file with a counter // -- Expansions: array => List of expansion records @@ -34,6 +36,7 @@ // -- FunctionCoverage: dict => Object summarizing function coverage // -- RegionCoverage: dict => Object summarizing region coverage // -- BranchCoverage: dict => Object summarizing branch coverage +// -- MCDCCoverage: dict => Object summarizing MC/DC coverage // -- Functions: array => List of objects describing coverage for functions // -- Function: dict => Coverage info for a single function // -- Filenames: array => List of filenames that the function relates to @@ -43,6 +46,7 @@ // -- InstantiationCoverage: dict => Object summarizing inst. coverage // -- RegionCoverage: dict => Object summarizing region coverage // -- BranchCoverage: dict => Object summarizing branch coverage +// -- MCDCCoverage: dict => Object summarizing MC/DC coverage // //===----------------------------------------------------------------------===// @@ -97,6 +101,20 @@ Region.ExpandedFileID, int64_t(Region.Kind)}); } +json::Array gatherConditions(const coverage::MCDCRecord &Record) { + json::Array Conditions; + for (unsigned c = 0; c < Record.getNumConditions(); c++) + Conditions.push_back(Record.isConditionIndependencePairCovered(c)); + return Conditions; +} + +json::Array renderMCDCRecord(const coverage::MCDCRecord &Record) { + const llvm::coverage::CounterMappingRegion &CMR = Record.getDecisionRegion(); + return json::Array({CMR.LineStart, CMR.ColumnStart, CMR.LineEnd, + CMR.ColumnEnd, CMR.ExpandedFileID, int64_t(CMR.Kind), + gatherConditions(Record)}); +} + json::Array renderRegions(ArrayRef Regions) { json::Array RegionArray; for (const auto &Region : Regions) @@ -112,6 +130,13 @@ return RegionArray; } +json::Array renderMCDCRecords(ArrayRef Records) { + json::Array RecordArray; + for (auto &Record : Records) + RecordArray.push_back(renderMCDCRecord(Record)); + return RecordArray; +} + std::vector collectNestedBranches(const coverage::CoverageMapping &Coverage, ArrayRef Expansions) { @@ -178,7 +203,14 @@ {"covered", int64_t(Summary.BranchCoverage.getCovered())}, {"notcovered", int64_t(Summary.BranchCoverage.getNumBranches() - Summary.BranchCoverage.getCovered())}, - {"percent", Summary.BranchCoverage.getPercentCovered()}})}}); + {"percent", Summary.BranchCoverage.getPercentCovered()}})}, + {"mcdc", + json::Object( + {{"count", int64_t(Summary.MCDCCoverage.getNumPairs())}, + {"covered", int64_t(Summary.MCDCCoverage.getCoveredPairs())}, + {"notcovered", int64_t(Summary.MCDCCoverage.getNumPairs() - + Summary.MCDCCoverage.getCoveredPairs())}, + {"percent", Summary.MCDCCoverage.getPercentCovered()}})}}); } json::Array renderFileExpansions(const coverage::CoverageMapping &Coverage, @@ -206,6 +238,14 @@ return BranchArray; } +json::Array renderFileMCDC(const coverage::CoverageData &FileCoverage, + const FileCoverageSummary &FileReport) { + json::Array MCDCRecordArray; + for (const auto &Record : FileCoverage.getMCDCRecords()) + MCDCRecordArray.push_back(renderMCDCRecord(Record)); + return MCDCRecordArray; +} + json::Object renderFile(const coverage::CoverageMapping &Coverage, const std::string &Filename, const FileCoverageSummary &FileReport, @@ -216,6 +256,7 @@ auto FileCoverage = Coverage.getCoverageForFile(Filename); File["segments"] = renderFileSegments(FileCoverage, FileReport); File["branches"] = renderFileBranches(FileCoverage, FileReport); + File["mcdc_records"] = renderFileMCDC(FileCoverage, FileReport); if (!Options.SkipExpansions) { File["expansions"] = renderFileExpansions(Coverage, FileCoverage, FileReport); @@ -264,6 +305,7 @@ {"count", clamp_uint64_to_int64(F.ExecutionCount)}, {"regions", renderRegions(F.CountedRegions)}, {"branches", renderBranchRegions(F.CountedBranchRegions)}, + {"mcdc_records", renderMCDCRecords(F.MCDCRecords)}, {"filenames", json::Array(F.Filenames)}})); return FunctionArray; } diff --git a/llvm/tools/llvm-cov/CoverageReport.cpp b/llvm/tools/llvm-cov/CoverageReport.cpp --- a/llvm/tools/llvm-cov/CoverageReport.cpp +++ b/llvm/tools/llvm-cov/CoverageReport.cpp @@ -86,9 +86,9 @@ } // Specify the default column widths. -size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10, 16, - 16, 10, 12, 18, 10, 12, 18, 10}; -size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8}; +size_t FileReportColumns[] = {25, 12, 18, 10, 12, 18, 10, 16, 16, 10, + 12, 18, 10, 12, 18, 10, 20, 21, 10}; +size_t FunctionReportColumns[] = {25, 10, 8, 8, 10, 8, 8, 10, 8, 8, 20, 8, 8}; /// Adjust column widths to fit long file paths and function names. void adjustColumnWidths(ArrayRef Files, @@ -291,6 +291,22 @@ OS << column("-", FileReportColumns[15], Column::RightAlignment); } + if (Options.ShowMCDCSummary) { + OS << format("%*u", FileReportColumns[16], + (unsigned)File.MCDCCoverage.getNumPairs()); + Options.colored_ostream(OS, LineCoverageColor) + << format("%*u", FileReportColumns[17], + (unsigned)(File.MCDCCoverage.getNumPairs() - + File.MCDCCoverage.getCoveredPairs())); + if (File.MCDCCoverage.getNumPairs()) + Options.colored_ostream(OS, LineCoverageColor) + << format("%*.2f", FileReportColumns[18] - 1, + File.MCDCCoverage.getPercentCovered()) + << '%'; + else + OS << column("-", FileReportColumns[18], Column::RightAlignment); + } + OS << "\n"; } @@ -338,6 +354,19 @@ Function.BranchCoverage.getPercentCovered()) << '%'; } + if (Options.ShowMCDCSummary) { + OS << format("%*u", FunctionReportColumns[10], + (unsigned)Function.MCDCCoverage.getNumPairs()); + Options.colored_ostream(OS, LineCoverageColor) + << format("%*u", FunctionReportColumns[11], + (unsigned)(Function.MCDCCoverage.getNumPairs() - + Function.MCDCCoverage.getCoveredPairs())); + Options.colored_ostream( + OS, determineCoveragePercentageColor(Function.MCDCCoverage)) + << format("%*.2f", FunctionReportColumns[12] - 1, + Function.MCDCCoverage.getPercentCovered()) + << '%'; + } OS << "\n"; } @@ -370,6 +399,11 @@ OS << column("Branches", FunctionReportColumns[7], Column::RightAlignment) << column("Miss", FunctionReportColumns[8], Column::RightAlignment) << column("Cover", FunctionReportColumns[9], Column::RightAlignment); + if (Options.ShowMCDCSummary) + OS << column("MC/DC Conditions", FunctionReportColumns[10], + Column::RightAlignment) + << column("Miss", FunctionReportColumns[11], Column::RightAlignment) + << column("Cover", FunctionReportColumns[12], Column::RightAlignment); OS << "\n"; renderDivider(FunctionReportColumns, OS); OS << "\n"; @@ -380,6 +414,7 @@ Totals.RegionCoverage += Function.RegionCoverage; Totals.LineCoverage += Function.LineCoverage; Totals.BranchCoverage += Function.BranchCoverage; + Totals.MCDCCoverage += Function.MCDCCoverage; render(Function, DC, OS); } if (Totals.ExecutionCount) { @@ -502,6 +537,12 @@ << column("Missed Branches", FileReportColumns[14], Column::RightAlignment) << column("Cover", FileReportColumns[15], Column::RightAlignment); + if (Options.ShowMCDCSummary) + OS << column("MC/DC Conditions", FileReportColumns[16], + Column::RightAlignment) + << column("Missed Conditions", FileReportColumns[17], + Column::RightAlignment) + << column("Cover", FileReportColumns[18], Column::RightAlignment); OS << "\n"; renderDivider(FileReportColumns, OS); OS << "\n"; diff --git a/llvm/tools/llvm-cov/CoverageSummaryInfo.h b/llvm/tools/llvm-cov/CoverageSummaryInfo.h --- a/llvm/tools/llvm-cov/CoverageSummaryInfo.h +++ b/llvm/tools/llvm-cov/CoverageSummaryInfo.h @@ -142,6 +142,47 @@ } }; +/// Provides information about MC/DC coverage for a function/file. +class MCDCCoverageInfo { + /// The number of Independence Pairs that were covered. + size_t CoveredPairs; + + /// The total number of Independence Pairs in a function/file. + size_t NumPairs; + +public: + MCDCCoverageInfo() : CoveredPairs(0), NumPairs(0) {} + + MCDCCoverageInfo(size_t CoveredPairs, size_t NumPairs) + : CoveredPairs(CoveredPairs), NumPairs(NumPairs) { + assert(CoveredPairs <= NumPairs && "Covered pairs over-counted"); + } + + MCDCCoverageInfo &operator+=(const MCDCCoverageInfo &RHS) { + CoveredPairs += RHS.CoveredPairs; + NumPairs += RHS.NumPairs; + return *this; + } + + void merge(const MCDCCoverageInfo &RHS) { + CoveredPairs = std::max(CoveredPairs, RHS.CoveredPairs); + NumPairs = std::max(NumPairs, RHS.NumPairs); + } + + size_t getCoveredPairs() const { return CoveredPairs; } + + size_t getNumPairs() const { return NumPairs; } + + bool isFullyCovered() const { return CoveredPairs == NumPairs; } + + double getPercentCovered() const { + assert(CoveredPairs <= NumPairs && "Covered pairs over-counted"); + if (NumPairs == 0) + return 0.0; + return double(CoveredPairs) / double(NumPairs) * 100.0; + } +}; + /// Provides information about function coverage for a file. class FunctionCoverageInfo { /// The number of functions that were executed. @@ -189,6 +230,7 @@ RegionCoverageInfo RegionCoverage; LineCoverageInfo LineCoverage; BranchCoverageInfo BranchCoverage; + MCDCCoverageInfo MCDCCoverage; FunctionCoverageSummary(const std::string &Name) : Name(Name), ExecutionCount(0) {} @@ -196,10 +238,11 @@ FunctionCoverageSummary(const std::string &Name, uint64_t ExecutionCount, const RegionCoverageInfo &RegionCoverage, const LineCoverageInfo &LineCoverage, - const BranchCoverageInfo &BranchCoverage) + const BranchCoverageInfo &BranchCoverage, + const MCDCCoverageInfo &MCDCCoverage) : Name(Name), ExecutionCount(ExecutionCount), RegionCoverage(RegionCoverage), LineCoverage(LineCoverage), - BranchCoverage(BranchCoverage) {} + BranchCoverage(BranchCoverage), MCDCCoverage(MCDCCoverage) {} /// Compute the code coverage summary for the given function coverage /// mapping record. @@ -219,6 +262,7 @@ RegionCoverageInfo RegionCoverage; LineCoverageInfo LineCoverage; BranchCoverageInfo BranchCoverage; + MCDCCoverageInfo MCDCCoverage; FunctionCoverageInfo FunctionCoverage; FunctionCoverageInfo InstantiationCoverage; @@ -230,6 +274,7 @@ LineCoverage += RHS.LineCoverage; FunctionCoverage += RHS.FunctionCoverage; BranchCoverage += RHS.BranchCoverage; + MCDCCoverage += RHS.MCDCCoverage; InstantiationCoverage += RHS.InstantiationCoverage; return *this; } @@ -238,6 +283,7 @@ RegionCoverage += Function.RegionCoverage; LineCoverage += Function.LineCoverage; BranchCoverage += Function.BranchCoverage; + MCDCCoverage += Function.MCDCCoverage; FunctionCoverage.addFunction(/*Covered=*/Function.ExecutionCount > 0); } diff --git a/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp b/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp --- a/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp +++ b/llvm/tools/llvm-cov/CoverageSummaryInfo.cpp @@ -44,6 +44,21 @@ } } +static std::pair +sumMCDCPairs(const ArrayRef &Records) { + 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)) + ++NumPairs; + if (Record.isConditionIndependencePairCovered(C)) + ++CoveredPairs; + } + } + return {NumPairs, CoveredPairs}; +} + FunctionCoverageSummary FunctionCoverageSummary::get(const CoverageMapping &CM, const coverage::FunctionRecord &Function) { @@ -73,11 +88,15 @@ sumBranches(NumBranches, CoveredBranches, CD.getBranches()); sumBranchExpansions(NumBranches, CoveredBranches, CM, CD.getExpansions()); + size_t NumPairs = 0, CoveredPairs = 0; + std::tie(NumPairs, CoveredPairs) = sumMCDCPairs(CD.getMCDCRecords()); + return FunctionCoverageSummary( Function.Name, Function.ExecutionCount, RegionCoverageInfo(CoveredRegions, NumCodeRegions), LineCoverageInfo(CoveredLines, NumLines), - BranchCoverageInfo(CoveredBranches, NumBranches)); + BranchCoverageInfo(CoveredBranches, NumBranches), + MCDCCoverageInfo(CoveredPairs, NumPairs)); } FunctionCoverageSummary @@ -97,10 +116,12 @@ Summary.RegionCoverage = Summaries[0].RegionCoverage; Summary.LineCoverage = Summaries[0].LineCoverage; Summary.BranchCoverage = Summaries[0].BranchCoverage; + Summary.MCDCCoverage = Summaries[0].MCDCCoverage; for (const auto &FCS : Summaries.drop_front()) { Summary.RegionCoverage.merge(FCS.RegionCoverage); Summary.LineCoverage.merge(FCS.LineCoverage); Summary.BranchCoverage.merge(FCS.BranchCoverage); + Summary.MCDCCoverage.merge(FCS.MCDCCoverage); } return Summary; } diff --git a/llvm/tools/llvm-cov/CoverageViewOptions.h b/llvm/tools/llvm-cov/CoverageViewOptions.h --- a/llvm/tools/llvm-cov/CoverageViewOptions.h +++ b/llvm/tools/llvm-cov/CoverageViewOptions.h @@ -30,12 +30,14 @@ bool ShowLineNumbers; bool ShowLineStats; bool ShowRegionMarkers; + bool ShowMCDC; bool ShowBranchCounts; bool ShowBranchPercents; bool ShowExpandedRegions; bool ShowFunctionInstantiations; bool ShowFullFilenames; bool ShowBranchSummary; + bool ShowMCDCSummary; bool ShowRegionSummary; bool ShowInstantiationSummary; bool ShowDirectoryCoverage; diff --git a/llvm/tools/llvm-cov/SourceCoverageView.h b/llvm/tools/llvm-cov/SourceCoverageView.h --- a/llvm/tools/llvm-cov/SourceCoverageView.h +++ b/llvm/tools/llvm-cov/SourceCoverageView.h @@ -84,6 +84,23 @@ } }; +/// A view that represents one or more MCDC regions on a given source line. +struct MCDCView { + std::vector Records; + std::unique_ptr View; + unsigned Line; + + MCDCView(unsigned Line, ArrayRef Records, + std::unique_ptr View) + : Records(Records), View(std::move(View)), Line(Line) {} + + unsigned getLine() const { return Line; } + + friend bool operator<(const MCDCView &LHS, const MCDCView &RHS) { + return LHS.Line < RHS.Line; + } +}; + /// A file manager that handles format-aware file creation. class CoveragePrinter { public: @@ -160,6 +177,9 @@ /// A container for all branches in the source on display. std::vector BranchSubViews; + /// A container for all MCDC records in the source on display. + std::vector MCDCSubViews; + /// A container for all instantiations (e.g template functions) in the source /// on display. std::vector InstantiationSubViews; @@ -233,6 +253,10 @@ virtual void renderBranchView(raw_ostream &OS, BranchView &BRV, unsigned ViewDepth) = 0; + /// Render an MCDC view. + virtual void renderMCDCView(raw_ostream &OS, MCDCView &BRV, + unsigned ViewDepth) = 0; + /// Render \p Title, a project title if one is available, and the /// created time. virtual void renderTitle(raw_ostream &OS, StringRef CellText) = 0; @@ -283,6 +307,10 @@ void addBranch(unsigned Line, ArrayRef Regions, std::unique_ptr View); + /// Add an MCDC subview to this view. + void addMCDCRecord(unsigned Line, ArrayRef Records, + std::unique_ptr View); + /// Print the code coverage information for a specific portion of a /// source file to the output stream. void print(raw_ostream &OS, bool WholeFile, bool ShowSourceName, diff --git a/llvm/tools/llvm-cov/SourceCoverageView.cpp b/llvm/tools/llvm-cov/SourceCoverageView.cpp --- a/llvm/tools/llvm-cov/SourceCoverageView.cpp +++ b/llvm/tools/llvm-cov/SourceCoverageView.cpp @@ -178,6 +178,12 @@ BranchSubViews.emplace_back(Line, Regions, std::move(View)); } +void SourceCoverageView::addMCDCRecord( + unsigned Line, ArrayRef Records, + std::unique_ptr View) { + MCDCSubViews.emplace_back(Line, Records, std::move(View)); +} + void SourceCoverageView::addInstantiation( StringRef FunctionName, unsigned Line, std::unique_ptr View) { @@ -203,12 +209,15 @@ llvm::stable_sort(ExpansionSubViews); llvm::stable_sort(InstantiationSubViews); llvm::stable_sort(BranchSubViews); + llvm::stable_sort(MCDCSubViews); auto NextESV = ExpansionSubViews.begin(); auto EndESV = ExpansionSubViews.end(); auto NextISV = InstantiationSubViews.begin(); auto EndISV = InstantiationSubViews.end(); auto NextBRV = BranchSubViews.begin(); auto EndBRV = BranchSubViews.end(); + auto NextMSV = MCDCSubViews.begin(); + auto EndMSV = MCDCSubViews.end(); // Get the coverage information for the file. auto StartSegment = CoverageInfo.begin(); @@ -276,6 +285,11 @@ renderBranchView(OS, *NextBRV, ViewDepth + 1); RenderedSubView = true; } + for (; NextMSV != EndMSV && NextMSV->Line == LI.line_number(); ++NextMSV) { + renderViewDivider(OS, ViewDepth + 1); + renderMCDCView(OS, *NextMSV, ViewDepth + 1); + RenderedSubView = true; + } if (RenderedSubView) renderViewDivider(OS, ViewDepth + 1); renderLineSuffix(OS, ViewDepth); diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.h b/llvm/tools/llvm-cov/SourceCoverageViewHTML.h --- a/llvm/tools/llvm-cov/SourceCoverageViewHTML.h +++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.h @@ -91,6 +91,9 @@ void renderBranchView(raw_ostream &OS, BranchView &BRV, unsigned ViewDepth) override; + void renderMCDCView(raw_ostream &OS, MCDCView &BRV, + unsigned ViewDepth) override; + void renderInstantiationView(raw_ostream &OS, InstantiationView &ISV, unsigned ViewDepth) override; diff --git a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp --- a/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp +++ b/llvm/tools/llvm-cov/SourceCoverageViewHTML.cpp @@ -335,6 +335,10 @@ AddCoverageTripleToColumn(FCS.BranchCoverage.getCovered(), FCS.BranchCoverage.getNumBranches(), FCS.BranchCoverage.getPercentCovered()); + if (Opts.ShowMCDCSummary) + AddCoverageTripleToColumn(FCS.MCDCCoverage.getCoveredPairs(), + FCS.MCDCCoverage.getNumPairs(), + FCS.MCDCCoverage.getPercentCovered()); if (IsTotals) OS << tag("tr", join(Columns.begin(), Columns.end(), ""), "light-row-bold"); @@ -385,6 +389,8 @@ Columns.emplace_back(tag("td", "Region Coverage", "column-entry-bold")); if (Opts.ShowBranchSummary) Columns.emplace_back(tag("td", "Branch Coverage", "column-entry-bold")); + if (Opts.ShowMCDCSummary) + Columns.emplace_back(tag("td", "MC/DC", "column-entry-bold")); OS << tag("tr", join(Columns.begin(), Columns.end(), "")); } @@ -955,6 +961,52 @@ OS << EndExpansionDiv; } +void SourceCoverageViewHTML::renderMCDCView(raw_ostream &OS, MCDCView &MRV, + unsigned ViewDepth) { + for (auto &Record : MRV.Records) { + OS << BeginExpansionDiv; + OS << BeginPre; + OS << " MC/DC Decision Region ("; + + // Display Line + Column information. + const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion(); + std::string LineNoStr = Twine(DecisionRegion.LineStart).str(); + std::string ColNoStr = Twine(DecisionRegion.ColumnStart).str(); + std::string TargetName = "L" + LineNoStr; + OS << tag("span", + a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr), + TargetName), + "line-number") + + ") to ("; + LineNoStr = utostr(uint64_t(DecisionRegion.LineEnd)); + ColNoStr = utostr(uint64_t(DecisionRegion.ColumnEnd)); + OS << tag("span", + a("#" + TargetName, tag("span", LineNoStr + ":" + ColNoStr), + TargetName), + "line-number") + + ")\n\n"; + + // Display MC/DC Information. + OS << " Number of Conditions: " << Record.getNumConditions() << "\n"; + for (unsigned i = 0; i < Record.getNumConditions(); i++) { + OS << " " << Record.getConditionHeaderString(i); + } + OS << "\n"; + OS << " Executed MC/DC Test Vectors:\n\n "; + OS << Record.getTestVectorHeaderString(); + for (unsigned i = 0; i < Record.getNumTestVectors(); i++) + OS << Record.getTestVectorString(i); + OS << "\n"; + 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"; + OS << EndPre; + OS << EndExpansionDiv; + } + return; +} + void SourceCoverageViewHTML::renderInstantiationView(raw_ostream &OS, InstantiationView &ISV, unsigned ViewDepth) { diff --git a/llvm/tools/llvm-cov/SourceCoverageViewText.h b/llvm/tools/llvm-cov/SourceCoverageViewText.h --- a/llvm/tools/llvm-cov/SourceCoverageViewText.h +++ b/llvm/tools/llvm-cov/SourceCoverageViewText.h @@ -77,6 +77,9 @@ void renderBranchView(raw_ostream &OS, BranchView &BRV, unsigned ViewDepth) override; + void renderMCDCView(raw_ostream &OS, MCDCView &BRV, + unsigned ViewDepth) override; + void renderInstantiationView(raw_ostream &OS, InstantiationView &ISV, unsigned ViewDepth) override; diff --git a/llvm/tools/llvm-cov/SourceCoverageViewText.cpp b/llvm/tools/llvm-cov/SourceCoverageViewText.cpp --- a/llvm/tools/llvm-cov/SourceCoverageViewText.cpp +++ b/llvm/tools/llvm-cov/SourceCoverageViewText.cpp @@ -337,6 +337,57 @@ } } +void SourceCoverageViewText::renderMCDCView(raw_ostream &OS, MCDCView &MRV, + unsigned ViewDepth) { + for (auto &Record : MRV.Records) { + renderLinePrefix(OS, ViewDepth); + OS << "---> MC/DC Decision Region ("; + // Display Line + Column information. + const CounterMappingRegion &DecisionRegion = Record.getDecisionRegion(); + OS << DecisionRegion.LineStart << ":"; + OS << DecisionRegion.ColumnStart << ") to ("; + OS << DecisionRegion.LineEnd << ":"; + OS << DecisionRegion.ColumnEnd << ")\n"; + renderLinePrefix(OS, ViewDepth); + OS << "\n"; + + // Display MC/DC Information. + renderLinePrefix(OS, ViewDepth); + OS << " Number of Conditions: " << Record.getNumConditions() << "\n"; + for (unsigned i = 0; i < Record.getNumConditions(); i++) { + renderLinePrefix(OS, ViewDepth); + OS << " " << Record.getConditionHeaderString(i); + } + renderLinePrefix(OS, ViewDepth); + OS << "\n"; + renderLinePrefix(OS, ViewDepth); + OS << " Executed MC/DC Test Vectors:\n"; + renderLinePrefix(OS, ViewDepth); + OS << "\n"; + renderLinePrefix(OS, ViewDepth); + OS << " "; + OS << Record.getTestVectorHeaderString(); + for (unsigned i = 0; i < Record.getNumTestVectors(); i++) { + renderLinePrefix(OS, ViewDepth); + OS << Record.getTestVectorString(i); + } + renderLinePrefix(OS, ViewDepth); + OS << "\n"; + for (unsigned i = 0; i < Record.getNumConditions(); i++) { + renderLinePrefix(OS, ViewDepth); + OS << Record.getConditionCoverageString(i); + } + 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()) << "%\n"; + renderLinePrefix(OS, ViewDepth); + OS << "\n"; + } +} + void SourceCoverageViewText::renderInstantiationView(raw_ostream &OS, InstantiationView &ISV, unsigned ViewDepth) { diff --git a/llvm/unittests/ProfileData/CoverageMappingTest.cpp b/llvm/unittests/ProfileData/CoverageMappingTest.cpp --- a/llvm/unittests/ProfileData/CoverageMappingTest.cpp +++ b/llvm/unittests/ProfileData/CoverageMappingTest.cpp @@ -187,6 +187,25 @@ : CounterMappingRegion::makeRegion(C, FileID, LS, CS, LE, CE)); } + void addMCDCDecisionCMR(unsigned Mask, unsigned NC, StringRef File, + unsigned LS, unsigned CS, unsigned LE, unsigned CE) { + auto &Regions = InputFunctions.back().Regions; + unsigned FileID = getFileIndexForFunction(File); + Regions.push_back(CounterMappingRegion::makeDecisionRegion( + CounterMappingRegion::MCDCParameters{Mask, NC}, FileID, LS, CS, LE, + CE)); + } + + void addMCDCBranchCMR(Counter C1, Counter C2, unsigned ID, unsigned TrueID, + unsigned FalseID, StringRef File, unsigned LS, + unsigned CS, unsigned LE, unsigned CE) { + auto &Regions = InputFunctions.back().Regions; + unsigned FileID = getFileIndexForFunction(File); + Regions.push_back(CounterMappingRegion::makeBranchRegion( + C1, C2, CounterMappingRegion::MCDCParameters{0, 0, ID, TrueID, FalseID}, + FileID, LS, CS, LE, CE)); + } + void addExpansionCMR(StringRef File, StringRef ExpandedFile, unsigned LS, unsigned CS, unsigned LE, unsigned CE) { InputFunctions.back().Regions.push_back(CounterMappingRegion::makeExpansion( @@ -828,6 +847,33 @@ ASSERT_EQ(1U, Names.size()); } +// Test that MCDC bitmasks not associated with any code regions are allowed. +TEST_P(CoverageMappingTest, non_code_region_bitmask) { + // No records in profdata + + startFunction("func", 0x1234); + addCMR(Counter::getCounter(0), "file", 1, 1, 5, 5); + addCMR(Counter::getCounter(1), "file", 1, 1, 5, 5); + addCMR(Counter::getCounter(2), "file", 1, 1, 5, 5); + addCMR(Counter::getCounter(3), "file", 1, 1, 5, 5); + + addMCDCDecisionCMR(0, 2, "file", 7, 1, 7, 6); + addMCDCBranchCMR(Counter::getCounter(0), Counter::getCounter(1), 1, 2, 0, + "file", 7, 2, 7, 3); + addMCDCBranchCMR(Counter::getCounter(2), Counter::getCounter(3), 2, 0, 0, + "file", 7, 4, 7, 5); + + EXPECT_THAT_ERROR(loadCoverageMapping(), Succeeded()); + + std::vector Names; + for (const auto &Func : LoadedCoverage->getCoveredFunctions()) { + Names.push_back(Func.Name); + ASSERT_EQ(2U, Func.CountedBranchRegions.size()); + ASSERT_EQ(1U, Func.MCDCRecords.size()); + } + ASSERT_EQ(1U, Names.size()); +} + TEST_P(CoverageMappingTest, strip_filename_prefix) { ProfileWriter.addRecord({"file1:func", 0x1234, {0}}, Err);