forked from OSchip/llvm-project
2318 lines
89 KiB
C++
2318 lines
89 KiB
C++
//===--- EasilySwappableParametersCheck.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 "EasilySwappableParametersCheck.h"
|
|
#include "../utils/OptionsUtils.h"
|
|
#include "clang/AST/ASTContext.h"
|
|
#include "clang/AST/RecursiveASTVisitor.h"
|
|
#include "clang/ASTMatchers/ASTMatchFinder.h"
|
|
#include "clang/Lex/Lexer.h"
|
|
#include "llvm/ADT/SmallSet.h"
|
|
|
|
#define DEBUG_TYPE "EasilySwappableParametersCheck"
|
|
#include "llvm/Support/Debug.h"
|
|
|
|
namespace optutils = clang::tidy::utils::options;
|
|
|
|
/// The default value for the MinimumLength check option.
|
|
static constexpr std::size_t DefaultMinimumLength = 2;
|
|
|
|
/// The default value for ignored parameter names.
|
|
static constexpr llvm::StringLiteral DefaultIgnoredParameterNames = "\"\";"
|
|
"iterator;"
|
|
"Iterator;"
|
|
"begin;"
|
|
"Begin;"
|
|
"end;"
|
|
"End;"
|
|
"first;"
|
|
"First;"
|
|
"last;"
|
|
"Last;"
|
|
"lhs;"
|
|
"LHS;"
|
|
"rhs;"
|
|
"RHS";
|
|
|
|
/// The default value for ignored parameter type suffixes.
|
|
static constexpr llvm::StringLiteral DefaultIgnoredParameterTypeSuffixes =
|
|
"bool;"
|
|
"Bool;"
|
|
"_Bool;"
|
|
"it;"
|
|
"It;"
|
|
"iterator;"
|
|
"Iterator;"
|
|
"inputit;"
|
|
"InputIt;"
|
|
"forwardit;"
|
|
"ForwardIt;"
|
|
"bidirit;"
|
|
"BidirIt;"
|
|
"constiterator;"
|
|
"const_iterator;"
|
|
"Const_Iterator;"
|
|
"Constiterator;"
|
|
"ConstIterator;"
|
|
"RandomIt;"
|
|
"randomit;"
|
|
"random_iterator;"
|
|
"ReverseIt;"
|
|
"reverse_iterator;"
|
|
"reverse_const_iterator;"
|
|
"ConstReverseIterator;"
|
|
"Const_Reverse_Iterator;"
|
|
"const_reverse_iterator;"
|
|
"Constreverseiterator;"
|
|
"constreverseiterator";
|
|
|
|
/// The default value for the QualifiersMix check option.
|
|
static constexpr bool DefaultQualifiersMix = false;
|
|
|
|
/// The default value for the ModelImplicitConversions check option.
|
|
static constexpr bool DefaultModelImplicitConversions = true;
|
|
|
|
/// The default value for suppressing diagnostics about parameters that are
|
|
/// used together.
|
|
static constexpr bool DefaultSuppressParametersUsedTogether = true;
|
|
|
|
/// The default value for the NamePrefixSuffixSilenceDissimilarityTreshold
|
|
/// check option.
|
|
static constexpr std::size_t
|
|
DefaultNamePrefixSuffixSilenceDissimilarityTreshold = 1;
|
|
|
|
using namespace clang::ast_matchers;
|
|
|
|
namespace clang {
|
|
namespace tidy {
|
|
namespace bugprone {
|
|
|
|
using TheCheck = EasilySwappableParametersCheck;
|
|
|
|
namespace filter {
|
|
class SimilarlyUsedParameterPairSuppressor;
|
|
|
|
static bool isIgnoredParameter(const TheCheck &Check, const ParmVarDecl *Node);
|
|
static inline bool
|
|
isSimilarlyUsedParameter(const SimilarlyUsedParameterPairSuppressor &Suppressor,
|
|
const ParmVarDecl *Param1, const ParmVarDecl *Param2);
|
|
static bool prefixSuffixCoverUnderThreshold(std::size_t Threshold,
|
|
StringRef Str1, StringRef Str2);
|
|
} // namespace filter
|
|
|
|
namespace model {
|
|
|
|
/// The language features involved in allowing the mix between two parameters.
|
|
enum class MixFlags : unsigned char {
|
|
Invalid = 0, ///< Sentinel bit pattern. DO NOT USE!
|
|
|
|
/// Certain constructs (such as pointers to noexcept/non-noexcept functions)
|
|
/// have the same CanonicalType, which would result in false positives.
|
|
/// During the recursive modelling call, this flag is set if a later diagnosed
|
|
/// canonical type equivalence should be thrown away.
|
|
WorkaroundDisableCanonicalEquivalence = 1,
|
|
|
|
None = 2, ///< Mix between the two parameters is not possible.
|
|
Trivial = 4, ///< The two mix trivially, and are the exact same type.
|
|
Canonical = 8, ///< The two mix because the types refer to the same
|
|
/// CanonicalType, but we do not elaborate as to how.
|
|
TypeAlias = 16, ///< The path from one type to the other involves
|
|
/// desugaring type aliases.
|
|
ReferenceBind = 32, ///< The mix involves the binding power of "const &".
|
|
Qualifiers = 64, ///< The mix involves change in the qualifiers.
|
|
ImplicitConversion = 128, ///< The mixing of the parameters is possible
|
|
/// through implicit conversions between the types.
|
|
|
|
LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue =*/ImplicitConversion)
|
|
};
|
|
LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
|
|
|
|
/// Returns whether the SearchedFlag is turned on in the Data.
|
|
static inline bool hasFlag(MixFlags Data, MixFlags SearchedFlag) {
|
|
assert(SearchedFlag != MixFlags::Invalid &&
|
|
"can't be used to detect lack of all bits!");
|
|
|
|
// "Data & SearchedFlag" would need static_cast<bool>() in conditions.
|
|
return (Data & SearchedFlag) == SearchedFlag;
|
|
}
|
|
|
|
#ifndef NDEBUG
|
|
|
|
// The modelling logic of this check is more complex than usual, and
|
|
// potentially hard to understand without the ability to see into the
|
|
// representation during the recursive descent. This debug code is only
|
|
// compiled in 'Debug' mode, or if LLVM_ENABLE_ASSERTIONS config is turned on.
|
|
|
|
/// Formats the MixFlags enum into a useful, user-readable representation.
|
|
static inline std::string formatMixFlags(MixFlags F) {
|
|
if (F == MixFlags::Invalid)
|
|
return "#Inv!";
|
|
|
|
SmallString<8> Str{"-------"};
|
|
|
|
if (hasFlag(F, MixFlags::None))
|
|
// Shows the None bit explicitly, as it can be applied in the recursion
|
|
// even if other bits are set.
|
|
Str[0] = '!';
|
|
if (hasFlag(F, MixFlags::Trivial))
|
|
Str[1] = 'T';
|
|
if (hasFlag(F, MixFlags::Canonical))
|
|
Str[2] = 'C';
|
|
if (hasFlag(F, MixFlags::TypeAlias))
|
|
Str[3] = 't';
|
|
if (hasFlag(F, MixFlags::ReferenceBind))
|
|
Str[4] = '&';
|
|
if (hasFlag(F, MixFlags::Qualifiers))
|
|
Str[5] = 'Q';
|
|
if (hasFlag(F, MixFlags::ImplicitConversion))
|
|
Str[6] = 'i';
|
|
|
|
if (hasFlag(F, MixFlags::WorkaroundDisableCanonicalEquivalence))
|
|
Str.append("(~C)");
|
|
|
|
return Str.str().str();
|
|
}
|
|
|
|
#endif // NDEBUG
|
|
|
|
/// The results of the steps of an Implicit Conversion Sequence is saved in
|
|
/// an instance of this record.
|
|
///
|
|
/// A ConversionSequence maps the steps of the conversion with a member for
|
|
/// each type involved in the conversion. Imagine going from a hypothetical
|
|
/// Complex class to projecting it to the real part as a const double.
|
|
///
|
|
/// I.e., given:
|
|
///
|
|
/// struct Complex {
|
|
/// operator double() const;
|
|
/// };
|
|
///
|
|
/// void functionBeingAnalysed(Complex C, const double R);
|
|
///
|
|
/// we will get the following sequence:
|
|
///
|
|
/// (Begin=) Complex
|
|
///
|
|
/// The first standard conversion is a qualification adjustment.
|
|
/// (AfterFirstStandard=) const Complex
|
|
///
|
|
/// Then the user-defined conversion is executed.
|
|
/// (UDConvOp.ConversionOperatorResultType=) double
|
|
///
|
|
/// Then this 'double' is qualifier-adjusted to 'const double'.
|
|
/// (AfterSecondStandard=) double
|
|
///
|
|
/// The conversion's result has now been calculated, so it ends here.
|
|
/// (End=) double.
|
|
///
|
|
/// Explicit storing of Begin and End in this record is needed, because
|
|
/// getting to what Begin and End here are needs further resolution of types,
|
|
/// e.g. in the case of typedefs:
|
|
///
|
|
/// using Comp = Complex;
|
|
/// using CD = const double;
|
|
/// void functionBeingAnalysed2(Comp C, CD R);
|
|
///
|
|
/// In this case, the user will be diagnosed with a potential conversion
|
|
/// between the two typedefs as written in the code, but to elaborate the
|
|
/// reasoning behind this conversion, we also need to show what the typedefs
|
|
/// mean. See FormattedConversionSequence towards the bottom of this file!
|
|
struct ConversionSequence {
|
|
enum UserDefinedConversionKind { UDCK_None, UDCK_Ctor, UDCK_Oper };
|
|
|
|
struct UserDefinedConvertingConstructor {
|
|
const CXXConstructorDecl *Fun;
|
|
QualType ConstructorParameterType;
|
|
QualType UserDefinedType;
|
|
};
|
|
|
|
struct UserDefinedConversionOperator {
|
|
const CXXConversionDecl *Fun;
|
|
QualType UserDefinedType;
|
|
QualType ConversionOperatorResultType;
|
|
};
|
|
|
|
/// The type the conversion stared from.
|
|
QualType Begin;
|
|
|
|
/// The intermediate type after the first Standard Conversion Sequence.
|
|
QualType AfterFirstStandard;
|
|
|
|
/// The details of the user-defined conversion involved, as a tagged union.
|
|
union {
|
|
char None;
|
|
UserDefinedConvertingConstructor UDConvCtor;
|
|
UserDefinedConversionOperator UDConvOp;
|
|
};
|
|
UserDefinedConversionKind UDConvKind;
|
|
|
|
/// The intermediate type after performing the second Standard Conversion
|
|
/// Sequence.
|
|
QualType AfterSecondStandard;
|
|
|
|
/// The result type the conversion targeted.
|
|
QualType End;
|
|
|
|
ConversionSequence() : None(0), UDConvKind(UDCK_None) {}
|
|
ConversionSequence(QualType From, QualType To)
|
|
: Begin(From), None(0), UDConvKind(UDCK_None), End(To) {}
|
|
|
|
explicit operator bool() const {
|
|
return !AfterFirstStandard.isNull() || UDConvKind != UDCK_None ||
|
|
!AfterSecondStandard.isNull();
|
|
}
|
|
|
|
/// Returns all the "steps" (non-unique and non-similar) types involved in
|
|
/// the conversion sequence. This method does **NOT** return Begin and End.
|
|
SmallVector<QualType, 4> getInvolvedTypesInSequence() const {
|
|
SmallVector<QualType, 4> Ret;
|
|
auto EmplaceIfDifferent = [&Ret](QualType QT) {
|
|
if (QT.isNull())
|
|
return;
|
|
if (Ret.empty())
|
|
Ret.emplace_back(QT);
|
|
else if (Ret.back() != QT)
|
|
Ret.emplace_back(QT);
|
|
};
|
|
|
|
EmplaceIfDifferent(AfterFirstStandard);
|
|
switch (UDConvKind) {
|
|
case UDCK_Ctor:
|
|
EmplaceIfDifferent(UDConvCtor.ConstructorParameterType);
|
|
EmplaceIfDifferent(UDConvCtor.UserDefinedType);
|
|
break;
|
|
case UDCK_Oper:
|
|
EmplaceIfDifferent(UDConvOp.UserDefinedType);
|
|
EmplaceIfDifferent(UDConvOp.ConversionOperatorResultType);
|
|
break;
|
|
case UDCK_None:
|
|
break;
|
|
}
|
|
EmplaceIfDifferent(AfterSecondStandard);
|
|
|
|
return Ret;
|
|
}
|
|
|
|
/// Updates the steps of the conversion sequence with the steps from the
|
|
/// other instance.
|
|
///
|
|
/// \note This method does not check if the resulting conversion sequence is
|
|
/// sensible!
|
|
ConversionSequence &update(const ConversionSequence &RHS) {
|
|
if (!RHS.AfterFirstStandard.isNull())
|
|
AfterFirstStandard = RHS.AfterFirstStandard;
|
|
switch (RHS.UDConvKind) {
|
|
case UDCK_Ctor:
|
|
UDConvKind = UDCK_Ctor;
|
|
UDConvCtor = RHS.UDConvCtor;
|
|
break;
|
|
case UDCK_Oper:
|
|
UDConvKind = UDCK_Oper;
|
|
UDConvOp = RHS.UDConvOp;
|
|
break;
|
|
case UDCK_None:
|
|
break;
|
|
}
|
|
if (!RHS.AfterSecondStandard.isNull())
|
|
AfterSecondStandard = RHS.AfterSecondStandard;
|
|
|
|
return *this;
|
|
}
|
|
|
|
/// Sets the user-defined conversion to the given constructor.
|
|
void setConversion(const UserDefinedConvertingConstructor &UDCC) {
|
|
UDConvKind = UDCK_Ctor;
|
|
UDConvCtor = UDCC;
|
|
}
|
|
|
|
/// Sets the user-defined conversion to the given operator.
|
|
void setConversion(const UserDefinedConversionOperator &UDCO) {
|
|
UDConvKind = UDCK_Oper;
|
|
UDConvOp = UDCO;
|
|
}
|
|
|
|
/// Returns the type in the conversion that's formally "in our hands" once
|
|
/// the user-defined conversion is executed.
|
|
QualType getTypeAfterUserDefinedConversion() const {
|
|
switch (UDConvKind) {
|
|
case UDCK_Ctor:
|
|
return UDConvCtor.UserDefinedType;
|
|
case UDCK_Oper:
|
|
return UDConvOp.ConversionOperatorResultType;
|
|
case UDCK_None:
|
|
return {};
|
|
}
|
|
llvm_unreachable("Invalid UDConv kind.");
|
|
}
|
|
|
|
const CXXMethodDecl *getUserDefinedConversionFunction() const {
|
|
switch (UDConvKind) {
|
|
case UDCK_Ctor:
|
|
return UDConvCtor.Fun;
|
|
case UDCK_Oper:
|
|
return UDConvOp.Fun;
|
|
case UDCK_None:
|
|
return {};
|
|
}
|
|
llvm_unreachable("Invalid UDConv kind.");
|
|
}
|
|
|
|
/// Returns the SourceRange in the text that corresponds to the interesting
|
|
/// part of the user-defined conversion. This is either the parameter type
|
|
/// in a converting constructor, or the conversion result type in a conversion
|
|
/// operator.
|
|
SourceRange getUserDefinedConversionHighlight() const {
|
|
switch (UDConvKind) {
|
|
case UDCK_Ctor:
|
|
return UDConvCtor.Fun->getParamDecl(0)->getSourceRange();
|
|
case UDCK_Oper:
|
|
// getReturnTypeSourceRange() does not work for CXXConversionDecls as the
|
|
// returned type is physically behind the declaration's name ("operator").
|
|
if (const FunctionTypeLoc FTL = UDConvOp.Fun->getFunctionTypeLoc())
|
|
if (const TypeLoc RetLoc = FTL.getReturnLoc())
|
|
return RetLoc.getSourceRange();
|
|
return {};
|
|
case UDCK_None:
|
|
return {};
|
|
}
|
|
llvm_unreachable("Invalid UDConv kind.");
|
|
}
|
|
};
|
|
|
|
/// Contains the metadata for the mixability result between two types,
|
|
/// independently of which parameters they were calculated from.
|
|
struct MixData {
|
|
/// The flag bits of the mix indicating what language features allow for it.
|
|
MixFlags Flags = MixFlags::Invalid;
|
|
|
|
/// A potentially calculated common underlying type after desugaring, that
|
|
/// both sides of the mix can originate from.
|
|
QualType CommonType;
|
|
|
|
/// The steps an implicit conversion performs to get from one type to the
|
|
/// other.
|
|
ConversionSequence Conversion, ConversionRTL;
|
|
|
|
/// True if the MixData was specifically created with only a one-way
|
|
/// conversion modelled.
|
|
bool CreatedFromOneWayConversion = false;
|
|
|
|
MixData(MixFlags Flags) : Flags(Flags) {}
|
|
MixData(MixFlags Flags, QualType CommonType)
|
|
: Flags(Flags), CommonType(CommonType) {}
|
|
MixData(MixFlags Flags, ConversionSequence Conv)
|
|
: Flags(Flags), Conversion(Conv), CreatedFromOneWayConversion(true) {}
|
|
MixData(MixFlags Flags, ConversionSequence LTR, ConversionSequence RTL)
|
|
: Flags(Flags), Conversion(LTR), ConversionRTL(RTL) {}
|
|
MixData(MixFlags Flags, QualType CommonType, ConversionSequence LTR,
|
|
ConversionSequence RTL)
|
|
: Flags(Flags), CommonType(CommonType), Conversion(LTR),
|
|
ConversionRTL(RTL) {}
|
|
|
|
void sanitize() {
|
|
assert(Flags != MixFlags::Invalid && "sanitize() called on invalid bitvec");
|
|
|
|
MixFlags CanonicalAndWorkaround =
|
|
MixFlags::Canonical | MixFlags::WorkaroundDisableCanonicalEquivalence;
|
|
if ((Flags & CanonicalAndWorkaround) == CanonicalAndWorkaround) {
|
|
// A workaround for too eagerly equivalent canonical types was requested,
|
|
// and a canonical equivalence was proven. Fulfill the request and throw
|
|
// this result away.
|
|
Flags = MixFlags::None;
|
|
return;
|
|
}
|
|
|
|
if (hasFlag(Flags, MixFlags::None)) {
|
|
// If anywhere down the recursion a potential mix "path" is deemed
|
|
// impossible, throw away all the other bits because the mix is not
|
|
// possible.
|
|
Flags = MixFlags::None;
|
|
return;
|
|
}
|
|
|
|
if (Flags == MixFlags::Trivial)
|
|
return;
|
|
|
|
if (static_cast<bool>(Flags ^ MixFlags::Trivial))
|
|
// If the mix involves somewhere trivial equivalence but down the
|
|
// recursion other bit(s) were set, remove the trivial bit, as it is not
|
|
// trivial.
|
|
Flags &= ~MixFlags::Trivial;
|
|
|
|
bool ShouldHaveImplicitConvFlag = false;
|
|
if (CreatedFromOneWayConversion && Conversion)
|
|
ShouldHaveImplicitConvFlag = true;
|
|
else if (!CreatedFromOneWayConversion && Conversion && ConversionRTL)
|
|
// Only say that we have implicit conversion mix possibility if it is
|
|
// bidirectional. Otherwise, the compiler would report an *actual* swap
|
|
// at a call site...
|
|
ShouldHaveImplicitConvFlag = true;
|
|
|
|
if (ShouldHaveImplicitConvFlag)
|
|
Flags |= MixFlags::ImplicitConversion;
|
|
else
|
|
Flags &= ~MixFlags::ImplicitConversion;
|
|
}
|
|
|
|
bool isValid() const { return Flags >= MixFlags::None; }
|
|
|
|
bool indicatesMixability() const { return Flags > MixFlags::None; }
|
|
|
|
/// Add the specified flag bits to the flags.
|
|
MixData operator|(MixFlags EnableFlags) const {
|
|
if (CreatedFromOneWayConversion) {
|
|
MixData M{Flags | EnableFlags, Conversion};
|
|
M.CommonType = CommonType;
|
|
return M;
|
|
}
|
|
return {Flags | EnableFlags, CommonType, Conversion, ConversionRTL};
|
|
}
|
|
|
|
/// Add the specified flag bits to the flags.
|
|
MixData &operator|=(MixFlags EnableFlags) {
|
|
Flags |= EnableFlags;
|
|
return *this;
|
|
}
|
|
|
|
template <class F> MixData withCommonTypeTransformed(F &&Func) const {
|
|
if (CommonType.isNull())
|
|
return *this;
|
|
|
|
QualType NewCommonType = Func(CommonType);
|
|
|
|
if (CreatedFromOneWayConversion) {
|
|
MixData M{Flags, Conversion};
|
|
M.CommonType = NewCommonType;
|
|
return M;
|
|
}
|
|
|
|
return {Flags, NewCommonType, Conversion, ConversionRTL};
|
|
}
|
|
};
|
|
|
|
/// A named tuple that contains the information for a mix between two concrete
|
|
/// parameters.
|
|
struct Mix {
|
|
const ParmVarDecl *First, *Second;
|
|
MixData Data;
|
|
|
|
Mix(const ParmVarDecl *F, const ParmVarDecl *S, MixData Data)
|
|
: First(F), Second(S), Data(std::move(Data)) {}
|
|
|
|
void sanitize() { Data.sanitize(); }
|
|
MixFlags flags() const { return Data.Flags; }
|
|
bool flagsValid() const { return Data.isValid(); }
|
|
bool mixable() const { return Data.indicatesMixability(); }
|
|
QualType commonUnderlyingType() const { return Data.CommonType; }
|
|
const ConversionSequence &leftToRightConversionSequence() const {
|
|
return Data.Conversion;
|
|
}
|
|
const ConversionSequence &rightToLeftConversionSequence() const {
|
|
return Data.ConversionRTL;
|
|
}
|
|
};
|
|
|
|
// NOLINTNEXTLINE(misc-redundant-expression): Seems to be a bogus warning.
|
|
static_assert(std::is_trivially_copyable<Mix>::value &&
|
|
std::is_trivially_move_constructible<Mix>::value &&
|
|
std::is_trivially_move_assignable<Mix>::value,
|
|
"Keep frequently used data simple!");
|
|
|
|
struct MixableParameterRange {
|
|
/// A container for Mixes.
|
|
using MixVector = SmallVector<Mix, 8>;
|
|
|
|
/// The number of parameters iterated to build the instance.
|
|
std::size_t NumParamsChecked = 0;
|
|
|
|
/// The individual flags and supporting information for the mixes.
|
|
MixVector Mixes;
|
|
|
|
/// Gets the leftmost parameter of the range.
|
|
const ParmVarDecl *getFirstParam() const {
|
|
// The first element is the LHS of the very first mix in the range.
|
|
assert(!Mixes.empty());
|
|
return Mixes.front().First;
|
|
}
|
|
|
|
/// Gets the rightmost parameter of the range.
|
|
const ParmVarDecl *getLastParam() const {
|
|
// The builder function breaks building an instance of this type if it
|
|
// finds something that can not be mixed with the rest, by going *forward*
|
|
// in the list of parameters. So at any moment of break, the RHS of the last
|
|
// element of the mix vector is also the last element of the mixing range.
|
|
assert(!Mixes.empty());
|
|
return Mixes.back().Second;
|
|
}
|
|
};
|
|
|
|
/// Helper enum for the recursive calls in the modelling that toggle what kinds
|
|
/// of implicit conversions are to be modelled.
|
|
enum class ImplicitConversionModellingMode : unsigned char {
|
|
///< No implicit conversions are modelled.
|
|
None,
|
|
|
|
///< The full implicit conversion sequence is modelled.
|
|
All,
|
|
|
|
///< Only model a unidirectional implicit conversion and within it only one
|
|
/// standard conversion sequence.
|
|
OneWaySingleStandardOnly
|
|
};
|
|
|
|
static MixData
|
|
isLRefEquallyBindingToType(const TheCheck &Check,
|
|
const LValueReferenceType *LRef, QualType Ty,
|
|
const ASTContext &Ctx, bool IsRefRHS,
|
|
ImplicitConversionModellingMode ImplicitMode);
|
|
|
|
static MixData
|
|
approximateImplicitConversion(const TheCheck &Check, QualType LType,
|
|
QualType RType, const ASTContext &Ctx,
|
|
ImplicitConversionModellingMode ImplicitMode);
|
|
|
|
static inline bool isUselessSugar(const Type *T) {
|
|
return isa<AttributedType, DecayedType, ElaboratedType, ParenType>(T);
|
|
}
|
|
|
|
namespace {
|
|
|
|
struct NonCVRQualifiersResult {
|
|
/// True if the types are qualified in a way that even after equating or
|
|
/// removing local CVR qualification, even if the unqualified types
|
|
/// themselves would mix, the qualified ones don't, because there are some
|
|
/// other local qualifiers that are not equal.
|
|
bool HasMixabilityBreakingQualifiers;
|
|
|
|
/// The set of equal qualifiers between the two types.
|
|
Qualifiers CommonQualifiers;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
/// Returns if the two types are qualified in a way that ever after equating or
|
|
/// removing local CVR qualification, even if the unqualified types would mix,
|
|
/// the qualified ones don't, because there are some other local qualifiers
|
|
/// that aren't equal.
|
|
static NonCVRQualifiersResult
|
|
getNonCVRQualifiers(const ASTContext &Ctx, QualType LType, QualType RType) {
|
|
LLVM_DEBUG(llvm::dbgs() << ">>> getNonCVRQualifiers for LType:\n";
|
|
LType.dump(llvm::dbgs(), Ctx); llvm::dbgs() << "\nand RType:\n";
|
|
RType.dump(llvm::dbgs(), Ctx); llvm::dbgs() << '\n';);
|
|
Qualifiers LQual = LType.getLocalQualifiers(),
|
|
RQual = RType.getLocalQualifiers();
|
|
|
|
// Strip potential CVR. That is handled by the check option QualifiersMix.
|
|
LQual.removeCVRQualifiers();
|
|
RQual.removeCVRQualifiers();
|
|
|
|
NonCVRQualifiersResult Ret;
|
|
Ret.CommonQualifiers = Qualifiers::removeCommonQualifiers(LQual, RQual);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "--- hasNonCVRMixabilityBreakingQualifiers. "
|
|
"Removed common qualifiers: ";
|
|
Ret.CommonQualifiers.print(llvm::dbgs(), Ctx.getPrintingPolicy());
|
|
llvm::dbgs() << "\n\tremaining on LType: ";
|
|
LQual.print(llvm::dbgs(), Ctx.getPrintingPolicy());
|
|
llvm::dbgs() << "\n\tremaining on RType: ";
|
|
RQual.print(llvm::dbgs(), Ctx.getPrintingPolicy());
|
|
llvm::dbgs() << '\n';);
|
|
|
|
// If there are no other non-cvr non-common qualifiers left, we can deduce
|
|
// that mixability isn't broken.
|
|
Ret.HasMixabilityBreakingQualifiers =
|
|
LQual.hasQualifiers() || RQual.hasQualifiers();
|
|
|
|
return Ret;
|
|
}
|
|
|
|
/// Approximate the way how LType and RType might refer to "essentially the
|
|
/// same" type, in a sense that at a particular call site, an expression of
|
|
/// type LType and RType might be successfully passed to a variable (in our
|
|
/// specific case, a parameter) of type RType and LType, respectively.
|
|
/// Note the swapped order!
|
|
///
|
|
/// The returned data structure is not guaranteed to be properly set, as this
|
|
/// function is potentially recursive. It is the caller's responsibility to
|
|
/// call sanitize() on the result once the recursion is over.
|
|
static MixData
|
|
calculateMixability(const TheCheck &Check, QualType LType, QualType RType,
|
|
const ASTContext &Ctx,
|
|
ImplicitConversionModellingMode ImplicitMode) {
|
|
LLVM_DEBUG(llvm::dbgs() << ">>> calculateMixability for LType:\n";
|
|
LType.dump(llvm::dbgs(), Ctx); llvm::dbgs() << "\nand RType:\n";
|
|
RType.dump(llvm::dbgs(), Ctx); llvm::dbgs() << '\n';);
|
|
if (LType == RType) {
|
|
LLVM_DEBUG(llvm::dbgs() << "<<< calculateMixability. Trivial equality.\n");
|
|
return {MixFlags::Trivial, LType};
|
|
}
|
|
|
|
// Dissolve certain type sugars that do not affect the mixability of one type
|
|
// with the other, and also do not require any sort of elaboration for the
|
|
// user to understand.
|
|
if (isUselessSugar(LType.getTypePtr())) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "--- calculateMixability. LHS is useless sugar.\n");
|
|
return calculateMixability(Check, LType.getSingleStepDesugaredType(Ctx),
|
|
RType, Ctx, ImplicitMode);
|
|
}
|
|
if (isUselessSugar(RType.getTypePtr())) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "--- calculateMixability. RHS is useless sugar.\n");
|
|
return calculateMixability(
|
|
Check, LType, RType.getSingleStepDesugaredType(Ctx), Ctx, ImplicitMode);
|
|
}
|
|
|
|
const auto *LLRef = LType->getAs<LValueReferenceType>();
|
|
const auto *RLRef = RType->getAs<LValueReferenceType>();
|
|
if (LLRef && RLRef) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. LHS and RHS are &.\n");
|
|
|
|
return calculateMixability(Check, LLRef->getPointeeType(),
|
|
RLRef->getPointeeType(), Ctx, ImplicitMode)
|
|
.withCommonTypeTransformed(
|
|
[&Ctx](QualType QT) { return Ctx.getLValueReferenceType(QT); });
|
|
}
|
|
// At a particular call site, what could be passed to a 'T' or 'const T' might
|
|
// also be passed to a 'const T &' without the call site putting a direct
|
|
// side effect on the passed expressions.
|
|
if (LLRef) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. LHS is &.\n");
|
|
return isLRefEquallyBindingToType(Check, LLRef, RType, Ctx, false,
|
|
ImplicitMode) |
|
|
MixFlags::ReferenceBind;
|
|
}
|
|
if (RLRef) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. RHS is &.\n");
|
|
return isLRefEquallyBindingToType(Check, RLRef, LType, Ctx, true,
|
|
ImplicitMode) |
|
|
MixFlags::ReferenceBind;
|
|
}
|
|
|
|
if (LType->getAs<TypedefType>()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. LHS is typedef.\n");
|
|
return calculateMixability(Check, LType.getSingleStepDesugaredType(Ctx),
|
|
RType, Ctx, ImplicitMode) |
|
|
MixFlags::TypeAlias;
|
|
}
|
|
if (RType->getAs<TypedefType>()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. RHS is typedef.\n");
|
|
return calculateMixability(Check, LType,
|
|
RType.getSingleStepDesugaredType(Ctx), Ctx,
|
|
ImplicitMode) |
|
|
MixFlags::TypeAlias;
|
|
}
|
|
|
|
// A parameter of type 'cvr1 T' and another of potentially differently
|
|
// qualified 'cvr2 T' may bind with the same power, if the user so requested.
|
|
//
|
|
// Whether to do this check for the inner unqualified types.
|
|
bool CompareUnqualifiedTypes = false;
|
|
if (LType.getLocalCVRQualifiers() != RType.getLocalCVRQualifiers()) {
|
|
LLVM_DEBUG(if (LType.getLocalCVRQualifiers()) {
|
|
llvm::dbgs() << "--- calculateMixability. LHS has CVR-Qualifiers: ";
|
|
Qualifiers::fromCVRMask(LType.getLocalCVRQualifiers())
|
|
.print(llvm::dbgs(), Ctx.getPrintingPolicy());
|
|
llvm::dbgs() << '\n';
|
|
});
|
|
LLVM_DEBUG(if (RType.getLocalCVRQualifiers()) {
|
|
llvm::dbgs() << "--- calculateMixability. RHS has CVR-Qualifiers: ";
|
|
Qualifiers::fromCVRMask(RType.getLocalCVRQualifiers())
|
|
.print(llvm::dbgs(), Ctx.getPrintingPolicy());
|
|
llvm::dbgs() << '\n';
|
|
});
|
|
|
|
if (!Check.QualifiersMix) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "<<< calculateMixability. QualifiersMix turned off - not "
|
|
"mixable.\n");
|
|
return {MixFlags::None};
|
|
}
|
|
|
|
CompareUnqualifiedTypes = true;
|
|
}
|
|
// Whether the two types had the same CVR qualifiers.
|
|
bool OriginallySameQualifiers = false;
|
|
if (LType.getLocalCVRQualifiers() == RType.getLocalCVRQualifiers() &&
|
|
LType.getLocalCVRQualifiers() != 0) {
|
|
LLVM_DEBUG(if (LType.getLocalCVRQualifiers()) {
|
|
llvm::dbgs()
|
|
<< "--- calculateMixability. LHS and RHS have same CVR-Qualifiers: ";
|
|
Qualifiers::fromCVRMask(LType.getLocalCVRQualifiers())
|
|
.print(llvm::dbgs(), Ctx.getPrintingPolicy());
|
|
llvm::dbgs() << '\n';
|
|
});
|
|
|
|
CompareUnqualifiedTypes = true;
|
|
OriginallySameQualifiers = true;
|
|
}
|
|
|
|
if (CompareUnqualifiedTypes) {
|
|
NonCVRQualifiersResult AdditionalQuals =
|
|
getNonCVRQualifiers(Ctx, LType, RType);
|
|
if (AdditionalQuals.HasMixabilityBreakingQualifiers) {
|
|
LLVM_DEBUG(llvm::dbgs() << "<<< calculateMixability. Additional "
|
|
"non-equal incompatible qualifiers.\n");
|
|
return {MixFlags::None};
|
|
}
|
|
|
|
MixData UnqualifiedMixability =
|
|
calculateMixability(Check, LType.getLocalUnqualifiedType(),
|
|
RType.getLocalUnqualifiedType(), Ctx, ImplicitMode)
|
|
.withCommonTypeTransformed([&AdditionalQuals, &Ctx](QualType QT) {
|
|
// Once the mixability was deduced, apply the qualifiers common
|
|
// to the two type back onto the diagnostic printout.
|
|
return Ctx.getQualifiedType(QT, AdditionalQuals.CommonQualifiers);
|
|
});
|
|
|
|
if (!OriginallySameQualifiers)
|
|
// User-enabled qualifier change modelled for the mix.
|
|
return UnqualifiedMixability | MixFlags::Qualifiers;
|
|
|
|
// Apply the same qualifier back into the found common type if they were
|
|
// the same.
|
|
return UnqualifiedMixability.withCommonTypeTransformed(
|
|
[&Ctx, LType](QualType QT) {
|
|
return Ctx.getQualifiedType(QT, LType.getLocalQualifiers());
|
|
});
|
|
}
|
|
|
|
// Certain constructs match on the last catch-all getCanonicalType() equality,
|
|
// which is perhaps something not what we want. If this variable is true,
|
|
// the canonical type equality will be ignored.
|
|
bool RecursiveReturnDiscardingCanonicalType = false;
|
|
|
|
if (LType->isPointerType() && RType->isPointerType()) {
|
|
// If both types are pointers, and pointed to the exact same type,
|
|
// LType == RType took care of that. Try to see if the pointee type has
|
|
// some other match. However, this must not consider implicit conversions.
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "--- calculateMixability. LHS and RHS are Ptrs.\n");
|
|
MixData MixOfPointee =
|
|
calculateMixability(Check, LType->getPointeeType(),
|
|
RType->getPointeeType(), Ctx,
|
|
ImplicitConversionModellingMode::None)
|
|
.withCommonTypeTransformed(
|
|
[&Ctx](QualType QT) { return Ctx.getPointerType(QT); });
|
|
if (hasFlag(MixOfPointee.Flags,
|
|
MixFlags::WorkaroundDisableCanonicalEquivalence))
|
|
RecursiveReturnDiscardingCanonicalType = true;
|
|
|
|
MixOfPointee.sanitize();
|
|
if (MixOfPointee.indicatesMixability()) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "<<< calculateMixability. Pointees are mixable.\n");
|
|
return MixOfPointee;
|
|
}
|
|
}
|
|
|
|
if (ImplicitMode > ImplicitConversionModellingMode::None) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. Start implicit...\n");
|
|
MixData MixLTR =
|
|
approximateImplicitConversion(Check, LType, RType, Ctx, ImplicitMode);
|
|
LLVM_DEBUG(
|
|
if (hasFlag(MixLTR.Flags, MixFlags::ImplicitConversion)) llvm::dbgs()
|
|
<< "--- calculateMixability. Implicit Left -> Right found.\n";);
|
|
|
|
if (ImplicitMode ==
|
|
ImplicitConversionModellingMode::OneWaySingleStandardOnly &&
|
|
MixLTR.Conversion && !MixLTR.Conversion.AfterFirstStandard.isNull() &&
|
|
MixLTR.Conversion.UDConvKind == ConversionSequence::UDCK_None &&
|
|
MixLTR.Conversion.AfterSecondStandard.isNull()) {
|
|
// The invoker of the method requested only modelling a single standard
|
|
// conversion, in only the forward direction, and they got just that.
|
|
LLVM_DEBUG(llvm::dbgs() << "<<< calculateMixability. Implicit "
|
|
"conversion, one-way, standard-only.\n");
|
|
return {MixFlags::ImplicitConversion, MixLTR.Conversion};
|
|
}
|
|
|
|
// Otherwise if the invoker requested a full modelling, do the other
|
|
// direction as well.
|
|
MixData MixRTL =
|
|
approximateImplicitConversion(Check, RType, LType, Ctx, ImplicitMode);
|
|
LLVM_DEBUG(
|
|
if (hasFlag(MixRTL.Flags, MixFlags::ImplicitConversion)) llvm::dbgs()
|
|
<< "--- calculateMixability. Implicit Right -> Left found.\n";);
|
|
|
|
if (MixLTR.Conversion && MixRTL.Conversion) {
|
|
LLVM_DEBUG(
|
|
llvm::dbgs()
|
|
<< "<<< calculateMixability. Implicit conversion, bidirectional.\n");
|
|
return {MixFlags::ImplicitConversion, MixLTR.Conversion,
|
|
MixRTL.Conversion};
|
|
}
|
|
}
|
|
|
|
if (RecursiveReturnDiscardingCanonicalType)
|
|
LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. Before CanonicalType, "
|
|
"Discard was enabled.\n");
|
|
|
|
// Certain kinds unfortunately need to be side-stepped for canonical type
|
|
// matching.
|
|
if (LType->getAs<FunctionProtoType>() || RType->getAs<FunctionProtoType>()) {
|
|
// Unfortunately, the canonical type of a function pointer becomes the
|
|
// same even if exactly one is "noexcept" and the other isn't, making us
|
|
// give a false positive report irrespective of implicit conversions.
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "--- calculateMixability. Discarding potential canonical "
|
|
"equivalence on FunctionProtoTypes.\n");
|
|
RecursiveReturnDiscardingCanonicalType = true;
|
|
}
|
|
|
|
MixData MixToReturn{MixFlags::None};
|
|
|
|
// If none of the previous logic found a match, try if Clang otherwise
|
|
// believes the types to be the same.
|
|
QualType LCanonical = LType.getCanonicalType();
|
|
if (LCanonical == RType.getCanonicalType()) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "<<< calculateMixability. Same CanonicalType.\n");
|
|
MixToReturn = {MixFlags::Canonical, LCanonical};
|
|
}
|
|
|
|
if (RecursiveReturnDiscardingCanonicalType)
|
|
MixToReturn |= MixFlags::WorkaroundDisableCanonicalEquivalence;
|
|
|
|
LLVM_DEBUG(if (MixToReturn.Flags == MixFlags::None) llvm::dbgs()
|
|
<< "<<< calculateMixability. No match found.\n");
|
|
return MixToReturn;
|
|
}
|
|
|
|
/// Calculates if the reference binds an expression of the given type. This is
|
|
/// true iff 'LRef' is some 'const T &' type, and the 'Ty' is 'T' or 'const T'.
|
|
///
|
|
/// \param ImplicitMode is forwarded in the possible recursive call to
|
|
/// calculateMixability.
|
|
static MixData
|
|
isLRefEquallyBindingToType(const TheCheck &Check,
|
|
const LValueReferenceType *LRef, QualType Ty,
|
|
const ASTContext &Ctx, bool IsRefRHS,
|
|
ImplicitConversionModellingMode ImplicitMode) {
|
|
LLVM_DEBUG(llvm::dbgs() << ">>> isLRefEquallyBindingToType for LRef:\n";
|
|
LRef->dump(llvm::dbgs(), Ctx); llvm::dbgs() << "\nand Type:\n";
|
|
Ty.dump(llvm::dbgs(), Ctx); llvm::dbgs() << '\n';);
|
|
|
|
QualType ReferredType = LRef->getPointeeType();
|
|
if (!ReferredType.isLocalConstQualified() &&
|
|
ReferredType->getAs<TypedefType>()) {
|
|
LLVM_DEBUG(
|
|
llvm::dbgs()
|
|
<< "--- isLRefEquallyBindingToType. Non-const LRef to Typedef.\n");
|
|
ReferredType = ReferredType.getDesugaredType(Ctx);
|
|
if (!ReferredType.isLocalConstQualified()) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "<<< isLRefEquallyBindingToType. Typedef is not const.\n");
|
|
return {MixFlags::None};
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "--- isLRefEquallyBindingToType. Typedef is "
|
|
"const, considering as const LRef.\n");
|
|
} else if (!ReferredType.isLocalConstQualified()) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "<<< isLRefEquallyBindingToType. Not const LRef.\n");
|
|
return {MixFlags::None};
|
|
};
|
|
|
|
assert(ReferredType.isLocalConstQualified() &&
|
|
"Reaching this point means we are sure LRef is effectively a const&.");
|
|
|
|
if (ReferredType == Ty) {
|
|
LLVM_DEBUG(
|
|
llvm::dbgs()
|
|
<< "<<< isLRefEquallyBindingToType. Type of referred matches.\n");
|
|
return {MixFlags::Trivial, ReferredType};
|
|
}
|
|
|
|
QualType NonConstReferredType = ReferredType;
|
|
NonConstReferredType.removeLocalConst();
|
|
if (NonConstReferredType == Ty) {
|
|
LLVM_DEBUG(llvm::dbgs() << "<<< isLRefEquallyBindingToType. Type of "
|
|
"referred matches to non-const qualified.\n");
|
|
return {MixFlags::Trivial, NonConstReferredType};
|
|
}
|
|
|
|
LLVM_DEBUG(
|
|
llvm::dbgs()
|
|
<< "--- isLRefEquallyBindingToType. Checking mix for underlying type.\n");
|
|
return IsRefRHS ? calculateMixability(Check, Ty, NonConstReferredType, Ctx,
|
|
ImplicitMode)
|
|
: calculateMixability(Check, NonConstReferredType, Ty, Ctx,
|
|
ImplicitMode);
|
|
}
|
|
|
|
static inline bool isDerivedToBase(const CXXRecordDecl *Derived,
|
|
const CXXRecordDecl *Base) {
|
|
return Derived && Base && Derived->isCompleteDefinition() &&
|
|
Base->isCompleteDefinition() && Derived->isDerivedFrom(Base);
|
|
}
|
|
|
|
static Optional<QualType>
|
|
approximateStandardConversionSequence(const TheCheck &Check, QualType From,
|
|
QualType To, const ASTContext &Ctx) {
|
|
LLVM_DEBUG(llvm::dbgs() << ">>> approximateStdConv for LType:\n";
|
|
From.dump(llvm::dbgs(), Ctx); llvm::dbgs() << "\nand RType:\n";
|
|
To.dump(llvm::dbgs(), Ctx); llvm::dbgs() << '\n';);
|
|
|
|
// A standard conversion sequence consists of the following, in order:
|
|
// * Maybe either LValue->RValue conv., Array->Ptr conv., Function->Ptr conv.
|
|
// * Maybe Numeric promotion or conversion.
|
|
// * Maybe function pointer conversion.
|
|
// * Maybe qualifier adjustments.
|
|
QualType WorkType = From;
|
|
// Get out the qualifiers of the original type. This will always be
|
|
// re-applied to the WorkType to ensure it is the same qualification as the
|
|
// original From was.
|
|
auto QualifiersToApply = From.split().Quals.getAsOpaqueValue();
|
|
|
|
// LValue->RValue is irrelevant for the check, because it is a thing to be
|
|
// done at a call site, and will be performed if need be performed.
|
|
|
|
// Array->Pointer decay is handled by the main method in desugaring
|
|
// the parameter's DecayedType as "useless sugar".
|
|
|
|
// Function->Pointer conversions are also irrelevant, because a
|
|
// "FunctionType" cannot be the type of a parameter variable, so this
|
|
// conversion is only meaningful at call sites.
|
|
|
|
// Numeric promotions and conversions.
|
|
const auto *FromBuiltin = WorkType->getAs<BuiltinType>();
|
|
const auto *ToBuiltin = To->getAs<BuiltinType>();
|
|
bool FromNumeric = FromBuiltin && (FromBuiltin->isIntegerType() ||
|
|
FromBuiltin->isFloatingType());
|
|
bool ToNumeric =
|
|
ToBuiltin && (ToBuiltin->isIntegerType() || ToBuiltin->isFloatingType());
|
|
if (FromNumeric && ToNumeric) {
|
|
// If both are integral types, the numeric conversion is performed.
|
|
// Reapply the qualifiers of the original type, however, so
|
|
// "const int -> double" in this case moves over to
|
|
// "const double -> double".
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "--- approximateStdConv. Conversion between numerics.\n");
|
|
WorkType = QualType{ToBuiltin, QualifiersToApply};
|
|
}
|
|
|
|
const auto *FromEnum = WorkType->getAs<EnumType>();
|
|
const auto *ToEnum = To->getAs<EnumType>();
|
|
if (FromEnum && ToNumeric && FromEnum->isUnscopedEnumerationType()) {
|
|
// Unscoped enumerations (or enumerations in C) convert to numerics.
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "--- approximateStdConv. Unscoped enum to numeric.\n");
|
|
WorkType = QualType{ToBuiltin, QualifiersToApply};
|
|
} else if (FromNumeric && ToEnum && ToEnum->isUnscopedEnumerationType()) {
|
|
// Numeric types convert to enumerations only in C.
|
|
if (Ctx.getLangOpts().CPlusPlus) {
|
|
LLVM_DEBUG(llvm::dbgs() << "<<< approximateStdConv. Numeric to unscoped "
|
|
"enum, not possible in C++!\n");
|
|
return {};
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "--- approximateStdConv. Numeric to unscoped enum.\n");
|
|
WorkType = QualType{ToEnum, QualifiersToApply};
|
|
}
|
|
|
|
// Check for pointer conversions.
|
|
const auto *FromPtr = WorkType->getAs<PointerType>();
|
|
const auto *ToPtr = To->getAs<PointerType>();
|
|
if (FromPtr && ToPtr) {
|
|
if (ToPtr->isVoidPointerType()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- approximateStdConv. To void pointer.\n");
|
|
WorkType = QualType{ToPtr, QualifiersToApply};
|
|
}
|
|
|
|
const auto *FromRecordPtr = FromPtr->getPointeeCXXRecordDecl();
|
|
const auto *ToRecordPtr = ToPtr->getPointeeCXXRecordDecl();
|
|
if (isDerivedToBase(FromRecordPtr, ToRecordPtr)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- approximateStdConv. Derived* to Base*\n");
|
|
WorkType = QualType{ToPtr, QualifiersToApply};
|
|
}
|
|
}
|
|
|
|
// Model the slicing Derived-to-Base too, as "BaseT temporary = derived;"
|
|
// can also be compiled.
|
|
const auto *FromRecord = WorkType->getAsCXXRecordDecl();
|
|
const auto *ToRecord = To->getAsCXXRecordDecl();
|
|
if (isDerivedToBase(FromRecord, ToRecord)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- approximateStdConv. Derived To Base.\n");
|
|
WorkType = QualType{ToRecord->getTypeForDecl(), QualifiersToApply};
|
|
}
|
|
|
|
if (Ctx.getLangOpts().CPlusPlus17 && FromPtr && ToPtr) {
|
|
// Function pointer conversion: A noexcept function pointer can be passed
|
|
// to a non-noexcept one.
|
|
const auto *FromFunctionPtr =
|
|
FromPtr->getPointeeType()->getAs<FunctionProtoType>();
|
|
const auto *ToFunctionPtr =
|
|
ToPtr->getPointeeType()->getAs<FunctionProtoType>();
|
|
if (FromFunctionPtr && ToFunctionPtr &&
|
|
FromFunctionPtr->hasNoexceptExceptionSpec() &&
|
|
!ToFunctionPtr->hasNoexceptExceptionSpec()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- approximateStdConv. noexcept function "
|
|
"pointer to non-noexcept.\n");
|
|
WorkType = QualType{ToPtr, QualifiersToApply};
|
|
}
|
|
}
|
|
|
|
// Qualifier adjustments are modelled according to the user's request in
|
|
// the QualifiersMix check config.
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "--- approximateStdConv. Trying qualifier adjustment...\n");
|
|
MixData QualConv = calculateMixability(Check, WorkType, To, Ctx,
|
|
ImplicitConversionModellingMode::None);
|
|
QualConv.sanitize();
|
|
if (hasFlag(QualConv.Flags, MixFlags::Qualifiers)) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "<<< approximateStdConv. Qualifiers adjusted.\n");
|
|
WorkType = To;
|
|
}
|
|
|
|
if (WorkType == To) {
|
|
LLVM_DEBUG(llvm::dbgs() << "<<< approximateStdConv. Reached 'To' type.\n");
|
|
return {WorkType};
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "<<< approximateStdConv. Did not reach 'To'.\n");
|
|
return {};
|
|
}
|
|
|
|
namespace {
|
|
|
|
/// Helper class for storing possible user-defined conversion calls that
|
|
/// *could* take place in an implicit conversion, and selecting the one that
|
|
/// most likely *does*, if any.
|
|
class UserDefinedConversionSelector {
|
|
public:
|
|
/// The conversion associated with a conversion function, together with the
|
|
/// mixability flags of the conversion function's parameter or return type
|
|
/// to the rest of the sequence the selector is used in, and the sequence
|
|
/// that applied through the conversion itself.
|
|
struct PreparedConversion {
|
|
const CXXMethodDecl *ConversionFun;
|
|
MixFlags Flags;
|
|
ConversionSequence Seq;
|
|
|
|
PreparedConversion(const CXXMethodDecl *CMD, MixFlags F,
|
|
ConversionSequence S)
|
|
: ConversionFun(CMD), Flags(F), Seq(S) {}
|
|
};
|
|
|
|
UserDefinedConversionSelector(const TheCheck &Check) : Check(Check) {}
|
|
|
|
/// Adds the conversion between the two types for the given function into
|
|
/// the possible implicit conversion set. FromType and ToType is either:
|
|
/// * the result of a standard sequence and a converting ctor parameter
|
|
/// * the return type of a conversion operator and the expected target of
|
|
/// an implicit conversion.
|
|
void addConversion(const CXXMethodDecl *ConvFun, QualType FromType,
|
|
QualType ToType) {
|
|
// Try to go from the FromType to the ToType with only a single implicit
|
|
// conversion, to see if the conversion function is applicable.
|
|
MixData Mix = calculateMixability(
|
|
Check, FromType, ToType, ConvFun->getASTContext(),
|
|
ImplicitConversionModellingMode::OneWaySingleStandardOnly);
|
|
Mix.sanitize();
|
|
if (!Mix.indicatesMixability())
|
|
return;
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "--- tryConversion. Found viable with flags: "
|
|
<< formatMixFlags(Mix.Flags) << '\n');
|
|
FlaggedConversions.emplace_back(ConvFun, Mix.Flags, Mix.Conversion);
|
|
}
|
|
|
|
/// Selects the best conversion function that is applicable from the
|
|
/// prepared set of potential conversion functions taken.
|
|
Optional<PreparedConversion> operator()() const {
|
|
if (FlaggedConversions.empty()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- selectUserDefinedConv. Empty.\n");
|
|
return {};
|
|
}
|
|
if (FlaggedConversions.size() == 1) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- selectUserDefinedConv. Single.\n");
|
|
return FlaggedConversions.front();
|
|
}
|
|
|
|
Optional<PreparedConversion> BestConversion;
|
|
unsigned short HowManyGoodConversions = 0;
|
|
for (const auto &Prepared : FlaggedConversions) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- selectUserDefinedConv. Candidate flags: "
|
|
<< formatMixFlags(Prepared.Flags) << '\n');
|
|
if (!BestConversion) {
|
|
BestConversion = Prepared;
|
|
++HowManyGoodConversions;
|
|
continue;
|
|
}
|
|
|
|
bool BestConversionHasImplicit =
|
|
hasFlag(BestConversion->Flags, MixFlags::ImplicitConversion);
|
|
bool ThisConversionHasImplicit =
|
|
hasFlag(Prepared.Flags, MixFlags::ImplicitConversion);
|
|
if (!BestConversionHasImplicit && ThisConversionHasImplicit)
|
|
// This is a worse conversion, because a better one was found earlier.
|
|
continue;
|
|
|
|
if (BestConversionHasImplicit && !ThisConversionHasImplicit) {
|
|
// If the so far best selected conversion needs a previous implicit
|
|
// conversion to match the user-defined converting function, but this
|
|
// conversion does not, this is a better conversion, and we can throw
|
|
// away the previously selected conversion(s).
|
|
BestConversion = Prepared;
|
|
HowManyGoodConversions = 1;
|
|
continue;
|
|
}
|
|
|
|
if (BestConversionHasImplicit == ThisConversionHasImplicit)
|
|
// The current conversion is the same in term of goodness than the
|
|
// already selected one.
|
|
++HowManyGoodConversions;
|
|
}
|
|
|
|
if (HowManyGoodConversions == 1) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "--- selectUserDefinedConv. Unique result. Flags: "
|
|
<< formatMixFlags(BestConversion->Flags) << '\n');
|
|
return BestConversion;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "--- selectUserDefinedConv. No, or ambiguous.\n");
|
|
return {};
|
|
}
|
|
|
|
private:
|
|
llvm::SmallVector<PreparedConversion, 2> FlaggedConversions;
|
|
const TheCheck &Check;
|
|
};
|
|
|
|
} // namespace
|
|
|
|
static Optional<ConversionSequence>
|
|
tryConversionOperators(const TheCheck &Check, const CXXRecordDecl *RD,
|
|
QualType ToType) {
|
|
if (!RD || !RD->isCompleteDefinition())
|
|
return {};
|
|
RD = RD->getDefinition();
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << ">>> tryConversionOperators: " << RD->getName()
|
|
<< " to:\n";
|
|
ToType.dump(llvm::dbgs(), RD->getASTContext());
|
|
llvm::dbgs() << '\n';);
|
|
|
|
UserDefinedConversionSelector ConversionSet{Check};
|
|
|
|
for (const NamedDecl *Method : RD->getVisibleConversionFunctions()) {
|
|
const auto *Con = dyn_cast<CXXConversionDecl>(Method);
|
|
if (!Con || Con->isExplicit())
|
|
continue;
|
|
LLVM_DEBUG(llvm::dbgs() << "--- tryConversionOperators. Trying:\n";
|
|
Con->dump(llvm::dbgs()); llvm::dbgs() << '\n';);
|
|
|
|
// Try to go from the result of conversion operator to the expected type,
|
|
// without calculating another user-defined conversion.
|
|
ConversionSet.addConversion(Con, Con->getConversionType(), ToType);
|
|
}
|
|
|
|
if (Optional<UserDefinedConversionSelector::PreparedConversion>
|
|
SelectedConversion = ConversionSet()) {
|
|
QualType RecordType{RD->getTypeForDecl(), 0};
|
|
|
|
ConversionSequence Result{RecordType, ToType};
|
|
// The conversion from the operator call's return type to ToType was
|
|
// modelled as a "pre-conversion" in the operator call, but it is the
|
|
// "post-conversion" from the point of view of the original conversion
|
|
// we are modelling.
|
|
Result.AfterSecondStandard = SelectedConversion->Seq.AfterFirstStandard;
|
|
|
|
ConversionSequence::UserDefinedConversionOperator ConvOp;
|
|
ConvOp.Fun = cast<CXXConversionDecl>(SelectedConversion->ConversionFun);
|
|
ConvOp.UserDefinedType = RecordType;
|
|
ConvOp.ConversionOperatorResultType = ConvOp.Fun->getConversionType();
|
|
Result.setConversion(ConvOp);
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "<<< tryConversionOperators. Found result.\n");
|
|
return Result;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "<<< tryConversionOperators. No conversion.\n");
|
|
return {};
|
|
}
|
|
|
|
static Optional<ConversionSequence>
|
|
tryConvertingConstructors(const TheCheck &Check, QualType FromType,
|
|
const CXXRecordDecl *RD) {
|
|
if (!RD || !RD->isCompleteDefinition())
|
|
return {};
|
|
RD = RD->getDefinition();
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << ">>> tryConveringConstructors: " << RD->getName()
|
|
<< " from:\n";
|
|
FromType.dump(llvm::dbgs(), RD->getASTContext());
|
|
llvm::dbgs() << '\n';);
|
|
|
|
UserDefinedConversionSelector ConversionSet{Check};
|
|
|
|
for (const CXXConstructorDecl *Con : RD->ctors()) {
|
|
if (Con->isCopyOrMoveConstructor() ||
|
|
!Con->isConvertingConstructor(/* AllowExplicit =*/false))
|
|
continue;
|
|
LLVM_DEBUG(llvm::dbgs() << "--- tryConvertingConstructors. Trying:\n";
|
|
Con->dump(llvm::dbgs()); llvm::dbgs() << '\n';);
|
|
|
|
// Try to go from the original FromType to the converting constructor's
|
|
// parameter type without another user-defined conversion.
|
|
ConversionSet.addConversion(Con, FromType, Con->getParamDecl(0)->getType());
|
|
}
|
|
|
|
if (Optional<UserDefinedConversionSelector::PreparedConversion>
|
|
SelectedConversion = ConversionSet()) {
|
|
QualType RecordType{RD->getTypeForDecl(), 0};
|
|
|
|
ConversionSequence Result{FromType, RecordType};
|
|
Result.AfterFirstStandard = SelectedConversion->Seq.AfterFirstStandard;
|
|
|
|
ConversionSequence::UserDefinedConvertingConstructor Ctor;
|
|
Ctor.Fun = cast<CXXConstructorDecl>(SelectedConversion->ConversionFun);
|
|
Ctor.ConstructorParameterType = Ctor.Fun->getParamDecl(0)->getType();
|
|
Ctor.UserDefinedType = RecordType;
|
|
Result.setConversion(Ctor);
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "<<< tryConvertingConstructors. Found result.\n");
|
|
return Result;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "<<< tryConvertingConstructors. No conversion.\n");
|
|
return {};
|
|
}
|
|
|
|
/// Returns whether an expression of LType can be used in an RType context, as
|
|
/// per the implicit conversion rules.
|
|
///
|
|
/// Note: the result of this operation, unlike that of calculateMixability, is
|
|
/// **NOT** symmetric.
|
|
static MixData
|
|
approximateImplicitConversion(const TheCheck &Check, QualType LType,
|
|
QualType RType, const ASTContext &Ctx,
|
|
ImplicitConversionModellingMode ImplicitMode) {
|
|
LLVM_DEBUG(llvm::dbgs() << ">>> approximateImplicitConversion for LType:\n";
|
|
LType.dump(llvm::dbgs(), Ctx); llvm::dbgs() << "\nand RType:\n";
|
|
RType.dump(llvm::dbgs(), Ctx);
|
|
llvm::dbgs() << "\nimplicit mode: "; switch (ImplicitMode) {
|
|
case ImplicitConversionModellingMode::None:
|
|
llvm::dbgs() << "None";
|
|
break;
|
|
case ImplicitConversionModellingMode::All:
|
|
llvm::dbgs() << "All";
|
|
break;
|
|
case ImplicitConversionModellingMode::OneWaySingleStandardOnly:
|
|
llvm::dbgs() << "OneWay, Single, STD Only";
|
|
break;
|
|
} llvm::dbgs() << '\n';);
|
|
if (LType == RType)
|
|
return {MixFlags::Trivial, LType};
|
|
|
|
// An implicit conversion sequence consists of the following, in order:
|
|
// * Maybe standard conversion sequence.
|
|
// * Maybe user-defined conversion.
|
|
// * Maybe standard conversion sequence.
|
|
ConversionSequence ImplicitSeq{LType, RType};
|
|
QualType WorkType = LType;
|
|
|
|
Optional<QualType> AfterFirstStdConv =
|
|
approximateStandardConversionSequence(Check, LType, RType, Ctx);
|
|
if (AfterFirstStdConv) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- approximateImplicitConversion. Standard "
|
|
"Pre-Conversion found!\n");
|
|
ImplicitSeq.AfterFirstStandard = AfterFirstStdConv.getValue();
|
|
WorkType = ImplicitSeq.AfterFirstStandard;
|
|
}
|
|
|
|
if (ImplicitMode == ImplicitConversionModellingMode::OneWaySingleStandardOnly)
|
|
// If the caller only requested modelling of a standard conversion, bail.
|
|
return {ImplicitSeq.AfterFirstStandard.isNull()
|
|
? MixFlags::None
|
|
: MixFlags::ImplicitConversion,
|
|
ImplicitSeq};
|
|
|
|
if (Ctx.getLangOpts().CPlusPlus) {
|
|
bool FoundConversionOperator = false, FoundConvertingCtor = false;
|
|
|
|
if (const auto *LRD = WorkType->getAsCXXRecordDecl()) {
|
|
Optional<ConversionSequence> ConversionOperatorResult =
|
|
tryConversionOperators(Check, LRD, RType);
|
|
if (ConversionOperatorResult) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- approximateImplicitConversion. Found "
|
|
"conversion operator.\n");
|
|
ImplicitSeq.update(ConversionOperatorResult.getValue());
|
|
WorkType = ImplicitSeq.getTypeAfterUserDefinedConversion();
|
|
FoundConversionOperator = true;
|
|
}
|
|
}
|
|
|
|
if (const auto *RRD = RType->getAsCXXRecordDecl()) {
|
|
// Use the original "LType" here, and not WorkType, because the
|
|
// conversion to the converting constructors' parameters will be
|
|
// modelled in the recursive call.
|
|
Optional<ConversionSequence> ConvCtorResult =
|
|
tryConvertingConstructors(Check, LType, RRD);
|
|
if (ConvCtorResult) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- approximateImplicitConversion. Found "
|
|
"converting constructor.\n");
|
|
ImplicitSeq.update(ConvCtorResult.getValue());
|
|
WorkType = ImplicitSeq.getTypeAfterUserDefinedConversion();
|
|
FoundConvertingCtor = true;
|
|
}
|
|
}
|
|
|
|
if (FoundConversionOperator && FoundConvertingCtor) {
|
|
// If both an operator and a ctor matches, the sequence is ambiguous.
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "<<< approximateImplicitConversion. Found both "
|
|
"user-defined conversion kinds in the same sequence!\n");
|
|
return {MixFlags::None};
|
|
}
|
|
}
|
|
|
|
// After the potential user-defined conversion, another standard conversion
|
|
// sequence might exist.
|
|
LLVM_DEBUG(
|
|
llvm::dbgs()
|
|
<< "--- approximateImplicitConversion. Try to find post-conversion.\n");
|
|
MixData SecondStdConv = approximateImplicitConversion(
|
|
Check, WorkType, RType, Ctx,
|
|
ImplicitConversionModellingMode::OneWaySingleStandardOnly);
|
|
if (SecondStdConv.indicatesMixability()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "--- approximateImplicitConversion. Standard "
|
|
"Post-Conversion found!\n");
|
|
|
|
// The single-step modelling puts the modelled conversion into the "PreStd"
|
|
// variable in the recursive call, but from the PoV of this function, it is
|
|
// the post-conversion.
|
|
ImplicitSeq.AfterSecondStandard =
|
|
SecondStdConv.Conversion.AfterFirstStandard;
|
|
WorkType = ImplicitSeq.AfterSecondStandard;
|
|
}
|
|
|
|
if (ImplicitSeq) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "<<< approximateImplicitConversion. Found a conversion.\n");
|
|
return {MixFlags::ImplicitConversion, ImplicitSeq};
|
|
}
|
|
|
|
LLVM_DEBUG(
|
|
llvm::dbgs() << "<<< approximateImplicitConversion. No match found.\n");
|
|
return {MixFlags::None};
|
|
}
|
|
|
|
static MixableParameterRange modelMixingRange(
|
|
const TheCheck &Check, const FunctionDecl *FD, std::size_t StartIndex,
|
|
const filter::SimilarlyUsedParameterPairSuppressor &UsageBasedSuppressor) {
|
|
std::size_t NumParams = FD->getNumParams();
|
|
assert(StartIndex < NumParams && "out of bounds for start");
|
|
const ASTContext &Ctx = FD->getASTContext();
|
|
|
|
MixableParameterRange Ret;
|
|
// A parameter at index 'StartIndex' had been trivially "checked".
|
|
Ret.NumParamsChecked = 1;
|
|
|
|
for (std::size_t I = StartIndex + 1; I < NumParams; ++I) {
|
|
const ParmVarDecl *Ith = FD->getParamDecl(I);
|
|
StringRef ParamName = Ith->getName();
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "Check param #" << I << " '" << ParamName << "'...\n");
|
|
if (filter::isIgnoredParameter(Check, Ith)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Param #" << I << " is ignored. Break!\n");
|
|
break;
|
|
}
|
|
|
|
StringRef PrevParamName = FD->getParamDecl(I - 1)->getName();
|
|
if (!ParamName.empty() && !PrevParamName.empty() &&
|
|
filter::prefixSuffixCoverUnderThreshold(
|
|
Check.NamePrefixSuffixSilenceDissimilarityTreshold, PrevParamName,
|
|
ParamName)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Parameter '" << ParamName
|
|
<< "' follows a pattern with previous parameter '"
|
|
<< PrevParamName << "'. Break!\n");
|
|
break;
|
|
}
|
|
|
|
// Now try to go forward and build the range of [Start, ..., I, I + 1, ...]
|
|
// parameters that can be messed up at a call site.
|
|
MixableParameterRange::MixVector MixesOfIth;
|
|
for (std::size_t J = StartIndex; J < I; ++J) {
|
|
const ParmVarDecl *Jth = FD->getParamDecl(J);
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "Check mix of #" << J << " against #" << I << "...\n");
|
|
|
|
if (isSimilarlyUsedParameter(UsageBasedSuppressor, Ith, Jth)) {
|
|
// Consider the two similarly used parameters to not be possible in a
|
|
// mix-up at the user's request, if they enabled this heuristic.
|
|
LLVM_DEBUG(llvm::dbgs() << "Parameters #" << I << " and #" << J
|
|
<< " deemed related, ignoring...\n");
|
|
|
|
// If the parameter #I and #J mixes, then I is mixable with something
|
|
// in the current range, so the range has to be broken and I not
|
|
// included.
|
|
MixesOfIth.clear();
|
|
break;
|
|
}
|
|
|
|
Mix M{Jth, Ith,
|
|
calculateMixability(Check, Jth->getType(), Ith->getType(), Ctx,
|
|
Check.ModelImplicitConversions
|
|
? ImplicitConversionModellingMode::All
|
|
: ImplicitConversionModellingMode::None)};
|
|
LLVM_DEBUG(llvm::dbgs() << "Mix flags (raw) : "
|
|
<< formatMixFlags(M.flags()) << '\n');
|
|
M.sanitize();
|
|
LLVM_DEBUG(llvm::dbgs() << "Mix flags (after sanitize): "
|
|
<< formatMixFlags(M.flags()) << '\n');
|
|
|
|
assert(M.flagsValid() && "All flags decayed!");
|
|
|
|
if (M.mixable())
|
|
MixesOfIth.emplace_back(std::move(M));
|
|
}
|
|
|
|
if (MixesOfIth.empty()) {
|
|
// If there weren't any new mixes stored for Ith, the range is
|
|
// [Start, ..., I].
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "Param #" << I
|
|
<< " does not mix with any in the current range. Break!\n");
|
|
break;
|
|
}
|
|
|
|
Ret.Mixes.insert(Ret.Mixes.end(), MixesOfIth.begin(), MixesOfIth.end());
|
|
++Ret.NumParamsChecked; // Otherwise a new param was iterated.
|
|
}
|
|
|
|
return Ret;
|
|
}
|
|
|
|
} // namespace model
|
|
|
|
/// Matches DeclRefExprs and their ignorable wrappers to ParmVarDecls.
|
|
AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<Stmt>, paramRefExpr) {
|
|
return expr(ignoringParenImpCasts(ignoringElidableConstructorCall(
|
|
declRefExpr(to(parmVarDecl().bind("param"))))));
|
|
}
|
|
|
|
namespace filter {
|
|
|
|
/// Returns whether the parameter's name or the parameter's type's name is
|
|
/// configured by the user to be ignored from analysis and diagnostic.
|
|
static bool isIgnoredParameter(const TheCheck &Check, const ParmVarDecl *Node) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Checking if '" << Node->getName()
|
|
<< "' is ignored.\n");
|
|
|
|
if (!Node->getIdentifier())
|
|
return llvm::find(Check.IgnoredParameterNames, "\"\"") !=
|
|
Check.IgnoredParameterNames.end();
|
|
|
|
StringRef NodeName = Node->getName();
|
|
if (llvm::find(Check.IgnoredParameterNames, NodeName) !=
|
|
Check.IgnoredParameterNames.end()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "\tName ignored.\n");
|
|
return true;
|
|
}
|
|
|
|
StringRef NodeTypeName = [Node] {
|
|
const ASTContext &Ctx = Node->getASTContext();
|
|
const SourceManager &SM = Ctx.getSourceManager();
|
|
SourceLocation B = Node->getTypeSpecStartLoc();
|
|
SourceLocation E = Node->getTypeSpecEndLoc();
|
|
LangOptions LO;
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "\tType name code is '"
|
|
<< Lexer::getSourceText(
|
|
CharSourceRange::getTokenRange(B, E), SM, LO)
|
|
<< "'...\n");
|
|
if (B.isMacroID()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "\t\tBeginning is macro.\n");
|
|
B = SM.getTopMacroCallerLoc(B);
|
|
}
|
|
if (E.isMacroID()) {
|
|
LLVM_DEBUG(llvm::dbgs() << "\t\tEnding is macro.\n");
|
|
E = Lexer::getLocForEndOfToken(SM.getTopMacroCallerLoc(E), 0, SM, LO);
|
|
}
|
|
LLVM_DEBUG(llvm::dbgs() << "\tType name code is '"
|
|
<< Lexer::getSourceText(
|
|
CharSourceRange::getTokenRange(B, E), SM, LO)
|
|
<< "'...\n");
|
|
|
|
return Lexer::getSourceText(CharSourceRange::getTokenRange(B, E), SM, LO);
|
|
}();
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "\tType name is '" << NodeTypeName << "'\n");
|
|
if (!NodeTypeName.empty()) {
|
|
if (llvm::any_of(Check.IgnoredParameterTypeSuffixes,
|
|
[NodeTypeName](const std::string &E) {
|
|
return !E.empty() && NodeTypeName.endswith(E);
|
|
})) {
|
|
LLVM_DEBUG(llvm::dbgs() << "\tType suffix ignored.\n");
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/// This namespace contains the implementations for the suppression of
|
|
/// diagnostics from similarly-used ("related") parameters.
|
|
namespace relatedness_heuristic {
|
|
|
|
static constexpr std::size_t SmallDataStructureSize = 4;
|
|
|
|
template <typename T, std::size_t N = SmallDataStructureSize>
|
|
using ParamToSmallSetMap =
|
|
llvm::DenseMap<const ParmVarDecl *, llvm::SmallSet<T, N>>;
|
|
|
|
/// Returns whether the sets mapped to the two elements in the map have at
|
|
/// least one element in common.
|
|
template <typename MapTy, typename ElemTy>
|
|
bool lazyMapOfSetsIntersectionExists(const MapTy &Map, const ElemTy &E1,
|
|
const ElemTy &E2) {
|
|
auto E1Iterator = Map.find(E1);
|
|
auto E2Iterator = Map.find(E2);
|
|
if (E1Iterator == Map.end() || E2Iterator == Map.end())
|
|
return false;
|
|
|
|
for (const auto &E1SetElem : E1Iterator->second)
|
|
if (llvm::find(E2Iterator->second, E1SetElem) != E2Iterator->second.end())
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
/// Implements the heuristic that marks two parameters related if there is
|
|
/// a usage for both in the same strict expression subtree. A strict
|
|
/// expression subtree is a tree which only includes Expr nodes, i.e. no
|
|
/// Stmts and no Decls.
|
|
class AppearsInSameExpr : public RecursiveASTVisitor<AppearsInSameExpr> {
|
|
using Base = RecursiveASTVisitor<AppearsInSameExpr>;
|
|
|
|
const FunctionDecl *FD;
|
|
const Expr *CurrentExprOnlyTreeRoot = nullptr;
|
|
llvm::DenseMap<const ParmVarDecl *,
|
|
llvm::SmallPtrSet<const Expr *, SmallDataStructureSize>>
|
|
ParentExprsForParamRefs;
|
|
|
|
public:
|
|
void setup(const FunctionDecl *FD) {
|
|
this->FD = FD;
|
|
TraverseFunctionDecl(const_cast<FunctionDecl *>(FD));
|
|
}
|
|
|
|
bool operator()(const ParmVarDecl *Param1, const ParmVarDecl *Param2) const {
|
|
return lazyMapOfSetsIntersectionExists(ParentExprsForParamRefs, Param1,
|
|
Param2);
|
|
}
|
|
|
|
bool TraverseDecl(Decl *D) {
|
|
CurrentExprOnlyTreeRoot = nullptr;
|
|
return Base::TraverseDecl(D);
|
|
}
|
|
|
|
bool TraverseStmt(Stmt *S, DataRecursionQueue *Queue = nullptr) {
|
|
if (auto *E = dyn_cast_or_null<Expr>(S)) {
|
|
bool RootSetInCurrentStackFrame = false;
|
|
if (!CurrentExprOnlyTreeRoot) {
|
|
CurrentExprOnlyTreeRoot = E;
|
|
RootSetInCurrentStackFrame = true;
|
|
}
|
|
|
|
bool Ret = Base::TraverseStmt(S);
|
|
|
|
if (RootSetInCurrentStackFrame)
|
|
CurrentExprOnlyTreeRoot = nullptr;
|
|
|
|
return Ret;
|
|
}
|
|
|
|
// A Stmt breaks the strictly Expr subtree.
|
|
CurrentExprOnlyTreeRoot = nullptr;
|
|
return Base::TraverseStmt(S);
|
|
}
|
|
|
|
bool VisitDeclRefExpr(DeclRefExpr *DRE) {
|
|
if (!CurrentExprOnlyTreeRoot)
|
|
return true;
|
|
|
|
if (auto *PVD = dyn_cast<ParmVarDecl>(DRE->getDecl()))
|
|
if (llvm::find(FD->parameters(), PVD))
|
|
ParentExprsForParamRefs[PVD].insert(CurrentExprOnlyTreeRoot);
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
/// Implements the heuristic that marks two parameters related if there are
|
|
/// two separate calls to the same function (overload) and the parameters are
|
|
/// passed to the same index in both calls, i.e f(a, b) and f(a, c) passes
|
|
/// b and c to the same index (2) of f(), marking them related.
|
|
class PassedToSameFunction {
|
|
ParamToSmallSetMap<std::pair<const FunctionDecl *, unsigned>> TargetParams;
|
|
|
|
public:
|
|
void setup(const FunctionDecl *FD) {
|
|
auto ParamsAsArgsInFnCalls =
|
|
match(functionDecl(forEachDescendant(
|
|
callExpr(forEachArgumentWithParam(
|
|
paramRefExpr(), parmVarDecl().bind("passed-to")))
|
|
.bind("call-expr"))),
|
|
*FD, FD->getASTContext());
|
|
for (const auto &Match : ParamsAsArgsInFnCalls) {
|
|
const auto *PassedParamOfThisFn = Match.getNodeAs<ParmVarDecl>("param");
|
|
const auto *CE = Match.getNodeAs<CallExpr>("call-expr");
|
|
const auto *PassedToParam = Match.getNodeAs<ParmVarDecl>("passed-to");
|
|
assert(PassedParamOfThisFn && CE && PassedToParam);
|
|
|
|
const FunctionDecl *CalledFn = CE->getDirectCallee();
|
|
if (!CalledFn)
|
|
continue;
|
|
|
|
llvm::Optional<unsigned> TargetIdx;
|
|
unsigned NumFnParams = CalledFn->getNumParams();
|
|
for (unsigned Idx = 0; Idx < NumFnParams; ++Idx)
|
|
if (CalledFn->getParamDecl(Idx) == PassedToParam)
|
|
TargetIdx.emplace(Idx);
|
|
|
|
assert(TargetIdx.hasValue() && "Matched, but didn't find index?");
|
|
TargetParams[PassedParamOfThisFn].insert(
|
|
{CalledFn->getCanonicalDecl(), *TargetIdx});
|
|
}
|
|
}
|
|
|
|
bool operator()(const ParmVarDecl *Param1, const ParmVarDecl *Param2) const {
|
|
return lazyMapOfSetsIntersectionExists(TargetParams, Param1, Param2);
|
|
}
|
|
};
|
|
|
|
/// Implements the heuristic that marks two parameters related if the same
|
|
/// member is accessed (referred to) inside the current function's body.
|
|
class AccessedSameMemberOf {
|
|
ParamToSmallSetMap<const Decl *> AccessedMembers;
|
|
|
|
public:
|
|
void setup(const FunctionDecl *FD) {
|
|
auto MembersCalledOnParams = match(
|
|
functionDecl(forEachDescendant(
|
|
memberExpr(hasObjectExpression(paramRefExpr())).bind("mem-expr"))),
|
|
*FD, FD->getASTContext());
|
|
|
|
for (const auto &Match : MembersCalledOnParams) {
|
|
const auto *AccessedParam = Match.getNodeAs<ParmVarDecl>("param");
|
|
const auto *ME = Match.getNodeAs<MemberExpr>("mem-expr");
|
|
assert(AccessedParam && ME);
|
|
AccessedMembers[AccessedParam].insert(
|
|
ME->getMemberDecl()->getCanonicalDecl());
|
|
}
|
|
}
|
|
|
|
bool operator()(const ParmVarDecl *Param1, const ParmVarDecl *Param2) const {
|
|
return lazyMapOfSetsIntersectionExists(AccessedMembers, Param1, Param2);
|
|
}
|
|
};
|
|
|
|
/// Implements the heuristic that marks two parameters related if different
|
|
/// ReturnStmts return them from the function.
|
|
class Returned {
|
|
llvm::SmallVector<const ParmVarDecl *, SmallDataStructureSize> ReturnedParams;
|
|
|
|
public:
|
|
void setup(const FunctionDecl *FD) {
|
|
// TODO: Handle co_return.
|
|
auto ParamReturns = match(functionDecl(forEachDescendant(
|
|
returnStmt(hasReturnValue(paramRefExpr())))),
|
|
*FD, FD->getASTContext());
|
|
for (const auto &Match : ParamReturns) {
|
|
const auto *ReturnedParam = Match.getNodeAs<ParmVarDecl>("param");
|
|
assert(ReturnedParam);
|
|
|
|
if (find(FD->parameters(), ReturnedParam) == FD->param_end())
|
|
// Inside the subtree of a FunctionDecl there might be ReturnStmts of
|
|
// a parameter that isn't the parameter of the function, e.g. in the
|
|
// case of lambdas.
|
|
continue;
|
|
|
|
ReturnedParams.emplace_back(ReturnedParam);
|
|
}
|
|
}
|
|
|
|
bool operator()(const ParmVarDecl *Param1, const ParmVarDecl *Param2) const {
|
|
return llvm::find(ReturnedParams, Param1) != ReturnedParams.end() &&
|
|
llvm::find(ReturnedParams, Param2) != ReturnedParams.end();
|
|
}
|
|
};
|
|
|
|
} // namespace relatedness_heuristic
|
|
|
|
/// Helper class that is used to detect if two parameters of the same function
|
|
/// are used in a similar fashion, to suppress the result.
|
|
class SimilarlyUsedParameterPairSuppressor {
|
|
const bool Enabled;
|
|
relatedness_heuristic::AppearsInSameExpr SameExpr;
|
|
relatedness_heuristic::PassedToSameFunction PassToFun;
|
|
relatedness_heuristic::AccessedSameMemberOf SameMember;
|
|
relatedness_heuristic::Returned Returns;
|
|
|
|
public:
|
|
SimilarlyUsedParameterPairSuppressor(const FunctionDecl *FD, bool Enable)
|
|
: Enabled(Enable) {
|
|
if (!Enable)
|
|
return;
|
|
|
|
SameExpr.setup(FD);
|
|
PassToFun.setup(FD);
|
|
SameMember.setup(FD);
|
|
Returns.setup(FD);
|
|
}
|
|
|
|
/// Returns whether the specified two parameters are deemed similarly used
|
|
/// or related by the heuristics.
|
|
bool operator()(const ParmVarDecl *Param1, const ParmVarDecl *Param2) const {
|
|
if (!Enabled)
|
|
return false;
|
|
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "::: Matching similar usage / relatedness heuristic...\n");
|
|
|
|
if (SameExpr(Param1, Param2)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "::: Used in the same expression.\n");
|
|
return true;
|
|
}
|
|
|
|
if (PassToFun(Param1, Param2)) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "::: Passed to same function in different calls.\n");
|
|
return true;
|
|
}
|
|
|
|
if (SameMember(Param1, Param2)) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "::: Same member field access or method called.\n");
|
|
return true;
|
|
}
|
|
|
|
if (Returns(Param1, Param2)) {
|
|
LLVM_DEBUG(llvm::dbgs() << "::: Both parameter returned.\n");
|
|
return true;
|
|
}
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "::: None.\n");
|
|
return false;
|
|
}
|
|
};
|
|
|
|
// (This function hoists the call to operator() of the wrapper, so we do not
|
|
// need to define the previous class at the top of the file.)
|
|
static inline bool
|
|
isSimilarlyUsedParameter(const SimilarlyUsedParameterPairSuppressor &Suppressor,
|
|
const ParmVarDecl *Param1, const ParmVarDecl *Param2) {
|
|
return Suppressor(Param1, Param2);
|
|
}
|
|
|
|
static void padStringAtEnd(SmallVectorImpl<char> &Str, std::size_t ToLen) {
|
|
while (Str.size() < ToLen)
|
|
Str.emplace_back('\0');
|
|
}
|
|
|
|
static void padStringAtBegin(SmallVectorImpl<char> &Str, std::size_t ToLen) {
|
|
while (Str.size() < ToLen)
|
|
Str.insert(Str.begin(), '\0');
|
|
}
|
|
|
|
static bool isCommonPrefixWithoutSomeCharacters(std::size_t N, StringRef S1,
|
|
StringRef S2) {
|
|
assert(S1.size() >= N && S2.size() >= N);
|
|
StringRef S1Prefix = S1.take_front(S1.size() - N),
|
|
S2Prefix = S2.take_front(S2.size() - N);
|
|
return S1Prefix == S2Prefix && !S1Prefix.empty();
|
|
}
|
|
|
|
static bool isCommonSuffixWithoutSomeCharacters(std::size_t N, StringRef S1,
|
|
StringRef S2) {
|
|
assert(S1.size() >= N && S2.size() >= N);
|
|
StringRef S1Suffix = S1.take_back(S1.size() - N),
|
|
S2Suffix = S2.take_back(S2.size() - N);
|
|
return S1Suffix == S2Suffix && !S1Suffix.empty();
|
|
}
|
|
|
|
/// Returns whether the two strings are prefixes or suffixes of each other with
|
|
/// at most Threshold characters differing on the non-common end.
|
|
static bool prefixSuffixCoverUnderThreshold(std::size_t Threshold,
|
|
StringRef Str1, StringRef Str2) {
|
|
if (Threshold == 0)
|
|
return false;
|
|
|
|
// Pad the two strings to the longer length.
|
|
std::size_t BiggerLength = std::max(Str1.size(), Str2.size());
|
|
|
|
if (BiggerLength <= Threshold)
|
|
// If the length of the strings is still smaller than the threshold, they
|
|
// would be covered by an empty prefix/suffix with the rest differing.
|
|
// (E.g. "A" and "X" with Threshold = 1 would mean we think they are
|
|
// similar and do not warn about them, which is a too eager assumption.)
|
|
return false;
|
|
|
|
SmallString<32> S1PadE{Str1}, S2PadE{Str2};
|
|
padStringAtEnd(S1PadE, BiggerLength);
|
|
padStringAtEnd(S2PadE, BiggerLength);
|
|
|
|
if (isCommonPrefixWithoutSomeCharacters(
|
|
Threshold, StringRef{S1PadE.begin(), BiggerLength},
|
|
StringRef{S2PadE.begin(), BiggerLength}))
|
|
return true;
|
|
|
|
SmallString<32> S1PadB{Str1}, S2PadB{Str2};
|
|
padStringAtBegin(S1PadB, BiggerLength);
|
|
padStringAtBegin(S2PadB, BiggerLength);
|
|
|
|
if (isCommonSuffixWithoutSomeCharacters(
|
|
Threshold, StringRef{S1PadB.begin(), BiggerLength},
|
|
StringRef{S2PadB.begin(), BiggerLength}))
|
|
return true;
|
|
|
|
return false;
|
|
}
|
|
|
|
} // namespace filter
|
|
|
|
/// Matches functions that have at least the specified amount of parameters.
|
|
AST_MATCHER_P(FunctionDecl, parameterCountGE, unsigned, N) {
|
|
return Node.getNumParams() >= N;
|
|
}
|
|
|
|
/// Matches *any* overloaded unary and binary operators.
|
|
AST_MATCHER(FunctionDecl, isOverloadedUnaryOrBinaryOperator) {
|
|
switch (Node.getOverloadedOperator()) {
|
|
case OO_None:
|
|
case OO_New:
|
|
case OO_Delete:
|
|
case OO_Array_New:
|
|
case OO_Array_Delete:
|
|
case OO_Conditional:
|
|
case OO_Coawait:
|
|
return false;
|
|
|
|
default:
|
|
return Node.getNumParams() <= 2;
|
|
}
|
|
}
|
|
|
|
/// Returns the DefaultMinimumLength if the Value of requested minimum length
|
|
/// is less than 2. Minimum lengths of 0 or 1 are not accepted.
|
|
static inline unsigned clampMinimumLength(const unsigned Value) {
|
|
return Value < 2 ? DefaultMinimumLength : Value;
|
|
}
|
|
|
|
// FIXME: Maybe unneeded, getNameForDiagnostic() is expected to change to return
|
|
// a crafted location when the node itself is unnamed. (See D84658, D85033.)
|
|
/// Returns the diagnostic-friendly name of the node, or empty string.
|
|
static SmallString<64> getName(const NamedDecl *ND) {
|
|
SmallString<64> Name;
|
|
llvm::raw_svector_ostream OS{Name};
|
|
ND->getNameForDiagnostic(OS, ND->getASTContext().getPrintingPolicy(), false);
|
|
return Name;
|
|
}
|
|
|
|
/// Returns the diagnostic-friendly name of the node, or a constant value.
|
|
static SmallString<64> getNameOrUnnamed(const NamedDecl *ND) {
|
|
auto Name = getName(ND);
|
|
if (Name.empty())
|
|
Name = "<unnamed>";
|
|
return Name;
|
|
}
|
|
|
|
/// Returns whether a particular Mix between two parameters should have the
|
|
/// types involved diagnosed to the user. This is only a flag check.
|
|
static inline bool needsToPrintTypeInDiagnostic(const model::Mix &M) {
|
|
using namespace model;
|
|
return static_cast<bool>(
|
|
M.flags() &
|
|
(MixFlags::TypeAlias | MixFlags::ReferenceBind | MixFlags::Qualifiers));
|
|
}
|
|
|
|
/// Returns whether a particular Mix between the two parameters should have
|
|
/// implicit conversions elaborated.
|
|
static inline bool needsToElaborateImplicitConversion(const model::Mix &M) {
|
|
return hasFlag(M.flags(), model::MixFlags::ImplicitConversion);
|
|
}
|
|
|
|
namespace {
|
|
|
|
/// This class formats a conversion sequence into a "Ty1 -> Ty2 -> Ty3" line
|
|
/// that can be used in diagnostics.
|
|
struct FormattedConversionSequence {
|
|
std::string DiagnosticText;
|
|
|
|
/// The formatted sequence is trivial if it is "Ty1 -> Ty2", but Ty1 and
|
|
/// Ty2 are the types that are shown in the code. A trivial diagnostic
|
|
/// does not need to be printed.
|
|
bool Trivial;
|
|
|
|
FormattedConversionSequence(const PrintingPolicy &PP,
|
|
StringRef StartTypeAsDiagnosed,
|
|
const model::ConversionSequence &Conv,
|
|
StringRef DestinationTypeAsDiagnosed) {
|
|
Trivial = true;
|
|
llvm::raw_string_ostream OS{DiagnosticText};
|
|
|
|
// Print the type name as it is printed in other places in the diagnostic.
|
|
OS << '\'' << StartTypeAsDiagnosed << '\'';
|
|
std::string LastAddedType = StartTypeAsDiagnosed.str();
|
|
std::size_t NumElementsAdded = 1;
|
|
|
|
// However, the parameter's defined type might not be what the implicit
|
|
// conversion started with, e.g. if a typedef is found to convert.
|
|
std::string SeqBeginTypeStr = Conv.Begin.getAsString(PP);
|
|
std::string SeqEndTypeStr = Conv.End.getAsString(PP);
|
|
if (StartTypeAsDiagnosed != SeqBeginTypeStr) {
|
|
OS << " (as '" << SeqBeginTypeStr << "')";
|
|
LastAddedType = SeqBeginTypeStr;
|
|
Trivial = false;
|
|
}
|
|
|
|
auto AddType = [&](StringRef ToAdd) {
|
|
if (LastAddedType != ToAdd && ToAdd != SeqEndTypeStr) {
|
|
OS << " -> '" << ToAdd << "'";
|
|
LastAddedType = ToAdd.str();
|
|
++NumElementsAdded;
|
|
}
|
|
};
|
|
for (QualType InvolvedType : Conv.getInvolvedTypesInSequence())
|
|
// Print every type that's unique in the sequence into the diagnosis.
|
|
AddType(InvolvedType.getAsString(PP));
|
|
|
|
if (LastAddedType != DestinationTypeAsDiagnosed) {
|
|
OS << " -> '" << DestinationTypeAsDiagnosed << "'";
|
|
LastAddedType = DestinationTypeAsDiagnosed.str();
|
|
++NumElementsAdded;
|
|
}
|
|
|
|
// Same reasoning as with the Begin, e.g. if the converted-to type is a
|
|
// typedef, it will not be the same inside the conversion sequence (where
|
|
// the model already tore off typedefs) as in the code.
|
|
if (DestinationTypeAsDiagnosed != SeqEndTypeStr) {
|
|
OS << " (as '" << SeqEndTypeStr << "')";
|
|
LastAddedType = SeqEndTypeStr;
|
|
Trivial = false;
|
|
}
|
|
|
|
if (Trivial && NumElementsAdded > 2)
|
|
// If the thing is still marked trivial but we have more than the
|
|
// from and to types added, it should not be trivial, and elaborated
|
|
// when printing the diagnostic.
|
|
Trivial = false;
|
|
}
|
|
};
|
|
|
|
/// Retains the elements called with and returns whether the call is done with
|
|
/// a new element.
|
|
template <typename E, std::size_t N> class InsertOnce {
|
|
llvm::SmallSet<E, N> CalledWith;
|
|
|
|
public:
|
|
bool operator()(E El) { return CalledWith.insert(std::move(El)).second; }
|
|
|
|
bool calledWith(const E &El) const { return CalledWith.contains(El); }
|
|
};
|
|
|
|
struct SwappedEqualQualTypePair {
|
|
QualType LHSType, RHSType;
|
|
|
|
bool operator==(const SwappedEqualQualTypePair &Other) const {
|
|
return (LHSType == Other.LHSType && RHSType == Other.RHSType) ||
|
|
(LHSType == Other.RHSType && RHSType == Other.LHSType);
|
|
}
|
|
|
|
bool operator<(const SwappedEqualQualTypePair &Other) const {
|
|
return LHSType < Other.LHSType && RHSType < Other.RHSType;
|
|
}
|
|
};
|
|
|
|
struct TypeAliasDiagnosticTuple {
|
|
QualType LHSType, RHSType, CommonType;
|
|
|
|
bool operator==(const TypeAliasDiagnosticTuple &Other) const {
|
|
return CommonType == Other.CommonType &&
|
|
((LHSType == Other.LHSType && RHSType == Other.RHSType) ||
|
|
(LHSType == Other.RHSType && RHSType == Other.LHSType));
|
|
}
|
|
|
|
bool operator<(const TypeAliasDiagnosticTuple &Other) const {
|
|
return CommonType < Other.CommonType && LHSType < Other.LHSType &&
|
|
RHSType < Other.RHSType;
|
|
}
|
|
};
|
|
|
|
/// Helper class to only emit a diagnostic related to MixFlags::TypeAlias once.
|
|
class UniqueTypeAliasDiagnosticHelper
|
|
: public InsertOnce<TypeAliasDiagnosticTuple, 8> {
|
|
using Base = InsertOnce<TypeAliasDiagnosticTuple, 8>;
|
|
|
|
public:
|
|
/// Returns whether the diagnostic for LHSType and RHSType which are both
|
|
/// referring to CommonType being the same has not been emitted already.
|
|
bool operator()(QualType LHSType, QualType RHSType, QualType CommonType) {
|
|
if (CommonType.isNull() || CommonType == LHSType || CommonType == RHSType)
|
|
return Base::operator()({LHSType, RHSType, {}});
|
|
|
|
TypeAliasDiagnosticTuple ThreeTuple{LHSType, RHSType, CommonType};
|
|
if (!Base::operator()(ThreeTuple))
|
|
return false;
|
|
|
|
bool AlreadySaidLHSAndCommonIsSame = calledWith({LHSType, CommonType, {}});
|
|
bool AlreadySaidRHSAndCommonIsSame = calledWith({RHSType, CommonType, {}});
|
|
if (AlreadySaidLHSAndCommonIsSame && AlreadySaidRHSAndCommonIsSame) {
|
|
// "SomeInt == int" && "SomeOtherInt == int" => "Common(SomeInt,
|
|
// SomeOtherInt) == int", no need to diagnose it. Save the 3-tuple only
|
|
// for shortcut if it ever appears again.
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
};
|
|
|
|
} // namespace
|
|
|
|
EasilySwappableParametersCheck::EasilySwappableParametersCheck(
|
|
StringRef Name, ClangTidyContext *Context)
|
|
: ClangTidyCheck(Name, Context),
|
|
MinimumLength(clampMinimumLength(
|
|
Options.get("MinimumLength", DefaultMinimumLength))),
|
|
IgnoredParameterNames(optutils::parseStringList(
|
|
Options.get("IgnoredParameterNames", DefaultIgnoredParameterNames))),
|
|
IgnoredParameterTypeSuffixes(optutils::parseStringList(
|
|
Options.get("IgnoredParameterTypeSuffixes",
|
|
DefaultIgnoredParameterTypeSuffixes))),
|
|
QualifiersMix(Options.get("QualifiersMix", DefaultQualifiersMix)),
|
|
ModelImplicitConversions(Options.get("ModelImplicitConversions",
|
|
DefaultModelImplicitConversions)),
|
|
SuppressParametersUsedTogether(
|
|
Options.get("SuppressParametersUsedTogether",
|
|
DefaultSuppressParametersUsedTogether)),
|
|
NamePrefixSuffixSilenceDissimilarityTreshold(
|
|
Options.get("NamePrefixSuffixSilenceDissimilarityTreshold",
|
|
DefaultNamePrefixSuffixSilenceDissimilarityTreshold)) {}
|
|
|
|
void EasilySwappableParametersCheck::storeOptions(
|
|
ClangTidyOptions::OptionMap &Opts) {
|
|
Options.store(Opts, "MinimumLength", MinimumLength);
|
|
Options.store(Opts, "IgnoredParameterNames",
|
|
optutils::serializeStringList(IgnoredParameterNames));
|
|
Options.store(Opts, "IgnoredParameterTypeSuffixes",
|
|
optutils::serializeStringList(IgnoredParameterTypeSuffixes));
|
|
Options.store(Opts, "QualifiersMix", QualifiersMix);
|
|
Options.store(Opts, "ModelImplicitConversions", ModelImplicitConversions);
|
|
Options.store(Opts, "SuppressParametersUsedTogether",
|
|
SuppressParametersUsedTogether);
|
|
Options.store(Opts, "NamePrefixSuffixSilenceDissimilarityTreshold",
|
|
NamePrefixSuffixSilenceDissimilarityTreshold);
|
|
}
|
|
|
|
void EasilySwappableParametersCheck::registerMatchers(MatchFinder *Finder) {
|
|
const auto BaseConstraints = functionDecl(
|
|
// Only report for definition nodes, as fixing the issues reported
|
|
// requires the user to be able to change code.
|
|
isDefinition(), parameterCountGE(MinimumLength),
|
|
unless(isOverloadedUnaryOrBinaryOperator()));
|
|
|
|
Finder->addMatcher(
|
|
functionDecl(BaseConstraints,
|
|
unless(ast_matchers::isTemplateInstantiation()))
|
|
.bind("func"),
|
|
this);
|
|
Finder->addMatcher(
|
|
functionDecl(BaseConstraints, isExplicitTemplateSpecialization())
|
|
.bind("func"),
|
|
this);
|
|
}
|
|
|
|
void EasilySwappableParametersCheck::check(
|
|
const MatchFinder::MatchResult &Result) {
|
|
using namespace model;
|
|
using namespace filter;
|
|
|
|
const auto *FD = Result.Nodes.getNodeAs<FunctionDecl>("func");
|
|
assert(FD);
|
|
|
|
const PrintingPolicy &PP = FD->getASTContext().getPrintingPolicy();
|
|
std::size_t NumParams = FD->getNumParams();
|
|
std::size_t MixableRangeStartIndex = 0;
|
|
|
|
// Spawn one suppressor and if the user requested, gather information from
|
|
// the AST for the parameters' usages.
|
|
filter::SimilarlyUsedParameterPairSuppressor UsageBasedSuppressor{
|
|
FD, SuppressParametersUsedTogether};
|
|
|
|
LLVM_DEBUG(llvm::dbgs() << "Begin analysis of " << getName(FD) << " with "
|
|
<< NumParams << " parameters...\n");
|
|
while (MixableRangeStartIndex < NumParams) {
|
|
if (isIgnoredParameter(*this, FD->getParamDecl(MixableRangeStartIndex))) {
|
|
LLVM_DEBUG(llvm::dbgs()
|
|
<< "Parameter #" << MixableRangeStartIndex << " ignored.\n");
|
|
++MixableRangeStartIndex;
|
|
continue;
|
|
}
|
|
|
|
MixableParameterRange R = modelMixingRange(
|
|
*this, FD, MixableRangeStartIndex, UsageBasedSuppressor);
|
|
assert(R.NumParamsChecked > 0 && "Ensure forward progress!");
|
|
MixableRangeStartIndex += R.NumParamsChecked;
|
|
if (R.NumParamsChecked < MinimumLength) {
|
|
LLVM_DEBUG(llvm::dbgs() << "Ignoring range of " << R.NumParamsChecked
|
|
<< " lower than limit.\n");
|
|
continue;
|
|
}
|
|
|
|
bool NeedsAnyTypeNote = llvm::any_of(R.Mixes, needsToPrintTypeInDiagnostic);
|
|
bool HasAnyImplicits =
|
|
llvm::any_of(R.Mixes, needsToElaborateImplicitConversion);
|
|
const ParmVarDecl *First = R.getFirstParam(), *Last = R.getLastParam();
|
|
std::string FirstParamTypeAsWritten = First->getType().getAsString(PP);
|
|
{
|
|
StringRef DiagText;
|
|
|
|
if (HasAnyImplicits)
|
|
DiagText = "%0 adjacent parameters of %1 of convertible types are "
|
|
"easily swapped by mistake";
|
|
else if (NeedsAnyTypeNote)
|
|
DiagText = "%0 adjacent parameters of %1 of similar type are easily "
|
|
"swapped by mistake";
|
|
else
|
|
DiagText = "%0 adjacent parameters of %1 of similar type ('%2') are "
|
|
"easily swapped by mistake";
|
|
|
|
auto Diag = diag(First->getOuterLocStart(), DiagText)
|
|
<< static_cast<unsigned>(R.NumParamsChecked) << FD;
|
|
if (!NeedsAnyTypeNote)
|
|
Diag << FirstParamTypeAsWritten;
|
|
|
|
CharSourceRange HighlightRange = CharSourceRange::getTokenRange(
|
|
First->getBeginLoc(), Last->getEndLoc());
|
|
Diag << HighlightRange;
|
|
}
|
|
|
|
// There is a chance that the previous highlight did not succeed, e.g. when
|
|
// the two parameters are on different lines. For clarity, show the user
|
|
// the involved variable explicitly.
|
|
diag(First->getLocation(), "the first parameter in the range is '%0'",
|
|
DiagnosticIDs::Note)
|
|
<< getNameOrUnnamed(First)
|
|
<< CharSourceRange::getTokenRange(First->getLocation(),
|
|
First->getLocation());
|
|
diag(Last->getLocation(), "the last parameter in the range is '%0'",
|
|
DiagnosticIDs::Note)
|
|
<< getNameOrUnnamed(Last)
|
|
<< CharSourceRange::getTokenRange(Last->getLocation(),
|
|
Last->getLocation());
|
|
|
|
// Helper classes to silence elaborative diagnostic notes that would be
|
|
// too verbose.
|
|
UniqueTypeAliasDiagnosticHelper UniqueTypeAlias;
|
|
InsertOnce<SwappedEqualQualTypePair, 8> UniqueBindPower;
|
|
InsertOnce<SwappedEqualQualTypePair, 8> UniqueImplicitConversion;
|
|
|
|
for (const model::Mix &M : R.Mixes) {
|
|
assert(M.mixable() && "Sentinel or false mix in result.");
|
|
if (!needsToPrintTypeInDiagnostic(M) &&
|
|
!needsToElaborateImplicitConversion(M))
|
|
continue;
|
|
|
|
// Typedefs might result in the type of the variable needing to be
|
|
// emitted to a note diagnostic, so prepare it.
|
|
const ParmVarDecl *LVar = M.First;
|
|
const ParmVarDecl *RVar = M.Second;
|
|
QualType LType = LVar->getType();
|
|
QualType RType = RVar->getType();
|
|
QualType CommonType = M.commonUnderlyingType();
|
|
std::string LTypeStr = LType.getAsString(PP);
|
|
std::string RTypeStr = RType.getAsString(PP);
|
|
std::string CommonTypeStr = CommonType.getAsString(PP);
|
|
|
|
if (hasFlag(M.flags(), MixFlags::TypeAlias) &&
|
|
UniqueTypeAlias(LType, RType, CommonType)) {
|
|
StringRef DiagText;
|
|
bool ExplicitlyPrintCommonType = false;
|
|
if (LTypeStr == CommonTypeStr || RTypeStr == CommonTypeStr) {
|
|
if (hasFlag(M.flags(), MixFlags::Qualifiers))
|
|
DiagText = "after resolving type aliases, '%0' and '%1' share a "
|
|
"common type";
|
|
else
|
|
DiagText =
|
|
"after resolving type aliases, '%0' and '%1' are the same";
|
|
} else if (!CommonType.isNull()) {
|
|
DiagText = "after resolving type aliases, the common type of '%0' "
|
|
"and '%1' is '%2'";
|
|
ExplicitlyPrintCommonType = true;
|
|
}
|
|
|
|
auto Diag =
|
|
diag(LVar->getOuterLocStart(), DiagText, DiagnosticIDs::Note)
|
|
<< LTypeStr << RTypeStr;
|
|
if (ExplicitlyPrintCommonType)
|
|
Diag << CommonTypeStr;
|
|
}
|
|
|
|
if ((hasFlag(M.flags(), MixFlags::ReferenceBind) ||
|
|
hasFlag(M.flags(), MixFlags::Qualifiers)) &&
|
|
UniqueBindPower({LType, RType})) {
|
|
StringRef DiagText = "'%0' and '%1' parameters accept and bind the "
|
|
"same kind of values";
|
|
diag(RVar->getOuterLocStart(), DiagText, DiagnosticIDs::Note)
|
|
<< LTypeStr << RTypeStr;
|
|
}
|
|
|
|
if (needsToElaborateImplicitConversion(M) &&
|
|
UniqueImplicitConversion({LType, RType})) {
|
|
const model::ConversionSequence <R =
|
|
M.leftToRightConversionSequence();
|
|
const model::ConversionSequence &RTL =
|
|
M.rightToLeftConversionSequence();
|
|
FormattedConversionSequence LTRFmt{PP, LTypeStr, LTR, RTypeStr};
|
|
FormattedConversionSequence RTLFmt{PP, RTypeStr, RTL, LTypeStr};
|
|
|
|
StringRef DiagText = "'%0' and '%1' may be implicitly converted";
|
|
if (!LTRFmt.Trivial || !RTLFmt.Trivial)
|
|
DiagText = "'%0' and '%1' may be implicitly converted: %2, %3";
|
|
|
|
{
|
|
auto Diag =
|
|
diag(RVar->getOuterLocStart(), DiagText, DiagnosticIDs::Note)
|
|
<< LTypeStr << RTypeStr;
|
|
|
|
if (!LTRFmt.Trivial || !RTLFmt.Trivial)
|
|
Diag << LTRFmt.DiagnosticText << RTLFmt.DiagnosticText;
|
|
}
|
|
|
|
StringRef ConversionFunctionDiagText =
|
|
"the implicit conversion involves the "
|
|
"%select{|converting constructor|conversion operator}0 "
|
|
"declared here";
|
|
if (const FunctionDecl *LFD = LTR.getUserDefinedConversionFunction())
|
|
diag(LFD->getLocation(), ConversionFunctionDiagText,
|
|
DiagnosticIDs::Note)
|
|
<< static_cast<unsigned>(LTR.UDConvKind)
|
|
<< LTR.getUserDefinedConversionHighlight();
|
|
if (const FunctionDecl *RFD = RTL.getUserDefinedConversionFunction())
|
|
diag(RFD->getLocation(), ConversionFunctionDiagText,
|
|
DiagnosticIDs::Note)
|
|
<< static_cast<unsigned>(RTL.UDConvKind)
|
|
<< RTL.getUserDefinedConversionHighlight();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
} // namespace bugprone
|
|
} // namespace tidy
|
|
} // namespace clang
|