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