clang-tools  8.0.0
ExceptionEscapeCheck.cpp
Go to the documentation of this file.
1 //===--- ExceptionEscapeCheck.cpp - clang-tidy-----------------------------===//
2 //
3 // The LLVM Compiler Infrastructure
4 //
5 // This file is distributed under the University of Illinois Open Source
6 // License. See LICENSE.TXT for details.
7 //
8 //===----------------------------------------------------------------------===//
9 
10 #include "ExceptionEscapeCheck.h"
11 
12 #include "clang/AST/ASTContext.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 
15 #include "llvm/ADT/SmallSet.h"
16 #include "llvm/ADT/StringSet.h"
17 
18 using namespace clang::ast_matchers;
19 
20 namespace {
21 typedef llvm::SmallVector<const clang::Type *, 8> TypeVec;
22 } // namespace
23 
24 namespace clang {
25 
26 static bool isBaseOf(const Type *DerivedType, const Type *BaseType) {
27  const auto *DerivedClass = DerivedType->getAsCXXRecordDecl();
28  const auto *BaseClass = BaseType->getAsCXXRecordDecl();
29  if (!DerivedClass || !BaseClass)
30  return false;
31 
32  return !DerivedClass->forallBases(
33  [BaseClass](const CXXRecordDecl *Cur) { return Cur != BaseClass; });
34 }
35 
36 static const TypeVec
37 throwsException(const Stmt *St, const TypeVec &Caught,
38  llvm::SmallSet<const FunctionDecl *, 32> &CallStack);
39 
40 static const TypeVec
41 throwsException(const FunctionDecl *Func,
42  llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
43  if (CallStack.count(Func))
44  return TypeVec();
45 
46  if (const Stmt *Body = Func->getBody()) {
47  CallStack.insert(Func);
48  const TypeVec Result = throwsException(Body, TypeVec(), CallStack);
49  CallStack.erase(Func);
50  return Result;
51  }
52 
53  TypeVec Result;
54  if (const auto *FPT = Func->getType()->getAs<FunctionProtoType>()) {
55  for (const QualType Ex : FPT->exceptions()) {
56  Result.push_back(Ex.getTypePtr());
57  }
58  }
59  return Result;
60 }
61 
62 static const TypeVec
63 throwsException(const Stmt *St, const TypeVec &Caught,
64  llvm::SmallSet<const FunctionDecl *, 32> &CallStack) {
65  TypeVec Results;
66 
67  if (!St)
68  return Results;
69 
70  if (const auto *Throw = dyn_cast<CXXThrowExpr>(St)) {
71  if (const auto *ThrownExpr = Throw->getSubExpr()) {
72  const auto *ThrownType =
73  ThrownExpr->getType()->getUnqualifiedDesugaredType();
74  if (ThrownType->isReferenceType()) {
75  ThrownType = ThrownType->castAs<ReferenceType>()
76  ->getPointeeType()
77  ->getUnqualifiedDesugaredType();
78  }
79  if (const auto *TD = ThrownType->getAsTagDecl()) {
80  if (TD->getDeclName().isIdentifier() && TD->getName() == "bad_alloc"
81  && TD->isInStdNamespace())
82  return Results;
83  }
84  Results.push_back(ThrownExpr->getType()->getUnqualifiedDesugaredType());
85  } else {
86  Results.append(Caught.begin(), Caught.end());
87  }
88  } else if (const auto *Try = dyn_cast<CXXTryStmt>(St)) {
89  TypeVec Uncaught = throwsException(Try->getTryBlock(), Caught, CallStack);
90  for (unsigned i = 0; i < Try->getNumHandlers(); ++i) {
91  const CXXCatchStmt *Catch = Try->getHandler(i);
92  if (!Catch->getExceptionDecl()) {
93  const TypeVec Rethrown =
94  throwsException(Catch->getHandlerBlock(), Uncaught, CallStack);
95  Results.append(Rethrown.begin(), Rethrown.end());
96  Uncaught.clear();
97  } else {
98  const auto *CaughtType =
99  Catch->getCaughtType()->getUnqualifiedDesugaredType();
100  if (CaughtType->isReferenceType()) {
101  CaughtType = CaughtType->castAs<ReferenceType>()
102  ->getPointeeType()
103  ->getUnqualifiedDesugaredType();
104  }
105  auto NewEnd =
106  llvm::remove_if(Uncaught, [&CaughtType](const Type *ThrownType) {
107  return ThrownType == CaughtType ||
108  isBaseOf(ThrownType, CaughtType);
109  });
110  if (NewEnd != Uncaught.end()) {
111  Uncaught.erase(NewEnd, Uncaught.end());
112  const TypeVec Rethrown = throwsException(
113  Catch->getHandlerBlock(), TypeVec(1, CaughtType), CallStack);
114  Results.append(Rethrown.begin(), Rethrown.end());
115  }
116  }
117  }
118  Results.append(Uncaught.begin(), Uncaught.end());
119  } else if (const auto *Call = dyn_cast<CallExpr>(St)) {
120  if (const FunctionDecl *Func = Call->getDirectCallee()) {
121  TypeVec Excs = throwsException(Func, CallStack);
122  Results.append(Excs.begin(), Excs.end());
123  }
124  } else {
125  for (const Stmt *Child : St->children()) {
126  TypeVec Excs = throwsException(Child, Caught, CallStack);
127  Results.append(Excs.begin(), Excs.end());
128  }
129  }
130  return Results;
131 }
132 
133 static const TypeVec throwsException(const FunctionDecl *Func) {
134  llvm::SmallSet<const FunctionDecl *, 32> CallStack;
135  return throwsException(Func, CallStack);
136 }
137 
138 namespace ast_matchers {
139 AST_MATCHER_P(FunctionDecl, throws, internal::Matcher<Type>, InnerMatcher) {
140  TypeVec ExceptionList = throwsException(&Node);
141  auto NewEnd = llvm::remove_if(
142  ExceptionList, [this, Finder, Builder](const Type *Exception) {
143  return !InnerMatcher.matches(*Exception, Finder, Builder);
144  });
145  ExceptionList.erase(NewEnd, ExceptionList.end());
146  return ExceptionList.size();
147 }
148 
149 AST_MATCHER_P(Type, isIgnored, llvm::StringSet<>, IgnoredExceptions) {
150  if (const auto *TD = Node.getAsTagDecl()) {
151  if (TD->getDeclName().isIdentifier())
152  return IgnoredExceptions.count(TD->getName()) > 0;
153  }
154  return false;
155 }
156 
157 AST_MATCHER_P(FunctionDecl, isEnabled, llvm::StringSet<>,
158  FunctionsThatShouldNotThrow) {
159  return FunctionsThatShouldNotThrow.count(Node.getNameAsString()) > 0;
160 }
161 } // namespace ast_matchers
162 
163 namespace tidy {
164 namespace bugprone {
165 
166 ExceptionEscapeCheck::ExceptionEscapeCheck(StringRef Name,
167  ClangTidyContext *Context)
168  : ClangTidyCheck(Name, Context), RawFunctionsThatShouldNotThrow(Options.get(
169  "FunctionsThatShouldNotThrow", "")),
170  RawIgnoredExceptions(Options.get("IgnoredExceptions", "")) {
171  llvm::SmallVector<StringRef, 8> FunctionsThatShouldNotThrowVec,
172  IgnoredExceptionsVec;
173  StringRef(RawFunctionsThatShouldNotThrow)
174  .split(FunctionsThatShouldNotThrowVec, ",", -1, false);
175  FunctionsThatShouldNotThrow.insert(FunctionsThatShouldNotThrowVec.begin(),
176  FunctionsThatShouldNotThrowVec.end());
177  StringRef(RawIgnoredExceptions).split(IgnoredExceptionsVec, ",", -1, false);
178  IgnoredExceptions.insert(IgnoredExceptionsVec.begin(),
179  IgnoredExceptionsVec.end());
180 }
181 
183  Options.store(Opts, "FunctionsThatShouldNotThrow",
184  RawFunctionsThatShouldNotThrow);
185  Options.store(Opts, "IgnoredExceptions", RawIgnoredExceptions);
186 }
187 
188 void ExceptionEscapeCheck::registerMatchers(MatchFinder *Finder) {
189  if (!getLangOpts().CPlusPlus || !getLangOpts().CXXExceptions)
190  return;
191 
192  Finder->addMatcher(
193  functionDecl(anyOf(isNoThrow(), cxxDestructorDecl(),
194  cxxConstructorDecl(isMoveConstructor()),
195  cxxMethodDecl(isMoveAssignmentOperator()),
196  hasName("main"), hasName("swap"),
197  isEnabled(FunctionsThatShouldNotThrow)),
198  throws(unless(isIgnored(IgnoredExceptions))))
199  .bind("thrower"),
200  this);
201 }
202 
203 void ExceptionEscapeCheck::check(const MatchFinder::MatchResult &Result) {
204  const FunctionDecl *MatchedDecl =
205  Result.Nodes.getNodeAs<FunctionDecl>("thrower");
206  if (!MatchedDecl)
207  return;
208 
209  // FIXME: We should provide more information about the exact location where
210  // the exception is thrown, maybe the full path the exception escapes
211  diag(MatchedDecl->getLocation(), "an exception may be thrown in function %0 "
212  "which should not throw exceptions") << MatchedDecl;
213 }
214 
215 } // namespace bugprone
216 } // namespace tidy
217 } // namespace clang
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Definition: ClangTidy.cpp:473
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:187
static bool isBaseOf(const Type *DerivedType, const Type *BaseType)
std::vector< CodeCompletionResult > Results
Base class for all clang-tidy checks.
Definition: ClangTidy.h:127
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
static constexpr llvm::StringLiteral Name
std::map< std::string, std::string > OptionMap
llvm::Optional< llvm::Expected< tooling::AtomicChanges > > Result
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
AST_MATCHER_P(FunctionDecl, throws, internal::Matcher< Type >, InnerMatcher)
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
static const TypeVec throwsException(const Stmt *St, const TypeVec &Caught, llvm::SmallSet< const FunctionDecl *, 32 > &CallStack)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidy.cpp:438