[clang-tidy] performance-unnecessary-copy-init: Add a hook... (#73921)

... so that derived checks can can observe for which
variables a warning has been emitted. Does nothing by default, which
makes this an NFC.
This commit is contained in:
Clement Courbet 2023-12-07 08:31:16 +01:00 committed by GitHub
parent fc42a2f5c4
commit 04ce9a34ea
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 85 additions and 63 deletions

View File

@ -15,6 +15,7 @@
#include "clang/AST/Decl.h" #include "clang/AST/Decl.h"
#include "clang/Basic/Diagnostic.h" #include "clang/Basic/Diagnostic.h"
#include <optional> #include <optional>
#include <utility>
namespace clang::tidy::performance { namespace clang::tidy::performance {
namespace { namespace {
@ -263,19 +264,25 @@ void UnnecessaryCopyInitialization::registerMatchers(MatchFinder *Finder) {
void UnnecessaryCopyInitialization::check( void UnnecessaryCopyInitialization::check(
const MatchFinder::MatchResult &Result) { const MatchFinder::MatchResult &Result) {
const auto *NewVar = Result.Nodes.getNodeAs<VarDecl>("newVarDecl"); 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);
const CheckContext Context{
NewVar, BlockStmt, VarDeclStmt, *Result.Context,
IssueFix, IsVarUnused, IsVarOnlyUsedAsConst};
const auto *OldVar = Result.Nodes.getNodeAs<VarDecl>(OldVarDeclId); const auto *OldVar = Result.Nodes.getNodeAs<VarDecl>(OldVarDeclId);
const auto *ObjectArg = Result.Nodes.getNodeAs<VarDecl>(ObjectArgId); const auto *ObjectArg = Result.Nodes.getNodeAs<VarDecl>(ObjectArgId);
const auto *BlockStmt = Result.Nodes.getNodeAs<Stmt>("blockStmt");
const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctorCall"); const auto *CtorCall = Result.Nodes.getNodeAs<CXXConstructExpr>("ctorCall");
const auto *Stmt = Result.Nodes.getNodeAs<DeclStmt>("declStmt");
TraversalKindScope RAII(*Result.Context, TK_AsIs); TraversalKindScope RAII(*Result.Context, TK_AsIs);
// Do not propose fixes if the DeclStmt has multiple VarDecls or in macros
// since we cannot place them correctly.
bool IssueFix = Stmt->isSingleDecl() && !NewVar->getLocation().isMacroID();
// A constructor that looks like T(const T& t, bool arg = false) counts as a // 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 // copy only when it is called with default arguments for the arguments after
// the first. // the first.
@ -289,74 +296,71 @@ void UnnecessaryCopyInitialization::check(
// instantiations where the types differ and rely on implicit conversion would // instantiations where the types differ and rely on implicit conversion would
// no longer compile if we switched to a reference. // no longer compile if we switched to a reference.
if (differentReplacedTemplateParams( if (differentReplacedTemplateParams(
NewVar->getType(), constructorArgumentType(OldVar, Result.Nodes), Context.Var.getType(), constructorArgumentType(OldVar, Result.Nodes),
*Result.Context)) *Result.Context))
return; return;
if (OldVar == nullptr) { if (OldVar == nullptr) {
handleCopyFromMethodReturn(*NewVar, *BlockStmt, *Stmt, IssueFix, ObjectArg, // `auto NewVar = functionCall();`
*Result.Context); handleCopyFromMethodReturn(Context, ObjectArg);
} else { } else {
handleCopyFromLocalVar(*NewVar, *OldVar, *BlockStmt, *Stmt, IssueFix, // `auto NewVar = OldVar;`
*Result.Context); handleCopyFromLocalVar(Context, *OldVar);
} }
} }
void UnnecessaryCopyInitialization::handleCopyFromMethodReturn( void UnnecessaryCopyInitialization::handleCopyFromMethodReturn(
const VarDecl &Var, const Stmt &BlockStmt, const DeclStmt &Stmt, const CheckContext &Ctx, const VarDecl *ObjectArg) {
bool IssueFix, const VarDecl *ObjectArg, ASTContext &Context) { bool IsConstQualified = Ctx.Var.getType().isConstQualified();
bool IsConstQualified = Var.getType().isConstQualified(); if (!IsConstQualified && !Ctx.IsVarOnlyUsedAsConst)
if (!IsConstQualified && !isOnlyUsedAsConst(Var, BlockStmt, Context))
return; return;
if (ObjectArg != nullptr && if (ObjectArg != nullptr &&
!isInitializingVariableImmutable(*ObjectArg, BlockStmt, Context, !isInitializingVariableImmutable(*ObjectArg, Ctx.BlockStmt, Ctx.ASTCtx,
ExcludedContainerTypes)) ExcludedContainerTypes))
return; return;
if (isVariableUnused(Var, BlockStmt, Context)) { diagnoseCopyFromMethodReturn(Ctx);
auto Diagnostic =
diag(Var.getLocation(),
"the %select{|const qualified }0variable %1 is copy-constructed "
"from a const reference but is never used; consider "
"removing the statement")
<< IsConstQualified << &Var;
if (IssueFix)
recordRemoval(Stmt, Context, Diagnostic);
} else {
auto Diagnostic =
diag(Var.getLocation(),
"the %select{|const qualified }0variable %1 is copy-constructed "
"from a const reference%select{ but is only used as const "
"reference|}0; consider making it a const reference")
<< IsConstQualified << &Var;
if (IssueFix)
recordFixes(Var, Context, Diagnostic);
}
} }
void UnnecessaryCopyInitialization::handleCopyFromLocalVar( void UnnecessaryCopyInitialization::handleCopyFromLocalVar(
const VarDecl &NewVar, const VarDecl &OldVar, const Stmt &BlockStmt, const CheckContext &Ctx, const VarDecl &OldVar) {
const DeclStmt &Stmt, bool IssueFix, ASTContext &Context) { if (!Ctx.IsVarOnlyUsedAsConst ||
if (!isOnlyUsedAsConst(NewVar, BlockStmt, Context) || !isInitializingVariableImmutable(OldVar, Ctx.BlockStmt, Ctx.ASTCtx,
!isInitializingVariableImmutable(OldVar, BlockStmt, Context,
ExcludedContainerTypes)) ExcludedContainerTypes))
return; return;
diagnoseCopyFromLocalVar(Ctx, OldVar);
}
if (isVariableUnused(NewVar, BlockStmt, Context)) { void UnnecessaryCopyInitialization::diagnoseCopyFromMethodReturn(
auto Diagnostic = diag(NewVar.getLocation(), const CheckContext &Ctx) {
"local copy %0 of the variable %1 is never modified " auto Diagnostic =
"and never used; " diag(Ctx.Var.getLocation(),
"consider removing the statement") "the %select{|const qualified }0variable %1 is "
<< &NewVar << &OldVar; "copy-constructed "
if (IssueFix) "from a const reference%select{%select{ but is only used as const "
recordRemoval(Stmt, Context, Diagnostic); "reference|}0| but is never used}2; consider "
} else { "%select{making it a const reference|removing the statement}2")
auto Diagnostic = << Ctx.Var.getType().isConstQualified() << &Ctx.Var << Ctx.IsVarUnused;
diag(NewVar.getLocation(), maybeIssueFixes(Ctx, Diagnostic);
"local copy %0 of the variable %1 is never modified; " }
"consider avoiding the copy")
<< &NewVar << &OldVar; void UnnecessaryCopyInitialization::diagnoseCopyFromLocalVar(
if (IssueFix) const CheckContext &Ctx, const VarDecl &OldVar) {
recordFixes(NewVar, Context, Diagnostic); auto Diagnostic =
diag(Ctx.Var.getLocation(),
"local copy %1 of the variable %0 is never modified%select{"
"| and never used}2; consider %select{avoiding the copy|removing "
"the statement}2")
<< &OldVar << &Ctx.Var << 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);
} }
} }

View File

@ -32,14 +32,32 @@ public:
void check(const ast_matchers::MatchFinder::MatchResult &Result) override; void check(const ast_matchers::MatchFinder::MatchResult &Result) override;
void storeOptions(ClangTidyOptions::OptionMap &Opts) override; void storeOptions(ClangTidyOptions::OptionMap &Opts) override;
protected:
// A helper to manipulate the state common to
// `CopyFromMethodReturn` and `CopyFromLocalVar`.
struct CheckContext {
const VarDecl &Var;
const Stmt &BlockStmt;
const DeclStmt &VarDeclStmt;
clang::ASTContext &ASTCtx;
const bool IssueFix;
const bool IsVarUnused;
const bool IsVarOnlyUsedAsConst;
};
// Create diagnostics. These are virtual so that derived classes can change
// behaviour.
virtual void diagnoseCopyFromMethodReturn(const CheckContext &Ctx);
virtual void diagnoseCopyFromLocalVar(const CheckContext &Ctx,
const VarDecl &OldVar);
private: private:
void handleCopyFromMethodReturn(const VarDecl &Var, const Stmt &BlockStmt, void handleCopyFromMethodReturn(const CheckContext &Ctx,
const DeclStmt &Stmt, bool IssueFix, const VarDecl *ObjectArg);
const VarDecl *ObjectArg, void handleCopyFromLocalVar(const CheckContext &Ctx, const VarDecl &OldVar);
ASTContext &Context);
void handleCopyFromLocalVar(const VarDecl &NewVar, const VarDecl &OldVar, void maybeIssueFixes(const CheckContext &Ctx, DiagnosticBuilder &Diagnostic);
const Stmt &BlockStmt, const DeclStmt &Stmt,
bool IssueFix, ASTContext &Context);
const std::vector<StringRef> AllowedTypes; const std::vector<StringRef> AllowedTypes;
const std::vector<StringRef> ExcludedContainerTypes; const std::vector<StringRef> ExcludedContainerTypes;
}; };