xref: /llvm-project/clang-tools-extra/clang-tidy/readability/SuspiciousCallArgumentCheck.cpp (revision b9d678d22f74ebd6e34f0a3501fb01d3d80984e7)
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 
17 using namespace clang::ast_matchers;
18 namespace optutils = clang::tidy::utils::options;
19 
20 namespace clang::tidy::readability {
21 
22 namespace {
23 struct 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 
44 static constexpr std::size_t DefaultMinimumIdentifierNameLength = 3;
45 
46 static constexpr StringRef HeuristicToString[] = {
47     "Equality",  "Abbreviation", "Prefix",      "Suffix",
48     "Substring", "Levenshtein",  "JaroWinkler", "Dice"};
49 
50 static constexpr DefaultHeuristicConfiguration Defaults[] = {
51     {true, -1, -1}, // Equality.
52     {true, -1, -1}, // Abbreviation.
53     {true, 25, 30}, // Prefix.
54     {true, 25, 30}, // Suffix.
55     {true, 40, 50}, // Substring.
56     {true, 50, 66}, // Levenshtein.
57     {true, 75, 85}, // Jaro-Winkler.
58     {true, 60, 70}, // Dice.
59 };
60 
61 static_assert(
62     sizeof(HeuristicToString) / sizeof(HeuristicToString[0]) ==
63         SuspiciousCallArgumentCheck::HeuristicCount,
64     "Ensure that every heuristic has a corresponding stringified name");
65 static_assert(sizeof(Defaults) / sizeof(Defaults[0]) ==
66                   SuspiciousCallArgumentCheck::HeuristicCount,
67               "Ensure that every heuristic has a default configuration.");
68 
69 namespace {
70 template <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 
77 template <std::size_t I> struct HasWellConfiguredBoundsFold {
78   static constexpr bool Value = HasWellConfiguredBounds<I>::Value &&
79                                 HasWellConfiguredBoundsFold<I - 1>::Value;
80 };
81 
82 template <> struct HasWellConfiguredBoundsFold<0> {
83   static constexpr bool Value = HasWellConfiguredBounds<0>::Value;
84 };
85 
86 struct AllHeuristicsBoundsWellConfigured {
87   static constexpr bool Value =
88       HasWellConfiguredBoundsFold<SuspiciousCallArgumentCheck::HeuristicCount -
89                                   1>::Value;
90 };
91 
92 static_assert(AllHeuristicsBoundsWellConfigured::Value);
93 } // namespace
94 
95 static 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 
127 static constexpr std::size_t SmallVectorSize =
128     SuspiciousCallArgumentCheck::SmallVectorSize;
129 
130 /// Returns how many % X is of Y.
131 static inline double percentage(double X, double Y) { return X / Y * 100.0; }
132 
133 static bool applyEqualityHeuristic(StringRef Arg, StringRef Param) {
134   return Arg.equals_insensitive(Param);
135 }
136 
137 static bool applyAbbreviationHeuristic(
138     const llvm::StringMap<std::string> &AbbreviationDictionary, StringRef Arg,
139     StringRef Param) {
140   if (AbbreviationDictionary.contains(Arg) &&
141       Param == AbbreviationDictionary.lookup(Arg))
142     return true;
143 
144   if (AbbreviationDictionary.contains(Param) &&
145       Arg == AbbreviationDictionary.lookup(Param))
146     return true;
147 
148   return false;
149 }
150 
151 /// Check whether the shorter String is a prefix of the longer String.
152 static 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(Shorter))
158     return percentage(Shorter.size(), Longer.size()) > Threshold;
159 
160   return false;
161 }
162 
163 /// Check whether the shorter String is a suffix of the longer String.
164 static 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(Shorter))
170     return percentage(Shorter.size(), Longer.size()) > Threshold;
171 
172   return false;
173 }
174 
175 static 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(MaxLength, Current[J]);
193       } else
194         Current[J] = 0;
195     }
196 
197     Current.swap(Previous);
198   }
199 
200   size_t LongerLength = std::max(Arg.size(), Param.size());
201   return percentage(MaxLength, LongerLength) > Threshold;
202 }
203 
204 static bool applyLevenshteinHeuristic(StringRef Arg, StringRef Param,
205                                       int8_t Threshold) {
206   std::size_t LongerLength = std::max(Arg.size(), Param.size());
207   double Dist = Arg.edit_distance(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.
213 static 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(std::ptrdiff_t{0}, std::max(ArgLen, 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(I - Range, std::ptrdiff_t{0}),
226                         L = std::min(I + Range + 1, ArgLen);
227          J < L; ++J)
228       if (tolower(Param[I]) == tolower(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(Param[I]) != tolower(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(std::min(ArgLen, ParamLen), std::ptrdiff_t{4}); ++I)
265     if (tolower(Arg[I]) == tolower(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
274 static 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(Arg.substr(I, 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(Param.substr(I, 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((IT->getKey()));
294 
295   // Calculate Dice coefficient.
296   return percentage(Intersection * 2.0,
297                     ArgBigrams.size() + ParamBigrams.size()) > Threshold;
298 }
299 
300 /// Checks if ArgType binds to ParamType regarding reference-ness and
301 /// cv-qualifiers.
302 static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType,
303                                     const ASTContext &Ctx) {
304   return !ParamType->isReferenceType() ||
305          ParamType.getNonReferenceType().isAtLeastAsQualifiedAs(
306              ArgType.getNonReferenceType(), Ctx);
307 }
308 
309 static bool isPointerOrArray(QualType TypeToCheck) {
310   return TypeToCheck->isPointerType() || TypeToCheck->isArrayType();
311 }
312 
313 /// Checks whether ArgType is an array type identical to ParamType's array type.
314 /// Enforces array elements' qualifier compatibility as well.
315 static bool isCompatibleWithArrayReference(QualType ArgType, QualType ParamType,
316                                            const ASTContext &Ctx) {
317   if (!ArgType->isArrayType())
318     return false;
319   // Here, qualifiers belong to the elements of the arrays.
320   if (!ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx))
321     return false;
322 
323   return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType();
324 }
325 
326 static QualType convertToPointeeOrArrayElementQualType(QualType TypeToConvert) {
327   unsigned CVRqualifiers = 0;
328   // Save array element qualifiers, since getElementType() removes qualifiers
329   // from array elements.
330   if (TypeToConvert->isArrayType())
331     CVRqualifiers = TypeToConvert.getLocalQualifiers().getCVRQualifiers();
332   TypeToConvert = TypeToConvert->isPointerType()
333                       ? TypeToConvert->getPointeeType()
334                       : TypeToConvert->getAsArrayTypeUnsafe()->getElementType();
335   TypeToConvert = TypeToConvert.withCVRQualifiers(CVRqualifiers);
336   return TypeToConvert;
337 }
338 
339 /// Checks if multilevel pointers' qualifiers compatibility continues on the
340 /// current pointer level. For multilevel pointers, C++ permits conversion, if
341 /// every cv-qualifier in ArgType also appears in the corresponding position in
342 /// ParamType, and if PramType has a cv-qualifier that's not in ArgType, then
343 /// every * in ParamType to the right of that cv-qualifier, except the last
344 /// one, must also be const-qualified.
345 static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType,
346                                            bool &IsParamContinuouslyConst,
347                                            const ASTContext &Ctx) {
348   // The types are compatible, if the parameter is at least as qualified as the
349   // argument, and if it is more qualified, it has to be const on upper pointer
350   // levels.
351   bool AreTypesQualCompatible =
352       ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx) &&
353       (!ParamType.hasQualifiers() || IsParamContinuouslyConst);
354   // Check whether the parameter's constness continues at the current pointer
355   // level.
356   IsParamContinuouslyConst &= ParamType.isConstQualified();
357 
358   return AreTypesQualCompatible;
359 }
360 
361 /// Checks whether multilevel pointers are compatible in terms of levels,
362 /// qualifiers and pointee type.
363 static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType,
364                                       bool IsParamContinuouslyConst,
365                                       const ASTContext &Ctx) {
366   if (!arePointersStillQualCompatible(ArgType, ParamType,
367                                       IsParamContinuouslyConst, Ctx))
368     return false;
369 
370   do {
371     // Step down one pointer level.
372     ArgType = convertToPointeeOrArrayElementQualType(ArgType);
373     ParamType = convertToPointeeOrArrayElementQualType(ParamType);
374 
375     // Check whether cv-qualifiers permit compatibility on
376     // current level.
377     if (!arePointersStillQualCompatible(ArgType, ParamType,
378                                         IsParamContinuouslyConst, Ctx))
379       return false;
380 
381     if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
382       return true;
383 
384   } while (ParamType->isPointerType() && ArgType->isPointerType());
385   // The final type does not match, or pointer levels differ.
386   return false;
387 }
388 
389 /// Checks whether ArgType converts implicitly to ParamType.
390 static bool areTypesCompatible(QualType ArgType, QualType ParamType,
391                                const ASTContext &Ctx) {
392   if (ArgType.isNull() || ParamType.isNull())
393     return false;
394 
395   ArgType = ArgType.getCanonicalType();
396   ParamType = ParamType.getCanonicalType();
397 
398   if (ArgType == ParamType)
399     return true;
400 
401   // Check for constness and reference compatibility.
402   if (!areRefAndQualCompatible(ArgType, ParamType, Ctx))
403     return false;
404 
405   bool IsParamReference = ParamType->isReferenceType();
406 
407   // Reference-ness has already been checked and should be removed
408   // before further checking.
409   ArgType = ArgType.getNonReferenceType();
410   ParamType = ParamType.getNonReferenceType();
411 
412   if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
413     return true;
414 
415   // Arithmetic types are interconvertible, except scoped enums.
416   if (ParamType->isArithmeticType() && ArgType->isArithmeticType()) {
417     if ((ParamType->isEnumeralType() &&
418          ParamType->castAs<EnumType>()->getDecl()->isScoped()) ||
419         (ArgType->isEnumeralType() &&
420          ArgType->castAs<EnumType>()->getDecl()->isScoped()))
421       return false;
422 
423     return true;
424   }
425 
426   // Check if the argument and the param are both function types (the parameter
427   // decayed to a function pointer).
428   if (ArgType->isFunctionType() && ParamType->isFunctionPointerType()) {
429     ParamType = ParamType->getPointeeType();
430     return ArgType == ParamType;
431   }
432 
433   // Arrays or pointer arguments convert to array or pointer parameters.
434   if (!(isPointerOrArray(ArgType) && isPointerOrArray(ParamType)))
435     return false;
436 
437   // When ParamType is an array reference, ArgType has to be of the same-sized
438   // array-type with cv-compatible element type.
439   if (IsParamReference && ParamType->isArrayType())
440     return isCompatibleWithArrayReference(ArgType, ParamType, Ctx);
441 
442   bool IsParamContinuouslyConst =
443       !IsParamReference || ParamType.getNonReferenceType().isConstQualified();
444 
445   // Remove the first level of indirection.
446   ArgType = convertToPointeeOrArrayElementQualType(ArgType);
447   ParamType = convertToPointeeOrArrayElementQualType(ParamType);
448 
449   // Check qualifier compatibility on the next level.
450   if (!ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx))
451     return false;
452 
453   if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
454     return true;
455 
456   // At this point, all possible C language implicit conversion were checked.
457   if (!Ctx.getLangOpts().CPlusPlus)
458     return false;
459 
460   // Check whether ParamType and ArgType were both pointers to a class or a
461   // struct, and check for inheritance.
462   if (ParamType->isStructureOrClassType() &&
463       ArgType->isStructureOrClassType()) {
464     const auto *ArgDecl = ArgType->getAsCXXRecordDecl();
465     const auto *ParamDecl = ParamType->getAsCXXRecordDecl();
466     if (!ArgDecl || !ArgDecl->hasDefinition() || !ParamDecl ||
467         !ParamDecl->hasDefinition())
468       return false;
469 
470     return ArgDecl->isDerivedFrom(ParamDecl);
471   }
472 
473   // Unless argument and param are both multilevel pointers, the types are not
474   // convertible.
475   if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType()))
476     return false;
477 
478   return arePointerTypesCompatible(ArgType, ParamType, IsParamContinuouslyConst,
479                                    Ctx);
480 }
481 
482 static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD) {
483   switch (FD->getOverloadedOperator()) {
484   case OO_None:
485   case OO_Call:
486   case OO_Subscript:
487   case OO_New:
488   case OO_Delete:
489   case OO_Array_New:
490   case OO_Array_Delete:
491   case OO_Conditional:
492   case OO_Coawait:
493     return false;
494 
495   default:
496     return FD->getNumParams() <= 2;
497   }
498 }
499 
500 SuspiciousCallArgumentCheck::SuspiciousCallArgumentCheck(
501     StringRef Name, ClangTidyContext *Context)
502     : ClangTidyCheck(Name, Context),
503       MinimumIdentifierNameLength(Options.get(
504           "MinimumIdentifierNameLength", DefaultMinimumIdentifierNameLength)) {
505   auto GetToggleOpt = [this](Heuristic H) -> bool {
506     auto Idx = static_cast<std::size_t>(H);
507     assert(Idx < HeuristicCount);
508     return Options.get(HeuristicToString[Idx], Defaults[Idx].Enabled);
509   };
510   auto GetBoundOpt = [this](Heuristic H, BoundKind BK) -> int8_t {
511     auto Idx = static_cast<std::size_t>(H);
512     assert(Idx < HeuristicCount);
513 
514     SmallString<32> Key = HeuristicToString[Idx];
515     Key.append(BK == BoundKind::DissimilarBelow ? "DissimilarBelow"
516                                                 : "SimilarAbove");
517     int8_t Default = BK == BoundKind::DissimilarBelow
518                          ? Defaults[Idx].DissimilarBelow
519                          : Defaults[Idx].SimilarAbove;
520     return Options.get(Key, Default);
521   };
522   for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) {
523     auto H = static_cast<Heuristic>(Idx);
524     if (GetToggleOpt(H))
525       AppliedHeuristics.emplace_back(H);
526     ConfiguredBounds.emplace_back(
527         std::make_pair(GetBoundOpt(H, BoundKind::DissimilarBelow),
528                        GetBoundOpt(H, BoundKind::SimilarAbove)));
529   }
530 
531   for (StringRef Abbreviation : optutils::parseStringList(
532            Options.get("Abbreviations", DefaultAbbreviations))) {
533     auto KeyAndValue = Abbreviation.split("=");
534     assert(!KeyAndValue.first.empty() && !KeyAndValue.second.empty());
535     AbbreviationDictionary.insert(
536         std::make_pair(KeyAndValue.first, KeyAndValue.second.str()));
537   }
538 }
539 
540 void SuspiciousCallArgumentCheck::storeOptions(
541     ClangTidyOptions::OptionMap &Opts) {
542   Options.store(Opts, "MinimumIdentifierNameLength",
543                 MinimumIdentifierNameLength);
544   const auto &SetToggleOpt = [this, &Opts](Heuristic H) -> void {
545     auto Idx = static_cast<std::size_t>(H);
546     Options.store(Opts, HeuristicToString[Idx], isHeuristicEnabled(H));
547   };
548   const auto &SetBoundOpt = [this, &Opts](Heuristic H, BoundKind BK) -> void {
549     auto Idx = static_cast<std::size_t>(H);
550     assert(Idx < HeuristicCount);
551     if (!Defaults[Idx].hasBounds())
552       return;
553 
554     SmallString<32> Key = HeuristicToString[Idx];
555     Key.append(BK == BoundKind::DissimilarBelow ? "DissimilarBelow"
556                                                 : "SimilarAbove");
557     Options.store(Opts, Key, *getBound(H, BK));
558   };
559 
560   for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) {
561     auto H = static_cast<Heuristic>(Idx);
562     SetToggleOpt(H);
563     SetBoundOpt(H, BoundKind::DissimilarBelow);
564     SetBoundOpt(H, BoundKind::SimilarAbove);
565   }
566 
567   SmallVector<std::string, 32> Abbreviations;
568   for (const auto &Abbreviation : AbbreviationDictionary) {
569     SmallString<32> EqualSignJoined;
570     EqualSignJoined.append(Abbreviation.first());
571     EqualSignJoined.append("=");
572     EqualSignJoined.append(Abbreviation.second);
573 
574     if (!Abbreviation.second.empty())
575       Abbreviations.emplace_back(EqualSignJoined.str());
576   }
577   Options.store(Opts, "Abbreviations",
578                 optutils::serializeStringList(std::vector<StringRef>(
579                     Abbreviations.begin(), Abbreviations.end())));
580 }
581 
582 bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H) const {
583   return llvm::is_contained(AppliedHeuristics, H);
584 }
585 
586 std::optional<int8_t>
587 SuspiciousCallArgumentCheck::getBound(Heuristic H, BoundKind BK) const {
588   auto Idx = static_cast<std::size_t>(H);
589   assert(Idx < HeuristicCount);
590 
591   if (!Defaults[Idx].hasBounds())
592     return std::nullopt;
593 
594   switch (BK) {
595   case BoundKind::DissimilarBelow:
596     return ConfiguredBounds[Idx].first;
597   case BoundKind::SimilarAbove:
598     return ConfiguredBounds[Idx].second;
599   }
600   llvm_unreachable("Unhandled Bound kind.");
601 }
602 
603 void SuspiciousCallArgumentCheck::registerMatchers(MatchFinder *Finder) {
604   // Only match calls with at least 2 arguments.
605   Finder->addMatcher(
606       functionDecl(forEachDescendant(callExpr(unless(anyOf(argumentCountIs(0),
607                                                            argumentCountIs(1))))
608                                          .bind("functionCall")))
609           .bind("callingFunc"),
610       this);
611 }
612 
613 void SuspiciousCallArgumentCheck::check(
614     const MatchFinder::MatchResult &Result) {
615   const auto *MatchedCallExpr =
616       Result.Nodes.getNodeAs<CallExpr>("functionCall");
617   const auto *Caller = Result.Nodes.getNodeAs<FunctionDecl>("callingFunc");
618   assert(MatchedCallExpr && Caller);
619 
620   const Decl *CalleeDecl = MatchedCallExpr->getCalleeDecl();
621   if (!CalleeDecl)
622     return;
623 
624   const FunctionDecl *CalleeFuncDecl = CalleeDecl->getAsFunction();
625   if (!CalleeFuncDecl)
626     return;
627   if (CalleeFuncDecl == Caller)
628     // Ignore recursive calls.
629     return;
630   if (isOverloadedUnaryOrBinarySymbolOperator(CalleeFuncDecl))
631     return;
632 
633   // Get param attributes.
634   setParamNamesAndTypes(CalleeFuncDecl);
635 
636   if (ParamNames.empty())
637     return;
638 
639   // Get Arg attributes.
640   std::size_t InitialArgIndex = 0;
641 
642   if (const auto *MethodDecl = dyn_cast<CXXMethodDecl>(CalleeFuncDecl)) {
643     if (MethodDecl->getParent()->isLambda())
644       // Lambda functions' first Arg are the lambda object.
645       InitialArgIndex = 1;
646     else if (MethodDecl->getOverloadedOperator() == OO_Call)
647       // For custom operator()s, the first Arg is the called object.
648       InitialArgIndex = 1;
649   }
650 
651   setArgNamesAndTypes(MatchedCallExpr, InitialArgIndex);
652 
653   if (ArgNames.empty())
654     return;
655 
656   std::size_t ParamCount = ParamNames.size();
657 
658   // Check similarity.
659   for (std::size_t I = 0; I < ParamCount; ++I) {
660     for (std::size_t J = I + 1; J < ParamCount; ++J) {
661       // Do not check if param or arg names are short, or not convertible.
662       if (!areParamAndArgComparable(I, J, *Result.Context))
663         continue;
664       if (!areArgsSwapped(I, J))
665         continue;
666 
667       // Warning at the call itself.
668       diag(MatchedCallExpr->getExprLoc(),
669            "%ordinal0 argument '%1' (passed to '%2') looks like it might be "
670            "swapped with the %ordinal3, '%4' (passed to '%5')")
671           << static_cast<unsigned>(I + 1) << ArgNames[I] << ParamNames[I]
672           << static_cast<unsigned>(J + 1) << ArgNames[J] << ParamNames[J]
673           << MatchedCallExpr->getArg(I)->getSourceRange()
674           << MatchedCallExpr->getArg(J)->getSourceRange();
675 
676       // Note at the functions declaration.
677       SourceLocation IParNameLoc =
678           CalleeFuncDecl->getParamDecl(I)->getLocation();
679       SourceLocation JParNameLoc =
680           CalleeFuncDecl->getParamDecl(J)->getLocation();
681 
682       diag(CalleeFuncDecl->getLocation(), "in the call to %0, declared here",
683            DiagnosticIDs::Note)
684           << CalleeFuncDecl
685           << CharSourceRange::getTokenRange(IParNameLoc, IParNameLoc)
686           << CharSourceRange::getTokenRange(JParNameLoc, JParNameLoc);
687     }
688   }
689 }
690 
691 void SuspiciousCallArgumentCheck::setParamNamesAndTypes(
692     const FunctionDecl *CalleeFuncDecl) {
693   // Reset vectors, and fill them with the currently checked function's
694   // parameters' data.
695   ParamNames.clear();
696   ParamTypes.clear();
697 
698   for (const ParmVarDecl *Param : CalleeFuncDecl->parameters()) {
699     ParamTypes.push_back(Param->getType());
700 
701     if (IdentifierInfo *II = Param->getIdentifier())
702       ParamNames.push_back(II->getName());
703     else
704       ParamNames.push_back(StringRef());
705   }
706 }
707 
708 void SuspiciousCallArgumentCheck::setArgNamesAndTypes(
709     const CallExpr *MatchedCallExpr, std::size_t InitialArgIndex) {
710   // Reset vectors, and fill them with the currently checked function's
711   // arguments' data.
712   ArgNames.clear();
713   ArgTypes.clear();
714 
715   for (std::size_t I = InitialArgIndex, J = MatchedCallExpr->getNumArgs();
716        I < J; ++I) {
717     assert(ArgTypes.size() == I - InitialArgIndex &&
718            ArgNames.size() == ArgTypes.size() &&
719            "Every iteration must put an element into the vectors!");
720 
721     if (const auto *ArgExpr = dyn_cast<DeclRefExpr>(
722             MatchedCallExpr->getArg(I)->IgnoreUnlessSpelledInSource())) {
723       if (const auto *Var = dyn_cast<VarDecl>(ArgExpr->getDecl())) {
724         ArgTypes.push_back(Var->getType());
725         ArgNames.push_back(Var->getName());
726         continue;
727       }
728       if (const auto *FCall = dyn_cast<FunctionDecl>(ArgExpr->getDecl())) {
729         if (FCall->getNameInfo().getName().isIdentifier()) {
730           ArgTypes.push_back(FCall->getType());
731           ArgNames.push_back(FCall->getName());
732           continue;
733         }
734       }
735     }
736 
737     ArgTypes.push_back(QualType());
738     ArgNames.push_back(StringRef());
739   }
740 }
741 
742 bool SuspiciousCallArgumentCheck::areParamAndArgComparable(
743     std::size_t Position1, std::size_t Position2, const ASTContext &Ctx) const {
744   if (Position1 >= ArgNames.size() || Position2 >= ArgNames.size())
745     return false;
746 
747   // Do not report for too short strings.
748   if (ArgNames[Position1].size() < MinimumIdentifierNameLength ||
749       ArgNames[Position2].size() < MinimumIdentifierNameLength ||
750       ParamNames[Position1].size() < MinimumIdentifierNameLength ||
751       ParamNames[Position2].size() < MinimumIdentifierNameLength)
752     return false;
753 
754   if (!areTypesCompatible(ArgTypes[Position1], ParamTypes[Position2], Ctx) ||
755       !areTypesCompatible(ArgTypes[Position2], ParamTypes[Position1], Ctx))
756     return false;
757 
758   return true;
759 }
760 
761 bool SuspiciousCallArgumentCheck::areArgsSwapped(std::size_t Position1,
762                                                  std::size_t Position2) const {
763   for (Heuristic H : AppliedHeuristics) {
764     bool A1ToP2Similar = areNamesSimilar(
765         ArgNames[Position2], ParamNames[Position1], H, BoundKind::SimilarAbove);
766     bool A2ToP1Similar = areNamesSimilar(
767         ArgNames[Position1], ParamNames[Position2], H, BoundKind::SimilarAbove);
768 
769     bool A1ToP1Dissimilar =
770         !areNamesSimilar(ArgNames[Position1], ParamNames[Position1], H,
771                          BoundKind::DissimilarBelow);
772     bool A2ToP2Dissimilar =
773         !areNamesSimilar(ArgNames[Position2], ParamNames[Position2], H,
774                          BoundKind::DissimilarBelow);
775 
776     if ((A1ToP2Similar || A2ToP1Similar) && A1ToP1Dissimilar &&
777         A2ToP2Dissimilar)
778       return true;
779   }
780   return false;
781 }
782 
783 bool SuspiciousCallArgumentCheck::areNamesSimilar(StringRef Arg,
784                                                   StringRef Param, Heuristic H,
785                                                   BoundKind BK) const {
786   int8_t Threshold = -1;
787   if (std::optional<int8_t> GotBound = getBound(H, BK))
788     Threshold = *GotBound;
789 
790   switch (H) {
791   case Heuristic::Equality:
792     return applyEqualityHeuristic(Arg, Param);
793   case Heuristic::Abbreviation:
794     return applyAbbreviationHeuristic(AbbreviationDictionary, Arg, Param);
795   case Heuristic::Prefix:
796     return applyPrefixHeuristic(Arg, Param, Threshold);
797   case Heuristic::Suffix:
798     return applySuffixHeuristic(Arg, Param, Threshold);
799   case Heuristic::Substring:
800     return applySubstringHeuristic(Arg, Param, Threshold);
801   case Heuristic::Levenshtein:
802     return applyLevenshteinHeuristic(Arg, Param, Threshold);
803   case Heuristic::JaroWinkler:
804     return applyJaroWinklerHeuristic(Arg, Param, Threshold);
805   case Heuristic::Dice:
806     return applyDiceHeuristic(Arg, Param, Threshold);
807   }
808   llvm_unreachable("Unhandled heuristic kind");
809 }
810 
811 } // namespace clang::tidy::readability
812