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/ParentMapContext.h"
16#include "clang/AST/RecursiveASTVisitor.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 "llvm/ADT/DenseSet.h"
23
24using namespace clang;
25using namespace ento;
26
27namespace {
28
29// for ( int a = ...) ... true
30// for ( int a : ...) ... true
31// if ( int* a = ) ... true
32// anything else ... false
33bool isDeclaredInForOrIf(const VarDecl *Var) {
34  assert(Var);
35  auto &ASTCtx = Var->getASTContext();
36  auto parent = ASTCtx.getParents(*Var);
37
38  if (parent.size() == 1) {
39    if (auto *DS = parent.begin()->get<DeclStmt>()) {
40      DynTypedNodeList grandParent = ASTCtx.getParents(*DS);
41      if (grandParent.size() == 1) {
42        return grandParent.begin()->get<ForStmt>() ||
43               grandParent.begin()->get<IfStmt>() ||
44               grandParent.begin()->get<CXXForRangeStmt>();
45      }
46    }
47  }
48  return false;
49}
50
51// FIXME: should be defined by anotations in the future
52bool isRefcountedStringsHack(const VarDecl *V) {
53  assert(V);
54  auto safeClass = [](const std::string &className) {
55    return className == "String" || className == "AtomString" ||
56           className == "UniquedString" || className == "Identifier";
57  };
58  QualType QT = V->getType();
59  auto *T = QT.getTypePtr();
60  if (auto *CXXRD = T->getAsCXXRecordDecl()) {
61    if (safeClass(safeGetName(CXXRD)))
62      return true;
63  }
64  if (T->isPointerType() || T->isReferenceType()) {
65    if (auto *CXXRD = T->getPointeeCXXRecordDecl()) {
66      if (safeClass(safeGetName(CXXRD)))
67        return true;
68    }
69  }
70  return false;
71}
72
73bool isGuardedScopeEmbeddedInGuardianScope(const VarDecl *Guarded,
74                                           const VarDecl *MaybeGuardian) {
75  assert(Guarded);
76  assert(MaybeGuardian);
77
78  if (!MaybeGuardian->isLocalVarDecl())
79    return false;
80
81  const CompoundStmt *guardiansClosestCompStmtAncestor = nullptr;
82
83  ASTContext &ctx = MaybeGuardian->getASTContext();
84
85  for (DynTypedNodeList guardianAncestors = ctx.getParents(*MaybeGuardian);
86       !guardianAncestors.empty();
87       guardianAncestors = ctx.getParents(
88           *guardianAncestors
89                .begin()) // FIXME - should we handle all of the parents?
90  ) {
91    for (auto &guardianAncestor : guardianAncestors) {
92      if (auto *CStmtParentAncestor = guardianAncestor.get<CompoundStmt>()) {
93        guardiansClosestCompStmtAncestor = CStmtParentAncestor;
94        break;
95      }
96    }
97    if (guardiansClosestCompStmtAncestor)
98      break;
99  }
100
101  if (!guardiansClosestCompStmtAncestor)
102    return false;
103
104  // We need to skip the first CompoundStmt to avoid situation when guardian is
105  // defined in the same scope as guarded variable.
106  bool HaveSkippedFirstCompoundStmt = false;
107  for (DynTypedNodeList guardedVarAncestors = ctx.getParents(*Guarded);
108       !guardedVarAncestors.empty();
109       guardedVarAncestors = ctx.getParents(
110           *guardedVarAncestors
111                .begin()) // FIXME - should we handle all of the parents?
112  ) {
113    for (auto &guardedVarAncestor : guardedVarAncestors) {
114      if (auto *CStmtAncestor = guardedVarAncestor.get<CompoundStmt>()) {
115        if (!HaveSkippedFirstCompoundStmt) {
116          HaveSkippedFirstCompoundStmt = true;
117          continue;
118        }
119        if (CStmtAncestor == guardiansClosestCompStmtAncestor)
120          return true;
121      }
122    }
123  }
124
125  return false;
126}
127
128class UncountedLocalVarsChecker
129    : public Checker<check::ASTDecl<TranslationUnitDecl>> {
130  BugType Bug{this,
131              "Uncounted raw pointer or reference not provably backed by "
132              "ref-counted variable",
133              "WebKit coding guidelines"};
134  mutable BugReporter *BR;
135
136public:
137  void checkASTDecl(const TranslationUnitDecl *TUD, AnalysisManager &MGR,
138                    BugReporter &BRArg) const {
139    BR = &BRArg;
140
141    // The calls to checkAST* from AnalysisConsumer don't
142    // visit template instantiations or lambda classes. We
143    // want to visit those, so we make our own RecursiveASTVisitor.
144    struct LocalVisitor : public RecursiveASTVisitor<LocalVisitor> {
145      const UncountedLocalVarsChecker *Checker;
146      explicit LocalVisitor(const UncountedLocalVarsChecker *Checker)
147          : Checker(Checker) {
148        assert(Checker);
149      }
150
151      bool shouldVisitTemplateInstantiations() const { return true; }
152      bool shouldVisitImplicitCode() const { return false; }
153
154      bool VisitVarDecl(VarDecl *V) {
155        Checker->visitVarDecl(V);
156        return true;
157      }
158    };
159
160    LocalVisitor visitor(this);
161    visitor.TraverseDecl(const_cast<TranslationUnitDecl *>(TUD));
162  }
163
164  void visitVarDecl(const VarDecl *V) const {
165    if (shouldSkipVarDecl(V))
166      return;
167
168    const auto *ArgType = V->getType().getTypePtr();
169    if (!ArgType)
170      return;
171
172    Optional<bool> IsUncountedPtr = isUncountedPtr(ArgType);
173    if (IsUncountedPtr && *IsUncountedPtr) {
174      const Expr *const InitExpr = V->getInit();
175      if (!InitExpr)
176        return; // FIXME: later on we might warn on uninitialized vars too
177
178      const clang::Expr *const InitArgOrigin =
179          tryToFindPtrOrigin(InitExpr, /*StopAtFirstRefCountedObj=*/false)
180              .first;
181      if (!InitArgOrigin)
182        return;
183
184      if (isa<CXXThisExpr>(InitArgOrigin))
185        return;
186
187      if (auto *Ref = llvm::dyn_cast<DeclRefExpr>(InitArgOrigin)) {
188        if (auto *MaybeGuardian =
189                dyn_cast_or_null<VarDecl>(Ref->getFoundDecl())) {
190          const auto *MaybeGuardianArgType =
191              MaybeGuardian->getType().getTypePtr();
192          if (!MaybeGuardianArgType)
193            return;
194          const CXXRecordDecl *const MaybeGuardianArgCXXRecord =
195              MaybeGuardianArgType->getAsCXXRecordDecl();
196          if (!MaybeGuardianArgCXXRecord)
197            return;
198
199          if (MaybeGuardian->isLocalVarDecl() &&
200              (isRefCounted(MaybeGuardianArgCXXRecord) ||
201               isRefcountedStringsHack(MaybeGuardian)) &&
202              isGuardedScopeEmbeddedInGuardianScope(V, MaybeGuardian)) {
203            return;
204          }
205
206          // Parameters are guaranteed to be safe for the duration of the call
207          // by another checker.
208          if (isa<ParmVarDecl>(MaybeGuardian))
209            return;
210        }
211      }
212
213      reportBug(V);
214    }
215  }
216
217  bool shouldSkipVarDecl(const VarDecl *V) const {
218    assert(V);
219    if (!V->isLocalVarDecl())
220      return true;
221
222    if (isDeclaredInForOrIf(V))
223      return true;
224
225    return false;
226  }
227
228  void reportBug(const VarDecl *V) const {
229    assert(V);
230    SmallString<100> Buf;
231    llvm::raw_svector_ostream Os(Buf);
232
233    Os << "Local variable ";
234    printQuotedQualifiedName(Os, V);
235    Os << " is uncounted and unsafe.";
236
237    PathDiagnosticLocation BSLoc(V->getLocation(), BR->getSourceManager());
238    auto Report = std::make_unique<BasicBugReport>(Bug, Os.str(), BSLoc);
239    Report->addRange(V->getSourceRange());
240    BR->emitReport(std::move(Report));
241  }
242};
243} // namespace
244
245void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
246  Mgr.registerChecker<UncountedLocalVarsChecker>();
247}
248
249bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
250  return true;
251}
252