blob: 771a8780b070e464ae9d577545321eaa9884f9b8 [file] [log] [blame]
//===--- 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 const std::string DefaultIgnoredParameterNames =
optutils::serializeStringList({"\"\"", "iterator", "Iterator", "begin",
"Begin", "end", "End", "first", "First",
"last", "Last", "lhs", "LHS", "rhs", "RHS"});
/// The default value for ignored parameter type suffixes.
static const std::string DefaultIgnoredParameterTypeSuffixes =
optutils::serializeStringList({"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 &LTR =
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