Skip to content

Commit c69e376

Browse files
author
Mandeep Singh Grang
authored
Support bounds widening for conditionals with while loops (#803)
Added support for widenening bounds for nt_array_ptr dereferences in while loops. For example, "while (*p) {}" would widen the bounds of p upon entry to the loop. To compute In[B] we compute the intersection of Out[B*->B], where B* are all preds of B. When there is a back edge from block B' to B (for example in loops), the Out set for block B' will be empty when we first enter B. As a result, the intersection operation would always result in an empty In set for B. So to handle this, we consider the In and Out sets for all blocks to have a default value of "Top" which indicates a set of all members of the Gen set. In this way we ensure that the intersection does not result in an empty set even if the Out set for a block is actually empty. But we also need to handle the case where there is an unconditional jump into a block (for example, as a result of a goto). In this case, we cannot widen the bounds because we would not have checked for the ptr dereference. So in this case we want the intersection to result in an empty set. So we initialize the In and Out sets of all blocks, except the Entry block, as "Top". Top represents the union of the Gen sets of all edges. We have chosen the offsets of ptr variables in Top to be the max unsigned int. The reason behind this is that in order to compute the actual In sets for blocks we are going to intersect the Out sets on all the incoming edges of the block. And in that case we would always pick the ptr with the smaller offset. Chosing max unsigned int also makes handling Top much easier as we do not need to explicitly store edge info.
1 parent f9bb238 commit c69e376

File tree

3 files changed

+209
-12
lines changed

3 files changed

+209
-12
lines changed

clang/include/clang/Sema/BoundsAnalysis.h

Lines changed: 27 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -132,6 +132,27 @@ namespace clang {
132132
// defined in the function.
133133
DeclSetTy NtPtrsInScope;
134134

135+
// To compute In[B] we compute the intersection of Out[B*->B], where B* are
136+
// all preds of B. When there is a back edge from block B' to B (for
137+
// example in loops), the Out set for block B' will be empty when we first
138+
// enter B. As a result, the intersection operation would always result in
139+
// an empty In set for B.
140+
141+
// So to handle this, we consider the In and Out sets for all blocks to
142+
// have a default value of "Top" which indicates a set of all members of
143+
// the Gen set. In this way we ensure that the intersection does not result
144+
// in an empty set even if the Out set for a block is actually empty.
145+
146+
// But we also need to handle the case where there is an unconditional jump
147+
// into a block (for example, as a result of a goto). In this case, we
148+
// cannot widen the bounds because we would not have checked for the ptr
149+
// dereference. So in this case we want the intersection to result in an
150+
// empty set.
151+
152+
// So we initialize the In and Out sets of all blocks, except the Entry
153+
// block, as "Top".
154+
BoundsMapTy Top;
155+
135156
public:
136157
BoundsAnalysis(Sema &S, CFG *Cfg) :
137158
S(S), Cfg(Cfg), Ctx(S.Context),
@@ -234,8 +255,8 @@ namespace clang {
234255
// @return E with casts stripped off.
235256
Expr *IgnoreCasts(const Expr *E);
236257

237-
// We do not want to run dataflow analysis on null, entry or exit blocks.
238-
// So we skip them.
258+
// We do not want to run dataflow analysis on null or exit blocks. So we
259+
// skip them.
239260
// @param[in] B is the block which may need to the skipped from dataflow
240261
// analysis.
241262
// @return Whether B should be skipped.
@@ -274,6 +295,10 @@ namespace clang {
274295
// @param[in] S is the current Stmt in the block.
275296
void FillKillSet(ElevatedCFGBlock *EB, const Stmt *S);
276297

298+
// Initialize the In and Out sets for all blocks, except the Entry block,
299+
// as Top.
300+
void InitInOutSets();
301+
277302
// Compute the intersection of sets A and B.
278303
// @param[in] A is a set.
279304
// @param[in] B is a set.

clang/lib/Sema/BoundsAnalysis.cpp

Lines changed: 52 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -26,9 +26,9 @@ void BoundsAnalysis::WidenBounds(FunctionDecl *FD) {
2626
// Note: By default, PostOrderCFGView iterates in reverse order. So we always
2727
// get a reverse post order when we iterate PostOrderCFGView.
2828
for (const CFGBlock *B : PostOrderCFGView(Cfg)) {
29-
// SkipBlock will skip all null, entry and exit blocks. PostOrderCFGView
30-
// does not contain any unreachable blocks. So at the end of this loop
31-
// BlockMap only contains reachable blocks.
29+
// SkipBlock will skip all null and exit blocks. PostOrderCFGView does not
30+
// contain any unreachable blocks. So at the end of this loop BlockMap only
31+
// contains reachable blocks.
3232
if (SkipBlock(B))
3333
continue;
3434

@@ -57,6 +57,9 @@ void BoundsAnalysis::WidenBounds(FunctionDecl *FD) {
5757
ComputeGenSets();
5858
ComputeKillSets();
5959

60+
// Initialize the In and Out sets to Top.
61+
InitInOutSets();
62+
6063
// Compute In and Out sets.
6164
while (!WorkList.empty()) {
6265
ElevatedCFGBlock *EB = WorkList.next();
@@ -67,6 +70,19 @@ void BoundsAnalysis::WidenBounds(FunctionDecl *FD) {
6770
}
6871
}
6972

73+
void BoundsAnalysis::InitInOutSets() {
74+
for (const auto item : BlockMap) {
75+
ElevatedCFGBlock *EB = item.second;
76+
77+
if (EB->Block == &Cfg->getEntry())
78+
continue;
79+
80+
EB->In = Top;
81+
for (const CFGBlock *succ : EB->Block->succs())
82+
EB->Out[succ] = Top;
83+
}
84+
}
85+
7086
void BoundsAnalysis::ComputeGenSets() {
7187
// If there is an edge B1->B2 and the edge condition is of the form
7288
// "if (*(p + i))" then Gen[B1] = {B2, p:i} .
@@ -307,7 +323,17 @@ void BoundsAnalysis::FillGenSetAndGetBoundsVars(const Expr *E,
307323
UpperOffset = DerefOffset.ssub_ov(UpperOffset, Overflow);
308324
if (Overflow)
309325
continue;
326+
310327
EB->Gen[SuccEB->Block][V] = UpperOffset.getLimitedValue();
328+
329+
// Top represents the union of the Gen sets of all edges. We have chosen
330+
// the offsets of ptr variables in Top to be the max unsigned int. The
331+
// reason behind this is that in order to compute the actual In sets for
332+
// blocks we are going to intersect the Out sets on all the incoming edges
333+
// of the block. And in that case we would always pick the ptr with the
334+
// smaller offset. Chosing max unsigned int also makes handling Top much
335+
// easier as we do not need to explicitly store edge info.
336+
Top[V] = std::numeric_limits<unsigned>::max();
311337
}
312338
}
313339

@@ -560,17 +586,17 @@ void BoundsAnalysis::ComputeInSets(ElevatedCFGBlock *EB) {
560586
// In[B1] = n Out[B*->B1], where B* are all preds of B1.
561587

562588
BoundsMapTy Intersections;
563-
bool ItersectionEmpty = true;
589+
bool FirstIntersection = true;
564590

565591
for (const CFGBlock *pred : EB->Block->preds()) {
566592
if (SkipBlock(pred))
567-
continue;
593+
return;
568594

569595
ElevatedCFGBlock *PredEB = BlockMap[pred];
570596

571-
if (ItersectionEmpty) {
597+
if (FirstIntersection) {
572598
Intersections = PredEB->Out[EB->Block];
573-
ItersectionEmpty = false;
599+
FirstIntersection = false;
574600
} else
575601
Intersections = Intersect(Intersections, PredEB->Out[EB->Block]);
576602
}
@@ -591,6 +617,7 @@ void BoundsAnalysis::ComputeOutSets(ElevatedCFGBlock *EB,
591617
}
592618

593619
BoundsMapTy Diff = Difference(EB->In, KilledVars);
620+
594621
for (const CFGBlock *succ : EB->Block->succs()) {
595622
if (SkipBlock(succ))
596623
continue;
@@ -635,6 +662,8 @@ Expr *BoundsAnalysis::GetTerminatorCondition(const CFGBlock *B) const {
635662
if (const Stmt *S = B->getTerminatorStmt()) {
636663
if (const auto *IfS = dyn_cast<IfStmt>(S))
637664
return const_cast<Expr *>(IfS->getCond());
665+
if (const auto *WhileS = dyn_cast<WhileStmt>(S))
666+
return const_cast<Expr *>(WhileS->getCond());
638667
}
639668
return nullptr;
640669
}
@@ -649,7 +678,7 @@ bool BoundsAnalysis::IsNtArrayType(const VarDecl *V) const {
649678
}
650679

651680
bool BoundsAnalysis::SkipBlock(const CFGBlock *B) const {
652-
return !B || B == &Cfg->getEntry() || B == &Cfg->getExit();
681+
return !B || B == &Cfg->getExit();
653682
}
654683

655684
template<class T>
@@ -715,8 +744,21 @@ template<class T>
715744
bool BoundsAnalysis::Differ(T &A, T &B) const {
716745
if (A.size() != B.size())
717746
return true;
718-
auto Ret = Intersect(A, B);
719-
return Ret.size() != A.size();
747+
748+
for (const auto item : A) {
749+
if (!B.count(item.first))
750+
return true;
751+
if (B[item.first] != item.second)
752+
return true;
753+
}
754+
755+
for (const auto item : B) {
756+
if (!A.count(item.first))
757+
return true;
758+
if (A[item.first] != item.second)
759+
return true;
760+
}
761+
return false;
720762
}
721763

722764
OrderedBlocksTy BoundsAnalysis::GetOrderedBlocks() {

clang/test/CheckedC/inferred-bounds/widened-bounds.c

Lines changed: 130 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -534,3 +534,133 @@ void f20() {
534534
// CHECK: [B1]
535535
// CHECK-NOT: upper_bound(u)
536536
}
537+
538+
void f21() {
539+
char p _Nt_checked[] : count(0) = "abc";
540+
541+
while (p[0])
542+
while (p[1]) // expected-error {{out-of-bounds memory access}}
543+
while (p[2]) // expected-error {{out-of-bounds memory access}}
544+
{}
545+
546+
// CHECK: In function: f21
547+
// CHECK: [B6]
548+
// CHECK: 1: p[0]
549+
// CHECK: [B5]
550+
// CHECK: 1: p[1]
551+
// CHECK: upper_bound(p) = 1
552+
// CHECK: [B4]
553+
// CHECK: 1: p[2]
554+
// CHECK: upper_bound(p) = 2
555+
// CHECK: [B3]
556+
// CHECK: upper_bound(p) = 3
557+
// CHECK: [B2]
558+
// CHECK: upper_bound(p) = 2
559+
// CHECK: [B1]
560+
// CHECK: upper_bound(p) = 1
561+
}
562+
563+
void f22() {
564+
_Nt_array_ptr<char> p : count(0) = "a";
565+
566+
if (*p)
567+
while (*(p + 1)) // expected-error {{out-of-bounds memory access}}
568+
if (*(p + 2)) // expected-error {{out-of-bounds memory access}}
569+
{}
570+
571+
// CHECK: In function: f22
572+
// CHECK: [B5]
573+
// CHECK: 2: *p
574+
// CHECK: [B4]
575+
// CHECK: 1: *(p + 1)
576+
// CHECK: upper_bound(p) = 1
577+
// CHECK: [B3]
578+
// CHECK: 1: *(p + 2)
579+
// CHECK: upper_bound(p) = 2
580+
// CHECK: [B2]
581+
// CHECK: upper_bound(p) = 3
582+
// CHECK: [B1]
583+
// CHECK: upper_bound(p) = 2
584+
}
585+
586+
void f23() {
587+
_Nt_array_ptr<char> p : count(0) = "";
588+
589+
// CHECK: In function: f23
590+
591+
goto B;
592+
while (*p) {
593+
B: p;
594+
while (*(p + 1)) { // expected-error {{out-of-bounds memory access}}
595+
p;
596+
}
597+
}
598+
599+
// CHECK: [B14]
600+
// CHECK: T: goto B;
601+
// CHECK: [B13]
602+
// CHECK: 1: *p
603+
// CHECK: T: while
604+
// CHECK: [B12]
605+
// CHECK: B:
606+
// CHECK: 1: p
607+
// CHECK-NOT: upper_bound(p)
608+
// CHECK: [B11]
609+
// CHECK: 1: *(p + 1)
610+
// CHECK: T: while
611+
// CHECK: [B10]
612+
// CHECK: 1: p
613+
// CHECK-NOT: upper_bound(p)
614+
615+
while (*p) {
616+
p;
617+
while (*(p + 1)) { // expected-error {{out-of-bounds memory access}}
618+
C: p;
619+
}
620+
}
621+
goto C;
622+
623+
// CHECK: [B7]
624+
// CHECK: 1: *p
625+
// CHECK: T: while
626+
// CHECK: [B6]
627+
// CHECK: 1: p
628+
// CHECK: upper_bound(p) = 1
629+
// CHECK: [B5]
630+
// CHECK: 1: *(p + 1)
631+
// CHECK: T: while
632+
// CHECK-NOT: upper_bound(p)
633+
// CHECK: [B4]
634+
// CHECK: C:
635+
// CHECK: 1: p
636+
// CHECK-NOT: upper_bound(p)
637+
// CHECK: [B2]
638+
// CHECK-NOT: upper_bound(p)
639+
// CHECK: [B1]
640+
// CHECK: T: goto C;
641+
}
642+
643+
void f24() {
644+
_Nt_array_ptr<char> p : count(0) = "";
645+
646+
while (*p) {
647+
p++;
648+
while (*(p+1)) { // expected-error {{out-of-bounds memory access}}
649+
p;
650+
}
651+
}
652+
653+
// CHECK: In function: f24
654+
// CHECK: [B6]
655+
// CHECK: 1: *p
656+
// CHECK: T: while
657+
// CHECK: [B5]
658+
// CHECK: 1: p++
659+
// CHECK: upper_bound(p) = 1
660+
// CHECK: [B4]
661+
// CHECK: 1: *(p + 1)
662+
// CHECK: T: while
663+
// CHECK: [B3]
664+
// CHECK: 1: p
665+
// CHECK-NOT: upper_bound(p)
666+
}

0 commit comments

Comments
 (0)