blob: b7077a1225f19f91e8f430289a39cd19f0d55a69 [file] [log] [blame]
Aaron Ballmanfd78cc82015-10-09 20:42:44 +00001//===--- ThrowByValueCatchByReferenceCheck.cpp - clang-tidy----------------===//
2//
Chandler Carruth2946cd72019-01-19 08:50:56 +00003// 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
Aaron Ballmanfd78cc82015-10-09 20:42:44 +00006//
7//===----------------------------------------------------------------------===//
8
9#include "ThrowByValueCatchByReferenceCheck.h"
10#include "clang/AST/ASTContext.h"
Aaron Ballmanfd78cc82015-10-09 20:42:44 +000011#include "clang/AST/OperationKinds.h"
Mandeep Singh Grang7c7ea7d2016-11-08 07:50:19 +000012#include "clang/ASTMatchers/ASTMatchFinder.h"
Aaron Ballmanfd78cc82015-10-09 20:42:44 +000013
14using namespace clang::ast_matchers;
15
16namespace clang {
17namespace tidy {
Kirill Bobyrev11cea452016-08-01 12:06:18 +000018namespace misc {
Aaron Ballmanfd78cc82015-10-09 20:42:44 +000019
20ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
21 StringRef Name, ClangTidyContext *Context)
22 : ClangTidyCheck(Name, Context),
23 CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)) {}
24
25void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) {
26 // This is a C++ only check thus we register the matchers only for C++
27 if (!getLangOpts().CPlusPlus)
28 return;
29
30 Finder->addMatcher(cxxThrowExpr().bind("throw"), this);
31 Finder->addMatcher(cxxCatchStmt().bind("catch"), this);
32}
33
34void ThrowByValueCatchByReferenceCheck::storeOptions(
35 ClangTidyOptions::OptionMap &Opts) {
36 Options.store(Opts, "CheckThrowTemporaries", true);
37}
38
39void ThrowByValueCatchByReferenceCheck::check(
40 const MatchFinder::MatchResult &Result) {
41 diagnoseThrowLocations(Result.Nodes.getNodeAs<CXXThrowExpr>("throw"));
42 diagnoseCatchLocations(Result.Nodes.getNodeAs<CXXCatchStmt>("catch"),
43 *Result.Context);
44}
45
46bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
47 const DeclRefExpr *declRefExpr) {
48 return isa<ParmVarDecl>(declRefExpr->getDecl());
49}
50
51bool ThrowByValueCatchByReferenceCheck::isCatchVariable(
52 const DeclRefExpr *declRefExpr) {
53 auto *valueDecl = declRefExpr->getDecl();
54 if (auto *varDecl = dyn_cast<VarDecl>(valueDecl))
55 return varDecl->isExceptionVariable();
56 return false;
57}
58
59bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
60 const DeclRefExpr *declRefExpr) {
61 return isFunctionParameter(declRefExpr) || isCatchVariable(declRefExpr);
62}
63
64void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations(
65 const CXXThrowExpr *throwExpr) {
66 if (!throwExpr)
67 return;
68 auto *subExpr = throwExpr->getSubExpr();
69 if (!subExpr)
70 return;
71 auto qualType = subExpr->getType();
72 if (qualType->isPointerType()) {
73 // The code is throwing a pointer.
74 // In case it is strng literal, it is safe and we return.
75 auto *inner = subExpr->IgnoreParenImpCasts();
76 if (isa<StringLiteral>(inner))
77 return;
78 // If it's a variable from a catch statement, we return as well.
79 auto *declRef = dyn_cast<DeclRefExpr>(inner);
80 if (declRef && isCatchVariable(declRef)) {
81 return;
82 }
Stephen Kelly43465bf2018-08-09 22:42:26 +000083 diag(subExpr->getBeginLoc(), "throw expression throws a pointer; it should "
Aaron Ballmanfd78cc82015-10-09 20:42:44 +000084 "throw a non-pointer value instead");
85 }
86 // If the throw statement does not throw by pointer then it throws by value
87 // which is ok.
88 // There are addition checks that emit diagnosis messages if the thrown value
89 // is not an RValue. See:
90 // https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries
91 // This behavior can be influenced by an option.
92
93 // If we encounter a CXXThrowExpr, we move through all casts until you either
94 // encounter a DeclRefExpr or a CXXConstructExpr.
95 // If it's a DeclRefExpr, we emit a message if the referenced variable is not
96 // a catch variable or function parameter.
97 // When encountering a CopyOrMoveConstructor: emit message if after casts,
98 // the expression is a LValue
99 if (CheckAnonymousTemporaries) {
100 bool emit = false;
101 auto *currentSubExpr = subExpr->IgnoreImpCasts();
Piotr Padlewski08124b12016-12-14 15:29:23 +0000102 const auto *variableReference = dyn_cast<DeclRefExpr>(currentSubExpr);
103 const auto *constructorCall = dyn_cast<CXXConstructExpr>(currentSubExpr);
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000104 // If we have a DeclRefExpr, we flag for emitting a diagnosis message in
105 // case the referenced variable is neither a function parameter nor a
106 // variable declared in the catch statement.
107 if (variableReference)
108 emit = !isFunctionOrCatchVar(variableReference);
109 else if (constructorCall &&
110 constructorCall->getConstructor()->isCopyOrMoveConstructor()) {
111 // If we have a copy / move construction, we emit a diagnosis message if
112 // the object that we copy construct from is neither a function parameter
113 // nor a variable declared in a catch statement
114 auto argIter =
115 constructorCall
116 ->arg_begin(); // there's only one for copy constructors
117 auto *currentSubExpr = (*argIter)->IgnoreImpCasts();
118 if (currentSubExpr->isLValue()) {
119 if (auto *tmp = dyn_cast<DeclRefExpr>(currentSubExpr))
120 emit = !isFunctionOrCatchVar(tmp);
121 else if (isa<CallExpr>(currentSubExpr))
122 emit = true;
123 }
124 }
125 if (emit)
Stephen Kelly43465bf2018-08-09 22:42:26 +0000126 diag(subExpr->getBeginLoc(),
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000127 "throw expression should throw anonymous temporary values instead");
128 }
129}
130
131void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
132 const CXXCatchStmt *catchStmt, ASTContext &context) {
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000133 if (!catchStmt)
134 return;
135 auto caughtType = catchStmt->getCaughtType();
136 if (caughtType.isNull())
137 return;
138 auto *varDecl = catchStmt->getExceptionDecl();
139 if (const auto *PT = caughtType.getCanonicalType()->getAs<PointerType>()) {
Alexander Kornienko5816a8b2017-03-23 15:17:44 +0000140 const char *diagMsgCatchReference = "catch handler catches a pointer value; "
141 "should throw a non-pointer value and "
142 "catch by reference instead";
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000143 // We do not diagnose when catching pointer to strings since we also allow
144 // throwing string literals.
145 if (!PT->getPointeeType()->isAnyCharacterType())
Stephen Kelly43465bf2018-08-09 22:42:26 +0000146 diag(varDecl->getBeginLoc(), diagMsgCatchReference);
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000147 } else if (!caughtType->isReferenceType()) {
Alexander Kornienko5816a8b2017-03-23 15:17:44 +0000148 const char *diagMsgCatchReference = "catch handler catches by value; "
149 "should catch by reference instead";
150 // If it's not a pointer and not a reference then it must be caught "by
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000151 // value". In this case we should emit a diagnosis message unless the type
152 // is trivial.
153 if (!caughtType.isTrivialType(context))
Stephen Kelly43465bf2018-08-09 22:42:26 +0000154 diag(varDecl->getBeginLoc(), diagMsgCatchReference);
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000155 }
156}
157
Etienne Bergeron456177b2016-05-02 18:00:29 +0000158} // namespace misc
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000159} // namespace tidy
160} // namespace clang