diff options
Diffstat (limited to 'clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStaticCheck.cpp')
| -rw-r--r-- | clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStaticCheck.cpp | 179 |
1 files changed, 179 insertions, 0 deletions
diff --git a/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStaticCheck.cpp b/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStaticCheck.cpp new file mode 100644 index 0000000..e6276e3 --- /dev/null +++ b/clang-tools-extra/clang-tidy/readability/ConvertMemberFunctionsToStaticCheck.cpp @@ -0,0 +1,179 @@ +//===----------------------------------------------------------------------===// +// +// 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 "ConvertMemberFunctionsToStaticCheck.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" +#include "clang/Lex/Lexer.h" + +using namespace clang::ast_matchers; + +namespace clang::tidy::readability { + +namespace { + +AST_MATCHER(CXXMethodDecl, isStatic) { return Node.isStatic(); } + +AST_MATCHER(CXXMethodDecl, hasTrivialBody) { return Node.hasTrivialBody(); } + +AST_MATCHER(CXXMethodDecl, isOverloadedOperator) { + return Node.isOverloadedOperator(); +} + +AST_MATCHER(CXXRecordDecl, hasAnyDependentBases) { + return Node.hasAnyDependentBases(); +} + +AST_MATCHER(CXXMethodDecl, isTemplate) { + return Node.getTemplatedKind() != FunctionDecl::TK_NonTemplate; +} + +AST_MATCHER(CXXMethodDecl, isDependentContext) { + return Node.isDependentContext(); +} + +AST_MATCHER(CXXMethodDecl, isInsideMacroDefinition) { + const ASTContext &Ctxt = Finder->getASTContext(); + return clang::Lexer::makeFileCharRange( + clang::CharSourceRange::getCharRange( + Node.getTypeSourceInfo()->getTypeLoc().getSourceRange()), + Ctxt.getSourceManager(), Ctxt.getLangOpts()) + .isInvalid(); +} + +AST_MATCHER_P(CXXMethodDecl, hasCanonicalDecl, + ast_matchers::internal::Matcher<CXXMethodDecl>, InnerMatcher) { + return InnerMatcher.matches(*Node.getCanonicalDecl(), Finder, Builder); +} + +AST_MATCHER(CXXMethodDecl, usesThis) { + class FindUsageOfThis : public RecursiveASTVisitor<FindUsageOfThis> { + public: + bool Used = false; + + bool VisitCXXThisExpr(const CXXThisExpr *E) { + Used = true; + return false; // Stop traversal. + } + + // If we enter a class declaration, don't traverse into it as any usages of + // `this` will correspond to the nested class. + bool TraverseCXXRecordDecl(CXXRecordDecl *RD) { return true; } + + } UsageOfThis; + + // TraverseStmt does not modify its argument. + UsageOfThis.TraverseStmt(Node.getBody()); + + return UsageOfThis.Used; +} + +} // namespace + +void ConvertMemberFunctionsToStaticCheck::registerMatchers( + MatchFinder *Finder) { + Finder->addMatcher( + cxxMethodDecl( + isDefinition(), isUserProvided(), + unless(anyOf( + isExpansionInSystemHeader(), isVirtual(), isStatic(), + hasTrivialBody(), isOverloadedOperator(), cxxConstructorDecl(), + cxxDestructorDecl(), cxxConversionDecl(), + isExplicitObjectMemberFunction(), isTemplate(), + isDependentContext(), + ofClass(anyOf( + isLambda(), + hasAnyDependentBases()) // Method might become virtual + // depending on template base class. + ), + isInsideMacroDefinition(), + hasCanonicalDecl(isInsideMacroDefinition()), usesThis()))) + .bind("x"), + this); +} + +/// 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) { + assert(TSI); + const auto FTL = TSI->getTypeLoc().IgnoreParens().getAs<FunctionTypeLoc>(); + assert(FTL); + + const SourceRange Range{FTL.getRParenLoc().getLocWithOffset(1), + FTL.getLocalRangeEnd()}; + // Inside Range, there might be other keywords and trailing return types. + // Find the exact position of "const". + const StringRef Text = getStringFromRange(SourceMgr, LangOpts, Range); + const size_t Offset = Text.find("const"); + if (Offset == StringRef::npos) + return {}; + + const SourceLocation Start = Range.getBegin().getLocWithOffset(Offset); + return {Start, Start.getLocWithOffset(strlen("const") - 1)}; +} + +void ConvertMemberFunctionsToStaticCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Definition = Result.Nodes.getNodeAs<CXXMethodDecl>("x"); + + // TODO: For out-of-line declarations, don't modify the source if the header + // is excluded by the -header-filter option. + const DiagnosticBuilder Diag = + diag(Definition->getLocation(), "method %0 can be made static") + << Definition; + + // TODO: Would need to remove those in a fix-it. + if (Definition->getMethodQualifiers().hasVolatile() || + Definition->getMethodQualifiers().hasRestrict() || + Definition->getRefQualifier() != RQ_None) + return; + + const CXXMethodDecl *Declaration = Definition->getCanonicalDecl(); + + if (Definition->isConst()) { + // Make sure that we either remove 'const' on both declaration and + // definition or emit no fix-it at all. + const SourceRange DefConst = getLocationOfConst( + Definition->getTypeSourceInfo(), *Result.SourceManager, + Result.Context->getLangOpts()); + + if (DefConst.isInvalid()) + return; + + if (Declaration != Definition) { + const 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 clang::tidy::readability |
