10#include "../utils/OptionsUtils.h"
11#include "clang/AST/ASTContext.h"
12#include "clang/AST/Type.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
23struct DefaultHeuristicConfiguration {
47 "Equality",
"Abbreviation",
"Prefix",
"Suffix",
48 "Substring",
"Levenshtein",
"JaroWinkler",
"Dice"};
50static constexpr DefaultHeuristicConfiguration
Defaults[] = {
64 "Ensure that every heuristic has a corresponding stringified name");
67 "Ensure that every heuristic has a default configuration.");
70template <std::
size_t I>
struct HasWellConfiguredBounds {
71 static constexpr bool Value =
73 static_assert(Value,
"A heuristic must either have a dissimilarity and "
74 "similarity bound, or neither!");
77template <std::
size_t I>
struct HasWellConfiguredBoundsFold {
78 static constexpr bool Value = HasWellConfiguredBounds<I>::Value &&
79 HasWellConfiguredBoundsFold<I - 1>::Value;
82template <>
struct HasWellConfiguredBoundsFold<0> {
83 static constexpr bool Value = HasWellConfiguredBounds<0>::Value;
86struct AllHeuristicsBoundsWellConfigured {
87 static constexpr bool Value =
92static_assert(AllHeuristicsBoundsWellConfigured::Value);
131static inline double percentage(
double X,
double Y) {
return X / Y * 100.0; }
134 return Arg.equals_insensitive(Param);
138 const llvm::StringMap<std::string> &AbbreviationDictionary, StringRef Arg,
140 if (AbbreviationDictionary.contains(Arg) &&
141 Param == AbbreviationDictionary.lookup(Arg))
144 if (AbbreviationDictionary.contains(Param) &&
145 Arg == AbbreviationDictionary.lookup(Param))
154 StringRef Shorter = Arg.size() < Param.size() ? Arg : Param;
155 StringRef Longer = Arg.size() >= Param.size() ? Arg : Param;
157 if (Longer.starts_with_insensitive(Shorter))
158 return percentage(Shorter.size(), Longer.size()) > Threshold;
166 StringRef Shorter = Arg.size() < Param.size() ? Arg : Param;
167 StringRef Longer = Arg.size() >= Param.size() ? Arg : Param;
169 if (Longer.ends_with_insensitive(Shorter))
170 return percentage(Shorter.size(), Longer.size()) > Threshold;
178 std::size_t MaxLength = 0;
179 SmallVector<std::size_t, SmallVectorSize> Current(Param.size());
180 SmallVector<std::size_t, SmallVectorSize> Previous(Param.size());
181 std::string ArgLower = Arg.lower();
182 std::string ParamLower = Param.lower();
184 for (std::size_t I = 0; I < Arg.size(); ++I) {
185 for (std::size_t J = 0; J < Param.size(); ++J) {
186 if (ArgLower[I] == ParamLower[J]) {
187 if (I == 0 || J == 0)
190 Current[J] = 1 + Previous[J - 1];
192 MaxLength = std::max(MaxLength, Current[J]);
197 Current.swap(Previous);
200 size_t LongerLength = std::max(Arg.size(), Param.size());
201 return percentage(MaxLength, LongerLength) > Threshold;
206 std::size_t LongerLength = std::max(Arg.size(), Param.size());
207 double Dist = Arg.edit_distance(Param);
208 Dist = (1.0 -
Dist / LongerLength) * 100.0;
209 return Dist > Threshold;
215 std::size_t Match = 0, Transpos = 0;
216 std::ptrdiff_t ArgLen = Arg.size();
217 std::ptrdiff_t ParamLen = Param.size();
218 SmallVector<int, SmallVectorSize> ArgFlags(ArgLen);
219 SmallVector<int, SmallVectorSize> ParamFlags(ParamLen);
220 std::ptrdiff_t
Range =
221 std::max(std::ptrdiff_t{0}, std::max(ArgLen, ParamLen) / 2 - 1);
224 for (std::ptrdiff_t I = 0; I < ParamLen; ++I)
225 for (std::ptrdiff_t J = std::max(I -
Range, std::ptrdiff_t{0}),
226 L = std::min(I +
Range + 1, ArgLen);
228 if (tolower(Param[I]) == tolower(Arg[J]) && !ArgFlags[J]) {
239 std::ptrdiff_t L = 0;
240 for (std::ptrdiff_t I = 0; I < ParamLen; ++I) {
241 if (ParamFlags[I] == 1) {
242 std::ptrdiff_t J = 0;
243 for (J = L; J < ArgLen; ++J)
244 if (ArgFlags[J] == 1) {
249 if (tolower(Param[I]) != tolower(Arg[J]))
256 double MatchD = Match;
257 double Dist = ((MatchD / ArgLen) + (MatchD / ParamLen) +
258 ((MatchD - Transpos) / Match)) /
263 for (std::ptrdiff_t I = 0;
264 I < std::min(std::min(ArgLen, ParamLen), std::ptrdiff_t{4}); ++I)
265 if (tolower(Arg[I]) == tolower(Param[I]))
270 return Dist > Threshold;
276 llvm::StringSet<> ArgBigrams;
277 llvm::StringSet<> ParamBigrams;
280 for (std::ptrdiff_t I = 0; I < static_cast<std::ptrdiff_t>(Arg.size()) - 1;
282 ArgBigrams.insert(Arg.substr(I, 2).lower());
285 for (std::ptrdiff_t I = 0; I < static_cast<std::ptrdiff_t>(Param.size()) - 1;
287 ParamBigrams.insert(Param.substr(I, 2).lower());
289 std::size_t Intersection = 0;
292 for (
auto IT = ParamBigrams.begin(); IT != ParamBigrams.end(); ++IT)
293 Intersection += ArgBigrams.count((IT->getKey()));
297 ArgBigrams.size() + ParamBigrams.size()) > Threshold;
303 return !ParamType->isReferenceType() ||
304 ParamType.getNonReferenceType().isAtLeastAsQualifiedAs(
305 ArgType.getNonReferenceType());
309 return TypeToCheck->isPointerType() || TypeToCheck->isArrayType();
315 QualType ParamType) {
316 if (!ArgType->isArrayType())
319 if (!ParamType.isAtLeastAsQualifiedAs(ArgType))
322 return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType();
326 unsigned CVRqualifiers = 0;
329 if (TypeToConvert->isArrayType())
330 CVRqualifiers = TypeToConvert.getLocalQualifiers().getCVRQualifiers();
331 TypeToConvert = TypeToConvert->isPointerType()
332 ? TypeToConvert->getPointeeType()
333 : TypeToConvert->getAsArrayTypeUnsafe()->getElementType();
334 TypeToConvert = TypeToConvert.withCVRQualifiers(CVRqualifiers);
335 return TypeToConvert;
345 bool &IsParamContinuouslyConst) {
349 bool AreTypesQualCompatible =
350 ParamType.isAtLeastAsQualifiedAs(ArgType) &&
351 (!ParamType.hasQualifiers() || IsParamContinuouslyConst);
354 IsParamContinuouslyConst &= ParamType.isConstQualified();
356 return AreTypesQualCompatible;
362 bool IsParamContinuouslyConst) {
364 IsParamContinuouslyConst))
375 IsParamContinuouslyConst))
378 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
381 }
while (ParamType->isPointerType() && ArgType->isPointerType());
388 const ASTContext &Ctx) {
389 if (ArgType.isNull() || ParamType.isNull())
392 ArgType = ArgType.getCanonicalType();
393 ParamType = ParamType.getCanonicalType();
395 if (ArgType == ParamType)
402 bool IsParamReference = ParamType->isReferenceType();
406 ArgType = ArgType.getNonReferenceType();
407 ParamType = ParamType.getNonReferenceType();
409 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
413 if (ParamType->isArithmeticType() && ArgType->isArithmeticType()) {
414 if ((ParamType->isEnumeralType() &&
415 ParamType->castAs<EnumType>()->getDecl()->isScoped()) ||
416 (ArgType->isEnumeralType() &&
417 ArgType->castAs<EnumType>()->getDecl()->isScoped()))
425 if (ArgType->isFunctionType() && ParamType->isFunctionPointerType()) {
426 ParamType = ParamType->getPointeeType();
427 return ArgType == ParamType;
436 if (IsParamReference && ParamType->isArrayType())
439 bool IsParamContinuouslyConst =
440 !IsParamReference || ParamType.getNonReferenceType().isConstQualified();
447 if (!ParamType.isAtLeastAsQualifiedAs(ArgType))
450 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
454 if (!Ctx.getLangOpts().CPlusPlus)
459 if (ParamType->isStructureOrClassType() &&
460 ArgType->isStructureOrClassType()) {
461 const auto *ArgDecl = ArgType->getAsCXXRecordDecl();
462 const auto *ParamDecl = ParamType->getAsCXXRecordDecl();
463 if (!ArgDecl || !ArgDecl->hasDefinition() || !ParamDecl ||
464 !ParamDecl->hasDefinition())
467 return ArgDecl->isDerivedFrom(ParamDecl);
472 if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType()))
476 IsParamContinuouslyConst);
480 switch (FD->getOverloadedOperator()) {
487 case OO_Array_Delete:
493 return FD->getNumParams() <= 2;
500 MinimumIdentifierNameLength(Options.get(
502 auto GetToggleOpt = [
this](Heuristic H) ->
bool {
503 auto Idx =
static_cast<std::size_t
>(H);
507 auto GetBoundOpt = [
this](Heuristic H, BoundKind BK) -> int8_t {
508 auto Idx =
static_cast<std::size_t
>(H);
512 Key.append(BK == BoundKind::DissimilarBelow ?
"DissimilarBelow"
514 int8_t Default = BK == BoundKind::DissimilarBelow
520 auto H =
static_cast<Heuristic
>(Idx);
522 AppliedHeuristics.emplace_back(H);
523 ConfiguredBounds.emplace_back(
524 std::make_pair(GetBoundOpt(H, BoundKind::DissimilarBelow),
525 GetBoundOpt(H, BoundKind::SimilarAbove)));
530 auto KeyAndValue = Abbreviation.split(
"=");
531 assert(!KeyAndValue.first.empty() && !KeyAndValue.second.empty());
532 AbbreviationDictionary.insert(
533 std::make_pair(KeyAndValue.first, KeyAndValue.second.str()));
540 MinimumIdentifierNameLength);
541 const auto &SetToggleOpt = [
this, &Opts](Heuristic H) ->
void {
542 auto Idx =
static_cast<std::size_t
>(H);
545 const auto &SetBoundOpt = [
this, &Opts](Heuristic H, BoundKind BK) ->
void {
546 auto Idx =
static_cast<std::size_t
>(H);
552 Key.append(BK == BoundKind::DissimilarBelow ?
"DissimilarBelow"
558 auto H =
static_cast<Heuristic
>(Idx);
560 SetBoundOpt(H, BoundKind::DissimilarBelow);
561 SetBoundOpt(H, BoundKind::SimilarAbove);
564 SmallVector<std::string, 32> Abbreviations;
565 for (
const auto &Abbreviation : AbbreviationDictionary) {
566 SmallString<32> EqualSignJoined;
567 EqualSignJoined.append(Abbreviation.first());
568 EqualSignJoined.append(
"=");
569 EqualSignJoined.append(Abbreviation.second);
571 if (!Abbreviation.second.empty())
572 Abbreviations.emplace_back(EqualSignJoined.str());
576 Abbreviations.begin(), Abbreviations.end())));
579bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H)
const {
580 return llvm::is_contained(AppliedHeuristics, H);
584SuspiciousCallArgumentCheck::getBound(Heuristic H, BoundKind BK)
const {
585 auto Idx =
static_cast<std::size_t
>(H);
592 case BoundKind::DissimilarBelow:
593 return ConfiguredBounds[Idx].first;
594 case BoundKind::SimilarAbove:
595 return ConfiguredBounds[Idx].second;
597 llvm_unreachable(
"Unhandled Bound kind.");
603 functionDecl(forEachDescendant(callExpr(unless(anyOf(argumentCountIs(0),
604 argumentCountIs(1))))
605 .bind(
"functionCall")))
606 .bind(
"callingFunc"),
611 const MatchFinder::MatchResult &Result) {
612 const auto *MatchedCallExpr =
613 Result.Nodes.getNodeAs<CallExpr>(
"functionCall");
614 const auto *Caller = Result.Nodes.getNodeAs<FunctionDecl>(
"callingFunc");
615 assert(MatchedCallExpr && Caller);
617 const Decl *CalleeDecl = MatchedCallExpr->getCalleeDecl();
621 const FunctionDecl *CalleeFuncDecl = CalleeDecl->getAsFunction();
624 if (CalleeFuncDecl == Caller)
631 setParamNamesAndTypes(CalleeFuncDecl);
633 if (ParamNames.empty())
637 std::size_t InitialArgIndex = 0;
639 if (
const auto *MethodDecl = dyn_cast<CXXMethodDecl>(CalleeFuncDecl)) {
640 if (MethodDecl->getParent()->isLambda())
643 else if (MethodDecl->getOverloadedOperator() == OO_Call)
648 setArgNamesAndTypes(MatchedCallExpr, InitialArgIndex);
650 if (ArgNames.empty())
653 std::size_t ParamCount = ParamNames.size();
656 for (std::size_t I = 0; I < ParamCount; ++I) {
657 for (std::size_t J = I + 1; J < ParamCount; ++J) {
659 if (!areParamAndArgComparable(I, J, *Result.Context))
661 if (!areArgsSwapped(I, J))
665 diag(MatchedCallExpr->getExprLoc(),
666 "%ordinal0 argument '%1' (passed to '%2') looks like it might be "
667 "swapped with the %ordinal3, '%4' (passed to '%5')")
668 <<
static_cast<unsigned>(I + 1) << ArgNames[I] << ParamNames[I]
669 <<
static_cast<unsigned>(J + 1) << ArgNames[J] << ParamNames[J]
670 << MatchedCallExpr->getArg(I)->getSourceRange()
671 << MatchedCallExpr->getArg(J)->getSourceRange();
674 SourceLocation IParNameLoc =
675 CalleeFuncDecl->getParamDecl(I)->getLocation();
676 SourceLocation JParNameLoc =
677 CalleeFuncDecl->getParamDecl(J)->getLocation();
679 diag(CalleeFuncDecl->getLocation(),
"in the call to %0, declared here",
682 << CharSourceRange::getTokenRange(IParNameLoc, IParNameLoc)
683 << CharSourceRange::getTokenRange(JParNameLoc, JParNameLoc);
688void SuspiciousCallArgumentCheck::setParamNamesAndTypes(
689 const FunctionDecl *CalleeFuncDecl) {
695 for (
const ParmVarDecl *Param : CalleeFuncDecl->parameters()) {
696 ParamTypes.push_back(Param->getType());
698 if (IdentifierInfo *II = Param->getIdentifier())
699 ParamNames.push_back(II->getName());
701 ParamNames.push_back(StringRef());
705void SuspiciousCallArgumentCheck::setArgNamesAndTypes(
706 const CallExpr *MatchedCallExpr, std::size_t InitialArgIndex) {
712 for (std::size_t I = InitialArgIndex, J = MatchedCallExpr->getNumArgs();
714 assert(ArgTypes.size() == I - InitialArgIndex &&
715 ArgNames.size() == ArgTypes.size() &&
716 "Every iteration must put an element into the vectors!");
718 if (
const auto *ArgExpr = dyn_cast<DeclRefExpr>(
719 MatchedCallExpr->getArg(I)->IgnoreUnlessSpelledInSource())) {
720 if (
const auto *Var = dyn_cast<VarDecl>(ArgExpr->getDecl())) {
721 ArgTypes.push_back(Var->getType());
722 ArgNames.push_back(Var->getName());
725 if (
const auto *FCall = dyn_cast<FunctionDecl>(ArgExpr->getDecl())) {
726 if (FCall->getNameInfo().getName().isIdentifier()) {
727 ArgTypes.push_back(FCall->getType());
728 ArgNames.push_back(FCall->getName());
734 ArgTypes.push_back(QualType());
735 ArgNames.push_back(StringRef());
739bool SuspiciousCallArgumentCheck::areParamAndArgComparable(
740 std::size_t Position1, std::size_t Position2,
const ASTContext &Ctx)
const {
741 if (Position1 >= ArgNames.size() || Position2 >= ArgNames.size())
745 if (ArgNames[Position1].size() < MinimumIdentifierNameLength ||
746 ArgNames[Position2].size() < MinimumIdentifierNameLength ||
747 ParamNames[Position1].size() < MinimumIdentifierNameLength ||
748 ParamNames[Position2].size() < MinimumIdentifierNameLength)
758bool SuspiciousCallArgumentCheck::areArgsSwapped(std::size_t Position1,
759 std::size_t Position2)
const {
760 for (Heuristic H : AppliedHeuristics) {
761 bool A1ToP2Similar = areNamesSimilar(
762 ArgNames[Position2], ParamNames[Position1], H, BoundKind::SimilarAbove);
763 bool A2ToP1Similar = areNamesSimilar(
764 ArgNames[Position1], ParamNames[Position2], H, BoundKind::SimilarAbove);
766 bool A1ToP1Dissimilar =
767 !areNamesSimilar(ArgNames[Position1], ParamNames[Position1], H,
768 BoundKind::DissimilarBelow);
769 bool A2ToP2Dissimilar =
770 !areNamesSimilar(ArgNames[Position2], ParamNames[Position2], H,
771 BoundKind::DissimilarBelow);
773 if ((A1ToP2Similar || A2ToP1Similar) && A1ToP1Dissimilar &&
780bool SuspiciousCallArgumentCheck::areNamesSimilar(StringRef Arg,
781 StringRef Param, Heuristic H,
782 BoundKind BK)
const {
783 int8_t Threshold = -1;
784 if (std::optional<int8_t> GotBound = getBound(H, BK))
785 Threshold = *GotBound;
788 case Heuristic::Equality:
790 case Heuristic::Abbreviation:
792 case Heuristic::Prefix:
794 case Heuristic::Suffix:
796 case Heuristic::Substring:
798 case Heuristic::Levenshtein:
800 case Heuristic::JaroWinkler:
802 case Heuristic::Dice:
805 llvm_unreachable(
"Unhandled heuristic kind");
const FunctionDecl * Decl
llvm::SmallString< 256U > Name
CharSourceRange Range
SourceRange for the file name.
const int8_t SimilarAbove
The lower bound of % of similarity the two string must have to be considered similar.
const bool Enabled
Whether the heuristic is to be enabled by default.
const int8_t DissimilarBelow
The upper bound of % of similarity the two strings might have to be considered dissimilar.
std::optional< StringRef > get(StringRef LocalName) const
Read a named option from the Context.
void store(ClangTidyOptions::OptionMap &Options, StringRef LocalName, StringRef Value) const
Stores an option with the check-local name LocalName with string value Value to Options.
Base class for all clang-tidy checks.
DiagnosticBuilder diag(SourceLocation Loc, StringRef Description, DiagnosticIDs::Level Level=DiagnosticIDs::Warning)
Add a diagnostic with the check's name.
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
ClangTidyChecks that register ASTMatchers should do the actual work in here.
static constexpr std::size_t HeuristicCount
SuspiciousCallArgumentCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
Override this to register AST matchers with Finder.
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
Should store all options supported by this check with their current values or default values for opti...
static constexpr std::size_t SmallVectorSize
static bool isPointerOrArray(QualType TypeToCheck)
static constexpr llvm::StringLiteral DefaultAbbreviations
static bool applyDiceHeuristic(StringRef Arg, StringRef Param, int8_t Threshold)
static bool arePointersStillQualCompatible(QualType ArgType, QualType ParamType, bool &IsParamContinuouslyConst)
Checks if multilevel pointers' qualifiers compatibility continues on the current pointer level.
static QualType convertToPointeeOrArrayElementQualType(QualType TypeToConvert)
static bool applyLevenshteinHeuristic(StringRef Arg, StringRef Param, int8_t Threshold)
static constexpr std::size_t SmallVectorSize
static bool applyJaroWinklerHeuristic(StringRef Arg, StringRef Param, int8_t Threshold)
static bool applySubstringHeuristic(StringRef Arg, StringRef Param, int8_t Threshold)
static double percentage(double X, double Y)
Returns how many % X is of Y.
static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType, bool IsParamContinuouslyConst)
Checks whether multilevel pointers are compatible in terms of levels, qualifiers and pointee type.
static bool isCompatibleWithArrayReference(QualType ArgType, QualType ParamType)
Checks whether ArgType is an array type identical to ParamType's array type.
static bool areTypesCompatible(QualType ArgType, QualType ParamType, const ASTContext &Ctx)
Checks whether ArgType converts implicitly to ParamType.
static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType)
Checks if ArgType binds to ParamType regarding reference-ness and cv-qualifiers.
static constexpr StringRef HeuristicToString[]
static constexpr DefaultHeuristicConfiguration Defaults[]
static bool applyAbbreviationHeuristic(const llvm::StringMap< std::string > &AbbreviationDictionary, StringRef Arg, StringRef Param)
static bool isOverloadedUnaryOrBinarySymbolOperator(const FunctionDecl *FD)
static bool applySuffixHeuristic(StringRef Arg, StringRef Param, int8_t Threshold)
Check whether the shorter String is a suffix of the longer String.
static bool applyEqualityHeuristic(StringRef Arg, StringRef Param)
static constexpr std::size_t DefaultMinimumIdentifierNameLength
static bool applyPrefixHeuristic(StringRef Arg, StringRef Param, int8_t Threshold)
Check whether the shorter String is a prefix of the longer String.
std::string serializeStringList(ArrayRef< StringRef > Strings)
Serialize a sequence of names that can be parsed by parseStringList.
std::vector< StringRef > parseStringList(StringRef Option)
Parse a semicolon separated list of strings.
llvm::StringMap< ClangTidyValue > OptionMap