diff options
Diffstat (limited to 'clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp')
| -rw-r--r-- | clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp | 393 |
1 files changed, 0 insertions, 393 deletions
diff --git a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp b/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp deleted file mode 100644 index 5918366..0000000 --- a/clang-tools-extra/clang-tidy/performance/UnnecessaryCopyInitialization.cpp +++ /dev/null @@ -1,393 +0,0 @@ -//===----------------------------------------------------------------------===// -// -// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. -// See https://llvm.org/LICENSE.txt for license information. -// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception -// -//===----------------------------------------------------------------------===// - -#include "UnnecessaryCopyInitialization.h" -#include "../utils/DeclRefExprUtils.h" -#include "../utils/FixItHintUtils.h" -#include "../utils/LexerUtils.h" -#include "../utils/Matchers.h" -#include "../utils/OptionsUtils.h" -#include "clang/AST/Decl.h" -#include "clang/Basic/Diagnostic.h" -#include <optional> - -namespace clang::tidy::performance { - -using namespace ::clang::ast_matchers; -using llvm::StringRef; -using utils::decl_ref_expr::allDeclRefExprs; -using utils::decl_ref_expr::isOnlyUsedAsConst; - -static constexpr StringRef ObjectArgId = "objectArg"; -static constexpr StringRef InitFunctionCallId = "initFunctionCall"; -static constexpr StringRef MethodDeclId = "methodDecl"; -static constexpr StringRef FunctionDeclId = "functionDecl"; -static constexpr StringRef OldVarDeclId = "oldVarDecl"; - -static void recordFixes(const VarDecl &Var, ASTContext &Context, - DiagnosticBuilder &Diagnostic) { - Diagnostic << utils::fixit::changeVarDeclToReference(Var, Context); - if (!Var.getType().isLocalConstQualified()) { - if (std::optional<FixItHint> Fix = utils::fixit::addQualifierToVarDecl( - Var, Context, Qualifiers::Const)) - Diagnostic << *Fix; - } -} - -static std::optional<SourceLocation> firstLocAfterNewLine(SourceLocation Loc, - SourceManager &SM) { - bool Invalid = false; - const char *TextAfter = SM.getCharacterData(Loc, &Invalid); - if (Invalid) { - return std::nullopt; - } - size_t Offset = std::strcspn(TextAfter, "\n"); - return Loc.getLocWithOffset(TextAfter[Offset] == '\0' ? Offset : Offset + 1); -} - -static void recordRemoval(const DeclStmt &Stmt, ASTContext &Context, - DiagnosticBuilder &Diagnostic) { - auto &SM = Context.getSourceManager(); - // Attempt to remove trailing comments as well. - auto Tok = utils::lexer::findNextTokenSkippingComments(Stmt.getEndLoc(), SM, - Context.getLangOpts()); - std::optional<SourceLocation> PastNewLine = - firstLocAfterNewLine(Stmt.getEndLoc(), SM); - if (Tok && PastNewLine) { - auto BeforeFirstTokenAfterComment = Tok->getLocation().getLocWithOffset(-1); - // Remove until the end of the line or the end of a trailing comment which - // ever comes first. - auto End = - SM.isBeforeInTranslationUnit(*PastNewLine, BeforeFirstTokenAfterComment) - ? *PastNewLine - : BeforeFirstTokenAfterComment; - Diagnostic << FixItHint::CreateRemoval( - SourceRange(Stmt.getBeginLoc(), End)); - } else { - Diagnostic << FixItHint::CreateRemoval(Stmt.getSourceRange()); - } -} - -namespace { - -AST_MATCHER_FUNCTION_P(StatementMatcher, - isRefReturningMethodCallWithConstOverloads, - std::vector<StringRef>, ExcludedContainerTypes) { - // Match method call expressions where the `this` argument is only used as - // const, this will be checked in `check()` part. This returned reference is - // highly likely to outlive the local const reference of the variable being - // declared. The assumption is that the reference being returned either points - // to a global static variable or to a member of the called object. - const auto MethodDecl = - cxxMethodDecl(returns(hasCanonicalType(referenceType()))) - .bind(MethodDeclId); - const auto ReceiverExpr = - ignoringParenImpCasts(declRefExpr(to(varDecl().bind(ObjectArgId)))); - const auto OnExpr = anyOf( - // Direct reference to `*this`: `a.f()` or `a->f()`. - ReceiverExpr, - // Access through dereference, typically used for `operator[]`: `(*a)[3]`. - unaryOperator(hasOperatorName("*"), hasUnaryOperand(ReceiverExpr))); - const auto ReceiverType = - hasCanonicalType(recordType(hasDeclaration(namedDecl( - unless(matchers::matchesAnyListedName(ExcludedContainerTypes)))))); - - return expr( - anyOf(cxxMemberCallExpr(callee(MethodDecl), on(OnExpr), - thisPointerType(ReceiverType)), - cxxOperatorCallExpr(callee(MethodDecl), hasArgument(0, OnExpr), - hasArgument(0, hasType(ReceiverType))))); -} - -AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } - -AST_MATCHER_FUNCTION(StatementMatcher, isConstRefReturningFunctionCall) { - // Only allow initialization of a const reference from a free function or - // static member function if it has no arguments. Otherwise it could return - // an alias to one of its arguments and the arguments need to be checked - // for const use as well. - return callExpr(argumentCountIs(0), - callee(functionDecl(returns(hasCanonicalType( - matchers::isReferenceToConst())), - unless(cxxMethodDecl(unless(isStatic())))) - .bind(FunctionDeclId))) - .bind(InitFunctionCallId); -} - -AST_MATCHER_FUNCTION_P(StatementMatcher, initializerReturnsReferenceToConst, - std::vector<StringRef>, ExcludedContainerTypes) { - auto OldVarDeclRef = - declRefExpr(to(varDecl(hasLocalStorage()).bind(OldVarDeclId))); - return expr( - anyOf(isConstRefReturningFunctionCall(), - isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes), - ignoringImpCasts(OldVarDeclRef), - ignoringImpCasts(unaryOperator(hasOperatorName("&"), - hasUnaryOperand(OldVarDeclRef))))); -} - -} // namespace - -// This checks that the variable itself is only used as const, and also makes -// sure that it does not reference another variable that could be modified in -// the BlockStmt. It does this by checking the following: -// 1. If the variable is neither a reference nor a pointer then the -// isOnlyUsedAsConst() check is sufficient. -// 2. If the (reference or pointer) variable is not initialized in a DeclStmt in -// the BlockStmt. In this case its pointee is likely not modified (unless it -// is passed as an alias into the method as well). -// 3. If the reference is initialized from a reference to const. This is -// the same set of criteria we apply when identifying the unnecessary copied -// variable in this check to begin with. In this case we check whether the -// object arg or variable that is referenced is immutable as well. -static bool isInitializingVariableImmutable( - const VarDecl &InitializingVar, const Stmt &BlockStmt, ASTContext &Context, - const std::vector<StringRef> &ExcludedContainerTypes) { - QualType T = InitializingVar.getType().getCanonicalType(); - if (!isOnlyUsedAsConst(InitializingVar, BlockStmt, Context, - T->isPointerType() ? 1 : 0)) - return false; - - // The variable is a value type and we know it is only used as const. Safe - // to reference it and avoid the copy. - if (!isa<ReferenceType, PointerType>(T)) - return true; - - // The reference or pointer is not declared and hence not initialized anywhere - // in the function. We assume its pointee is not modified then. - if (!InitializingVar.isLocalVarDecl() || !InitializingVar.hasInit()) { - return true; - } - - auto Matches = - match(initializerReturnsReferenceToConst(ExcludedContainerTypes), - *InitializingVar.getInit(), Context); - // The reference is initialized from a free function without arguments - // returning a const reference. This is a global immutable object. - if (selectFirst<CallExpr>(InitFunctionCallId, Matches) != nullptr) - return true; - // Check that the object argument is immutable as well. - if (const auto *OrigVar = selectFirst<VarDecl>(ObjectArgId, Matches)) - return isInitializingVariableImmutable(*OrigVar, BlockStmt, Context, - ExcludedContainerTypes); - // Check that the old variable we reference is immutable as well. - if (const auto *OrigVar = selectFirst<VarDecl>(OldVarDeclId, Matches)) - return isInitializingVariableImmutable(*OrigVar, BlockStmt, Context, - ExcludedContainerTypes); - - return false; -} - -static bool isVariableUnused(const VarDecl &Var, const Stmt &BlockStmt, - ASTContext &Context) { - return allDeclRefExprs(Var, BlockStmt, Context).empty(); -} - -static const SubstTemplateTypeParmType * -getSubstitutedType(const QualType &Type, ASTContext &Context) { - auto Matches = match( - qualType(anyOf(substTemplateTypeParmType().bind("subst"), - hasDescendant(substTemplateTypeParmType().bind("subst")))), - Type, Context); - return selectFirst<SubstTemplateTypeParmType>("subst", Matches); -} - -static bool differentReplacedTemplateParams(const QualType &VarType, - const QualType &InitializerType, - ASTContext &Context) { - if (const SubstTemplateTypeParmType *VarTmplType = - getSubstitutedType(VarType, Context)) { - if (const SubstTemplateTypeParmType *InitializerTmplType = - getSubstitutedType(InitializerType, Context)) { - const TemplateTypeParmDecl *VarTTP = VarTmplType->getReplacedParameter(); - const TemplateTypeParmDecl *InitTTP = - InitializerTmplType->getReplacedParameter(); - return (VarTTP->getDepth() != InitTTP->getDepth() || - VarTTP->getIndex() != InitTTP->getIndex() || - VarTTP->isParameterPack() != InitTTP->isParameterPack()); - } - } - return false; -} - -static QualType constructorArgumentType(const VarDecl *OldVar, - const BoundNodes &Nodes) { - if (OldVar) { - return OldVar->getType(); - } - if (const auto *FuncDecl = Nodes.getNodeAs<FunctionDecl>(FunctionDeclId)) { - return FuncDecl->getReturnType(); - } - const auto *MethodDecl = Nodes.getNodeAs<CXXMethodDecl>(MethodDeclId); - return MethodDecl->getReturnType(); -} - -UnnecessaryCopyInitialization::UnnecessaryCopyInitialization( - StringRef Name, ClangTidyContext *Context) - : ClangTidyCheck(Name, Context), - AllowedTypes( - utils::options::parseStringList(Options.get("AllowedTypes", ""))), - ExcludedContainerTypes(utils::options::parseStringList( - Options.get("ExcludedContainerTypes", ""))) {} - -void UnnecessaryCopyInitialization::registerMatchers(MatchFinder *Finder) { - auto LocalVarCopiedFrom = - [this](const ast_matchers::internal::Matcher<Expr> &CopyCtorArg) { - return compoundStmt( - forEachDescendant( - declStmt( - unless(has(decompositionDecl())), - has(varDecl( - hasLocalStorage(), - hasType(qualType( - hasCanonicalType(allOf( - matchers::isExpensiveToCopy(), - unless(hasDeclaration(namedDecl( - hasName("::std::function")))))), - unless(hasDeclaration(namedDecl( - matchers::matchesAnyListedName( - AllowedTypes)))))), - unless(isImplicit()), - hasInitializer(traverse( - TK_AsIs, - cxxConstructExpr( - hasDeclaration(cxxConstructorDecl( - isCopyConstructor())), - hasArgument(0, CopyCtorArg)) - .bind("ctorCall")))) - .bind("newVarDecl"))) - .bind("declStmt"))) - .bind("blockStmt"); - }; - - Finder->addMatcher( - LocalVarCopiedFrom(anyOf( - isConstRefReturningFunctionCall(), - isRefReturningMethodCallWithConstOverloads(ExcludedContainerTypes))), - this); - - Finder->addMatcher(LocalVarCopiedFrom(declRefExpr( - to(varDecl(hasLocalStorage()).bind(OldVarDeclId)))), - this); -} - -void UnnecessaryCopyInitialization::check( - const MatchFinder::MatchResult &Result) { - const auto &NewVar = *Result.Nodes.getNodeAs<VarDecl>("newVarDecl"); - const auto &BlockStmt = *Result.Nodes.getNodeAs<Stmt>("blockStmt"); - const auto &VarDeclStmt = *Result.Nodes.getNodeAs<DeclStmt>("declStmt"); - // Do not propose fixes if the DeclStmt has multiple VarDecls or in - // macros since we cannot place them correctly. - const bool IssueFix = - VarDeclStmt.isSingleDecl() && !NewVar.getLocation().isMacroID(); - const bool IsVarUnused = isVariableUnused(NewVar, BlockStmt, *Result.Context); - const bool IsVarOnlyUsedAsConst = - isOnlyUsedAsConst(NewVar, BlockStmt, *Result.Context, - // `NewVar` is always of non-pointer type. - 0); - const CheckContext Context{ - NewVar, BlockStmt, VarDeclStmt, *Result.Context, - IssueFix, IsVarUnused, IsVarOnlyUsedAsConst}; - const auto *OldVar = Result.Nodes.getNodeAs<VarDecl>(OldVarDeclId); - const auto *ObjectArg = Result.Nodes.getNodeAs<VarDecl>(ObjectArgId); - const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctorCall"); - - TraversalKindScope RAII(*Result.Context, TK_AsIs); - - // A constructor that looks like T(const T& t, bool arg = false) counts as a - // copy only when it is called with default arguments for the arguments after - // the first. - for (unsigned int I = 1; I < CtorCall->getNumArgs(); ++I) - if (!CtorCall->getArg(I)->isDefaultArgument()) - return; - - // Don't apply the check if the variable and its initializer have different - // replaced template parameter types. In this case the check triggers for a - // template instantiation where the substituted types are the same, but - // instantiations where the types differ and rely on implicit conversion would - // no longer compile if we switched to a reference. - if (differentReplacedTemplateParams( - Context.Var.getType(), constructorArgumentType(OldVar, Result.Nodes), - *Result.Context)) - return; - - if (OldVar == nullptr) { - // `auto NewVar = functionCall();` - handleCopyFromMethodReturn(Context, ObjectArg); - } else { - // `auto NewVar = OldVar;` - handleCopyFromLocalVar(Context, *OldVar); - } -} - -void UnnecessaryCopyInitialization::handleCopyFromMethodReturn( - const CheckContext &Ctx, const VarDecl *ObjectArg) { - bool IsConstQualified = Ctx.Var.getType().isConstQualified(); - if (!IsConstQualified && !Ctx.IsVarOnlyUsedAsConst) - return; - if (ObjectArg != nullptr && - !isInitializingVariableImmutable(*ObjectArg, Ctx.BlockStmt, Ctx.ASTCtx, - ExcludedContainerTypes)) - return; - diagnoseCopyFromMethodReturn(Ctx); -} - -void UnnecessaryCopyInitialization::handleCopyFromLocalVar( - const CheckContext &Ctx, const VarDecl &OldVar) { - if (!Ctx.IsVarOnlyUsedAsConst || - !isInitializingVariableImmutable(OldVar, Ctx.BlockStmt, Ctx.ASTCtx, - ExcludedContainerTypes)) - return; - diagnoseCopyFromLocalVar(Ctx, OldVar); -} - -void UnnecessaryCopyInitialization::diagnoseCopyFromMethodReturn( - const CheckContext &Ctx) { - auto Diagnostic = - diag(Ctx.Var.getLocation(), - "the %select{|const qualified }0variable %1 of type %2 is " - "copy-constructed " - "from a const reference%select{%select{ but is only used as const " - "reference|}0| but is never used}3; consider " - "%select{making it a const reference|removing the statement}3") - << Ctx.Var.getType().isConstQualified() << &Ctx.Var << Ctx.Var.getType() - << Ctx.IsVarUnused; - maybeIssueFixes(Ctx, Diagnostic); -} - -void UnnecessaryCopyInitialization::diagnoseCopyFromLocalVar( - const CheckContext &Ctx, const VarDecl &OldVar) { - auto Diagnostic = - diag(Ctx.Var.getLocation(), - "local copy %0 of the variable %1 of type %2 is never " - "modified%select{" - "| and never used}3; consider %select{avoiding the copy|removing " - "the statement}3") - << &Ctx.Var << &OldVar << Ctx.Var.getType() << Ctx.IsVarUnused; - maybeIssueFixes(Ctx, Diagnostic); -} - -void UnnecessaryCopyInitialization::maybeIssueFixes( - const CheckContext &Ctx, DiagnosticBuilder &Diagnostic) { - if (Ctx.IssueFix) { - if (Ctx.IsVarUnused) - recordRemoval(Ctx.VarDeclStmt, Ctx.ASTCtx, Diagnostic); - else - recordFixes(Ctx.Var, Ctx.ASTCtx, Diagnostic); - } -} - -void UnnecessaryCopyInitialization::storeOptions( - ClangTidyOptions::OptionMap &Opts) { - Options.store(Opts, "AllowedTypes", - utils::options::serializeStringList(AllowedTypes)); - Options.store(Opts, "ExcludedContainerTypes", - utils::options::serializeStringList(ExcludedContainerTypes)); -} - -} // namespace clang::tidy::performance |
