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