Skip to content

Commit 67d6420

Browse files
committed
Reapply "[analyzer] Handle [[assume(cond)]] as __builtin_assume(cond) (llvm#116462)"
This reverts commit 2b9abf0.
1 parent 1aea024 commit 67d6420

File tree

8 files changed

+214
-25
lines changed

8 files changed

+214
-25
lines changed

clang/include/clang/AST/AttrIterator.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
#include "clang/Basic/LLVM.h"
1717
#include "llvm/ADT/ADL.h"
1818
#include "llvm/ADT/SmallVector.h"
19+
#include "llvm/ADT/iterator_range.h"
1920
#include "llvm/Support/Casting.h"
2021
#include <cassert>
2122
#include <cstddef>
@@ -124,6 +125,17 @@ inline auto *getSpecificAttr(const Container &container) {
124125
return It != specific_attr_end<IterTy>(container) ? *It : nullptr;
125126
}
126127

128+
template <typename SpecificAttr, typename Container>
129+
inline auto getSpecificAttrs(const Container &container) {
130+
using ValueTy = llvm::detail::ValueOfRange<Container>;
131+
using ValuePointeeTy = std::remove_pointer_t<ValueTy>;
132+
using IterTy = std::conditional_t<std::is_const_v<ValuePointeeTy>,
133+
const SpecificAttr, SpecificAttr>;
134+
auto Begin = specific_attr_begin<IterTy>(container);
135+
auto End = specific_attr_end<IterTy>(container);
136+
return llvm::make_range(Begin, End);
137+
}
138+
127139
} // namespace clang
128140

129141
#endif // LLVM_CLANG_AST_ATTRITERATOR_H

