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
10 #include "ThrowByValueCatchByReferenceCheck.h"
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
ThrowByValueCatchByReferenceCheck(StringRef Name,ClangTidyContext * Context)21 ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
22 StringRef Name, ClangTidyContext *Context)
23 : ClangTidyCheck(Name, Context),
24 CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)) {}
25
registerMatchers(MatchFinder * Finder)26 void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) {
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
storeOptions(ClangTidyOptions::OptionMap & Opts)35 void ThrowByValueCatchByReferenceCheck::storeOptions(
36 ClangTidyOptions::OptionMap &Opts) {
37 Options.store(Opts, "CheckThrowTemporaries", true);
38 }
39
check(const MatchFinder::MatchResult & Result)40 void ThrowByValueCatchByReferenceCheck::check(
41 const MatchFinder::MatchResult &Result) {
42 diagnoseThrowLocations(Result.Nodes.getNodeAs<CXXThrowExpr>("throw"));
43 diagnoseCatchLocations(Result.Nodes.getNodeAs<CXXCatchStmt>("catch"),
44 *Result.Context);
45 }
46
isFunctionParameter(const DeclRefExpr * declRefExpr)47 bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
48 const DeclRefExpr *declRefExpr) {
49 return isa<ParmVarDecl>(declRefExpr->getDecl());
50 }
51
isCatchVariable(const DeclRefExpr * declRefExpr)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
isFunctionOrCatchVar(const DeclRefExpr * declRefExpr)60 bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
61 const DeclRefExpr *declRefExpr) {
62 return isFunctionParameter(declRefExpr) || isCatchVariable(declRefExpr);
63 }
64
diagnoseThrowLocations(const CXXThrowExpr * throwExpr)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->getBeginLoc(), "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->getBeginLoc(),
128 "throw expression should throw anonymous temporary values instead");
129 }
130 }
131
diagnoseCatchLocations(const CXXCatchStmt * catchStmt,ASTContext & context)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->getBeginLoc(), 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->getBeginLoc(), diagMsgCatchReference);
156 }
157 }
158
159 } // namespace misc
160 } // namespace tidy
161 } // namespace clang
162