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 
25 void 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 
34 void ThrowByValueCatchByReferenceCheck::storeOptions(
35     ClangTidyOptions::OptionMap &Opts) {
36   Options.store(Opts, "CheckThrowTemporaries", true);
37 }
38 
39 void 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 
46 bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
47     const DeclRefExpr *declRefExpr) {
48   return isa<ParmVarDecl>(declRefExpr->getDecl());
49 }
50 
51 bool 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 
59 bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
60     const DeclRefExpr *declRefExpr) {
61   return isFunctionParameter(declRefExpr) || isCatchVariable(declRefExpr);
62 }
63 
64 void 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     }
83     diag(subExpr->getBeginLoc(), "throw expression throws a pointer; it should "
84                                  "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();
102     const auto *variableReference = dyn_cast<DeclRefExpr>(currentSubExpr);
103     const auto *constructorCall = dyn_cast<CXXConstructExpr>(currentSubExpr);
104     // 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)
126       diag(subExpr->getBeginLoc(),
127            "throw expression should throw anonymous temporary values instead");
128   }
129 }
130 
131 void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
132     const CXXCatchStmt *catchStmt, ASTContext &context) {
133   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>()) {
140     const char *diagMsgCatchReference = "catch handler catches a pointer value; "
141                                         "should throw a non-pointer value and "
142                                         "catch by reference instead";
143     // We do not diagnose when catching pointer to strings since we also allow
144     // throwing string literals.
145     if (!PT->getPointeeType()->isAnyCharacterType())
146       diag(varDecl->getBeginLoc(), diagMsgCatchReference);
147   } else if (!caughtType->isReferenceType()) {
148     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
151     // value". In this case we should emit a diagnosis message unless the type
152     // is trivial.
153     if (!caughtType.isTrivialType(context))
154       diag(varDecl->getBeginLoc(), diagMsgCatchReference);
155   }
156 }
157 
158 } // namespace misc
159 } // namespace tidy
160 } // namespace clang
161