348 lines · cpp
1//===----------------------------------------------------------------------===//2//3// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.4// See https://llvm.org/LICENSE.txt for license information.5// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception6//7//===----------------------------------------------------------------------===//8 9#include "InconsistentDeclarationParameterNameCheck.h"10#include "clang/ASTMatchers/ASTMatchFinder.h"11#include "llvm/ADT/STLExtras.h"12 13using namespace clang::ast_matchers;14 15namespace clang::tidy::readability {16 17namespace {18 19AST_MATCHER(FunctionDecl, hasOtherDeclarations) {20 auto It = Node.redecls_begin();21 auto EndIt = Node.redecls_end();22 23 if (It == EndIt)24 return false;25 26 ++It;27 return It != EndIt;28}29 30struct DifferingParamInfo {31 DifferingParamInfo(StringRef SourceName, StringRef OtherName,32 SourceRange OtherNameRange, bool GenerateFixItHint)33 : SourceName(SourceName), OtherName(OtherName),34 OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {}35 36 StringRef SourceName;37 StringRef OtherName;38 SourceRange OtherNameRange;39 bool GenerateFixItHint;40};41 42using DifferingParamsContainer = llvm::SmallVector<DifferingParamInfo, 10>;43 44struct InconsistentDeclarationInfo {45 InconsistentDeclarationInfo(SourceLocation DeclarationLocation,46 DifferingParamsContainer &&DifferingParams)47 : DeclarationLocation(DeclarationLocation),48 DifferingParams(std::move(DifferingParams)) {}49 50 SourceLocation DeclarationLocation;51 DifferingParamsContainer DifferingParams;52};53 54using InconsistentDeclarationsContainer =55 llvm::SmallVector<InconsistentDeclarationInfo, 2>;56 57} // namespace58 59static bool60checkIfFixItHintIsApplicable(const FunctionDecl *ParameterSourceDeclaration,61 const ParmVarDecl *SourceParam,62 const FunctionDecl *OriginalDeclaration) {63 // Assumptions with regard to function declarations/definition:64 // * If both function declaration and definition are seen, assume that65 // definition is most up-to-date, and use it to generate replacements.66 // * If only function declarations are seen, there is no easy way to tell67 // which is up-to-date and which is not, so don't do anything.68 // TODO: This may be changed later, but for now it seems the reasonable69 // solution.70 if (!ParameterSourceDeclaration->isThisDeclarationADefinition())71 return false;72 73 // Assumption: if parameter is not referenced in function definition body, it74 // may indicate that it's outdated, so don't touch it.75 if (!SourceParam->isReferenced())76 return false;77 78 // In case there is the primary template definition and (possibly several)79 // template specializations (and each with possibly several redeclarations),80 // it is not at all clear what to change.81 if (OriginalDeclaration->getTemplatedKind() ==82 FunctionDecl::TK_FunctionTemplateSpecialization)83 return false;84 85 // Other cases seem OK to allow replacements.86 return true;87}88 89static bool nameMatch(StringRef L, StringRef R, bool Strict) {90 if (Strict)91 return L.empty() || R.empty() || L == R;92 // We allow two names if one is a prefix/suffix of the other, ignoring case.93 // Important special case: this is true if either parameter has no name!94 return L.starts_with_insensitive(R) || R.starts_with_insensitive(L) ||95 L.ends_with_insensitive(R) || R.ends_with_insensitive(L);96}97 98static DifferingParamsContainer99findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration,100 const FunctionDecl *OtherDeclaration,101 const FunctionDecl *OriginalDeclaration,102 bool Strict) {103 DifferingParamsContainer DifferingParams;104 105 const auto *SourceParamIt = ParameterSourceDeclaration->param_begin();106 const auto *OtherParamIt = OtherDeclaration->param_begin();107 108 while (SourceParamIt != ParameterSourceDeclaration->param_end() &&109 OtherParamIt != OtherDeclaration->param_end()) {110 auto SourceParamName = (*SourceParamIt)->getName();111 auto OtherParamName = (*OtherParamIt)->getName();112 113 // FIXME: Provide a way to extract commented out parameter name from comment114 // next to it.115 if (!nameMatch(SourceParamName, OtherParamName, Strict)) {116 const SourceRange OtherParamNameRange =117 DeclarationNameInfo((*OtherParamIt)->getDeclName(),118 (*OtherParamIt)->getLocation())119 .getSourceRange();120 121 const bool GenerateFixItHint = checkIfFixItHintIsApplicable(122 ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration);123 124 DifferingParams.emplace_back(SourceParamName, OtherParamName,125 OtherParamNameRange, GenerateFixItHint);126 }127 128 ++SourceParamIt;129 ++OtherParamIt;130 }131 132 return DifferingParams;133}134 135static InconsistentDeclarationsContainer136findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration,137 const FunctionDecl *ParameterSourceDeclaration,138 SourceManager &SM, bool Strict) {139 InconsistentDeclarationsContainer InconsistentDeclarations;140 const SourceLocation ParameterSourceLocation =141 ParameterSourceDeclaration->getLocation();142 143 for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {144 const SourceLocation OtherLocation = OtherDeclaration->getLocation();145 if (OtherLocation != ParameterSourceLocation) { // Skip self.146 DifferingParamsContainer DifferingParams =147 findDifferingParamsInDeclaration(ParameterSourceDeclaration,148 OtherDeclaration,149 OriginalDeclaration, Strict);150 if (!DifferingParams.empty()) {151 InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(),152 std::move(DifferingParams));153 }154 }155 }156 157 // Sort in order of appearance in translation unit to generate clear158 // diagnostics.159 llvm::sort(InconsistentDeclarations,160 [&SM](const InconsistentDeclarationInfo &Info1,161 const InconsistentDeclarationInfo &Info2) {162 return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation,163 Info2.DeclarationLocation);164 });165 return InconsistentDeclarations;166}167 168static const FunctionDecl *169getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) {170 const FunctionTemplateDecl *PrimaryTemplate =171 OriginalDeclaration->getPrimaryTemplate();172 if (PrimaryTemplate != nullptr) {173 // In case of template specializations, use primary template declaration as174 // the source of parameter names.175 return PrimaryTemplate->getTemplatedDecl();176 }177 178 // In other cases, try to change to function definition, if available.179 180 if (OriginalDeclaration->isThisDeclarationADefinition())181 return OriginalDeclaration;182 183 for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {184 if (OtherDeclaration->isThisDeclarationADefinition()) {185 return OtherDeclaration;186 }187 }188 189 // No definition found, so return original declaration.190 return OriginalDeclaration;191}192 193static std::string joinParameterNames(194 const DifferingParamsContainer &DifferingParams,195 llvm::function_ref<StringRef(const DifferingParamInfo &)> ChooseParamName) {196 llvm::SmallString<40> Str;197 bool First = true;198 for (const DifferingParamInfo &ParamInfo : DifferingParams) {199 if (First)200 First = false;201 else202 Str += ", ";203 Str.append({"'", ChooseParamName(ParamInfo), "'"});204 }205 return std::string(Str);206}207 208static void formatDifferingParamsDiagnostic(209 InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location,210 StringRef OtherDeclarationDescription,211 const DifferingParamsContainer &DifferingParams) {212 auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) {213 return ParamInfo.OtherName;214 };215 auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) {216 return ParamInfo.SourceName;217 };218 219 auto ParamDiag =220 Check->diag(Location,221 "differing parameters are named here: (%0), in %1: (%2)",222 DiagnosticIDs::Level::Note)223 << joinParameterNames(DifferingParams, ChooseOtherName)224 << OtherDeclarationDescription225 << joinParameterNames(DifferingParams, ChooseSourceName);226 227 for (const DifferingParamInfo &ParamInfo : DifferingParams) {228 if (ParamInfo.GenerateFixItHint) {229 ParamDiag << FixItHint::CreateReplacement(230 CharSourceRange::getTokenRange(ParamInfo.OtherNameRange),231 ParamInfo.SourceName);232 }233 }234}235 236static void formatDiagnosticsForDeclarations(237 InconsistentDeclarationParameterNameCheck *Check,238 const FunctionDecl *ParameterSourceDeclaration,239 const FunctionDecl *OriginalDeclaration,240 const InconsistentDeclarationsContainer &InconsistentDeclarations) {241 Check->diag(242 OriginalDeclaration->getLocation(),243 "function %q0 has %1 other declaration%s1 with different parameter names")244 << OriginalDeclaration245 << static_cast<int>(InconsistentDeclarations.size());246 int Count = 1;247 for (const InconsistentDeclarationInfo &InconsistentDeclaration :248 InconsistentDeclarations) {249 Check->diag(InconsistentDeclaration.DeclarationLocation,250 "the %ordinal0 inconsistent declaration seen here",251 DiagnosticIDs::Level::Note)252 << Count;253 254 formatDifferingParamsDiagnostic(255 Check, InconsistentDeclaration.DeclarationLocation,256 "the other declaration", InconsistentDeclaration.DifferingParams);257 258 ++Count;259 }260}261 262static void formatDiagnostics(263 InconsistentDeclarationParameterNameCheck *Check,264 const FunctionDecl *ParameterSourceDeclaration,265 const FunctionDecl *OriginalDeclaration,266 const InconsistentDeclarationsContainer &InconsistentDeclarations,267 StringRef FunctionDescription, StringRef ParameterSourceDescription) {268 for (const InconsistentDeclarationInfo &InconsistentDeclaration :269 InconsistentDeclarations) {270 Check->diag(InconsistentDeclaration.DeclarationLocation,271 "%0 %q1 has a %2 with different parameter names")272 << FunctionDescription << OriginalDeclaration273 << ParameterSourceDescription;274 275 Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here",276 DiagnosticIDs::Level::Note)277 << ParameterSourceDescription;278 279 formatDifferingParamsDiagnostic(280 Check, InconsistentDeclaration.DeclarationLocation,281 ParameterSourceDescription, InconsistentDeclaration.DifferingParams);282 }283}284 285void InconsistentDeclarationParameterNameCheck::storeOptions(286 ClangTidyOptions::OptionMap &Opts) {287 Options.store(Opts, "IgnoreMacros", IgnoreMacros);288 Options.store(Opts, "Strict", Strict);289}290 291void InconsistentDeclarationParameterNameCheck::registerMatchers(292 MatchFinder *Finder) {293 Finder->addMatcher(functionDecl(hasOtherDeclarations()).bind("functionDecl"),294 this);295}296 297void InconsistentDeclarationParameterNameCheck::check(298 const MatchFinder::MatchResult &Result) {299 const auto *OriginalDeclaration =300 Result.Nodes.getNodeAs<FunctionDecl>("functionDecl");301 302 if (VisitedDeclarations.contains(OriginalDeclaration))303 return; // Avoid multiple warnings.304 305 const FunctionDecl *ParameterSourceDeclaration =306 getParameterSourceDeclaration(OriginalDeclaration);307 308 const InconsistentDeclarationsContainer InconsistentDeclarations =309 findInconsistentDeclarations(OriginalDeclaration,310 ParameterSourceDeclaration,311 *Result.SourceManager, Strict);312 if (InconsistentDeclarations.empty()) {313 // Avoid unnecessary further visits.314 markRedeclarationsAsVisited(OriginalDeclaration);315 return;316 }317 318 const SourceLocation StartLoc = OriginalDeclaration->getBeginLoc();319 if (StartLoc.isMacroID() && IgnoreMacros) {320 markRedeclarationsAsVisited(OriginalDeclaration);321 return;322 }323 324 if (OriginalDeclaration->getTemplatedKind() ==325 FunctionDecl::TK_FunctionTemplateSpecialization) {326 formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,327 InconsistentDeclarations,328 "function template specialization",329 "primary template declaration");330 } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) {331 formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,332 InconsistentDeclarations, "function", "definition");333 } else {334 formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration,335 OriginalDeclaration,336 InconsistentDeclarations);337 }338 339 markRedeclarationsAsVisited(OriginalDeclaration);340}341 342void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited(343 const FunctionDecl *OriginalDeclaration) {344 VisitedDeclarations.insert_range(OriginalDeclaration->redecls());345}346 347} // namespace clang::tidy::readability348