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 defintion 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::SmallVector<char, 40> Buffer; 200 llvm::raw_svector_ostream Str(Buffer); 201 bool First = true; 202 for (const DifferingParamInfo &ParamInfo : DifferingParams) { 203 if (First) 204 First = false; 205 else 206 Str << ", "; 207 208 Str << "'" << ChooseParamName(ParamInfo).str() << "'"; 209 } 210 return Str.str().str(); 211 } 212 213 void formatDifferingParamsDiagnostic( 214 InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location, 215 StringRef OtherDeclarationDescription, 216 const DifferingParamsContainer &DifferingParams) { 217 auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) { 218 return ParamInfo.OtherName; 219 }; 220 auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) { 221 return ParamInfo.SourceName; 222 }; 223 224 auto ParamDiag = 225 Check->diag(Location, 226 "differing parameters are named here: (%0), in %1: (%2)", 227 DiagnosticIDs::Level::Note) 228 << joinParameterNames(DifferingParams, ChooseOtherName) 229 << OtherDeclarationDescription 230 << joinParameterNames(DifferingParams, ChooseSourceName); 231 232 for (const DifferingParamInfo &ParamInfo : DifferingParams) { 233 if (ParamInfo.GenerateFixItHint) { 234 ParamDiag << FixItHint::CreateReplacement( 235 CharSourceRange::getTokenRange(ParamInfo.OtherNameRange), 236 ParamInfo.SourceName); 237 } 238 } 239 } 240 241 void formatDiagnosticsForDeclarations( 242 InconsistentDeclarationParameterNameCheck *Check, 243 const FunctionDecl *ParameterSourceDeclaration, 244 const FunctionDecl *OriginalDeclaration, 245 const InconsistentDeclarationsContainer &InconsistentDeclarations) { 246 Check->diag( 247 OriginalDeclaration->getLocation(), 248 "function %q0 has %1 other declaration%s1 with different parameter names") 249 << OriginalDeclaration 250 << static_cast<int>(InconsistentDeclarations.size()); 251 int Count = 1; 252 for (const InconsistentDeclarationInfo &InconsistentDeclaration : 253 InconsistentDeclarations) { 254 Check->diag(InconsistentDeclaration.DeclarationLocation, 255 "the %ordinal0 inconsistent declaration seen here", 256 DiagnosticIDs::Level::Note) 257 << Count; 258 259 formatDifferingParamsDiagnostic( 260 Check, InconsistentDeclaration.DeclarationLocation, 261 "the other declaration", InconsistentDeclaration.DifferingParams); 262 263 ++Count; 264 } 265 } 266 267 void formatDiagnostics( 268 InconsistentDeclarationParameterNameCheck *Check, 269 const FunctionDecl *ParameterSourceDeclaration, 270 const FunctionDecl *OriginalDeclaration, 271 const InconsistentDeclarationsContainer &InconsistentDeclarations, 272 StringRef FunctionDescription, StringRef ParameterSourceDescription) { 273 for (const InconsistentDeclarationInfo &InconsistentDeclaration : 274 InconsistentDeclarations) { 275 Check->diag(InconsistentDeclaration.DeclarationLocation, 276 "%0 %q1 has a %2 with different parameter names") 277 << FunctionDescription << OriginalDeclaration 278 << ParameterSourceDescription; 279 280 Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here", 281 DiagnosticIDs::Level::Note) 282 << ParameterSourceDescription; 283 284 formatDifferingParamsDiagnostic( 285 Check, InconsistentDeclaration.DeclarationLocation, 286 ParameterSourceDescription, InconsistentDeclaration.DifferingParams); 287 } 288 } 289 290 } // anonymous namespace 291 292 void InconsistentDeclarationParameterNameCheck::storeOptions( 293 ClangTidyOptions::OptionMap &Opts) { 294 Options.store(Opts, "IgnoreMacros", IgnoreMacros); 295 Options.store(Opts, "Strict", Strict); 296 } 297 298 void InconsistentDeclarationParameterNameCheck::registerMatchers( 299 MatchFinder *Finder) { 300 Finder->addMatcher(functionDecl(unless(isImplicit()), hasOtherDeclarations()) 301 .bind("functionDecl"), 302 this); 303 } 304 305 void InconsistentDeclarationParameterNameCheck::check( 306 const MatchFinder::MatchResult &Result) { 307 const auto *OriginalDeclaration = 308 Result.Nodes.getNodeAs<FunctionDecl>("functionDecl"); 309 310 if (VisitedDeclarations.count(OriginalDeclaration) > 0) 311 return; // Avoid multiple warnings. 312 313 const FunctionDecl *ParameterSourceDeclaration = 314 getParameterSourceDeclaration(OriginalDeclaration); 315 316 InconsistentDeclarationsContainer InconsistentDeclarations = 317 findInconsistentDeclarations(OriginalDeclaration, 318 ParameterSourceDeclaration, 319 *Result.SourceManager, Strict); 320 if (InconsistentDeclarations.empty()) { 321 // Avoid unnecessary further visits. 322 markRedeclarationsAsVisited(OriginalDeclaration); 323 return; 324 } 325 326 SourceLocation StartLoc = OriginalDeclaration->getBeginLoc(); 327 if (StartLoc.isMacroID() && IgnoreMacros) { 328 markRedeclarationsAsVisited(OriginalDeclaration); 329 return; 330 } 331 332 if (OriginalDeclaration->getTemplatedKind() == 333 FunctionDecl::TK_FunctionTemplateSpecialization) { 334 formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, 335 InconsistentDeclarations, 336 "function template specialization", 337 "primary template declaration"); 338 } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) { 339 formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, 340 InconsistentDeclarations, "function", "definition"); 341 } else { 342 formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration, 343 OriginalDeclaration, 344 InconsistentDeclarations); 345 } 346 347 markRedeclarationsAsVisited(OriginalDeclaration); 348 } 349 350 void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited( 351 const FunctionDecl *OriginalDeclaration) { 352 for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) { 353 VisitedDeclarations.insert(Redecl); 354 } 355 } 356 357 } // namespace readability 358 } // namespace tidy 359 } // namespace clang 360