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(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 | |
283 | void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) { |
284 | Mgr.registerChecker<UncountedCallArgsChecker>(); |
285 | } |
286 | |
287 | bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) { |
288 | return true; |
289 | } |
290 | |