Skip to content

Commit cf6b7bd

Browse files
committed
[webkit.UncountedLambdaCapturesChecker] Ignore trivial functions and [[clang::noescape]]. (llvm#114897)
This PR makes webkit.UncountedLambdaCapturesChecker ignore trivial functions as well as the one being passed to an argument with [[clang::noescape]] attribute. This dramatically reduces the false positive rate for this checker. To do this, this PR replaces VisitLambdaExpr in favor of checking lambdas via VisitDeclRefExpr and VisitCallExpr. The idea is that if a lambda is defined but never called or stored somewhere, then capturing whatever variable in such a lambda is harmless. VisitCallExpr explicitly looks for direct invocation of lambdas and registers its DeclRefExpr to be ignored in VisitDeclRefExpr. If a lambda is being passed to a function, it checks whether its argument is annotated with [[clang::noescape]]. If it's not annotated such, it checks captures for their safety. Because WTF::switchOn could not be annotated with [[clang::noescape]] as function type parameters are variadic template function so we hard-code this function into the checker. In order to check whether "this" pointer is ref-counted type or not, we override TraverseDecl and record the most recent method's declaration. In addition, this PR fixes a bug in isUnsafePtr that it was erroneously checking whether std::nullopt was returned by isUncounted and isUnchecked as opposed to the actual boolean value. Finally, this PR also converts the accompanying test to use -verify and adds a bunch of tests.
1 parent d8d6d11 commit cf6b7bd

File tree

5 files changed

+285
-35
lines changed

5 files changed

+285
-35
lines changed

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

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -222,7 +222,13 @@ std::optional<bool> isUncheckedPtr(const QualType T) {
222222
std::optional<bool> isUnsafePtr(const QualType T) {
223223
if (T->isPointerType() || T->isReferenceType()) {
224224
if (auto *CXXRD = T->getPointeeCXXRecordDecl()) {
225-
return isUncounted(CXXRD) || isUnchecked(CXXRD);
225+
auto isUncountedPtr = isUncounted(CXXRD);
226+
auto isUncheckedPtr = isUnchecked(CXXRD);
227+
if (isUncountedPtr && isUncheckedPtr)
228+
return *isUncountedPtr || *isUncheckedPtr;
229+
if (isUncountedPtr)
230+
return *isUncountedPtr;
231+
return isUncheckedPtr;
226232
}
227233
}
228234
return false;

clang/lib/StaticAnalyzer/Checkers/WebKit/PtrTypesSemantics.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,10 @@ std::optional<bool> isUncountedPtr(const clang::QualType T);
7575
/// class, false if not, std::nullopt if inconclusive.
7676
std::optional<bool> isUncheckedPtr(const clang::QualType T);
7777

78+
/// \returns true if \p T is either a raw pointer or reference to an uncounted
79+
/// or unchecked class, false if not, std::nullopt if inconclusive.
80+
std::optional<bool> isUnsafePtr(const QualType T);
81+
7882
/// \returns true if \p T is a RefPtr, Ref, CheckedPtr, CheckedRef, or its
7983
/// variant, false if not.
8084
bool isSafePtrType(const clang::QualType T);

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

Lines changed: 121 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
//
77
//===----------------------------------------------------------------------===//
88

9+
#include "ASTUtils.h"
910
#include "DiagOutputUtils.h"
1011
#include "PtrTypesSemantics.h"
1112
#include "clang/AST/CXXInheritance.h"
@@ -26,6 +27,7 @@ class UncountedLambdaCapturesChecker
2627
BugType Bug{this, "Lambda capture of uncounted variable",
2728
"WebKit coding guidelines"};
2829
mutable BugReporter *BR = nullptr;
30+
TrivialFunctionAnalysis TFA;
2931

3032
public:
3133
void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
@@ -37,6 +39,11 @@ class UncountedLambdaCapturesChecker
3739
// want to visit those, so we make our own RecursiveASTVisitor.
3840
struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
3941
const UncountedLambdaCapturesChecker *Checker;
42+
llvm::DenseSet<const DeclRefExpr *> DeclRefExprsToIgnore;
43+
QualType ClsType;
44+
45+
using Base = RecursiveASTVisitor<LocalVisitor>;
46+
4047
explicit LocalVisitor(const UncountedLambdaCapturesChecker *Checker)
4148
: Checker(Checker) {
4249
assert(Checker);
@@ -45,32 +52,118 @@ class UncountedLambdaCapturesChecker
4552
bool shouldVisitTemplateInstantiations() const { return true; }
4653
bool shouldVisitImplicitCode() const { return false; }
4754

48-
bool VisitLambdaExpr(LambdaExpr *L) {
49-
Checker->visitLambdaExpr(L);
55+
bool TraverseCXXMethodDecl(CXXMethodDecl *CXXMD) {
56+
llvm::SaveAndRestore SavedDecl(ClsType);
57+
ClsType = CXXMD->getThisType();
58+
return Base::TraverseCXXMethodDecl(CXXMD);
59+
}
60+
61+
bool shouldCheckThis() {
62+
auto result = !ClsType.isNull() ? isUnsafePtr(ClsType) : std::nullopt;
63+
return result && *result;
64+
}
65+
66+
bool VisitDeclRefExpr(DeclRefExpr *DRE) {
67+
if (DeclRefExprsToIgnore.contains(DRE))
68+
return true;
69+
auto *VD = dyn_cast_or_null<VarDecl>(DRE->getDecl());
70+
if (!VD)
71+
return true;
72+
auto *Init = VD->getInit();
73+
if (!Init)
74+
return true;
75+
auto *L = dyn_cast_or_null<LambdaExpr>(Init->IgnoreParenCasts());
76+
if (!L)
77+
return true;
78+
Checker->visitLambdaExpr(L, shouldCheckThis());
5079
return true;
5180
}
81+
82+
// WTF::switchOn(T, F... f) is a variadic template function and couldn't
83+
// be annotated with NOESCAPE. We hard code it here to workaround that.
84+
bool shouldTreatAllArgAsNoEscape(FunctionDecl *Decl) {
85+
auto *NsDecl = Decl->getParent();
86+
if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
87+
return false;
88+
return safeGetName(NsDecl) == "WTF" && safeGetName(Decl) == "switchOn";
89+
}
90+
91+
bool VisitCallExpr(CallExpr *CE) {
92+
checkCalleeLambda(CE);
93+
if (auto *Callee = CE->getDirectCallee()) {
94+
bool TreatAllArgsAsNoEscape = shouldTreatAllArgAsNoEscape(Callee);
95+
unsigned ArgIndex = 0;
96+
for (auto *Param : Callee->parameters()) {
97+
if (ArgIndex >= CE->getNumArgs())
98+
return true;
99+
auto *Arg = CE->getArg(ArgIndex)->IgnoreParenCasts();
100+
if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape) {
101+
if (auto *L = dyn_cast_or_null<LambdaExpr>(Arg)) {
102+
Checker->visitLambdaExpr(L, shouldCheckThis());
103+
}
104+
}
105+
++ArgIndex;
106+
}
107+
}
108+
return true;
109+
}
110+
111+
void checkCalleeLambda(CallExpr *CE) {
112+
auto *Callee = CE->getCallee();
113+
if (!Callee)
114+
return;
115+
auto *DRE = dyn_cast<DeclRefExpr>(Callee->IgnoreParenCasts());
116+
if (!DRE)
117+
return;
118+
auto *MD = dyn_cast_or_null<CXXMethodDecl>(DRE->getDecl());
119+
if (!MD || CE->getNumArgs() != 1)
120+
return;
121+
auto *Arg = CE->getArg(0)->IgnoreParenCasts();
122+
auto *ArgRef = dyn_cast<DeclRefExpr>(Arg);
123+
if (!ArgRef)
124+
return;
125+
auto *VD = dyn_cast_or_null<VarDecl>(ArgRef->getDecl());
126+
if (!VD)
127+
return;
128+
auto *Init = VD->getInit();
129+
if (!Init)
130+
return;
131+
auto *L = dyn_cast_or_null<LambdaExpr>(Init->IgnoreParenCasts());
132+
if (!L)
133+
return;
134+
DeclRefExprsToIgnore.insert(ArgRef);
135+
Checker->visitLambdaExpr(L, shouldCheckThis(),
136+
/* ignoreParamVarDecl */ true);
137+
}
52138
};
53139

54140
LocalVisitor visitor(this);
55141
visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
56142
}
57143

58-
void visitLambdaExpr(LambdaExpr *L) const {
144+
void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis,
145+
bool ignoreParamVarDecl = false) const {
146+
if (TFA.isTrivial(L->getBody()))
147+
return;
59148
for (const LambdaCapture &C : L->captures()) {
60149
if (C.capturesVariable()) {
61150
ValueDecl *CapturedVar = C.getCapturedVar();
151+
if (ignoreParamVarDecl && isa<ParmVarDecl>(CapturedVar))
152+
continue;
62153
QualType CapturedVarQualType = CapturedVar->getType();
63-
if (auto *CapturedVarType = CapturedVarQualType.getTypePtrOrNull()) {
64-
auto IsUncountedPtr = isUncountedPtr(CapturedVarQualType);
65-
if (IsUncountedPtr && *IsUncountedPtr)
66-
reportBug(C, CapturedVar, CapturedVarType);
67-
}
154+
auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType());
155+
if (IsUncountedPtr && *IsUncountedPtr)
156+
reportBug(C, CapturedVar, CapturedVarQualType);
157+
} else if (C.capturesThis() && shouldCheckThis) {
158+
if (ignoreParamVarDecl) // this is always a parameter to this function.
159+
continue;
160+
reportBugOnThisPtr(C);
68161
}
69162
}
70163
}
71164

72165
void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar,
73-
const Type *T) const {
166+
const QualType T) const {
74167
assert(CapturedVar);
75168

76169
SmallString<100> Buf;
@@ -89,7 +182,25 @@ class UncountedLambdaCapturesChecker
89182
}
90183

91184
printQuotedQualifiedName(Os, Capture.getCapturedVar());
92-
Os << " to uncounted type is unsafe.";
185+
Os << " to ref-counted type or CheckedPtr-capable type is unsafe.";
186+
187+
PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
188+
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
189+
BR->emitReport(std::move(Report));
190+
}
191+
192+
void reportBugOnThisPtr(const LambdaCapture &Capture) const {
193+
SmallString<100> Buf;
194+
llvm::raw_svector_ostream Os(Buf);
195+
196+
if (Capture.isExplicit()) {
197+
Os << "Captured ";
198+
} else {
199+
Os << "Implicitly captured ";
200+
}
201+
202+
Os << "raw-pointer 'this' to ref-counted type or CheckedPtr-capable type "
203+
"is unsafe.";
93204

94205
PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager());
95206
auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);

clang/test/Analysis/Checkers/WebKit/mock-types.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,7 +135,9 @@ struct RefCountable {
135135
void ref() {}
136136
void deref() {}
137137
void method();
138+
void constMethod() const;
138139
int trivial() { return 123; }
140+
RefCountable* next();
139141
};
140142

141143
template <typename T> T *downcast(T *t) { return t; }

0 commit comments

Comments
 (0)