Skip to content

Commit b2ac5fd

Browse files
committed
[-Wunsafe-buffer-usage] Add a new forEachDescendant matcher that skips callable declarations
Note this is a change local to -Wunsafe-buffer-usage checks. Add a new matcher `forEveryDescendant` that recursively matches descendants of a `Stmt` but skips nested callable definitions. This matcher has same effect as using `forEachDescendant` and skipping `forCallable` explicitly but does not require the AST construction to be complete. Reviewed by: NoQ, xazax.hun Differential revision: https://reviews.llvm.org/D138329
1 parent 733740b commit b2ac5fd

File tree

2 files changed

+166
-5
lines changed

2 files changed

+166
-5
lines changed

clang/lib/Analysis/UnsafeBufferUsage.cpp

Lines changed: 100 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,12 +8,111 @@
88

99
#include "clang/Analysis/Analyses/UnsafeBufferUsage.h"
1010
#include "clang/ASTMatchers/ASTMatchFinder.h"
11+
#include "clang/AST/RecursiveASTVisitor.h"
1112
#include "llvm/ADT/SmallVector.h"
1213

1314
using namespace llvm;
1415
using namespace clang;
1516
using namespace ast_matchers;
1617

18+
namespace clang::ast_matchers::internal {
19+
// A `RecursiveASTVisitor` that traverses all descendants of a given node "n"
20+
// except for those belonging to a different callable of "n".
21+
class MatchDescendantVisitor
22+
: public RecursiveASTVisitor<MatchDescendantVisitor> {
23+
public:
24+
typedef RecursiveASTVisitor<MatchDescendantVisitor> VisitorBase;
25+
26+
// Creates an AST visitor that matches `Matcher` on all
27+
// descendants of a given node "n" except for the ones
28+
// belonging to a different callable of "n".
29+
MatchDescendantVisitor(const DynTypedMatcher *Matcher, ASTMatchFinder *Finder,
30+
BoundNodesTreeBuilder *Builder,
31+
ASTMatchFinder::BindKind Bind)
32+
: Matcher(Matcher), Finder(Finder), Builder(Builder), Bind(Bind),
33+
Matches(false) {}
34+
35+
// Returns true if a match is found in a subtree of `DynNode`, which belongs
36+
// to the same callable of `DynNode`.
37+
bool findMatch(const DynTypedNode &DynNode) {
38+
Matches = false;
39+
if (const Stmt *StmtNode = DynNode.get<Stmt>()) {
40+
TraverseStmt(const_cast<Stmt *>(StmtNode));
41+
*Builder = ResultBindings;
42+
return Matches;
43+
}
44+
return false;
45+
}
46+
47+
// The following are overriding methods from the base visitor class.
48+
// They are public only to allow CRTP to work. They are *not *part
49+
// of the public API of this class.
50+
51+
// For the matchers so far used in safe buffers, we only need to match
52+
// `Stmt`s. To override more as needed.
53+
54+
bool TraverseDecl(Decl *Node) {
55+
if (!Node)
56+
return true;
57+
if (!match(*Node))
58+
return false;
59+
// To skip callables:
60+
if (isa<FunctionDecl, BlockDecl, ObjCMethodDecl>(Node))
61+
return true;
62+
// Traverse descendants
63+
return VisitorBase::TraverseDecl(Node);
64+
}
65+
66+
bool TraverseStmt(Stmt *Node, DataRecursionQueue *Queue = nullptr) {
67+
if (!Node)
68+
return true;
69+
if (!match(*Node))
70+
return false;
71+
// To skip callables:
72+
if (isa<LambdaExpr>(Node))
73+
return true;
74+
return VisitorBase::TraverseStmt(Node);
75+
}
76+
77+
bool shouldVisitTemplateInstantiations() const { return true; }
78+
bool shouldVisitImplicitCode() const {
79+
// TODO: let's ignore implicit code for now
80+
return false;
81+
}
82+
83+
private:
84+
// Sets 'Matched' to true if 'Matcher' matches 'Node'
85+
//
86+
// Returns 'true' if traversal should continue after this function
87+
// returns, i.e. if no match is found or 'Bind' is 'BK_All'.
88+
template <typename T> bool match(const T &Node) {
89+
BoundNodesTreeBuilder RecursiveBuilder(*Builder);
90+
91+
if (Matcher->matches(DynTypedNode::create(Node), Finder,
92+
&RecursiveBuilder)) {
93+
ResultBindings.addMatch(RecursiveBuilder);
94+
Matches = true;
95+
if (Bind != ASTMatchFinder::BK_All)
96+
return false; // Abort as soon as a match is found.
97+
}
98+
return true;
99+
}
100+
101+
const DynTypedMatcher *const Matcher;
102+
ASTMatchFinder *const Finder;
103+
BoundNodesTreeBuilder *const Builder;
104+
BoundNodesTreeBuilder ResultBindings;
105+
const ASTMatchFinder::BindKind Bind;
106+
bool Matches;
107+
};
108+
109+
AST_MATCHER_P(Stmt, forEveryDescendant, Matcher<Stmt>, innerMatcher) {
110+
MatchDescendantVisitor Visitor(new DynTypedMatcher(innerMatcher), Finder,
111+
Builder, ASTMatchFinder::BK_All);
112+
return Visitor.findMatch(DynTypedNode::create(Node));
113+
}
114+
} // namespace clang::ast_matchers::internal
115+
17116
namespace {
18117
// Because the analysis revolves around variables and their types, we'll need to
19118
// track uses of variables (aka DeclRefExprs).
@@ -349,7 +448,7 @@ static std::pair<GadgetList, DeclUseTracker> findGadgets(const Decl *D) {
349448

350449
// clang-format off
351450
M.addMatcher(
352-
stmt(forEachDescendant(
451+
stmt(forEveryDescendant(
353452
stmt(anyOf(
354453
// Add Gadget::matcher() for every gadget in the registry.
355454
#define GADGET(x) \

clang/test/SemaCXX/warn-unsafe-buffer-usage.cpp

Lines changed: 66 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -include %s -verify %s
1+
// RUN: %clang_cc1 -std=c++20 -Wunsafe-buffer-usage -fblocks -include %s -verify %s
22
#ifndef INCLUDED
33
#define INCLUDED
44
#pragma clang system_header
@@ -141,15 +141,15 @@ void testStructMembers(struct T * sp, struct T s, T_t * sp2, T_t s2) {
141141

142142
int garray[10];
143143
int * gp = garray;
144-
int gvar = gp[1]; // TODO: this is not warned
144+
int gvar = gp[1]; // FIXME: file scope unsafe buffer access is not warned
145145

146146
void testLambdaCaptureAndGlobal(int * p) {
147147
int a[10];
148148

149149
auto Lam = [p, a]() {
150-
return p[1] // expected-warning2{{unchecked operation on raw buffer in expression}}
150+
return p[1] // expected-warning{{unchecked operation on raw buffer in expression}}
151151
+ a[1] + garray[1]
152-
+ gp[1]; // expected-warning2{{unchecked operation on raw buffer in expression}}
152+
+ gp[1]; // expected-warning{{unchecked operation on raw buffer in expression}}
153153
};
154154
}
155155

@@ -187,4 +187,66 @@ void testPointerToMember() {
187187
foo(S.*p,
188188
(S.*q)[1]); // expected-warning{{unchecked operation on raw buffer in expression}}
189189
}
190+
191+
// test that nested callable definitions are scanned only once
192+
void testNestedCallableDefinition(int * p) {
193+
class A {
194+
void inner(int * p) {
195+
p++; // expected-warning{{unchecked operation on raw buffer in expression}}
196+
}
197+
198+
static void innerStatic(int * p) {
199+
p++; // expected-warning{{unchecked operation on raw buffer in expression}}
200+
}
201+
202+
void innerInner(int * p) {
203+
auto Lam = [p]() {
204+
int * q = p;
205+
q++; // expected-warning{{unchecked operation on raw buffer in expression}}
206+
return *q;
207+
};
208+
}
209+
};
210+
211+
auto Lam = [p]() {
212+
int * q = p;
213+
q++; // expected-warning{{unchecked operation on raw buffer in expression}}
214+
return *q;
215+
};
216+
217+
auto LamLam = [p]() {
218+
auto Lam = [p]() {
219+
int * q = p;
220+
q++; // expected-warning{{unchecked operation on raw buffer in expression}}
221+
return *q;
222+
};
223+
};
224+
225+
void (^Blk)(int*) = ^(int *p) {
226+
p++; // expected-warning{{unchecked operation on raw buffer in expression}}
227+
};
228+
229+
void (^BlkBlk)(int*) = ^(int *p) {
230+
void (^Blk)(int*) = ^(int *p) {
231+
p++; // expected-warning{{unchecked operation on raw buffer in expression}}
232+
};
233+
Blk(p);
234+
};
235+
236+
// lambda and block as call arguments...
237+
foo( [p]() { int * q = p;
238+
q++; // expected-warning{{unchecked operation on raw buffer in expression}}
239+
return *q;
240+
},
241+
^(int *p) { p++; // expected-warning{{unchecked operation on raw buffer in expression}}
242+
}
243+
);
244+
}
245+
246+
void testVariableDecls(int * p) {
247+
int * q = p++; // expected-warning{{unchecked operation on raw buffer in expression}}
248+
int a[p[1]]; // expected-warning{{unchecked operation on raw buffer in expression}}
249+
int b = p[1]; // expected-warning{{unchecked operation on raw buffer in expression}}
250+
}
251+
190252
#endif

0 commit comments

Comments
 (0)