clang/include/clang/StaticAnalyzer/Core/PathSensitive/ExprEngine.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -498,6 +498,10 @@ class ExprEngine {
498498
void VisitInitListExpr(const InitListExpr *E, ExplodedNode *Pred,
499499
ExplodedNodeSet &Dst);
500500

501+
/// VisitAttributedStmt - Transfer function logic for AttributedStmt
502+
void VisitAttributedStmt(const AttributedStmt *A, ExplodedNode *Pred,
503+
ExplodedNodeSet &Dst);
504+
501505
/// VisitLogicalExpr - Transfer function logic for '&&', '||'
502506
void VisitLogicalExpr(const BinaryOperator* B, ExplodedNode *Pred,
503507
ExplodedNodeSet &Dst);

clang/lib/Analysis/CFG.cpp

Lines changed: 52 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -433,7 +433,7 @@ class reverse_children {
433433
ArrayRef<Stmt *> children;
434434

435435
public:
436-
reverse_children(Stmt *S);
436+
reverse_children(Stmt *S, ASTContext &Ctx);
437437

438438
using iterator = ArrayRef<Stmt *>::reverse_iterator;
439439

@@ -443,28 +443,47 @@ class reverse_children {
443443

444444
} // namespace
445445

446-
reverse_children::reverse_children(Stmt *S) {
447-
if (CallExpr *CE = dyn_cast<CallExpr>(S)) {
448-
children = CE->getRawSubExprs();
446+
reverse_children::reverse_children(Stmt *S, ASTContext &Ctx) {
447+
switch (S->getStmtClass()) {
448+
case Stmt::CallExprClass: {
449+
children = cast<CallExpr>(S)->getRawSubExprs();
449450
return;
450451
}
451-
switch (S->getStmtClass()) {
452-
// Note: Fill in this switch with more cases we want to optimize.
453-
case Stmt::InitListExprClass: {
454-
InitListExpr *IE = cast<InitListExpr>(S);
455-
children = llvm::ArrayRef(reinterpret_cast<Stmt **>(IE->getInits()),
456-
IE->getNumInits());
457-
return;
458-
}
459-
default:
460-
break;
452+
453+
// Note: Fill in this switch with more cases we want to optimize.
454+
case Stmt::InitListExprClass: {
455+
InitListExpr *IE = cast<InitListExpr>(S);
456+
children = llvm::ArrayRef(reinterpret_cast<Stmt **>(IE->getInits()),
457+
IE->getNumInits());
458+
return;
461459
}
460+
case Stmt::AttributedStmtClass: {
461+
auto *AS = cast<AttributedStmt>(S);
462462

463-
// Default case for all other statements.
464-
llvm::append_range(childrenBuf, S->children());
463+
// for an attributed stmt, the "children()" returns only the NullStmt
464+
// (;) but semantically the "children" are supposed to be the
465+
// expressions _within_ i.e. the two square brackets i.e. [[ HERE ]]
466+
// so we add the subexpressions first, _then_ add the "children"
465467

466-
// This needs to be done *after* childrenBuf has been populated.
467-
children = childrenBuf;
468+
for (const auto *Attr : AS->getAttrs()) {
469+
if (const auto *AssumeAttr = dyn_cast<CXXAssumeAttr>(Attr)) {
470+
Expr *AssumeExpr = AssumeAttr->getAssumption();
471+
if (!AssumeExpr->HasSideEffects(Ctx)) {
472+
childrenBuf.push_back(AssumeExpr);
473+
}
474+
}
475+
// Visit the actual children AST nodes.
476+
// For CXXAssumeAttrs, this is always a NullStmt.
477+
llvm::append_range(childrenBuf, AS->children());
478+
children = childrenBuf;
479+
}
480+
return;
481+
}
482+
default:
483+
// Default case for all other statements.
484+
llvm::append_range(childrenBuf, S->children());
485+
children = childrenBuf;
486+
}
468487
}
469488

470489
namespace {
@@ -2433,7 +2452,7 @@ CFGBlock *CFGBuilder::VisitChildren(Stmt *S) {
24332452

24342453
// Visit the children in their reverse order so that they appear in
24352454
// left-to-right (natural) order in the CFG.
2436-
reverse_children RChildren(S);
2455+
reverse_children RChildren(S, *Context);
24372456
for (Stmt *Child : RChildren) {
24382457
if (Child)
24392458
if (CFGBlock *R = Visit(Child))
@@ -2449,7 +2468,7 @@ CFGBlock *CFGBuilder::VisitInitListExpr(InitListExpr *ILE, AddStmtChoice asc) {
24492468
}
24502469
CFGBlock *B = Block;
24512470

2452-
reverse_children RChildren(ILE);
2471+
reverse_children RChildren(ILE, *Context);
24532472
for (Stmt *Child : RChildren) {
24542473
if (!Child)
24552474
continue;
@@ -2484,6 +2503,14 @@ static bool isFallthroughStatement(const AttributedStmt *A) {
24842503
return isFallthrough;
24852504
}
24862505

2506+
static bool isCXXAssumeAttr(const AttributedStmt *A) {
2507+
bool hasAssumeAttr = hasSpecificAttr<CXXAssumeAttr>(A->getAttrs());
2508+
2509+
assert((!hasAssumeAttr || isa<NullStmt>(A->getSubStmt())) &&
2510+
"expected [[assume]] not to have children");
2511+
return hasAssumeAttr;
2512+
}
2513+
24872514
CFGBlock *CFGBuilder::VisitAttributedStmt(AttributedStmt *A,
24882515
AddStmtChoice asc) {
24892516
// AttributedStmts for [[likely]] can have arbitrary statements as children,
@@ -2499,6 +2526,11 @@ CFGBlock *CFGBuilder::VisitAttributedStmt(AttributedStmt *A,
24992526
appendStmt(Block, A);
25002527
}
25012528

2529+
if (isCXXAssumeAttr(A) && asc.alwaysAdd(*this, A)) {
2530+
autoCreateBlock();
2531+
appendStmt(Block, A);
2532+
}
2533+
25022534
return VisitChildren(A);
25032535
}
25042536

clang/lib/StaticAnalyzer/Core/ExprEngine.cpp

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1950,7 +1950,6 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
19501950
// to be explicitly evaluated.
19511951
case Stmt::PredefinedExprClass:
19521952
case Stmt::AddrLabelExprClass:
1953-
case Stmt::AttributedStmtClass:
19541953
case Stmt::IntegerLiteralClass:
19551954
case Stmt::FixedPointLiteralClass:
19561955
case Stmt::CharacterLiteralClass:
@@ -1981,6 +1980,13 @@ void ExprEngine::Visit(const Stmt *S, ExplodedNode *Pred,
19811980
break;
19821981
}
19831982

1983+
case Stmt::AttributedStmtClass: {
1984+
Bldr.takeNodes(Pred);
1985+
VisitAttributedStmt(cast<AttributedStmt>(S), Pred, Dst);
1986+
Bldr.addNodes(Dst);
1987+
break;
1988+
}
1989+
19841990
case Stmt::CXXDefaultArgExprClass:
19851991
case Stmt::CXXDefaultInitExprClass: {
19861992
Bldr.takeNodes(Pred);

clang/lib/StaticAnalyzer/Core/ExprEngineC.cpp

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -796,17 +796,18 @@ void ExprEngine::VisitGuardedExpr(const Expr *Ex,
796796

797797
// Find the predecessor block.
798798
ProgramStateRef SrcState = state;
799+
799800
for (const ExplodedNode *N = Pred ; N ; N = *N->pred_begin()) {
800-
ProgramPoint PP = N->getLocation();
801-
if (PP.getAs<PreStmtPurgeDeadSymbols>() || PP.getAs<BlockEntrance>()) {
801+
auto Edge = N->getLocationAs<BlockEdge>();
802+
if (!Edge.has_value()) {
802803
// If the state N has multiple predecessors P, it means that successors
803804
// of P are all equivalent.
804805
// In turn, that means that all nodes at P are equivalent in terms
805806
// of observable behavior at N, and we can follow any of them.
806807
// FIXME: a more robust solution which does not walk up the tree.
807808
continue;
808809
}
809-
SrcBlock = PP.castAs<BlockEdge>().getSrc();
810+
SrcBlock = Edge->getSrc();
810811
SrcState = N->getState();
811812
break;
812813
}

clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
//
1111
//===----------------------------------------------------------------------===//
1212

13+
#include "clang/AST/AttrIterator.h"
1314
#include "clang/AST/DeclCXX.h"
1415
#include "clang/AST/ParentMap.h"
1516
#include "clang/AST/StmtCXX.h"
@@ -1200,3 +1201,20 @@ void ExprEngine::VisitLambdaExpr(const LambdaExpr *LE, ExplodedNode *Pred,
12001201
// FIXME: Move all post/pre visits to ::Visit().
12011202
getCheckerManager().runCheckersForPostStmt(Dst, Tmp, LE, *this);
12021203
}
1204+
1205+
void ExprEngine::VisitAttributedStmt(const AttributedStmt *A,
1206+
ExplodedNode *Pred, ExplodedNodeSet &Dst) {
1207+
ExplodedNodeSet CheckerPreStmt;
1208+
getCheckerManager().runCheckersForPreStmt(CheckerPreStmt, Pred, A, *this);
1209+
1210+
ExplodedNodeSet EvalSet;
1211+
StmtNodeBuilder Bldr(CheckerPreStmt, EvalSet, *currBldrCtx);
1212+
1213+
for (const auto *Attr : getSpecificAttrs<CXXAssumeAttr>(A->getAttrs())) {
1214+
for (ExplodedNode *N : CheckerPreStmt) {
1215+
Visit(Attr->getAssumption(), N, EvalSet);
1216+
}
1217+
}
1218+
1219+
getCheckerManager().runCheckersForPostStmt(Dst, EvalSet, A, *this);
1220+
}
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
// RUN: %clang_analyze_cc1 -std=c++23 -triple x86_64-pc-linux-gnu \
2+
// RUN: -analyzer-checker=core,debug.ExprInspection -verify %s
3+
4+
template <typename T> void clang_analyzer_dump(T);
5+
template <typename T> void clang_analyzer_value(T);
6+
7+
int ternary_in_builtin_assume(int a, int b) {
8+
__builtin_assume(a > 10 ? b == 4 : b == 10);
9+
10+
clang_analyzer_value(a);
11+
// expected-warning@-1 {{[-2147483648, 10]}}
12+
// expected-warning@-2 {{[11, 2147483647]}}
13+
14+
clang_analyzer_dump(b); // expected-warning{{4}} expected-warning{{10}}
15+
16+
if (a > 20) {
17+
clang_analyzer_dump(b + 100); // expected-warning {{104}}
18+
return 2;
19+
}
20+
if (a > 10) {
21+
clang_analyzer_dump(b + 200); // expected-warning {{204}}
22+
return 1;
23+
}
24+
clang_analyzer_dump(b + 300); // expected-warning {{310}}
25+
return 0;
26+
}
27+
28+
// From: https://github.com/llvm/llvm-project/pull/116462#issuecomment-2517853226
29+
int ternary_in_assume(int a, int b) {
30+
// FIXME notes
31+
// Currently, if this test is run without the core.builtin.Builtin checker, the above function with the __builtin_assume behaves identically to the following test
32+
// i.e. calls to `clang_analyzer_dump` result in "extraneous" prints of the SVal(s) `reg<int b> ...`
33+
// as opposed to 4 or 10
34+
// which likely implies the Program State(s) did not get narrowed.
35+
// A new checker is likely needed to be implemented to properly handle the expressions within `[[assume]]` to eliminate the states where `b` is not narrowed.
36+
37+
[[assume(a > 10 ? b == 4 : b == 10)]];
38+
clang_analyzer_value(a);
39+
// expected-warning@-1 {{[-2147483648, 10]}}
40+
// expected-warning@-2 {{[11, 2147483647]}}
41+
42+
clang_analyzer_dump(b); // expected-warning {{4}} expected-warning {{10}}
43+
// expected-warning-re@-1 {{reg_${{[0-9]+}}<int b>}} FIXME: We shouldn't have this dump.
44+
45+
if (a > 20) {
46+
clang_analyzer_dump(b + 100); // expected-warning {{104}}
47+
// expected-warning-re@-1 {{(reg_${{[0-9]+}}<int b>) + 100}} FIXME: We shouldn't have this dump.
48+
return 2;
49+
}
50+
if (a > 10) {
51+
clang_analyzer_dump(b + 200); // expected-warning {{204}}
52+
// expected-warning-re@-1 {{(reg_${{[0-9]+}}<int b>) + 200}} FIXME: We shouldn't have this dump.
53+
return 1;
54+
}
55+
clang_analyzer_dump(b + 300); // expected-warning {{310}}
56+
// expected-warning-re@-1 {{(reg_${{[0-9]+}}<int b>) + 300}} FIXME: We shouldn't have this dump.
57+
return 0;
58+
}

clang/test/Analysis/out-of-bounds-new.cpp

Lines changed: 59 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
1-
// RUN: %clang_analyze_cc1 -std=c++11 -Wno-array-bounds -analyzer-checker=unix,core,security.ArrayBound -verify %s
1+
// RUN: %clang_analyze_cc1 -std=c++11 -Wno-array-bounds -verify %s \
2+
// RUN: -analyzer-checker=unix,core,security.ArrayBound,debug.ExprInspection
3+
4+
void clang_analyzer_eval(bool);
25

36
// Tests doing an out-of-bounds access after the end of an array using:
47
// - constant integer index
@@ -180,3 +183,58 @@ int test_reference_that_might_be_after_the_end(int idx) {
180183
return ref;
181184
}
182185

186+
// From: https://github.com/llvm/llvm-project/issues/100762
187+
extern int arrOf10[10];
188+
void using_builtin(int x) {
189+
__builtin_assume(x > 101); // CallExpr
190+
arrOf10[x] = 404; // expected-warning {{Out of bound access to memory}}
191+
}
192+
193+
void using_assume_attr(int ax) {
194+
[[assume(ax > 100)]]; // NullStmt with an "assume" attribute.
195+
arrOf10[ax] = 405; // expected-warning {{Out of bound access to memory}}
196+
}
197+
198+
void using_many_assume_attr(int yx) {
199+
[[assume(yx > 104), assume(yx > 200), assume(yx < 300)]]; // NullStmt with an attribute
200+
arrOf10[yx] = 406; // expected-warning{{Out of bound access to memory}}
201+
}
202+
203+
204+
int using_builtin_assume_has_no_sideeffects(int y) {
205+
// We should not apply sideeffects of the argument of [[assume(...)]].
206+
// "y" should not get incremented;
207+
__builtin_assume(++y == 43); // expected-warning {{assumption is ignored because it contains (potential) side-effects}}
208+
clang_analyzer_eval(y == 42); // expected-warning {{FALSE}}
209+
return y;
210+
}
211+
212+
213+
214+
int using_assume_attr_has_no_sideeffects(int y) {
215+
216+
// We should not apply sideeffects of the argument of [[assume(...)]].
217+
// "y" should not get incremented;
218+
[[assume(++y == 43)]]; // expected-warning {{assumption is ignored because it contains (potential) side-effects}}
219+
220+
clang_analyzer_eval(y == 42); // expected-warning {{TRUE}} expected-warning {{FALSE}} FIXME: This should be only TRUE.
221+
222+
clang_analyzer_eval(y == 43); // expected-warning {{FALSE}} expected-warning {{TRUE}} FIXME: This should be only FALSE.
223+
224+
return y;
225+
}
226+
227+
228+
int using_builtinassume_has_no_sideeffects(int u) {
229+
// We should not apply sideeffects of the argument of __builtin_assume(...)
230+
// "u" should not get incremented;
231+
__builtin_assume(++u == 43); // expected-warning {{assumption is ignored because it contains (potential) side-effects}}
232+
233+
// FIXME: evaluate this to true
234+
clang_analyzer_eval(u == 42); // expected-warning {{FALSE}} current behavior
235+
236+
// FIXME: evaluate this to false
237+
clang_analyzer_eval(u == 43); // expected-warning {{TRUE}} current behavior
238+
239+
return u;
240+
}

0 commit comments

Comments
 (0)