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