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