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
14using namespace clang::ast_matchers;
15
16namespace clang::tidy::misc {
17
18ThrowByValueCatchByReferenceCheck::ThrowByValueCatchByReferenceCheck(
19 StringRef Name, ClangTidyContext *Context)
20 : ClangTidyCheck(Name, Context),
21 CheckAnonymousTemporaries(Options.get(LocalName: "CheckThrowTemporaries", Default: true)),
22 WarnOnLargeObject(Options.get(LocalName: "WarnOnLargeObject", Default: false)),
23 // Cannot access `ASTContext` from here so set it to an extremal value.
24 MaxSizeOptions(
25 Options.get(LocalName: "MaxSize", Default: std::numeric_limits<uint64_t>::max())),
26 MaxSize(MaxSizeOptions) {}
27
28void ThrowByValueCatchByReferenceCheck::registerMatchers(MatchFinder *Finder) {
29 Finder->addMatcher(NodeMatch: cxxThrowExpr().bind(ID: "throw"), Action: this);
30 Finder->addMatcher(NodeMatch: cxxCatchStmt().bind(ID: "catch"), Action: this);
31}
32
33void ThrowByValueCatchByReferenceCheck::storeOptions(
34 ClangTidyOptions::OptionMap &Opts) {
35 Options.store(Options&: Opts, LocalName: "CheckThrowTemporaries", Value: true);
36 Options.store(Options&: Opts, LocalName: "WarnOnLargeObjects", Value: WarnOnLargeObject);
37 Options.store(Options&: Opts, LocalName: "MaxSize", Value: MaxSizeOptions);
38}
39
40void ThrowByValueCatchByReferenceCheck::check(
41 const MatchFinder::MatchResult &Result) {
42 diagnoseThrowLocations(ThrowExpr: Result.Nodes.getNodeAs<CXXThrowExpr>(ID: "throw"));
43 diagnoseCatchLocations(CatchStmt: Result.Nodes.getNodeAs<CXXCatchStmt>(ID: "catch"),
44 Context&: *Result.Context);
45}
46
47bool ThrowByValueCatchByReferenceCheck::isFunctionParameter(
48 const DeclRefExpr *DeclRefExpr) {
49 return isa<ParmVarDecl>(Val: DeclRefExpr->getDecl());
50}
51
52bool ThrowByValueCatchByReferenceCheck::isCatchVariable(
53 const DeclRefExpr *DeclRefExpr) {
54 auto *ValueDecl = DeclRefExpr->getDecl();
55 if (auto *VarDecl = dyn_cast<clang::VarDecl>(Val: ValueDecl))
56 return VarDecl->isExceptionVariable();
57 return false;
58}
59
60bool ThrowByValueCatchByReferenceCheck::isFunctionOrCatchVar(
61 const DeclRefExpr *DeclRefExpr) {
62 return isFunctionParameter(DeclRefExpr) || isCatchVariable(DeclRefExpr);
63}
64
65void 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>(Val: Inner))
78 return;
79 // If it's a variable from a catch statement, we return as well.
80 auto *DeclRef = dyn_cast<DeclRefExpr>(Val: Inner);
81 if (DeclRef && isCatchVariable(DeclRefExpr: 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>(Val: CurrentSubExpr);
104 const auto *ConstructorCall = dyn_cast<CXXConstructExpr>(Val: 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(DeclRefExpr: 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(DeclRefExpr: 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
132void 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(T: Context.getSizeType());
165 if (Context.getTypeSize(T: CaughtType) > MaxSize)
166 diag(VarDecl->getBeginLoc(), DiagMsgCatchReference);
167 }
168 }
169}
170
171} // namespace clang::tidy::misc
172

source code of clang-tools-extra/clang-tidy/misc/ThrowByValueCatchByReferenceCheck.cpp