//===--- 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" #include namespace optutils = clang::tidy::utils::options; /// The default value for the MinimumLength check option. static constexpr std::size_t DefaultMinimumLength = 2; /// The default value for ignored parameter names. static constexpr llvm::StringLiteral DefaultIgnoredParameterNames = "\"\";" "iterator;" "Iterator;" "begin;" "Begin;" "end;" "End;" "first;" "First;" "last;" "Last;" "lhs;" "LHS;" "rhs;" "RHS"; /// The default value for ignored parameter type suffixes. static constexpr llvm::StringLiteral DefaultIgnoredParameterTypeSuffixes = "bool;" "Bool;" "_Bool;" "it;" "It;" "iterator;" "Iterator;" "inputit;" "InputIt;" "forwardit;" "ForwardIt;" "bidirit;" "BidirIt;" "constiterator;" "const_iterator;" "Const_Iterator;" "Constiterator;" "ConstIterator;" "RandomIt;" "randomit;" "random_iterator;" "ReverseIt;" "reverse_iterator;" "reverse_const_iterator;" "ConstReverseIterator;" "Const_Reverse_Iterator;" "const_reverse_iterator;" "Constreverseiterator;" "constreverseiterator"; /// The default value for the QualifiersMix check option. static constexpr bool DefaultQualifiersMix = false; /// The default value for the ModelImplicitConversions check option. static constexpr bool DefaultModelImplicitConversions = true; /// The default value for suppressing diagnostics about parameters that are /// used together. static constexpr bool DefaultSuppressParametersUsedTogether = true; /// The default value for the NamePrefixSuffixSilenceDissimilarityTreshold /// check option. static constexpr std::size_t DefaultNamePrefixSuffixSilenceDissimilarityTreshold = 1; using namespace clang::ast_matchers; namespace clang::tidy::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() 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 getInvolvedTypesInSequence() const { SmallVector 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(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 MixData withCommonTypeTransformed(const 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_v && std::is_trivially_move_constructible_v && std::is_trivially_move_assignable_v, "Keep frequently used data simple!"); struct MixableParameterRange { /// A container for Mixes. using MixVector = SmallVector; /// 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(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(); const auto *RLRef = RType->getAs(); 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()) { LLVM_DEBUG(llvm::dbgs() << "--- calculateMixability. LHS is typedef.\n"); return calculateMixability(Check, LType.getSingleStepDesugaredType(Ctx), RType, Ctx, ImplicitMode) | MixFlags::TypeAlias; } if (RType->getAs()) { 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() || RType->getAs()) { // 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()) { 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 std::optional 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(); const auto *ToBuiltin = To->getAs(); 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(); const auto *ToEnum = To->getAs(); 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(); const auto *ToPtr = To->getAs(); 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(); const auto *ToFunctionPtr = ToPtr->getPointeeType()->getAs(); 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. std::optional 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(); } std::optional 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 FlaggedConversions; const TheCheck &Check; }; } // namespace static std::optional 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(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 (std::optional 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(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 std::optional 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 (std::optional SelectedConversion = ConversionSet()) { QualType RecordType{RD->getTypeForDecl(), 0}; ConversionSequence Result{FromType, RecordType}; Result.AfterFirstStandard = SelectedConversion->Seq.AfterFirstStandard; ConversionSequence::UserDefinedConvertingConstructor Ctor; Ctor.Fun = cast(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; std::optional AfterFirstStdConv = approximateStandardConversionSequence(Check, LType, RType, Ctx); if (AfterFirstStdConv) { LLVM_DEBUG(llvm::dbgs() << "--- approximateImplicitConversion. Standard " "Pre-Conversion found!\n"); ImplicitSeq.AfterFirstStandard = *AfterFirstStdConv; 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()) { std::optional ConversionOperatorResult = tryConversionOperators(Check, LRD, RType); if (ConversionOperatorResult) { LLVM_DEBUG(llvm::dbgs() << "--- approximateImplicitConversion. Found " "conversion operator.\n"); ImplicitSeq.update(*ConversionOperatorResult); 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. std::optional ConvCtorResult = tryConvertingConstructors(Check, LType, RRD); if (ConvCtorResult) { LLVM_DEBUG(llvm::dbgs() << "--- approximateImplicitConversion. Found " "converting constructor.\n"); ImplicitSeq.update(*ConvCtorResult); 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, 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::is_contained(Check.IgnoredParameterNames, "\"\""); StringRef NodeName = Node->getName(); if (llvm::is_contained(Check.IgnoredParameterNames, NodeName)) { 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](StringRef E) { return !E.empty() && NodeTypeName.ends_with(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 using ParamToSmallSetMap = llvm::DenseMap>; /// Returns whether the sets mapped to the two elements in the map have at /// least one element in common. template 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 (E2Iterator->second.contains(E1SetElem)) 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 { using Base = RecursiveASTVisitor; const FunctionDecl *FD; const Expr *CurrentExprOnlyTreeRoot = nullptr; llvm::DenseMap> ParentExprsForParamRefs; public: void setup(const FunctionDecl *FD) { this->FD = FD; TraverseFunctionDecl(const_cast(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(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(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> 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("param"); const auto *CE = Match.getNodeAs("call-expr"); const auto *PassedToParam = Match.getNodeAs("passed-to"); assert(PassedParamOfThisFn && CE && PassedToParam); const FunctionDecl *CalledFn = CE->getDirectCallee(); if (!CalledFn) continue; std::optional TargetIdx; unsigned NumFnParams = CalledFn->getNumParams(); for (unsigned Idx = 0; Idx < NumFnParams; ++Idx) if (CalledFn->getParamDecl(Idx) == PassedToParam) TargetIdx.emplace(Idx); assert(TargetIdx && "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 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("param"); const auto *ME = Match.getNodeAs("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 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("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::is_contained(ReturnedParams, Param1) && llvm::is_contained(ReturnedParams, Param2); } }; } // 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 &Str, std::size_t ToLen) { while (Str.size() < ToLen) Str.emplace_back('\0'); } static void padStringAtBegin(SmallVectorImpl &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 = ""; 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( 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 = true; FormattedConversionSequence(const PrintingPolicy &PP, StringRef StartTypeAsDiagnosed, const model::ConversionSequence &Conv, StringRef DestinationTypeAsDiagnosed) { 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 class InsertOnce { llvm::SmallSet 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 { using Base = InsertOnce; 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("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(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 UniqueBindPower; InsertOnce UniqueImplicitConversion; for (const model::Mix &M : R.Mixes) { assert(M.mixable() && "Sentinel or false mix in result."); if (!needsToPrintTypeInDiagnostic(M) && !needsToElaborateImplicitConversion(M)) continue; // Typedefs might result in the type of the variable needing to be // emitted to a note diagnostic, so prepare it. const ParmVarDecl *LVar = M.First; const ParmVarDecl *RVar = M.Second; QualType LType = LVar->getType(); QualType RType = RVar->getType(); QualType CommonType = M.commonUnderlyingType(); std::string LTypeStr = LType.getAsString(PP); std::string RTypeStr = RType.getAsString(PP); std::string CommonTypeStr = CommonType.getAsString(PP); if (hasFlag(M.flags(), MixFlags::TypeAlias) && UniqueTypeAlias(LType, RType, CommonType)) { StringRef DiagText; bool ExplicitlyPrintCommonType = false; if (LTypeStr == CommonTypeStr || RTypeStr == CommonTypeStr) { if (hasFlag(M.flags(), MixFlags::Qualifiers)) DiagText = "after resolving type aliases, '%0' and '%1' share a " "common type"; else DiagText = "after resolving type aliases, '%0' and '%1' are the same"; } else if (!CommonType.isNull()) { DiagText = "after resolving type aliases, the common type of '%0' " "and '%1' is '%2'"; ExplicitlyPrintCommonType = true; } auto Diag = diag(LVar->getOuterLocStart(), DiagText, DiagnosticIDs::Note) << LTypeStr << RTypeStr; if (ExplicitlyPrintCommonType) Diag << CommonTypeStr; } if ((hasFlag(M.flags(), MixFlags::ReferenceBind) || hasFlag(M.flags(), MixFlags::Qualifiers)) && UniqueBindPower({LType, RType})) { StringRef DiagText = "'%0' and '%1' parameters accept and bind the " "same kind of values"; diag(RVar->getOuterLocStart(), DiagText, DiagnosticIDs::Note) << LTypeStr << RTypeStr; } if (needsToElaborateImplicitConversion(M) && UniqueImplicitConversion({LType, RType})) { const model::ConversionSequence <R = M.leftToRightConversionSequence(); const model::ConversionSequence &RTL = M.rightToLeftConversionSequence(); FormattedConversionSequence LTRFmt{PP, LTypeStr, LTR, RTypeStr}; FormattedConversionSequence RTLFmt{PP, RTypeStr, RTL, LTypeStr}; StringRef DiagText = "'%0' and '%1' may be implicitly converted"; if (!LTRFmt.Trivial || !RTLFmt.Trivial) DiagText = "'%0' and '%1' may be implicitly converted: %2, %3"; { auto Diag = diag(RVar->getOuterLocStart(), DiagText, DiagnosticIDs::Note) << LTypeStr << RTypeStr; if (!LTRFmt.Trivial || !RTLFmt.Trivial) Diag << LTRFmt.DiagnosticText << RTLFmt.DiagnosticText; } StringRef ConversionFunctionDiagText = "the implicit conversion involves the " "%select{|converting constructor|conversion operator}0 " "declared here"; if (const FunctionDecl *LFD = LTR.getUserDefinedConversionFunction()) diag(LFD->getLocation(), ConversionFunctionDiagText, DiagnosticIDs::Note) << static_cast(LTR.UDConvKind) << LTR.getUserDefinedConversionHighlight(); if (const FunctionDecl *RFD = RTL.getUserDefinedConversionFunction()) diag(RFD->getLocation(), ConversionFunctionDiagText, DiagnosticIDs::Note) << static_cast(RTL.UDConvKind) << RTL.getUserDefinedConversionHighlight(); } } } } } // namespace clang::tidy::bugprone