1 | //=======- RawPtrRefCallArgsChecker.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/Decl.h" |
13 | #include "clang/AST/DeclCXX.h" |
14 | #include "clang/AST/DynamicRecursiveASTVisitor.h" |
15 | #include "clang/Analysis/DomainSpecific/CocoaConventions.h" |
16 | #include "clang/Basic/SourceLocation.h" |
17 | #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" |
18 | #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" |
19 | #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h" |
20 | #include "clang/StaticAnalyzer/Core/Checker.h" |
21 | #include "llvm/Support/SaveAndRestore.h" |
22 | #include <optional> |
23 | |
24 | using namespace clang; |
25 | using namespace ento; |
26 | |
27 | namespace { |
28 | |
29 | class RawPtrRefCallArgsChecker |
30 | : public Checker<check::ASTDecl<TranslationUnitDecl>> { |
31 | BugType Bug; |
32 | mutable BugReporter *BR; |
33 | |
34 | TrivialFunctionAnalysis TFA; |
35 | EnsureFunctionAnalysis EFA; |
36 | |
37 | protected: |
38 | mutable std::optional<RetainTypeChecker> RTC; |
39 | |
40 | public: |
41 | RawPtrRefCallArgsChecker(const char *description) |
42 | : Bug(this, description, "WebKit coding guidelines" ) {} |
43 | |
44 | virtual std::optional<bool> isUnsafeType(QualType) const = 0; |
45 | virtual std::optional<bool> isUnsafePtr(QualType) const = 0; |
46 | virtual bool isSafePtr(const CXXRecordDecl *Record) const = 0; |
47 | virtual bool isSafePtrType(const QualType type) const = 0; |
48 | virtual bool isSafeExpr(const Expr *) const { return false; } |
49 | virtual const char *ptrKind() const = 0; |
50 | |
51 | void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR, |
52 | BugReporter &BRArg) const { |
53 | BR = &BRArg; |
54 | |
55 | // The calls to checkAST* from AnalysisConsumer don't |
56 | // visit template instantiations or lambda classes. We |
57 | // want to visit those, so we make our own RecursiveASTVisitor. |
58 | struct LocalVisitor : DynamicRecursiveASTVisitor { |
59 | const RawPtrRefCallArgsChecker *Checker; |
60 | Decl *DeclWithIssue{nullptr}; |
61 | |
62 | explicit LocalVisitor(const RawPtrRefCallArgsChecker *Checker) |
63 | : Checker(Checker) { |
64 | assert(Checker); |
65 | ShouldVisitTemplateInstantiations = true; |
66 | ShouldVisitImplicitCode = false; |
67 | } |
68 | |
69 | bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) override { |
70 | if (isSmartPtrClass(Name: safeGetName(ASTNode: Decl))) |
71 | return true; |
72 | return DynamicRecursiveASTVisitor::TraverseClassTemplateDecl(D: Decl); |
73 | } |
74 | |
75 | bool TraverseDecl(Decl *D) override { |
76 | llvm::SaveAndRestore SavedDecl(DeclWithIssue); |
77 | if (D && (isa<FunctionDecl>(Val: D) || isa<ObjCMethodDecl>(Val: D))) |
78 | DeclWithIssue = D; |
79 | return DynamicRecursiveASTVisitor::TraverseDecl(D); |
80 | } |
81 | |
82 | bool VisitCallExpr(CallExpr *CE) override { |
83 | Checker->visitCallExpr(CE, D: DeclWithIssue); |
84 | return true; |
85 | } |
86 | |
87 | bool VisitTypedefDecl(TypedefDecl *TD) override { |
88 | if (Checker->RTC) |
89 | Checker->RTC->visitTypedef(TD); |
90 | return true; |
91 | } |
92 | |
93 | bool VisitObjCMessageExpr(ObjCMessageExpr *ObjCMsgExpr) override { |
94 | Checker->visitObjCMessageExpr(E: ObjCMsgExpr, D: DeclWithIssue); |
95 | return true; |
96 | } |
97 | }; |
98 | |
99 | LocalVisitor visitor(this); |
100 | if (RTC) |
101 | RTC->visitTranslationUnitDecl(TUD); |
102 | visitor.TraverseDecl(D: const_cast<TranslationUnitDecl *>(TUD)); |
103 | } |
104 | |
105 | void visitCallExpr(const CallExpr *CE, const Decl *D) const { |
106 | if (shouldSkipCall(CE)) |
107 | return; |
108 | |
109 | if (auto *F = CE->getDirectCallee()) { |
110 | // Skip the first argument for overloaded member operators (e. g. lambda |
111 | // or std::function call operator). |
112 | unsigned ArgIdx = |
113 | isa<CXXOperatorCallExpr>(Val: CE) && isa_and_nonnull<CXXMethodDecl>(Val: F); |
114 | |
115 | if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(Val: CE)) { |
116 | if (auto *MD = MemberCallExpr->getMethodDecl()) { |
117 | auto name = safeGetName(ASTNode: MD); |
118 | if (name == "ref" || name == "deref" ) |
119 | return; |
120 | if (name == "incrementCheckedPtrCount" || |
121 | name == "decrementCheckedPtrCount" ) |
122 | return; |
123 | } |
124 | auto *E = MemberCallExpr->getImplicitObjectArgument(); |
125 | QualType ArgType = MemberCallExpr->getObjectType().getCanonicalType(); |
126 | std::optional<bool> IsUnsafe = isUnsafeType(ArgType); |
127 | if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Arg: E)) |
128 | reportBugOnThis(CallArg: E, DeclWithIssue: D); |
129 | } |
130 | |
131 | for (auto P = F->param_begin(); |
132 | // FIXME: Also check variadic function parameters. |
133 | // FIXME: Also check default function arguments. Probably a different |
134 | // checker. In case there are default arguments the call can have |
135 | // fewer arguments than the callee has parameters. |
136 | P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx) { |
137 | // TODO: attributes. |
138 | // if ((*P)->hasAttr<SafeRefCntblRawPtrAttr>()) |
139 | // continue; |
140 | |
141 | QualType ArgType = (*P)->getType(); |
142 | // FIXME: more complex types (arrays, references to raw pointers, etc) |
143 | std::optional<bool> IsUncounted = isUnsafePtr(ArgType); |
144 | if (!IsUncounted || !(*IsUncounted)) |
145 | continue; |
146 | |
147 | const auto *Arg = CE->getArg(Arg: ArgIdx); |
148 | |
149 | if (auto *defaultArg = dyn_cast<CXXDefaultArgExpr>(Val: Arg)) |
150 | Arg = defaultArg->getExpr(); |
151 | |
152 | if (isPtrOriginSafe(Arg)) |
153 | continue; |
154 | |
155 | reportBug(CallArg: Arg, Param: *P, DeclWithIssue: D); |
156 | } |
157 | for (; ArgIdx < CE->getNumArgs(); ++ArgIdx) { |
158 | const auto *Arg = CE->getArg(Arg: ArgIdx); |
159 | auto ArgType = Arg->getType(); |
160 | std::optional<bool> IsUncounted = isUnsafePtr(ArgType); |
161 | if (!IsUncounted || !(*IsUncounted)) |
162 | continue; |
163 | |
164 | if (auto *defaultArg = dyn_cast<CXXDefaultArgExpr>(Val: Arg)) |
165 | Arg = defaultArg->getExpr(); |
166 | |
167 | if (isPtrOriginSafe(Arg)) |
168 | continue; |
169 | |
170 | reportBug(CallArg: Arg, Param: nullptr, DeclWithIssue: D); |
171 | } |
172 | } |
173 | } |
174 | |
175 | void visitObjCMessageExpr(const ObjCMessageExpr *E, const Decl *D) const { |
176 | if (BR->getSourceManager().isInSystemHeader(Loc: E->getExprLoc())) |
177 | return; |
178 | |
179 | auto Selector = E->getSelector(); |
180 | if (auto *Receiver = E->getInstanceReceiver()) { |
181 | std::optional<bool> IsUnsafe = isUnsafePtr(E->getReceiverType()); |
182 | if (IsUnsafe && *IsUnsafe && !isPtrOriginSafe(Arg: Receiver)) { |
183 | if (auto *InnerMsg = dyn_cast<ObjCMessageExpr>(Val: Receiver)) { |
184 | auto InnerSelector = InnerMsg->getSelector(); |
185 | if (InnerSelector.getNameForSlot(argIndex: 0) == "alloc" && |
186 | Selector.getNameForSlot(argIndex: 0).starts_with(Prefix: "init" )) |
187 | return; |
188 | } |
189 | reportBugOnReceiver(CallArg: Receiver, DeclWithIssue: D); |
190 | } |
191 | } |
192 | |
193 | auto *MethodDecl = E->getMethodDecl(); |
194 | if (!MethodDecl) |
195 | return; |
196 | |
197 | auto ArgCount = E->getNumArgs(); |
198 | for (unsigned i = 0; i < ArgCount; ++i) { |
199 | auto *Arg = E->getArg(Arg: i); |
200 | bool hasParam = i < MethodDecl->param_size(); |
201 | auto *Param = hasParam ? MethodDecl->getParamDecl(Idx: i) : nullptr; |
202 | auto ArgType = Arg->getType(); |
203 | std::optional<bool> IsUnsafe = isUnsafePtr(ArgType); |
204 | if (!IsUnsafe || !(*IsUnsafe)) |
205 | continue; |
206 | if (isPtrOriginSafe(Arg)) |
207 | continue; |
208 | reportBug(CallArg: Arg, Param, DeclWithIssue: D); |
209 | } |
210 | } |
211 | |
212 | bool isPtrOriginSafe(const Expr *Arg) const { |
213 | return tryToFindPtrOrigin( |
214 | E: Arg, /*StopAtFirstRefCountedObj=*/true, |
215 | isSafePtr: [&](const clang::CXXRecordDecl *Record) { return isSafePtr(Record); }, |
216 | isSafePtrType: [&](const clang::QualType T) { return isSafePtrType(type: T); }, |
217 | callback: [&](const clang::Expr *ArgOrigin, bool IsSafe) { |
218 | if (IsSafe) |
219 | return true; |
220 | if (isa<CXXNullPtrLiteralExpr>(Val: ArgOrigin)) { |
221 | // foo(nullptr) |
222 | return true; |
223 | } |
224 | if (isa<IntegerLiteral>(Val: ArgOrigin)) { |
225 | // FIXME: Check the value. |
226 | // foo(NULL) |
227 | return true; |
228 | } |
229 | if (isa<ObjCStringLiteral>(Val: ArgOrigin)) |
230 | return true; |
231 | if (isASafeCallArg(E: ArgOrigin)) |
232 | return true; |
233 | if (EFA.isACallToEnsureFn(E: ArgOrigin)) |
234 | return true; |
235 | if (isSafeExpr(ArgOrigin)) |
236 | return true; |
237 | return false; |
238 | }); |
239 | } |
240 | |
241 | bool shouldSkipCall(const CallExpr *CE) const { |
242 | const auto *Callee = CE->getDirectCallee(); |
243 | |
244 | if (BR->getSourceManager().isInSystemHeader(Loc: CE->getExprLoc())) |
245 | return true; |
246 | |
247 | if (Callee && TFA.isTrivial(D: Callee) && !Callee->isVirtualAsWritten()) |
248 | return true; |
249 | |
250 | if (isTrivialBuiltinFunction(F: Callee)) |
251 | return true; |
252 | |
253 | if (CE->getNumArgs() == 0) |
254 | return false; |
255 | |
256 | // If an assignment is problematic we should warn about the sole existence |
257 | // of object on LHS. |
258 | if (auto *MemberOp = dyn_cast<CXXOperatorCallExpr>(Val: CE)) { |
259 | // Note: assignemnt to built-in type isn't derived from CallExpr. |
260 | if (MemberOp->getOperator() == |
261 | OO_Equal) { // Ignore assignment to Ref/RefPtr. |
262 | auto *callee = MemberOp->getDirectCallee(); |
263 | if (auto *calleeDecl = dyn_cast<CXXMethodDecl>(Val: callee)) { |
264 | if (const CXXRecordDecl *classDecl = calleeDecl->getParent()) { |
265 | if (isSafePtr(Record: classDecl)) |
266 | return true; |
267 | } |
268 | } |
269 | } |
270 | if (MemberOp->isAssignmentOp()) |
271 | return false; |
272 | } |
273 | |
274 | if (!Callee) |
275 | return false; |
276 | |
277 | if (isMethodOnWTFContainerType(Decl: Callee)) |
278 | return true; |
279 | |
280 | auto overloadedOperatorType = Callee->getOverloadedOperator(); |
281 | if (overloadedOperatorType == OO_EqualEqual || |
282 | overloadedOperatorType == OO_ExclaimEqual || |
283 | overloadedOperatorType == OO_LessEqual || |
284 | overloadedOperatorType == OO_GreaterEqual || |
285 | overloadedOperatorType == OO_Spaceship || |
286 | overloadedOperatorType == OO_AmpAmp || |
287 | overloadedOperatorType == OO_PipePipe) |
288 | return true; |
289 | |
290 | if (isCtorOfSafePtr(F: Callee) || isPtrConversion(F: Callee)) |
291 | return true; |
292 | |
293 | auto name = safeGetName(ASTNode: Callee); |
294 | if (name == "adoptRef" || name == "getPtr" || name == "WeakPtr" || |
295 | name == "is" || name == "equal" || name == "hash" || name == "isType" || |
296 | // FIXME: Most/all of these should be implemented via attributes. |
297 | name == "CFEqual" || name == "equalIgnoringASCIICase" || |
298 | name == "equalIgnoringASCIICaseCommon" || |
299 | name == "equalIgnoringNullity" || name == "toString" ) |
300 | return true; |
301 | |
302 | return false; |
303 | } |
304 | |
305 | bool isMethodOnWTFContainerType(const FunctionDecl *Decl) const { |
306 | if (!isa<CXXMethodDecl>(Val: Decl)) |
307 | return false; |
308 | auto *ClassDecl = Decl->getParent(); |
309 | if (!ClassDecl || !isa<CXXRecordDecl>(Val: ClassDecl)) |
310 | return false; |
311 | |
312 | auto *NsDecl = ClassDecl->getParent(); |
313 | if (!NsDecl || !isa<NamespaceDecl>(Val: NsDecl)) |
314 | return false; |
315 | |
316 | auto MethodName = safeGetName(ASTNode: Decl); |
317 | auto ClsNameStr = safeGetName(ASTNode: ClassDecl); |
318 | StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef. |
319 | auto NamespaceName = safeGetName(ASTNode: NsDecl); |
320 | // FIXME: These should be implemented via attributes. |
321 | return NamespaceName == "WTF" && |
322 | (MethodName == "find" || MethodName == "findIf" || |
323 | MethodName == "reverseFind" || MethodName == "reverseFindIf" || |
324 | MethodName == "findIgnoringASCIICase" || MethodName == "get" || |
325 | MethodName == "inlineGet" || MethodName == "contains" || |
326 | MethodName == "containsIf" || |
327 | MethodName == "containsIgnoringASCIICase" || |
328 | MethodName == "startsWith" || MethodName == "endsWith" || |
329 | MethodName == "startsWithIgnoringASCIICase" || |
330 | MethodName == "endsWithIgnoringASCIICase" || |
331 | MethodName == "substring" ) && |
332 | (ClsName.ends_with(Suffix: "Vector" ) || ClsName.ends_with(Suffix: "Set" ) || |
333 | ClsName.ends_with(Suffix: "Map" ) || ClsName == "StringImpl" || |
334 | ClsName.ends_with(Suffix: "String" )); |
335 | } |
336 | |
337 | void reportBug(const Expr *CallArg, const ParmVarDecl *Param, |
338 | const Decl *DeclWithIssue) const { |
339 | assert(CallArg); |
340 | |
341 | SmallString<100> Buf; |
342 | llvm::raw_svector_ostream Os(Buf); |
343 | |
344 | const std::string paramName = safeGetName(ASTNode: Param); |
345 | Os << "Call argument" ; |
346 | if (!paramName.empty()) { |
347 | Os << " for parameter " ; |
348 | printQuotedQualifiedName(Os, D: Param); |
349 | } |
350 | Os << " is " << ptrKind() << " and unsafe." ; |
351 | |
352 | bool usesDefaultArgValue = isa<CXXDefaultArgExpr>(Val: CallArg) && Param; |
353 | const SourceLocation SrcLocToReport = |
354 | usesDefaultArgValue ? Param->getDefaultArg()->getExprLoc() |
355 | : CallArg->getSourceRange().getBegin(); |
356 | |
357 | PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager()); |
358 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
359 | Report->addRange(R: CallArg->getSourceRange()); |
360 | Report->setDeclWithIssue(DeclWithIssue); |
361 | BR->emitReport(R: std::move(Report)); |
362 | } |
363 | |
364 | void reportBugOnThis(const Expr *CallArg, const Decl *DeclWithIssue) const { |
365 | assert(CallArg); |
366 | |
367 | const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin(); |
368 | |
369 | SmallString<100> Buf; |
370 | llvm::raw_svector_ostream Os(Buf); |
371 | Os << "Call argument for 'this' parameter is " << ptrKind(); |
372 | Os << " and unsafe." ; |
373 | |
374 | PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager()); |
375 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
376 | Report->addRange(R: CallArg->getSourceRange()); |
377 | Report->setDeclWithIssue(DeclWithIssue); |
378 | BR->emitReport(R: std::move(Report)); |
379 | } |
380 | |
381 | void reportBugOnReceiver(const Expr *CallArg, |
382 | const Decl *DeclWithIssue) const { |
383 | assert(CallArg); |
384 | |
385 | const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin(); |
386 | |
387 | SmallString<100> Buf; |
388 | llvm::raw_svector_ostream Os(Buf); |
389 | Os << "Reciever is " << ptrKind() << " and unsafe." ; |
390 | |
391 | PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager()); |
392 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
393 | Report->addRange(R: CallArg->getSourceRange()); |
394 | Report->setDeclWithIssue(DeclWithIssue); |
395 | BR->emitReport(R: std::move(Report)); |
396 | } |
397 | }; |
398 | |
399 | class UncountedCallArgsChecker final : public RawPtrRefCallArgsChecker { |
400 | public: |
401 | UncountedCallArgsChecker() |
402 | : RawPtrRefCallArgsChecker("Uncounted call argument for a raw " |
403 | "pointer/reference parameter" ) {} |
404 | |
405 | std::optional<bool> isUnsafeType(QualType QT) const final { |
406 | return isUncounted(T: QT); |
407 | } |
408 | |
409 | std::optional<bool> isUnsafePtr(QualType QT) const final { |
410 | return isUncountedPtr(T: QT.getCanonicalType()); |
411 | } |
412 | |
413 | bool isSafePtr(const CXXRecordDecl *Record) const final { |
414 | return isRefCounted(Class: Record) || isCheckedPtr(Class: Record); |
415 | } |
416 | |
417 | bool isSafePtrType(const QualType type) const final { |
418 | return isRefOrCheckedPtrType(T: type); |
419 | } |
420 | |
421 | const char *ptrKind() const final { return "uncounted" ; } |
422 | }; |
423 | |
424 | class UncheckedCallArgsChecker final : public RawPtrRefCallArgsChecker { |
425 | public: |
426 | UncheckedCallArgsChecker() |
427 | : RawPtrRefCallArgsChecker("Unchecked call argument for a raw " |
428 | "pointer/reference parameter" ) {} |
429 | |
430 | std::optional<bool> isUnsafeType(QualType QT) const final { |
431 | return isUnchecked(T: QT); |
432 | } |
433 | |
434 | std::optional<bool> isUnsafePtr(QualType QT) const final { |
435 | return isUncheckedPtr(T: QT.getCanonicalType()); |
436 | } |
437 | |
438 | bool isSafePtr(const CXXRecordDecl *Record) const final { |
439 | return isRefCounted(Class: Record) || isCheckedPtr(Class: Record); |
440 | } |
441 | |
442 | bool isSafePtrType(const QualType type) const final { |
443 | return isRefOrCheckedPtrType(T: type); |
444 | } |
445 | |
446 | bool isSafeExpr(const Expr *E) const final { |
447 | return isExprToGetCheckedPtrCapableMember(E); |
448 | } |
449 | |
450 | const char *ptrKind() const final { return "unchecked" ; } |
451 | }; |
452 | |
453 | class UnretainedCallArgsChecker final : public RawPtrRefCallArgsChecker { |
454 | public: |
455 | UnretainedCallArgsChecker() |
456 | : RawPtrRefCallArgsChecker("Unretained call argument for a raw " |
457 | "pointer/reference parameter" ) { |
458 | RTC = RetainTypeChecker(); |
459 | } |
460 | |
461 | std::optional<bool> isUnsafeType(QualType QT) const final { |
462 | return RTC->isUnretained(QT); |
463 | } |
464 | |
465 | std::optional<bool> isUnsafePtr(QualType QT) const final { |
466 | return RTC->isUnretained(QT); |
467 | } |
468 | |
469 | bool isSafePtr(const CXXRecordDecl *Record) const final { |
470 | return isRetainPtr(Class: Record); |
471 | } |
472 | |
473 | bool isSafePtrType(const QualType type) const final { |
474 | return isRetainPtrType(T: type); |
475 | } |
476 | |
477 | bool isSafeExpr(const Expr *E) const final { |
478 | return ento::cocoa::isCocoaObjectRef(T: E->getType()) && |
479 | isa<ObjCMessageExpr>(Val: E); |
480 | } |
481 | |
482 | const char *ptrKind() const final { return "unretained" ; } |
483 | }; |
484 | |
485 | } // namespace |
486 | |
487 | void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) { |
488 | Mgr.registerChecker<UncountedCallArgsChecker>(); |
489 | } |
490 | |
491 | bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) { |
492 | return true; |
493 | } |
494 | |
495 | void ento::registerUncheckedCallArgsChecker(CheckerManager &Mgr) { |
496 | Mgr.registerChecker<UncheckedCallArgsChecker>(); |
497 | } |
498 | |
499 | bool ento::shouldRegisterUncheckedCallArgsChecker(const CheckerManager &) { |
500 | return true; |
501 | } |
502 | |
503 | void ento::registerUnretainedCallArgsChecker(CheckerManager &Mgr) { |
504 | Mgr.registerChecker<UnretainedCallArgsChecker>(); |
505 | } |
506 | |
507 | bool ento::shouldRegisterUnretainedCallArgsChecker(const CheckerManager &) { |
508 | return true; |
509 | } |
510 | |