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