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