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