diff --git a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/readability/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/readability/CMakeLists.txt @@ -32,6 +32,7 @@ SimplifySubscriptExprCheck.cpp StaticAccessedThroughInstanceCheck.cpp StaticDefinitionInAnonymousNamespaceCheck.cpp + StaticMethodCheck.cpp StringCompareCheck.cpp UniqueptrDeleteReleaseCheck.cpp UppercaseLiteralSuffixCheck.cpp diff --git a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp --- a/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/readability/ReadabilityTidyModule.cpp @@ -38,6 +38,7 @@ #include "SimplifySubscriptExprCheck.h" #include "StaticAccessedThroughInstanceCheck.h" #include "StaticDefinitionInAnonymousNamespaceCheck.h" +#include "StaticMethodCheck.h" #include "StringCompareCheck.h" #include "UniqueptrDeleteReleaseCheck.h" #include "UppercaseLiteralSuffixCheck.h" @@ -91,6 +92,8 @@ "readability-static-accessed-through-instance"); CheckFactories.registerCheck( "readability-static-definition-in-anonymous-namespace"); + CheckFactories.registerCheck( + "readability-static-method"); CheckFactories.registerCheck( "readability-string-compare"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/clang-tidy/readability/StaticMethodCheck.h b/clang-tools-extra/clang-tidy/readability/StaticMethodCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/StaticMethodCheck.h @@ -0,0 +1,36 @@ +//===--- StaticMethodCheck.h - clang-tidy -----------------------*- C++ -*-===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATICMETHODCHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATICMETHODCHECK_H + +#include "../ClangTidy.h" + +namespace clang { +namespace tidy { +namespace readability { + +/// This check finds C++ class methods than can be made static +/// because they don't use the 'this' pointer. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/readability-static-method.html +class StaticMethodCheck : public ClangTidyCheck { +public: + StaticMethodCheck(StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context) {} + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; +}; + +} // namespace readability +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_READABILITY_STATICMETHODCHECK_H diff --git a/clang-tools-extra/clang-tidy/readability/StaticMethodCheck.cpp b/clang-tools-extra/clang-tidy/readability/StaticMethodCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/StaticMethodCheck.cpp @@ -0,0 +1,164 @@ +//===--- StaticMethodCheck.cpp - clang-tidy -------------------------------===// +// +// The LLVM Compiler Infrastructure +// +// This file is distributed under the University of Illinois Open Source +// License. See LICENSE.TXT for details. +// +//===----------------------------------------------------------------------===// + +#include "StaticMethodCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/AST/DeclCXX.h" +#include "clang/AST/RecursiveASTVisitor.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "clang/Basic/SourceLocation.h" + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace readability { + +class FindUsageOfThis : public RecursiveASTVisitor { +public: + FindUsageOfThis() {} + bool Used = false; + + bool VisitCXXThisExpr(const CXXThisExpr *E) { + Used = true; + return false; // Stop traversal. + } +}; + +void StaticMethodCheck::registerMatchers(MatchFinder *Finder) { + Finder->addMatcher( + cxxMethodDecl(unless(isExpansionInSystemHeader()), isDefinition()) + .bind("x"), + this); +} + +/// \brief Obtain the original source code text from a SourceRange. +static StringRef getStringFromRange(SourceManager &SourceMgr, + const LangOptions &LangOpts, + SourceRange Range) { + if (SourceMgr.getFileID(Range.getBegin()) != + SourceMgr.getFileID(Range.getEnd())) + return {}; + + return Lexer::getSourceText(CharSourceRange(Range, true), SourceMgr, + LangOpts); +} + +static SourceRange getLocationOfConst(const TypeSourceInfo *TSI, + SourceManager &SourceMgr, + const LangOptions &LangOpts) { + if (!TSI) + return {}; + FunctionTypeLoc FTL = + TSI->getTypeLoc().IgnoreParens().getAs(); + if (!FTL) + return {}; + + auto Range = SourceRange{FTL.getRParenLoc().getLocWithOffset(1), + FTL.getLocalRangeEnd()}; + // Inside Range, there might be other keywords and trailing return types. + // Find the exact position of "const". + StringRef Text = getStringFromRange(SourceMgr, LangOpts, Range); + auto Offset = Text.find("const"); + if (Offset == StringRef::npos) + return {}; + + SourceLocation Start = Range.getBegin().getLocWithOffset(Offset); + return {Start, Start.getLocWithOffset(strlen("const") - 1)}; +} + +// Returns `true` if `Range` is inside a macro definition. +static bool insideMacroDefinition(const MatchFinder::MatchResult &Result, + SourceRange Range) { + return !clang::Lexer::makeFileCharRange( + clang::CharSourceRange::getCharRange(Range), + *Result.SourceManager, Result.Context->getLangOpts()) + .isValid(); +} + +void StaticMethodCheck::check(const MatchFinder::MatchResult &Result) { + const auto *Definition = Result.Nodes.getNodeAs("x"); + if (!Definition->isUserProvided()) + return; + if (Definition->isStatic()) + return; // Nothing we can improve. + if (Definition->isVirtual()) + return; + if (Definition->getParent()->hasAnyDependentBases()) + return; // Method might become virtual depending on template base class. + if (Definition->hasTrivialBody()) + return; + if (Definition->isOverloadedOperator()) + return; + if (Definition->getTemplateSpecializationKind() != TSK_Undeclared) + return; + if (Definition->getTemplatedKind() != FunctionDecl::TK_NonTemplate) + return; // We might not see all specializations. + if (isa(Definition) || + isa(Definition) || isa(Definition)) + return; + if (Definition->isDependentContext()) + return; + if (Definition->getParent()->isLambda()) + return; + + if (insideMacroDefinition( + Result, + Definition->getTypeSourceInfo()->getTypeLoc().getSourceRange())) + return; + + const CXXMethodDecl* Declaration = Definition->getCanonicalDecl(); + + if (Declaration != Definition && + insideMacroDefinition( + Result, + Declaration->getTypeSourceInfo()->getTypeLoc().getSourceRange())) + return; + + FindUsageOfThis UsageOfThis; + // TraverseStmt does not modify its argument. + UsageOfThis.TraverseStmt(const_cast(Definition->getBody())); + + if (UsageOfThis.Used) + return; + + // TODO: For out-of-line declarations, don't modify the source if the header + // is excluded by the -header-filter option. + DiagnosticBuilder Diag = diag(Definition->getLocation(), "method %0 can be made static") + << Definition; + + if (Definition->isConst()) { + // Make sure that we either remove 'const' on both declaration and + // definition or emit no fix-it at all. + SourceRange DefConst = getLocationOfConst(Definition->getTypeSourceInfo(), + *Result.SourceManager, + Result.Context->getLangOpts()); + + if (DefConst.isInvalid()) + return; + + if (Declaration != Definition) { + SourceRange DeclConst = getLocationOfConst(Declaration->getTypeSourceInfo(), + *Result.SourceManager, + Result.Context->getLangOpts()); + + if (DeclConst.isInvalid()) + return; + Diag << FixItHint::CreateRemoval(DeclConst); + } + + // Remove existing 'const' from both declaration and definition. + Diag << FixItHint::CreateRemoval(DefConst); + } + Diag << FixItHint::CreateInsertion(Declaration->getBeginLoc(), "static "); +} + +} // namespace readability +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -163,6 +163,11 @@ Rewrites function signatures to use a trailing return type. +- New :doc:`readability-static-method + ` check. + + Finds non-static member functions that can be made ``static``. + Improvements to include-fixer ----------------------------- diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -275,6 +275,7 @@ readability-simplify-subscript-expr readability-static-accessed-through-instance readability-static-definition-in-anonymous-namespace + readability-static-method readability-string-compare readability-uniqueptr-delete-release readability-uppercase-literal-suffix diff --git a/clang-tools-extra/docs/clang-tidy/checks/readability-static-method.rst b/clang-tools-extra/docs/clang-tidy/checks/readability-static-method.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/readability-static-method.rst @@ -0,0 +1,14 @@ +.. title:: clang-tidy - readability-static-method + +readability-static-method +========================= + +Finds non-static member functions that can be made ``static`` +because the functions don't use ``this``. + +After applying modifications as suggested by the check, runnnig the check again +might find more opportunities to mark member functions ``static``. + +After making a member function ``static``, you might want to run the check +`readability-static-accessed-through-instance` to replace calls like +``Instance.method()`` by ``Class::method()``. diff --git a/clang-tools-extra/test/clang-tidy/readability-static-method.cpp b/clang-tools-extra/test/clang-tidy/readability-static-method.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/readability-static-method.cpp @@ -0,0 +1,174 @@ +// RUN: %check_clang_tidy %s readability-static-method %t + +class DoNotMakeEmptyStatic { + void emptyMethod() {} + void empty_method_out_of_line(); +}; + +void DoNotMakeEmptyStatic::empty_method_out_of_line() {} + +class A { + int field; + const int const_field; + static int static_field; + + void no_use() { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'no_use' can be made static + // CHECK-FIXES: {{^}} static void no_use() { + int i = 1; + } + + int read_field() { + return field; + } + + void write_field() { + field = 1; + } + + int call_non_const_member() { return read_field(); } + + int call_static_member() { + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: method 'call_static_member' can be made static + // CHECK-FIXES: {{^}} static int call_static_member() { + already_static(); + } + + int read_static() { + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: method 'read_static' can be made static + // CHECK-FIXES: {{^}} static int read_static() { + return static_field; + } + void write_static() { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'write_static' can be made static + // CHECK-FIXES: {{^}} static void write_static() { + static_field = 1; + } + + static int already_static() { return static_field; } + + int already_const() const { return field; } + + int already_const_convert_to_static() const { + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: method 'already_const_convert_to_static' can be made static + // CHECK-FIXES: {{^}} static int already_const_convert_to_static() { + return static_field; + } + + static int out_of_line_already_static(); + + void out_of_line_call_static(); + // CHECK-FIXES: {{^}} static void out_of_line_call_static(); + int out_of_line_const_to_static() const; + // CHECK-FIXES: {{^}} static int out_of_line_const_to_static() ; +}; + +int A::out_of_line_already_static() { return 0; } + +void A::out_of_line_call_static() { + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: method 'out_of_line_call_static' can be made static + // CHECK-FIXES: {{^}}void A::out_of_line_call_static() { + already_static(); +} + +int A::out_of_line_const_to_static() const { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'out_of_line_const_to_static' can be made static + // CHECK-FIXES: {{^}}int A::out_of_line_const_to_static() { + return 0; +} + +struct KeepVirtual { + virtual int f() { return 0; } + virtual int h() const { return 0; } +}; + +struct KeepVirtualDerived : public KeepVirtual { + int f() { return 0; } + int h() const override { return 0; } +}; + +// Don't add 'static' to special member functions and operators. +struct KeepSpecial { + KeepSpecial() { int L = 0; } + ~KeepSpecial() { int L = 0; } + int operator+() { return 0; } + operator int() { return 0; } +}; + +void KeepLambdas() { + auto F = +[]() { return 0; }; + auto F2 = []() { return 0; }; +} + +template +struct KeepWithTemplateBase : public Base { + int i; + // We cannot make these methods static because they might need to override + // a function from Base. + int static_f() { return 0; } +}; + +template +struct KeepTemplateClass { + int i; + // We cannot make these methods static because a specialization + // might use *this differently. + int static_f() { return 0; } +}; + +struct KeepTemplateMethod { + int i; + // We cannot make these methods static because a specialization + // might use *this differently. + template + static int static_f() { return 0; } +}; + +void instantiate() { + struct S {}; + KeepWithTemplateBase I1; + I1.static_f(); + + KeepTemplateClass I2; + I2.static_f(); + + KeepTemplateMethod I3; + I3.static_f(); +} + +struct TrailingReturn { + auto g() const -> int { + // CHECK-MESSAGES: :[[@LINE-1]]:8: warning: method 'g' can be made static + // CHECK-FIXES: {{^}} static auto g() -> int { + return 0; + } +}; + +struct NoFixitInMacro { +#define CONST const + int no_use_macro_const() CONST { + // CHECK-MESSAGES: :[[@LINE-1]]:7: warning: method 'no_use_macro_const' can be made static + return 0; + } + +#define ADD_CONST(F) F const + int ADD_CONST(no_use_macro2()) { + return 0; + } + +#define FUN no_use_macro() + int i; + int FUN { + return i; + } + +#define T(FunctionName, Keyword) \ + Keyword int FunctionName() { return 0; } +#define EMPTY + T(A, EMPTY) + T(B, static) + +#define T2(FunctionName) \ + int FunctionName() { return 0; } + T2(A2) +};