10#include "../utils/DeclRefExprUtils.h"
11#include "../utils/FixItHintUtils.h"
12#include "../utils/LexerUtils.h"
13#include "../utils/Matchers.h"
14#include "../utils/OptionsUtils.h"
15#include "clang/AST/Decl.h"
16#include "clang/Basic/Diagnostic.h"
28static constexpr StringRef ObjectArgId =
"objectArg";
29static constexpr StringRef InitFunctionCallId =
"initFunctionCall";
30static constexpr StringRef MethodDeclId =
"methodDecl";
31static constexpr StringRef FunctionDeclId =
"functionDecl";
32static constexpr StringRef OldVarDeclId =
"oldVarDecl";
34void recordFixes(
const VarDecl &Var, ASTContext &Context,
37 if (!Var.getType().isLocalConstQualified()) {
39 Var, Context, DeclSpec::TQ::TQ_const))
44std::optional<SourceLocation> firstLocAfterNewLine(SourceLocation
Loc,
47 const char *TextAfter = SM.getCharacterData(
Loc, &Invalid);
51 size_t Offset = std::strcspn(TextAfter,
"\n");
55void recordRemoval(
const DeclStmt &Stmt, ASTContext &Context,
57 auto &SM = Context.getSourceManager();
60 Context.getLangOpts());
61 std::optional<SourceLocation> PastNewLine =
62 firstLocAfterNewLine(Stmt.getEndLoc(), SM);
63 if (Tok && PastNewLine) {
64 auto BeforeFirstTokenAfterComment = Tok->getLocation().getLocWithOffset(-1);
68 SM.isBeforeInTranslationUnit(*PastNewLine, BeforeFirstTokenAfterComment)
70 : BeforeFirstTokenAfterComment;
72 SourceRange(Stmt.getBeginLoc(), End));
74 Diagnostic << FixItHint::CreateRemoval(Stmt.getSourceRange());
79 isRefReturningMethodCallWithConstOverloads,
80 std::vector<StringRef>, ExcludedContainerTypes) {
86 const auto MethodDecl =
87 cxxMethodDecl(returns(hasCanonicalType(referenceType())))
89 const auto ReceiverExpr =
90 ignoringParenImpCasts(declRefExpr(to(varDecl().bind(ObjectArgId))));
91 const auto OnExpr = anyOf(
95 unaryOperator(hasOperatorName(
"*"), hasUnaryOperand(ReceiverExpr)));
96 const auto ReceiverType =
97 hasCanonicalType(recordType(hasDeclaration(namedDecl(
101 anyOf(cxxMemberCallExpr(callee(MethodDecl), on(OnExpr),
102 thisPointerType(ReceiverType)),
103 cxxOperatorCallExpr(callee(MethodDecl), hasArgument(0, OnExpr),
104 hasArgument(0, hasType(ReceiverType)))));
111 return callExpr(callee(functionDecl(returns(hasCanonicalType(
112 matchers::isReferenceToConst())))
113 .bind(FunctionDeclId)),
114 argumentCountIs(0), unless(callee(cxxMethodDecl())))
115 .bind(InitFunctionCallId);
119 std::vector<StringRef>, ExcludedContainerTypes) {
121 declRefExpr(to(varDecl(hasLocalStorage()).bind(OldVarDeclId)));
123 anyOf(isConstRefReturningFunctionCall(),
124 isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes),
125 ignoringImpCasts(OldVarDeclRef),
126 ignoringImpCasts(unaryOperator(hasOperatorName(
"&"),
127 hasUnaryOperand(OldVarDeclRef)))));
142static bool isInitializingVariableImmutable(
143 const VarDecl &InitializingVar,
const Stmt &BlockStmt, ASTContext &Context,
144 const std::vector<StringRef> &ExcludedContainerTypes) {
145 QualType
T = InitializingVar.getType().getCanonicalType();
147 T->isPointerType() ? 1 : 0))
152 if (!isa<ReferenceType, PointerType>(T))
157 if (!InitializingVar.isLocalVarDecl() || !InitializingVar.hasInit()) {
162 match(initializerReturnsReferenceToConst(ExcludedContainerTypes),
163 *InitializingVar.getInit(), Context);
166 if (selectFirst<CallExpr>(InitFunctionCallId, Matches) !=
nullptr)
169 if (
const auto *OrigVar = selectFirst<VarDecl>(ObjectArgId, Matches))
170 return isInitializingVariableImmutable(*OrigVar, BlockStmt, Context,
171 ExcludedContainerTypes);
173 if (
const auto *OrigVar = selectFirst<VarDecl>(OldVarDeclId, Matches))
174 return isInitializingVariableImmutable(*OrigVar, BlockStmt, Context,
175 ExcludedContainerTypes);
180bool isVariableUnused(
const VarDecl &Var,
const Stmt &BlockStmt,
181 ASTContext &Context) {
185const SubstTemplateTypeParmType *getSubstitutedType(
const QualType &
Type,
186 ASTContext &Context) {
187 auto Matches =
match(
188 qualType(anyOf(substTemplateTypeParmType().bind(
"subst"),
189 hasDescendant(substTemplateTypeParmType().bind(
"subst")))),
191 return selectFirst<SubstTemplateTypeParmType>(
"subst", Matches);
194bool differentReplacedTemplateParams(
const QualType &VarType,
195 const QualType &InitializerType,
196 ASTContext &Context) {
197 if (
const SubstTemplateTypeParmType *VarTmplType =
198 getSubstitutedType(VarType, Context)) {
199 if (
const SubstTemplateTypeParmType *InitializerTmplType =
200 getSubstitutedType(InitializerType, Context)) {
201 const TemplateTypeParmDecl *VarTTP = VarTmplType->getReplacedParameter();
202 const TemplateTypeParmDecl *InitTTP =
203 InitializerTmplType->getReplacedParameter();
204 return (VarTTP->getDepth() != InitTTP->getDepth() ||
205 VarTTP->getIndex() != InitTTP->getIndex() ||
206 VarTTP->isParameterPack() != InitTTP->isParameterPack());
212QualType constructorArgumentType(
const VarDecl *OldVar,
213 const BoundNodes &Nodes) {
215 return OldVar->getType();
217 if (
const auto *
FuncDecl = Nodes.getNodeAs<FunctionDecl>(FunctionDeclId)) {
220 const auto *MethodDecl = Nodes.getNodeAs<CXXMethodDecl>(MethodDeclId);
221 return MethodDecl->getReturnType();
230 utils::options::parseStringList(Options.get(
"AllowedTypes",
""))),
231 ExcludedContainerTypes(utils::options::parseStringList(
232 Options.get(
"ExcludedContainerTypes",
""))) {}
235 auto LocalVarCopiedFrom = [
this](
const internal::Matcher<Expr> &CopyCtorArg) {
239 unless(has(decompositionDecl())),
240 has(varDecl(hasLocalStorage(),
242 hasCanonicalType(allOf(
243 matchers::isExpensiveToCopy(),
244 unless(hasDeclaration(namedDecl(
245 hasName(
"::std::function")))))),
246 unless(hasDeclaration(namedDecl(
249 unless(isImplicit()),
250 hasInitializer(traverse(
253 hasDeclaration(cxxConstructorDecl(
254 isCopyConstructor())),
255 hasArgument(0, CopyCtorArg))
257 .bind(
"newVarDecl")))
263 LocalVarCopiedFrom(anyOf(
264 isConstRefReturningFunctionCall(),
265 isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes))),
268 Finder->addMatcher(LocalVarCopiedFrom(declRefExpr(
269 to(varDecl(hasLocalStorage()).bind(OldVarDeclId)))),
274 const MatchFinder::MatchResult &Result) {
275 const auto &NewVar = *Result.Nodes.getNodeAs<VarDecl>(
"newVarDecl");
276 const auto &BlockStmt = *Result.Nodes.getNodeAs<Stmt>(
"blockStmt");
277 const auto &VarDeclStmt = *Result.Nodes.getNodeAs<DeclStmt>(
"declStmt");
280 const bool IssueFix =
281 VarDeclStmt.isSingleDecl() && !NewVar.getLocation().isMacroID();
282 const bool IsVarUnused = isVariableUnused(NewVar, BlockStmt, *Result.Context);
283 const bool IsVarOnlyUsedAsConst =
284 isOnlyUsedAsConst(NewVar, BlockStmt, *Result.Context,
288 NewVar, BlockStmt, VarDeclStmt, *Result.Context,
289 IssueFix, IsVarUnused, IsVarOnlyUsedAsConst};
290 const auto *OldVar = Result.Nodes.getNodeAs<VarDecl>(OldVarDeclId);
291 const auto *ObjectArg = Result.Nodes.getNodeAs<VarDecl>(ObjectArgId);
292 const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>(
"ctorCall");
294 TraversalKindScope RAII(*Result.Context, TK_AsIs);
299 for (
unsigned int I = 1; I < CtorCall->getNumArgs(); ++I)
300 if (!CtorCall->getArg(I)->isDefaultArgument())
308 if (differentReplacedTemplateParams(
309 Context.Var.getType(), constructorArgumentType(OldVar, Result.Nodes),
313 if (OldVar ==
nullptr) {
315 handleCopyFromMethodReturn(Context, ObjectArg);
318 handleCopyFromLocalVar(Context, *OldVar);
322void UnnecessaryCopyInitialization::handleCopyFromMethodReturn(
323 const CheckContext &Ctx,
const VarDecl *ObjectArg) {
324 bool IsConstQualified = Ctx.Var.getType().isConstQualified();
325 if (!IsConstQualified && !Ctx.IsVarOnlyUsedAsConst)
327 if (ObjectArg !=
nullptr &&
328 !isInitializingVariableImmutable(*ObjectArg, Ctx.BlockStmt, Ctx.ASTCtx,
329 ExcludedContainerTypes))
334void UnnecessaryCopyInitialization::handleCopyFromLocalVar(
335 const CheckContext &Ctx,
const VarDecl &OldVar) {
336 if (!Ctx.IsVarOnlyUsedAsConst ||
337 !isInitializingVariableImmutable(OldVar, Ctx.BlockStmt, Ctx.ASTCtx,
338 ExcludedContainerTypes))
346 diag(Ctx.Var.getLocation(),
347 "the %select{|const qualified }0variable %1 is "
349 "from a const reference%select{%select{ but is only used as const "
350 "reference|}0| but is never used}2; consider "
351 "%select{making it a const reference|removing the statement}2")
352 << Ctx.Var.getType().isConstQualified() << &Ctx.Var << Ctx.IsVarUnused;
359 diag(Ctx.Var.getLocation(),
360 "local copy %1 of the variable %0 is never modified%select{"
361 "| and never used}2; consider %select{avoiding the copy|removing "
363 << &OldVar << &Ctx.Var << Ctx.IsVarUnused;
367void UnnecessaryCopyInitialization::maybeIssueFixes(
368 const CheckContext &Ctx, DiagnosticBuilder &
Diagnostic) {
371 recordRemoval(Ctx.VarDeclStmt, Ctx.ASTCtx,
Diagnostic);
llvm::SmallString< 256U > Name
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))
DiagnosticCallback Diagnostic
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
std::vector< std::string > match(const SymbolIndex &I, const FuzzyFindRequest &Req, bool *Incomplete)
AST_MATCHER_FUNCTION_P(ast_matchers::internal::Matcher< Stmt >, comparisonOperatorWithCallee, ast_matchers::internal::Matcher< Decl >, funcDecl)
AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher< FunctionDecl >, DurationConversionFunction)
@ Invalid
Sentinel bit pattern. DO NOT USE!
inline ::clang::ast_matchers::internal::Matcher< NamedDecl > matchesAnyListedName(llvm::ArrayRef< StringRef > NameList)
SmallPtrSet< const DeclRefExpr *, 16 > allDeclRefExprs(const VarDecl &VarDecl, const Stmt &Stmt, ASTContext &Context)
Returns set of all DeclRefExprs to VarDecl within Stmt.
bool isOnlyUsedAsConst(const VarDecl &Var, const Stmt &Stmt, ASTContext &Context, int Indirections)
Returns true if all DeclRefExpr to the variable within Stmt do not modify it.
FixItHint changeVarDeclToReference(const VarDecl &Var, ASTContext &Context)
Creates fix to make VarDecl a reference by adding &.
std::optional< FixItHint > addQualifierToVarDecl(const VarDecl &Var, const ASTContext &Context, DeclSpec::TQ Qualifier, QualifierTarget QualTarget, QualifierPolicy QualPolicy)
Creates fix to qualify VarDecl with the specified Qualifier.
std::optional< Token > findNextTokenSkippingComments(SourceLocation Start, const SourceManager &SM, const LangOptions &LangOpts)
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
llvm::StringMap< ClangTidyValue > OptionMap
static constexpr const char FuncDecl[]