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