//===--- 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 … #include "llvm/Support/Debug.h" #include <optional> optutils; /// The default value for the MinimumLength check option. static constexpr std::size_t DefaultMinimumLength = …; /// The default value for ignored parameter names. static constexpr llvm::StringLiteral DefaultIgnoredParameterNames = …; /// The default value for ignored parameter type suffixes. static constexpr llvm::StringLiteral DefaultIgnoredParameterTypeSuffixes = …; /// The default value for the QualifiersMix check option. static constexpr bool DefaultQualifiersMix = …; /// The default value for the ModelImplicitConversions check option. static constexpr bool DefaultModelImplicitConversions = …; /// The default value for suppressing diagnostics about parameters that are /// used together. static constexpr bool DefaultSuppressParametersUsedTogether = …; /// The default value for the NamePrefixSuffixSilenceDissimilarityTreshold /// check option. static constexpr std::size_t DefaultNamePrefixSuffixSilenceDissimilarityTreshold = …; usingnamespaceclang::ast_matchers; namespace clang::tidy::bugprone { TheCheck; 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 { … }; LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(…); /// Returns whether the SearchedFlag is turned on in the Data. static inline bool hasFlag(MixFlags Data, MixFlags 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 { … }; /// Contains the metadata for the mixability result between two types, /// independently of which parameters they were calculated from. struct MixData { … }; /// A named tuple that contains the information for a mix between two concrete /// parameters. struct Mix { … }; // NOLINTNEXTLINE(misc-redundant-expression): Seems to be a bogus warning. static_assert …; struct MixableParameterRange { … }; /// 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 { … }; 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) { … } namespace { struct NonCVRQualifiersResult { … }; } // 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) { … } /// 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) { … } /// 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) { … } static inline bool isDerivedToBase(const CXXRecordDecl *Derived, const CXXRecordDecl *Base) { … } static std::optional<QualType> approximateStandardConversionSequence(const TheCheck &Check, QualType From, QualType To, const ASTContext &Ctx) { … } 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 { … }; } // namespace static std::optional<ConversionSequence> tryConversionOperators(const TheCheck &Check, const CXXRecordDecl *RD, QualType ToType) { … } static std::optional<ConversionSequence> tryConvertingConstructors(const TheCheck &Check, QualType FromType, const CXXRecordDecl *RD) { … } /// 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) { … } static MixableParameterRange modelMixingRange( const TheCheck &Check, const FunctionDecl *FD, std::size_t StartIndex, const filter::SimilarlyUsedParameterPairSuppressor &UsageBasedSuppressor) { … } } // namespace model /// Matches DeclRefExprs and their ignorable wrappers to ParmVarDecls. AST_MATCHER_FUNCTION(ast_matchers::internal::Matcher<Stmt>, paramRefExpr) { … } 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) { … } /// This namespace contains the implementations for the suppression of /// diagnostics from similarly-used ("related") parameters. namespace relatedness_heuristic { static constexpr std::size_t SmallDataStructureSize = …; ParamToSmallSetMap; /// Returns whether the sets mapped to the two elements in the map have at /// least one element in common. template <typename MapTy, typename ElemTy> bool lazyMapOfSetsIntersectionExists(const MapTy &Map, const ElemTy &E1, const ElemTy &E2) { … } /// Implements the heuristic that marks two parameters related if there is /// a usage for both in the same strict expression subtree. A strict /// expression subtree is a tree which only includes Expr nodes, i.e. no /// Stmts and no Decls. class AppearsInSameExpr : public RecursiveASTVisitor<AppearsInSameExpr> { … }; /// 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 { … }; /// Implements the heuristic that marks two parameters related if the same /// member is accessed (referred to) inside the current function's body. class AccessedSameMemberOf { … }; /// Implements the heuristic that marks two parameters related if different /// ReturnStmts return them from the function. class Returned { … }; } // 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 { … }; // (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) { … } static void padStringAtEnd(SmallVectorImpl<char> &Str, std::size_t ToLen) { … } static void padStringAtBegin(SmallVectorImpl<char> &Str, std::size_t ToLen) { … } static bool isCommonPrefixWithoutSomeCharacters(std::size_t N, StringRef S1, StringRef S2) { … } static bool isCommonSuffixWithoutSomeCharacters(std::size_t N, StringRef S1, StringRef S2) { … } /// 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) { … } } // namespace filter /// Matches functions that have at least the specified amount of parameters. AST_MATCHER_P(FunctionDecl, parameterCountGE, unsigned, N) { … } /// Matches *any* overloaded unary and binary operators. AST_MATCHER(FunctionDecl, isOverloadedUnaryOrBinaryOperator) { … } /// 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) { … } // 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) { … } /// Returns the diagnostic-friendly name of the node, or a constant value. static SmallString<64> getNameOrUnnamed(const NamedDecl *ND) { … } /// 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) { … } /// Returns whether a particular Mix between the two parameters should have /// implicit conversions elaborated. static inline bool needsToElaborateImplicitConversion(const model::Mix &M) { … } namespace { /// This class formats a conversion sequence into a "Ty1 -> Ty2 -> Ty3" line /// that can be used in diagnostics. struct FormattedConversionSequence { … }; /// 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 { … }; struct SwappedEqualQualTypePair { … }; struct TypeAliasDiagnosticTuple { … }; /// Helper class to only emit a diagnostic related to MixFlags::TypeAlias once. class UniqueTypeAliasDiagnosticHelper : public InsertOnce<TypeAliasDiagnosticTuple, 8> { … }; } // namespace EasilySwappableParametersCheck::EasilySwappableParametersCheck( StringRef Name, ClangTidyContext *Context) : … { … } void EasilySwappableParametersCheck::storeOptions( ClangTidyOptions::OptionMap &Opts) { … } void EasilySwappableParametersCheck::registerMatchers(MatchFinder *Finder) { … } void EasilySwappableParametersCheck::check( const MatchFinder::MatchResult &Result) { … } } // namespace clang::tidy::bugprone