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