blob: 8b8c379ac6967c95a2cc4b71fad8c2d3ad0cc0b9 [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"
12#include "clang/ASTMatchers/ASTMatchFinder.h"
13#include "clang/AST/OperationKinds.h"
14
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();
103 const DeclRefExpr *variableReference =
104 dyn_cast<DeclRefExpr>(currentSubExpr);
105 const CXXConstructExpr *constructorCall =
106 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->getLocStart(),
130 "throw expression should throw anonymous temporary values instead");
131 }
132}
133
134void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
135 const CXXCatchStmt *catchStmt, ASTContext &context) {
136 const char *diagMsgCatchReference = "catch handler catches a pointer value; "
137 "should throw a non-pointer value and "
138 "catch by reference instead";
139 if (!catchStmt)
140 return;
141 auto caughtType = catchStmt->getCaughtType();
142 if (caughtType.isNull())
143 return;
144 auto *varDecl = catchStmt->getExceptionDecl();
145 if (const auto *PT = caughtType.getCanonicalType()->getAs<PointerType>()) {
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->getLocStart(), diagMsgCatchReference);
150 } else if (!caughtType->isReferenceType()) {
151 // If it's not a pointer and not a reference then it must be thrown "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->getLocStart(), diagMsgCatchReference);
156 }
157}
158
Etienne Bergeron456177b2016-05-02 18:00:29 +0000159} // namespace misc
Aaron Ballmanfd78cc82015-10-09 20:42:44 +0000160} // namespace tidy
161} // namespace clang