[clang-tools-extra] e33d047 - [clang-tidy] Extend 'bugprone-easily-swappable-parameters' with mixability because of implicit conversions
via cfe-commits
cfe-commits at lists.llvm.org
Mon Jun 28 01:50:45 PDT 2021
Author: Whisperity
Date: 2021-06-28T10:49:37+02:00
New Revision: e33d0478831e4a295cb136ce1f58587155309fa2
URL: https://github.com/llvm/llvm-project/commit/e33d0478831e4a295cb136ce1f58587155309fa2
DIFF: https://github.com/llvm/llvm-project/commit/e33d0478831e4a295cb136ce1f58587155309fa2.diff
LOG: [clang-tidy] Extend 'bugprone-easily-swappable-parameters' with mixability because of implicit conversions
Adds a relaxation option ModelImplicitConversions which will make the check
report for cases where parameters refer to types that are implicitly
convertible to one another.
Example:
struct IntBox { IntBox(int); operator int(); };
void foo(int i, double d, IntBox ib) {}
Implicit conversions are the last to model in the set of things that are
reasons for the possibility of a function being called the wrong way which is
not always immediately apparent when looking at the function (signature or
call).
Reviewed By: aaron.ballman, martong
Differential Revision: http://reviews.llvm.org/D75041
Added:
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicit-qualifiers.cpp
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicits.c
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicits.cpp
Modified:
clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp
clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.h
clang-tools-extra/docs/clang-tidy/checks/bugprone-easily-swappable-parameters.rst
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-ignore.cpp
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len3.cpp
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-qualifiermixing.cpp
clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters.c
Removed:
################################################################################
diff --git a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp
index 8266ee62ada64..c4896979d2e99 100644
--- a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp
+++ b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.cpp
@@ -62,6 +62,9 @@ static const std::string DefaultIgnoredParameterTypeSuffixes =
/// 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;
+
using namespace clang::ast_matchers;
namespace clang {
@@ -80,16 +83,24 @@ namespace model {
enum class MixFlags : unsigned char {
Invalid = 0, //< Sentinel bit pattern. DO NOT USE!
- None = 1, //< Mix between the two parameters is not possible.
- Trivial = 2, //< The two mix trivially, and are the exact same type.
- Canonical = 4, //< The two mix because the types refer to the same
+ //< 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 = 8, //< The path from one type to the other involves
+ TypeAlias = 16, //< The path from one type to the other involves
// desugaring type aliases.
- ReferenceBind = 16, //< The mix involves the binding power of "const &".
- Qualifiers = 32, //< The mix involves change in the qualifiers.
+ 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 =*/Qualifiers)
+ LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue =*/ImplicitConversion)
};
LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE();
@@ -114,7 +125,7 @@ static inline std::string formatMixFlags(MixFlags F) {
if (F == MixFlags::Invalid)
return "#Inv!";
- SmallString<8> Str{"------"};
+ SmallString<8> Str{"-------"};
if (hasFlag(F, MixFlags::None))
// Shows the None bit explicitly, as it can be applied in the recursion
@@ -130,6 +141,11 @@ static inline std::string formatMixFlags(MixFlags F) {
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();
}
@@ -140,23 +156,254 @@ static inline std::string formatMixFlags(MixFlags F);
#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 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
@@ -173,11 +420,34 @@ struct MixData {
// 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 {
- return {Flags | EnableFlags, CommonType};
+ 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.
@@ -190,8 +460,14 @@ struct MixData {
MixData qualify(Qualifiers Quals) const {
SplitQualType Split = CommonType.split();
Split.Quals.addQualifiers(Quals);
+ QualType CommonType{Split.Ty, Split.Quals.getAsOpaqueValue()};
- return {Flags, QualType(Split.Ty, Split.Quals.getAsOpaqueValue())};
+ if (CreatedFromOneWayConversion) {
+ MixData M{Flags, Conversion};
+ M.CommonType = CommonType;
+ return M;
+ }
+ return {Flags, CommonType, Conversion, ConversionRTL};
}
};
@@ -206,7 +482,15 @@ struct Mix {
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.
@@ -243,10 +527,34 @@ struct MixableParameterRange {
}
};
-static MixData isLRefEquallyBindingToType(const TheCheck &Check,
- const LValueReferenceType *LRef,
- QualType Ty, const ASTContext &Ctx,
- bool IsRefRHS);
+/// Helper enum for the recursive calls in the modelling that toggle what kinds
+/// of implicit conversions are to be modelled.
+enum ImplicitConversionModellingMode : unsigned char {
+ //< No implicit conversions are modelled.
+ ICMM_None,
+
+ //< The full implicit conversion sequence is modelled.
+ ICMM_All,
+
+ //< Only model a unidirectional implicit conversion and within it only one
+ // standard conversion sequence.
+ ICMM_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<DecayedType, ElaboratedType, ParenType>(T);
+}
/// 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
@@ -257,13 +565,19 @@ static MixData isLRefEquallyBindingToType(const TheCheck &Check,
/// 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, const QualType LType,
- const QualType RType,
- const ASTContext &Ctx) {
+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';);
+ // 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 == RType) {
LLVM_DEBUG(llvm::dbgs() << "<<< calculateMixability. Trivial equality.\n");
return {MixFlags::Trivial, LType};
@@ -272,15 +586,17 @@ static MixData calculateMixability(const TheCheck &Check, const QualType 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 (isa<ParenType>(LType.getTypePtr())) {
- LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. LHS is ParenType.\n");
+ if (isUselessSugar(LType.getTypePtr())) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "--- calculateMixability. LHS is useless sugar.\n");
return calculateMixability(Check, LType.getSingleStepDesugaredType(Ctx),
- RType, Ctx);
+ RType, Ctx, ImplicitMode);
}
- if (isa<ParenType>(RType.getTypePtr())) {
- LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. RHS is ParenType.\n");
- return calculateMixability(Check, LType,
- RType.getSingleStepDesugaredType(Ctx), Ctx);
+ if (isUselessSugar(RType.getTypePtr())) {
+ LLVM_DEBUG(llvm::dbgs()
+ << "--- calculateMixability. RHS is useless sugar.\n");
+ return calculateMixability(
+ Check, LType, RType.getSingleStepDesugaredType(Ctx), Ctx, ImplicitMode);
}
// At a particular call site, what could be passed to a 'T' or 'const T' might
@@ -288,12 +604,14 @@ static MixData calculateMixability(const TheCheck &Check, const QualType LType,
// side effect on the passed expressions.
if (const auto *LRef = LType->getAs<LValueReferenceType>()) {
LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. LHS is &.\n");
- return isLRefEquallyBindingToType(Check, LRef, RType, Ctx, false) |
+ return isLRefEquallyBindingToType(Check, LRef, RType, Ctx, false,
+ ImplicitMode) |
MixFlags::ReferenceBind;
}
if (const auto *RRef = RType->getAs<LValueReferenceType>()) {
LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. RHS is &.\n");
- return isLRefEquallyBindingToType(Check, RRef, LType, Ctx, true) |
+ return isLRefEquallyBindingToType(Check, RRef, LType, Ctx, true,
+ ImplicitMode) |
MixFlags::ReferenceBind;
}
@@ -301,13 +619,14 @@ static MixData calculateMixability(const TheCheck &Check, const QualType LType,
if (LType->getAs<TypedefType>()) {
LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. LHS is typedef.\n");
return calculateMixability(Check, LType.getSingleStepDesugaredType(Ctx),
- RType, 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) |
+ RType.getSingleStepDesugaredType(Ctx), Ctx,
+ ImplicitMode) |
MixFlags::TypeAlias;
}
@@ -326,7 +645,8 @@ static MixData calculateMixability(const TheCheck &Check, const QualType LType,
}
return calculateMixability(Check, LType.getLocalUnqualifiedType(),
- RType.getLocalUnqualifiedType(), Ctx) |
+ RType.getLocalUnqualifiedType(), Ctx,
+ ImplicitMode) |
MixFlags::Qualifiers;
}
if (LType.getLocalCVRQualifiers() == RType.getLocalCVRQualifiers() &&
@@ -336,38 +656,113 @@ static MixData calculateMixability(const TheCheck &Check, const QualType LType,
// Apply the same qualifier back into the found common type if we found
// a common type between the unqualified versions.
return calculateMixability(Check, LType.getLocalUnqualifiedType(),
- RType.getLocalUnqualifiedType(), Ctx)
+ RType.getLocalUnqualifiedType(), Ctx,
+ ImplicitMode)
.qualify(LType.getLocalQualifiers());
}
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.
+ // 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");
- return calculateMixability(Check, LType->getPointeeType(),
- RType->getPointeeType(), Ctx);
+ MixData MixOfPointee =
+ calculateMixability(Check, LType->getPointeeType(),
+ RType->getPointeeType(), Ctx, ICMM_None);
+ 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 > ICMM_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 == ICMM_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.
- if (LType.getCanonicalType() == RType.getCanonicalType()) {
+ QualType LCanonical = LType.getCanonicalType();
+ if (LCanonical == RType.getCanonicalType()) {
LLVM_DEBUG(llvm::dbgs()
<< "<<< calculateMixability. Same CanonicalType.\n");
- return {MixFlags::Canonical, LType.getCanonicalType()};
+ MixToReturn = {MixFlags::Canonical, LCanonical};
}
- LLVM_DEBUG(llvm::dbgs() << "<<< calculateMixability. No match found.\n");
- return {MixFlags::None};
+ 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'.
-static MixData isLRefEquallyBindingToType(const TheCheck &Check,
- const LValueReferenceType *LRef,
- QualType Ty, const ASTContext &Ctx,
- bool IsRefRHS) {
+///
+/// \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';);
@@ -414,8 +809,464 @@ static MixData isLRefEquallyBindingToType(const TheCheck &Check,
LLVM_DEBUG(
llvm::dbgs()
<< "--- isLRefEquallyBindingToType. Checking mix for underlying type.\n");
- return IsRefRHS ? calculateMixability(Check, Ty, NonConstReferredType, Ctx)
- : calculateMixability(Check, NonConstReferredType, Ty, Ctx);
+ 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->Ptr decay.
+ if (const auto *ArrayT = dyn_cast<ArrayType>(From)) {
+ LLVM_DEBUG(llvm::dbgs() << "--- approximateStdConv. Array->Ptr decayed.\n");
+ WorkType = ArrayT->getPointeeType();
+ }
+
+ // 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, ICMM_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 wiht only a single implicit
+ // conversion, to see if the conversion function is applicable.
+ MixData Mix =
+ calculateMixability(Check, FromType, ToType, ConvFun->getASTContext(),
+ ICMM_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: " << ImplicitMode << '\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 == ICMM_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, ICMM_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,
@@ -447,16 +1298,18 @@ static MixableParameterRange modelMixingRange(const TheCheck &Check,
<< "Check mix of #" << J << " against #" << I << "...\n");
Mix M{Jth, Ith,
- calculateMixability(Check, Jth->getType(), Ith->getType(), Ctx)};
+ calculateMixability(Check, Jth->getType(), Ith->getType(), Ctx,
+ Check.ModelImplicitConversions ? ICMM_All
+ : ICMM_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.flags() != MixFlags::Invalid && "All flags decayed!");
+ assert(M.flagsValid() && "All flags decayed!");
- if (M.flags() != MixFlags::None)
+ if (M.mixable())
MixesOfIth.emplace_back(std::move(M));
}
@@ -595,8 +1448,80 @@ static inline bool needsToPrintTypeInDiagnostic(const model::Mix &M) {
(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 {
@@ -677,7 +1602,9 @@ EasilySwappableParametersCheck::EasilySwappableParametersCheck(
IgnoredParameterTypeSuffixes(optutils::parseStringList(
Options.get("IgnoredParameterTypeSuffixes",
DefaultIgnoredParameterTypeSuffixes))),
- QualifiersMix(Options.get("QualifiersMix", DefaultQualifiersMix)) {}
+ QualifiersMix(Options.get("QualifiersMix", DefaultQualifiersMix)),
+ ModelImplicitConversions(Options.get("ModelImplicitConversions",
+ DefaultModelImplicitConversions)) {}
void EasilySwappableParametersCheck::storeOptions(
ClangTidyOptions::OptionMap &Opts) {
@@ -687,6 +1614,7 @@ void EasilySwappableParametersCheck::storeOptions(
Options.store(Opts, "IgnoredParameterTypeSuffixes",
optutils::serializeStringList(IgnoredParameterTypeSuffixes));
Options.store(Opts, "QualifiersMix", QualifiersMix);
+ Options.store(Opts, "ModelImplicitConversions", ModelImplicitConversions);
}
void EasilySwappableParametersCheck::registerMatchers(MatchFinder *Finder) {
@@ -740,12 +1668,17 @@ void EasilySwappableParametersCheck::check(
}
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 (NeedsAnyTypeNote)
+ 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
@@ -780,55 +1713,94 @@ void EasilySwappableParametersCheck::check(
// 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};
- for (const Mix &M : R.Mixes) {
- assert(M.flags() >= MixFlags::Trivial &&
- "Sentinel or false mix in result.");
-
- if (needsToPrintTypeInDiagnostic(M)) {
- // 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 {
- DiagText = "after resolving type aliases, the common type of '%0' "
- "and '%1' is '%2'";
- ExplicitlyPrintCommonType = true;
- }
+ 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(LVar->getOuterLocStart(), DiagText, DiagnosticIDs::Note)
+ diag(RVar->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 (!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();
}
}
}
diff --git a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.h b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.h
index e3c58eb5d7013..b072c46680452 100644
--- a/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.h
+++ b/clang-tools-extra/clang-tidy/bugprone/EasilySwappableParametersCheck.h
@@ -39,8 +39,14 @@ class EasilySwappableParametersCheck : public ClangTidyCheck {
/// ignored.
const std::vector<std::string> IgnoredParameterTypeSuffixes;
- /// Whether to consider an unqualified and a qualified type mixable.
+ /// Whether to consider
diff erently qualified versions of the same type
+ /// mixable.
const bool QualifiersMix;
+
+ /// Whether to model implicit conversions "in full" (conditions apply)
+ /// during analysis and consider types that are implicitly convertible to
+ /// one another mixable.
+ const bool ModelImplicitConversions;
};
} // namespace bugprone
diff --git a/clang-tools-extra/docs/clang-tidy/checks/bugprone-easily-swappable-parameters.rst b/clang-tools-extra/docs/clang-tidy/checks/bugprone-easily-swappable-parameters.rst
index b9dafd3b32602..5158394e4e1fe 100644
--- a/clang-tools-extra/docs/clang-tidy/checks/bugprone-easily-swappable-parameters.rst
+++ b/clang-tools-extra/docs/clang-tidy/checks/bugprone-easily-swappable-parameters.rst
@@ -57,8 +57,40 @@ report longer mixable ranges.
.. code-block:: c++
- void *memcpy(const void *Destination, void *Source, std::size_t N) {}
+ void *memcpy(const void *Destination, void *Source, std::size_t N) { /* ... */ }
+.. option:: ModelImplicitConversions
+
+ Whether to consider parameters of type ``T`` and ``U`` mixable if there
+ exists an implicit conversion from ``T`` to ``U`` and ``U`` to ``T``.
+ If `false`, the check will not consider implicitly convertible types for
+ mixability.
+ `True` turns warnings for implicit conversions on.
+ Defaults to `true`.
+
+ The following examples produce a diagnostic only if
+ `ModelImplicitConversions` is enabled:
+
+ .. code-block:: c++
+
+ void fun(int Int, double Double) { /* ... */ }
+ void compare(const char *CharBuf, std::string String) { /* ... */ }
+
+ .. note::
+
+ Changing the qualifiers of an expression's type (e.g. from ``int`` to
+ ``const int``) is defined as an *implicit conversion* in the C++
+ Standard.
+ However, the check separates this decision-making on the mixability of
+
diff erently qualified types based on whether `QualifiersMix` was
+ enabled.
+
+ For example, the following code snippet will only produce a diagnostic
+ if **both** `QualifiersMix` and `ModelImplicitConversions` are enabled:
+
+ .. code-block:: c++
+
+ void fun2(int Int, const double Double) { /* ... */ }
Filtering options
^^^^^^^^^^^^^^^^^
@@ -133,7 +165,7 @@ None of the following cases produce a diagnostic:
template <typename T, typename U>
int add(T X, U Y) { return X + Y };
- void TheseAreNotWarnedAbout() {
+ void theseAreNotWarnedAbout() {
printf("%d %d\n", 1, 2); // Two ints passed, they could be swapped.
someOldCFunction(1, 2, 3); // Similarly, multiple ints passed.
@@ -153,14 +185,43 @@ not diagnosed.
// Diagnosed: Explicit instantiation was done by the user, we can prove it
// is the same type.
- void Explicit(int A, Vector<int>::element_type B) { /* ... */ }
+ void instantiated(int A, Vector<int>::element_type B) { /* ... */ }
// Diagnosed: The two parameter types are exactly the same.
template <typename T>
- void Exact(typename Vector<T>::element_type A,
+ void exact(typename Vector<T>::element_type A,
typename Vector<T>::element_type B) { /* ... */ }
// Skipped: The two parameters are both 'T' but we can not prove this
// without actually instantiating.
template <typename T>
- void FalseNegative(T A, typename Vector<T>::element_type B) { /* ... */ }
+ void falseNegative(T A, typename Vector<T>::element_type B) { /* ... */ }
+
+In the context of *implicit conversions* (when `ModelImplicitConversions` is
+enabled), the modelling performed by the check
+warns if the parameters are swappable and the swapped order matches implicit
+conversions.
+It does not model whether there exists an unrelated third type from which
+*both* parameters can be given in a function call.
+This means that in the following example, even while ``strs()`` clearly carries
+the possibility to be called with swapped arguments (as long as the arguments
+are string literals), will not be warned about.
+
+.. code-block:: c++
+
+ struct String {
+ String(const char *Buf);
+ };
+
+ struct StringView {
+ StringView(const char *Buf);
+ operator const char *() const;
+ };
+
+ // Skipped: Directly swapping expressions of the two type cannot mix.
+ // (Note: StringView -> const char * -> String would be **two**
+ // user-defined conversions, which is disallowed by the language.)
+ void strs(String Str, StringView SV) { /* ... */ }
+
+ // Diagnosed: StringView implicitly converts to and from a buffer.
+ void cStr(StringView SV, const char *Buf() { /* ... */ }
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-ignore.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-ignore.cpp
index a61c666ac9682..528363bef815f 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-ignore.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-ignore.cpp
@@ -3,7 +3,8 @@
// RUN: {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: "\"\";Foo;Bar"}, \
// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: "T"}, \
-// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 0} \
+// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 0}, \
+// RUN: {key: bugprone-easily-swappable-parameters.ModelImplicitConversions, value: 0} \
// RUN: ]}' --
void ignoredUnnamed(int I, int, int) {} // NO-WARN: No >= 2 length of non-unnamed.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicit-qualifiers.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicit-qualifiers.cpp
new file mode 100644
index 0000000000000..42dbe9101126f
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicit-qualifiers.cpp
@@ -0,0 +1,15 @@
+// RUN: %check_clang_tidy %s bugprone-easily-swappable-parameters %t \
+// RUN: -config='{CheckOptions: [ \
+// RUN: {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
+// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
+// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""}, \
+// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 1}, \
+// RUN: {key: bugprone-easily-swappable-parameters.ModelImplicitConversions, value: 1} \
+// RUN: ]}' --
+
+void numericAndQualifierConversion(int I, const double CD) { numericAndQualifierConversion(CD, I); }
+// CHECK-MESSAGES: :[[@LINE-1]]:36: warning: 2 adjacent parameters of 'numericAndQualifierConversion' of convertible types are easily swapped by mistake [bugprone-easily-swappable-parameters]
+// CHECK-MESSAGES: :[[@LINE-2]]:40: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:56: note: the last parameter in the range is 'CD'
+// CHECK-MESSAGES: :[[@LINE-4]]:43: note: 'int' and 'const double' parameters accept and bind the same kind of values
+// CHECK-MESSAGES: :[[@LINE-5]]:43: note: 'int' and 'const double' may be implicitly converted: 'int' -> 'const double' (as 'double'), 'const double' (as 'double') -> 'int'
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicits.c b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicits.c
new file mode 100644
index 0000000000000..48a3e10d3e316
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicits.c
@@ -0,0 +1,75 @@
+// RUN: %check_clang_tidy %s bugprone-easily-swappable-parameters %t \
+// RUN: -config='{CheckOptions: [ \
+// RUN: {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
+// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
+// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""}, \
+// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 0}, \
+// RUN: {key: bugprone-easily-swappable-parameters.ModelImplicitConversions, value: 1} \
+// RUN: ]}' --
+
+void implicitDoesntBreakOtherStuff(int A, int B) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:36: warning: 2 adjacent parameters of 'implicitDoesntBreakOtherStuff' of similar type ('int') are easily swapped by mistake [bugprone-easily-swappable-parameters]
+// CHECK-MESSAGES: :[[@LINE-2]]:40: note: the first parameter in the range is 'A'
+// CHECK-MESSAGES: :[[@LINE-3]]:47: note: the last parameter in the range is 'B'
+
+void arrayAndPtr1(int *IP, int IA[]) { arrayAndPtr1(IA, IP); }
+// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters of 'arrayAndPtr1' of similar type ('int *')
+// CHECK-MESSAGES: :[[@LINE-2]]:24: note: the first parameter in the range is 'IP'
+// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in the range is 'IA'
+
+void arrayAndPtr2(int *IP, int IA[8]) { arrayAndPtr2(IA, IP); }
+// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters of 'arrayAndPtr2' of similar type ('int *')
+// CHECK-MESSAGES: :[[@LINE-2]]:24: note: the first parameter in the range is 'IP'
+// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in the range is 'IA'
+
+void arrayAndElement(int I, int IA[]) {} // NO-WARN.
+
+void numericConversion1(int I, double D) { numericConversion1(D, I); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'numericConversion1' of convertible types are easily swapped by mistake [bugprone-easily-swappable-parameters]
+// CHECK-MESSAGES: :[[@LINE-2]]:29: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:39: note: the last parameter in the range is 'D'
+// CHECK-MESSAGES: :[[@LINE-4]]:32: note: 'int' and 'double' may be implicitly converted{{$}}
+
+void numericConversion2(int I, short S) { numericConversion2(S, I); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'numericConversion2' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:29: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:38: note: the last parameter in the range is 'S'
+// CHECK-MESSAGES: :[[@LINE-4]]:32: note: 'int' and 'short' may be implicitly converted{{$}}
+
+void numericConversion3(float F, unsigned long UL) { numericConversion3(UL, F); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'numericConversion3' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:31: note: the first parameter in the range is 'F'
+// CHECK-MESSAGES: :[[@LINE-3]]:48: note: the last parameter in the range is 'UL'
+// CHECK-MESSAGES: :[[@LINE-4]]:34: note: 'float' and 'unsigned long' may be implicitly converted{{$}}
+
+enum Unscoped { U_A,
+ U_B };
+enum UnscopedFixed : char { UF_A,
+ UF_B };
+
+void numericConversion4(int I, enum Unscoped U) { numericConversion4(U, I); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'numericConversion4' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:29: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:46: note: the last parameter in the range is 'U'
+// CHECK-MESSAGES: :[[@LINE-4]]:32: note: 'int' and 'enum Unscoped' may be implicitly converted{{$}}
+
+void numericConversion5(int I, enum UnscopedFixed UF) { numericConversion5(UF, I); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'numericConversion5' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:29: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:51: note: the last parameter in the range is 'UF'
+// CHECK-MESSAGES: :[[@LINE-4]]:32: note: 'int' and 'enum UnscopedFixed' may be implicitly converted{{$}}
+
+void numericConversion7(double D, enum Unscoped U) { numericConversion7(U, D); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'numericConversion7' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:32: note: the first parameter in the range is 'D'
+// CHECK-MESSAGES: :[[@LINE-3]]:49: note: the last parameter in the range is 'U'
+// CHECK-MESSAGES: :[[@LINE-4]]:35: note: 'double' and 'enum Unscoped' may be implicitly converted{{$}}
+
+void numericConversion8(double D, enum UnscopedFixed UF) { numericConversion8(UF, D); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'numericConversion8' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:32: note: the first parameter in the range is 'D'
+// CHECK-MESSAGES: :[[@LINE-3]]:54: note: the last parameter in the range is 'UF'
+// CHECK-MESSAGES: :[[@LINE-4]]:35: note: 'double' and 'enum UnscopedFixed' may be implicitly converted{{$}}
+
+void pointeeConverison(int *IP, double *DP) { pointeeConversion(DP, IP); }
+// NO-WARN: Even though this is possible in C, a swap is diagnosed by the compiler.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicits.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicits.cpp
new file mode 100644
index 0000000000000..7205d87a41e78
--- /dev/null
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-implicits.cpp
@@ -0,0 +1,303 @@
+// RUN: %check_clang_tidy -std=c++17 %s bugprone-easily-swappable-parameters %t \
+// RUN: -config='{CheckOptions: [ \
+// RUN: {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
+// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
+// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""}, \
+// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 0}, \
+// RUN: {key: bugprone-easily-swappable-parameters.ModelImplicitConversions, value: 1} \
+// RUN: ]}' --
+
+void implicitDoesntBreakOtherStuff(int A, int B) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:36: warning: 2 adjacent parameters of 'implicitDoesntBreakOtherStuff' of similar type ('int') are easily swapped by mistake [bugprone-easily-swappable-parameters]
+// CHECK-MESSAGES: :[[@LINE-2]]:40: note: the first parameter in the range is 'A'
+// CHECK-MESSAGES: :[[@LINE-3]]:47: note: the last parameter in the range is 'B'
+
+void arrayAndPtr1(int *IP, int IA[]) { arrayAndPtr1(IA, IP); }
+// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters of 'arrayAndPtr1' of similar type ('int *')
+// CHECK-MESSAGES: :[[@LINE-2]]:24: note: the first parameter in the range is 'IP'
+// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in the range is 'IA'
+
+void arrayAndPtr2(int *IP, int IA[8]) { arrayAndPtr2(IA, IP); }
+// CHECK-MESSAGES: :[[@LINE-1]]:19: warning: 2 adjacent parameters of 'arrayAndPtr2' of similar type ('int *')
+// CHECK-MESSAGES: :[[@LINE-2]]:24: note: the first parameter in the range is 'IP'
+// CHECK-MESSAGES: :[[@LINE-3]]:32: note: the last parameter in the range is 'IA'
+
+void arrayAndElement(int I, int IA[]) {} // NO-WARN.
+
+void numericConversion1(int I, double D) { numericConversion1(D, I); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'numericConversion1' of convertible types are easily swapped by mistake [bugprone-easily-swappable-parameters]
+// CHECK-MESSAGES: :[[@LINE-2]]:29: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:39: note: the last parameter in the range is 'D'
+// CHECK-MESSAGES: :[[@LINE-4]]:32: note: 'int' and 'double' may be implicitly converted{{$}}
+
+void numericConversion2(int I, short S) { numericConversion2(S, I); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'numericConversion2' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:29: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:38: note: the last parameter in the range is 'S'
+// CHECK-MESSAGES: :[[@LINE-4]]:32: note: 'int' and 'short' may be implicitly converted{{$}}
+
+void numericConversion3(float F, unsigned long long ULL) { numericConversion3(ULL, F); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'numericConversion3' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:31: note: the first parameter in the range is 'F'
+// CHECK-MESSAGES: :[[@LINE-3]]:53: note: the last parameter in the range is 'ULL'
+// CHECK-MESSAGES: :[[@LINE-4]]:34: note: 'float' and 'unsigned long long' may be implicitly converted{{$}}
+
+enum Unscoped { U_A,
+ U_B };
+enum UnscopedFixed : char { UF_A,
+ UF_B };
+enum struct Scoped { A,
+ B };
+
+void numericConversion4(int I, Unscoped U) {} // NO-WARN.
+
+void numericConversion5(int I, UnscopedFixed UF) {} // NO-WARN.
+
+void numericConversion6(int I, Scoped S) {} // NO-WARN.
+
+void numericConversion7(double D, Unscoped U) {} // NO-WARN.
+
+void numericConversion8(double D, UnscopedFixed UF) {} // NO-WARN.
+
+void numericConversion9(double D, Scoped S) {} // NO-WARN.
+
+void numericConversionMultiUnique(int I, double D1, double D2) {}
+// CHECK-MESSAGES: :[[@LINE-1]]:35: warning: 3 adjacent parameters of 'numericConversionMultiUnique' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:39: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:60: note: the last parameter in the range is 'D2'
+// CHECK-MESSAGES: :[[@LINE-4]]:42: note: 'int' and 'double' may be implicitly converted{{$}}
+// (Note: int<->double conversion for I<->D2 not diagnosed again.)
+
+typedef int MyInt;
+using MyDouble = double;
+
+void numericConversion10(MyInt MI, MyDouble MD) { numericConversion10(MD, MI); }
+// CHECK-MESSAGES: :[[@LINE-1]]:26: warning: 2 adjacent parameters of 'numericConversion10' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:32: note: the first parameter in the range is 'MI'
+// CHECK-MESSAGES: :[[@LINE-3]]:45: note: the last parameter in the range is 'MD'
+// CHECK-MESSAGES: :[[@LINE-4]]:36: note: 'MyInt' and 'MyDouble' may be implicitly converted: 'MyInt' (as 'int') -> 'MyDouble' (as 'double'), 'MyDouble' (as 'double') -> 'MyInt' (as 'int')
+
+void numericAndQualifierConversion(int I, const double CD) { numericAndQualifierConversion(CD, I); }
+// NO-WARN: Qualifier mixing is handled by a
diff erent check option.
+
+struct FromInt {
+ FromInt(int);
+};
+
+void oneWayConversion1(int I, FromInt FI) {} // NO-WARN: One-way.
+
+struct AmbiguousConvCtor {
+ AmbiguousConvCtor(int);
+ AmbiguousConvCtor(double);
+};
+
+void ambiguous1(long L, AmbiguousConvCtor ACC) {} // NO-WARN: Ambiguous, one-way.
+
+struct ToInt {
+ operator int() const;
+};
+
+void oneWayConversion2(ToInt TI, int I) {} // NO-WARN: One-way.
+
+struct AmbiguousConvOp {
+ operator int() const;
+ operator double() const;
+};
+
+void ambiguous2(AmbiguousConvOp ACO, long L) {} // NO-WARN: Ambiguous, one-way.
+
+struct AmbiguousEverything1;
+struct AmbiguousEverything2;
+struct AmbiguousEverything1 {
+ AmbiguousEverything1();
+ AmbiguousEverything1(AmbiguousEverything2);
+ operator AmbiguousEverything2() const;
+};
+struct AmbiguousEverything2 {
+ AmbiguousEverything2();
+ AmbiguousEverything2(AmbiguousEverything1);
+ operator AmbiguousEverything1() const;
+};
+
+void ambiguous3(AmbiguousEverything1 AE1, AmbiguousEverything2 AE2) {} // NO-WARN: Ambiguous.
+
+struct Integer {
+ Integer(int);
+ operator int() const;
+};
+
+void userDefinedConversion1(int I1, Integer I2) { userDefinedConversion1(I2, I1); }
+// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters of 'userDefinedConversion1' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:33: note: the first parameter in the range is 'I1'
+// CHECK-MESSAGES: :[[@LINE-3]]:45: note: the last parameter in the range is 'I2'
+// CHECK-MESSAGES: :[[@LINE-4]]:37: note: 'int' and 'Integer' may be implicitly converted{{$}}
+// CHECK-MESSAGES: :[[@LINE-9]]:3: note: the implicit conversion involves the converting constructor declared here
+// CHECK-MESSAGES: :[[@LINE-9]]:3: note: the implicit conversion involves the conversion operator declared here
+
+struct Ambiguous {
+ Ambiguous(int);
+ Ambiguous(double);
+ operator long() const;
+ operator float() const;
+};
+
+void ambiguous3(char C, Ambiguous A) {} // NO-WARN: Ambiguous.
+
+struct CDouble {
+ CDouble(const double &);
+ operator const double &() const;
+};
+
+void userDefinedConversion2(double D, CDouble CD) { userDefinedConversion2(CD, D); }
+// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters of 'userDefinedConversion2' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:36: note: the first parameter in the range is 'D'
+// CHECK-MESSAGES: :[[@LINE-3]]:47: note: the last parameter in the range is 'CD'
+// CHECK-MESSAGES: :[[@LINE-4]]:39: note: 'double' and 'CDouble' may be implicitly converted: 'double' -> 'const double &' -> 'CDouble', 'CDouble' -> 'const double &' -> 'double'
+// CHECK-MESSAGES: :[[@LINE-9]]:3: note: the implicit conversion involves the converting constructor declared here
+// CHECK-MESSAGES: :[[@LINE-9]]:3: note: the implicit conversion involves the conversion operator declared here
+
+void userDefinedConversion3(int I, CDouble CD) { userDefinedConversion3(CD, I); }
+// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters of 'userDefinedConversion3' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:33: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:44: note: the last parameter in the range is 'CD'
+// CHECK-MESSAGES: :[[@LINE-4]]:36: note: 'int' and 'CDouble' may be implicitly converted: 'int' -> 'double' -> 'const double &' -> 'CDouble', 'CDouble' -> 'const double &' -> 'int'
+// CHECK-MESSAGES: :[[@LINE-17]]:3: note: the implicit conversion involves the converting constructor declared here
+// CHECK-MESSAGES: :[[@LINE-17]]:3: note: the implicit conversion involves the conversion operator declared here
+
+struct TDInt {
+ TDInt(const MyInt &);
+ operator MyInt() const;
+};
+
+void userDefinedConversion4(int I, TDInt TDI) { userDefinedConversion4(TDI, I); }
+// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters of 'userDefinedConversion4' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:33: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:42: note: the last parameter in the range is 'TDI'
+// CHECK-MESSAGES: :[[@LINE-4]]:36: note: 'int' and 'TDInt' may be implicitly converted: 'int' -> 'const MyInt &' -> 'TDInt', 'TDInt' -> 'MyInt' -> 'int'
+// CHECK-MESSAGES: :[[@LINE-9]]:3: note: the implicit conversion involves the converting constructor declared here
+// CHECK-MESSAGES: :[[@LINE-9]]:3: note: the implicit conversion involves the conversion operator declared here
+
+struct TDIntDouble {
+ TDIntDouble(const MyInt &);
+ TDIntDouble(const MyDouble &);
+ operator MyInt() const;
+ operator MyDouble() const;
+};
+
+void userDefinedConversion5(int I, TDIntDouble TDID) { userDefinedConversion5(TDID, I); }
+// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters of 'userDefinedConversion5' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:33: note: the first parameter in the range is 'I'
+// CHECK-MESSAGES: :[[@LINE-3]]:48: note: the last parameter in the range is 'TDID'
+// CHECK-MESSAGES: :[[@LINE-4]]:36: note: 'int' and 'TDIntDouble' may be implicitly converted: 'int' -> 'const MyInt &' -> 'TDIntDouble', 'TDIntDouble' -> 'MyInt' -> 'int'
+// CHECK-MESSAGES: :[[@LINE-11]]:3: note: the implicit conversion involves the converting constructor declared here
+// CHECK-MESSAGES: :[[@LINE-10]]:3: note: the implicit conversion involves the conversion operator declared here
+
+void userDefinedConversion6(double D, TDIntDouble TDID) { userDefinedConversion6(TDID, D); }
+// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent parameters of 'userDefinedConversion6' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:36: note: the first parameter in the range is 'D'
+// CHECK-MESSAGES: :[[@LINE-3]]:51: note: the last parameter in the range is 'TDID'
+// CHECK-MESSAGES: :[[@LINE-4]]:39: note: 'double' and 'TDIntDouble' may be implicitly converted: 'double' -> 'const MyDouble &' -> 'TDIntDouble', 'TDIntDouble' -> 'MyDouble' -> 'double'
+// CHECK-MESSAGES: :[[@LINE-18]]:3: note: the implicit conversion involves the converting constructor declared here
+// CHECK-MESSAGES: :[[@LINE-17]]:3: note: the implicit conversion involves the conversion operator declared here
+
+void userDefinedConversion7(char C, TDIntDouble TDID) {} // NO-WARN: Ambiguous.
+
+struct Forward1;
+struct Forward2;
+
+void incomplete(Forward1 *F1, Forward2 *F2) {} // NO-WARN: Do not compare incomplete types.
+
+void pointeeConverison(int *IP, double *DP) {} // NO-WARN.
+
+void pointerConversion1(void *VP, int *IP) {} // NO-WARN: One-way.
+
+struct PointerBox {
+ PointerBox(void *);
+ operator int *() const;
+};
+
+void pointerConversion2(PointerBox PB, int *IP) { pointerConversion2(IP, PB); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'pointerConversion2' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:36: note: the first parameter in the range is 'PB'
+// CHECK-MESSAGES: :[[@LINE-3]]:45: note: the last parameter in the range is 'IP'
+// CHECK-MESSAGES: :[[@LINE-4]]:40: note: 'PointerBox' and 'int *' may be implicitly converted: 'PointerBox' -> 'int *', 'int *' -> 'void *' -> 'PointerBox'
+// CHECK-MESSAGES: :[[@LINE-8]]:3: note: the implicit conversion involves the conversion operator declared here
+// CHECK-MESSAGES: :[[@LINE-10]]:3: note: the implicit conversion involves the converting constructor declared here
+
+void pointerConversion3(PointerBox PB, double *DP) {} // NO-WARN: Not convertible.
+
+struct Base {};
+struct Derived : Base {};
+
+void pointerConversion4(Base *BP, Derived *DP) {} // NO-WARN: One-way.
+
+struct BaseAndDerivedInverter {
+ BaseAndDerivedInverter(Base); // Takes a Base
+ operator Derived() const; // and becomes a Derived.
+};
+
+void pointerConversion5(BaseAndDerivedInverter BADI, Derived D) { pointerConversion5(D, BADI); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'pointerConversion5' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:48: note: the first parameter in the range is 'BADI'
+// CHECK-MESSAGES: :[[@LINE-3]]:62: note: the last parameter in the range is 'D'
+// CHECK-MESSAGES: :[[@LINE-4]]:54: note: 'BaseAndDerivedInverter' and 'Derived' may be implicitly converted: 'BaseAndDerivedInverter' -> 'Derived', 'Derived' -> 'Base' -> 'BaseAndDerivedInverter'
+// CHECK-MESSAGES: :[[@LINE-8]]:3: note: the implicit conversion involves the conversion operator declared here
+// CHECK-MESSAGES: :[[@LINE-10]]:3: note: the implicit conversion involves the converting constructor declared here
+
+void pointerConversion6(void (*NTF)() noexcept, void (*TF)()) {}
+// NO-WARN: This call cannot be swapped, even if "getCanonicalType()" believes otherwise.
+
+using NonThrowingFunction = void (*)() noexcept;
+
+struct NoexceptMaker {
+ NoexceptMaker(void (*ThrowingFunction)());
+ // Need to use a typedef here because
+ // "conversion function cannot convert to a function type".
+ // operator (void (*)() noexcept) () const;
+ operator NonThrowingFunction() const;
+};
+
+void pointerConversion7(void (*NTF)() noexcept, NoexceptMaker NM) { pointerConversion7(NM, NTF); }
+// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 2 adjacent parameters of 'pointerConversion7' of convertible types
+// CHECK-MESSAGES: :[[@LINE-2]]:32: note: the first parameter in the range is 'NTF'
+// CHECK-MESSAGES: :[[@LINE-3]]:63: note: the last parameter in the range is 'NM'
+// CHECK-MESSAGES: :[[@LINE-4]]:49: note: 'void (*)() noexcept' and 'NoexceptMaker' may be implicitly converted: 'void (*)() noexcept' -> 'void (*)()' -> 'NoexceptMaker', 'NoexceptMaker' -> 'NonThrowingFunction' -> 'void (*)() noexcept'
+// CHECK-MESSAGES: :[[@LINE-12]]:3: note: the implicit conversion involves the converting constructor declared here
+// CHECK-MESSAGES: :[[@LINE-9]]:3: note: the implicit conversion involves the conversion operator declared here
+
+struct ToType;
+struct MiddleStep1 {
+ operator ToType() const;
+};
+struct FromType {
+ operator MiddleStep1() const;
+};
+struct MiddleStep2 {
+ operator FromType() const;
+};
+struct ToType {
+ operator MiddleStep2() const;
+};
+
+void f(FromType F, ToType T) { // NO-WARN: The path takes two steps.
+ MiddleStep2 MS2 = T;
+ FromType F2 = MS2;
+
+ MiddleStep1 MS1 = F;
+ ToType T2 = MS1;
+
+ f(F2, T2);
+}
+
+// Synthesised example from OpenCV.
+template <typename T>
+struct TemplateConversion {
+ template <typename T2>
+ operator TemplateConversion<T2>() const;
+};
+using IntConverter = TemplateConversion<int>;
+using FloatConverter = TemplateConversion<float>;
+
+void templateConversion(IntConverter IC, FloatConverter FC) { templateConversion(FC, IC); }
+// Note: even though this swap is possible, we do not model things when it comes to "template magic".
+// But at least the check should not crash!
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp
index f0c1c57e25848..3bcdee2e49796 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len2.cpp
@@ -3,7 +3,8 @@
// RUN: {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""}, \
-// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 0} \
+// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 0}, \
+// RUN: {key: bugprone-easily-swappable-parameters.ModelImplicitConversions, value: 0} \
// RUN: ]}' --
namespace std {
@@ -340,3 +341,6 @@ void memberTypedefDependentReference3(
// CHECK-MESSAGES: :[[@LINE-3]]:38: note: the first parameter in the range is 'E'
// CHECK-MESSAGES: :[[@LINE-3]]:45: note: the last parameter in the range is 'R'
// CHECK-MESSAGES: :[[@LINE-4]]:5: note: 'typename Vector<T>::element_type' and 'const typename Vector<T>::element_type &' parameters accept and bind the same kind of values
+
+void functionPrototypeLosesNoexcept(void (*NonThrowing)() noexcept, void (*Throwing)()) {}
+// NO-WARN: This call cannot be swapped, even if "getCanonicalType()" believes otherwise.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len3.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len3.cpp
index c833077285be9..4b1f086aba948 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len3.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-len3.cpp
@@ -3,7 +3,8 @@
// RUN: {key: bugprone-easily-swappable-parameters.MinimumLength, value: 3}, \
// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""}, \
-// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 0} \
+// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 0}, \
+// RUN: {key: bugprone-easily-swappable-parameters.ModelImplicitConversions, value: 0} \
// RUN: ]}' --
int add(int Left, int Right) { return Left + Right; } // NO-WARN: Only 2 parameters.
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-qualifiermixing.cpp b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-qualifiermixing.cpp
index 7b9fdceda7465..02d41661802d2 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-qualifiermixing.cpp
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters-qualifiermixing.cpp
@@ -3,7 +3,8 @@
// RUN: {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: ""}, \
-// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 1} \
+// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 1}, \
+// RUN: {key: bugprone-easily-swappable-parameters.ModelImplicitConversions, value: 0} \
// RUN: ]}' --
typedef int MyInt1;
diff --git a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters.c b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters.c
index 591a5cb353ee8..06a3993472ae6 100644
--- a/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters.c
+++ b/clang-tools-extra/test/clang-tidy/checkers/bugprone-easily-swappable-parameters.c
@@ -3,7 +3,8 @@
// RUN: {key: bugprone-easily-swappable-parameters.MinimumLength, value: 2}, \
// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterNames, value: ""}, \
// RUN: {key: bugprone-easily-swappable-parameters.IgnoredParameterTypeSuffixes, value: "bool;MyBool;struct U;MAKE_LOGICAL_TYPE(int)"}, \
-// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 0} \
+// RUN: {key: bugprone-easily-swappable-parameters.QualifiersMix, value: 0}, \
+// RUN: {key: bugprone-easily-swappable-parameters.ModelImplicitConversions, value: 0} \
// RUN: ]}' -- -x c
#define bool _Bool
More information about the cfe-commits
mailing list