11#include "clang/AST/ASTContext.h"
12#include "clang/AST/Type.h"
13#include "clang/ASTMatchers/ASTMatchFinder.h"
22struct DefaultHeuristicConfiguration {
30 const int8_t DissimilarBelow;
36 const int8_t SimilarAbove;
39 bool hasBounds()
const {
return DissimilarBelow > -1 && SimilarAbove > -1; }
46 "Equality",
"Abbreviation",
"Prefix",
"Suffix",
47 "Substring",
"Levenshtein",
"JaroWinkler",
"Dice"};
49static constexpr DefaultHeuristicConfiguration
Defaults[] = {
63 "Ensure that every heuristic has a corresponding stringified name");
66 "Ensure that every heuristic has a default configuration.");
69template <std::
size_t I>
struct HasWellConfiguredBounds {
70 static constexpr bool Value =
72 static_assert(Value,
"A heuristic must either have a dissimilarity and "
73 "similarity bound, or neither!");
76template <std::
size_t I>
struct HasWellConfiguredBoundsFold {
77 static constexpr bool Value = HasWellConfiguredBounds<I>::Value &&
78 HasWellConfiguredBoundsFold<I - 1>::Value;
81template <>
struct HasWellConfiguredBoundsFold<0> {
82 static constexpr bool Value = HasWellConfiguredBounds<0>::Value;
85struct AllHeuristicsBoundsWellConfigured {
86 static constexpr bool Value =
91static_assert(AllHeuristicsBoundsWellConfigured::Value);
130static inline double percentage(
double X,
double Y) {
return X / Y * 100.0; }
133 return Arg.equals_insensitive(Param);
137 const llvm::StringMap<std::string> &AbbreviationDictionary, StringRef Arg,
139 if (AbbreviationDictionary.contains(Arg) &&
140 Param == AbbreviationDictionary.lookup(Arg))
143 if (AbbreviationDictionary.contains(Param) &&
144 Arg == AbbreviationDictionary.lookup(Param))
153 const StringRef Shorter = Arg.size() < Param.size() ? Arg : Param;
154 const StringRef Longer = Arg.size() >= Param.size() ? Arg : Param;
156 if (Longer.starts_with_insensitive(Shorter))
157 return percentage(Shorter.size(), Longer.size()) > Threshold;
165 const StringRef Shorter = Arg.size() < Param.size() ? Arg : Param;
166 const StringRef Longer = Arg.size() >= Param.size() ? Arg : Param;
168 if (Longer.ends_with_insensitive(Shorter))
169 return percentage(Shorter.size(), Longer.size()) > 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();
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)
188 Current[J] = 1 + Previous[J - 1];
190 MaxLength = std::max(MaxLength, Current[J]);
195 Current.swap(Previous);
198 const size_t LongerLength = std::max(Arg.size(), Param.size());
199 return percentage(MaxLength, LongerLength) > 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;
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);
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);
226 if (tolower(Param[I]) == tolower(Arg[J]) && !ArgFlags[J]) {
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) {
247 if (tolower(Param[I]) != tolower(Arg[J]))
254 const double MatchD = Match;
255 double Dist = ((MatchD / ArgLen) + (MatchD / ParamLen) +
256 ((MatchD - Transpos) / Match)) /
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]))
267 Dist = (Dist + (L * 0.1 * (1.0 - Dist))) * 100.0;
268 return Dist > Threshold;
274 llvm::StringSet<> ArgBigrams;
275 llvm::StringSet<> ParamBigrams;
278 for (std::ptrdiff_t I = 0; I < static_cast<std::ptrdiff_t>(Arg.size()) - 1;
280 ArgBigrams.insert(Arg.substr(I, 2).lower());
283 for (std::ptrdiff_t I = 0; I < static_cast<std::ptrdiff_t>(Param.size()) - 1;
285 ParamBigrams.insert(Param.substr(I, 2).lower());
287 std::size_t Intersection = 0;
290 for (
const auto &[Key, _] : ParamBigrams)
291 Intersection += ArgBigrams.count(Key);
295 ArgBigrams.size() + ParamBigrams.size()) > Threshold;
301 const ASTContext &Ctx) {
302 return !ParamType->isReferenceType() ||
303 ParamType.getNonReferenceType().isAtLeastAsQualifiedAs(
304 ArgType.getNonReferenceType(), Ctx);
308 return TypeToCheck->isPointerType() || TypeToCheck->isArrayType();
314 const ASTContext &Ctx) {
315 if (!ArgType->isArrayType())
318 if (!ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx))
321 return ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType();
325 unsigned CVRqualifiers = 0;
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;
344 bool &IsParamContinuouslyConst,
345 const ASTContext &Ctx) {
349 const bool AreTypesQualCompatible =
350 ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx) &&
351 (!ParamType.hasQualifiers() || IsParamContinuouslyConst);
354 IsParamContinuouslyConst &= ParamType.isConstQualified();
356 return AreTypesQualCompatible;
362 bool IsParamContinuouslyConst,
363 const ASTContext &Ctx) {
365 IsParamContinuouslyConst, Ctx))
376 IsParamContinuouslyConst, Ctx))
379 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
382 }
while (ParamType->isPointerType() && ArgType->isPointerType());
389 const ASTContext &Ctx) {
390 if (ArgType.isNull() || ParamType.isNull())
393 ArgType = ArgType.getCanonicalType();
394 ParamType = ParamType.getCanonicalType();
396 if (ArgType == ParamType)
403 const bool IsParamReference = ParamType->isReferenceType();
407 ArgType = ArgType.getNonReferenceType();
408 ParamType = ParamType.getNonReferenceType();
410 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
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()))
426 if (ArgType->isFunctionType() && ParamType->isFunctionPointerType()) {
427 ParamType = ParamType->getPointeeType();
428 return ArgType == ParamType;
437 if (IsParamReference && ParamType->isArrayType())
440 const bool IsParamContinuouslyConst =
441 !IsParamReference || ParamType.getNonReferenceType().isConstQualified();
448 if (!ParamType.isAtLeastAsQualifiedAs(ArgType, Ctx))
451 if (ParamType.getUnqualifiedType() == ArgType.getUnqualifiedType())
455 if (!Ctx.getLangOpts().CPlusPlus)
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())
468 return ArgDecl->isDerivedFrom(ParamDecl);
473 if (!(ParamType->isAnyPointerType() && ArgType->isAnyPointerType()))
481 switch (FD->getOverloadedOperator()) {
488 case OO_Array_Delete:
494 return FD->getNumParams() <= 2;
501 MinimumIdentifierNameLength(Options.get(
503 auto GetToggleOpt = [
this](Heuristic H) ->
bool {
504 auto Idx =
static_cast<std::size_t
>(H);
508 auto GetBoundOpt = [
this](Heuristic H, BoundKind BK) -> int8_t {
509 auto Idx =
static_cast<std::size_t
>(H);
513 Key.append(BK == BoundKind::DissimilarBelow ?
"DissimilarBelow"
515 const int8_t Default = BK == BoundKind::DissimilarBelow
518 return Options.get(Key, Default);
521 auto H =
static_cast<Heuristic
>(Idx);
523 AppliedHeuristics.emplace_back(H);
524 ConfiguredBounds.emplace_back(
525 std::make_pair(GetBoundOpt(H, BoundKind::DissimilarBelow),
526 GetBoundOpt(H, BoundKind::SimilarAbove)));
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()));
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);
546 const auto &SetBoundOpt = [
this, &Opts](Heuristic H, BoundKind BK) ->
void {
547 auto Idx =
static_cast<std::size_t
>(H);
553 Key.append(BK == BoundKind::DissimilarBelow ?
"DissimilarBelow"
555 Options.store(Opts, Key, *getBound(H, BK));
559 auto H =
static_cast<Heuristic
>(Idx);
561 SetBoundOpt(H, BoundKind::DissimilarBelow);
562 SetBoundOpt(H, BoundKind::SimilarAbove);
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);
572 if (!Abbreviation.second.empty())
573 Abbreviations.emplace_back(EqualSignJoined.str());
575 Options.store(Opts,
"Abbreviations",
577 Abbreviations.begin(), Abbreviations.end())));
580bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H)
const {
581 return llvm::is_contained(AppliedHeuristics, H);
585SuspiciousCallArgumentCheck::getBound(Heuristic H, BoundKind BK)
const {
586 auto Idx =
static_cast<std::size_t
>(H);
593 case BoundKind::DissimilarBelow:
594 return ConfiguredBounds[Idx].first;
595 case BoundKind::SimilarAbove:
596 return ConfiguredBounds[Idx].second;
598 llvm_unreachable(
"Unhandled Bound kind.");
604 functionDecl(forEachDescendant(callExpr(unless(anyOf(argumentCountIs(0),
605 argumentCountIs(1))))
606 .bind(
"functionCall")))
607 .bind(
"callingFunc"),
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);
618 const Decl *CalleeDecl = MatchedCallExpr->getCalleeDecl();
622 const FunctionDecl *CalleeFuncDecl = CalleeDecl->getAsFunction();
625 if (CalleeFuncDecl == Caller)
632 setParamNamesAndTypes(CalleeFuncDecl);
634 if (ParamNames.empty())
638 std::size_t InitialArgIndex = 0;
640 if (
const auto *MethodDecl = dyn_cast<CXXMethodDecl>(CalleeFuncDecl)) {
641 if (MethodDecl->getParent()->isLambda())
644 else if (MethodDecl->getOverloadedOperator() == OO_Call)
649 setArgNamesAndTypes(MatchedCallExpr, InitialArgIndex);
651 if (ArgNames.empty())
654 const std::size_t ParamCount = ParamNames.size();
657 for (std::size_t I = 0; I < ParamCount; ++I) {
658 for (std::size_t J = I + 1; J < ParamCount; ++J) {
660 if (!areParamAndArgComparable(I, J, *Result.Context))
662 if (!areArgsSwapped(I, J))
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();
675 const SourceLocation IParNameLoc =
676 CalleeFuncDecl->getParamDecl(I)->getLocation();
677 const SourceLocation JParNameLoc =
678 CalleeFuncDecl->getParamDecl(J)->getLocation();
680 diag(CalleeFuncDecl->getLocation(),
"in the call to %0, declared here",
683 << CharSourceRange::getTokenRange(IParNameLoc, IParNameLoc)
684 << CharSourceRange::getTokenRange(JParNameLoc, JParNameLoc);
689void SuspiciousCallArgumentCheck::setParamNamesAndTypes(
690 const FunctionDecl *CalleeFuncDecl) {
696 for (
const ParmVarDecl *Param : CalleeFuncDecl->parameters()) {
697 ParamTypes.push_back(Param->getType());
699 if (
const IdentifierInfo *II = Param->getIdentifier())
700 ParamNames.push_back(II->getName());
702 ParamNames.push_back(StringRef());
706void SuspiciousCallArgumentCheck::setArgNamesAndTypes(
707 const CallExpr *MatchedCallExpr, std::size_t InitialArgIndex) {
713 for (std::size_t I = InitialArgIndex, J = MatchedCallExpr->getNumArgs();
715 assert(ArgTypes.size() == I - InitialArgIndex &&
716 ArgNames.size() == ArgTypes.size() &&
717 "Every iteration must put an element into the vectors!");
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());
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());
735 ArgTypes.push_back(QualType());
736 ArgNames.push_back(StringRef());
740bool SuspiciousCallArgumentCheck::areParamAndArgComparable(
741 std::size_t Position1, std::size_t Position2,
const ASTContext &Ctx)
const {
742 if (Position1 >= ArgNames.size() || Position2 >= ArgNames.size())
746 if (ArgNames[Position1].size() < MinimumIdentifierNameLength ||
747 ArgNames[Position2].size() < MinimumIdentifierNameLength ||
748 ParamNames[Position1].size() < MinimumIdentifierNameLength ||
749 ParamNames[Position2].size() < MinimumIdentifierNameLength)
759bool SuspiciousCallArgumentCheck::areArgsSwapped(std::size_t Position1,
760 std::size_t Position2)
const {
761 return llvm::any_of(AppliedHeuristics, [&](Heuristic H) {
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);
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);
774 return (A1ToP2Similar || A2ToP1Similar) && A1ToP1Dissimilar &&
779bool SuspiciousCallArgumentCheck::areNamesSimilar(StringRef Arg,
780 StringRef Param, Heuristic H,
781 BoundKind BK)
const {
782 int8_t Threshold = -1;
783 if (std::optional<int8_t> GotBound = getBound(H, BK))
784 Threshold = *GotBound;
787 case Heuristic::Equality:
789 case Heuristic::Abbreviation:
791 case Heuristic::Prefix:
793 case Heuristic::Suffix:
795 case Heuristic::Substring:
797 case Heuristic::Levenshtein:
799 case Heuristic::JaroWinkler:
801 case Heuristic::Dice:
804 llvm_unreachable(
"Unhandled heuristic kind");
Every ClangTidyCheck reports errors through a DiagnosticsEngine provided by this context.
void check(const ast_matchers::MatchFinder::MatchResult &Result) override
static constexpr std::size_t HeuristicCount
SuspiciousCallArgumentCheck(StringRef Name, ClangTidyContext *Context)
void registerMatchers(ast_matchers::MatchFinder *Finder) override
void storeOptions(ClangTidyOptions::OptionMap &Opts) override
static constexpr std::size_t SmallVectorSize
static bool isPointerOrArray(QualType TypeToCheck)
static bool isCompatibleWithArrayReference(QualType ArgType, QualType ParamType, const ASTContext &Ctx)
Checks whether ArgType is an array type identical to ParamType's array type.
static bool arePointerTypesCompatible(QualType ArgType, QualType ParamType, bool IsParamContinuouslyConst, const ASTContext &Ctx)
Checks whether multilevel pointers are compatible in terms of levels, qualifiers and pointee type.
static ClangTidyModuleRegistry::Add< ReadabilityModule > X("readability-module", "Adds readability-related checks.")
static bool areRefAndQualCompatible(QualType ArgType, QualType ParamType, const ASTContext &Ctx)
Checks if ArgType binds to ParamType regarding reference-ness and cv-qualifiers.
static constexpr llvm::StringLiteral DefaultAbbreviations
static bool applyDiceHeuristic(StringRef Arg, StringRef Param, int8_t Threshold)
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 areTypesCompatible(QualType ArgType, QualType ParamType, const ASTContext &Ctx)
Checks whether ArgType converts implicitly to ParamType.
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 arePointersStillQualCompatible(QualType ArgType, QualType ParamType, bool &IsParamContinuouslyConst, const ASTContext &Ctx)
Checks if multilevel pointers' qualifiers compatibility continues on the current pointer level.
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