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