//===--- InconsistentDeclarationParameterNameCheck.cpp - clang-tidy-------===// // // 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 "InconsistentDeclarationParameterNameCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include #include #include using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace readability { namespace { AST_MATCHER(FunctionDecl, hasOtherDeclarations) { auto It = Node.redecls_begin(); auto EndIt = Node.redecls_end(); if (It == EndIt) return false; ++It; return It != EndIt; } struct DifferingParamInfo { DifferingParamInfo(StringRef SourceName, StringRef OtherName, SourceRange OtherNameRange, bool GenerateFixItHint) : SourceName(SourceName), OtherName(OtherName), OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {} StringRef SourceName; StringRef OtherName; SourceRange OtherNameRange; bool GenerateFixItHint; }; using DifferingParamsContainer = llvm::SmallVector; struct InconsistentDeclarationInfo { InconsistentDeclarationInfo(SourceLocation DeclarationLocation, DifferingParamsContainer &&DifferingParams) : DeclarationLocation(DeclarationLocation), DifferingParams(std::move(DifferingParams)) {} SourceLocation DeclarationLocation; DifferingParamsContainer DifferingParams; }; using InconsistentDeclarationsContainer = llvm::SmallVector; bool checkIfFixItHintIsApplicable( const FunctionDecl *ParameterSourceDeclaration, const ParmVarDecl *SourceParam, const FunctionDecl *OriginalDeclaration) { // Assumptions with regard to function declarations/definition: // * If both function declaration and definition are seen, assume that // definition is most up-to-date, and use it to generate replacements. // * If only function declarations are seen, there is no easy way to tell // which is up-to-date and which is not, so don't do anything. // TODO: This may be changed later, but for now it seems the reasonable // solution. if (!ParameterSourceDeclaration->isThisDeclarationADefinition()) return false; // Assumption: if parameter is not referenced in function definition body, it // may indicate that it's outdated, so don't touch it. if (!SourceParam->isReferenced()) return false; // In case there is the primary template definition and (possibly several) // template specializations (and each with possibly several redeclarations), // it is not at all clear what to change. if (OriginalDeclaration->getTemplatedKind() == FunctionDecl::TK_FunctionTemplateSpecialization) return false; // Other cases seem OK to allow replacements. return true; } bool nameMatch(StringRef L, StringRef R, bool Strict) { if (Strict) return L.empty() || R.empty() || L == R; // We allow two names if one is a prefix/suffix of the other, ignoring case. // Important special case: this is true if either parameter has no name! return L.startswith_lower(R) || R.startswith_lower(L) || L.endswith_lower(R) || R.endswith_lower(L); } DifferingParamsContainer findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration, const FunctionDecl *OtherDeclaration, const FunctionDecl *OriginalDeclaration, bool Strict) { DifferingParamsContainer DifferingParams; auto SourceParamIt = ParameterSourceDeclaration->param_begin(); auto OtherParamIt = OtherDeclaration->param_begin(); while (SourceParamIt != ParameterSourceDeclaration->param_end() && OtherParamIt != OtherDeclaration->param_end()) { auto SourceParamName = (*SourceParamIt)->getName(); auto OtherParamName = (*OtherParamIt)->getName(); // FIXME: Provide a way to extract commented out parameter name from comment // next to it. if (!nameMatch(SourceParamName, OtherParamName, Strict)) { SourceRange OtherParamNameRange = DeclarationNameInfo((*OtherParamIt)->getDeclName(), (*OtherParamIt)->getLocation()) .getSourceRange(); bool GenerateFixItHint = checkIfFixItHintIsApplicable( ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration); DifferingParams.emplace_back(SourceParamName, OtherParamName, OtherParamNameRange, GenerateFixItHint); } ++SourceParamIt; ++OtherParamIt; } return DifferingParams; } InconsistentDeclarationsContainer findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration, const FunctionDecl *ParameterSourceDeclaration, SourceManager &SM, bool Strict) { InconsistentDeclarationsContainer InconsistentDeclarations; SourceLocation ParameterSourceLocation = ParameterSourceDeclaration->getLocation(); for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { SourceLocation OtherLocation = OtherDeclaration->getLocation(); if (OtherLocation != ParameterSourceLocation) { // Skip self. DifferingParamsContainer DifferingParams = findDifferingParamsInDeclaration(ParameterSourceDeclaration, OtherDeclaration, OriginalDeclaration, Strict); if (!DifferingParams.empty()) { InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(), std::move(DifferingParams)); } } } // Sort in order of appearance in translation unit to generate clear // diagnostics. std::sort(InconsistentDeclarations.begin(), InconsistentDeclarations.end(), [&SM](const InconsistentDeclarationInfo &Info1, const InconsistentDeclarationInfo &Info2) { return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation, Info2.DeclarationLocation); }); return InconsistentDeclarations; } const FunctionDecl * getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) { const FunctionTemplateDecl *PrimaryTemplate = OriginalDeclaration->getPrimaryTemplate(); if (PrimaryTemplate != nullptr) { // In case of template specializations, use primary template declaration as // the source of parameter names. return PrimaryTemplate->getTemplatedDecl(); } // In other cases, try to change to function definition, if available. if (OriginalDeclaration->isThisDeclarationADefinition()) return OriginalDeclaration; for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { if (OtherDeclaration->isThisDeclarationADefinition()) { return OtherDeclaration; } } // No definition found, so return original declaration. return OriginalDeclaration; } std::string joinParameterNames( const DifferingParamsContainer &DifferingParams, llvm::function_ref ChooseParamName) { llvm::SmallVector Buffer; llvm::raw_svector_ostream Str(Buffer); bool First = true; for (const DifferingParamInfo &ParamInfo : DifferingParams) { if (First) First = false; else Str << ", "; Str << "'" << ChooseParamName(ParamInfo).str() << "'"; } return Str.str().str(); } void formatDifferingParamsDiagnostic( InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location, StringRef OtherDeclarationDescription, const DifferingParamsContainer &DifferingParams) { auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) { return ParamInfo.OtherName; }; auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) { return ParamInfo.SourceName; }; auto ParamDiag = Check->diag(Location, "differing parameters are named here: (%0), in %1: (%2)", DiagnosticIDs::Level::Note) << joinParameterNames(DifferingParams, ChooseOtherName) << OtherDeclarationDescription << joinParameterNames(DifferingParams, ChooseSourceName); for (const DifferingParamInfo &ParamInfo : DifferingParams) { if (ParamInfo.GenerateFixItHint) { ParamDiag << FixItHint::CreateReplacement( CharSourceRange::getTokenRange(ParamInfo.OtherNameRange), ParamInfo.SourceName); } } } void formatDiagnosticsForDeclarations( InconsistentDeclarationParameterNameCheck *Check, const FunctionDecl *ParameterSourceDeclaration, const FunctionDecl *OriginalDeclaration, const InconsistentDeclarationsContainer &InconsistentDeclarations) { Check->diag( OriginalDeclaration->getLocation(), "function %q0 has %1 other declaration%s1 with different parameter names") << OriginalDeclaration << static_cast(InconsistentDeclarations.size()); int Count = 1; for (const InconsistentDeclarationInfo &InconsistentDeclaration : InconsistentDeclarations) { Check->diag(InconsistentDeclaration.DeclarationLocation, "the %ordinal0 inconsistent declaration seen here", DiagnosticIDs::Level::Note) << Count; formatDifferingParamsDiagnostic( Check, InconsistentDeclaration.DeclarationLocation, "the other declaration", InconsistentDeclaration.DifferingParams); ++Count; } } void formatDiagnostics( InconsistentDeclarationParameterNameCheck *Check, const FunctionDecl *ParameterSourceDeclaration, const FunctionDecl *OriginalDeclaration, const InconsistentDeclarationsContainer &InconsistentDeclarations, StringRef FunctionDescription, StringRef ParameterSourceDescription) { for (const InconsistentDeclarationInfo &InconsistentDeclaration : InconsistentDeclarations) { Check->diag(InconsistentDeclaration.DeclarationLocation, "%0 %q1 has a %2 with different parameter names") << FunctionDescription << OriginalDeclaration << ParameterSourceDescription; Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here", DiagnosticIDs::Level::Note) << ParameterSourceDescription; formatDifferingParamsDiagnostic( Check, InconsistentDeclaration.DeclarationLocation, ParameterSourceDescription, InconsistentDeclaration.DifferingParams); } } } // anonymous namespace void InconsistentDeclarationParameterNameCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "IgnoreMacros", IgnoreMacros); Options.store(Opts, "Strict", Strict); } void InconsistentDeclarationParameterNameCheck::registerMatchers( MatchFinder *Finder) { Finder->addMatcher(functionDecl(unless(isImplicit()), hasOtherDeclarations()) .bind("functionDecl"), this); } void InconsistentDeclarationParameterNameCheck::check( const MatchFinder::MatchResult &Result) { const auto *OriginalDeclaration = Result.Nodes.getNodeAs("functionDecl"); if (VisitedDeclarations.count(OriginalDeclaration) > 0) return; // Avoid multiple warnings. const FunctionDecl *ParameterSourceDeclaration = getParameterSourceDeclaration(OriginalDeclaration); InconsistentDeclarationsContainer InconsistentDeclarations = findInconsistentDeclarations(OriginalDeclaration, ParameterSourceDeclaration, *Result.SourceManager, Strict); if (InconsistentDeclarations.empty()) { // Avoid unnecessary further visits. markRedeclarationsAsVisited(OriginalDeclaration); return; } SourceLocation StartLoc = OriginalDeclaration->getBeginLoc(); if (StartLoc.isMacroID() && IgnoreMacros) { markRedeclarationsAsVisited(OriginalDeclaration); return; } if (OriginalDeclaration->getTemplatedKind() == FunctionDecl::TK_FunctionTemplateSpecialization) { formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, InconsistentDeclarations, "function template specialization", "primary template declaration"); } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) { formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, InconsistentDeclarations, "function", "definition"); } else { formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration, OriginalDeclaration, InconsistentDeclarations); } markRedeclarationsAsVisited(OriginalDeclaration); } void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited( const FunctionDecl *OriginalDeclaration) { for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) { VisitedDeclarations.insert(Redecl); } } } // namespace readability } // namespace tidy } // namespace clang