1//===--- SuspiciousCallArgumentCheck.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 "SuspiciousCallArgumentCheck.h"
10#include "../utils/OptionsUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/Type.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
14#include <optional>
15#include <sstream>
16
17using namespace clang::ast_matchers;
18namespace optutils = clang::tidy::utils::options;
19
20namespace clang::tidy::readability {
21
22namespace {
23struct DefaultHeuristicConfiguration {
24 /// Whether the heuristic is to be enabled by default.
25 const bool Enabled;
26
27 /// The upper bound of % of similarity the two strings might have to be
28 /// considered dissimilar.
29 /// (For purposes of configuration, -1 if the heuristic is not configurable
30 /// with bounds.)
31 const int8_t DissimilarBelow;
32
33 /// The lower bound of % of similarity the two string must have to be
34 /// considered similar.
35 /// (For purposes of configuration, -1 if the heuristic is not configurable
36 /// with bounds.)
37 const int8_t SimilarAbove;
38
39 /// Can the heuristic be configured with bounds?
40 bool hasBounds() const { return DissimilarBelow > -1 && SimilarAbove > -1; }
41};
42} // namespace
43
44static constexpr std::size_t DefaultMinimumIdentifierNameLength = 3;
45
46static constexpr StringRef HeuristicToString[] = {
47 "Equality", "Abbreviation", "Prefix", "Suffix",
48 "Substring", "Levenshtein", "JaroWinkler", "Dice"};
49
50static constexpr DefaultHeuristicConfiguration Defaults[] = {
51 {.Enabled: true, .DissimilarBelow: -1, .SimilarAbove: -1}, // Equality.
52 {.Enabled: true, .DissimilarBelow: -1, .SimilarAbove: -1}, // Abbreviation.
53 {.Enabled: true, .DissimilarBelow: 25, .SimilarAbove: 30}, // Prefix.
54 {.Enabled: true, .DissimilarBelow: 25, .SimilarAbove: 30}, // Suffix.
55 {.Enabled: true, .DissimilarBelow: 40, .SimilarAbove: 50}, // Substring.
56 {.Enabled: true, .DissimilarBelow: 50, .SimilarAbove: 66}, // Levenshtein.
57 {.Enabled: true, .DissimilarBelow: 75, .SimilarAbove: 85}, // Jaro-Winkler.
58 {.Enabled: true, .DissimilarBelow: 60, .SimilarAbove: 70}, // Dice.
59};
60
61static_assert(
62 sizeof(HeuristicToString) / sizeof(HeuristicToString[0]) ==
63 SuspiciousCallArgumentCheck::HeuristicCount,
64 "Ensure that every heuristic has a corresponding stringified name");
65static_assert(sizeof(Defaults) / sizeof(Defaults[0]) ==
66 SuspiciousCallArgumentCheck::HeuristicCount,
67 "Ensure that every heuristic has a default configuration.");
68
69namespace {
70template <std::size_t I> struct HasWellConfiguredBounds {
71 static constexpr bool Value =
72 !((Defaults[I].DissimilarBelow == -1) ^ (Defaults[I].SimilarAbove == -1));
73 static_assert(Value, "A heuristic must either have a dissimilarity and "
74 "similarity bound, or neither!");
75};
76
77template <std::size_t I> struct HasWellConfiguredBoundsFold {
78 static constexpr bool Value = HasWellConfiguredBounds<I>::Value &&
79 HasWellConfiguredBoundsFold<I - 1>::Value;
80};
81
82template <> struct HasWellConfiguredBoundsFold<0> {
83 static constexpr bool Value = HasWellConfiguredBounds<0>::Value;
84};
85
86struct AllHeuristicsBoundsWellConfigured {
87 static constexpr bool Value =
88 HasWellConfiguredBoundsFold<SuspiciousCallArgumentCheck::HeuristicCount -
89 1>::Value;
90};
91
92static_assert(AllHeuristicsBoundsWellConfigured::Value);
93} // namespace
94
95static constexpr llvm::StringLiteral DefaultAbbreviations = "addr=address;"
96 "arr=array;"
97 "attr=attribute;"
98 "buf=buffer;"
99 "cl=client;"
100 "cnt=count;"
101 "col=column;"
102 "cpy=copy;"
103 "dest=destination;"
104 "dist=distance"
105 "dst=distance;"
106 "elem=element;"
107 "hght=height;"
108 "i=index;"
109 "idx=index;"
110 "len=length;"
111 "ln=line;"
112 "lst=list;"
113 "nr=number;"
114 "num=number;"
115 "pos=position;"
116 "ptr=pointer;"
117 "ref=reference;"
118 "src=source;"
119 "srv=server;"
120 "stmt=statement;"
121 "str=string;"
122 "val=value;"
123 "var=variable;"
124 "vec=vector;"
125 "wdth=width";
126
127static constexpr std::size_t SmallVectorSize =
128 SuspiciousCallArgumentCheck::SmallVectorSize;
129
130/// Returns how many % X is of Y.
131static inline double percentage(double X, double Y) { return X / Y * 100.0; }
132
133static bool applyEqualityHeuristic(StringRef Arg, StringRef Param) {
134 return Arg.equals_insensitive(RHS: Param);
135}
136
137static bool applyAbbreviationHeuristic(
138 const llvm::StringMap<std::string> &AbbreviationDictionary, StringRef Arg,
139 StringRef Param) {
140 if (AbbreviationDictionary.contains(Key: Arg) &&
141 Param.equals(RHS: AbbreviationDictionary.lookup(Key: Arg)))
142 return true;
143
144 if (AbbreviationDictionary.contains(Key: Param) &&
145 Arg.equals(RHS: AbbreviationDictionary.lookup(Key: Param)))
146 return true;
147
148 return false;
149}
150
151/// Check whether the shorter String is a prefix of the longer String.
152static bool applyPrefixHeuristic(StringRef Arg, StringRef Param,
153 int8_t Threshold) {
154 StringRef Shorter = Arg.size() < Param.size() ? Arg : Param;
155 StringRef Longer = Arg.size() >= Param.size() ? Arg : Param;
156
157 if (Longer.starts_with_insensitive(Prefix: Shorter))
158 return percentage(X: Shorter.size(), Y: Longer.size()) > Threshold;
159
160 return false;
161}
162
163/// Check whether the shorter String is a suffix of the longer String.
164static bool applySuffixHeuristic(StringRef Arg, StringRef Param,
165 int8_t Threshold) {
166 StringRef Shorter = Arg.size() < Param.size() ? Arg : Param;
167 StringRef Longer = Arg.size() >= Param.size() ? Arg : Param;
168
169 if (Longer.ends_with_insensitive(Suffix: Shorter))
170 return percentage(X: Shorter.size(), Y: Longer.size()) > Threshold;
171
172 return false;
173}
174
175static bool applySubstringHeuristic(StringRef Arg, StringRef Param,
176 int8_t Threshold) {
177
178 std::size_t MaxLength = 0;
179 SmallVector<std::size_t, SmallVectorSize> Current(Param.size());
180 SmallVector<std::size_t, SmallVectorSize> Previous(Param.size());
181 std::string ArgLower = Arg.lower();
182 std::string ParamLower = Param.lower();
183
184 for (std::size_t I = 0; I < Arg.size(); ++I) {
185 for (std::size_t J = 0; J < Param.size(); ++J) {
186 if (ArgLower[I] == ParamLower[J]) {
187 if (I == 0 || J == 0)
188 Current[J] = 1;
189 else
190 Current[J] = 1 + Previous[J - 1];
191
192 MaxLength = std::max(a: MaxLength, b: Current[J]);
193 } else
194 Current[J] = 0;
195 }
196
197 Current.swap(RHS&: Previous);
198 }
199
200 size_t LongerLength = std::max(a: Arg.size(), b: Param.size());
201 return percentage(X: MaxLength, Y: LongerLength) > Threshold;
202}
203
204static bool applyLevenshteinHeuristic(StringRef Arg, StringRef Param,
205 int8_t Threshold) {
206 std::size_t LongerLength = std::max(a: Arg.size(), b: Param.size());
207 double Dist = Arg.edit_distance(Other: Param);
208 Dist = (1.0 - Dist / LongerLength) * 100.0;
209 return Dist > Threshold;
210}
211
212// Based on http://en.wikipedia.org/wiki/Jaro–Winkler_distance.
213static bool applyJaroWinklerHeuristic(StringRef Arg, StringRef Param,
214 int8_t Threshold) {
215 std::size_t Match = 0, Transpos = 0;
216 std::ptrdiff_t ArgLen = Arg.size();
217 std::ptrdiff_t ParamLen = Param.size();
218 SmallVector<int, SmallVectorSize> ArgFlags(ArgLen);
219 SmallVector<int, SmallVectorSize> ParamFlags(ParamLen);
220 std::ptrdiff_t Range =
221 std::max(a: std::ptrdiff_t{0}, b: std::max(a: ArgLen, b: ParamLen) / 2 - 1);
222
223 // Calculate matching characters.
224 for (std::ptrdiff_t I = 0; I < ParamLen; ++I)
225 for (std::ptrdiff_t J = std::max(a: I - Range, b: std::ptrdiff_t{0}),
226 L = std::min(a: I + Range + 1, b: ArgLen);
227 J < L; ++J)
228 if (tolower(c: Param[I]) == tolower(c: Arg[J]) && !ArgFlags[J]) {
229 ArgFlags[J] = 1;
230 ParamFlags[I] = 1;
231 ++Match;
232 break;
233 }
234
235 if (!Match)
236 return false;
237
238 // Calculate character transpositions.
239 std::ptrdiff_t L = 0;
240 for (std::ptrdiff_t I = 0; I < ParamLen; ++I) {
241 if (ParamFlags[I] == 1) {
242 std::ptrdiff_t J = 0;
243 for (J = L; J < ArgLen; ++J)
244 if (ArgFlags[J] == 1) {
245 L = J + 1;
246 break;
247 }
248
249 if (tolower(c: Param[I]) != tolower(c: Arg[J]))
250 ++Transpos;
251 }
252 }
253 Transpos /= 2;
254
255 // Jaro distance.
256 double MatchD = Match;
257 double Dist = ((MatchD / ArgLen) + (MatchD / ParamLen) +
258 ((MatchD - Transpos) / Match)) /
259 3.0;
260
261 // Calculate common string prefix up to 4 chars.
262 L = 0;
263 for (std::ptrdiff_t I = 0;
264 I < std::min(a: std::min(a: ArgLen, b: ParamLen), b: std::ptrdiff_t{4}); ++I)
265 if (tolower(c: Arg[I]) == tolower(c: Param[I]))
266 ++L;
267
268 // Jaro-Winkler distance.
269 Dist = (Dist + (L * 0.1 * (1.0 - Dist))) * 100.0;
270 return Dist > Threshold;
271}
272
273// Based on http://en.wikipedia.org/wiki/Sørensen–Dice_coefficient
274static bool applyDiceHeuristic(StringRef Arg, StringRef Param,
275 int8_t Threshold) {
276 llvm::StringSet<> ArgBigrams;
277 llvm::StringSet<> ParamBigrams;
278
279 // Extract character bigrams from Arg.
280 for (std::ptrdiff_t I = 0; I < static_cast<std::ptrdiff_t>(Arg.size()) - 1;
281 ++I)
282 ArgBigrams.insert(key: Arg.substr(Start: I, N: 2).lower());
283
284 // Extract character bigrams from Param.
285 for (std::ptrdiff_t I = 0; I < static_cast<std::ptrdiff_t>(Param.size()) - 1;
286 ++I)
287 ParamBigrams.insert(key: Param.substr(Start: I, N: 2).lower());
288
289 std::size_t Intersection = 0;
290
291 // Find the intersection between the two sets.
292 for (auto IT = ParamBigrams.begin(); IT != ParamBigrams.end(); ++IT)
293 Intersection += ArgBigrams.count(Key: (IT->getKey()));
294
295 // Calculate Dice coefficient.
296 return percentage(X: Intersection * 2.0,
297 Y: ArgBigrams.size() + ParamBigrams.size()) > Threshold;
298}
299
300/// Checks if ArgType binds to ParamType regarding reference-ness and
301/// cv-qualifiers.
302static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType) {
303 return !ParamType->isReferenceType() ||
304 ParamType.getNonReferenceType().isAtLeastAsQualifiedAs(
305 other: ArgType.getNonReferenceType());
306}
307
308static bool isPointerOrArray(QualType TypeToCheck) {
309 return TypeToCheck->isPointerType() || TypeToCheck->isArrayType();
310}
311
312/// Checks whether ArgType is an array type identical to ParamType's array type.
313/// Enforces array elements' qualifier compatibility as well.
314static bool isCompatibleWithArrayReference(QualType ArgType,
315 QualType ParamType) {
316 if (!ArgType->isArrayType())
317 return false;
318 // Here, qualifiers belong to the elements of the arrays.
319 if (!ParamType.isAtLeastAsQualifiedAs(other: ArgType))
320 return false;
321
322 return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType();
323}
324
325static QualType convertToPointeeOrArrayElementQualType(QualType TypeToConvert) {
326 unsigned CVRqualifiers = 0;
327 // Save array element qualifiers, since getElementType() removes qualifiers
328 // from array elements.
329 if (TypeToConvert->isArrayType())
330 CVRqualifiers = TypeToConvert.getLocalQualifiers().getCVRQualifiers();
331 TypeToConvert = TypeToConvert->isPointerType()
332 ? TypeToConvert->getPointeeType()
333 : TypeToConvert->getAsArrayTypeUnsafe()->getElementType();
334 TypeToConvert = TypeToConvert.withCVRQualifiers(CVR: CVRqualifiers);
335 return TypeToConvert;
336}
337
338/// Checks if multilevel pointers' qualifiers compatibility continues on the
339/// current pointer level. For multilevel pointers, C++ permits conversion, if
340/// every cv-qualifier in ArgType also appears in the corresponding position in
341/// ParamType, and if PramType has a cv-qualifier that's not in ArgType, then
342/// every * in ParamType to the right of that cv-qualifier, except the last
343/// one, must also be const-qualified.
344static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType,
345 bool &IsParamContinuouslyConst) {
346 // The types are compatible, if the parameter is at least as qualified as the
347 // argument, and if it is more qualified, it has to be const on upper pointer
348 // levels.
349 bool AreTypesQualCompatible =
350 ParamType.isAtLeastAsQualifiedAs(other: ArgType) &&
351 (!ParamType.hasQualifiers() || IsParamContinuouslyConst);
352 // Check whether the parameter's constness continues at the current pointer
353 // level.
354 IsParamContinuouslyConst &= ParamType.isConstQualified();
355
356 return AreTypesQualCompatible;
357}
358
359/// Checks whether multilevel pointers are compatible in terms of levels,
360/// qualifiers and pointee type.
361static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType,
362 bool IsParamContinuouslyConst) {
363 if (!arePointersStillQualCompatible(ArgType, ParamType,
364 IsParamContinuouslyConst))
365 return false;
366
367 do {
368 // Step down one pointer level.
369 ArgType = convertToPointeeOrArrayElementQualType(TypeToConvert: ArgType);
370 ParamType = convertToPointeeOrArrayElementQualType(TypeToConvert: ParamType);
371
372 // Check whether cv-qualifiers permit compatibility on
373 // current level.
374 if (!arePointersStillQualCompatible(ArgType, ParamType,
375 IsParamContinuouslyConst))
376 return false;
377
378 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
379 return true;
380
381 } while (ParamType->isPointerType() && ArgType->isPointerType());
382 // The final type does not match, or pointer levels differ.
383 return false;
384}
385
386/// Checks whether ArgType converts implicitly to ParamType.
387static bool areTypesCompatible(QualType ArgType, QualType ParamType,
388 const ASTContext &Ctx) {
389 if (ArgType.isNull() || ParamType.isNull())
390 return false;
391
392 ArgType = ArgType.getCanonicalType();
393 ParamType = ParamType.getCanonicalType();
394
395 if (ArgType == ParamType)
396 return true;
397
398 // Check for constness and reference compatibility.
399 if (!areRefAndQualCompatible(ArgType, ParamType))
400 return false;
401
402 bool IsParamReference = ParamType->isReferenceType();
403
404 // Reference-ness has already been checked and should be removed
405 // before further checking.
406 ArgType = ArgType.getNonReferenceType();
407 ParamType = ParamType.getNonReferenceType();
408
409 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
410 return true;
411
412 // Arithmetic types are interconvertible, except scoped enums.
413 if (ParamType->isArithmeticType() && ArgType->isArithmeticType()) {
414 if ((ParamType->isEnumeralType() &&
415 ParamType->castAs<EnumType>()->getDecl()->isScoped()) ||
416 (ArgType->isEnumeralType() &&
417 ArgType->castAs<EnumType>()->getDecl()->isScoped()))
418 return false;
419
420 return true;
421 }
422
423 // Check if the argument and the param are both function types (the parameter
424 // decayed to a function pointer).
425 if (ArgType->isFunctionType() && ParamType->isFunctionPointerType()) {
426 ParamType = ParamType->getPointeeType();
427 return ArgType == ParamType;
428 }
429
430 // Arrays or pointer arguments convert to array or pointer parameters.
431 if (!(isPointerOrArray(TypeToCheck: ArgType) && isPointerOrArray(TypeToCheck: ParamType)))
432 return false;
433
434 // When ParamType is an array reference, ArgType has to be of the same-sized
435 // array-type with cv-compatible element type.
436 if (IsParamReference && ParamType->isArrayType())
437 return isCompatibleWithArrayReference(ArgType, ParamType);
438
439 bool IsParamContinuouslyConst =
440 !IsParamReference || ParamType.getNonReferenceType().isConstQualified();
441
442 // Remove the first level of indirection.
443 ArgType = convertToPointeeOrArrayElementQualType(TypeToConvert: ArgType);
444 ParamType = convertToPointeeOrArrayElementQualType(TypeToConvert: ParamType);
445
446 // Check qualifier compatibility on the next level.
447 if (!ParamType.isAtLeastAsQualifiedAs(other: ArgType))
448 return false;
449
450 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
451 return true;
452
453 // At this point, all possible C language implicit conversion were checked.
454 if (!Ctx.getLangOpts().CPlusPlus)
455 return false;
456
457 // Check whether ParamType and ArgType were both pointers to a class or a
458 // struct, and check for inheritance.
459 if (ParamType->isStructureOrClassType() &&
460 ArgType->isStructureOrClassType()) {
461 const auto *ArgDecl = ArgType->getAsCXXRecordDecl();
462 const auto *ParamDecl = ParamType->getAsCXXRecordDecl();
463 if (!ArgDecl || !ArgDecl->hasDefinition() || !ParamDecl ||
464 !ParamDecl->hasDefinition())
465 return false;
466
467 return ArgDecl->isDerivedFrom(Base: ParamDecl);
468 }
469
470 // Unless argument and param are both multilevel pointers, the types are not
471 // convertible.
472 if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType()))
473 return false;
474
475 return arePointerTypesCompatible(ArgType, ParamType,
476 IsParamContinuouslyConst);
477}
478
479static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD) {
480 switch (FD->getOverloadedOperator()) {
481 case OO_None:
482 case OO_Call:
483 case OO_Subscript:
484 case OO_New:
485 case OO_Delete:
486 case OO_Array_New:
487 case OO_Array_Delete:
488 case OO_Conditional:
489 case OO_Coawait:
490 return false;
491
492 default:
493 return FD->getNumParams() <= 2;
494 }
495}
496
497SuspiciousCallArgumentCheck::SuspiciousCallArgumentCheck(
498 StringRef Name, ClangTidyContext *Context)
499 : ClangTidyCheck(Name, Context),
500 MinimumIdentifierNameLength(Options.get(
501 LocalName: "MinimumIdentifierNameLength", Default: DefaultMinimumIdentifierNameLength)) {
502 auto GetToggleOpt = [this](Heuristic H) -> bool {
503 auto Idx = static_cast<std::size_t>(H);
504 assert(Idx < HeuristicCount);
505 return Options.get(LocalName: HeuristicToString[Idx], Default: Defaults[Idx].Enabled);
506 };
507 auto GetBoundOpt = [this](Heuristic H, BoundKind BK) -> int8_t {
508 auto Idx = static_cast<std::size_t>(H);
509 assert(Idx < HeuristicCount);
510
511 SmallString<32> Key = HeuristicToString[Idx];
512 Key.append(RHS: BK == BoundKind::DissimilarBelow ? "DissimilarBelow"
513 : "SimilarAbove");
514 int8_t Default = BK == BoundKind::DissimilarBelow
515 ? Defaults[Idx].DissimilarBelow
516 : Defaults[Idx].SimilarAbove;
517 return Options.get(LocalName: Key, Default);
518 };
519 for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) {
520 auto H = static_cast<Heuristic>(Idx);
521 if (GetToggleOpt(H))
522 AppliedHeuristics.emplace_back(Args&: H);
523 ConfiguredBounds.emplace_back(
524 Args: std::make_pair(x: GetBoundOpt(H, BoundKind::DissimilarBelow),
525 y: GetBoundOpt(H, BoundKind::SimilarAbove)));
526 }
527
528 for (StringRef Abbreviation : optutils::parseStringList(
529 Option: Options.get(LocalName: "Abbreviations", Default: DefaultAbbreviations))) {
530 auto KeyAndValue = Abbreviation.split(Separator: "=");
531 assert(!KeyAndValue.first.empty() && !KeyAndValue.second.empty());
532 AbbreviationDictionary.insert(
533 KV: std::make_pair(x&: KeyAndValue.first, y: KeyAndValue.second.str()));
534 }
535}
536
537void SuspiciousCallArgumentCheck::storeOptions(
538 ClangTidyOptions::OptionMap &Opts) {
539 Options.store(Options&: Opts, LocalName: "MinimumIdentifierNameLength",
540 Value: MinimumIdentifierNameLength);
541 const auto &SetToggleOpt = [this, &Opts](Heuristic H) -> void {
542 auto Idx = static_cast<std::size_t>(H);
543 Options.store(Options&: Opts, LocalName: HeuristicToString[Idx], Value: isHeuristicEnabled(H));
544 };
545 const auto &SetBoundOpt = [this, &Opts](Heuristic H, BoundKind BK) -> void {
546 auto Idx = static_cast<std::size_t>(H);
547 assert(Idx < HeuristicCount);
548 if (!Defaults[Idx].hasBounds())
549 return;
550
551 SmallString<32> Key = HeuristicToString[Idx];
552 Key.append(RHS: BK == BoundKind::DissimilarBelow ? "DissimilarBelow"
553 : "SimilarAbove");
554 Options.store(Options&: Opts, LocalName: Key, Value: *getBound(H, BK));
555 };
556
557 for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) {
558 auto H = static_cast<Heuristic>(Idx);
559 SetToggleOpt(H);
560 SetBoundOpt(H, BoundKind::DissimilarBelow);
561 SetBoundOpt(H, BoundKind::SimilarAbove);
562 }
563
564 SmallVector<std::string, 32> Abbreviations;
565 for (const auto &Abbreviation : AbbreviationDictionary) {
566 SmallString<32> EqualSignJoined;
567 EqualSignJoined.append(RHS: Abbreviation.first());
568 EqualSignJoined.append(RHS: "=");
569 EqualSignJoined.append(RHS: Abbreviation.second);
570
571 if (!Abbreviation.second.empty())
572 Abbreviations.emplace_back(Args: EqualSignJoined.str());
573 }
574 Options.store(Options&: Opts, LocalName: "Abbreviations",
575 Value: optutils::serializeStringList(Strings: std::vector<StringRef>(
576 Abbreviations.begin(), Abbreviations.end())));
577}
578
579bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H) const {
580 return llvm::is_contained(Range: AppliedHeuristics, Element: H);
581}
582
583std::optional<int8_t>
584SuspiciousCallArgumentCheck::getBound(Heuristic H, BoundKind BK) const {
585 auto Idx = static_cast<std::size_t>(H);
586 assert(Idx < HeuristicCount);
587
588 if (!Defaults[Idx].hasBounds())
589 return std::nullopt;
590
591 switch (BK) {
592 case BoundKind::DissimilarBelow:
593 return ConfiguredBounds[Idx].first;
594 case BoundKind::SimilarAbove:
595 return ConfiguredBounds[Idx].second;
596 }
597 llvm_unreachable("Unhandled Bound kind.");
598}
599
600void SuspiciousCallArgumentCheck::registerMatchers(MatchFinder *Finder) {
601 // Only match calls with at least 2 arguments.
602 Finder->addMatcher(
603 NodeMatch: functionDecl(forEachDescendant(callExpr(unless(anyOf(argumentCountIs(N: 0),
604 argumentCountIs(N: 1))))
605 .bind(ID: "functionCall")))
606 .bind(ID: "callingFunc"),
607 Action: this);
608}
609
610void SuspiciousCallArgumentCheck::check(
611 const MatchFinder::MatchResult &Result) {
612 const auto *MatchedCallExpr =
613 Result.Nodes.getNodeAs<CallExpr>(ID: "functionCall");
614 const auto *Caller = Result.Nodes.getNodeAs<FunctionDecl>(ID: "callingFunc");
615 assert(MatchedCallExpr && Caller);
616
617 const Decl *CalleeDecl = MatchedCallExpr->getCalleeDecl();
618 if (!CalleeDecl)
619 return;
620
621 const FunctionDecl *CalleeFuncDecl = CalleeDecl->getAsFunction();
622 if (!CalleeFuncDecl)
623 return;
624 if (CalleeFuncDecl == Caller)
625 // Ignore recursive calls.
626 return;
627 if (isOverloadedUnaryOrBinarySymbolOperator(FD: CalleeFuncDecl))
628 return;
629
630 // Get param attributes.
631 setParamNamesAndTypes(CalleeFuncDecl);
632
633 if (ParamNames.empty())
634 return;
635
636 // Get Arg attributes.
637 std::size_t InitialArgIndex = 0;
638
639 if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(Val: CalleeFuncDecl)) {
640 if (MethodDecl->getParent()->isLambda())
641 // Lambda functions' first Arg are the lambda object.
642 InitialArgIndex = 1;
643 else if (MethodDecl->getOverloadedOperator() == OO_Call)
644 // For custom operator()s, the first Arg is the called object.
645 InitialArgIndex = 1;
646 }
647
648 setArgNamesAndTypes(MatchedCallExpr, InitialArgIndex);
649
650 if (ArgNames.empty())
651 return;
652
653 std::size_t ParamCount = ParamNames.size();
654
655 // Check similarity.
656 for (std::size_t I = 0; I < ParamCount; ++I) {
657 for (std::size_t J = I + 1; J < ParamCount; ++J) {
658 // Do not check if param or arg names are short, or not convertible.
659 if (!areParamAndArgComparable(Position1: I, Position2: J, Ctx: *Result.Context))
660 continue;
661 if (!areArgsSwapped(Position1: I, Position2: J))
662 continue;
663
664 // Warning at the call itself.
665 diag(MatchedCallExpr->getExprLoc(),
666 "%ordinal0 argument '%1' (passed to '%2') looks like it might be "
667 "swapped with the %ordinal3, '%4' (passed to '%5')")
668 << static_cast<unsigned>(I + 1) << ArgNames[I] << ParamNames[I]
669 << static_cast<unsigned>(J + 1) << ArgNames[J] << ParamNames[J]
670 << MatchedCallExpr->getArg(Arg: I)->getSourceRange()
671 << MatchedCallExpr->getArg(Arg: J)->getSourceRange();
672
673 // Note at the functions declaration.
674 SourceLocation IParNameLoc =
675 CalleeFuncDecl->getParamDecl(i: I)->getLocation();
676 SourceLocation JParNameLoc =
677 CalleeFuncDecl->getParamDecl(i: J)->getLocation();
678
679 diag(CalleeFuncDecl->getLocation(), "in the call to %0, declared here",
680 DiagnosticIDs::Note)
681 << CalleeFuncDecl
682 << CharSourceRange::getTokenRange(B: IParNameLoc, E: IParNameLoc)
683 << CharSourceRange::getTokenRange(B: JParNameLoc, E: JParNameLoc);
684 }
685 }
686}
687
688void SuspiciousCallArgumentCheck::setParamNamesAndTypes(
689 const FunctionDecl *CalleeFuncDecl) {
690 // Reset vectors, and fill them with the currently checked function's
691 // parameters' data.
692 ParamNames.clear();
693 ParamTypes.clear();
694
695 for (const ParmVarDecl *Param : CalleeFuncDecl->parameters()) {
696 ParamTypes.push_back(Elt: Param->getType());
697
698 if (IdentifierInfo *II = Param->getIdentifier())
699 ParamNames.push_back(Elt: II->getName());
700 else
701 ParamNames.push_back(Elt: StringRef());
702 }
703}
704
705void SuspiciousCallArgumentCheck::setArgNamesAndTypes(
706 const CallExpr *MatchedCallExpr, std::size_t InitialArgIndex) {
707 // Reset vectors, and fill them with the currently checked function's
708 // arguments' data.
709 ArgNames.clear();
710 ArgTypes.clear();
711
712 for (std::size_t I = InitialArgIndex, J = MatchedCallExpr->getNumArgs();
713 I < J; ++I) {
714 assert(ArgTypes.size() == I - InitialArgIndex &&
715 ArgNames.size() == ArgTypes.size() &&
716 "Every iteration must put an element into the vectors!");
717
718 if (const auto *ArgExpr = dyn_cast<DeclRefExpr>(
719 Val: MatchedCallExpr->getArg(Arg: I)->IgnoreUnlessSpelledInSource())) {
720 if (const auto *Var = dyn_cast<VarDecl>(Val: ArgExpr->getDecl())) {
721 ArgTypes.push_back(Elt: Var->getType());
722 ArgNames.push_back(Elt: Var->getName());
723 continue;
724 }
725 if (const auto *FCall = dyn_cast<FunctionDecl>(Val: ArgExpr->getDecl())) {
726 if (FCall->getNameInfo().getName().isIdentifier()) {
727 ArgTypes.push_back(Elt: FCall->getType());
728 ArgNames.push_back(Elt: FCall->getName());
729 continue;
730 }
731 }
732 }
733
734 ArgTypes.push_back(Elt: QualType());
735 ArgNames.push_back(Elt: StringRef());
736 }
737}
738
739bool SuspiciousCallArgumentCheck::areParamAndArgComparable(
740 std::size_t Position1, std::size_t Position2, const ASTContext &Ctx) const {
741 if (Position1 >= ArgNames.size() || Position2 >= ArgNames.size())
742 return false;
743
744 // Do not report for too short strings.
745 if (ArgNames[Position1].size() < MinimumIdentifierNameLength ||
746 ArgNames[Position2].size() < MinimumIdentifierNameLength ||
747 ParamNames[Position1].size() < MinimumIdentifierNameLength ||
748 ParamNames[Position2].size() < MinimumIdentifierNameLength)
749 return false;
750
751 if (!areTypesCompatible(ArgType: ArgTypes[Position1], ParamType: ParamTypes[Position2], Ctx) ||
752 !areTypesCompatible(ArgType: ArgTypes[Position2], ParamType: ParamTypes[Position1], Ctx))
753 return false;
754
755 return true;
756}
757
758bool SuspiciousCallArgumentCheck::areArgsSwapped(std::size_t Position1,
759 std::size_t Position2) const {
760 for (Heuristic H : AppliedHeuristics) {
761 bool A1ToP2Similar = areNamesSimilar(
762 Arg: ArgNames[Position2], Param: ParamNames[Position1], H, BK: BoundKind::SimilarAbove);
763 bool A2ToP1Similar = areNamesSimilar(
764 Arg: ArgNames[Position1], Param: ParamNames[Position2], H, BK: BoundKind::SimilarAbove);
765
766 bool A1ToP1Dissimilar =
767 !areNamesSimilar(Arg: ArgNames[Position1], Param: ParamNames[Position1], H,
768 BK: BoundKind::DissimilarBelow);
769 bool A2ToP2Dissimilar =
770 !areNamesSimilar(Arg: ArgNames[Position2], Param: ParamNames[Position2], H,
771 BK: BoundKind::DissimilarBelow);
772
773 if ((A1ToP2Similar || A2ToP1Similar) && A1ToP1Dissimilar &&
774 A2ToP2Dissimilar)
775 return true;
776 }
777 return false;
778}
779
780bool SuspiciousCallArgumentCheck::areNamesSimilar(StringRef Arg,
781 StringRef Param, Heuristic H,
782 BoundKind BK) const {
783 int8_t Threshold = -1;
784 if (std::optional<int8_t> GotBound = getBound(H, BK))
785 Threshold = *GotBound;
786
787 switch (H) {
788 case Heuristic::Equality:
789 return applyEqualityHeuristic(Arg, Param);
790 case Heuristic::Abbreviation:
791 return applyAbbreviationHeuristic(AbbreviationDictionary, Arg, Param);
792 case Heuristic::Prefix:
793 return applyPrefixHeuristic(Arg, Param, Threshold);
794 case Heuristic::Suffix:
795 return applySuffixHeuristic(Arg, Param, Threshold);
796 case Heuristic::Substring:
797 return applySubstringHeuristic(Arg, Param, Threshold);
798 case Heuristic::Levenshtein:
799 return applyLevenshteinHeuristic(Arg, Param, Threshold);
800 case Heuristic::JaroWinkler:
801 return applyJaroWinklerHeuristic(Arg, Param, Threshold);
802 case Heuristic::Dice:
803 return applyDiceHeuristic(Arg, Param, Threshold);
804 }
805 llvm_unreachable("Unhandled heuristic kind");
806}
807
808} // namespace clang::tidy::readability
809

source code of clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp