clang-tools  7.0.0
ThrowByValueCatchByReferenceCheck.cpp
Go to the documentation of this file.
1 //===--- ThrowByValueCatchByReferenceCheck.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 
11 #include "clang/AST/ASTContext.h"
12 #include "clang/AST/OperationKinds.h"
13 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 
15 using namespace clang::ast_matchers;
16 
17 namespace clang {
18 namespace tidy {
19 namespace misc {
20 
21 ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
22  StringRef Name, ClangTidyContext *Context)
23  : ClangTidyCheck(Name, Context),
24  CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)) {}
25 
27  // This is a C++ only check thus we register the matchers only for C++
28  if (!getLangOpts().CPlusPlus)
29  return;
30 
31  Finder->addMatcher(cxxThrowExpr().bind("throw"), this);
32  Finder->addMatcher(cxxCatchStmt().bind("catch"), this);
33 }
34 
37  Options.store(Opts, "CheckThrowTemporaries", true);
38 }
39 
41  const MatchFinder::MatchResult &Result) {
42  diagnoseThrowLocations(Result.Nodes.getNodeAs<CXXThrowExpr>("throw"));
43  diagnoseCatchLocations(Result.Nodes.getNodeAs<CXXCatchStmt>("catch"),
44  *Result.Context);
45 }
46 
47 bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
48  const DeclRefExpr *declRefExpr) {
49  return isa<ParmVarDecl>(declRefExpr->getDecl());
50 }
51 
52 bool ThrowByValueCatchByReferenceCheck::isCatchVariable(
53  const DeclRefExpr *declRefExpr) {
54  auto *valueDecl = declRefExpr->getDecl();
55  if (auto *varDecl = dyn_cast<VarDecl>(valueDecl))
56  return varDecl->isExceptionVariable();
57  return false;
58 }
59 
60 bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
61  const DeclRefExpr *declRefExpr) {
62  return isFunctionParameter(declRefExpr) || isCatchVariable(declRefExpr);
63 }
64 
65 void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations(
66  const CXXThrowExpr *throwExpr) {
67  if (!throwExpr)
68  return;
69  auto *subExpr = throwExpr->getSubExpr();
70  if (!subExpr)
71  return;
72  auto qualType = subExpr->getType();
73  if (qualType->isPointerType()) {
74  // The code is throwing a pointer.
75  // In case it is strng literal, it is safe and we return.
76  auto *inner = subExpr->IgnoreParenImpCasts();
77  if (isa<StringLiteral>(inner))
78  return;
79  // If it's a variable from a catch statement, we return as well.
80  auto *declRef = dyn_cast<DeclRefExpr>(inner);
81  if (declRef && isCatchVariable(declRef)) {
82  return;
83  }
84  diag(subExpr->getLocStart(), "throw expression throws a pointer; it should "
85  "throw a non-pointer value instead");
86  }
87  // If the throw statement does not throw by pointer then it throws by value
88  // which is ok.
89  // There are addition checks that emit diagnosis messages if the thrown value
90  // is not an RValue. See:
91  // https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries
92  // This behavior can be influenced by an option.
93 
94  // If we encounter a CXXThrowExpr, we move through all casts until you either
95  // encounter a DeclRefExpr or a CXXConstructExpr.
96  // If it's a DeclRefExpr, we emit a message if the referenced variable is not
97  // a catch variable or function parameter.
98  // When encountering a CopyOrMoveConstructor: emit message if after casts,
99  // the expression is a LValue
100  if (CheckAnonymousTemporaries) {
101  bool emit = false;
102  auto *currentSubExpr = subExpr->IgnoreImpCasts();
103  const auto *variableReference = dyn_cast<DeclRefExpr>(currentSubExpr);
104  const auto *constructorCall = dyn_cast<CXXConstructExpr>(currentSubExpr);
105  // If we have a DeclRefExpr, we flag for emitting a diagnosis message in
106  // case the referenced variable is neither a function parameter nor a
107  // variable declared in the catch statement.
108  if (variableReference)
109  emit = !isFunctionOrCatchVar(variableReference);
110  else if (constructorCall &&
111  constructorCall->getConstructor()->isCopyOrMoveConstructor()) {
112  // If we have a copy / move construction, we emit a diagnosis message if
113  // the object that we copy construct from is neither a function parameter
114  // nor a variable declared in a catch statement
115  auto argIter =
116  constructorCall
117  ->arg_begin(); // there's only one for copy constructors
118  auto *currentSubExpr = (*argIter)->IgnoreImpCasts();
119  if (currentSubExpr->isLValue()) {
120  if (auto *tmp = dyn_cast<DeclRefExpr>(currentSubExpr))
121  emit = !isFunctionOrCatchVar(tmp);
122  else if (isa<CallExpr>(currentSubExpr))
123  emit = true;
124  }
125  }
126  if (emit)
127  diag(subExpr->getLocStart(),
128  "throw expression should throw anonymous temporary values instead");
129  }
130 }
131 
132 void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
133  const CXXCatchStmt *catchStmt, ASTContext &context) {
134  if (!catchStmt)
135  return;
136  auto caughtType = catchStmt->getCaughtType();
137  if (caughtType.isNull())
138  return;
139  auto *varDecl = catchStmt->getExceptionDecl();
140  if (const auto *PT = caughtType.getCanonicalType()->getAs<PointerType>()) {
141  const char *diagMsgCatchReference = "catch handler catches a pointer value; "
142  "should throw a non-pointer value and "
143  "catch by reference instead";
144  // We do not diagnose when catching pointer to strings since we also allow
145  // throwing string literals.
146  if (!PT->getPointeeType()->isAnyCharacterType())
147  diag(varDecl->getLocStart(), diagMsgCatchReference);
148  } else if (!caughtType->isReferenceType()) {
149  const char *diagMsgCatchReference = "catch handler catches by value; "
150  "should catch by reference instead";
151  // If it's not a pointer and not a reference then it must be caught "by
152  // value". In this case we should emit a diagnosis message unless the type
153  // is trivial.
154  if (!caughtType.isTrivialType(context))
155  diag(varDecl->getLocStart(), diagMsgCatchReference);
156  }
157 }
158 
159 } // namespace misc
160 } // namespace tidy
161 } // namespace clang
llvm::StringRef Name
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:460
LangOptions getLangOpts() const
Returns the language options from the context.
Definition: ClangTidy.h:187
Base class for all clang-tidy checks.
Definition: ClangTidy.h:127
std::map< std::string, std::string > OptionMap
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidy.cpp:427