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"
27static constexpr StringRef ObjectArgId =
"objectArg";
28static constexpr StringRef InitFunctionCallId =
"initFunctionCall";
29static constexpr StringRef MethodDeclId =
"methodDecl";
30static constexpr StringRef FunctionDeclId =
"functionDecl";
31static constexpr StringRef OldVarDeclId =
"oldVarDecl";
33void recordFixes(
const VarDecl &Var, ASTContext &Context,
36 if (!Var.getType().isLocalConstQualified()) {
38 Var, Context, DeclSpec::TQ::TQ_const))
43std::optional<SourceLocation> firstLocAfterNewLine(SourceLocation
Loc,
46 const char *TextAfter = SM.getCharacterData(
Loc, &Invalid);
50 size_t Offset = std::strcspn(TextAfter,
"\n");
54void recordRemoval(
const DeclStmt &Stmt, ASTContext &Context,
56 auto &SM = Context.getSourceManager();
59 Context.getLangOpts());
60 std::optional<SourceLocation> PastNewLine =
61 firstLocAfterNewLine(Stmt.getEndLoc(), SM);
62 if (Tok && PastNewLine) {
63 auto BeforeFirstTokenAfterComment = Tok->getLocation().getLocWithOffset(-1);
67 SM.isBeforeInTranslationUnit(*PastNewLine, BeforeFirstTokenAfterComment)
69 : BeforeFirstTokenAfterComment;
71 SourceRange(Stmt.getBeginLoc(), End));
73 Diagnostic << FixItHint::CreateRemoval(Stmt.getSourceRange());
78 std::vector<StringRef>, ExcludedContainerTypes) {
85 const auto MethodDecl =
86 cxxMethodDecl(returns(hasCanonicalType(matchers::isReferenceToConst())))
88 const auto ReceiverExpr = declRefExpr(to(varDecl().bind(ObjectArgId)));
89 const auto ReceiverType =
90 hasCanonicalType(recordType(hasDeclaration(namedDecl(
94 cxxMemberCallExpr(callee(MethodDecl), on(ReceiverExpr),
95 thisPointerType(ReceiverType)),
96 cxxOperatorCallExpr(callee(MethodDecl), hasArgument(0, ReceiverExpr),
97 hasArgument(0, hasType(ReceiverType)))));
104 return callExpr(callee(functionDecl(returns(hasCanonicalType(
105 matchers::isReferenceToConst())))
106 .bind(FunctionDeclId)),
107 argumentCountIs(0), unless(callee(cxxMethodDecl())))
108 .bind(InitFunctionCallId);
112 std::vector<StringRef>, ExcludedContainerTypes) {
114 declRefExpr(to(varDecl(hasLocalStorage()).bind(OldVarDeclId)));
116 anyOf(isConstRefReturningFunctionCall(),
117 isConstRefReturningMethodCall(ExcludedContainerTypes),
118 ignoringImpCasts(OldVarDeclRef),
119 ignoringImpCasts(unaryOperator(hasOperatorName(
"&"),
120 hasUnaryOperand(OldVarDeclRef)))));
135static bool isInitializingVariableImmutable(
136 const VarDecl &InitializingVar,
const Stmt &BlockStmt, ASTContext &Context,
137 const std::vector<StringRef> &ExcludedContainerTypes) {
141 QualType T = InitializingVar.getType().getCanonicalType();
144 if (!isa<ReferenceType, PointerType>(T))
149 if (!InitializingVar.isLocalVarDecl() || !InitializingVar.hasInit()) {
154 match(initializerReturnsReferenceToConst(ExcludedContainerTypes),
155 *InitializingVar.getInit(), Context);
158 if (selectFirst<CallExpr>(InitFunctionCallId, Matches) !=
nullptr)
161 if (
const auto *OrigVar = selectFirst<VarDecl>(ObjectArgId, Matches))
162 return isInitializingVariableImmutable(*OrigVar, BlockStmt, Context,
163 ExcludedContainerTypes);
165 if (
const auto *OrigVar = selectFirst<VarDecl>(OldVarDeclId, Matches))
166 return isInitializingVariableImmutable(*OrigVar, BlockStmt, Context,
167 ExcludedContainerTypes);
172bool isVariableUnused(
const VarDecl &Var,
const Stmt &BlockStmt,
173 ASTContext &Context) {
177const SubstTemplateTypeParmType *getSubstitutedType(
const QualType &
Type,
178 ASTContext &Context) {
179 auto Matches =
match(
180 qualType(anyOf(substTemplateTypeParmType().bind(
"subst"),
181 hasDescendant(substTemplateTypeParmType().bind(
"subst")))),
183 return selectFirst<SubstTemplateTypeParmType>(
"subst", Matches);
186bool differentReplacedTemplateParams(
const QualType &VarType,
187 const QualType &InitializerType,
188 ASTContext &Context) {
189 if (
const SubstTemplateTypeParmType *VarTmplType =
190 getSubstitutedType(VarType, Context)) {
191 if (
const SubstTemplateTypeParmType *InitializerTmplType =
192 getSubstitutedType(InitializerType, Context)) {
193 const TemplateTypeParmDecl *VarTTP = VarTmplType->getReplacedParameter();
194 const TemplateTypeParmDecl *InitTTP =
195 InitializerTmplType->getReplacedParameter();
196 return (VarTTP->getDepth() != InitTTP->getDepth() ||
197 VarTTP->getIndex() != InitTTP->getIndex() ||
198 VarTTP->isParameterPack() != InitTTP->isParameterPack());
204QualType constructorArgumentType(
const VarDecl *OldVar,
205 const BoundNodes &Nodes) {
207 return OldVar->getType();
209 if (
const auto *FuncDecl = Nodes.getNodeAs<FunctionDecl>(FunctionDeclId)) {
210 return FuncDecl->getReturnType();
212 const auto *MethodDecl = Nodes.getNodeAs<CXXMethodDecl>(MethodDeclId);
213 return MethodDecl->getReturnType();
222 utils::options::parseStringList(Options.get(
"AllowedTypes",
""))),
223 ExcludedContainerTypes(utils::options::parseStringList(
224 Options.get(
"ExcludedContainerTypes",
""))) {}
227 auto LocalVarCopiedFrom = [
this](
const internal::Matcher<Expr> &CopyCtorArg) {
231 unless(has(decompositionDecl())),
232 has(varDecl(hasLocalStorage(),
234 hasCanonicalType(allOf(
235 matchers::isExpensiveToCopy(),
236 unless(hasDeclaration(namedDecl(
237 hasName(
"::std::function")))))),
238 unless(hasDeclaration(namedDecl(
241 unless(isImplicit()),
242 hasInitializer(traverse(
245 hasDeclaration(cxxConstructorDecl(
246 isCopyConstructor())),
247 hasArgument(0, CopyCtorArg))
249 .bind(
"newVarDecl")))
254 Finder->addMatcher(LocalVarCopiedFrom(anyOf(isConstRefReturningFunctionCall(),
255 isConstRefReturningMethodCall(
256 ExcludedContainerTypes))),
259 Finder->addMatcher(LocalVarCopiedFrom(declRefExpr(
260 to(varDecl(hasLocalStorage()).bind(OldVarDeclId)))),
265 const MatchFinder::MatchResult &Result) {
266 const auto *NewVar = Result.Nodes.getNodeAs<VarDecl>(
"newVarDecl");
267 const auto *OldVar = Result.Nodes.getNodeAs<VarDecl>(OldVarDeclId);
268 const auto *ObjectArg = Result.Nodes.getNodeAs<VarDecl>(ObjectArgId);
269 const auto *BlockStmt = Result.Nodes.getNodeAs<Stmt>(
"blockStmt");
270 const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>(
"ctorCall");
271 const auto *Stmt = Result.Nodes.getNodeAs<DeclStmt>(
"declStmt");
273 TraversalKindScope RAII(*Result.Context, TK_AsIs);
277 bool IssueFix = Stmt->isSingleDecl() && !NewVar->getLocation().isMacroID();
282 for (
unsigned int I = 1; I < CtorCall->getNumArgs(); ++I)
283 if (!CtorCall->getArg(I)->isDefaultArgument())
291 if (differentReplacedTemplateParams(
292 NewVar->getType(), constructorArgumentType(OldVar, Result.Nodes),
296 if (OldVar ==
nullptr) {
297 handleCopyFromMethodReturn(*NewVar, *BlockStmt, *Stmt, IssueFix, ObjectArg,
300 handleCopyFromLocalVar(*NewVar, *OldVar, *BlockStmt, *Stmt, IssueFix,
305void UnnecessaryCopyInitialization::handleCopyFromMethodReturn(
306 const VarDecl &Var,
const Stmt &BlockStmt,
const DeclStmt &Stmt,
307 bool IssueFix,
const VarDecl *ObjectArg, ASTContext &Context) {
308 bool IsConstQualified = Var.getType().isConstQualified();
309 if (!IsConstQualified && !isOnlyUsedAsConst(Var, BlockStmt, Context))
311 if (ObjectArg !=
nullptr &&
312 !isInitializingVariableImmutable(*ObjectArg, BlockStmt, Context,
313 ExcludedContainerTypes))
315 if (isVariableUnused(Var, BlockStmt, Context)) {
317 diag(Var.getLocation(),
318 "the %select{|const qualified }0variable %1 is copy-constructed "
319 "from a const reference but is never used; consider "
320 "removing the statement")
321 << IsConstQualified << &Var;
326 diag(Var.getLocation(),
327 "the %select{|const qualified }0variable %1 is copy-constructed "
328 "from a const reference%select{ but is only used as const "
329 "reference|}0; consider making it a const reference")
330 << IsConstQualified << &Var;
336void UnnecessaryCopyInitialization::handleCopyFromLocalVar(
337 const VarDecl &NewVar,
const VarDecl &OldVar,
const Stmt &BlockStmt,
338 const DeclStmt &Stmt,
bool IssueFix, ASTContext &Context) {
339 if (!isOnlyUsedAsConst(NewVar, BlockStmt, Context) ||
340 !isInitializingVariableImmutable(OldVar, BlockStmt, Context,
341 ExcludedContainerTypes))
344 if (isVariableUnused(NewVar, BlockStmt, Context)) {
346 "local copy %0 of the variable %1 is never modified "
348 "consider removing the statement")
349 << &NewVar << &OldVar;
354 diag(NewVar.getLocation(),
355 "local copy %0 of the variable %1 is never modified; "
356 "consider avoiding the copy")
357 << &NewVar << &OldVar;
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)
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