Skip to content

Commit d2f589f

Browse files
author
Mandeep Singh Grang
authored
Support bounds widening for conditionals within switch statements (#805)
Added support for widening bounds for nt_array_ptr dereferences in switch statements. For example, "switch (*p) {case 'a': break;}" would widen the bounds of p inside the "case 'a'" block.
1 parent 68db5a9 commit d2f589f

File tree

3 files changed

+446
-3
lines changed

3 files changed

+446
-3
lines changed

clang/include/clang/Sema/BoundsAnalysis.h

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,30 @@ namespace clang {
116116
// block.
117117
BoundsVarTy BoundsVars;
118118

119+
// To compute In[B] we compute the intersection of Out[B*->B], where B*
120+
// are all preds of B. When there is a back edge from block B' to B (for
121+
// example in loops), the Out set for block B' will be empty when we
122+
// first enter B. As a result, the intersection operation would always
123+
// result in an empty In set for B.
124+
125+
// So to handle this, we consider the In and Out sets for all blocks to
126+
// have a default value of "Top" which indicates a set of all members of
127+
// the Gen set. In this way we ensure that the intersection does not
128+
// result in an empty set even if the Out set for a block is actually
129+
// empty.
130+
131+
// But we also need to handle the case where there is an unconditional
132+
// jump into a block (for example, as a result of a goto). In this case,
133+
// we cannot widen the bounds because we would not have checked for the
134+
// ptr dereference. So in this case we want the intersection to result in
135+
// an empty set.
136+
137+
// So we mark the In and Out sets of the Entry block as "empty".
138+
// IsInSetEmpty and IsOutSetEmpty indicate whether the In and Out sets
139+
// for a block have been marked as "empty".
140+
bool IsInSetEmpty;
141+
llvm::DenseMap<const CFGBlock *, bool> IsOutSetEmpty;
142+
119143
ElevatedCFGBlock(const CFGBlock *B) : Block(B) {}
120144
};
121145

@@ -299,6 +323,10 @@ namespace clang {
299323
// as Top.
300324
void InitInOutSets();
301325

326+
// Check if the switch case label is null.
327+
// @param[in] EB is the ElevatedCFGBlock for the current block.
328+
bool CheckIsSwitchCaseNull(ElevatedCFGBlock *EB);
329+
302330
// Compute the intersection of sets A and B.
303331
// @param[in] A is a set.
304332
// @param[in] B is a set.

clang/lib/Sema/BoundsAnalysis.cpp

Lines changed: 77 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,10 @@ void BoundsAnalysis::WidenBounds(FunctionDecl *FD) {
3737
// iterate WorkList.
3838
WorkList.append(EB);
3939
BlockMap[B] = EB;
40+
41+
// Mark the In set for the Entry block as "empty". The Out set for the
42+
// Entry block would be marked as "empty" in ComputeOutSets.
43+
EB->IsInSetEmpty = B == &Cfg->getEntry();
4044
}
4145

4246
// At this time, BlockMap only contains reachable blocks. We iterate through
@@ -83,13 +87,48 @@ void BoundsAnalysis::InitInOutSets() {
8387
}
8488
}
8589

90+
bool BoundsAnalysis::CheckIsSwitchCaseNull(ElevatedCFGBlock *EB) {
91+
if (const auto *CS = dyn_cast_or_null<CaseStmt>(EB->Block->getLabel())) {
92+
93+
// We mimic how clang (in SemaStmt.cpp) gets the value of a switch case. It
94+
// invokes EvaluateKnownConstInt and we do the same here. SemaStmt has
95+
// already extended/truncated the case value to fit the integer range and
96+
// EvaluateKnownConstInt gives us that value.
97+
llvm::APSInt LHSVal = CS->getLHS()->EvaluateKnownConstInt(Ctx);
98+
llvm::APSInt LHSZero (LHSVal.getBitWidth(), LHSVal.isUnsigned());
99+
if (llvm::APSInt::compareValues(LHSVal, LHSZero) == 0)
100+
return true;
101+
102+
// Check if the case statement is of the form "case LHS ... RHS" (a GNU
103+
// extension).
104+
if (CS->caseStmtIsGNURange()) {
105+
llvm::APSInt RHSVal = CS->getRHS()->EvaluateKnownConstInt(Ctx);
106+
llvm::APSInt RHSZero (RHSVal.getBitWidth(), RHSVal.isUnsigned());
107+
if (llvm::APSInt::compareValues(RHSVal, RHSZero) == 0)
108+
return true;
109+
110+
// Check if 0 if contained within the range [LHS, RHS].
111+
return (LHSVal <= LHSZero && RHSZero <= RHSVal) ||
112+
(LHSVal >= LHSZero && RHSZero >= RHSVal);
113+
}
114+
return false;
115+
}
116+
return true;
117+
}
118+
86119
void BoundsAnalysis::ComputeGenSets() {
87120
// If there is an edge B1->B2 and the edge condition is of the form
88121
// "if (*(p + i))" then Gen[B1] = {B2, p:i} .
89122

123+
// Here, EB is B2.
90124
for (const auto item : BlockMap) {
91125
ElevatedCFGBlock *EB = item.second;
92126

127+
// Check if this is a switch case and whether the case label is non-null.
128+
// We can only widen the bounds for a non-null case label.
129+
bool IsSwitchCaseNull = CheckIsSwitchCaseNull(EB);
130+
131+
// Iterate through all preds of EB.
93132
for (const CFGBlock *pred : EB->Block->preds()) {
94133
if (SkipBlock(pred))
95134
continue;
@@ -103,10 +142,39 @@ void BoundsAnalysis::ComputeGenSets() {
103142
// Here we have the edges (B1->B2) and (B1->B3). We can add "p:i" only
104143
// on the true edge. Which means we will add the following entry to
105144
// Gen[B1]: {B2, p:i}
106-
if (!pred->succ_empty() && *pred->succs().begin() == EB->Block)
107-
// Get the edge condition and fill the Gen set.
108-
if (Expr *E = GetTerminatorCondition(pred))
145+
146+
// Check if EB is on a true edge of pred. The false edge (including the
147+
// default case for a switch) is always the last edge in the list of
148+
// edges. So we check that EB is not on the last edge for pred.
149+
150+
// TODO: Allow bounds widening for the default case of a switch-case.
151+
// If we establish that another label in a switch statement tests for 0,
152+
// then the default case will handle non-zero case, and the bounds can be
153+
// widened there. The following github issue tracks this:
154+
// https://github.com/microsoft/checkedc-clang/issues/818.
155+
156+
if (pred->succ_size() && EB->Block != *(pred->succs().end() - 1)) {
157+
// Get the edge condition and fill the Gen set.
158+
if (Expr *E = GetTerminatorCondition(pred)) {
159+
160+
// Check if the pred ends in a switch statement.
161+
if (isa<SwitchStmt>(pred->getTerminatorStmt())) {
162+
// We can widen the bounds only if the current block has a non-null
163+
// case statement.
164+
if (IsSwitchCaseNull)
165+
continue;
166+
167+
// If the switch expression is integral, strip off the
168+
// IntegralCast.
169+
if (auto *CE = dyn_cast<CastExpr>(E)) {
170+
if (CE->getCastKind() == CastKind::CK_IntegralCast)
171+
E = CE->getSubExpr();
172+
}
173+
}
174+
109175
FillGenSet(E, BlockMap[pred], EB);
176+
}
177+
}
110178
}
111179
}
112180
}
@@ -643,6 +711,10 @@ void BoundsAnalysis::ComputeOutSets(ElevatedCFGBlock *EB,
643711

644712
EB->Out[succ] = Union(Diff, EB->Gen[succ]);
645713

714+
// The Out set on an edge is marked "empty" if the In set is marked "empty"
715+
// and the Gen set on that edge is empty.
716+
EB->IsOutSetEmpty[succ] = EB->IsInSetEmpty && !EB->Gen[succ].size();
717+
646718
if (Differ(OldOut, EB->Out[succ]))
647719
WorkList.append(BlockMap[succ]);
648720
}
@@ -666,6 +738,8 @@ Expr *BoundsAnalysis::GetTerminatorCondition(const CFGBlock *B) const {
666738
return const_cast<Expr *>(WhileS->getCond());
667739
if (const auto *ForS = dyn_cast<ForStmt>(S))
668740
return const_cast<Expr *>(ForS->getCond());
741+
if (const auto *SwitchS = dyn_cast<SwitchStmt>(S))
742+
return const_cast<Expr *>(SwitchS->getCond());
669743
}
670744
return nullptr;
671745
}

0 commit comments

Comments
 (0)