//===--- SuspiciousCallArgumentCheck.cpp - clang-tidy ---------------------===// // // Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. // See https://llvm.org/LICENSE.txt for license information. // SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception // //===----------------------------------------------------------------------===// #include "SuspiciousCallArgumentCheck.h" #include "../utils/OptionsUtils.h" #include "clang/AST/ASTContext.h" #include "clang/AST/Type.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include #include using namespace clang::ast_matchers; namespace optutils = clang::tidy::utils::options; namespace clang::tidy::readability { namespace { struct DefaultHeuristicConfiguration { /// Whether the heuristic is to be enabled by default. const bool Enabled; /// The upper bound of % of similarity the two strings might have to be /// considered dissimilar. /// (For purposes of configuration, -1 if the heuristic is not configurable /// with bounds.) const int8_t DissimilarBelow; /// The lower bound of % of similarity the two string must have to be /// considered similar. /// (For purposes of configuration, -1 if the heuristic is not configurable /// with bounds.) const int8_t SimilarAbove; /// Can the heuristic be configured with bounds? bool hasBounds() const { return DissimilarBelow > -1 && SimilarAbove > -1; } }; } // namespace static constexpr std::size_t DefaultMinimumIdentifierNameLength = 3; static constexpr StringRef HeuristicToString[] = { "Equality", "Abbreviation", "Prefix", "Suffix", "Substring", "Levenshtein", "JaroWinkler", "Dice"}; static constexpr DefaultHeuristicConfiguration Defaults[] = { {true, -1, -1}, // Equality. {true, -1, -1}, // Abbreviation. {true, 25, 30}, // Prefix. {true, 25, 30}, // Suffix. {true, 40, 50}, // Substring. {true, 50, 66}, // Levenshtein. {true, 75, 85}, // Jaro-Winkler. {true, 60, 70}, // Dice. }; static_assert( sizeof(HeuristicToString) / sizeof(HeuristicToString[0]) == SuspiciousCallArgumentCheck::HeuristicCount, "Ensure that every heuristic has a corresponding stringified name"); static_assert(sizeof(Defaults) / sizeof(Defaults[0]) == SuspiciousCallArgumentCheck::HeuristicCount, "Ensure that every heuristic has a default configuration."); namespace { template struct HasWellConfiguredBounds { static constexpr bool Value = !((Defaults[I].DissimilarBelow == -1) ^ (Defaults[I].SimilarAbove == -1)); static_assert(Value, "A heuristic must either have a dissimilarity and " "similarity bound, or neither!"); }; template struct HasWellConfiguredBoundsFold { static constexpr bool Value = HasWellConfiguredBounds::Value && HasWellConfiguredBoundsFold::Value; }; template <> struct HasWellConfiguredBoundsFold<0> { static constexpr bool Value = HasWellConfiguredBounds<0>::Value; }; struct AllHeuristicsBoundsWellConfigured { static constexpr bool Value = HasWellConfiguredBoundsFold::Value; }; static_assert(AllHeuristicsBoundsWellConfigured::Value); } // namespace static constexpr llvm::StringLiteral DefaultAbbreviations = "addr=address;" "arr=array;" "attr=attribute;" "buf=buffer;" "cl=client;" "cnt=count;" "col=column;" "cpy=copy;" "dest=destination;" "dist=distance" "dst=distance;" "elem=element;" "hght=height;" "i=index;" "idx=index;" "len=length;" "ln=line;" "lst=list;" "nr=number;" "num=number;" "pos=position;" "ptr=pointer;" "ref=reference;" "src=source;" "srv=server;" "stmt=statement;" "str=string;" "val=value;" "var=variable;" "vec=vector;" "wdth=width"; static constexpr std::size_t SmallVectorSize = SuspiciousCallArgumentCheck::SmallVectorSize; /// Returns how many % X is of Y. static inline double percentage(double X, double Y) { return X / Y * 100.0; } static bool applyEqualityHeuristic(StringRef Arg, StringRef Param) { return Arg.equals_insensitive(Param); } static bool applyAbbreviationHeuristic( const llvm::StringMap &AbbreviationDictionary, StringRef Arg, StringRef Param) { if (AbbreviationDictionary.contains(Arg) && Param.equals(AbbreviationDictionary.lookup(Arg))) return true; if (AbbreviationDictionary.contains(Param) && Arg.equals(AbbreviationDictionary.lookup(Param))) return true; return false; } /// Check whether the shorter String is a prefix of the longer String. static bool applyPrefixHeuristic(StringRef Arg, StringRef Param, int8_t Threshold) { StringRef Shorter = Arg.size() < Param.size() ? Arg : Param; StringRef Longer = Arg.size() >= Param.size() ? Arg : Param; if (Longer.starts_with_insensitive(Shorter)) return percentage(Shorter.size(), Longer.size()) > Threshold; return false; } /// Check whether the shorter String is a suffix of the longer String. static bool applySuffixHeuristic(StringRef Arg, StringRef Param, int8_t Threshold) { StringRef Shorter = Arg.size() < Param.size() ? Arg : Param; StringRef Longer = Arg.size() >= Param.size() ? Arg : Param; if (Longer.ends_with_insensitive(Shorter)) return percentage(Shorter.size(), Longer.size()) > Threshold; return false; } static bool applySubstringHeuristic(StringRef Arg, StringRef Param, int8_t Threshold) { std::size_t MaxLength = 0; SmallVector Current(Param.size()); SmallVector Previous(Param.size()); std::string ArgLower = Arg.lower(); std::string ParamLower = Param.lower(); for (std::size_t I = 0; I < Arg.size(); ++I) { for (std::size_t J = 0; J < Param.size(); ++J) { if (ArgLower[I] == ParamLower[J]) { if (I == 0 || J == 0) Current[J] = 1; else Current[J] = 1 + Previous[J - 1]; MaxLength = std::max(MaxLength, Current[J]); } else Current[J] = 0; } Current.swap(Previous); } size_t LongerLength = std::max(Arg.size(), Param.size()); return percentage(MaxLength, LongerLength) > Threshold; } static bool applyLevenshteinHeuristic(StringRef Arg, StringRef Param, int8_t Threshold) { std::size_t LongerLength = std::max(Arg.size(), Param.size()); double Dist = Arg.edit_distance(Param); Dist = (1.0 - Dist / LongerLength) * 100.0; return Dist > Threshold; } // Based on http://en.wikipedia.org/wiki/Jaro–Winkler_distance. static bool applyJaroWinklerHeuristic(StringRef Arg, StringRef Param, int8_t Threshold) { std::size_t Match = 0, Transpos = 0; std::ptrdiff_t ArgLen = Arg.size(); std::ptrdiff_t ParamLen = Param.size(); SmallVector ArgFlags(ArgLen); SmallVector ParamFlags(ParamLen); std::ptrdiff_t Range = std::max(std::ptrdiff_t{0}, std::max(ArgLen, ParamLen) / 2 - 1); // Calculate matching characters. for (std::ptrdiff_t I = 0; I < ParamLen; ++I) for (std::ptrdiff_t J = std::max(I - Range, std::ptrdiff_t{0}), L = std::min(I + Range + 1, ArgLen); J < L; ++J) if (tolower(Param[I]) == tolower(Arg[J]) && !ArgFlags[J]) { ArgFlags[J] = 1; ParamFlags[I] = 1; ++Match; break; } if (!Match) return false; // Calculate character transpositions. std::ptrdiff_t L = 0; for (std::ptrdiff_t I = 0; I < ParamLen; ++I) { if (ParamFlags[I] == 1) { std::ptrdiff_t J = 0; for (J = L; J < ArgLen; ++J) if (ArgFlags[J] == 1) { L = J + 1; break; } if (tolower(Param[I]) != tolower(Arg[J])) ++Transpos; } } Transpos /= 2; // Jaro distance. double MatchD = Match; double Dist = ((MatchD / ArgLen) + (MatchD / ParamLen) + ((MatchD - Transpos) / Match)) / 3.0; // Calculate common string prefix up to 4 chars. L = 0; for (std::ptrdiff_t I = 0; I < std::min(std::min(ArgLen, ParamLen), std::ptrdiff_t{4}); ++I) if (tolower(Arg[I]) == tolower(Param[I])) ++L; // Jaro-Winkler distance. Dist = (Dist + (L * 0.1 * (1.0 - Dist))) * 100.0; return Dist > Threshold; } // Based on http://en.wikipedia.org/wiki/Sørensen–Dice_coefficient static bool applyDiceHeuristic(StringRef Arg, StringRef Param, int8_t Threshold) { llvm::StringSet<> ArgBigrams; llvm::StringSet<> ParamBigrams; // Extract character bigrams from Arg. for (std::ptrdiff_t I = 0; I < static_cast(Arg.size()) - 1; ++I) ArgBigrams.insert(Arg.substr(I, 2).lower()); // Extract character bigrams from Param. for (std::ptrdiff_t I = 0; I < static_cast(Param.size()) - 1; ++I) ParamBigrams.insert(Param.substr(I, 2).lower()); std::size_t Intersection = 0; // Find the intersection between the two sets. for (auto IT = ParamBigrams.begin(); IT != ParamBigrams.end(); ++IT) Intersection += ArgBigrams.count((IT->getKey())); // Calculate Dice coefficient. return percentage(Intersection * 2.0, ArgBigrams.size() + ParamBigrams.size()) > Threshold; } /// Checks if ArgType binds to ParamType regarding reference-ness and /// cv-qualifiers. static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType) { return !ParamType->isReferenceType() || ParamType.getNonReferenceType().isAtLeastAsQualifiedAs( ArgType.getNonReferenceType()); } static bool isPointerOrArray(QualType TypeToCheck) { return TypeToCheck->isPointerType() || TypeToCheck->isArrayType(); } /// Checks whether ArgType is an array type identical to ParamType's array type. /// Enforces array elements' qualifier compatibility as well. static bool isCompatibleWithArrayReference(QualType ArgType, QualType ParamType) { if (!ArgType->isArrayType()) return false; // Here, qualifiers belong to the elements of the arrays. if (!ParamType.isAtLeastAsQualifiedAs(ArgType)) return false; return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType(); } static QualType convertToPointeeOrArrayElementQualType(QualType TypeToConvert) { unsigned CVRqualifiers = 0; // Save array element qualifiers, since getElementType() removes qualifiers // from array elements. if (TypeToConvert->isArrayType()) CVRqualifiers = TypeToConvert.getLocalQualifiers().getCVRQualifiers(); TypeToConvert = TypeToConvert->isPointerType() ? TypeToConvert->getPointeeType() : TypeToConvert->getAsArrayTypeUnsafe()->getElementType(); TypeToConvert = TypeToConvert.withCVRQualifiers(CVRqualifiers); return TypeToConvert; } /// Checks if multilevel pointers' qualifiers compatibility continues on the /// current pointer level. For multilevel pointers, C++ permits conversion, if /// every cv-qualifier in ArgType also appears in the corresponding position in /// ParamType, and if PramType has a cv-qualifier that's not in ArgType, then /// every * in ParamType to the right of that cv-qualifier, except the last /// one, must also be const-qualified. static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType, bool &IsParamContinuouslyConst) { // The types are compatible, if the parameter is at least as qualified as the // argument, and if it is more qualified, it has to be const on upper pointer // levels. bool AreTypesQualCompatible = ParamType.isAtLeastAsQualifiedAs(ArgType) && (!ParamType.hasQualifiers() || IsParamContinuouslyConst); // Check whether the parameter's constness continues at the current pointer // level. IsParamContinuouslyConst &= ParamType.isConstQualified(); return AreTypesQualCompatible; } /// Checks whether multilevel pointers are compatible in terms of levels, /// qualifiers and pointee type. static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType, bool IsParamContinuouslyConst) { if (!arePointersStillQualCompatible(ArgType, ParamType, IsParamContinuouslyConst)) return false; do { // Step down one pointer level. ArgType = convertToPointeeOrArrayElementQualType(ArgType); ParamType = convertToPointeeOrArrayElementQualType(ParamType); // Check whether cv-qualifiers permit compatibility on // current level. if (!arePointersStillQualCompatible(ArgType, ParamType, IsParamContinuouslyConst)) return false; if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) return true; } while (ParamType->isPointerType() && ArgType->isPointerType()); // The final type does not match, or pointer levels differ. return false; } /// Checks whether ArgType converts implicitly to ParamType. static bool areTypesCompatible(QualType ArgType, QualType ParamType, const ASTContext &Ctx) { if (ArgType.isNull() || ParamType.isNull()) return false; ArgType = ArgType.getCanonicalType(); ParamType = ParamType.getCanonicalType(); if (ArgType == ParamType) return true; // Check for constness and reference compatibility. if (!areRefAndQualCompatible(ArgType, ParamType)) return false; bool IsParamReference = ParamType->isReferenceType(); // Reference-ness has already been checked and should be removed // before further checking. ArgType = ArgType.getNonReferenceType(); ParamType = ParamType.getNonReferenceType(); if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) return true; // Arithmetic types are interconvertible, except scoped enums. if (ParamType->isArithmeticType() && ArgType->isArithmeticType()) { if ((ParamType->isEnumeralType() && ParamType->castAs()->getDecl()->isScoped()) || (ArgType->isEnumeralType() && ArgType->castAs()->getDecl()->isScoped())) return false; return true; } // Check if the argument and the param are both function types (the parameter // decayed to a function pointer). if (ArgType->isFunctionType() && ParamType->isFunctionPointerType()) { ParamType = ParamType->getPointeeType(); return ArgType == ParamType; } // Arrays or pointer arguments convert to array or pointer parameters. if (!(isPointerOrArray(ArgType) && isPointerOrArray(ParamType))) return false; // When ParamType is an array reference, ArgType has to be of the same-sized // array-type with cv-compatible element type. if (IsParamReference && ParamType->isArrayType()) return isCompatibleWithArrayReference(ArgType, ParamType); bool IsParamContinuouslyConst = !IsParamReference || ParamType.getNonReferenceType().isConstQualified(); // Remove the first level of indirection. ArgType = convertToPointeeOrArrayElementQualType(ArgType); ParamType = convertToPointeeOrArrayElementQualType(ParamType); // Check qualifier compatibility on the next level. if (!ParamType.isAtLeastAsQualifiedAs(ArgType)) return false; if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType()) return true; // At this point, all possible C language implicit conversion were checked. if (!Ctx.getLangOpts().CPlusPlus) return false; // Check whether ParamType and ArgType were both pointers to a class or a // struct, and check for inheritance. if (ParamType->isStructureOrClassType() && ArgType->isStructureOrClassType()) { const auto *ArgDecl = ArgType->getAsCXXRecordDecl(); const auto *ParamDecl = ParamType->getAsCXXRecordDecl(); if (!ArgDecl || !ArgDecl->hasDefinition() || !ParamDecl || !ParamDecl->hasDefinition()) return false; return ArgDecl->isDerivedFrom(ParamDecl); } // Unless argument and param are both multilevel pointers, the types are not // convertible. if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType())) return false; return arePointerTypesCompatible(ArgType, ParamType, IsParamContinuouslyConst); } static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD) { switch (FD->getOverloadedOperator()) { case OO_None: case OO_Call: case OO_Subscript: case OO_New: case OO_Delete: case OO_Array_New: case OO_Array_Delete: case OO_Conditional: case OO_Coawait: return false; default: return FD->getNumParams() <= 2; } } SuspiciousCallArgumentCheck::SuspiciousCallArgumentCheck( StringRef Name, ClangTidyContext *Context) : ClangTidyCheck(Name, Context), MinimumIdentifierNameLength(Options.get( "MinimumIdentifierNameLength", DefaultMinimumIdentifierNameLength)) { auto GetToggleOpt = [this](Heuristic H) -> bool { auto Idx = static_cast(H); assert(Idx < HeuristicCount); return Options.get(HeuristicToString[Idx], Defaults[Idx].Enabled); }; auto GetBoundOpt = [this](Heuristic H, BoundKind BK) -> int8_t { auto Idx = static_cast(H); assert(Idx < HeuristicCount); SmallString<32> Key = HeuristicToString[Idx]; Key.append(BK == BoundKind::DissimilarBelow ? "DissimilarBelow" : "SimilarAbove"); int8_t Default = BK == BoundKind::DissimilarBelow ? Defaults[Idx].DissimilarBelow : Defaults[Idx].SimilarAbove; return Options.get(Key, Default); }; for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) { auto H = static_cast(Idx); if (GetToggleOpt(H)) AppliedHeuristics.emplace_back(H); ConfiguredBounds.emplace_back( std::make_pair(GetBoundOpt(H, BoundKind::DissimilarBelow), GetBoundOpt(H, BoundKind::SimilarAbove))); } for (StringRef Abbreviation : optutils::parseStringList( Options.get("Abbreviations", DefaultAbbreviations))) { auto KeyAndValue = Abbreviation.split("="); assert(!KeyAndValue.first.empty() && !KeyAndValue.second.empty()); AbbreviationDictionary.insert( std::make_pair(KeyAndValue.first, KeyAndValue.second.str())); } } void SuspiciousCallArgumentCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { Options.store(Opts, "MinimumIdentifierNameLength", MinimumIdentifierNameLength); const auto &SetToggleOpt = [this, &Opts](Heuristic H) -> void { auto Idx = static_cast(H); Options.store(Opts, HeuristicToString[Idx], isHeuristicEnabled(H)); }; const auto &SetBoundOpt = [this, &Opts](Heuristic H, BoundKind BK) -> void { auto Idx = static_cast(H); assert(Idx < HeuristicCount); if (!Defaults[Idx].hasBounds()) return; SmallString<32> Key = HeuristicToString[Idx]; Key.append(BK == BoundKind::DissimilarBelow ? "DissimilarBelow" : "SimilarAbove"); Options.store(Opts, Key, *getBound(H, BK)); }; for (std::size_t Idx = 0; Idx < HeuristicCount; ++Idx) { auto H = static_cast(Idx); SetToggleOpt(H); SetBoundOpt(H, BoundKind::DissimilarBelow); SetBoundOpt(H, BoundKind::SimilarAbove); } SmallVector Abbreviations; for (const auto &Abbreviation : AbbreviationDictionary) { SmallString<32> EqualSignJoined; EqualSignJoined.append(Abbreviation.first()); EqualSignJoined.append("="); EqualSignJoined.append(Abbreviation.second); if (!Abbreviation.second.empty()) Abbreviations.emplace_back(EqualSignJoined.str()); } Options.store(Opts, "Abbreviations", optutils::serializeStringList(std::vector( Abbreviations.begin(), Abbreviations.end()))); } bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H) const { return llvm::is_contained(AppliedHeuristics, H); } std::optional SuspiciousCallArgumentCheck::getBound(Heuristic H, BoundKind BK) const { auto Idx = static_cast(H); assert(Idx < HeuristicCount); if (!Defaults[Idx].hasBounds()) return std::nullopt; switch (BK) { case BoundKind::DissimilarBelow: return ConfiguredBounds[Idx].first; case BoundKind::SimilarAbove: return ConfiguredBounds[Idx].second; } llvm_unreachable("Unhandled Bound kind."); } void SuspiciousCallArgumentCheck::registerMatchers(MatchFinder *Finder) { // Only match calls with at least 2 arguments. Finder->addMatcher( functionDecl(forEachDescendant(callExpr(unless(anyOf(argumentCountIs(0), argumentCountIs(1)))) .bind("functionCall"))) .bind("callingFunc"), this); } void SuspiciousCallArgumentCheck::check( const MatchFinder::MatchResult &Result) { const auto *MatchedCallExpr = Result.Nodes.getNodeAs("functionCall"); const auto *Caller = Result.Nodes.getNodeAs("callingFunc"); assert(MatchedCallExpr && Caller); const Decl *CalleeDecl = MatchedCallExpr->getCalleeDecl(); if (!CalleeDecl) return; const FunctionDecl *CalleeFuncDecl = CalleeDecl->getAsFunction(); if (!CalleeFuncDecl) return; if (CalleeFuncDecl == Caller) // Ignore recursive calls. return; if (isOverloadedUnaryOrBinarySymbolOperator(CalleeFuncDecl)) return; // Get param attributes. setParamNamesAndTypes(CalleeFuncDecl); if (ParamNames.empty()) return; // Get Arg attributes. std::size_t InitialArgIndex = 0; if (const auto *MethodDecl = dyn_cast(CalleeFuncDecl)) { if (MethodDecl->getParent()->isLambda()) // Lambda functions' first Arg are the lambda object. InitialArgIndex = 1; else if (MethodDecl->getOverloadedOperator() == OO_Call) // For custom operator()s, the first Arg is the called object. InitialArgIndex = 1; } setArgNamesAndTypes(MatchedCallExpr, InitialArgIndex); if (ArgNames.empty()) return; std::size_t ParamCount = ParamNames.size(); // Check similarity. for (std::size_t I = 0; I < ParamCount; ++I) { for (std::size_t J = I + 1; J < ParamCount; ++J) { // Do not check if param or arg names are short, or not convertible. if (!areParamAndArgComparable(I, J, *Result.Context)) continue; if (!areArgsSwapped(I, J)) continue; // Warning at the call itself. diag(MatchedCallExpr->getExprLoc(), "%ordinal0 argument '%1' (passed to '%2') looks like it might be " "swapped with the %ordinal3, '%4' (passed to '%5')") << static_cast(I + 1) << ArgNames[I] << ParamNames[I] << static_cast(J + 1) << ArgNames[J] << ParamNames[J] << MatchedCallExpr->getArg(I)->getSourceRange() << MatchedCallExpr->getArg(J)->getSourceRange(); // Note at the functions declaration. SourceLocation IParNameLoc = CalleeFuncDecl->getParamDecl(I)->getLocation(); SourceLocation JParNameLoc = CalleeFuncDecl->getParamDecl(J)->getLocation(); diag(CalleeFuncDecl->getLocation(), "in the call to %0, declared here", DiagnosticIDs::Note) << CalleeFuncDecl << CharSourceRange::getTokenRange(IParNameLoc, IParNameLoc) << CharSourceRange::getTokenRange(JParNameLoc, JParNameLoc); } } } void SuspiciousCallArgumentCheck::setParamNamesAndTypes( const FunctionDecl *CalleeFuncDecl) { // Reset vectors, and fill them with the currently checked function's // parameters' data. ParamNames.clear(); ParamTypes.clear(); for (const ParmVarDecl *Param : CalleeFuncDecl->parameters()) { ParamTypes.push_back(Param->getType()); if (IdentifierInfo *II = Param->getIdentifier()) ParamNames.push_back(II->getName()); else ParamNames.push_back(StringRef()); } } void SuspiciousCallArgumentCheck::setArgNamesAndTypes( const CallExpr *MatchedCallExpr, std::size_t InitialArgIndex) { // Reset vectors, and fill them with the currently checked function's // arguments' data. ArgNames.clear(); ArgTypes.clear(); for (std::size_t I = InitialArgIndex, J = MatchedCallExpr->getNumArgs(); I < J; ++I) { assert(ArgTypes.size() == I - InitialArgIndex && ArgNames.size() == ArgTypes.size() && "Every iteration must put an element into the vectors!"); if (const auto *ArgExpr = dyn_cast( MatchedCallExpr->getArg(I)->IgnoreUnlessSpelledInSource())) { if (const auto *Var = dyn_cast(ArgExpr->getDecl())) { ArgTypes.push_back(Var->getType()); ArgNames.push_back(Var->getName()); continue; } if (const auto *FCall = dyn_cast(ArgExpr->getDecl())) { if (FCall->getNameInfo().getName().isIdentifier()) { ArgTypes.push_back(FCall->getType()); ArgNames.push_back(FCall->getName()); continue; } } } ArgTypes.push_back(QualType()); ArgNames.push_back(StringRef()); } } bool SuspiciousCallArgumentCheck::areParamAndArgComparable( std::size_t Position1, std::size_t Position2, const ASTContext &Ctx) const { if (Position1 >= ArgNames.size() || Position2 >= ArgNames.size()) return false; // Do not report for too short strings. if (ArgNames[Position1].size() < MinimumIdentifierNameLength || ArgNames[Position2].size() < MinimumIdentifierNameLength || ParamNames[Position1].size() < MinimumIdentifierNameLength || ParamNames[Position2].size() < MinimumIdentifierNameLength) return false; if (!areTypesCompatible(ArgTypes[Position1], ParamTypes[Position2], Ctx) || !areTypesCompatible(ArgTypes[Position2], ParamTypes[Position1], Ctx)) return false; return true; } bool SuspiciousCallArgumentCheck::areArgsSwapped(std::size_t Position1, std::size_t Position2) const { for (Heuristic H : AppliedHeuristics) { bool A1ToP2Similar = areNamesSimilar( ArgNames[Position2], ParamNames[Position1], H, BoundKind::SimilarAbove); bool A2ToP1Similar = areNamesSimilar( ArgNames[Position1], ParamNames[Position2], H, BoundKind::SimilarAbove); bool A1ToP1Dissimilar = !areNamesSimilar(ArgNames[Position1], ParamNames[Position1], H, BoundKind::DissimilarBelow); bool A2ToP2Dissimilar = !areNamesSimilar(ArgNames[Position2], ParamNames[Position2], H, BoundKind::DissimilarBelow); if ((A1ToP2Similar || A2ToP1Similar) && A1ToP1Dissimilar && A2ToP2Dissimilar) return true; } return false; } bool SuspiciousCallArgumentCheck::areNamesSimilar(StringRef Arg, StringRef Param, Heuristic H, BoundKind BK) const { int8_t Threshold = -1; if (std::optional GotBound = getBound(H, BK)) Threshold = *GotBound; switch (H) { case Heuristic::Equality: return applyEqualityHeuristic(Arg, Param); case Heuristic::Abbreviation: return applyAbbreviationHeuristic(AbbreviationDictionary, Arg, Param); case Heuristic::Prefix: return applyPrefixHeuristic(Arg, Param, Threshold); case Heuristic::Suffix: return applySuffixHeuristic(Arg, Param, Threshold); case Heuristic::Substring: return applySubstringHeuristic(Arg, Param, Threshold); case Heuristic::Levenshtein: return applyLevenshteinHeuristic(Arg, Param, Threshold); case Heuristic::JaroWinkler: return applyJaroWinklerHeuristic(Arg, Param, Threshold); case Heuristic::Dice: return applyDiceHeuristic(Arg, Param, Threshold); } llvm_unreachable("Unhandled heuristic kind"); } } // namespace clang::tidy::readability