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 DifferingParamsContainer 94 findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration, 95 const FunctionDecl *OtherDeclaration, 96 const FunctionDecl *OriginalDeclaration) { 97 DifferingParamsContainer DifferingParams; 98 99 auto SourceParamIt = ParameterSourceDeclaration->param_begin(); 100 auto OtherParamIt = OtherDeclaration->param_begin(); 101 102 while (SourceParamIt != ParameterSourceDeclaration->param_end() && 103 OtherParamIt != OtherDeclaration->param_end()) { 104 auto SourceParamName = (*SourceParamIt)->getName(); 105 auto OtherParamName = (*OtherParamIt)->getName(); 106 107 // FIXME: Provide a way to extract commented out parameter name from comment 108 // next to it. 109 if (!SourceParamName.empty() && !OtherParamName.empty() && 110 SourceParamName != OtherParamName) { 111 SourceRange OtherParamNameRange = 112 DeclarationNameInfo((*OtherParamIt)->getDeclName(), 113 (*OtherParamIt)->getLocation()) 114 .getSourceRange(); 115 116 bool GenerateFixItHint = checkIfFixItHintIsApplicable( 117 ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration); 118 119 DifferingParams.emplace_back(SourceParamName, OtherParamName, 120 OtherParamNameRange, GenerateFixItHint); 121 } 122 123 ++SourceParamIt; 124 ++OtherParamIt; 125 } 126 127 return DifferingParams; 128 } 129 130 InconsistentDeclarationsContainer 131 findInconsitentDeclarations(const FunctionDecl *OriginalDeclaration, 132 const FunctionDecl *ParameterSourceDeclaration, 133 SourceManager &SM) { 134 InconsistentDeclarationsContainer InconsistentDeclarations; 135 SourceLocation ParameterSourceLocation = 136 ParameterSourceDeclaration->getLocation(); 137 138 for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { 139 SourceLocation OtherLocation = OtherDeclaration->getLocation(); 140 if (OtherLocation != ParameterSourceLocation) { // Skip self. 141 DifferingParamsContainer DifferingParams = 142 findDifferingParamsInDeclaration(ParameterSourceDeclaration, 143 OtherDeclaration, 144 OriginalDeclaration); 145 if (!DifferingParams.empty()) { 146 InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(), 147 std::move(DifferingParams)); 148 } 149 } 150 } 151 152 // Sort in order of appearance in translation unit to generate clear 153 // diagnostics. 154 std::sort(InconsistentDeclarations.begin(), InconsistentDeclarations.end(), 155 [&SM](const InconsistentDeclarationInfo &Info1, 156 const InconsistentDeclarationInfo &Info2) { 157 return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation, 158 Info2.DeclarationLocation); 159 }); 160 return InconsistentDeclarations; 161 } 162 163 const FunctionDecl * 164 getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) { 165 const FunctionTemplateDecl *PrimaryTemplate = 166 OriginalDeclaration->getPrimaryTemplate(); 167 if (PrimaryTemplate != nullptr) { 168 // In case of template specializations, use primary template declaration as 169 // the source of parameter names. 170 return PrimaryTemplate->getTemplatedDecl(); 171 } 172 173 // In other cases, try to change to function definition, if available. 174 175 if (OriginalDeclaration->isThisDeclarationADefinition()) 176 return OriginalDeclaration; 177 178 for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) { 179 if (OtherDeclaration->isThisDeclarationADefinition()) { 180 return OtherDeclaration; 181 } 182 } 183 184 // No definition found, so return original declaration. 185 return OriginalDeclaration; 186 } 187 188 std::string joinParameterNames( 189 const DifferingParamsContainer &DifferingParams, 190 llvm::function_ref<StringRef(const DifferingParamInfo &)> ChooseParamName) { 191 llvm::SmallVector<char, 40> Buffer; 192 llvm::raw_svector_ostream Str(Buffer); 193 bool First = true; 194 for (const DifferingParamInfo &ParamInfo : DifferingParams) { 195 if (First) 196 First = false; 197 else 198 Str << ", "; 199 200 Str << "'" << ChooseParamName(ParamInfo).str() << "'"; 201 } 202 return Str.str().str(); 203 } 204 205 void formatDifferingParamsDiagnostic( 206 InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location, 207 StringRef OtherDeclarationDescription, 208 const DifferingParamsContainer &DifferingParams) { 209 auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) { 210 return ParamInfo.OtherName; 211 }; 212 auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) { 213 return ParamInfo.SourceName; 214 }; 215 216 auto ParamDiag = 217 Check->diag(Location, 218 "differing parameters are named here: (%0), in %1: (%2)", 219 DiagnosticIDs::Level::Note) 220 << joinParameterNames(DifferingParams, ChooseOtherName) 221 << OtherDeclarationDescription 222 << joinParameterNames(DifferingParams, ChooseSourceName); 223 224 for (const DifferingParamInfo &ParamInfo : DifferingParams) { 225 if (ParamInfo.GenerateFixItHint) { 226 ParamDiag << FixItHint::CreateReplacement( 227 CharSourceRange::getTokenRange(ParamInfo.OtherNameRange), 228 ParamInfo.SourceName); 229 } 230 } 231 } 232 233 void formatDiagnosticsForDeclarations( 234 InconsistentDeclarationParameterNameCheck *Check, 235 const FunctionDecl *ParameterSourceDeclaration, 236 const FunctionDecl *OriginalDeclaration, 237 const InconsistentDeclarationsContainer &InconsistentDeclarations) { 238 Check->diag( 239 OriginalDeclaration->getLocation(), 240 "function %q0 has %1 other declaration%s1 with different parameter names") 241 << OriginalDeclaration 242 << static_cast<int>(InconsistentDeclarations.size()); 243 int Count = 1; 244 for (const InconsistentDeclarationInfo &InconsistentDeclaration : 245 InconsistentDeclarations) { 246 Check->diag(InconsistentDeclaration.DeclarationLocation, 247 "the %ordinal0 inconsistent declaration seen here", 248 DiagnosticIDs::Level::Note) 249 << Count; 250 251 formatDifferingParamsDiagnostic( 252 Check, InconsistentDeclaration.DeclarationLocation, 253 "the other declaration", InconsistentDeclaration.DifferingParams); 254 255 ++Count; 256 } 257 } 258 259 void formatDiagnostics( 260 InconsistentDeclarationParameterNameCheck *Check, 261 const FunctionDecl *ParameterSourceDeclaration, 262 const FunctionDecl *OriginalDeclaration, 263 const InconsistentDeclarationsContainer &InconsistentDeclarations, 264 StringRef FunctionDescription, StringRef ParameterSourceDescription) { 265 for (const InconsistentDeclarationInfo &InconsistentDeclaration : 266 InconsistentDeclarations) { 267 Check->diag(InconsistentDeclaration.DeclarationLocation, 268 "%0 %q1 has a %2 with different parameter names") 269 << FunctionDescription << OriginalDeclaration 270 << ParameterSourceDescription; 271 272 Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here", 273 DiagnosticIDs::Level::Note) 274 << ParameterSourceDescription; 275 276 formatDifferingParamsDiagnostic( 277 Check, InconsistentDeclaration.DeclarationLocation, 278 ParameterSourceDescription, InconsistentDeclaration.DifferingParams); 279 } 280 } 281 282 } // anonymous namespace 283 284 void InconsistentDeclarationParameterNameCheck::registerMatchers( 285 MatchFinder *Finder) { 286 Finder->addMatcher(functionDecl(unless(isImplicit()), hasOtherDeclarations()) 287 .bind("functionDecl"), 288 this); 289 } 290 291 void InconsistentDeclarationParameterNameCheck::check( 292 const MatchFinder::MatchResult &Result) { 293 const auto *OriginalDeclaration = 294 Result.Nodes.getNodeAs<FunctionDecl>("functionDecl"); 295 296 if (VisitedDeclarations.count(OriginalDeclaration) > 0) 297 return; // Avoid multiple warnings. 298 299 const FunctionDecl *ParameterSourceDeclaration = 300 getParameterSourceDeclaration(OriginalDeclaration); 301 302 InconsistentDeclarationsContainer InconsistentDeclarations = 303 findInconsitentDeclarations(OriginalDeclaration, 304 ParameterSourceDeclaration, 305 *Result.SourceManager); 306 if (InconsistentDeclarations.empty()) { 307 // Avoid unnecessary further visits. 308 markRedeclarationsAsVisited(OriginalDeclaration); 309 return; 310 } 311 312 if (OriginalDeclaration->getTemplatedKind() == 313 FunctionDecl::TK_FunctionTemplateSpecialization) { 314 formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, 315 InconsistentDeclarations, 316 "function template specialization", 317 "primary template declaration"); 318 } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) { 319 formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration, 320 InconsistentDeclarations, "function", "definition"); 321 } else { 322 formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration, 323 OriginalDeclaration, 324 InconsistentDeclarations); 325 } 326 327 markRedeclarationsAsVisited(OriginalDeclaration); 328 } 329 330 void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited( 331 const FunctionDecl *OriginalDeclaration) { 332 for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) { 333 VisitedDeclarations.insert(Redecl); 334 } 335 } 336 337 } // namespace readability 338 } // namespace tidy 339 } // namespace clang 340