123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808 |
- //===--- 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 <optional>
- #include <sstream>
- 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 <std::size_t I> 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 <std::size_t I> struct HasWellConfiguredBoundsFold {
- static constexpr bool Value = HasWellConfiguredBounds<I>::Value &&
- HasWellConfiguredBoundsFold<I - 1>::Value;
- };
- template <> struct HasWellConfiguredBoundsFold<0> {
- static constexpr bool Value = HasWellConfiguredBounds<0>::Value;
- };
- struct AllHeuristicsBoundsWellConfigured {
- static constexpr bool Value =
- HasWellConfiguredBoundsFold<SuspiciousCallArgumentCheck::HeuristicCount -
- 1>::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<std::string> &AbbreviationDictionary, StringRef Arg,
- StringRef Param) {
- if (AbbreviationDictionary.find(Arg) != AbbreviationDictionary.end() &&
- Param.equals(AbbreviationDictionary.lookup(Arg)))
- return true;
- if (AbbreviationDictionary.find(Param) != AbbreviationDictionary.end() &&
- 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.startswith_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.endswith_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<std::size_t, SmallVectorSize> Current(Param.size());
- SmallVector<std::size_t, SmallVectorSize> 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<int, SmallVectorSize> ArgFlags(ArgLen);
- SmallVector<int, SmallVectorSize> 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;
- 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<std::ptrdiff_t>(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<std::ptrdiff_t>(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<EnumType>()->getDecl()->isScoped()) ||
- (ArgType->isEnumeralType() &&
- ArgType->castAs<EnumType>()->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<std::size_t>(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<std::size_t>(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<Heuristic>(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<std::size_t>(H);
- Options.store(Opts, HeuristicToString[Idx], isHeuristicEnabled(H));
- };
- const auto &SetBoundOpt = [this, &Opts](Heuristic H, BoundKind BK) -> void {
- auto Idx = static_cast<std::size_t>(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<Heuristic>(Idx);
- SetToggleOpt(H);
- SetBoundOpt(H, BoundKind::DissimilarBelow);
- SetBoundOpt(H, BoundKind::SimilarAbove);
- }
- SmallVector<std::string, 32> 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<StringRef>(
- Abbreviations.begin(), Abbreviations.end())));
- }
- bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H) const {
- return llvm::is_contained(AppliedHeuristics, H);
- }
- std::optional<int8_t>
- SuspiciousCallArgumentCheck::getBound(Heuristic H, BoundKind BK) const {
- auto Idx = static_cast<std::size_t>(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<CallExpr>("functionCall");
- const auto *Caller = Result.Nodes.getNodeAs<FunctionDecl>("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<CXXMethodDecl>(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<unsigned>(I + 1) << ArgNames[I] << ParamNames[I]
- << static_cast<unsigned>(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<DeclRefExpr>(
- MatchedCallExpr->getArg(I)->IgnoreUnlessSpelledInSource())) {
- if (const auto *Var = dyn_cast<VarDecl>(ArgExpr->getDecl())) {
- ArgTypes.push_back(Var->getType());
- ArgNames.push_back(Var->getName());
- continue;
- }
- if (const auto *FCall = dyn_cast<FunctionDecl>(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<int8_t> 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
|