809 lines
29 KiB
C++
809 lines
29 KiB
C++
|
//===--- 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.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<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 = 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<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
|