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