clang-tools  14.0.0git
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 
16 using namespace clang::ast_matchers;
17 
18 namespace clang {
19 namespace tidy {
20 namespace readability {
21 
22 namespace {
23 
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 {
36  DifferingParamInfo(StringRef SourceName, StringRef OtherName,
37  SourceRange OtherNameRange, bool GenerateFixItHint)
40 
41  StringRef SourceName;
42  StringRef OtherName;
43  SourceRange OtherNameRange;
45 };
46 
47 using DifferingParamsContainer = llvm::SmallVector<DifferingParamInfo, 10>;
48 
49 struct InconsistentDeclarationInfo {
50  InconsistentDeclarationInfo(SourceLocation DeclarationLocation,
51  DifferingParamsContainer &&DifferingParams)
53  DifferingParams(std::move(DifferingParams)) {}
54 
55  SourceLocation DeclarationLocation;
56  DifferingParamsContainer DifferingParams;
57 };
58 
59 using InconsistentDeclarationsContainer =
60  llvm::SmallVector<InconsistentDeclarationInfo, 2>;
61 
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 
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
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
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  std::sort(InconsistentDeclarations.begin(), InconsistentDeclarations.end(),
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 *
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 
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 
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 
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 
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 
289 void InconsistentDeclarationParameterNameCheck::storeOptions(
291  Options.store(Opts, "IgnoreMacros", IgnoreMacros);
292  Options.store(Opts, "Strict", Strict);
293 }
294 
295 void InconsistentDeclarationParameterNameCheck::registerMatchers(
296  MatchFinder *Finder) {
297  Finder->addMatcher(functionDecl(hasOtherDeclarations()).bind("functionDecl"),
298  this);
299 }
300 
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 
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
clang::tidy::ClangTidyOptions::OptionMap
llvm::StringMap< ClangTidyValue > OptionMap
Definition: ClangTidyOptions.h:115
Location
Definition: Modularize.cpp:382
InconsistentDeclarationParameterNameCheck.h
OtherNameRange
SourceRange OtherNameRange
Definition: InconsistentDeclarationParameterNameCheck.cpp:43
DeclarationLocation
SourceLocation DeclarationLocation
Definition: InconsistentDeclarationParameterNameCheck.cpp:55
DifferingParams
DifferingParamsContainer DifferingParams
Definition: InconsistentDeclarationParameterNameCheck.cpp:56
clang::ast_matchers
Definition: AbseilMatcher.h:14
OtherName
StringRef OtherName
Definition: InconsistentDeclarationParameterNameCheck.cpp:42
clang::ast_matchers::AST_MATCHER
AST_MATCHER(Expr, isMacroID)
Definition: PreferIsaOrDynCastInConditionalsCheck.cpp:19
clang::clangd::check
bool check(llvm::StringRef File, llvm::function_ref< bool(const Position &)> ShouldCheckLine, const ThreadsafeFS &TFS, const ClangdLSPServer::Options &Opts, bool EnableCodeCompletion)
Definition: Check.cpp:258
GenerateFixItHint
bool GenerateFixItHint
Definition: InconsistentDeclarationParameterNameCheck.cpp:44
clang
===– Representation.cpp - ClangDoc Representation --------—*- C++ -*-===//
Definition: ApplyReplacements.h:27
SourceName
StringRef SourceName
Definition: InconsistentDeclarationParameterNameCheck.cpp:41