xref: /llvm-project/clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp (revision 7d2ea6c422d3f5712b7253407005e1a465a76946)
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::tidy::misc {
17 
ThrowByValueCatchByReferenceCheck(StringRef Name,ClangTidyContext * Context)18 ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
19     StringRef Name, ClangTidyContext *Context)
20     : ClangTidyCheck(Name, Context),
21       CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)),
22       WarnOnLargeObject(Options.get("WarnOnLargeObject", false)),
23       // Cannot access `ASTContext` from here so set it to an extremal value.
24       MaxSizeOptions(
25           Options.get("MaxSize", std::numeric_limits<uint64_t>::max())),
26       MaxSize(MaxSizeOptions) {}
27 
registerMatchers(MatchFinder * Finder)28 void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) {
29   Finder->addMatcher(cxxThrowExpr().bind("throw"), this);
30   Finder->addMatcher(cxxCatchStmt().bind("catch"), this);
31 }
32 
storeOptions(ClangTidyOptions::OptionMap & Opts)33 void ThrowByValueCatchByReferenceCheck::storeOptions(
34     ClangTidyOptions::OptionMap &Opts) {
35   Options.store(Opts, "CheckThrowTemporaries", true);
36   Options.store(Opts, "WarnOnLargeObjects", WarnOnLargeObject);
37   Options.store(Opts, "MaxSize", MaxSizeOptions);
38 }
39 
check(const MatchFinder::MatchResult & Result)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 
isFunctionParameter(const DeclRefExpr * DeclRefExpr)47 bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
48     const DeclRefExpr *DeclRefExpr) {
49   return isa<ParmVarDecl>(DeclRefExpr->getDecl());
50 }
51 
isCatchVariable(const DeclRefExpr * DeclRefExpr)52 bool ThrowByValueCatchByReferenceCheck::isCatchVariable(
53     const DeclRefExpr *DeclRefExpr) {
54   auto *ValueDecl = DeclRefExpr->getDecl();
55   if (auto *VarDecl = dyn_cast<clang::VarDecl>(ValueDecl))
56     return VarDecl->isExceptionVariable();
57   return false;
58 }
59 
isFunctionOrCatchVar(const DeclRefExpr * DeclRefExpr)60 bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
61     const DeclRefExpr *DeclRefExpr) {
62   return isFunctionParameter(DeclRefExpr) || isCatchVariable(DeclRefExpr);
63 }
64 
diagnoseThrowLocations(const CXXThrowExpr * ThrowExpr)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 string 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->getBeginLoc(), "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 auto *VariableReference = dyn_cast<DeclRefExpr>(CurrentSubExpr);
104     const auto *ConstructorCall = dyn_cast<CXXConstructExpr>(CurrentSubExpr);
105     // 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->getBeginLoc(),
128            "throw expression should throw anonymous temporary values instead");
129   }
130 }
131 
diagnoseCatchLocations(const CXXCatchStmt * CatchStmt,ASTContext & Context)132 void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
133     const CXXCatchStmt *CatchStmt, ASTContext &Context) {
134   if (!CatchStmt)
135     return;
136   auto CaughtType = CatchStmt->getCaughtType();
137   if (CaughtType.isNull())
138     return;
139   auto *VarDecl = CatchStmt->getExceptionDecl();
140   if (const auto *PT = CaughtType.getCanonicalType()->getAs<PointerType>()) {
141     const char *DiagMsgCatchReference =
142         "catch handler catches a pointer value; "
143         "should throw a non-pointer value and "
144         "catch by reference instead";
145     // We do not diagnose when catching pointer to strings since we also allow
146     // throwing string literals.
147     if (!PT->getPointeeType()->isAnyCharacterType())
148       diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
149   } else if (!CaughtType->isReferenceType()) {
150     const char *DiagMsgCatchReference = "catch handler catches by value; "
151                                         "should catch by reference instead";
152     // If it's not a pointer and not a reference then it must be caught "by
153     // value". In this case we should emit a diagnosis message unless the type
154     // is trivial.
155     if (!CaughtType.isTrivialType(Context)) {
156       diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
157     } else if (WarnOnLargeObject) {
158       // If the type is trivial, then catching it by reference is not dangerous.
159       // However, catching large objects by value decreases the performance.
160 
161       // We can now access `ASTContext` so if `MaxSize` is an extremal value
162       // then set it to the size of `size_t`.
163       if (MaxSize == std::numeric_limits<uint64_t>::max())
164         MaxSize = Context.getTypeSize(Context.getSizeType());
165       if (Context.getTypeSize(CaughtType) > MaxSize)
166         diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
167     }
168   }
169 }
170 
171 } // namespace clang::tidy::misc
172