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 VisitCallExpr(const CallExpr *CE) { |
57 | Checker->visitCallExpr(CE); |
58 | return true; |
59 | } |
60 | }; |
61 | |
62 | LocalVisitor visitor(this); |
63 | visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD)); |
64 | } |
65 | |
66 | void visitCallExpr(const CallExpr *CE) const { |
67 | if (shouldSkipCall(CE)) |
68 | return; |
69 | |
70 | if (auto *F = CE->getDirectCallee()) { |
71 | // Skip the first argument for overloaded member operators (e. g. lambda |
72 | // or std::function call operator). |
73 | unsigned ArgIdx = isa<CXXOperatorCallExpr>(Val: CE) && isa_and_nonnull<CXXMethodDecl>(Val: F); |
74 | |
75 | if (auto *MemberCallExpr = dyn_cast<CXXMemberCallExpr>(Val: CE)) { |
76 | if (auto *MD = MemberCallExpr->getMethodDecl()) { |
77 | auto name = safeGetName(ASTNode: MD); |
78 | if (name == "ref" || name == "deref" ) |
79 | return; |
80 | } |
81 | auto *E = MemberCallExpr->getImplicitObjectArgument(); |
82 | QualType ArgType = MemberCallExpr->getObjectType(); |
83 | std::optional<bool> IsUncounted = |
84 | isUncounted(Class: ArgType->getAsCXXRecordDecl()); |
85 | if (IsUncounted && *IsUncounted && !isPtrOriginSafe(Arg: E)) |
86 | reportBugOnThis(CallArg: E); |
87 | } |
88 | |
89 | for (auto P = F->param_begin(); |
90 | // FIXME: Also check variadic function parameters. |
91 | // FIXME: Also check default function arguments. Probably a different |
92 | // checker. In case there are default arguments the call can have |
93 | // fewer arguments than the callee has parameters. |
94 | P < F->param_end() && ArgIdx < CE->getNumArgs(); ++P, ++ArgIdx) { |
95 | // TODO: attributes. |
96 | // if ((*P)->hasAttr<SafeRefCntblRawPtrAttr>()) |
97 | // continue; |
98 | |
99 | const auto *ArgType = (*P)->getType().getTypePtrOrNull(); |
100 | if (!ArgType) |
101 | continue; // FIXME? Should we bail? |
102 | |
103 | // FIXME: more complex types (arrays, references to raw pointers, etc) |
104 | std::optional<bool> IsUncounted = isUncountedPtr(ArgType); |
105 | if (!IsUncounted || !(*IsUncounted)) |
106 | continue; |
107 | |
108 | const auto *Arg = CE->getArg(Arg: ArgIdx); |
109 | |
110 | if (auto *defaultArg = dyn_cast<CXXDefaultArgExpr>(Val: Arg)) |
111 | Arg = defaultArg->getExpr(); |
112 | |
113 | if (isPtrOriginSafe(Arg)) |
114 | continue; |
115 | |
116 | reportBug(CallArg: Arg, Param: *P); |
117 | } |
118 | } |
119 | } |
120 | |
121 | bool isPtrOriginSafe(const Expr *Arg) const { |
122 | std::pair<const clang::Expr *, bool> ArgOrigin = |
123 | tryToFindPtrOrigin(E: Arg, StopAtFirstRefCountedObj: true); |
124 | |
125 | // Temporary ref-counted object created as part of the call argument |
126 | // would outlive the call. |
127 | if (ArgOrigin.second) |
128 | return true; |
129 | |
130 | if (isa<CXXNullPtrLiteralExpr>(Val: ArgOrigin.first)) { |
131 | // foo(nullptr) |
132 | return true; |
133 | } |
134 | if (isa<IntegerLiteral>(Val: ArgOrigin.first)) { |
135 | // FIXME: Check the value. |
136 | // foo(NULL) |
137 | return true; |
138 | } |
139 | |
140 | return isASafeCallArg(E: ArgOrigin.first); |
141 | } |
142 | |
143 | bool shouldSkipCall(const CallExpr *CE) const { |
144 | const auto *Callee = CE->getDirectCallee(); |
145 | |
146 | if (Callee && TFA.isTrivial(Callee)) |
147 | return true; |
148 | |
149 | if (CE->getNumArgs() == 0) |
150 | return false; |
151 | |
152 | // If an assignment is problematic we should warn about the sole existence |
153 | // of object on LHS. |
154 | if (auto *MemberOp = dyn_cast<CXXOperatorCallExpr>(Val: CE)) { |
155 | // Note: assignemnt to built-in type isn't derived from CallExpr. |
156 | if (MemberOp->getOperator() == |
157 | OO_Equal) { // Ignore assignment to Ref/RefPtr. |
158 | auto *callee = MemberOp->getDirectCallee(); |
159 | if (auto *calleeDecl = dyn_cast<CXXMethodDecl>(callee)) { |
160 | if (const CXXRecordDecl *classDecl = calleeDecl->getParent()) { |
161 | if (isRefCounted(Class: classDecl)) |
162 | return true; |
163 | } |
164 | } |
165 | } |
166 | if (MemberOp->isAssignmentOp()) |
167 | return false; |
168 | } |
169 | |
170 | if (!Callee) |
171 | return false; |
172 | |
173 | if (isMethodOnWTFContainerType(Decl: Callee)) |
174 | return true; |
175 | |
176 | auto overloadedOperatorType = Callee->getOverloadedOperator(); |
177 | if (overloadedOperatorType == OO_EqualEqual || |
178 | overloadedOperatorType == OO_ExclaimEqual || |
179 | overloadedOperatorType == OO_LessEqual || |
180 | overloadedOperatorType == OO_GreaterEqual || |
181 | overloadedOperatorType == OO_Spaceship || |
182 | overloadedOperatorType == OO_AmpAmp || |
183 | overloadedOperatorType == OO_PipePipe) |
184 | return true; |
185 | |
186 | if (isCtorOfRefCounted(F: Callee)) |
187 | return true; |
188 | |
189 | auto name = safeGetName(ASTNode: Callee); |
190 | if (name == "adoptRef" || name == "getPtr" || name == "WeakPtr" || |
191 | name == "dynamicDowncast" || name == "downcast" || |
192 | name == "checkedDowncast" || name == "uncheckedDowncast" || |
193 | name == "bitwise_cast" || name == "is" || name == "equal" || |
194 | name == "hash" || name == "isType" || |
195 | // FIXME: Most/all of these should be implemented via attributes. |
196 | name == "equalIgnoringASCIICase" || |
197 | name == "equalIgnoringASCIICaseCommon" || |
198 | name == "equalIgnoringNullity" || name == "toString" ) |
199 | return true; |
200 | |
201 | return false; |
202 | } |
203 | |
204 | bool isMethodOnWTFContainerType(const FunctionDecl *Decl) const { |
205 | if (!isa<CXXMethodDecl>(Val: Decl)) |
206 | return false; |
207 | auto *ClassDecl = Decl->getParent(); |
208 | if (!ClassDecl || !isa<CXXRecordDecl>(ClassDecl)) |
209 | return false; |
210 | |
211 | auto *NsDecl = ClassDecl->getParent(); |
212 | if (!NsDecl || !isa<NamespaceDecl>(NsDecl)) |
213 | return false; |
214 | |
215 | auto MethodName = safeGetName(ASTNode: Decl); |
216 | auto ClsNameStr = safeGetName(ClassDecl); |
217 | StringRef ClsName = ClsNameStr; // FIXME: Make safeGetName return StringRef. |
218 | auto NamespaceName = safeGetName(NsDecl); |
219 | // FIXME: These should be implemented via attributes. |
220 | return NamespaceName == "WTF" && |
221 | (MethodName == "find" || MethodName == "findIf" || |
222 | MethodName == "reverseFind" || MethodName == "reverseFindIf" || |
223 | MethodName == "get" || MethodName == "inlineGet" || |
224 | MethodName == "contains" || MethodName == "containsIf" ) && |
225 | (ClsName.ends_with(Suffix: "Vector" ) || ClsName.ends_with(Suffix: "Set" ) || |
226 | ClsName.ends_with(Suffix: "Map" )); |
227 | } |
228 | |
229 | void reportBug(const Expr *CallArg, const ParmVarDecl *Param) const { |
230 | assert(CallArg); |
231 | |
232 | SmallString<100> Buf; |
233 | llvm::raw_svector_ostream Os(Buf); |
234 | |
235 | const std::string paramName = safeGetName(ASTNode: Param); |
236 | Os << "Call argument" ; |
237 | if (!paramName.empty()) { |
238 | Os << " for parameter " ; |
239 | printQuotedQualifiedName(Os, D: Param); |
240 | } |
241 | Os << " is uncounted and unsafe." ; |
242 | |
243 | const SourceLocation SrcLocToReport = |
244 | isa<CXXDefaultArgExpr>(Val: CallArg) ? Param->getDefaultArg()->getExprLoc() |
245 | : CallArg->getSourceRange().getBegin(); |
246 | |
247 | PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager()); |
248 | auto Report = std::make_unique<BasicBugReport>(args: Bug, args: Os.str(), args&: BSLoc); |
249 | Report->addRange(CallArg->getSourceRange()); |
250 | BR->emitReport(R: std::move(Report)); |
251 | } |
252 | |
253 | void reportBugOnThis(const Expr *CallArg) const { |
254 | assert(CallArg); |
255 | |
256 | const SourceLocation SrcLocToReport = CallArg->getSourceRange().getBegin(); |
257 | |
258 | PathDiagnosticLocation BSLoc(SrcLocToReport, BR->getSourceManager()); |
259 | auto Report = std::make_unique<BasicBugReport>( |
260 | args: Bug, args: "Call argument for 'this' parameter is uncounted and unsafe." , |
261 | args&: BSLoc); |
262 | Report->addRange(CallArg->getSourceRange()); |
263 | BR->emitReport(R: std::move(Report)); |
264 | } |
265 | }; |
266 | } // namespace |
267 | |
268 | void ento::registerUncountedCallArgsChecker(CheckerManager &Mgr) { |
269 | Mgr.registerChecker<UncountedCallArgsChecker>(); |
270 | } |
271 | |
272 | bool ento::shouldRegisterUncountedCallArgsChecker(const CheckerManager &) { |
273 | return true; |
274 | } |
275 | |