xref: /llvm-project/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp (revision 7c7ea7d0ae4041e92a4d510874e78da9f5e3bc1c)
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 
21 ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
22     StringRef Name, ClangTidyContext *Context)
23     : ClangTidyCheck(Name, Context),
24       CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)) {}
25 
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 
35 void ThrowByValueCatchByReferenceCheck::storeOptions(
36     ClangTidyOptions::OptionMap &Opts) {
37   Options.store(Opts, "CheckThrowTemporaries", true);
38 }
39 
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 
47 bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
48     const DeclRefExpr *declRefExpr) {
49   return isa<ParmVarDecl>(declRefExpr->getDecl());
50 }
51 
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 
60 bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
61     const DeclRefExpr *declRefExpr) {
62   return isFunctionParameter(declRefExpr) || isCatchVariable(declRefExpr);
63 }
64 
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->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 
134 void 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 
159 } // namespace misc
160 } // namespace tidy
161 } // namespace clang
162