1 //===--- UnnecessaryCopyInitialization.cpp - clang-tidy--------------------===// 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 9 #include "UnnecessaryCopyInitialization.h" 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" 17 #include <optional> 18 #include <utility> 19 20 namespace clang::tidy::performance { 21 namespace { 22 23 using namespace ::clang::ast_matchers; 24 using llvm::StringRef; 25 using utils::decl_ref_expr::allDeclRefExprs; 26 using utils::decl_ref_expr::isOnlyUsedAsConst; 27 28 static constexpr StringRef ObjectArgId = "objectArg"; 29 static constexpr StringRef InitFunctionCallId = "initFunctionCall"; 30 static constexpr StringRef MethodDeclId = "methodDecl"; 31 static constexpr StringRef FunctionDeclId = "functionDecl"; 32 static constexpr StringRef OldVarDeclId = "oldVarDecl"; 33 34 void recordFixes(const VarDecl &Var, ASTContext &Context, 35 DiagnosticBuilder &Diagnostic) { 36 Diagnostic << utils::fixit::changeVarDeclToReference(Var, Context); 37 if (!Var.getType().isLocalConstQualified()) { 38 if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl( 39 Var, Context, Qualifiers::Const)) 40 Diagnostic << *Fix; 41 } 42 } 43 44 std::optional<SourceLocation> firstLocAfterNewLine(SourceLocation Loc, 45 SourceManager &SM) { 46 bool Invalid = false; 47 const char *TextAfter = SM.getCharacterData(Loc, &Invalid); 48 if (Invalid) { 49 return std::nullopt; 50 } 51 size_t Offset = std::strcspn(TextAfter, "\n"); 52 return Loc.getLocWithOffset(TextAfter[Offset] == '\0' ? Offset : Offset + 1); 53 } 54 55 void recordRemoval(const DeclStmt &Stmt, ASTContext &Context, 56 DiagnosticBuilder &Diagnostic) { 57 auto &SM = Context.getSourceManager(); 58 // Attempt to remove trailing comments as well. 59 auto Tok = utils::lexer::findNextTokenSkippingComments(Stmt.getEndLoc(), SM, 60 Context.getLangOpts()); 61 std::optional<SourceLocation> PastNewLine = 62 firstLocAfterNewLine(Stmt.getEndLoc(), SM); 63 if (Tok && PastNewLine) { 64 auto BeforeFirstTokenAfterComment = Tok->getLocation().getLocWithOffset(-1); 65 // Remove until the end of the line or the end of a trailing comment which 66 // ever comes first. 67 auto End = 68 SM.isBeforeInTranslationUnit(*PastNewLine, BeforeFirstTokenAfterComment) 69 ? *PastNewLine 70 : BeforeFirstTokenAfterComment; 71 Diagnostic << FixItHint::CreateRemoval( 72 SourceRange(Stmt.getBeginLoc(), End)); 73 } else { 74 Diagnostic << FixItHint::CreateRemoval(Stmt.getSourceRange()); 75 } 76 } 77 78 AST_MATCHER_FUNCTION_P(StatementMatcher, 79 isRefReturningMethodCallWithConstOverloads, 80 std::vector<StringRef>, ExcludedContainerTypes) { 81 // Match method call expressions where the `this` argument is only used as 82 // const, this will be checked in `check()` part. This returned reference is 83 // highly likely to outlive the local const reference of the variable being 84 // declared. The assumption is that the reference being returned either points 85 // to a global static variable or to a member of the called object. 86 const auto MethodDecl = 87 cxxMethodDecl(returns(hasCanonicalType(referenceType()))) 88 .bind(MethodDeclId); 89 const auto ReceiverExpr = 90 ignoringParenImpCasts(declRefExpr(to(varDecl().bind(ObjectArgId)))); 91 const auto OnExpr = anyOf( 92 // Direct reference to `*this`: `a.f()` or `a->f()`. 93 ReceiverExpr, 94 // Access through dereference, typically used for `operator[]`: `(*a)[3]`. 95 unaryOperator(hasOperatorName("*"), hasUnaryOperand(ReceiverExpr))); 96 const auto ReceiverType = 97 hasCanonicalType(recordType(hasDeclaration(namedDecl( 98 unless(matchers::matchesAnyListedName(ExcludedContainerTypes)))))); 99 100 return expr( 101 anyOf(cxxMemberCallExpr(callee(MethodDecl), on(OnExpr), 102 thisPointerType(ReceiverType)), 103 cxxOperatorCallExpr(callee(MethodDecl), hasArgument(0, OnExpr), 104 hasArgument(0, hasType(ReceiverType))))); 105 } 106 107 AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } 108 109 AST_MATCHER_FUNCTION(StatementMatcher, isConstRefReturningFunctionCall) { 110 // Only allow initialization of a const reference from a free function or 111 // static member function if it has no arguments. Otherwise it could return 112 // an alias to one of its arguments and the arguments need to be checked 113 // for const use as well. 114 return callExpr(argumentCountIs(0), 115 callee(functionDecl(returns(hasCanonicalType(matchers::isReferenceToConst())), 116 unless(cxxMethodDecl(unless(isStatic())))) 117 .bind(FunctionDeclId))) 118 .bind(InitFunctionCallId); 119 } 120 121 AST_MATCHER_FUNCTION_P(StatementMatcher, initializerReturnsReferenceToConst, 122 std::vector<StringRef>, ExcludedContainerTypes) { 123 auto OldVarDeclRef = 124 declRefExpr(to(varDecl(hasLocalStorage()).bind(OldVarDeclId))); 125 return expr( 126 anyOf(isConstRefReturningFunctionCall(), 127 isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes), 128 ignoringImpCasts(OldVarDeclRef), 129 ignoringImpCasts(unaryOperator(hasOperatorName("&"), 130 hasUnaryOperand(OldVarDeclRef))))); 131 } 132 133 // This checks that the variable itself is only used as const, and also makes 134 // sure that it does not reference another variable that could be modified in 135 // the BlockStmt. It does this by checking the following: 136 // 1. If the variable is neither a reference nor a pointer then the 137 // isOnlyUsedAsConst() check is sufficient. 138 // 2. If the (reference or pointer) variable is not initialized in a DeclStmt in 139 // the BlockStmt. In this case its pointee is likely not modified (unless it 140 // is passed as an alias into the method as well). 141 // 3. If the reference is initialized from a reference to const. This is 142 // the same set of criteria we apply when identifying the unnecessary copied 143 // variable in this check to begin with. In this case we check whether the 144 // object arg or variable that is referenced is immutable as well. 145 static bool isInitializingVariableImmutable( 146 const VarDecl &InitializingVar, const Stmt &BlockStmt, ASTContext &Context, 147 const std::vector<StringRef> &ExcludedContainerTypes) { 148 QualType T = InitializingVar.getType().getCanonicalType(); 149 if (!isOnlyUsedAsConst(InitializingVar, BlockStmt, Context, 150 T->isPointerType() ? 1 : 0)) 151 return false; 152 153 // The variable is a value type and we know it is only used as const. Safe 154 // to reference it and avoid the copy. 155 if (!isa<ReferenceType, PointerType>(T)) 156 return true; 157 158 // The reference or pointer is not declared and hence not initialized anywhere 159 // in the function. We assume its pointee is not modified then. 160 if (!InitializingVar.isLocalVarDecl() || !InitializingVar.hasInit()) { 161 return true; 162 } 163 164 auto Matches = 165 match(initializerReturnsReferenceToConst(ExcludedContainerTypes), 166 *InitializingVar.getInit(), Context); 167 // The reference is initialized from a free function without arguments 168 // returning a const reference. This is a global immutable object. 169 if (selectFirst<CallExpr>(InitFunctionCallId, Matches) != nullptr) 170 return true; 171 // Check that the object argument is immutable as well. 172 if (const auto *OrigVar = selectFirst<VarDecl>(ObjectArgId, Matches)) 173 return isInitializingVariableImmutable(*OrigVar, BlockStmt, Context, 174 ExcludedContainerTypes); 175 // Check that the old variable we reference is immutable as well. 176 if (const auto *OrigVar = selectFirst<VarDecl>(OldVarDeclId, Matches)) 177 return isInitializingVariableImmutable(*OrigVar, BlockStmt, Context, 178 ExcludedContainerTypes); 179 180 return false; 181 } 182 183 bool isVariableUnused(const VarDecl &Var, const Stmt &BlockStmt, 184 ASTContext &Context) { 185 return allDeclRefExprs(Var, BlockStmt, Context).empty(); 186 } 187 188 const SubstTemplateTypeParmType *getSubstitutedType(const QualType &Type, 189 ASTContext &Context) { 190 auto Matches = match( 191 qualType(anyOf(substTemplateTypeParmType().bind("subst"), 192 hasDescendant(substTemplateTypeParmType().bind("subst")))), 193 Type, Context); 194 return selectFirst<SubstTemplateTypeParmType>("subst", Matches); 195 } 196 197 bool differentReplacedTemplateParams(const QualType &VarType, 198 const QualType &InitializerType, 199 ASTContext &Context) { 200 if (const SubstTemplateTypeParmType *VarTmplType = 201 getSubstitutedType(VarType, Context)) { 202 if (const SubstTemplateTypeParmType *InitializerTmplType = 203 getSubstitutedType(InitializerType, Context)) { 204 const TemplateTypeParmDecl *VarTTP = VarTmplType->getReplacedParameter(); 205 const TemplateTypeParmDecl *InitTTP = 206 InitializerTmplType->getReplacedParameter(); 207 return (VarTTP->getDepth() != InitTTP->getDepth() || 208 VarTTP->getIndex() != InitTTP->getIndex() || 209 VarTTP->isParameterPack() != InitTTP->isParameterPack()); 210 } 211 } 212 return false; 213 } 214 215 QualType constructorArgumentType(const VarDecl *OldVar, 216 const BoundNodes &Nodes) { 217 if (OldVar) { 218 return OldVar->getType(); 219 } 220 if (const auto *FuncDecl = Nodes.getNodeAs<FunctionDecl>(FunctionDeclId)) { 221 return FuncDecl->getReturnType(); 222 } 223 const auto *MethodDecl = Nodes.getNodeAs<CXXMethodDecl>(MethodDeclId); 224 return MethodDecl->getReturnType(); 225 } 226 227 } // namespace 228 229 UnnecessaryCopyInitialization::UnnecessaryCopyInitialization( 230 StringRef Name, ClangTidyContext *Context) 231 : ClangTidyCheck(Name, Context), 232 AllowedTypes( 233 utils::options::parseStringList(Options.get("AllowedTypes", ""))), 234 ExcludedContainerTypes(utils::options::parseStringList( 235 Options.get("ExcludedContainerTypes", ""))) {} 236 237 void UnnecessaryCopyInitialization::registerMatchers(MatchFinder *Finder) { 238 auto LocalVarCopiedFrom = [this](const ast_matchers::internal::Matcher<Expr> &CopyCtorArg) { 239 return compoundStmt( 240 forEachDescendant( 241 declStmt( 242 unless(has(decompositionDecl())), 243 has(varDecl(hasLocalStorage(), 244 hasType(qualType( 245 hasCanonicalType(allOf( 246 matchers::isExpensiveToCopy(), 247 unless(hasDeclaration(namedDecl( 248 hasName("::std::function")))))), 249 unless(hasDeclaration(namedDecl( 250 matchers::matchesAnyListedName( 251 AllowedTypes)))))), 252 unless(isImplicit()), 253 hasInitializer(traverse( 254 TK_AsIs, 255 cxxConstructExpr( 256 hasDeclaration(cxxConstructorDecl( 257 isCopyConstructor())), 258 hasArgument(0, CopyCtorArg)) 259 .bind("ctorCall")))) 260 .bind("newVarDecl"))) 261 .bind("declStmt"))) 262 .bind("blockStmt"); 263 }; 264 265 Finder->addMatcher( 266 LocalVarCopiedFrom(anyOf( 267 isConstRefReturningFunctionCall(), 268 isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes))), 269 this); 270 271 Finder->addMatcher(LocalVarCopiedFrom(declRefExpr( 272 to(varDecl(hasLocalStorage()).bind(OldVarDeclId)))), 273 this); 274 } 275 276 void UnnecessaryCopyInitialization::check( 277 const MatchFinder::MatchResult &Result) { 278 const auto &NewVar = *Result.Nodes.getNodeAs<VarDecl>("newVarDecl"); 279 const auto &BlockStmt = *Result.Nodes.getNodeAs<Stmt>("blockStmt"); 280 const auto &VarDeclStmt = *Result.Nodes.getNodeAs<DeclStmt>("declStmt"); 281 // Do not propose fixes if the DeclStmt has multiple VarDecls or in 282 // macros since we cannot place them correctly. 283 const bool IssueFix = 284 VarDeclStmt.isSingleDecl() && !NewVar.getLocation().isMacroID(); 285 const bool IsVarUnused = isVariableUnused(NewVar, BlockStmt, *Result.Context); 286 const bool IsVarOnlyUsedAsConst = 287 isOnlyUsedAsConst(NewVar, BlockStmt, *Result.Context, 288 // `NewVar` is always of non-pointer type. 289 0); 290 const CheckContext Context{ 291 NewVar, BlockStmt, VarDeclStmt, *Result.Context, 292 IssueFix, IsVarUnused, IsVarOnlyUsedAsConst}; 293 const auto *OldVar = Result.Nodes.getNodeAs<VarDecl>(OldVarDeclId); 294 const auto *ObjectArg = Result.Nodes.getNodeAs<VarDecl>(ObjectArgId); 295 const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctorCall"); 296 297 TraversalKindScope RAII(*Result.Context, TK_AsIs); 298 299 // A constructor that looks like T(const T& t, bool arg = false) counts as a 300 // copy only when it is called with default arguments for the arguments after 301 // the first. 302 for (unsigned int I = 1; I < CtorCall->getNumArgs(); ++I) 303 if (!CtorCall->getArg(I)->isDefaultArgument()) 304 return; 305 306 // Don't apply the check if the variable and its initializer have different 307 // replaced template parameter types. In this case the check triggers for a 308 // template instantiation where the substituted types are the same, but 309 // instantiations where the types differ and rely on implicit conversion would 310 // no longer compile if we switched to a reference. 311 if (differentReplacedTemplateParams( 312 Context.Var.getType(), constructorArgumentType(OldVar, Result.Nodes), 313 *Result.Context)) 314 return; 315 316 if (OldVar == nullptr) { 317 // `auto NewVar = functionCall();` 318 handleCopyFromMethodReturn(Context, ObjectArg); 319 } else { 320 // `auto NewVar = OldVar;` 321 handleCopyFromLocalVar(Context, *OldVar); 322 } 323 } 324 325 void UnnecessaryCopyInitialization::handleCopyFromMethodReturn( 326 const CheckContext &Ctx, const VarDecl *ObjectArg) { 327 bool IsConstQualified = Ctx.Var.getType().isConstQualified(); 328 if (!IsConstQualified && !Ctx.IsVarOnlyUsedAsConst) 329 return; 330 if (ObjectArg != nullptr && 331 !isInitializingVariableImmutable(*ObjectArg, Ctx.BlockStmt, Ctx.ASTCtx, 332 ExcludedContainerTypes)) 333 return; 334 diagnoseCopyFromMethodReturn(Ctx); 335 } 336 337 void UnnecessaryCopyInitialization::handleCopyFromLocalVar( 338 const CheckContext &Ctx, const VarDecl &OldVar) { 339 if (!Ctx.IsVarOnlyUsedAsConst || 340 !isInitializingVariableImmutable(OldVar, Ctx.BlockStmt, Ctx.ASTCtx, 341 ExcludedContainerTypes)) 342 return; 343 diagnoseCopyFromLocalVar(Ctx, OldVar); 344 } 345 346 void UnnecessaryCopyInitialization::diagnoseCopyFromMethodReturn( 347 const CheckContext &Ctx) { 348 auto Diagnostic = 349 diag(Ctx.Var.getLocation(), 350 "the %select{|const qualified }0variable %1 is " 351 "copy-constructed " 352 "from a const reference%select{%select{ but is only used as const " 353 "reference|}0| but is never used}2; consider " 354 "%select{making it a const reference|removing the statement}2") 355 << Ctx.Var.getType().isConstQualified() << &Ctx.Var << Ctx.IsVarUnused; 356 maybeIssueFixes(Ctx, Diagnostic); 357 } 358 359 void UnnecessaryCopyInitialization::diagnoseCopyFromLocalVar( 360 const CheckContext &Ctx, const VarDecl &OldVar) { 361 auto Diagnostic = 362 diag(Ctx.Var.getLocation(), 363 "local copy %1 of the variable %0 is never modified%select{" 364 "| and never used}2; consider %select{avoiding the copy|removing " 365 "the statement}2") 366 << &OldVar << &Ctx.Var << Ctx.IsVarUnused; 367 maybeIssueFixes(Ctx, Diagnostic); 368 } 369 370 void UnnecessaryCopyInitialization::maybeIssueFixes( 371 const CheckContext &Ctx, DiagnosticBuilder &Diagnostic) { 372 if (Ctx.IssueFix) { 373 if (Ctx.IsVarUnused) 374 recordRemoval(Ctx.VarDeclStmt, Ctx.ASTCtx, Diagnostic); 375 else 376 recordFixes(Ctx.Var, Ctx.ASTCtx, Diagnostic); 377 } 378 } 379 380 void UnnecessaryCopyInitialization::storeOptions( 381 ClangTidyOptions::OptionMap &Opts) { 382 Options.store(Opts, "AllowedTypes", 383 utils::options::serializeStringList(AllowedTypes)); 384 Options.store(Opts, "ExcludedContainerTypes", 385 utils::options::serializeStringList(ExcludedContainerTypes)); 386 } 387 388 } // namespace clang::tidy::performance 389