blob: 00befd2e7cee1a894c396a4cbb357fb8feee2e5e [file] [log] [blame]
Aaron Ballmanfd78cc82015-10-09 20:42:44 +00001//===--- 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"
Aaron Ballmanfd78cc82015-10-09 20:42:44 +000012#include "clang/AST/OperationKinds.h"
Mandeep Singh Grang7c7ea7d2016-11-08 07:50:19 +000013#include "clang/ASTMatchers/ASTMatchFinder.h"
Aaron Ballmanfd78cc82015-10-09 20:42:44 +000014
15using namespace clang::ast_matchers;
16
17namespace clang {
18namespace tidy {
Kirill Bobyrev11cea452016-08-01 12:06:18 +000019namespace misc {
Aaron Ballmanfd78cc82015-10-09 20:42:44 +000020
21ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
22 StringRef Name, ClangTidyContext *Context)
23 : ClangTidyCheck(Name, Context),
24 CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)) {}
25
26void 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
35void ThrowByValueCatchByReferenceCheck::storeOptions(
36 ClangTidyOptions::OptionMap &Opts) {
37 Options.store(Opts, "CheckThrowTemporaries", true);
38}
39
40void 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
47bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
48 const DeclRefExpr *declRefExpr) {
49 return isa<ParmVarDecl>(declRefExpr->getDecl());
50}
51
52bool 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
60bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
61 const DeclRefExpr *declRefExpr) {
62 return isFunctionParameter(declRefExpr) || isCatchVariable(declRefExpr);
63}
64
65void 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();
Piotr Padlewski08124b12016-12-14 15:29:23 +0000103 const auto *variableReference = dyn_cast<DeclRefExpr>(currentSubExpr);
104 const auto *constructorCall = dyn_cast<CXXConstructExpr>(currentSubExpr);
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000105 // 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
132void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
133 const CXXCatchStmt *catchStmt, ASTContext &context) {
134 const char *diagMsgCatchReference = "catch handler catches a pointer value; "
135 "should throw a non-pointer value and "
136 "catch by reference instead";
137 if (!catchStmt)
138 return;
139 auto caughtType = catchStmt->getCaughtType();
140 if (caughtType.isNull())
141 return;
142 auto *varDecl = catchStmt->getExceptionDecl();
143 if (const auto *PT = caughtType.getCanonicalType()->getAs<PointerType>()) {
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 // If it's not a pointer and not a reference then it must be thrown "by
150 // value". In this case we should emit a diagnosis message unless the type
151 // is trivial.
152 if (!caughtType.isTrivialType(context))
153 diag(varDecl->getLocStart(), diagMsgCatchReference);
154 }
155}
156
Etienne Bergeron456177b2016-05-02 18:00:29 +0000157} // namespace misc
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000158} // namespace tidy
159} // namespace clang