1 //===--- InconsistentDeclarationParameterNameCheck.cpp - clang-tidy-------===//
2 //
3 // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
4 // See https://llvm.org/LICENSE.txt for license information.
5 // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
6 //
7 //===----------------------------------------------------------------------===//
8 
9 #include "InconsistentDeclarationParameterNameCheck.h"
10 #include "clang/AST/ASTContext.h"
11 #include "clang/ASTMatchers/ASTMatchFinder.h"
12 
13 #include <algorithm>
14 #include <functional>
15 #include <sstream>
16 
17 using namespace clang::ast_matchers;
18 
19 namespace clang {
20 namespace tidy {
21 namespace readability {
22 
23 namespace {
24 
AST_MATCHER(FunctionDecl,hasOtherDeclarations)25 AST_MATCHER(FunctionDecl, hasOtherDeclarations) {
26   auto It = Node.redecls_begin();
27   auto EndIt = Node.redecls_end();
28 
29   if (It == EndIt)
30     return false;
31 
32   ++It;
33   return It != EndIt;
34 }
35 
36 struct DifferingParamInfo {
DifferingParamInfoclang::tidy::readability::__anonffe8a80e0111::DifferingParamInfo37   DifferingParamInfo(StringRef SourceName, StringRef OtherName,
38                      SourceRange OtherNameRange, bool GenerateFixItHint)
39       : SourceName(SourceName), OtherName(OtherName),
40         OtherNameRange(OtherNameRange), GenerateFixItHint(GenerateFixItHint) {}
41 
42   StringRef SourceName;
43   StringRef OtherName;
44   SourceRange OtherNameRange;
45   bool GenerateFixItHint;
46 };
47 
48 using DifferingParamsContainer = llvm::SmallVector<DifferingParamInfo, 10>;
49 
50 struct InconsistentDeclarationInfo {
InconsistentDeclarationInfoclang::tidy::readability::__anonffe8a80e0111::InconsistentDeclarationInfo51   InconsistentDeclarationInfo(SourceLocation DeclarationLocation,
52                               DifferingParamsContainer &&DifferingParams)
53       : DeclarationLocation(DeclarationLocation),
54         DifferingParams(std::move(DifferingParams)) {}
55 
56   SourceLocation DeclarationLocation;
57   DifferingParamsContainer DifferingParams;
58 };
59 
60 using InconsistentDeclarationsContainer =
61     llvm::SmallVector<InconsistentDeclarationInfo, 2>;
62 
checkIfFixItHintIsApplicable(const FunctionDecl * ParameterSourceDeclaration,const ParmVarDecl * SourceParam,const FunctionDecl * OriginalDeclaration)63 bool checkIfFixItHintIsApplicable(
64     const FunctionDecl *ParameterSourceDeclaration,
65     const ParmVarDecl *SourceParam, const FunctionDecl *OriginalDeclaration) {
66   // Assumptions with regard to function declarations/definition:
67   //  * If both function declaration and definition are seen, assume that
68   //    definition is most up-to-date, and use it to generate replacements.
69   //  * If only function declarations are seen, there is no easy way to tell
70   //    which is up-to-date and which is not, so don't do anything.
71   // TODO: This may be changed later, but for now it seems the reasonable
72   // solution.
73   if (!ParameterSourceDeclaration->isThisDeclarationADefinition())
74     return false;
75 
76   // Assumption: if parameter is not referenced in function definition body, it
77   // may indicate that it's outdated, so don't touch it.
78   if (!SourceParam->isReferenced())
79     return false;
80 
81   // In case there is the primary template definition and (possibly several)
82   // template specializations (and each with possibly several redeclarations),
83   // it is not at all clear what to change.
84   if (OriginalDeclaration->getTemplatedKind() ==
85       FunctionDecl::TK_FunctionTemplateSpecialization)
86     return false;
87 
88   // Other cases seem OK to allow replacements.
89   return true;
90 }
91 
nameMatch(StringRef L,StringRef R,bool Strict)92 bool nameMatch(StringRef L, StringRef R, bool Strict) {
93   if (Strict)
94     return L.empty() || R.empty() || L == R;
95   // We allow two names if one is a prefix/suffix of the other, ignoring case.
96   // Important special case: this is true if either parameter has no name!
97   return L.startswith_lower(R) || R.startswith_lower(L) ||
98          L.endswith_lower(R) || R.endswith_lower(L);
99 }
100 
101 DifferingParamsContainer
findDifferingParamsInDeclaration(const FunctionDecl * ParameterSourceDeclaration,const FunctionDecl * OtherDeclaration,const FunctionDecl * OriginalDeclaration,bool Strict)102 findDifferingParamsInDeclaration(const FunctionDecl *ParameterSourceDeclaration,
103                                  const FunctionDecl *OtherDeclaration,
104                                  const FunctionDecl *OriginalDeclaration,
105                                  bool Strict) {
106   DifferingParamsContainer DifferingParams;
107 
108   auto SourceParamIt = ParameterSourceDeclaration->param_begin();
109   auto OtherParamIt = OtherDeclaration->param_begin();
110 
111   while (SourceParamIt != ParameterSourceDeclaration->param_end() &&
112          OtherParamIt != OtherDeclaration->param_end()) {
113     auto SourceParamName = (*SourceParamIt)->getName();
114     auto OtherParamName = (*OtherParamIt)->getName();
115 
116     // FIXME: Provide a way to extract commented out parameter name from comment
117     // next to it.
118     if (!nameMatch(SourceParamName, OtherParamName, Strict)) {
119       SourceRange OtherParamNameRange =
120           DeclarationNameInfo((*OtherParamIt)->getDeclName(),
121                               (*OtherParamIt)->getLocation())
122               .getSourceRange();
123 
124       bool GenerateFixItHint = checkIfFixItHintIsApplicable(
125           ParameterSourceDeclaration, *SourceParamIt, OriginalDeclaration);
126 
127       DifferingParams.emplace_back(SourceParamName, OtherParamName,
128                                    OtherParamNameRange, GenerateFixItHint);
129     }
130 
131     ++SourceParamIt;
132     ++OtherParamIt;
133   }
134 
135   return DifferingParams;
136 }
137 
138 InconsistentDeclarationsContainer
findInconsistentDeclarations(const FunctionDecl * OriginalDeclaration,const FunctionDecl * ParameterSourceDeclaration,SourceManager & SM,bool Strict)139 findInconsistentDeclarations(const FunctionDecl *OriginalDeclaration,
140                             const FunctionDecl *ParameterSourceDeclaration,
141                             SourceManager &SM, bool Strict) {
142   InconsistentDeclarationsContainer InconsistentDeclarations;
143   SourceLocation ParameterSourceLocation =
144       ParameterSourceDeclaration->getLocation();
145 
146   for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {
147     SourceLocation OtherLocation = OtherDeclaration->getLocation();
148     if (OtherLocation != ParameterSourceLocation) { // Skip self.
149       DifferingParamsContainer DifferingParams =
150           findDifferingParamsInDeclaration(ParameterSourceDeclaration,
151                                            OtherDeclaration,
152                                            OriginalDeclaration, Strict);
153       if (!DifferingParams.empty()) {
154         InconsistentDeclarations.emplace_back(OtherDeclaration->getLocation(),
155                                               std::move(DifferingParams));
156       }
157     }
158   }
159 
160   // Sort in order of appearance in translation unit to generate clear
161   // diagnostics.
162   std::sort(InconsistentDeclarations.begin(), InconsistentDeclarations.end(),
163             [&SM](const InconsistentDeclarationInfo &Info1,
164                   const InconsistentDeclarationInfo &Info2) {
165               return SM.isBeforeInTranslationUnit(Info1.DeclarationLocation,
166                                                   Info2.DeclarationLocation);
167             });
168   return InconsistentDeclarations;
169 }
170 
171 const FunctionDecl *
getParameterSourceDeclaration(const FunctionDecl * OriginalDeclaration)172 getParameterSourceDeclaration(const FunctionDecl *OriginalDeclaration) {
173   const FunctionTemplateDecl *PrimaryTemplate =
174       OriginalDeclaration->getPrimaryTemplate();
175   if (PrimaryTemplate != nullptr) {
176     // In case of template specializations, use primary template declaration as
177     // the source of parameter names.
178     return PrimaryTemplate->getTemplatedDecl();
179   }
180 
181   // In other cases, try to change to function definition, if available.
182 
183   if (OriginalDeclaration->isThisDeclarationADefinition())
184     return OriginalDeclaration;
185 
186   for (const FunctionDecl *OtherDeclaration : OriginalDeclaration->redecls()) {
187     if (OtherDeclaration->isThisDeclarationADefinition()) {
188       return OtherDeclaration;
189     }
190   }
191 
192   // No definition found, so return original declaration.
193   return OriginalDeclaration;
194 }
195 
joinParameterNames(const DifferingParamsContainer & DifferingParams,llvm::function_ref<StringRef (const DifferingParamInfo &)> ChooseParamName)196 std::string joinParameterNames(
197     const DifferingParamsContainer &DifferingParams,
198     llvm::function_ref<StringRef(const DifferingParamInfo &)> ChooseParamName) {
199   llvm::SmallString<40> Str;
200   bool First = true;
201   for (const DifferingParamInfo &ParamInfo : DifferingParams) {
202     if (First)
203       First = false;
204     else
205       Str += ", ";
206     Str.append({"'", ChooseParamName(ParamInfo), "'"});
207   }
208   return std::string(Str);
209 }
210 
formatDifferingParamsDiagnostic(InconsistentDeclarationParameterNameCheck * Check,SourceLocation Location,StringRef OtherDeclarationDescription,const DifferingParamsContainer & DifferingParams)211 void formatDifferingParamsDiagnostic(
212     InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location,
213     StringRef OtherDeclarationDescription,
214     const DifferingParamsContainer &DifferingParams) {
215   auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) {
216     return ParamInfo.OtherName;
217   };
218   auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) {
219     return ParamInfo.SourceName;
220   };
221 
222   auto ParamDiag =
223       Check->diag(Location,
224                   "differing parameters are named here: (%0), in %1: (%2)",
225                   DiagnosticIDs::Level::Note)
226       << joinParameterNames(DifferingParams, ChooseOtherName)
227       << OtherDeclarationDescription
228       << joinParameterNames(DifferingParams, ChooseSourceName);
229 
230   for (const DifferingParamInfo &ParamInfo : DifferingParams) {
231     if (ParamInfo.GenerateFixItHint) {
232       ParamDiag << FixItHint::CreateReplacement(
233           CharSourceRange::getTokenRange(ParamInfo.OtherNameRange),
234           ParamInfo.SourceName);
235     }
236   }
237 }
238 
formatDiagnosticsForDeclarations(InconsistentDeclarationParameterNameCheck * Check,const FunctionDecl * ParameterSourceDeclaration,const FunctionDecl * OriginalDeclaration,const InconsistentDeclarationsContainer & InconsistentDeclarations)239 void formatDiagnosticsForDeclarations(
240     InconsistentDeclarationParameterNameCheck *Check,
241     const FunctionDecl *ParameterSourceDeclaration,
242     const FunctionDecl *OriginalDeclaration,
243     const InconsistentDeclarationsContainer &InconsistentDeclarations) {
244   Check->diag(
245       OriginalDeclaration->getLocation(),
246       "function %q0 has %1 other declaration%s1 with different parameter names")
247       << OriginalDeclaration
248       << static_cast<int>(InconsistentDeclarations.size());
249   int Count = 1;
250   for (const InconsistentDeclarationInfo &InconsistentDeclaration :
251        InconsistentDeclarations) {
252     Check->diag(InconsistentDeclaration.DeclarationLocation,
253                 "the %ordinal0 inconsistent declaration seen here",
254                 DiagnosticIDs::Level::Note)
255         << Count;
256 
257     formatDifferingParamsDiagnostic(
258         Check, InconsistentDeclaration.DeclarationLocation,
259         "the other declaration", InconsistentDeclaration.DifferingParams);
260 
261     ++Count;
262   }
263 }
264 
formatDiagnostics(InconsistentDeclarationParameterNameCheck * Check,const FunctionDecl * ParameterSourceDeclaration,const FunctionDecl * OriginalDeclaration,const InconsistentDeclarationsContainer & InconsistentDeclarations,StringRef FunctionDescription,StringRef ParameterSourceDescription)265 void formatDiagnostics(
266     InconsistentDeclarationParameterNameCheck *Check,
267     const FunctionDecl *ParameterSourceDeclaration,
268     const FunctionDecl *OriginalDeclaration,
269     const InconsistentDeclarationsContainer &InconsistentDeclarations,
270     StringRef FunctionDescription, StringRef ParameterSourceDescription) {
271   for (const InconsistentDeclarationInfo &InconsistentDeclaration :
272        InconsistentDeclarations) {
273     Check->diag(InconsistentDeclaration.DeclarationLocation,
274                 "%0 %q1 has a %2 with different parameter names")
275         << FunctionDescription << OriginalDeclaration
276         << ParameterSourceDescription;
277 
278     Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here",
279                 DiagnosticIDs::Level::Note)
280         << ParameterSourceDescription;
281 
282     formatDifferingParamsDiagnostic(
283         Check, InconsistentDeclaration.DeclarationLocation,
284         ParameterSourceDescription, InconsistentDeclaration.DifferingParams);
285   }
286 }
287 
288 } // anonymous namespace
289 
storeOptions(ClangTidyOptions::OptionMap & Opts)290 void InconsistentDeclarationParameterNameCheck::storeOptions(
291     ClangTidyOptions::OptionMap &Opts) {
292   Options.store(Opts, "IgnoreMacros", IgnoreMacros);
293   Options.store(Opts, "Strict", Strict);
294 }
295 
registerMatchers(MatchFinder * Finder)296 void InconsistentDeclarationParameterNameCheck::registerMatchers(
297     MatchFinder *Finder) {
298   Finder->addMatcher(functionDecl(unless(isImplicit()), hasOtherDeclarations())
299                          .bind("functionDecl"),
300                      this);
301 }
302 
check(const MatchFinder::MatchResult & Result)303 void InconsistentDeclarationParameterNameCheck::check(
304     const MatchFinder::MatchResult &Result) {
305   const auto *OriginalDeclaration =
306       Result.Nodes.getNodeAs<FunctionDecl>("functionDecl");
307 
308   if (VisitedDeclarations.count(OriginalDeclaration) > 0)
309     return; // Avoid multiple warnings.
310 
311   const FunctionDecl *ParameterSourceDeclaration =
312       getParameterSourceDeclaration(OriginalDeclaration);
313 
314   InconsistentDeclarationsContainer InconsistentDeclarations =
315       findInconsistentDeclarations(OriginalDeclaration,
316                                    ParameterSourceDeclaration,
317                                    *Result.SourceManager, Strict);
318   if (InconsistentDeclarations.empty()) {
319     // Avoid unnecessary further visits.
320     markRedeclarationsAsVisited(OriginalDeclaration);
321     return;
322   }
323 
324   SourceLocation StartLoc = OriginalDeclaration->getBeginLoc();
325   if (StartLoc.isMacroID() && IgnoreMacros) {
326     markRedeclarationsAsVisited(OriginalDeclaration);
327     return;
328   }
329 
330   if (OriginalDeclaration->getTemplatedKind() ==
331       FunctionDecl::TK_FunctionTemplateSpecialization) {
332     formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,
333                       InconsistentDeclarations,
334                       "function template specialization",
335                       "primary template declaration");
336   } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) {
337     formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,
338                       InconsistentDeclarations, "function", "definition");
339   } else {
340     formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration,
341                                      OriginalDeclaration,
342                                      InconsistentDeclarations);
343   }
344 
345   markRedeclarationsAsVisited(OriginalDeclaration);
346 }
347 
markRedeclarationsAsVisited(const FunctionDecl * OriginalDeclaration)348 void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited(
349     const FunctionDecl *OriginalDeclaration) {
350   for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) {
351     VisitedDeclarations.insert(Redecl);
352   }
353 }
354 
355 } // namespace readability
356 } // namespace tidy
357 } // namespace clang
358