Skip to content

[Coverage][MC/DC] Show uncoverable and unreachable conditions #94137

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions clang/docs/SourceBasedCodeCoverage.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
-----------------

Expand Down
4 changes: 2 additions & 2 deletions clang/lib/CodeGen/CoverageMappingGen.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
12 changes: 12 additions & 0 deletions llvm/docs/CommandGuide/llvm-cov.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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.
Expand Down
66 changes: 49 additions & 17 deletions llvm/include/llvm/ProfileData/Coverage/CoverageMapping.h
Original file line number Diff line number Diff line change
Expand Up @@ -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<CondState> with a pair of BitVector.
///
/// True False DontCare (Impossible)
Expand Down Expand Up @@ -454,19 +461,21 @@ struct MCDCRecord {
using TVPairMap = llvm::DenseMap<unsigned, TVRowPair>;
using CondIDMap = llvm::DenseMap<unsigned, unsigned>;
using LineColPairMap = llvm::DenseMap<unsigned, LineColPair>;
using ResultVector = llvm::SmallVector<CondResult>;

private:
CounterMappingRegion Region;
TestVectors TV;
std::optional<TVPairMap> 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();
}
Expand All @@ -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,
Expand Down Expand Up @@ -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<bool, float> 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<double>(Covered) / static_cast<double>(Total)) * 100.0;
return {false, 0.0};
return {
true,
(static_cast<double>(Covered) / static_cast<double>(Total)) * 100.0,
};
}

std::string getConditionHeaderString(unsigned Condition) {
Expand Down Expand Up @@ -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)) {
Expand Down Expand Up @@ -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();
}
Expand Down
Loading