blob: 557e95bc2407342fcd2c16769a411cb57feca13b [file] [log] [blame]
//===--- 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 <sstream>
using namespace clang::ast_matchers;
namespace optutils = clang::tidy::utils::options;
namespace clang {
namespace tidy {
namespace 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 const std::string DefaultAbbreviations =
optutils::serializeStringList({"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->getAs<EnumType>()->getDecl()->isScoped()) ||
(ArgType->isEnumeralType() &&
ArgType->getAs<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 (const std::string &Abbreviation : optutils::parseStringList(
Options.get("Abbreviations", DefaultAbbreviations))) {
auto KeyAndValue = StringRef{Abbreviation}.split("=");
assert(!KeyAndValue.first.empty() && !KeyAndValue.second.empty());
AbbreviationDictionary.insert(
std::make_pair(KeyAndValue.first.str(), 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).getValue());
};
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(Abbreviations));
}
bool SuspiciousCallArgumentCheck::isHeuristicEnabled(Heuristic H) const {
return llvm::is_contained(AppliedHeuristics, H);
}
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 None;
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) {
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());
} else if (const auto *FCall =
dyn_cast<FunctionDecl>(ArgExpr->getDecl())) {
ArgTypes.push_back(FCall->getType());
ArgNames.push_back(FCall->getName());
} else {
ArgTypes.push_back(QualType());
ArgNames.push_back(StringRef());
}
} else {
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 (Optional<int8_t> GotBound = getBound(H, BK))
Threshold = GotBound.getValue();
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 readability
} // namespace tidy
} // namespace clang