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