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
23using namespace clang;
24using namespace ento;
25
26namespace {
27
28class 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
37public:
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(Name: safeGetName(ASTNode: Decl)))
58 return true;
59 return RecursiveASTVisitor<LocalVisitor>::TraverseClassTemplateDecl(
60 D: 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(D: 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>(Val: CE) && isa_and_nonnull<CXXMethodDecl>(Val: F);
81
82 if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(Val: CE)) {
83 if (auto *MD = MemberCallExpr->getMethodDecl()) {
84 auto name = safeGetName(ASTNode: 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(Class: ArgType->getAsCXXRecordDecl());
92 if (IsUncounted && *IsUncounted && !isPtrOriginSafe(Arg: E))
93 reportBugOnThis(CallArg: 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(T: ArgType);
112 if (!IsUncounted || !(*IsUncounted))
113 continue;
114
115 const auto *Arg = CE->getArg(Arg: ArgIdx);
116
117 if (auto *defaultArg = dyn_cast<CXXDefaultArgExpr>(Val: Arg))
118 Arg = defaultArg->getExpr();
119
120 if (isPtrOriginSafe(Arg))
121 continue;
122
123 reportBug(CallArg: Arg, Param: *P);
124 }
125 }
126 }
127
128 bool isPtrOriginSafe(const Expr *Arg) const {
129 return tryToFindPtrOrigin(E: Arg, /*StopAtFirstRefCountedObj=*/true,
130 callback: [](const clang::Expr *ArgOrigin, bool IsSafe) {
131 if (IsSafe)
132 return true;
133 if (isa<CXXNullPtrLiteralExpr>(Val: ArgOrigin)) {
134 // foo(nullptr)
135 return true;
136 }
137 if (isa<IntegerLiteral>(Val: ArgOrigin)) {
138 // FIXME: Check the value.
139 // foo(NULL)
140 return true;
141 }
142 if (isASafeCallArg(E: 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(Loc: CE->getExprLoc()))
152 return true;
153
154 if (Callee && TFA.isTrivial(D: 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>(Val: 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>(Val: callee)) {
168 if (const CXXRecordDecl *classDecl = calleeDecl->getParent()) {
169 if (isRefCounted(Class: 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(Decl: 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(F: Callee))
195 return true;
196
197 auto name = safeGetName(ASTNode: 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>(Val: Decl))
214 return false;
215 auto *ClassDecl = Decl->getParent();
216 if (!ClassDecl || !isa<CXXRecordDecl>(Val: ClassDecl))
217 return false;
218
219 auto *NsDecl = ClassDecl->getParent();
220 if (!NsDecl || !isa<NamespaceDecl>(Val: NsDecl))
221 return false;
222
223 auto MethodName = safeGetName(ASTNode: Decl);
224 auto ClsNameStr = safeGetName(ASTNode: ClassDecl);
225 StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef.
226 auto NamespaceName = safeGetName(ASTNode: 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(Suffix: "Vector") || ClsName.ends_with(Suffix: "Set") ||
240 ClsName.ends_with(Suffix: "Map") || ClsName == "StringImpl" ||
241 ClsName.ends_with(Suffix: "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(ASTNode: Param);
251 Os << "Call argument";
252 if (!paramName.empty()) {
253 Os << " for parameter ";
254 printQuotedQualifiedName(Os, D: Param);
255 }
256 Os << " is uncounted and unsafe.";
257
258 const SourceLocation SrcLocToReport =
259 isa<CXXDefaultArgExpr>(Val: CallArg) ? Param->getDefaultArg()->getExprLoc()
260 : CallArg->getSourceRange().getBegin();
261
262 PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager());
263 auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc);
264 Report->addRange(R: CallArg->getSourceRange());
265 BR->emitReport(R: 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 args: Bug, args: "Call argument for 'this' parameter is uncounted and unsafe.",
276 args&: BSLoc);
277 Report->addRange(R: CallArg->getSourceRange());
278 BR->emitReport(R: std::move(Report));
279 }
280};
281} // namespace
282
283void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) {
284 Mgr.registerChecker<UncountedCallArgsChecker>();
285}
286
287bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) {
288 return true;
289}
290