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