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