xref: /freebsd/contrib/llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/UncountedCallArgsChecker.cpp (revision b64c5a0ace59af62eff52bfe110a521dc73c937b)
1 //=======- UncountedCallArgsChecker.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/CXXInheritance.h"
13 #include "clang/AST/Decl.h"
14 #include "clang/AST/DeclCXX.h"
15 #include "clang/AST/RecursiveASTVisitor.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 <optional>
22 
23 using namespace clang;
24 using namespace ento;
25 
26 namespace {
27 
28 class UncountedCallArgsChecker
29     : public Checker<check::ASTDecl<TranslationUnitDecl>> {
30   BugType Bug{this,
31             "Uncounted call argument for a raw pointer/reference parameter",
32             "WebKit coding guidelines"};
33   mutable BugReporter *BR;
34 
35   TrivialFunctionAnalysis TFA;
36 
37 public:
38 
39   void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
40                     BugReporter &BRArg) const {
41     BR = &BRArg;
42 
43     // The calls to checkAST* from AnalysisConsumer don't
44     // visit template instantiations or lambda classes. We
45     // want to visit those, so we make our own RecursiveASTVisitor.
46     struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
47       const UncountedCallArgsChecker *Checker;
48       explicit LocalVisitor(const UncountedCallArgsChecker *Checker)
49           : Checker(Checker) {
50         assert(Checker);
51       }
52 
53       bool shouldVisitTemplateInstantiations() const { return true; }
54       bool shouldVisitImplicitCode() const { return false; }
55 
56       bool TraverseClassTemplateDecl(ClassTemplateDecl *Decl) {
57         if (isRefType(safeGetName(Decl)))
58           return true;
59         return RecursiveASTVisitor<LocalVisitor>::TraverseClassTemplateDecl(
60             Decl);
61       }
62 
63       bool VisitCallExpr(const CallExpr *CE) {
64         Checker->visitCallExpr(CE);
65         return true;
66       }
67     };
68 
69     LocalVisitor visitor(this);
70     visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
71   }
72 
73   void visitCallExpr(const CallExpr *CE) const {
74     if (shouldSkipCall(CE))
75       return;
76 
77     if (auto *F = CE->getDirectCallee()) {
78       // Skip the first argument for overloaded member operators (e. g. lambda
79       // or std::function call operator).
80       unsigned ArgIdx = isa<CXXOperatorCallExpr>(CE) && isa_and_nonnull<CXXMethodDecl>(F);
81 
82       if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(CE)) {
83         if (auto *MD = MemberCallExpr->getMethodDecl()) {
84           auto name = safeGetName(MD);
85           if (name == "ref" || name == "deref")
86             return;
87         }
88         auto *E = MemberCallExpr->getImplicitObjectArgument();
89         QualType ArgType = MemberCallExpr->getObjectType();
90         std::optional<bool> IsUncounted =
91             isUncounted(ArgType->getAsCXXRecordDecl());
92         if (IsUncounted && *IsUncounted && !isPtrOriginSafe(E))
93           reportBugOnThis(E);
94       }
95 
96       for (auto P = F->param_begin();
97            // FIXME: Also check variadic function parameters.
98            // FIXME: Also check default function arguments. Probably a different
99            // checker. In case there are default arguments the call can have
100            // fewer arguments than the callee has parameters.
101            P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx) {
102         // TODO: attributes.
103         // if ((*P)->hasAttr<SafeRefCntblRawPtrAttr>())
104         //  continue;
105 
106         const auto *ArgType = (*P)->getType().getTypePtrOrNull();
107         if (!ArgType)
108           continue; // FIXME? Should we bail?
109 
110         // FIXME: more complex types (arrays, references to raw pointers, etc)
111         std::optional<bool> IsUncounted = isUncountedPtr(ArgType);
112         if (!IsUncounted || !(*IsUncounted))
113           continue;
114 
115         const auto *Arg = CE->getArg(ArgIdx);
116 
117         if (auto *defaultArg = dyn_cast<CXXDefaultArgExpr>(Arg))
118           Arg = defaultArg->getExpr();
119 
120         if (isPtrOriginSafe(Arg))
121           continue;
122 
123         reportBug(Arg, *P);
124       }
125     }
126   }
127 
128   bool isPtrOriginSafe(const Expr *Arg) const {
129     return tryToFindPtrOrigin(Arg, /*StopAtFirstRefCountedObj=*/true,
130                               [](const clang::Expr *ArgOrigin, bool IsSafe) {
131                                 if (IsSafe)
132                                   return true;
133                                 if (isa<CXXNullPtrLiteralExpr>(ArgOrigin)) {
134                                   // foo(nullptr)
135                                   return true;
136                                 }
137                                 if (isa<IntegerLiteral>(ArgOrigin)) {
138                                   // FIXME: Check the value.
139                                   // foo(NULL)
140                                   return true;
141                                 }
142                                 if (isASafeCallArg(ArgOrigin))
143                                   return true;
144                                 return false;
145                               });
146   }
147 
148   bool shouldSkipCall(const CallExpr *CE) const {
149     const auto *Callee = CE->getDirectCallee();
150 
151     if (BR->getSourceManager().isInSystemHeader(CE->getExprLoc()))
152       return true;
153 
154     if (Callee && TFA.isTrivial(Callee))
155       return true;
156 
157     if (CE->getNumArgs() == 0)
158       return false;
159 
160     // If an assignment is problematic we should warn about the sole existence
161     // of object on LHS.
162     if (auto *MemberOp = dyn_cast<CXXOperatorCallExpr>(CE)) {
163       // Note: assignemnt to built-in type isn't derived from CallExpr.
164       if (MemberOp->getOperator() ==
165           OO_Equal) { // Ignore assignment to Ref/RefPtr.
166         auto *callee = MemberOp->getDirectCallee();
167         if (auto *calleeDecl = dyn_cast<CXXMethodDecl>(callee)) {
168           if (const CXXRecordDecl *classDecl = calleeDecl->getParent()) {
169             if (isRefCounted(classDecl))
170               return true;
171           }
172         }
173       }
174       if (MemberOp->isAssignmentOp())
175         return false;
176     }
177 
178     if (!Callee)
179       return false;
180 
181     if (isMethodOnWTFContainerType(Callee))
182       return true;
183 
184     auto overloadedOperatorType = Callee->getOverloadedOperator();
185     if (overloadedOperatorType == OO_EqualEqual ||
186         overloadedOperatorType == OO_ExclaimEqual ||
187         overloadedOperatorType == OO_LessEqual ||
188         overloadedOperatorType == OO_GreaterEqual ||
189         overloadedOperatorType == OO_Spaceship ||
190         overloadedOperatorType == OO_AmpAmp ||
191         overloadedOperatorType == OO_PipePipe)
192       return true;
193 
194     if (isCtorOfRefCounted(Callee))
195       return true;
196 
197     auto name = safeGetName(Callee);
198     if (name == "adoptRef" || name == "getPtr" || name == "WeakPtr" ||
199         name == "dynamicDowncast" || name == "downcast" ||
200         name == "checkedDowncast" || name == "uncheckedDowncast" ||
201         name == "bitwise_cast" || name == "is" || name == "equal" ||
202         name == "hash" || name == "isType" ||
203         // FIXME: Most/all of these should be implemented via attributes.
204         name == "equalIgnoringASCIICase" ||
205         name == "equalIgnoringASCIICaseCommon" ||
206         name == "equalIgnoringNullity" || name == "toString")
207       return true;
208 
209     return false;
210   }
211 
212   bool isMethodOnWTFContainerType(const FunctionDecl *Decl) const {
213     if (!isa<CXXMethodDecl>(Decl))
214       return false;
215     auto *ClassDecl = Decl->getParent();
216     if (!ClassDecl || !isa<CXXRecordDecl>(ClassDecl))
217       return false;
218 
219     auto *NsDecl = ClassDecl->getParent();
220     if (!NsDecl || !isa<NamespaceDecl>(NsDecl))
221       return false;
222 
223     auto MethodName = safeGetName(Decl);
224     auto ClsNameStr = safeGetName(ClassDecl);
225     StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
226     auto NamespaceName = safeGetName(NsDecl);
227     // FIXME: These should be implemented via attributes.
228     return NamespaceName == "WTF" &&
229            (MethodName == "find" || MethodName == "findIf" ||
230             MethodName == "reverseFind" || MethodName == "reverseFindIf" ||
231             MethodName == "findIgnoringASCIICase" || MethodName == "get" ||
232             MethodName == "inlineGet" || MethodName == "contains" ||
233             MethodName == "containsIf" ||
234             MethodName == "containsIgnoringASCIICase" ||
235             MethodName == "startsWith" || MethodName == "endsWith" ||
236             MethodName == "startsWithIgnoringASCIICase" ||
237             MethodName == "endsWithIgnoringASCIICase" ||
238             MethodName == "substring") &&
239            (ClsName.ends_with("Vector") || ClsName.ends_with("Set") ||
240             ClsName.ends_with("Map") || ClsName == "StringImpl" ||
241             ClsName.ends_with("String"));
242   }
243 
244   void reportBug(const Expr *CallArg, const ParmVarDecl *Param) const {
245     assert(CallArg);
246 
247     SmallString<100> Buf;
248     llvm::raw_svector_ostream Os(Buf);
249 
250     const std::string paramName = safeGetName(Param);
251     Os << "Call argument";
252     if (!paramName.empty()) {
253       Os << " for parameter ";
254       printQuotedQualifiedName(Os, Param);
255     }
256     Os << " is uncounted and unsafe.";
257 
258     const SourceLocation SrcLocToReport =
259         isa<CXXDefaultArgExpr>(CallArg) ? Param->getDefaultArg()->getExprLoc()
260                                         : CallArg->getSourceRange().getBegin();
261 
262     PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
263     auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
264     Report->addRange(CallArg->getSourceRange());
265     BR->emitReport(std::move(Report));
266   }
267 
268   void reportBugOnThis(const Expr *CallArg) const {
269     assert(CallArg);
270 
271     const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin();
272 
273     PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
274     auto Report = std::make_unique<BasicBugReport>(
275         Bug, "Call argument for 'this' parameter is uncounted and unsafe.",
276         BSLoc);
277     Report->addRange(CallArg->getSourceRange());
278     BR->emitReport(std::move(Report));
279   }
280 };
281 } // namespace
282 
283 void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
284   Mgr.registerChecker<UncountedCallArgsChecker>();
285 }
286 
287 bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) {
288   return true;
289 }
290