xref: /llvm-project/clang/lib/StaticAnalyzer/Checkers/WebKit/RawPtrRefLocalVarsChecker.cpp (revision 05860f9b384b9b8f8bb01fa8984dbc2833669a27)
1 //=======- UncountedLocalVarsChecker.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/DynamicRecursiveASTVisitor.h"
16 #include "clang/AST/ParentMapContext.h"
17 #include "clang/Basic/SourceLocation.h"
18 #include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h"
19 #include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h"
20 #include "clang/StaticAnalyzer/Core/BugReporter/BugType.h"
21 #include "clang/StaticAnalyzer/Core/Checker.h"
22 #include <optional>
23 
24 using namespace clang;
25 using namespace ento;
26 
27 namespace {
28 
29 // FIXME: should be defined by anotations in the future
30 bool isRefcountedStringsHack(const VarDecl *V) {
31   assert(V);
32   auto safeClass = [](const std::string &className) {
33     return className == "String" || className == "AtomString" ||
34            className == "UniquedString" || className == "Identifier";
35   };
36   QualType QT = V->getType();
37   auto *T = QT.getTypePtr();
38   if (auto *CXXRD = T->getAsCXXRecordDecl()) {
39     if (safeClass(safeGetName(CXXRD)))
40       return true;
41   }
42   if (T->isPointerType() || T->isReferenceType()) {
43     if (auto *CXXRD = T->getPointeeCXXRecordDecl()) {
44       if (safeClass(safeGetName(CXXRD)))
45         return true;
46     }
47   }
48   return false;
49 }
50 
51 struct GuardianVisitor : DynamicRecursiveASTVisitor {
52   const VarDecl *Guardian{nullptr};
53 
54   explicit GuardianVisitor(const VarDecl *Guardian) : Guardian(Guardian) {
55     assert(Guardian);
56   }
57 
58   bool VisitBinaryOperator(BinaryOperator *BO) override {
59     if (BO->isAssignmentOp()) {
60       if (auto *VarRef = dyn_cast<DeclRefExpr>(BO->getLHS())) {
61         if (VarRef->getDecl() == Guardian)
62           return false;
63       }
64     }
65     return true;
66   }
67 
68   bool VisitCXXConstructExpr(CXXConstructExpr *CE) override {
69     if (auto *Ctor = CE->getConstructor()) {
70       if (Ctor->isMoveConstructor() && CE->getNumArgs() == 1) {
71         auto *Arg = CE->getArg(0)->IgnoreParenCasts();
72         if (auto *VarRef = dyn_cast<DeclRefExpr>(Arg)) {
73           if (VarRef->getDecl() == Guardian)
74             return false;
75         }
76       }
77     }
78     return true;
79   }
80 
81   bool VisitCXXMemberCallExpr(CXXMemberCallExpr *MCE) override {
82     auto MethodName = safeGetName(MCE->getMethodDecl());
83     if (MethodName == "swap" || MethodName == "leakRef" ||
84         MethodName == "releaseNonNull") {
85       auto *ThisArg = MCE->getImplicitObjectArgument()->IgnoreParenCasts();
86       if (auto *VarRef = dyn_cast<DeclRefExpr>(ThisArg)) {
87         if (VarRef->getDecl() == Guardian)
88           return false;
89       }
90     }
91     return true;
92   }
93 
94   bool VisitCXXOperatorCallExpr(CXXOperatorCallExpr *OCE) override {
95     if (OCE->isAssignmentOp()) {
96       assert(OCE->getNumArgs() == 2);
97       auto *ThisArg = OCE->getArg(0)->IgnoreParenCasts();
98       if (auto *VarRef = dyn_cast<DeclRefExpr>(ThisArg)) {
99         if (VarRef->getDecl() == Guardian)
100           return false;
101       }
102     }
103     return true;
104   }
105 };
106 
107 bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded,
108                                            const VarDecl *MaybeGuardian) {
109   assert(Guarded);
110   assert(MaybeGuardian);
111 
112   if (!MaybeGuardian->isLocalVarDecl())
113     return false;
114 
115   const CompoundStmt *guardiansClosestCompStmtAncestor = nullptr;
116 
117   ASTContext &ctx = MaybeGuardian->getASTContext();
118 
119   for (DynTypedNodeList guardianAncestors = ctx.getParents(*MaybeGuardian);
120        !guardianAncestors.empty();
121        guardianAncestors = ctx.getParents(
122            *guardianAncestors
123                 .begin()) // FIXME - should we handle all of the parents?
124   ) {
125     for (auto &guardianAncestor : guardianAncestors) {
126       if (auto *CStmtParentAncestor = guardianAncestor.get<CompoundStmt>()) {
127         guardiansClosestCompStmtAncestor = CStmtParentAncestor;
128         break;
129       }
130     }
131     if (guardiansClosestCompStmtAncestor)
132       break;
133   }
134 
135   if (!guardiansClosestCompStmtAncestor)
136     return false;
137 
138   // We need to skip the first CompoundStmt to avoid situation when guardian is
139   // defined in the same scope as guarded variable.
140   const CompoundStmt *FirstCompondStmt = nullptr;
141   for (DynTypedNodeList guardedVarAncestors = ctx.getParents(*Guarded);
142        !guardedVarAncestors.empty();
143        guardedVarAncestors = ctx.getParents(
144            *guardedVarAncestors
145                 .begin()) // FIXME - should we handle all of the parents?
146   ) {
147     for (auto &guardedVarAncestor : guardedVarAncestors) {
148       if (auto *CStmtAncestor = guardedVarAncestor.get<CompoundStmt>()) {
149         if (!FirstCompondStmt) {
150           FirstCompondStmt = CStmtAncestor;
151           continue;
152         }
153         if (CStmtAncestor == guardiansClosestCompStmtAncestor) {
154           GuardianVisitor guardianVisitor(MaybeGuardian);
155           auto *GuardedScope = const_cast<CompoundStmt *>(FirstCompondStmt);
156           return guardianVisitor.TraverseCompoundStmt(GuardedScope);
157         }
158       }
159     }
160   }
161 
162   return false;
163 }
164 
165 class RawPtrRefLocalVarsChecker
166     : public Checker<check::ASTDecl<TranslationUnitDecl>> {
167   BugType Bug;
168   mutable BugReporter *BR;
169   EnsureFunctionAnalysis EFA;
170 
171 public:
172   RawPtrRefLocalVarsChecker(const char *description)
173       : Bug(this, description, "WebKit coding guidelines") {}
174 
175   virtual std::optional<bool> isUnsafePtr(const QualType T) const = 0;
176   virtual const char *ptrKind() const = 0;
177 
178   void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
179                     BugReporter &BRArg) const {
180     BR = &BRArg;
181 
182     // The calls to checkAST* from AnalysisConsumer don't
183     // visit template instantiations or lambda classes. We
184     // want to visit those, so we make our own RecursiveASTVisitor.
185     struct LocalVisitor : DynamicRecursiveASTVisitor {
186       const RawPtrRefLocalVarsChecker *Checker;
187       Decl *DeclWithIssue{nullptr};
188 
189       TrivialFunctionAnalysis TFA;
190 
191       explicit LocalVisitor(const RawPtrRefLocalVarsChecker *Checker)
192           : Checker(Checker) {
193         assert(Checker);
194         ShouldVisitTemplateInstantiations = true;
195         ShouldVisitImplicitCode = false;
196       }
197 
198       bool TraverseDecl(Decl *D) override {
199         llvm::SaveAndRestore SavedDecl(DeclWithIssue);
200         if (D && (isa<FunctionDecl>(D) || isa<ObjCMethodDecl>(D)))
201           DeclWithIssue = D;
202         return DynamicRecursiveASTVisitor::TraverseDecl(D);
203       }
204 
205       bool VisitVarDecl(VarDecl *V) override {
206         auto *Init = V->getInit();
207         if (Init && V->isLocalVarDecl())
208           Checker->visitVarDecl(V, Init, DeclWithIssue);
209         return true;
210       }
211 
212       bool VisitBinaryOperator(BinaryOperator *BO) override {
213         if (BO->isAssignmentOp()) {
214           if (auto *VarRef = dyn_cast<DeclRefExpr>(BO->getLHS())) {
215             if (auto *V = dyn_cast<VarDecl>(VarRef->getDecl()))
216               Checker->visitVarDecl(V, BO->getRHS(), DeclWithIssue);
217           }
218         }
219         return true;
220       }
221 
222       bool TraverseIfStmt(IfStmt *IS) override {
223         if (!TFA.isTrivial(IS))
224           return DynamicRecursiveASTVisitor::TraverseIfStmt(IS);
225         return true;
226       }
227 
228       bool TraverseForStmt(ForStmt *FS) override {
229         if (!TFA.isTrivial(FS))
230           return DynamicRecursiveASTVisitor::TraverseForStmt(FS);
231         return true;
232       }
233 
234       bool TraverseCXXForRangeStmt(CXXForRangeStmt *FRS) override {
235         if (!TFA.isTrivial(FRS))
236           return DynamicRecursiveASTVisitor::TraverseCXXForRangeStmt(FRS);
237         return true;
238       }
239 
240       bool TraverseWhileStmt(WhileStmt *WS) override {
241         if (!TFA.isTrivial(WS))
242           return DynamicRecursiveASTVisitor::TraverseWhileStmt(WS);
243         return true;
244       }
245 
246       bool TraverseCompoundStmt(CompoundStmt *CS) override {
247         if (!TFA.isTrivial(CS))
248           return DynamicRecursiveASTVisitor::TraverseCompoundStmt(CS);
249         return true;
250       }
251     };
252 
253     LocalVisitor visitor(this);
254     visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
255   }
256 
257   void visitVarDecl(const VarDecl *V, const Expr *Value,
258                     const Decl *DeclWithIssue) const {
259     if (shouldSkipVarDecl(V))
260       return;
261 
262     std::optional<bool> IsUncountedPtr = isUnsafePtr(V->getType());
263     if (IsUncountedPtr && *IsUncountedPtr) {
264       if (tryToFindPtrOrigin(
265               Value, /*StopAtFirstRefCountedObj=*/false,
266               [&](const clang::Expr *InitArgOrigin, bool IsSafe) {
267                 if (!InitArgOrigin || IsSafe)
268                   return true;
269 
270                 if (isa<CXXThisExpr>(InitArgOrigin))
271                   return true;
272 
273                 if (isa<CXXNullPtrLiteralExpr>(InitArgOrigin))
274                   return true;
275 
276                 if (isa<IntegerLiteral>(InitArgOrigin))
277                   return true;
278 
279                 if (isConstOwnerPtrMemberExpr(InitArgOrigin))
280                   return true;
281 
282                 if (EFA.isACallToEnsureFn(InitArgOrigin))
283                   return true;
284 
285                 if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) {
286                   if (auto *MaybeGuardian =
287                           dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) {
288                     const auto *MaybeGuardianArgType =
289                         MaybeGuardian->getType().getTypePtr();
290                     if (MaybeGuardianArgType) {
291                       const CXXRecordDecl *const MaybeGuardianArgCXXRecord =
292                           MaybeGuardianArgType->getAsCXXRecordDecl();
293                       if (MaybeGuardianArgCXXRecord) {
294                         if (MaybeGuardian->isLocalVarDecl() &&
295                             (isRefCounted(MaybeGuardianArgCXXRecord) ||
296                              isCheckedPtr(MaybeGuardianArgCXXRecord) ||
297                              isRefcountedStringsHack(MaybeGuardian)) &&
298                             isGuardedScopeEmbeddedInGuardianScope(
299                                 V, MaybeGuardian))
300                           return true;
301                       }
302                     }
303 
304                     // Parameters are guaranteed to be safe for the duration of
305                     // the call by another checker.
306                     if (isa<ParmVarDecl>(MaybeGuardian))
307                       return true;
308                   }
309                 }
310 
311                 return false;
312               }))
313         return;
314 
315       reportBug(V, Value, DeclWithIssue);
316     }
317   }
318 
319   bool shouldSkipVarDecl(const VarDecl *V) const {
320     assert(V);
321     return BR->getSourceManager().isInSystemHeader(V->getLocation());
322   }
323 
324   void reportBug(const VarDecl *V, const Expr *Value,
325                  const Decl *DeclWithIssue) const {
326     assert(V);
327     SmallString<100> Buf;
328     llvm::raw_svector_ostream Os(Buf);
329 
330     if (dyn_cast<ParmVarDecl>(V)) {
331       Os << "Assignment to an " << ptrKind() << " parameter ";
332       printQuotedQualifiedName(Os, V);
333       Os << " is unsafe.";
334 
335       PathDiagnosticLocation BSLoc(Value->getExprLoc(), BR->getSourceManager());
336       auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
337       Report->addRange(Value->getSourceRange());
338       BR->emitReport(std::move(Report));
339     } else {
340       if (V->hasLocalStorage())
341         Os << "Local variable ";
342       else if (V->isStaticLocal())
343         Os << "Static local variable ";
344       else if (V->hasGlobalStorage())
345         Os << "Global variable ";
346       else
347         Os << "Variable ";
348       printQuotedQualifiedName(Os, V);
349       Os << " is " << ptrKind() << " and unsafe.";
350 
351       PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager());
352       auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
353       Report->addRange(V->getSourceRange());
354       Report->setDeclWithIssue(DeclWithIssue);
355       BR->emitReport(std::move(Report));
356     }
357   }
358 };
359 
360 class UncountedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
361 public:
362   UncountedLocalVarsChecker()
363       : RawPtrRefLocalVarsChecker("Uncounted raw pointer or reference not "
364                                   "provably backed by ref-counted variable") {}
365   std::optional<bool> isUnsafePtr(const QualType T) const final {
366     return isUncountedPtr(T);
367   }
368   const char *ptrKind() const final { return "uncounted"; }
369 };
370 
371 class UncheckedLocalVarsChecker final : public RawPtrRefLocalVarsChecker {
372 public:
373   UncheckedLocalVarsChecker()
374       : RawPtrRefLocalVarsChecker("Unchecked raw pointer or reference not "
375                                   "provably backed by checked variable") {}
376   std::optional<bool> isUnsafePtr(const QualType T) const final {
377     return isUncheckedPtr(T);
378   }
379   const char *ptrKind() const final { return "unchecked"; }
380 };
381 
382 } // namespace
383 
384 void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
385   Mgr.registerChecker<UncountedLocalVarsChecker>();
386 }
387 
388 bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
389   return true;
390 }
391 
392 void ento::registerUncheckedLocalVarsChecker(CheckerManager &Mgr) {
393   Mgr.registerChecker<UncheckedLocalVarsChecker>();
394 }
395 
396 bool ento::shouldRegisterUncheckedLocalVarsChecker(const CheckerManager &) {
397   return true;
398 }
399