1 //===--- ThrowByValueCatchByReferenceCheck.cpp - clang-tidy----------------===//
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
7 //===----------------------------------------------------------------------===//
9 #include "ThrowByValueCatchByReferenceCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/AST/OperationKinds.h"
12 #include "clang/ASTMatchers/ASTMatchFinder.h"
14 using namespace clang::ast_matchers
;
16 namespace clang::tidy::misc
{
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.
25 Options
.get("MaxSize", std::numeric_limits
<uint64_t>::max())),
26 MaxSize(MaxSizeOptions
) {}
28 void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder
*Finder
) {
29 Finder
->addMatcher(cxxThrowExpr().bind("throw"), this);
30 Finder
->addMatcher(cxxCatchStmt().bind("catch"), this);
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
);
40 void ThrowByValueCatchByReferenceCheck::check(
41 const MatchFinder::MatchResult
&Result
) {
42 diagnoseThrowLocations(Result
.Nodes
.getNodeAs
<CXXThrowExpr
>("throw"));
43 diagnoseCatchLocations(Result
.Nodes
.getNodeAs
<CXXCatchStmt
>("catch"),
47 bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
48 const DeclRefExpr
*DeclRefExpr
) {
49 return isa
<ParmVarDecl
>(DeclRefExpr
->getDecl());
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();
60 bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
61 const DeclRefExpr
*DeclRefExpr
) {
62 return isFunctionParameter(DeclRefExpr
) || isCatchVariable(DeclRefExpr
);
65 void ThrowByValueCatchByReferenceCheck::diagnoseThrowLocations(
66 const CXXThrowExpr
*ThrowExpr
) {
69 auto *SubExpr
= ThrowExpr
->getSubExpr();
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
))
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
)) {
84 diag(SubExpr
->getBeginLoc(), "throw expression throws a pointer; it should "
85 "throw a non-pointer value instead");
87 // If the throw statement does not throw by pointer then it throws by value
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.
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
) {
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
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
))
127 diag(SubExpr
->getBeginLoc(),
128 "throw expression should throw anonymous temporary values instead");
132 void ThrowByValueCatchByReferenceCheck::diagnoseCatchLocations(
133 const CXXCatchStmt
*CatchStmt
, ASTContext
&Context
) {
136 auto CaughtType
= CatchStmt
->getCaughtType();
137 if (CaughtType
.isNull())
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
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.
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
);
171 } // namespace clang::tidy::misc