brintos

brintos / llvm-project-archived public Read only

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