Skip to content

Commit 4a420fb

Browse files
committed
[webkit.UncountedLambdaCapturesChecker] Treat arguments of std::ranges::all_of as [[clang::noescape]] (llvm#158419)
The checker already had std::ranges hard-coded to treat its arguments as [[clang::oescape]] but the fact std::ranges::all_of is implemented as a struct instead of a function confused the checker and resuled in a superflous warning being emitted for std::ranges::all_of. This PR adds the support for recognizing DeclRefExpr which appears as a callee in VisitCallExpr and generalizes the check in shouldTreatAllArgAsNoEscape to walk up the decl contexts to find the target namespaces such as std::ranges:: or a namespace and a function like WTF::switchOn.
1 parent 2413856 commit 4a420fb

File tree

2 files changed

+69
-30
lines changed

2 files changed

+69
-30
lines changed

clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLambdaCapturesChecker.cpp

Lines changed: 37 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -130,20 +130,21 @@ class RawPtrRefLambdaCapturesChecker
130130
return true;
131131
}
132132

133-
bool shouldTreatAllArgAsNoEscape(FunctionDecl *Decl) {
134-
auto *NsDecl = Decl->getParent();
135-
if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
136-
return false;
137-
// WTF::switchOn(T, F... f) is a variadic template function and couldn't
138-
// be annotated with NOESCAPE. We hard code it here to workaround that.
139-
if (safeGetName(NsDecl) == "WTF" && safeGetName(Decl) == "switchOn")
140-
return true;
141-
// Treat every argument of functions in std::ranges as noescape.
142-
if (safeGetName(NsDecl) == "ranges") {
143-
if (auto *OuterDecl = NsDecl->getParent();
144-
OuterDecl && isa<NamespaceDecl>(OuterDecl) &&
145-
safeGetName(OuterDecl) == "std")
133+
bool shouldTreatAllArgAsNoEscape(FunctionDecl *FDecl) {
134+
std::string PreviousName = safeGetName(FDecl);
135+
for (auto *Decl = FDecl->getParent(); Decl; Decl = Decl->getParent()) {
136+
if (!isa<NamespaceDecl>(Decl) && !isa<CXXRecordDecl>(Decl))
137+
return false;
138+
auto Name = safeGetName(Decl);
139+
// WTF::switchOn(T, F... f) is a variadic template function and
140+
// couldn't be annotated with NOESCAPE. We hard code it here to
141+
// workaround that.
142+
if (Name == "WTF" && PreviousName == "switchOn")
146143
return true;
144+
// Treat every argument of functions in std::ranges as noescape.
145+
if (Name == "std" && PreviousName == "ranges")
146+
return true;
147+
PreviousName = Name;
147148
}
148149
return false;
149150
}
@@ -171,25 +172,34 @@ class RawPtrRefLambdaCapturesChecker
171172

172173
bool VisitCallExpr(CallExpr *CE) {
173174
checkCalleeLambda(CE);
174-
if (auto *Callee = CE->getDirectCallee()) {
175-
unsigned ArgIndex = isa<CXXOperatorCallExpr>(CE);
176-
bool TreatAllArgsAsNoEscape = shouldTreatAllArgAsNoEscape(Callee);
177-
for (auto *Param : Callee->parameters()) {
178-
if (ArgIndex >= CE->getNumArgs())
179-
return true;
180-
auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts();
181-
if (auto *L = findLambdaInArg(Arg)) {
182-
LambdasToIgnore.insert(L);
183-
if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape)
184-
Checker->visitLambdaExpr(
185-
L, shouldCheckThis() && !hasProtectedThis(L), ClsType);
186-
}
187-
++ArgIndex;
175+
if (auto *Callee = CE->getDirectCallee())
176+
checkParameters(CE, Callee);
177+
else if (auto *CalleeE = CE->getCallee()) {
178+
if (auto *DRE = dyn_cast<DeclRefExpr>(CalleeE->IgnoreParenCasts())) {
179+
if (auto *Callee = dyn_cast_or_null<FunctionDecl>(DRE->getDecl()))
180+
checkParameters(CE, Callee);
188181
}
189182
}
190183
return true;
191184
}
192185

186+
void checkParameters(CallExpr *CE, FunctionDecl *Callee) {
187+
unsigned ArgIndex = isa<CXXOperatorCallExpr>(CE);
188+
bool TreatAllArgsAsNoEscape = shouldTreatAllArgAsNoEscape(Callee);
189+
for (auto *Param : Callee->parameters()) {
190+
if (ArgIndex >= CE->getNumArgs())
191+
return;
192+
auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts();
193+
if (auto *L = findLambdaInArg(Arg)) {
194+
LambdasToIgnore.insert(L);
195+
if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape)
196+
Checker->visitLambdaExpr(
197+
L, shouldCheckThis() && !hasProtectedThis(L), ClsType);
198+
}
199+
++ArgIndex;
200+
}
201+
}
202+
193203
LambdaExpr *findLambdaInArg(Expr *E) {
194204
if (auto *Lambda = dyn_cast_or_null<LambdaExpr>(E))
195205
return Lambda;

clang/test/Analysis/Checkers/WebKit/uncounted-lambda-captures.cpp

Lines changed: 32 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,18 @@ void for_each(IteratorType first, IteratorType last, CallbackType callback) {
1717
callback(*it);
1818
}
1919

20+
struct all_of_impl {
21+
template <typename Collection, typename Predicate>
22+
constexpr bool operator()(const Collection& collection, Predicate predicate) const {
23+
for (auto it = collection.begin(); it != collection.end(); ++it) {
24+
if (!predicate(*it))
25+
return false;
26+
}
27+
return true;
28+
}
29+
};
30+
inline constexpr auto all_of = all_of_impl {};
31+
2032
}
2133

2234
}
@@ -435,7 +447,7 @@ class Iterator {
435447
bool operator==(const Iterator&);
436448

437449
Iterator& operator++();
438-
void* operator*();
450+
int& operator*();
439451

440452
private:
441453
void* current { nullptr };
@@ -444,22 +456,39 @@ class Iterator {
444456

445457
void ranges_for_each(RefCountable* obj) {
446458
int array[] = { 1, 2, 3, 4, 5 };
447-
std::ranges::for_each(Iterator(array, sizeof(*array), 0), Iterator(array, sizeof(*array), 5), [&](void* item) {
459+
std::ranges::for_each(Iterator(array, sizeof(*array), 0), Iterator(array, sizeof(*array), 5), [&](int& item) {
448460
obj->method();
449-
++(*static_cast<unsigned*>(item));
461+
++item;
450462
});
451463
}
452464

465+
class IntCollection {
466+
public:
467+
int* begin();
468+
int* end();
469+
const int* begin() const;
470+
const int* end() const;
471+
};
472+
453473
class RefCountedObj {
454474
public:
455475
void ref();
456476
void deref();
457477

478+
bool allOf(const IntCollection&);
479+
bool isMatch(int);
480+
458481
void call() const;
459482
void callLambda([[clang::noescape]] const WTF::Function<void ()>& callback) const;
460483
void doSomeWork() const;
461484
};
462485

486+
bool RefCountedObj::allOf(const IntCollection& collection) {
487+
return std::ranges::all_of(collection, [&](auto& number) {
488+
return isMatch(number);
489+
});
490+
}
491+
463492
void RefCountedObj::callLambda([[clang::noescape]] const WTF::Function<void ()>& callback) const
464493
{
465494
callback();

0 commit comments

Comments
 (0)