clang-tools  10.0.0
InconsistentDeclarationParameterNameCheck.cpp
Go to the documentation of this file.
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 
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 
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 {
37  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;
46 };
47 
48 using DifferingParamsContainer = llvm::SmallVector<DifferingParamInfo, 10>;
49 
50 struct InconsistentDeclarationInfo {
51  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 
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 
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
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
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 *
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 
196 std::string joinParameterNames(
197  const DifferingParamsContainer &DifferingParams,
198  llvm::function_ref<StringRef(const DifferingParamInfo &)> ChooseParamName) {
199  llvm::SmallVector<char, 40> Buffer;
200  llvm::raw_svector_ostream Str(Buffer);
201  bool First = true;
202  for (const DifferingParamInfo &ParamInfo : DifferingParams) {
203  if (First)
204  First = false;
205  else
206  Str << ", ";
207 
208  Str << "'" << ChooseParamName(ParamInfo).str() << "'";
209  }
210  return Str.str().str();
211 }
212 
213 void formatDifferingParamsDiagnostic(
214  InconsistentDeclarationParameterNameCheck *Check, SourceLocation Location,
215  StringRef OtherDeclarationDescription,
216  const DifferingParamsContainer &DifferingParams) {
217  auto ChooseOtherName = [](const DifferingParamInfo &ParamInfo) {
218  return ParamInfo.OtherName;
219  };
220  auto ChooseSourceName = [](const DifferingParamInfo &ParamInfo) {
221  return ParamInfo.SourceName;
222  };
223 
224  auto ParamDiag =
225  Check->diag(Location,
226  "differing parameters are named here: (%0), in %1: (%2)",
227  DiagnosticIDs::Level::Note)
228  << joinParameterNames(DifferingParams, ChooseOtherName)
229  << OtherDeclarationDescription
230  << joinParameterNames(DifferingParams, ChooseSourceName);
231 
232  for (const DifferingParamInfo &ParamInfo : DifferingParams) {
233  if (ParamInfo.GenerateFixItHint) {
234  ParamDiag << FixItHint::CreateReplacement(
235  CharSourceRange::getTokenRange(ParamInfo.OtherNameRange),
236  ParamInfo.SourceName);
237  }
238  }
239 }
240 
241 void formatDiagnosticsForDeclarations(
242  InconsistentDeclarationParameterNameCheck *Check,
243  const FunctionDecl *ParameterSourceDeclaration,
244  const FunctionDecl *OriginalDeclaration,
245  const InconsistentDeclarationsContainer &InconsistentDeclarations) {
246  Check->diag(
247  OriginalDeclaration->getLocation(),
248  "function %q0 has %1 other declaration%s1 with different parameter names")
249  << OriginalDeclaration
250  << static_cast<int>(InconsistentDeclarations.size());
251  int Count = 1;
252  for (const InconsistentDeclarationInfo &InconsistentDeclaration :
253  InconsistentDeclarations) {
254  Check->diag(InconsistentDeclaration.DeclarationLocation,
255  "the %ordinal0 inconsistent declaration seen here",
256  DiagnosticIDs::Level::Note)
257  << Count;
258 
259  formatDifferingParamsDiagnostic(
260  Check, InconsistentDeclaration.DeclarationLocation,
261  "the other declaration", InconsistentDeclaration.DifferingParams);
262 
263  ++Count;
264  }
265 }
266 
267 void formatDiagnostics(
268  InconsistentDeclarationParameterNameCheck *Check,
269  const FunctionDecl *ParameterSourceDeclaration,
270  const FunctionDecl *OriginalDeclaration,
271  const InconsistentDeclarationsContainer &InconsistentDeclarations,
272  StringRef FunctionDescription, StringRef ParameterSourceDescription) {
273  for (const InconsistentDeclarationInfo &InconsistentDeclaration :
274  InconsistentDeclarations) {
275  Check->diag(InconsistentDeclaration.DeclarationLocation,
276  "%0 %q1 has a %2 with different parameter names")
277  << FunctionDescription << OriginalDeclaration
278  << ParameterSourceDescription;
279 
280  Check->diag(ParameterSourceDeclaration->getLocation(), "the %0 seen here",
281  DiagnosticIDs::Level::Note)
282  << ParameterSourceDescription;
283 
284  formatDifferingParamsDiagnostic(
285  Check, InconsistentDeclaration.DeclarationLocation,
286  ParameterSourceDescription, InconsistentDeclaration.DifferingParams);
287  }
288 }
289 
290 } // anonymous namespace
291 
292 void InconsistentDeclarationParameterNameCheck::storeOptions(
294  Options.store(Opts, "IgnoreMacros", IgnoreMacros);
295  Options.store(Opts, "Strict", Strict);
296 }
297 
298 void InconsistentDeclarationParameterNameCheck::registerMatchers(
299  MatchFinder *Finder) {
300  Finder->addMatcher(functionDecl(unless(isImplicit()), hasOtherDeclarations())
301  .bind("functionDecl"),
302  this);
303 }
304 
305 void InconsistentDeclarationParameterNameCheck::check(
306  const MatchFinder::MatchResult &Result) {
307  const auto *OriginalDeclaration =
308  Result.Nodes.getNodeAs<FunctionDecl>("functionDecl");
309 
310  if (VisitedDeclarations.count(OriginalDeclaration) > 0)
311  return; // Avoid multiple warnings.
312 
313  const FunctionDecl *ParameterSourceDeclaration =
314  getParameterSourceDeclaration(OriginalDeclaration);
315 
316  InconsistentDeclarationsContainer InconsistentDeclarations =
317  findInconsistentDeclarations(OriginalDeclaration,
318  ParameterSourceDeclaration,
319  *Result.SourceManager, Strict);
320  if (InconsistentDeclarations.empty()) {
321  // Avoid unnecessary further visits.
322  markRedeclarationsAsVisited(OriginalDeclaration);
323  return;
324  }
325 
326  SourceLocation StartLoc = OriginalDeclaration->getBeginLoc();
327  if (StartLoc.isMacroID() && IgnoreMacros) {
328  markRedeclarationsAsVisited(OriginalDeclaration);
329  return;
330  }
331 
332  if (OriginalDeclaration->getTemplatedKind() ==
333  FunctionDecl::TK_FunctionTemplateSpecialization) {
334  formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,
335  InconsistentDeclarations,
336  "function template specialization",
337  "primary template declaration");
338  } else if (ParameterSourceDeclaration->isThisDeclarationADefinition()) {
339  formatDiagnostics(this, ParameterSourceDeclaration, OriginalDeclaration,
340  InconsistentDeclarations, "function", "definition");
341  } else {
342  formatDiagnosticsForDeclarations(this, ParameterSourceDeclaration,
343  OriginalDeclaration,
344  InconsistentDeclarations);
345  }
346 
347  markRedeclarationsAsVisited(OriginalDeclaration);
348 }
349 
350 void InconsistentDeclarationParameterNameCheck::markRedeclarationsAsVisited(
351  const FunctionDecl *OriginalDeclaration) {
352  for (const FunctionDecl *Redecl : OriginalDeclaration->redecls()) {
353  VisitedDeclarations.insert(Redecl);
354  }
355 }
356 
357 } // namespace readability
358 } // namespace tidy
359 } // namespace clang
SourceLocation DeclarationLocation
DifferingParamsContainer DifferingParams
std::map< std::string, std::string > OptionMap
llvm::Optional< Range > getTokenRange(const SourceManager &SM, const LangOptions &LangOpts, SourceLocation TokLoc)
Returns the taken range at TokLoc.
Definition: SourceCode.cpp:227
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//