Home | History | Annotate | Line # | Download | only in WebKit
      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 
     24 using namespace clang;
     25 using namespace ento;
     26 
     27 namespace {
     28 
     29 // for ( int a = ...) ... true
     30 // for ( int a : ...) ... true
     31 // if ( int* a = ) ... true
     32 // anything else ... false
     33 bool 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
     52 bool 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 
     73 bool 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 
    128 class 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 
    136 public:
    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 
    245 void ento::registerUncountedLocalVarsChecker(CheckerManager &Mgr) {
    246   Mgr.registerChecker<UncountedLocalVarsChecker>();
    247 }
    248 
    249 bool ento::shouldRegisterUncountedLocalVarsChecker(const CheckerManager &) {
    250   return true;
    251 }
    252