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 
ThrowByValueCatchByReferenceCheck(StringRef Name,ClangTidyContext * Context)20 ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
21     StringRef Name, ClangTidyContext *Context)
22     : ClangTidyCheck(Name, Context),
23       CheckAnonymousTemporaries(Options.get("CheckThrowTemporaries", true)),
24       WarnOnLargeObject(Options.get("WarnOnLargeObject", false)),
25       // Cannot access `ASTContext` from here so set it to an extremal value.
26       MaxSizeOptions(
27           Options.get("MaxSize", std::numeric_limits<uint64_t>::max())),
28       MaxSize(MaxSizeOptions) {}
29 
registerMatchers(MatchFinder * Finder)30 void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) {
31   Finder->addMatcher(cxxThrowExpr().bind("throw"), this);
32   Finder->addMatcher(cxxCatchStmt().bind("catch"), this);
33 }
34 
storeOptions(ClangTidyOptions::OptionMap & Opts)35 void ThrowByValueCatchByReferenceCheck::storeOptions(
36     ClangTidyOptions::OptionMap &Opts) {
37   Options.store(Opts, "CheckThrowTemporaries", true);
38   Options.store(Opts, "WarnOnLargeObjects", WarnOnLargeObject);
39   Options.store(Opts, "MaxSize", MaxSizeOptions);
40 }
41 
check(const MatchFinder::MatchResult & Result)42 void ThrowByValueCatchByReferenceCheck::check(
43     const MatchFinder::MatchResult &Result) {
44   diagnoseThrowLocations(Result.Nodes.getNodeAs<CXXThrowExpr>("throw"));
45   diagnoseCatchLocations(Result.Nodes.getNodeAs<CXXCatchStmt>("catch"),
46                          *Result.Context);
47 }
48 
isFunctionParameter(const DeclRefExpr * DeclRefExpr)49 bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
50     const DeclRefExpr *DeclRefExpr) {
51   return isa<ParmVarDecl>(DeclRefExpr->getDecl());
52 }
53 
isCatchVariable(const DeclRefExpr * DeclRefExpr)54 bool ThrowByValueCatchByReferenceCheck::isCatchVariable(
55     const DeclRefExpr *DeclRefExpr) {
56   auto *ValueDecl = DeclRefExpr->getDecl();
57   if (auto *VarDecl = dyn_cast<clang::VarDecl>(ValueDecl))
58     return VarDecl->isExceptionVariable();
59   return false;
60 }
61 
isFunctionOrCatchVar(const DeclRefExpr * DeclRefExpr)62 bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
63     const DeclRefExpr *DeclRefExpr) {
64   return isFunctionParameter(DeclRefExpr) || isCatchVariable(DeclRefExpr);
65 }
66 
diagnoseThrowLocations(const CXXThrowExpr * ThrowExpr)67 void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations(
68     const CXXThrowExpr *ThrowExpr) {
69   if (!ThrowExpr)
70     return;
71   auto *SubExpr = ThrowExpr->getSubExpr();
72   if (!SubExpr)
73     return;
74   auto QualType = SubExpr->getType();
75   if (QualType->isPointerType()) {
76     // The code is throwing a pointer.
77     // In case it is string literal, it is safe and we return.
78     auto *Inner = SubExpr->IgnoreParenImpCasts();
79     if (isa<StringLiteral>(Inner))
80       return;
81     // If it's a variable from a catch statement, we return as well.
82     auto *DeclRef = dyn_cast<DeclRefExpr>(Inner);
83     if (DeclRef && isCatchVariable(DeclRef)) {
84       return;
85     }
86     diag(SubExpr->getBeginLoc(), "throw expression throws a pointer; it should "
87                                  "throw a non-pointer value instead");
88   }
89   // If the throw statement does not throw by pointer then it throws by value
90   // which is ok.
91   // There are addition checks that emit diagnosis messages if the thrown value
92   // is not an RValue. See:
93   // https://www.securecoding.cert.org/confluence/display/cplusplus/ERR09-CPP.+Throw+anonymous+temporaries
94   // This behavior can be influenced by an option.
95 
96   // If we encounter a CXXThrowExpr, we move through all casts until you either
97   // encounter a DeclRefExpr or a CXXConstructExpr.
98   // If it's a DeclRefExpr, we emit a message if the referenced variable is not
99   // a catch variable or function parameter.
100   // When encountering a CopyOrMoveConstructor: emit message if after casts,
101   // the expression is a LValue
102   if (CheckAnonymousTemporaries) {
103     bool Emit = false;
104     auto *CurrentSubExpr = SubExpr->IgnoreImpCasts();
105     const auto *VariableReference = dyn_cast<DeclRefExpr>(CurrentSubExpr);
106     const auto *ConstructorCall = 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->getBeginLoc(),
130            "throw expression should throw anonymous temporary values instead");
131   }
132 }
133 
diagnoseCatchLocations(const CXXCatchStmt * CatchStmt,ASTContext & Context)134 void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
135     const CXXCatchStmt *CatchStmt, ASTContext &Context) {
136   if (!CatchStmt)
137     return;
138   auto CaughtType = CatchStmt->getCaughtType();
139   if (CaughtType.isNull())
140     return;
141   auto *VarDecl = CatchStmt->getExceptionDecl();
142   if (const auto *PT = CaughtType.getCanonicalType()->getAs<PointerType>()) {
143     const char *DiagMsgCatchReference =
144         "catch handler catches a pointer value; "
145         "should throw a non-pointer value and "
146         "catch by reference instead";
147     // We do not diagnose when catching pointer to strings since we also allow
148     // throwing string literals.
149     if (!PT->getPointeeType()->isAnyCharacterType())
150       diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
151   } else if (!CaughtType->isReferenceType()) {
152     const char *DiagMsgCatchReference = "catch handler catches by value; "
153                                         "should catch by reference instead";
154     // If it's not a pointer and not a reference then it must be caught "by
155     // value". In this case we should emit a diagnosis message unless the type
156     // is trivial.
157     if (!CaughtType.isTrivialType(Context)) {
158       diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
159     } else if (WarnOnLargeObject) {
160       // If the type is trivial, then catching it by reference is not dangerous.
161       // However, catching large objects by value decreases the performance.
162 
163       // We can now access `ASTContext` so if `MaxSize` is an extremal value
164       // then set it to the size of `size_t`.
165       if (MaxSize == std::numeric_limits<uint64_t>::max())
166         MaxSize = Context.getTypeSize(Context.getSizeType());
167       if (Context.getTypeSize(CaughtType) > MaxSize)
168         diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
169     }
170   }
171 }
172 
173 } // namespace misc
174 } // namespace tidy
175 } // namespace clang
176