Skip to content

Commit 2286789

Browse files
authored
[clang][CoverageMapping] Refactor setting MC/DC True/False Condition IDs (#78202)
Clean-up of the algorithm that assigns MC/DC True/False control-flow condition IDs when constructing an MC/DC decision region. This patch creates a common API for setting/getting the condition IDs, making the binary logical operator visitor functions much cleaner. This patch also fixes issue #77873 in which a record's control flow map can be malformed due to an incorrect calculation of the True/False condition IDs.
1 parent 0fc5f4b commit 2286789

File tree

2 files changed

+179
-119
lines changed

2 files changed

+179
-119
lines changed

clang/lib/CodeGen/CoverageMappingGen.cpp

Lines changed: 102 additions & 119 deletions
Original file line numberDiff line numberDiff line change
@@ -573,6 +573,11 @@ struct EmptyCoverageMappingBuilder : public CoverageMappingBuilder {
573573
/// creation.
574574
struct MCDCCoverageBuilder {
575575

576+
struct DecisionIDPair {
577+
MCDCConditionID TrueID = 0;
578+
MCDCConditionID FalseID = 0;
579+
};
580+
576581
/// The AST walk recursively visits nested logical-AND or logical-OR binary
577582
/// operator nodes and then visits their LHS and RHS children nodes. As this
578583
/// happens, the algorithm will assign IDs to each operator's LHS and RHS side
@@ -616,14 +621,14 @@ struct MCDCCoverageBuilder {
616621
///
617622
/// A node ID of '0' always means MC/DC isn't being tracked.
618623
///
619-
/// As the AST walk proceeds recursively, the algorithm will also use stacks
624+
/// As the AST walk proceeds recursively, the algorithm will also use a stack
620625
/// to track the IDs of logical-AND and logical-OR operations on the RHS so
621626
/// that it can be determined which nodes are executed next, depending on how
622627
/// a LHS or RHS of a logical-AND or logical-OR is evaluated. This
623628
/// information relies on the assigned IDs and are embedded within the
624629
/// coverage region IDs of each branch region associated with a leaf-level
625630
/// condition. This information helps the visualization tool reconstruct all
626-
/// possible test vectors for the purposes of MC/DC analysis. if a "next" node
631+
/// possible test vectors for the purposes of MC/DC analysis. If a "next" node
627632
/// ID is '0', it means it's the end of the test vector. The following rules
628633
/// are used:
629634
///
@@ -663,54 +668,40 @@ struct MCDCCoverageBuilder {
663668
private:
664669
CodeGenModule &CGM;
665670

666-
llvm::SmallVector<MCDCConditionID> AndRHS;
667-
llvm::SmallVector<MCDCConditionID> OrRHS;
668-
llvm::SmallVector<const BinaryOperator *> NestLevel;
671+
llvm::SmallVector<DecisionIDPair> DecisionStack;
669672
llvm::DenseMap<const Stmt *, MCDCConditionID> &CondIDs;
670673
llvm::DenseMap<const Stmt *, unsigned> &MCDCBitmapMap;
671674
MCDCConditionID NextID = 1;
672675
bool NotMapped = false;
673676

677+
/// Represent a sentinel value of [0,0] for the bottom of DecisionStack.
678+
static constexpr DecisionIDPair DecisionStackSentinel{0, 0};
679+
674680
/// Is this a logical-AND operation?
675681
bool isLAnd(const BinaryOperator *E) const {
676682
return E->getOpcode() == BO_LAnd;
677683
}
678684

679-
/// Push an ID onto the corresponding RHS stack.
680-
void pushRHS(const BinaryOperator *E) {
681-
llvm::SmallVector<MCDCConditionID> &rhs = isLAnd(E) ? AndRHS : OrRHS;
682-
rhs.push_back(CondIDs[CodeGenFunction::stripCond(E->getRHS())]);
683-
}
684-
685-
/// Pop an ID from the corresponding RHS stack.
686-
void popRHS(const BinaryOperator *E) {
687-
llvm::SmallVector<MCDCConditionID> &rhs = isLAnd(E) ? AndRHS : OrRHS;
688-
if (!rhs.empty())
689-
rhs.pop_back();
690-
}
691-
692-
/// If the expected ID is on top, pop it off the corresponding RHS stack.
693-
void popRHSifTop(const BinaryOperator *E) {
694-
if (!OrRHS.empty() && CondIDs[E] == OrRHS.back())
695-
OrRHS.pop_back();
696-
else if (!AndRHS.empty() && CondIDs[E] == AndRHS.back())
697-
AndRHS.pop_back();
698-
}
699-
700685
public:
701686
MCDCCoverageBuilder(CodeGenModule &CGM,
702687
llvm::DenseMap<const Stmt *, MCDCConditionID> &CondIDMap,
703688
llvm::DenseMap<const Stmt *, unsigned> &MCDCBitmapMap)
704-
: CGM(CGM), CondIDs(CondIDMap), MCDCBitmapMap(MCDCBitmapMap) {}
689+
: CGM(CGM), DecisionStack(1, DecisionStackSentinel), CondIDs(CondIDMap),
690+
MCDCBitmapMap(MCDCBitmapMap) {}
705691

706-
/// Return the ID of the RHS of the next, upper nest-level logical-OR.
707-
MCDCConditionID getNextLOrCondID() const {
708-
return OrRHS.empty() ? 0 : OrRHS.back();
709-
}
692+
/// Return whether the build of the control flow map is at the top-level
693+
/// (root) of a logical operator nest in a boolean expression prior to the
694+
/// assignment of condition IDs.
695+
bool isIdle() const { return (NextID == 1 && !NotMapped); }
696+
697+
/// Return whether any IDs have been assigned in the build of the control
698+
/// flow map, indicating that the map is being generated for this boolean
699+
/// expression.
700+
bool isBuilding() const { return (NextID > 1); }
710701

711-
/// Return the ID of the RHS of the next, upper nest-level logical-AND.
712-
MCDCConditionID getNextLAndCondID() const {
713-
return AndRHS.empty() ? 0 : AndRHS.back();
702+
/// Set the given condition's ID.
703+
void setCondID(const Expr *Cond, MCDCConditionID ID) {
704+
CondIDs[CodeGenFunction::stripCond(Cond)] = ID;
714705
}
715706

716707
/// Return the ID of a given condition.
@@ -722,6 +713,9 @@ struct MCDCCoverageBuilder {
722713
return I->second;
723714
}
724715

716+
/// Return the LHS Decision ([0,0] if not set).
717+
const DecisionIDPair &back() const { return DecisionStack.back(); }
718+
725719
/// Push the binary operator statement to track the nest level and assign IDs
726720
/// to the operator's LHS and RHS. The RHS may be a larger subtree that is
727721
/// broken up on successive levels.
@@ -730,68 +724,67 @@ struct MCDCCoverageBuilder {
730724
return;
731725

732726
// If binary expression is disqualified, don't do mapping.
733-
if (NestLevel.empty() &&
734-
!MCDCBitmapMap.contains(CodeGenFunction::stripCond(E)))
727+
if (!isBuilding() && !MCDCBitmapMap.contains(CodeGenFunction::stripCond(E)))
735728
NotMapped = true;
736729

737-
// Push Stmt on 'NestLevel' stack to keep track of nest location.
738-
NestLevel.push_back(E);
739-
740730
// Don't go any further if we don't need to map condition IDs.
741731
if (NotMapped)
742732
return;
743733

734+
const DecisionIDPair &ParentDecision = DecisionStack.back();
735+
744736
// If the operator itself has an assigned ID, this means it represents a
745-
// larger subtree. In this case, pop its ID out of the RHS stack and
746-
// assign that ID to its LHS node. Its RHS will receive a new ID.
747-
if (CondIDs.contains(CodeGenFunction::stripCond(E))) {
748-
// If Stmt has an ID, assign its ID to LHS
749-
CondIDs[CodeGenFunction::stripCond(E->getLHS())] = CondIDs[E];
750-
751-
// Since the operator's LHS assumes the operator's same ID, pop the
752-
// operator from the RHS stack so that if LHS short-circuits, it won't be
753-
// incorrectly re-used as the node executed next.
754-
popRHSifTop(E);
755-
} else {
756-
// Otherwise, assign ID+1 to LHS.
757-
CondIDs[CodeGenFunction::stripCond(E->getLHS())] = NextID++;
758-
}
737+
// larger subtree. In this case, assign that ID to its LHS node. Its RHS
738+
// will receive a new ID below. Otherwise, assign ID+1 to LHS.
739+
if (CondIDs.contains(CodeGenFunction::stripCond(E)))
740+
setCondID(E->getLHS(), getCondID(E));
741+
else
742+
setCondID(E->getLHS(), NextID++);
759743

760-
// Assign ID+1 to RHS.
761-
CondIDs[CodeGenFunction::stripCond(E->getRHS())] = NextID++;
744+
// Assign a ID+1 for the RHS.
745+
MCDCConditionID RHSid = NextID++;
746+
setCondID(E->getRHS(), RHSid);
762747

763-
// Push ID of Stmt's RHS so that LHS nodes know about it
764-
pushRHS(E);
748+
// Push the LHS decision IDs onto the DecisionStack.
749+
if (isLAnd(E))
750+
DecisionStack.push_back({RHSid, ParentDecision.FalseID});
751+
else
752+
DecisionStack.push_back({ParentDecision.TrueID, RHSid});
753+
}
754+
755+
/// Pop and return the LHS Decision ([0,0] if not set).
756+
DecisionIDPair pop() {
757+
if (!CGM.getCodeGenOpts().MCDCCoverage || NotMapped)
758+
return DecisionStack.front();
759+
760+
assert(DecisionStack.size() > 1);
761+
DecisionIDPair D = DecisionStack.back();
762+
DecisionStack.pop_back();
763+
return D;
765764
}
766765

767-
/// Pop the binary operator from the next level. If the walk is at the top of
768-
/// the next, assign the total number of conditions.
769-
unsigned popAndReturnCondCount(const BinaryOperator *E) {
766+
/// Return the total number of conditions and reset the state. The number of
767+
/// conditions is zero if the expression isn't mapped.
768+
unsigned getTotalConditionsAndReset(const BinaryOperator *E) {
770769
if (!CGM.getCodeGenOpts().MCDCCoverage)
771770
return 0;
772771

773-
unsigned TotalConds = 0;
774-
775-
// Pop Stmt from 'NestLevel' stack.
776-
assert(NestLevel.back() == E);
777-
NestLevel.pop_back();
772+
assert(!isIdle());
773+
assert(DecisionStack.size() == 1);
778774

779775
// Reset state if not doing mapping.
780-
if (NestLevel.empty() && NotMapped) {
776+
if (NotMapped) {
781777
NotMapped = false;
778+
assert(NextID == 1);
782779
return 0;
783780
}
784781

785-
// Pop RHS ID.
786-
popRHS(E);
782+
// Set number of conditions and reset.
783+
unsigned TotalConds = NextID - 1;
787784

788-
// If at the parent (NestLevel=0), set conds and reset.
789-
if (NestLevel.empty()) {
790-
TotalConds = NextID - 1;
785+
// Reset ID back to beginning.
786+
NextID = 1;
791787

792-
// Reset ID back to beginning.
793-
NextID = 1;
794-
}
795788
return TotalConds;
796789
}
797790
};
@@ -1018,13 +1011,15 @@ struct CounterCoverageMappingBuilder
10181011
return (Cond->EvaluateAsInt(Result, CVM.getCodeGenModule().getContext()));
10191012
}
10201013

1014+
using MCDCDecisionIDPair = MCDCCoverageBuilder::DecisionIDPair;
1015+
10211016
/// Create a Branch Region around an instrumentable condition for coverage
10221017
/// and add it to the function's SourceRegions. A branch region tracks a
10231018
/// "True" counter and a "False" counter for boolean expressions that
10241019
/// result in the generation of a branch.
1025-
void createBranchRegion(const Expr *C, Counter TrueCnt, Counter FalseCnt,
1026-
MCDCConditionID ID = 0, MCDCConditionID TrueID = 0,
1027-
MCDCConditionID FalseID = 0) {
1020+
void
1021+
createBranchRegion(const Expr *C, Counter TrueCnt, Counter FalseCnt,
1022+
const MCDCDecisionIDPair &IDPair = MCDCDecisionIDPair()) {
10281023
// Check for NULL conditions.
10291024
if (!C)
10301025
return;
@@ -1034,6 +1029,10 @@ struct CounterCoverageMappingBuilder
10341029
// function's SourceRegions) because it doesn't apply to any other source
10351030
// code other than the Condition.
10361031
if (CodeGenFunction::isInstrumentedCondition(C)) {
1032+
MCDCConditionID ID = MCDCBuilder.getCondID(C);
1033+
MCDCConditionID TrueID = IDPair.TrueID;
1034+
MCDCConditionID FalseID = IDPair.FalseID;
1035+
10371036
// If a condition can fold to true or false, the corresponding branch
10381037
// will be removed. Create a region with both counters hard-coded to
10391038
// zero. This allows us to visualize them in a special way.
@@ -1822,20 +1821,28 @@ struct CounterCoverageMappingBuilder
18221821
}
18231822

18241823
void VisitBinLAnd(const BinaryOperator *E) {
1825-
// Keep track of Binary Operator and assign MCDC condition IDs
1824+
bool IsRootNode = MCDCBuilder.isIdle();
1825+
1826+
// Keep track of Binary Operator and assign MCDC condition IDs.
18261827
MCDCBuilder.pushAndAssignIDs(E);
18271828

18281829
extendRegion(E->getLHS());
18291830
propagateCounts(getRegion().getCounter(), E->getLHS());
18301831
handleFileExit(getEnd(E->getLHS()));
18311832

1833+
// Track LHS True/False Decision.
1834+
const auto DecisionLHS = MCDCBuilder.pop();
1835+
18321836
// Counter tracks the right hand side of a logical and operator.
18331837
extendRegion(E->getRHS());
18341838
propagateCounts(getRegionCounter(E), E->getRHS());
18351839

1836-
// Process Binary Operator and create MCDC Decision Region if top-level
1840+
// Track RHS True/False Decision.
1841+
const auto DecisionRHS = MCDCBuilder.back();
1842+
1843+
// Create MCDC Decision Region if at top-level (root).
18371844
unsigned NumConds = 0;
1838-
if ((NumConds = MCDCBuilder.popAndReturnCondCount(E)))
1845+
if (IsRootNode && (NumConds = MCDCBuilder.getTotalConditionsAndReset(E)))
18391846
createDecisionRegion(E, getRegionBitmap(E), NumConds);
18401847

18411848
// Extract the RHS's Execution Counter.
@@ -1847,30 +1854,13 @@ struct CounterCoverageMappingBuilder
18471854
// Extract the Parent Region Counter.
18481855
Counter ParentCnt = getRegion().getCounter();
18491856

1850-
// Extract the MCDC condition IDs (returns 0 if not needed).
1851-
MCDCConditionID NextOrID = MCDCBuilder.getNextLOrCondID();
1852-
MCDCConditionID NextAndID = MCDCBuilder.getNextLAndCondID();
1853-
MCDCConditionID LHSid = MCDCBuilder.getCondID(E->getLHS());
1854-
MCDCConditionID RHSid = MCDCBuilder.getCondID(E->getRHS());
1855-
18561857
// Create Branch Region around LHS condition.
1857-
// MC/DC: For "LHS && RHS"
1858-
// - If LHS is TRUE, execution goes to the RHS.
1859-
// - If LHS is FALSE, execution goes to the LHS of the next logical-OR.
1860-
// If that does not exist, execution exits (ID == 0).
18611858
createBranchRegion(E->getLHS(), RHSExecCnt,
1862-
subtractCounters(ParentCnt, RHSExecCnt), LHSid, RHSid,
1863-
NextOrID);
1859+
subtractCounters(ParentCnt, RHSExecCnt), DecisionLHS);
18641860

18651861
// Create Branch Region around RHS condition.
1866-
// MC/DC: For "LHS && RHS"
1867-
// - If RHS is TRUE, execution goes to LHS of the next logical-AND.
1868-
// If that does not exist, execution exits (ID == 0).
1869-
// - If RHS is FALSE, execution goes to the LHS of the next logical-OR.
1870-
// If that does not exist, execution exits (ID == 0).
18711862
createBranchRegion(E->getRHS(), RHSTrueCnt,
1872-
subtractCounters(RHSExecCnt, RHSTrueCnt), RHSid,
1873-
NextAndID, NextOrID);
1863+
subtractCounters(RHSExecCnt, RHSTrueCnt), DecisionRHS);
18741864
}
18751865

18761866
// Determine whether the right side of OR operation need to be visited.
@@ -1884,20 +1874,28 @@ struct CounterCoverageMappingBuilder
18841874
}
18851875

18861876
void VisitBinLOr(const BinaryOperator *E) {
1887-
// Keep track of Binary Operator and assign MCDC condition IDs
1877+
bool IsRootNode = MCDCBuilder.isIdle();
1878+
1879+
// Keep track of Binary Operator and assign MCDC condition IDs.
18881880
MCDCBuilder.pushAndAssignIDs(E);
18891881

18901882
extendRegion(E->getLHS());
18911883
Counter OutCount = propagateCounts(getRegion().getCounter(), E->getLHS());
18921884
handleFileExit(getEnd(E->getLHS()));
18931885

1886+
// Track LHS True/False Decision.
1887+
const auto DecisionLHS = MCDCBuilder.pop();
1888+
18941889
// Counter tracks the right hand side of a logical or operator.
18951890
extendRegion(E->getRHS());
18961891
propagateCounts(getRegionCounter(E), E->getRHS());
18971892

1898-
// Process Binary Operator and create MCDC Decision Region if top-level
1893+
// Track RHS True/False Decision.
1894+
const auto DecisionRHS = MCDCBuilder.back();
1895+
1896+
// Create MCDC Decision Region if at top-level (root).
18991897
unsigned NumConds = 0;
1900-
if ((NumConds = MCDCBuilder.popAndReturnCondCount(E)))
1898+
if (IsRootNode && (NumConds = MCDCBuilder.getTotalConditionsAndReset(E)))
19011899
createDecisionRegion(E, getRegionBitmap(E), NumConds);
19021900

19031901
// Extract the RHS's Execution Counter.
@@ -1913,28 +1911,13 @@ struct CounterCoverageMappingBuilder
19131911
// Extract the Parent Region Counter.
19141912
Counter ParentCnt = getRegion().getCounter();
19151913

1916-
// Extract the MCDC condition IDs (returns 0 if not needed).
1917-
MCDCConditionID NextOrID = MCDCBuilder.getNextLOrCondID();
1918-
MCDCConditionID NextAndID = MCDCBuilder.getNextLAndCondID();
1919-
MCDCConditionID LHSid = MCDCBuilder.getCondID(E->getLHS());
1920-
MCDCConditionID RHSid = MCDCBuilder.getCondID(E->getRHS());
1921-
19221914
// Create Branch Region around LHS condition.
1923-
// MC/DC: For "LHS || RHS"
1924-
// - If LHS is TRUE, execution goes to the LHS of the next logical-AND.
1925-
// If that does not exist, execution exits (ID == 0).
1926-
// - If LHS is FALSE, execution goes to the RHS.
19271915
createBranchRegion(E->getLHS(), subtractCounters(ParentCnt, RHSExecCnt),
1928-
RHSExecCnt, LHSid, NextAndID, RHSid);
1916+
RHSExecCnt, DecisionLHS);
19291917

19301918
// Create Branch Region around RHS condition.
1931-
// MC/DC: For "LHS || RHS"
1932-
// - If RHS is TRUE, execution goes to LHS of the next logical-AND.
1933-
// If that does not exist, execution exits (ID == 0).
1934-
// - If RHS is FALSE, execution goes to the LHS of the next logical-OR.
1935-
// If that does not exist, execution exits (ID == 0).
19361919
createBranchRegion(E->getRHS(), subtractCounters(RHSExecCnt, RHSFalseCnt),
1937-
RHSFalseCnt, RHSid, NextAndID, NextOrID);
1920+
RHSFalseCnt, DecisionRHS);
19381921
}
19391922

19401923
void VisitLambdaExpr(const LambdaExpr *LE) {

0 commit comments

Comments
 (0)