diff options
Diffstat (limited to 'clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp')
| -rw-r--r-- | clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp | 319 | 
1 files changed, 319 insertions, 0 deletions
diff --git a/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp b/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp new file mode 100644 index 000000000000..7522fdd0a99b --- /dev/null +++ b/clang/lib/StaticAnalyzer/Checkers/LLVMConventionsChecker.cpp @@ -0,0 +1,319 @@ +//=== LLVMConventionsChecker.cpp - Check LLVM codebase conventions ---*- C++ -*- +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This defines LLVMConventionsChecker, a bunch of small little checks +// for checking specific coding conventions in the LLVM/Clang codebase. +// +//===----------------------------------------------------------------------===// + +#include "clang/StaticAnalyzer/Checkers/BuiltinCheckerRegistration.h" +#include "clang/AST/DeclTemplate.h" +#include "clang/AST/StmtVisitor.h" +#include "clang/StaticAnalyzer/Core/BugReporter/BugReporter.h" +#include "clang/StaticAnalyzer/Core/Checker.h" +#include "llvm/ADT/SmallString.h" +#include "llvm/Support/raw_ostream.h" + +using namespace clang; +using namespace ento; + +//===----------------------------------------------------------------------===// +// Generic type checking routines. +//===----------------------------------------------------------------------===// + +static bool IsLLVMStringRef(QualType T) { +  const RecordType *RT = T->getAs<RecordType>(); +  if (!RT) +    return false; + +  return StringRef(QualType(RT, 0).getAsString()) == "class StringRef"; +} + +/// Check whether the declaration is semantically inside the top-level +/// namespace named by ns. +static bool InNamespace(const Decl *D, StringRef NS) { +  const NamespaceDecl *ND = dyn_cast<NamespaceDecl>(D->getDeclContext()); +  if (!ND) +    return false; +  const IdentifierInfo *II = ND->getIdentifier(); +  if (!II || !II->getName().equals(NS)) +    return false; +  return isa<TranslationUnitDecl>(ND->getDeclContext()); +} + +static bool IsStdString(QualType T) { +  if (const ElaboratedType *QT = T->getAs<ElaboratedType>()) +    T = QT->getNamedType(); + +  const TypedefType *TT = T->getAs<TypedefType>(); +  if (!TT) +    return false; + +  const TypedefNameDecl *TD = TT->getDecl(); + +  if (!TD->isInStdNamespace()) +    return false; + +  return TD->getName() == "string"; +} + +static bool IsClangType(const RecordDecl *RD) { +  return RD->getName() == "Type" && InNamespace(RD, "clang"); +} + +static bool IsClangDecl(const RecordDecl *RD) { +  return RD->getName() == "Decl" && InNamespace(RD, "clang"); +} + +static bool IsClangStmt(const RecordDecl *RD) { +  return RD->getName() == "Stmt" && InNamespace(RD, "clang"); +} + +static bool IsClangAttr(const RecordDecl *RD) { +  return RD->getName() == "Attr" && InNamespace(RD, "clang"); +} + +static bool IsStdVector(QualType T) { +  const TemplateSpecializationType *TS = T->getAs<TemplateSpecializationType>(); +  if (!TS) +    return false; + +  TemplateName TM = TS->getTemplateName(); +  TemplateDecl *TD = TM.getAsTemplateDecl(); + +  if (!TD || !InNamespace(TD, "std")) +    return false; + +  return TD->getName() == "vector"; +} + +static bool IsSmallVector(QualType T) { +  const TemplateSpecializationType *TS = T->getAs<TemplateSpecializationType>(); +  if (!TS) +    return false; + +  TemplateName TM = TS->getTemplateName(); +  TemplateDecl *TD = TM.getAsTemplateDecl(); + +  if (!TD || !InNamespace(TD, "llvm")) +    return false; + +  return TD->getName() == "SmallVector"; +} + +//===----------------------------------------------------------------------===// +// CHECK: a StringRef should not be bound to a temporary std::string whose +// lifetime is shorter than the StringRef's. +//===----------------------------------------------------------------------===// + +namespace { +class StringRefCheckerVisitor : public StmtVisitor<StringRefCheckerVisitor> { +  const Decl *DeclWithIssue; +  BugReporter &BR; +  const CheckerBase *Checker; + +public: +  StringRefCheckerVisitor(const Decl *declWithIssue, BugReporter &br, +                          const CheckerBase *checker) +      : DeclWithIssue(declWithIssue), BR(br), Checker(checker) {} +  void VisitChildren(Stmt *S) { +    for (Stmt *Child : S->children()) +      if (Child) +        Visit(Child); +  } +  void VisitStmt(Stmt *S) { VisitChildren(S); } +  void VisitDeclStmt(DeclStmt *DS); +private: +  void VisitVarDecl(VarDecl *VD); +}; +} // end anonymous namespace + +static void CheckStringRefAssignedTemporary(const Decl *D, BugReporter &BR, +                                            const CheckerBase *Checker) { +  StringRefCheckerVisitor walker(D, BR, Checker); +  walker.Visit(D->getBody()); +} + +void StringRefCheckerVisitor::VisitDeclStmt(DeclStmt *S) { +  VisitChildren(S); + +  for (auto *I : S->decls()) +    if (VarDecl *VD = dyn_cast<VarDecl>(I)) +      VisitVarDecl(VD); +} + +void StringRefCheckerVisitor::VisitVarDecl(VarDecl *VD) { +  Expr *Init = VD->getInit(); +  if (!Init) +    return; + +  // Pattern match for: +  // StringRef x = call() (where call returns std::string) +  if (!IsLLVMStringRef(VD->getType())) +    return; +  ExprWithCleanups *Ex1 = dyn_cast<ExprWithCleanups>(Init); +  if (!Ex1) +    return; +  CXXConstructExpr *Ex2 = dyn_cast<CXXConstructExpr>(Ex1->getSubExpr()); +  if (!Ex2 || Ex2->getNumArgs() != 1) +    return; +  ImplicitCastExpr *Ex3 = dyn_cast<ImplicitCastExpr>(Ex2->getArg(0)); +  if (!Ex3) +    return; +  CXXConstructExpr *Ex4 = dyn_cast<CXXConstructExpr>(Ex3->getSubExpr()); +  if (!Ex4 || Ex4->getNumArgs() != 1) +    return; +  ImplicitCastExpr *Ex5 = dyn_cast<ImplicitCastExpr>(Ex4->getArg(0)); +  if (!Ex5) +    return; +  CXXBindTemporaryExpr *Ex6 = dyn_cast<CXXBindTemporaryExpr>(Ex5->getSubExpr()); +  if (!Ex6 || !IsStdString(Ex6->getType())) +    return; + +  // Okay, badness!  Report an error. +  const char *desc = "StringRef should not be bound to temporary " +                     "std::string that it outlives"; +  PathDiagnosticLocation VDLoc = +    PathDiagnosticLocation::createBegin(VD, BR.getSourceManager()); +  BR.EmitBasicReport(DeclWithIssue, Checker, desc, "LLVM Conventions", desc, +                     VDLoc, Init->getSourceRange()); +} + +//===----------------------------------------------------------------------===// +// CHECK: Clang AST nodes should not have fields that can allocate +//   memory. +//===----------------------------------------------------------------------===// + +static bool AllocatesMemory(QualType T) { +  return IsStdVector(T) || IsStdString(T) || IsSmallVector(T); +} + +// This type checking could be sped up via dynamic programming. +static bool IsPartOfAST(const CXXRecordDecl *R) { +  if (IsClangStmt(R) || IsClangType(R) || IsClangDecl(R) || IsClangAttr(R)) +    return true; + +  for (const auto &BS : R->bases()) { +    QualType T = BS.getType(); +    if (const RecordType *baseT = T->getAs<RecordType>()) { +      CXXRecordDecl *baseD = cast<CXXRecordDecl>(baseT->getDecl()); +      if (IsPartOfAST(baseD)) +        return true; +    } +  } + +  return false; +} + +namespace { +class ASTFieldVisitor { +  SmallVector<FieldDecl*, 10> FieldChain; +  const CXXRecordDecl *Root; +  BugReporter &BR; +  const CheckerBase *Checker; + +public: +  ASTFieldVisitor(const CXXRecordDecl *root, BugReporter &br, +                  const CheckerBase *checker) +      : Root(root), BR(br), Checker(checker) {} + +  void Visit(FieldDecl *D); +  void ReportError(QualType T); +}; +} // end anonymous namespace + +static void CheckASTMemory(const CXXRecordDecl *R, BugReporter &BR, +                           const CheckerBase *Checker) { +  if (!IsPartOfAST(R)) +    return; + +  for (auto *I : R->fields()) { +    ASTFieldVisitor walker(R, BR, Checker); +    walker.Visit(I); +  } +} + +void ASTFieldVisitor::Visit(FieldDecl *D) { +  FieldChain.push_back(D); + +  QualType T = D->getType(); + +  if (AllocatesMemory(T)) +    ReportError(T); + +  if (const RecordType *RT = T->getAs<RecordType>()) { +    const RecordDecl *RD = RT->getDecl()->getDefinition(); +    for (auto *I : RD->fields()) +      Visit(I); +  } + +  FieldChain.pop_back(); +} + +void ASTFieldVisitor::ReportError(QualType T) { +  SmallString<1024> buf; +  llvm::raw_svector_ostream os(buf); + +  os << "AST class '" << Root->getName() << "' has a field '" +     << FieldChain.front()->getName() << "' that allocates heap memory"; +  if (FieldChain.size() > 1) { +    os << " via the following chain: "; +    bool isFirst = true; +    for (SmallVectorImpl<FieldDecl*>::iterator I=FieldChain.begin(), +         E=FieldChain.end(); I!=E; ++I) { +      if (!isFirst) +        os << '.'; +      else +        isFirst = false; +      os << (*I)->getName(); +    } +  } +  os << " (type " << FieldChain.back()->getType().getAsString() << ")"; + +  // Note that this will fire for every translation unit that uses this +  // class.  This is suboptimal, but at least scan-build will merge +  // duplicate HTML reports.  In the future we need a unified way of merging +  // duplicate reports across translation units.  For C++ classes we cannot +  // just report warnings when we see an out-of-line method definition for a +  // class, as that heuristic doesn't always work (the complete definition of +  // the class may be in the header file, for example). +  PathDiagnosticLocation L = PathDiagnosticLocation::createBegin( +                               FieldChain.front(), BR.getSourceManager()); +  BR.EmitBasicReport(Root, Checker, "AST node allocates heap memory", +                     "LLVM Conventions", os.str(), L); +} + +//===----------------------------------------------------------------------===// +// LLVMConventionsChecker +//===----------------------------------------------------------------------===// + +namespace { +class LLVMConventionsChecker : public Checker< +                                                check::ASTDecl<CXXRecordDecl>, +                                                check::ASTCodeBody > { +public: +  void checkASTDecl(const CXXRecordDecl *R, AnalysisManager& mgr, +                    BugReporter &BR) const { +    if (R->isCompleteDefinition()) +      CheckASTMemory(R, BR, this); +  } + +  void checkASTCodeBody(const Decl *D, AnalysisManager& mgr, +                        BugReporter &BR) const { +    CheckStringRefAssignedTemporary(D, BR, this); +  } +}; +} + +void ento::registerLLVMConventionsChecker(CheckerManager &mgr) { +  mgr.registerChecker<LLVMConventionsChecker>(); +} + +bool ento::shouldRegisterLLVMConventionsChecker(const LangOptions &LO) { +  return true; +}  | 
