clang-tools 10.0.0

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
9 #include "ThrowByValueCatchByReferenceCheck.h"
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 MaxSize(Options.get("MaxSize", std::numeric_limits<uint64_t>::max())) {}
27
28 void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) {
29 // This is a C++ only check thus we register the matchers only for C++
30 if (!getLangOpts().CPlusPlus)
31 return;
32
33 Finder->addMatcher(cxxThrowExpr().bind("throw"), this);
34 Finder->addMatcher(cxxCatchStmt().bind("catch"), this);
35 }
36
37 void ThrowByValueCatchByReferenceCheck::storeOptions(
38 ClangTidyOptions::OptionMap &Opts) {
39 Options.store(Opts, "CheckThrowTemporaries", true);
40 }
41
42 void ThrowByValueCatchByReferenceCheck::check(
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<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 strng 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 = "catch handler catches a pointer value; "
144 "should throw a non-pointer value and "
145 "catch by reference instead";
146 // We do not diagnose when catching pointer to strings since we also allow
147 // throwing string literals.
148 if (!PT->getPointeeType()->isAnyCharacterType())
149 diag(varDecl->getBeginLoc(), diagMsgCatchReference);
150 } else if (!caughtType->isReferenceType()) {
151 const char *diagMsgCatchReference = "catch handler catches by value; "
152 "should catch by reference instead";
153 // If it's not a pointer and not a reference then it must be caught "by
154 // value". In this case we should emit a diagnosis message unless the type
155 // is trivial.
156 if (!caughtType.isTrivialType(context)) {
157 diag(varDecl->getBeginLoc(), diagMsgCatchReference);
158 } else if (WarnOnLargeObject) {
159 // If the type is trivial, then catching it by reference is not dangerous.
160 // However, catching large objects by value decreases the performance.
161
162 // We can now access `ASTContext` so if `MaxSize` is an extremal value
163 // then set it to the size of `size_t`.
164 if (MaxSize == std::numeric_limits<uint64_t>::max())
165 MaxSize = context.getTypeSize(context.getSizeType());
166 if (context.getTypeSize(caughtType) > MaxSize)
167 diag(varDecl->getBeginLoc(), diagMsgCatchReference);
168 }
169 }
170 }
171
172 } // namespace misc
173 } // namespace tidy
174 } // namespace clang
clang::ast_matchers
Definition: AbseilMatcher.h:14
ThrowByValueCatchByReferenceCheck.h
clang::tidy::ClangTidyCheck
Base class for all clang-tidy checks.
Definition: ClangTidyCheck.h:47
clang::tidy::ClangTidyCheck::getLangOpts
const LangOptions & getLangOpts() const
Returns the language options from the context.
Definition: ClangTidyCheck.h:188
clang::tidy::ClangTidyCheck::Options
OptionsView Options
Definition: ClangTidyCheck.h:184
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:58
Name
static constexpr llvm::StringLiteral Name
Definition: UppercaseLiteralSuffixCheck.cpp:27
clang::tidy::ClangTidyOptions::OptionMap
std::map< std::string, std::string > OptionMap
Definition: ClangTidyOptions.h:97
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
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:37
clang::tidy::ClangTidyContext
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
Definition: ClangTidyDiagnosticConsumer.h:77
clang::tidy::misc::ThrowByValueCatchByReferenceCheck::registerMatchers
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
Definition: ThrowByValueCatchByReferenceCheck.cpp:28
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::diag
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check&#39;s name.
Definition: ClangTidyCheck.cpp:21