15#include "clang/AST/Decl.h"
16#include "clang/Basic/Diagnostic.h"
21using namespace ::clang::ast_matchers;
32static void recordFixes(
const VarDecl &Var, ASTContext &Context,
33 DiagnosticBuilder &Diagnostic) {
35 if (!Var.getType().isLocalConstQualified()) {
37 Var, Context, Qualifiers::Const))
45 const char *TextAfter = SM.getCharacterData(Loc, &Invalid);
49 size_t Offset = std::strcspn(TextAfter,
"\n");
50 return Loc.getLocWithOffset(TextAfter[Offset] ==
'\0' ? Offset : Offset + 1);
54 DiagnosticBuilder &Diagnostic) {
55 auto &SM = Context.getSourceManager();
58 Context.getLangOpts());
59 std::optional<SourceLocation> PastNewLine =
61 if (Tok && PastNewLine) {
62 auto BeforeFirstTokenAfterComment = Tok->getLocation().getLocWithOffset(-1);
66 SM.isBeforeInTranslationUnit(*PastNewLine, BeforeFirstTokenAfterComment)
68 : BeforeFirstTokenAfterComment;
69 Diagnostic << FixItHint::CreateRemoval(
70 SourceRange(Stmt.getBeginLoc(), End));
72 Diagnostic << FixItHint::CreateRemoval(Stmt.getSourceRange());
78AST_MATCHER_FUNCTION_P(StatementMatcher,
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)))));
107AST_MATCHER(CXXMethodDecl, isStatic) {
return Node.isStatic(); }
109AST_MATCHER_FUNCTION(StatementMatcher, isConstRefReturningFunctionCall) {
114 return callExpr(argumentCountIs(0),
115 callee(functionDecl(returns(hasCanonicalType(
116 matchers::isReferenceToConst())),
117 unless(cxxMethodDecl(unless(isStatic()))))
123 std::vector<StringRef>, ExcludedContainerTypes) {
125 declRefExpr(to(varDecl(hasLocalStorage()).bind(
OldVarDeclId)));
127 anyOf(isConstRefReturningFunctionCall(),
128 isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes),
129 ignoringImpCasts(OldVarDeclRef),
130 ignoringImpCasts(unaryOperator(hasOperatorName(
"&"),
131 hasUnaryOperand(OldVarDeclRef)))));
149 const VarDecl &InitializingVar,
const Stmt &BlockStmt, ASTContext &Context,
150 const std::vector<StringRef> &ExcludedContainerTypes) {
151 QualType T = InitializingVar.getType().getCanonicalType();
153 T->isPointerType() ? 1 : 0))
158 if (!isa<ReferenceType, PointerType>(T))
163 if (!InitializingVar.isLocalVarDecl() || !InitializingVar.hasInit()) {
168 match(initializerReturnsReferenceToConst(ExcludedContainerTypes),
169 *InitializingVar.getInit(), Context);
175 if (
const auto *OrigVar = selectFirst<VarDecl>(
ObjectArgId, Matches))
177 ExcludedContainerTypes);
179 if (
const auto *OrigVar = selectFirst<VarDecl>(
OldVarDeclId, Matches))
181 ExcludedContainerTypes);
187 ASTContext &Context) {
191static const SubstTemplateTypeParmType *
193 auto Matches = match(
194 qualType(anyOf(substTemplateTypeParmType().bind(
"subst"),
195 hasDescendant(substTemplateTypeParmType().bind(
"subst")))),
197 return selectFirst<SubstTemplateTypeParmType>(
"subst", Matches);
201 const QualType &InitializerType,
202 ASTContext &Context) {
203 if (
const SubstTemplateTypeParmType *VarTmplType =
205 if (
const SubstTemplateTypeParmType *InitializerTmplType =
207 const TemplateTypeParmDecl *VarTTP = VarTmplType->getReplacedParameter();
208 const TemplateTypeParmDecl *InitTTP =
209 InitializerTmplType->getReplacedParameter();
210 return (VarTTP->getDepth() != InitTTP->getDepth() ||
211 VarTTP->getIndex() != InitTTP->getIndex() ||
212 VarTTP->isParameterPack() != InitTTP->isParameterPack());
219 const BoundNodes &Nodes) {
221 return OldVar->getType();
226 const auto *MethodDecl = Nodes.getNodeAs<CXXMethodDecl>(
MethodDeclId);
227 return MethodDecl->getReturnType();
234 utils::options::parseStringList(Options.get(
"AllowedTypes",
""))),
235 ExcludedContainerTypes(
utils::options::parseStringList(
236 Options.get(
"ExcludedContainerTypes",
""))) {}
239 auto LocalVarCopiedFrom =
240 [
this](
const ast_matchers::internal::Matcher<Expr> &CopyCtorArg) {
244 unless(has(decompositionDecl())),
248 hasCanonicalType(allOf(
249 matchers::isExpensiveToCopy(),
250 unless(hasDeclaration(namedDecl(
251 hasName(
"::std::function")))))),
252 unless(hasDeclaration(namedDecl(
255 unless(isImplicit()),
256 hasInitializer(traverse(
259 hasDeclaration(cxxConstructorDecl(
260 isCopyConstructor())),
261 hasArgument(0, CopyCtorArg))
263 .bind(
"newVarDecl")))
269 LocalVarCopiedFrom(anyOf(
270 isConstRefReturningFunctionCall(),
271 isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes))),
274 Finder->addMatcher(LocalVarCopiedFrom(declRefExpr(
280 const MatchFinder::MatchResult &Result) {
281 const auto &NewVar = *Result.Nodes.getNodeAs<VarDecl>(
"newVarDecl");
282 const auto &BlockStmt = *Result.Nodes.getNodeAs<Stmt>(
"blockStmt");
283 const auto &VarDeclStmt = *Result.Nodes.getNodeAs<DeclStmt>(
"declStmt");
286 const bool IssueFix =
287 VarDeclStmt.isSingleDecl() && !NewVar.getLocation().isMacroID();
288 const bool IsVarUnused =
isVariableUnused(NewVar, BlockStmt, *Result.Context);
289 const bool IsVarOnlyUsedAsConst =
294 NewVar, BlockStmt, VarDeclStmt, *Result.Context,
295 IssueFix, IsVarUnused, IsVarOnlyUsedAsConst};
296 const auto *OldVar = Result.Nodes.getNodeAs<VarDecl>(
OldVarDeclId);
297 const auto *ObjectArg = Result.Nodes.getNodeAs<VarDecl>(
ObjectArgId);
298 const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>(
"ctorCall");
300 TraversalKindScope RAII(*Result.Context, TK_AsIs);
305 for (
unsigned int I = 1; I < CtorCall->getNumArgs(); ++I)
306 if (!CtorCall->getArg(I)->isDefaultArgument())
319 if (OldVar ==
nullptr) {
321 handleCopyFromMethodReturn(Context, ObjectArg);
324 handleCopyFromLocalVar(Context, *OldVar);
328void UnnecessaryCopyInitialization::handleCopyFromMethodReturn(
329 const CheckContext &Ctx,
const VarDecl *ObjectArg) {
330 bool IsConstQualified = Ctx.Var.getType().isConstQualified();
331 if (!IsConstQualified && !Ctx.IsVarOnlyUsedAsConst)
333 if (ObjectArg !=
nullptr &&
335 ExcludedContainerTypes))
340void UnnecessaryCopyInitialization::handleCopyFromLocalVar(
341 const CheckContext &Ctx,
const VarDecl &OldVar) {
342 if (!Ctx.IsVarOnlyUsedAsConst ||
344 ExcludedContainerTypes))
352 diag(Ctx.
Var.getLocation(),
353 "the %select{|const qualified }0variable %1 of type %2 is "
355 "from a const reference%select{%select{ but is only used as const "
356 "reference|}0| but is never used}3; consider "
357 "%select{making it a const reference|removing the statement}3")
358 << Ctx.
Var.getType().isConstQualified() << &Ctx.
Var << Ctx.
Var.getType()
360 maybeIssueFixes(Ctx, Diagnostic);
366 diag(Ctx.
Var.getLocation(),
367 "local copy %0 of the variable %1 of type %2 is never "
369 "| and never used}3; consider %select{avoiding the copy|removing "
372 maybeIssueFixes(Ctx, Diagnostic);
375void UnnecessaryCopyInitialization::maybeIssueFixes(
376 const CheckContext &Ctx, DiagnosticBuilder &Diagnostic) {
387 Options.store(Opts,
"AllowedTypes",
389 Options.store(Opts,
"ExcludedContainerTypes",
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.
AST_MATCHER_FUNCTION_P(ast_matchers::internal::Matcher< Stmt >, comparisonOperatorWithCallee, ast_matchers::internal::Matcher< Decl >, funcDecl)
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. See constReferenceDeclR...
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, Qualifiers::TQ Qualifier, QualifierTarget QualTarget, QualifierPolicy QualPolicy)
Creates fix to qualify VarDecl with the specified Qualifier. Requires that Var is isolated in written...
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[]