Skip to content

Commit 725d9e8

Browse files
authored
[webkit.UncountedLambdaCapturesChecker] Treat arguments of std::ranges::all_of as [[clang::noescape]] (#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 bda1cab commit 725d9e8

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
@@ -126,20 +126,21 @@ class RawPtrRefLambdaCapturesChecker
126126
return true;
127127
}
128128

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

168169
bool VisitCallExpr(CallExpr *CE) override {
169170
checkCalleeLambda(CE);
170-
if (auto *Callee = CE->getDirectCallee()) {
171-
unsigned ArgIndex = isa<CXXOperatorCallExpr>(CE);
172-
bool TreatAllArgsAsNoEscape = shouldTreatAllArgAsNoEscape(Callee);
173-
for (auto *Param : Callee->parameters()) {
174-
if (ArgIndex >= CE->getNumArgs())
175-
return true;
176-
auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts();
177-
if (auto *L = findLambdaInArg(Arg)) {
178-
LambdasToIgnore.insert(L);
179-
if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape)
180-
Checker->visitLambdaExpr(
181-
L, shouldCheckThis() && !hasProtectedThis(L), ClsType);
182-
}
183-
++ArgIndex;
171+
if (auto *Callee = CE->getDirectCallee())
172+
checkParameters(CE, Callee);
173+
else if (auto *CalleeE = CE->getCallee()) {
174+
if (auto *DRE = dyn_cast<DeclRefExpr>(CalleeE->IgnoreParenCasts())) {
175+
if (auto *Callee = dyn_cast_or_null<FunctionDecl>(DRE->getDecl()))
176+
checkParameters(CE, Callee);
184177
}
185178
}
186179
return true;
187180
}
188181

182+
void checkParameters(CallExpr *CE, FunctionDecl *Callee) {
183+
unsigned ArgIndex = isa<CXXOperatorCallExpr>(CE);
184+
bool TreatAllArgsAsNoEscape = shouldTreatAllArgAsNoEscape(Callee);
185+
for (auto *Param : Callee->parameters()) {
186+
if (ArgIndex >= CE->getNumArgs())
187+
return;
188+
auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts();
189+
if (auto *L = findLambdaInArg(Arg)) {
190+
LambdasToIgnore.insert(L);
191+
if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape)
192+
Checker->visitLambdaExpr(
193+
L, shouldCheckThis() && !hasProtectedThis(L), ClsType);
194+
}
195+
++ArgIndex;
196+
}
197+
}
198+
189199
LambdaExpr *findLambdaInArg(Expr *E) {
190200
if (auto *Lambda = dyn_cast_or_null<LambdaExpr>(E))
191201
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)