1 | //=======- UncountedLambdaCapturesChecker.cpp --------------------*- C++ -*-==// |
2 | // |
3 | // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. |
4 | // See https://llvm.org/LICENSE.txt for license information. |
5 | // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception |
6 | // |
7 | //===----------------------------------------------------------------------===// |
8 | |
9 | #include "ASTUtils.h" |
10 | #include "DiagOutputUtils.h" |
11 | #include "PtrTypesSemantics.h" |
12 | #include "clang/AST/DynamicRecursiveASTVisitor.h" |
13 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
14 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
15 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
16 | #include "clang/StaticAnalyzer/Core/Checker.h" |
17 | #include <optional> |
18 | |
19 | using namespace clang; |
20 | using namespace ento; |
21 | |
22 | namespace { |
23 | class RawPtrRefLambdaCapturesChecker |
24 | : public Checker<check::ASTDecl<TranslationUnitDecl>> { |
25 | private: |
26 | BugType Bug; |
27 | mutable BugReporter *BR = nullptr; |
28 | TrivialFunctionAnalysis TFA; |
29 | |
30 | protected: |
31 | mutable std::optional<RetainTypeChecker> RTC; |
32 | |
33 | public: |
34 | RawPtrRefLambdaCapturesChecker(const char *description) |
35 | : Bug(this, description, "WebKit coding guidelines" ) {} |
36 | |
37 | virtual std::optional<bool> isUnsafePtr(QualType) const = 0; |
38 | virtual bool isPtrType(const std::string &) const = 0; |
39 | virtual const char *ptrKind(QualType QT) const = 0; |
40 | |
41 | void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, |
42 | BugReporter &BRArg) const { |
43 | BR = &BRArg; |
44 | |
45 | // The calls to checkAST* from AnalysisConsumer don't |
46 | // visit template instantiations or lambda classes. We |
47 | // want to visit those, so we make our own RecursiveASTVisitor. |
48 | struct LocalVisitor : DynamicRecursiveASTVisitor { |
49 | const RawPtrRefLambdaCapturesChecker *Checker; |
50 | llvm::DenseSet<const DeclRefExpr *> DeclRefExprsToIgnore; |
51 | llvm::DenseSet<const LambdaExpr *> LambdasToIgnore; |
52 | llvm::DenseSet<const ValueDecl *> ProtectedThisDecls; |
53 | llvm::DenseSet<const CXXConstructExpr *> ConstructToIgnore; |
54 | |
55 | QualType ClsType; |
56 | |
57 | explicit LocalVisitor(const RawPtrRefLambdaCapturesChecker *Checker) |
58 | : Checker(Checker) { |
59 | assert(Checker); |
60 | ShouldVisitTemplateInstantiations = true; |
61 | ShouldVisitImplicitCode = false; |
62 | } |
63 | |
64 | bool TraverseCXXMethodDecl(CXXMethodDecl *CXXMD) override { |
65 | llvm::SaveAndRestore SavedDecl(ClsType); |
66 | if (CXXMD->isInstance()) |
67 | ClsType = CXXMD->getThisType(); |
68 | return DynamicRecursiveASTVisitor::TraverseCXXMethodDecl(D: CXXMD); |
69 | } |
70 | |
71 | bool TraverseObjCMethodDecl(ObjCMethodDecl *OCMD) override { |
72 | llvm::SaveAndRestore SavedDecl(ClsType); |
73 | if (OCMD && OCMD->isInstanceMethod()) { |
74 | if (auto *ImplParamDecl = OCMD->getSelfDecl()) |
75 | ClsType = ImplParamDecl->getType(); |
76 | } |
77 | return DynamicRecursiveASTVisitor::TraverseObjCMethodDecl(D: OCMD); |
78 | } |
79 | |
80 | bool VisitTypedefDecl(TypedefDecl *TD) override { |
81 | if (Checker->RTC) |
82 | Checker->RTC->visitTypedef(TD); |
83 | return true; |
84 | } |
85 | |
86 | bool shouldCheckThis() { |
87 | auto result = |
88 | !ClsType.isNull() ? Checker->isUnsafePtr(ClsType) : std::nullopt; |
89 | return result && *result; |
90 | } |
91 | |
92 | bool VisitLambdaExpr(LambdaExpr *L) override { |
93 | if (LambdasToIgnore.contains(V: L)) |
94 | return true; |
95 | Checker->visitLambdaExpr(L, shouldCheckThis: shouldCheckThis() && !hasProtectedThis(L), |
96 | T: ClsType); |
97 | return true; |
98 | } |
99 | |
100 | bool VisitVarDecl(VarDecl *VD) override { |
101 | auto *Init = VD->getInit(); |
102 | if (!Init) |
103 | return true; |
104 | auto *L = dyn_cast_or_null<LambdaExpr>(Val: Init->IgnoreParenCasts()); |
105 | if (!L) |
106 | return true; |
107 | LambdasToIgnore.insert(V: L); // Evaluate lambdas in VisitDeclRefExpr. |
108 | return true; |
109 | } |
110 | |
111 | bool VisitDeclRefExpr(DeclRefExpr *DRE) override { |
112 | if (DeclRefExprsToIgnore.contains(V: DRE)) |
113 | return true; |
114 | auto *VD = dyn_cast_or_null<VarDecl>(Val: DRE->getDecl()); |
115 | if (!VD) |
116 | return true; |
117 | auto *Init = VD->getInit(); |
118 | if (!Init) |
119 | return true; |
120 | auto *L = dyn_cast_or_null<LambdaExpr>(Val: Init->IgnoreParenCasts()); |
121 | if (!L) |
122 | return true; |
123 | LambdasToIgnore.insert(V: L); |
124 | Checker->visitLambdaExpr(L, shouldCheckThis: shouldCheckThis() && !hasProtectedThis(L), |
125 | T: ClsType); |
126 | return true; |
127 | } |
128 | |
129 | bool shouldTreatAllArgAsNoEscape(FunctionDecl *Decl) { |
130 | auto *NsDecl = Decl->getParent(); |
131 | if (!NsDecl || !isa<NamespaceDecl>(Val: 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(ASTNode: NsDecl) == "WTF" && safeGetName(ASTNode: Decl) == "switchOn" ) |
136 | return true; |
137 | // Treat every argument of functions in std::ranges as noescape. |
138 | if (safeGetName(ASTNode: NsDecl) == "ranges" ) { |
139 | if (auto *OuterDecl = NsDecl->getParent(); |
140 | OuterDecl && isa<NamespaceDecl>(Val: OuterDecl) && |
141 | safeGetName(ASTNode: OuterDecl) == "std" ) |
142 | return true; |
143 | } |
144 | return false; |
145 | } |
146 | |
147 | bool VisitCXXConstructExpr(CXXConstructExpr *CE) override { |
148 | if (ConstructToIgnore.contains(V: CE)) |
149 | return true; |
150 | if (auto *Callee = CE->getConstructor()) { |
151 | unsigned ArgIndex = 0; |
152 | for (auto *Param : Callee->parameters()) { |
153 | if (ArgIndex >= CE->getNumArgs()) |
154 | return true; |
155 | auto *Arg = CE->getArg(Arg: ArgIndex)->IgnoreParenCasts(); |
156 | if (auto *L = findLambdaInArg(E: Arg)) { |
157 | LambdasToIgnore.insert(V: L); |
158 | if (!Param->hasAttr<NoEscapeAttr>()) |
159 | Checker->visitLambdaExpr( |
160 | L, shouldCheckThis: shouldCheckThis() && !hasProtectedThis(L), T: ClsType); |
161 | } |
162 | ++ArgIndex; |
163 | } |
164 | } |
165 | return true; |
166 | } |
167 | |
168 | bool VisitCallExpr(CallExpr *CE) override { |
169 | checkCalleeLambda(CE); |
170 | if (auto *Callee = CE->getDirectCallee()) { |
171 | unsigned ArgIndex = isa<CXXOperatorCallExpr>(Val: CE); |
172 | bool TreatAllArgsAsNoEscape = shouldTreatAllArgAsNoEscape(Decl: Callee); |
173 | for (auto *Param : Callee->parameters()) { |
174 | if (ArgIndex >= CE->getNumArgs()) |
175 | return true; |
176 | auto *Arg = CE->getArg(Arg: ArgIndex)->IgnoreParenCasts(); |
177 | if (auto *L = findLambdaInArg(E: Arg)) { |
178 | LambdasToIgnore.insert(V: L); |
179 | if (!Param->hasAttr<NoEscapeAttr>() && !TreatAllArgsAsNoEscape) |
180 | Checker->visitLambdaExpr( |
181 | L, shouldCheckThis: shouldCheckThis() && !hasProtectedThis(L), T: ClsType); |
182 | } |
183 | ++ArgIndex; |
184 | } |
185 | } |
186 | return true; |
187 | } |
188 | |
189 | LambdaExpr *findLambdaInArg(Expr *E) { |
190 | if (auto *Lambda = dyn_cast_or_null<LambdaExpr>(Val: E)) |
191 | return Lambda; |
192 | auto *TempExpr = dyn_cast_or_null<CXXBindTemporaryExpr>(Val: E); |
193 | if (!TempExpr) |
194 | return nullptr; |
195 | E = TempExpr->getSubExpr()->IgnoreParenCasts(); |
196 | if (!E) |
197 | return nullptr; |
198 | if (auto *Lambda = dyn_cast<LambdaExpr>(Val: E)) |
199 | return Lambda; |
200 | auto *CE = dyn_cast_or_null<CXXConstructExpr>(Val: E); |
201 | if (!CE || !CE->getNumArgs()) |
202 | return nullptr; |
203 | auto *CtorArg = CE->getArg(Arg: 0)->IgnoreParenCasts(); |
204 | if (!CtorArg) |
205 | return nullptr; |
206 | auto *InnerCE = dyn_cast_or_null<CXXConstructExpr>(Val: CtorArg); |
207 | if (InnerCE && InnerCE->getNumArgs()) |
208 | CtorArg = InnerCE->getArg(Arg: 0)->IgnoreParenCasts(); |
209 | auto updateIgnoreList = [&] { |
210 | ConstructToIgnore.insert(V: CE); |
211 | if (InnerCE) |
212 | ConstructToIgnore.insert(V: InnerCE); |
213 | }; |
214 | if (auto *Lambda = dyn_cast<LambdaExpr>(Val: CtorArg)) { |
215 | updateIgnoreList(); |
216 | return Lambda; |
217 | } |
218 | if (auto *TempExpr = dyn_cast<CXXBindTemporaryExpr>(Val: CtorArg)) { |
219 | E = TempExpr->getSubExpr()->IgnoreParenCasts(); |
220 | if (auto *Lambda = dyn_cast<LambdaExpr>(Val: E)) { |
221 | updateIgnoreList(); |
222 | return Lambda; |
223 | } |
224 | } |
225 | auto *DRE = dyn_cast<DeclRefExpr>(Val: CtorArg); |
226 | if (!DRE) |
227 | return nullptr; |
228 | auto *VD = dyn_cast_or_null<VarDecl>(Val: DRE->getDecl()); |
229 | if (!VD) |
230 | return nullptr; |
231 | auto *Init = VD->getInit(); |
232 | if (!Init) |
233 | return nullptr; |
234 | if (auto *Lambda = dyn_cast<LambdaExpr>(Val: Init)) { |
235 | updateIgnoreList(); |
236 | return Lambda; |
237 | } |
238 | TempExpr = dyn_cast<CXXBindTemporaryExpr>(Val: Init->IgnoreParenCasts()); |
239 | if (!TempExpr) |
240 | return nullptr; |
241 | updateIgnoreList(); |
242 | return dyn_cast_or_null<LambdaExpr>(Val: TempExpr->getSubExpr()); |
243 | } |
244 | |
245 | void checkCalleeLambda(CallExpr *CE) { |
246 | auto *Callee = CE->getCallee(); |
247 | if (!Callee) |
248 | return; |
249 | auto *DRE = dyn_cast<DeclRefExpr>(Val: Callee->IgnoreParenCasts()); |
250 | if (!DRE) |
251 | return; |
252 | auto *MD = dyn_cast_or_null<CXXMethodDecl>(Val: DRE->getDecl()); |
253 | if (!MD || CE->getNumArgs() < 1) |
254 | return; |
255 | auto *Arg = CE->getArg(Arg: 0)->IgnoreParenCasts(); |
256 | if (auto *L = dyn_cast_or_null<LambdaExpr>(Val: Arg)) { |
257 | LambdasToIgnore.insert(V: L); // Calling a lambda upon creation is safe. |
258 | return; |
259 | } |
260 | auto *ArgRef = dyn_cast<DeclRefExpr>(Val: Arg); |
261 | if (!ArgRef) |
262 | return; |
263 | auto *VD = dyn_cast_or_null<VarDecl>(Val: ArgRef->getDecl()); |
264 | if (!VD) |
265 | return; |
266 | auto *Init = VD->getInit(); |
267 | if (!Init) |
268 | return; |
269 | auto *L = dyn_cast_or_null<LambdaExpr>(Val: Init->IgnoreParenCasts()); |
270 | if (!L) |
271 | return; |
272 | DeclRefExprsToIgnore.insert(V: ArgRef); |
273 | LambdasToIgnore.insert(V: L); |
274 | } |
275 | |
276 | bool hasProtectedThis(LambdaExpr *L) { |
277 | for (const LambdaCapture &OtherCapture : L->captures()) { |
278 | if (!OtherCapture.capturesVariable()) |
279 | continue; |
280 | if (auto *ValueDecl = OtherCapture.getCapturedVar()) { |
281 | if (declProtectsThis(ValueDecl)) { |
282 | ProtectedThisDecls.insert(V: ValueDecl); |
283 | return true; |
284 | } |
285 | } |
286 | } |
287 | return false; |
288 | } |
289 | |
290 | bool declProtectsThis(const ValueDecl *ValueDecl) const { |
291 | auto *VD = dyn_cast<VarDecl>(Val: ValueDecl); |
292 | if (!VD) |
293 | return false; |
294 | auto *Init = VD->getInit(); |
295 | if (!Init) |
296 | return false; |
297 | const Expr *Arg = Init->IgnoreParenCasts(); |
298 | do { |
299 | if (auto *BTE = dyn_cast<CXXBindTemporaryExpr>(Val: Arg)) |
300 | Arg = BTE->getSubExpr()->IgnoreParenCasts(); |
301 | if (auto *CE = dyn_cast<CXXConstructExpr>(Val: Arg)) { |
302 | auto *Ctor = CE->getConstructor(); |
303 | if (!Ctor) |
304 | return false; |
305 | auto clsName = safeGetName(ASTNode: Ctor->getParent()); |
306 | if (Checker->isPtrType(clsName) && CE->getNumArgs()) { |
307 | Arg = CE->getArg(Arg: 0)->IgnoreParenCasts(); |
308 | continue; |
309 | } |
310 | if (auto *Type = ClsType.getTypePtrOrNull()) { |
311 | if (auto *CXXR = Type->getPointeeCXXRecordDecl()) { |
312 | if (CXXR == Ctor->getParent() && Ctor->isMoveConstructor() && |
313 | CE->getNumArgs() == 1) { |
314 | Arg = CE->getArg(Arg: 0)->IgnoreParenCasts(); |
315 | continue; |
316 | } |
317 | } |
318 | } |
319 | return false; |
320 | } |
321 | if (auto *CE = dyn_cast<CallExpr>(Val: Arg)) { |
322 | if (CE->isCallToStdMove() && CE->getNumArgs() == 1) { |
323 | Arg = CE->getArg(Arg: 0)->IgnoreParenCasts(); |
324 | continue; |
325 | } |
326 | if (auto *Callee = CE->getDirectCallee()) { |
327 | if (isCtorOfSafePtr(F: Callee) && CE->getNumArgs() == 1) { |
328 | Arg = CE->getArg(Arg: 0)->IgnoreParenCasts(); |
329 | continue; |
330 | } |
331 | } |
332 | } |
333 | if (auto *OpCE = dyn_cast<CXXOperatorCallExpr>(Val: Arg)) { |
334 | auto OpCode = OpCE->getOperator(); |
335 | if (OpCode == OO_Star || OpCode == OO_Amp) { |
336 | auto *Callee = OpCE->getDirectCallee(); |
337 | if (!Callee) |
338 | return false; |
339 | auto clsName = safeGetName(ASTNode: Callee->getParent()); |
340 | if (!Checker->isPtrType(clsName) || !OpCE->getNumArgs()) |
341 | return false; |
342 | Arg = OpCE->getArg(Arg: 0)->IgnoreParenCasts(); |
343 | continue; |
344 | } |
345 | } |
346 | if (auto *UO = dyn_cast<UnaryOperator>(Val: Arg)) { |
347 | auto OpCode = UO->getOpcode(); |
348 | if (OpCode == UO_Deref || OpCode == UO_AddrOf) { |
349 | Arg = UO->getSubExpr()->IgnoreParenCasts(); |
350 | continue; |
351 | } |
352 | } |
353 | break; |
354 | } while (Arg); |
355 | if (auto *DRE = dyn_cast<DeclRefExpr>(Val: Arg)) { |
356 | auto *Decl = DRE->getDecl(); |
357 | if (auto *ImplicitParam = dyn_cast<ImplicitParamDecl>(Val: Decl)) { |
358 | auto kind = ImplicitParam->getParameterKind(); |
359 | return kind == ImplicitParamKind::ObjCSelf || |
360 | kind == ImplicitParamKind::CXXThis; |
361 | } |
362 | return ProtectedThisDecls.contains(V: Decl); |
363 | } |
364 | return isa<CXXThisExpr>(Val: Arg); |
365 | } |
366 | }; |
367 | |
368 | LocalVisitor visitor(this); |
369 | if (RTC) |
370 | RTC->visitTranslationUnitDecl(TUD); |
371 | visitor.TraverseDecl(D: const_cast<TranslationUnitDecl *>(TUD)); |
372 | } |
373 | |
374 | void visitLambdaExpr(LambdaExpr *L, bool shouldCheckThis, const QualType T, |
375 | bool ignoreParamVarDecl = false) const { |
376 | if (TFA.isTrivial(S: L->getBody())) |
377 | return; |
378 | for (const LambdaCapture &C : L->captures()) { |
379 | if (C.capturesVariable()) { |
380 | ValueDecl *CapturedVar = C.getCapturedVar(); |
381 | if (ignoreParamVarDecl && isa<ParmVarDecl>(Val: CapturedVar)) |
382 | continue; |
383 | if (auto *ImplicitParam = dyn_cast<ImplicitParamDecl>(Val: CapturedVar)) { |
384 | auto kind = ImplicitParam->getParameterKind(); |
385 | if ((kind == ImplicitParamKind::ObjCSelf || |
386 | kind == ImplicitParamKind::CXXThis) && |
387 | !shouldCheckThis) |
388 | continue; |
389 | } |
390 | QualType CapturedVarQualType = CapturedVar->getType(); |
391 | auto IsUncountedPtr = isUnsafePtr(CapturedVar->getType()); |
392 | if (C.getCaptureKind() == LCK_ByCopy && |
393 | CapturedVarQualType->isReferenceType()) |
394 | continue; |
395 | if (IsUncountedPtr && *IsUncountedPtr) |
396 | reportBug(Capture: C, CapturedVar, T: CapturedVarQualType, L); |
397 | } else if (C.capturesThis() && shouldCheckThis) { |
398 | if (ignoreParamVarDecl) // this is always a parameter to this function. |
399 | continue; |
400 | reportBugOnThisPtr(Capture: C, T); |
401 | } |
402 | } |
403 | } |
404 | |
405 | void reportBug(const LambdaCapture &Capture, ValueDecl *CapturedVar, |
406 | const QualType T, LambdaExpr *L) const { |
407 | assert(CapturedVar); |
408 | |
409 | auto Location = Capture.getLocation(); |
410 | if (isa<ImplicitParamDecl>(Val: CapturedVar) && !Location.isValid()) |
411 | Location = L->getBeginLoc(); |
412 | |
413 | SmallString<100> Buf; |
414 | llvm::raw_svector_ostream Os(Buf); |
415 | |
416 | if (Capture.isExplicit()) { |
417 | Os << "Captured " ; |
418 | } else { |
419 | Os << "Implicitly captured " ; |
420 | } |
421 | if (isa<PointerType>(Val: T) || isa<ObjCObjectPointerType>(Val: T)) { |
422 | Os << "raw-pointer " ; |
423 | } else { |
424 | Os << "reference " ; |
425 | } |
426 | |
427 | printQuotedQualifiedName(Os, D: CapturedVar); |
428 | Os << " to " << ptrKind(QT: T) << " type is unsafe." ; |
429 | |
430 | PathDiagnosticLocation BSLoc(Location, BR->getSourceManager()); |
431 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
432 | BR->emitReport(R: std::move(Report)); |
433 | } |
434 | |
435 | void reportBugOnThisPtr(const LambdaCapture &Capture, |
436 | const QualType T) const { |
437 | SmallString<100> Buf; |
438 | llvm::raw_svector_ostream Os(Buf); |
439 | |
440 | if (Capture.isExplicit()) { |
441 | Os << "Captured " ; |
442 | } else { |
443 | Os << "Implicitly captured " ; |
444 | } |
445 | |
446 | Os << "raw-pointer 'this' to " << ptrKind(QT: T) << " type is unsafe." ; |
447 | |
448 | PathDiagnosticLocation BSLoc(Capture.getLocation(), BR->getSourceManager()); |
449 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
450 | BR->emitReport(R: std::move(Report)); |
451 | } |
452 | }; |
453 | |
454 | class UncountedLambdaCapturesChecker : public RawPtrRefLambdaCapturesChecker { |
455 | public: |
456 | UncountedLambdaCapturesChecker() |
457 | : RawPtrRefLambdaCapturesChecker("Lambda capture of uncounted or " |
458 | "unchecked variable" ) {} |
459 | |
460 | std::optional<bool> isUnsafePtr(QualType QT) const final { |
461 | auto result1 = isUncountedPtr(T: QT); |
462 | auto result2 = isUncheckedPtr(T: QT); |
463 | if (result1 && *result1) |
464 | return true; |
465 | if (result2 && *result2) |
466 | return true; |
467 | if (result1) |
468 | return *result1; |
469 | return result2; |
470 | } |
471 | |
472 | virtual bool isPtrType(const std::string &Name) const final { |
473 | return isRefType(Name) || isCheckedPtr(Name); |
474 | } |
475 | |
476 | const char *ptrKind(QualType QT) const final { |
477 | if (isUncounted(T: QT)) |
478 | return "uncounted" ; |
479 | return "unchecked" ; |
480 | } |
481 | }; |
482 | |
483 | class UnretainedLambdaCapturesChecker : public RawPtrRefLambdaCapturesChecker { |
484 | public: |
485 | UnretainedLambdaCapturesChecker() |
486 | : RawPtrRefLambdaCapturesChecker("Lambda capture of unretained " |
487 | "variables" ) { |
488 | RTC = RetainTypeChecker(); |
489 | } |
490 | |
491 | std::optional<bool> isUnsafePtr(QualType QT) const final { |
492 | return RTC->isUnretained(QT); |
493 | } |
494 | |
495 | virtual bool isPtrType(const std::string &Name) const final { |
496 | return isRetainPtr(Name); |
497 | } |
498 | |
499 | const char *ptrKind(QualType QT) const final { return "unretained" ; } |
500 | }; |
501 | |
502 | } // namespace |
503 | |
504 | void ento::registerUncountedLambdaCapturesChecker(CheckerManager &Mgr) { |
505 | Mgr.registerChecker<UncountedLambdaCapturesChecker>(); |
506 | } |
507 | |
508 | bool ento::shouldRegisterUncountedLambdaCapturesChecker( |
509 | const CheckerManager &mgr) { |
510 | return true; |
511 | } |
512 | |
513 | void ento::registerUnretainedLambdaCapturesChecker(CheckerManager &Mgr) { |
514 | Mgr.registerChecker<UnretainedLambdaCapturesChecker>(); |
515 | } |
516 | |
517 | bool ento::shouldRegisterUnretainedLambdaCapturesChecker( |
518 | const CheckerManager &mgr) { |
519 | return true; |
520 | } |
521 | |