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);
48 const size_t Offset = std::strcspn(TextAfter,
"\n");
49 return Loc.getLocWithOffset(TextAfter[Offset] ==
'\0' ? Offset : Offset + 1);
53 DiagnosticBuilder &Diagnostic) {
54 auto &SM = Context.getSourceManager();
57 Context.getLangOpts());
58 std::optional<SourceLocation> PastNewLine =
60 if (Tok && PastNewLine) {
61 auto BeforeFirstTokenAfterComment = Tok->getLocation().getLocWithOffset(-1);
65 SM.isBeforeInTranslationUnit(*PastNewLine, BeforeFirstTokenAfterComment)
67 : BeforeFirstTokenAfterComment;
68 Diagnostic << FixItHint::CreateRemoval(
69 SourceRange(Stmt.getBeginLoc(), End));
71 Diagnostic << FixItHint::CreateRemoval(Stmt.getSourceRange());
77AST_MATCHER_FUNCTION_P(StatementMatcher,
78 isRefReturningMethodCallWithConstOverloads,
79 std::vector<StringRef>, ExcludedContainerTypes) {
85 const auto MethodDecl =
86 cxxMethodDecl(returns(hasCanonicalType(referenceType())))
88 const auto ReceiverExpr =
89 ignoringParenImpCasts(declRefExpr(to(varDecl().bind(
ObjectArgId))));
90 const auto OnExpr = anyOf(
94 unaryOperator(hasOperatorName(
"*"), hasUnaryOperand(ReceiverExpr)));
95 const auto ReceiverType =
96 hasCanonicalType(recordType(hasDeclaration(namedDecl(unless(
100 anyOf(cxxMemberCallExpr(callee(MethodDecl), on(OnExpr),
101 thisPointerType(ReceiverType)),
102 cxxOperatorCallExpr(callee(MethodDecl), hasArgument(0, OnExpr),
103 hasArgument(0, hasType(ReceiverType)))));
106AST_MATCHER(CXXMethodDecl, isStatic) {
return Node.isStatic(); }
108AST_MATCHER_FUNCTION(StatementMatcher, isConstRefReturningFunctionCall) {
113 return callExpr(argumentCountIs(0),
114 callee(functionDecl(returns(hasCanonicalType(
115 matchers::isReferenceToConst())),
116 unless(cxxMethodDecl(unless(isStatic()))))
122 std::vector<StringRef>, ExcludedContainerTypes) {
124 declRefExpr(to(varDecl(hasLocalStorage()).bind(
OldVarDeclId)));
126 anyOf(isConstRefReturningFunctionCall(),
127 isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes),
128 ignoringImpCasts(OldVarDeclRef),
129 ignoringImpCasts(unaryOperator(hasOperatorName(
"&"),
130 hasUnaryOperand(OldVarDeclRef)))));
148 const VarDecl &InitializingVar,
const Stmt &BlockStmt, ASTContext &Context,
149 const std::vector<StringRef> &ExcludedContainerTypes) {
150 const QualType T = InitializingVar.getType().getCanonicalType();
152 T->isPointerType() ? 1 : 0))
157 if (!isa<ReferenceType, PointerType>(T))
162 if (!InitializingVar.isLocalVarDecl() || !InitializingVar.hasInit())
166 match(initializerReturnsReferenceToConst(ExcludedContainerTypes),
167 *InitializingVar.getInit(), Context);
173 if (
const auto *OrigVar = selectFirst<VarDecl>(
ObjectArgId, Matches))
175 ExcludedContainerTypes);
177 if (
const auto *OrigVar = selectFirst<VarDecl>(
OldVarDeclId, Matches))
179 ExcludedContainerTypes);
185 ASTContext &Context) {
189static const SubstTemplateTypeParmType *
191 auto Matches = match(
192 qualType(anyOf(substTemplateTypeParmType().bind(
"subst"),
193 hasDescendant(substTemplateTypeParmType().bind(
"subst")))),
195 return selectFirst<SubstTemplateTypeParmType>(
"subst", Matches);
199 const QualType &InitializerType,
200 ASTContext &Context) {
201 if (
const SubstTemplateTypeParmType *VarTmplType =
203 if (
const SubstTemplateTypeParmType *InitializerTmplType =
205 const TemplateTypeParmDecl *VarTTP = VarTmplType->getReplacedParameter();
206 const TemplateTypeParmDecl *InitTTP =
207 InitializerTmplType->getReplacedParameter();
208 return (VarTTP->getDepth() != InitTTP->getDepth() ||
209 VarTTP->getIndex() != InitTTP->getIndex() ||
210 VarTTP->isParameterPack() != InitTTP->isParameterPack());
217 const BoundNodes &Nodes) {
219 return OldVar->getType();
222 const auto *MethodDecl = Nodes.getNodeAs<CXXMethodDecl>(
MethodDeclId);
223 return MethodDecl->getReturnType();
230 utils::options::parseStringList(Options.get(
"AllowedTypes",
""))),
231 ExcludedContainerTypes(
utils::options::parseStringList(
232 Options.get(
"ExcludedContainerTypes",
""))) {}
235 auto LocalVarCopiedFrom =
236 [
this](
const ast_matchers::internal::Matcher<Expr> &CopyCtorArg) {
240 unless(has(decompositionDecl())),
244 hasCanonicalType(allOf(
245 matchers::isExpensiveToCopy(),
246 unless(hasDeclaration(namedDecl(
247 hasName(
"::std::function")))))),
248 unless(hasDeclaration(namedDecl(
251 unless(isImplicit()),
252 hasInitializer(traverse(
255 hasDeclaration(cxxConstructorDecl(
256 isCopyConstructor())),
257 hasArgument(0, CopyCtorArg))
259 .bind(
"newVarDecl")))
265 LocalVarCopiedFrom(anyOf(
266 isConstRefReturningFunctionCall(),
267 isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes))),
270 Finder->addMatcher(LocalVarCopiedFrom(declRefExpr(
276 const MatchFinder::MatchResult &Result) {
277 const auto &NewVar = *Result.Nodes.getNodeAs<VarDecl>(
"newVarDecl");
278 const auto &BlockStmt = *Result.Nodes.getNodeAs<Stmt>(
"blockStmt");
279 const auto &VarDeclStmt = *Result.Nodes.getNodeAs<DeclStmt>(
"declStmt");
282 const bool IssueFix =
283 VarDeclStmt.isSingleDecl() && !NewVar.getLocation().isMacroID();
284 const bool IsVarUnused =
isVariableUnused(NewVar, BlockStmt, *Result.Context);
285 const bool IsVarOnlyUsedAsConst =
290 NewVar, BlockStmt, VarDeclStmt, *Result.Context,
291 IssueFix, IsVarUnused, IsVarOnlyUsedAsConst};
292 const auto *OldVar = Result.Nodes.getNodeAs<VarDecl>(
OldVarDeclId);
293 const auto *ObjectArg = Result.Nodes.getNodeAs<VarDecl>(
ObjectArgId);
294 const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>(
"ctorCall");
296 const TraversalKindScope RAII(*Result.Context, TK_AsIs);
301 for (
unsigned int I = 1; I < CtorCall->getNumArgs(); ++I)
302 if (!CtorCall->getArg(I)->isDefaultArgument())
315 if (OldVar ==
nullptr) {
317 handleCopyFromMethodReturn(Context, ObjectArg);
320 handleCopyFromLocalVar(Context, *OldVar);
324void UnnecessaryCopyInitializationCheck::handleCopyFromMethodReturn(
325 const CheckContext &Ctx,
const VarDecl *ObjectArg) {
326 const bool IsConstQualified = Ctx.Var.getType().isConstQualified();
327 if (!IsConstQualified && !Ctx.IsVarOnlyUsedAsConst)
329 if (ObjectArg !=
nullptr &&
331 ExcludedContainerTypes))
336void UnnecessaryCopyInitializationCheck::handleCopyFromLocalVar(
337 const CheckContext &Ctx,
const VarDecl &OldVar) {
338 if (!Ctx.IsVarOnlyUsedAsConst ||
340 ExcludedContainerTypes))
348 diag(Ctx.
Var.getLocation(),
349 "the %select{|const qualified }0variable %1 of type %2 is "
351 "from a const reference%select{%select{ but is only used as const "
352 "reference|}0| but is never used}3; consider "
353 "%select{making it a const reference|removing the statement}3")
354 << Ctx.
Var.getType().isConstQualified() << &Ctx.
Var << Ctx.
Var.getType()
356 maybeIssueFixes(Ctx, Diagnostic);
362 diag(Ctx.
Var.getLocation(),
363 "local copy %0 of the variable %1 of type %2 is never "
365 "| and never used}3; consider %select{avoiding the copy|removing "
368 maybeIssueFixes(Ctx, Diagnostic);
371void UnnecessaryCopyInitializationCheck::maybeIssueFixes(
372 const CheckContext &Ctx, DiagnosticBuilder &Diagnostic) {
383 Options.store(Opts,
"AllowedTypes",
385 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 > matchesAnyListedRegexName(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[]