clang-tools 24.0.0git
ExpensiveValueOrCheck.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 "../utils/Matchers.h"
12#include "../utils/TypeTraits.h"
13#include "clang/AST/ASTContext.h"
14#include "clang/ASTMatchers/ASTMatchFinder.h"
15#include "clang/Tooling/FixIt.h"
16
17using namespace clang::ast_matchers;
18
20
21namespace {
22
23AST_MATCHER(Expr, isLValueExpr) { return Node.isLValue(); }
24
25AST_MATCHER(QualType, hasNonTrivialMoveCtor) {
27}
28
29AST_MATCHER_P(QualType, isLargerThan, unsigned, SizeThreshold) {
30 if (Node.isNull() || Node->isDependentType() || Node->isIncompleteType())
31 return false;
32 return Finder->getASTContext().getTypeSizeInChars(Node).getQuantity() >
33 static_cast<int64_t>(SizeThreshold);
34}
35
36} // namespace
37
38static bool hasOperatorStar(const CXXRecordDecl *RD) {
39 const DeclarationName OpStar =
40 RD->getASTContext().DeclarationNames.getCXXOperatorName(OO_Star);
41 return !RD->lookup(OpStar).empty();
42}
43
44static StringRef findValueMethod(const CXXRecordDecl *RD) {
45 ASTContext &Ctx = RD->getASTContext();
46 for (StringRef Name : {"value", "Value"}) {
47 const DeclarationName DN = &Ctx.Idents.get(Name);
48 if (!RD->lookup(DN).empty())
49 return Name;
50 }
51 return {};
52}
53
54static std::string buildSuggestion(const CXXRecordDecl *OptionalClass) {
55 const bool HasDeref = hasOperatorStar(OptionalClass);
56 StringRef ValueName = findValueMethod(OptionalClass);
57
58 if (HasDeref && !ValueName.empty())
59 return (llvm::Twine("consider using 'operator*' or '") + ValueName +
60 "()' with a separate fallback")
61 .str();
62 if (HasDeref)
63 return "consider using 'operator*' with a separate fallback";
64 if (!ValueName.empty())
65 return (llvm::Twine("consider using '") + ValueName +
66 "()' with a separate fallback")
67 .str();
68 return "consider avoiding the copy";
69}
70
71static std::optional<FixItHint> buildFixIt(const CXXMemberCallExpr *Call,
72 const Expr *ObjExpr,
73 const Expr *FallbackArg,
74 const CXXRecordDecl *OptionalClass,
75 const ASTContext &Ctx) {
76 if (Call->getBeginLoc().isMacroID())
77 return std::nullopt;
78 if (!ObjExpr->isLValue())
79 return std::nullopt;
80 if (ObjExpr->HasSideEffects(Ctx))
81 return std::nullopt;
82 if (!hasOperatorStar(OptionalClass))
83 return std::nullopt;
84
85 StringRef ObjText = tooling::fixit::getText(*ObjExpr, Ctx);
86 StringRef ArgText = tooling::fixit::getText(*FallbackArg, Ctx);
87
88 if (ObjText.empty() || ArgText.empty())
89 return std::nullopt;
90
91 const std::string Replacement =
92 ("(" + ObjText + " ? *" + ObjText + " : " + ArgText + ")").str();
93 return tooling::fixit::createReplacement(*Call, Replacement);
94}
95
97 ClangTidyContext *Context)
98 : ClangTidyCheck(Name, Context),
99 SizeThreshold(Options.get("SizeThreshold", 16U)),
100 OptionalTypes(utils::options::parseStringList(
101 Options.get("OptionalTypes",
102 "::std::optional;::absl::optional;::boost::optional"))),
103 WarnOnOwnershipTaking(Options.get("WarnOnOwnershipTaking", false)) {}
104
106 Options.store(Opts, "SizeThreshold", SizeThreshold);
107 Options.store(Opts, "OptionalTypes",
109 Options.store(Opts, "WarnOnOwnershipTaking", WarnOnOwnershipTaking);
110}
111
113 auto OptionalTypesMatcher =
115 auto ValueOrMatcher = hasAnyName("value_or", "valueOr", "ValueOr");
116 auto ValueOrCall = cxxMemberCallExpr(
117 callee(cxxMethodDecl(ValueOrMatcher, ofClass(OptionalTypesMatcher))),
118 anyOf(on(isLValueExpr()),
119 hasType(qualType(unless(hasNonTrivialMoveCtor())))),
120 hasType(qualType(
121 anyOf(matchers::isExpensiveToCopy(), isLargerThan(SizeThreshold)))),
122 unless(anyOf(hasAncestor(typeLoc()),
123 hasAncestor(expr(matchers::hasUnevaluatedContext())))));
124
125 if (WarnOnOwnershipTaking) {
126 Finder->addMatcher(ValueOrCall.bind("call"), this);
127 return;
128 }
129
130 // Binding to const T& variable.
131 Finder->addMatcher(
132 varDecl(hasType(lValueReferenceType(pointee(isConstQualified()))),
133 hasInitializer(ignoringImplicit(ValueOrCall.bind("call")))),
134 this);
135
136 // Passing to a const T& parameter.
137 Finder->addMatcher(callExpr(forEachArgumentWithParam(
138 ignoringImplicit(ValueOrCall.bind("call")),
139 parmVarDecl(hasType(lValueReferenceType(
140 pointee(isConstQualified())))))),
141 this);
142
143 // Calling a const member function on the result.
144 Finder->addMatcher(
145 cxxMemberCallExpr(on(ignoringImplicit(ValueOrCall.bind("call"))),
146 callee(cxxMethodDecl(isConst()))),
147 this);
148}
149
150void ExpensiveValueOrCheck::check(const MatchFinder::MatchResult &Result) {
151 const auto *Call = Result.Nodes.getNodeAs<CXXMemberCallExpr>("call");
152 assert(Call && "Matcher guaranteed a bound 'call' node");
153 const Expr *ObjExpr = Call->getImplicitObjectArgument();
154 assert(ObjExpr && "CXXMemberCallExpr must have an implicit object argument");
155
156 const ASTContext &Ctx = *Result.Context;
157 const QualType ValueType = Call->getType();
158
159 const CXXMethodDecl *Method = Call->getMethodDecl();
160 const CXXRecordDecl *OptionalClass = Method->getParent();
161 const Expr *FallbackArg = Call->getArg(0)->IgnoreImplicit();
162 const bool HasSideEffects = FallbackArg->HasSideEffects(Ctx);
163
164 {
165 auto Diag = diag(Call->getExprLoc(), "'%0' copies expensive type %1; %2")
166 << Method->getName() << ValueType
167 << buildSuggestion(OptionalClass);
168
169 if (!HasSideEffects) {
170 if (auto Fix = buildFixIt(Call, ObjExpr, FallbackArg, OptionalClass, Ctx))
171 Diag << *Fix;
172 }
173 }
174
175 if (HasSideEffects)
176 diag(FallbackArg->getExprLoc(),
177 "the fallback is always evaluated; a conditional rewrite would "
178 "change evaluation semantics",
179 DiagnosticIDs::Note);
180}
181
182} // namespace clang::tidy::performance
static cl::opt< bool > Fix("fix", desc(R"( Apply suggested fixes. Without -fix-errors clang-tidy will bail out if any compilation errors were found. )"), cl::init(false), cl::cat(ClangTidyCategory))
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void registerMatchers(ast_matchers::MatchFinder *Finder) override
ExpensiveValueOrCheck(StringRef Name, ClangTidyContext *Context)
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
AST_MATCHER_P(Stmt, isStatementIdenticalToBoundNode, std::string, ID)
AST_MATCHER(BinaryOperator, isRelationalOperator)
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedRegexName(llvm::ArrayRef< StringRef > NameList)
static std::optional< FixItHint > buildFixIt(const CXXMemberCallExpr *Call, const Expr *ObjExpr, const Expr *FallbackArg, const CXXRecordDecl *OptionalClass, const ASTContext &Ctx)
static std::string buildSuggestion(const CXXRecordDecl *OptionalClass)
static StringRef findValueMethod(const CXXRecordDecl *RD)
static bool hasOperatorStar(const CXXRecordDecl *RD)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
bool hasNonTrivialMoveConstructor(QualType Type)
Returns true if Type has a non-trivial move constructor.
llvm::StringMap< ClangTidyValue > OptionMap