diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/AdjacentArgumentsOfSameTypeCheck.h b/clang-tools-extra/clang-tidy/cppcoreguidelines/AdjacentArgumentsOfSameTypeCheck.h new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/AdjacentArgumentsOfSameTypeCheck.h @@ -0,0 +1,45 @@ +//===--- AvoidAdjacentArgumentsOfSameTypeCheck.h - clang-tidy ---*- C++ -*-===// +// +// 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 +// +//===----------------------------------------------------------------------===// + +#ifndef LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_ADJACENTARGUMENTSOFSAMETYPECHECK_H +#define LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_ADJACENTARGUMENTSOFSAMETYPECHECK_H + +#include "../ClangTidyCheck.h" + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +/// This check finds function definitions where arguments of the same type +/// follow each other directly, making call sites prone to calling the function +/// with swapped or badly ordered arguments. +/// +/// For the user-facing documentation see: +/// http://clang.llvm.org/extra/clang-tidy/checks/cppcoreguidelines-avoid-adjacent-arguments-of-same-type.html +class AdjacentArgumentsOfSameTypeCheck : public ClangTidyCheck { +public: + AdjacentArgumentsOfSameTypeCheck(StringRef Name, ClangTidyContext *Context); + void registerMatchers(ast_matchers::MatchFinder *Finder) override; + void check(const ast_matchers::MatchFinder::MatchResult &Result) override; + void storeOptions(ClangTidyOptions::OptionMap &Opts) override; + +private: + /// The minimum length of an adjacent range required to have to produce + /// a diagnostic. + const unsigned MinimumLength; + + /// Whether to consider 'T' and 'const T'/'volatile T'/etc. arguments to be + /// possible mixup. + const bool CVRMixPossible; +}; + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang + +#endif // LLVM_CLANG_TOOLS_EXTRA_CLANG_TIDY_CPPCOREGUIDELINES_ADJACENTARGUMENTSOFSAMETYPECHECK_H diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/AdjacentArgumentsOfSameTypeCheck.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/AdjacentArgumentsOfSameTypeCheck.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/AdjacentArgumentsOfSameTypeCheck.cpp @@ -0,0 +1,602 @@ +//===--- AvoidAdjacentArgumentsOfSameTypeCheck.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 +#include + +#include "AdjacentArgumentsOfSameTypeCheck.h" +#include "clang/AST/ASTContext.h" +#include "clang/ASTMatchers/ASTMatchFinder.h" +#include "llvm/ADT/BitmaskEnum.h" +#include "llvm/ADT/SmallVector.h" + +// clang-format off +// FIXME: Remove debug things. +#define DEBUG_TYPE "AdjacentArguments" +#include "llvm/Support/Debug.h" +// clang-format on + +using namespace clang::ast_matchers; + +namespace clang { +namespace tidy { +namespace cppcoreguidelines { + +namespace { + +/// Attempts to strip away as many qualification, sugar and spice from the +/// type as possible to reach the innermost purest the user eventually uses. +/// This method attempts to discard everything as much as possible. +const Type *stripType(const Type *T); + +/// Annotates how a mixup might happen. This is a flag enumeration. +enum MixupTag : unsigned char { + MIXUP_Invalid = 0, //< Sentinel 0 bit pattern value for masking. DO NOT USE! + MIXUP_None = 1, //< Mixup is not possible. + MIXUP_Trivial = 2, //< No extra information needed. + MIXUP_Typedef = 4, //< Argument of a typedef which resolves to an + //< effective desugared type same as the other arg. + MIXUP_RefBind = 8, //< Argument mixes with another due to reference binding. + MIXUP_CVR = 16, //< Argument mixes with another through implicit + //< qualification. + + LLVM_MARK_AS_BITMASK_ENUM(/* LargestValue = */ MIXUP_CVR) +}; +LLVM_ENABLE_BITMASK_ENUMS_IN_NAMESPACE(); + +/// Returns whether an lvalue reference refers to the same type as T. +MixupTag refBindsToSameType(const LValueReferenceType *LRef, const Type *T, + const bool CVRMixPossible); + +/// Sanitises the MixupTag so it doesn't contain contradictory bits set. +MixupTag sanitiseMixup(MixupTag Tag) { + assert(Tag != MIXUP_Invalid && "Mixup tag had full zero bit pattern value!"); + + if (Tag & MIXUP_None) + // If at any point the check recursion marks the mixup impossible, it is + // just impossible. + return MIXUP_None; + + if (Tag == MIXUP_Trivial) + return Tag; + + if (Tag ^ MIXUP_Trivial) + // If any other bits than Trivial is set, unset Trivial, so only the + // annotation bits warranting extra diagnostic are set. + return Tag & ~MIXUP_Trivial; + + return Tag; +} + +/// Returns whether LType and RType refer to the same type in a sense that at a +/// call site it is possible to mix the types up if the actual arguments are +/// specified in opposite order. +/// \returns MixupTag indicating how a mixup between the arguments happens. +/// The final output of this potentially recursive function must be sanitised. +MixupTag howPossibleToMixUpAtCallSite(const QualType LType, + const QualType RType, + const ASTContext &Ctx, + const bool CVRMixPossible) { + LLVM_DEBUG(llvm::dbgs() << "isPossibleToMixUpAtCallSite?\n Left:"; + LType.dump(llvm::dbgs()); llvm::dbgs() << "\n\t and \n Right: "; + RType.dump(llvm::dbgs()); llvm::dbgs() << '\n';); + + if (LType == RType) { + LLVM_DEBUG(llvm::dbgs() << "!!! Both Left and Right are same type.\n";); + return MIXUP_Trivial; + } + + // Remove certain sugars that don't affect mixability from the types. + if (dyn_cast(LType.getTypePtr())) + return howPossibleToMixUpAtCallSite(LType.getSingleStepDesugaredType(Ctx), + RType, Ctx, CVRMixPossible); + if (dyn_cast(RType.getTypePtr())) + return howPossibleToMixUpAtCallSite( + LType, RType.getSingleStepDesugaredType(Ctx), Ctx, CVRMixPossible); + + const LangOptions &LOpts = Ctx.getLangOpts(); + assert(LOpts.CPlusPlus && "check should only be enabled for C++ TUs"); + + { + const auto *LTypedef = LType->getAs(); + const auto *RTypedef = RType->getAs(); + if (LTypedef && RTypedef) { + LLVM_DEBUG(llvm::dbgs() + << "!!! Both Left and Right are typedef types.\n";); + return MIXUP_Typedef | howPossibleToMixUpAtCallSite(LTypedef->desugar(), + RTypedef->desugar(), + Ctx, CVRMixPossible); + } + if (LTypedef) { + LLVM_DEBUG(llvm::dbgs() << "!!! Left is typedef type.\n";); + return MIXUP_Typedef | + howPossibleToMixUpAtCallSite(LTypedef->desugar(), RType, Ctx, + CVRMixPossible); + } + if (RTypedef) { + LLVM_DEBUG(llvm::dbgs() << "!!! Right is typedef type.\n";); + return MIXUP_Typedef | + howPossibleToMixUpAtCallSite(LType, RTypedef->desugar(), Ctx, + CVRMixPossible); + } + } + + if (LType->isPointerType() && RType->isPointerType()) { + // (Both types being the same pointer is handled by LType == RType.) + LLVM_DEBUG(llvm::dbgs() << "!!! Both Left and Right are pointer types.\n";); + MixupTag Mixup = howPossibleToMixUpAtCallSite( + LType->getPointeeType(), RType->getPointeeType(), Ctx, CVRMixPossible); + + if (LType.isLocalConstQualified() || LType.isLocalVolatileQualified() || + RType.isLocalConstQualified() || RType.isLocalVolatileQualified()) { + // A qualified ptr might be mixed up with an unqualified one at call, but + // this is up to user configuration to report about. + if (CVRMixPossible) + Mixup |= MIXUP_CVR; + else + Mixup |= MIXUP_None; + } + + return Mixup; + } + + // An argument of type 'T' and 'const T' may bind with the same power. + // Case for both types being const qualified (for the same type) is handled + // by LType == RType. + if (CVRMixPossible && + (LType.isLocalConstQualified() || LType.isLocalVolatileQualified() || + LType.isLocalRestrictQualified())) { + LLVM_DEBUG(llvm::dbgs() << "!!! Left is CVR qualified."; + LType.dump(llvm::dbgs()); llvm::dbgs() << '\n';); + return MIXUP_CVR | howPossibleToMixUpAtCallSite(LType.getUnqualifiedType(), + RType, Ctx, CVRMixPossible); + } + if (CVRMixPossible && + (RType.isLocalConstQualified() || RType.isLocalVolatileQualified() || + RType.isLocalRestrictQualified())) { + LLVM_DEBUG(llvm::dbgs() << "!!! Right is CVR qualified\n"; + RType.dump(llvm::dbgs()); llvm::dbgs() << '\n';); + return MIXUP_CVR | + howPossibleToMixUpAtCallSite(LType, RType.getUnqualifiedType(), Ctx, + CVRMixPossible); + } + + // An argument of type 'T' and 'const T &' may bind with the same power. + // (Note this is a different case, as 'const T &' is on the top level a '&', + // and only then a const.) + if (LType->isLValueReferenceType() || RType->isLValueReferenceType()) { + // (If both is the same reference type, earlier a return happened.) + + if (LType->isLValueReferenceType()) { + LLVM_DEBUG(llvm::dbgs() << "!!! Left is an lvalue reference type\n";); + MixupTag RefBind = refBindsToSameType(LType->getAs(), + RType.getTypePtr(), CVRMixPossible); + // RefBind may or may not have given us a tag (e.g. reference was to a + // typedef) via a recursive chain back to this function. Apply the + // "bind power" tag here to indicate a reference binding happened. + // (If RefBind was MIXUP_None, a later sanitise step will undo every bit + // except for None.) + return RefBind | MIXUP_RefBind; + } + if (RType->isLValueReferenceType()) { + LLVM_DEBUG(llvm::dbgs() << "!!! Right is an lvalue reference type.\n";); + MixupTag RefBind = refBindsToSameType(RType->getAs(), + LType.getTypePtr(), CVRMixPossible); + return RefBind | MIXUP_RefBind; + } + } + + LLVM_DEBUG(llvm::dbgs() << "<<< End of logic reached, types don't match.";); + return MIXUP_None; +} + +MixupTag refBindsToSameType(const LValueReferenceType *LRef, const Type *T, + const bool CVRMixPossible) { + LLVM_DEBUG(llvm::dbgs() << "refBindsToSameType?\n"; LRef->dump(llvm::dbgs()); + llvm::dbgs() << "\n\t and \n"; T->dump(llvm::dbgs()); + llvm::dbgs() << '\n';); + + const QualType ReferredType = LRef->getPointeeType(); + LLVM_DEBUG(llvm::dbgs() << "Referred type:\n"; + ReferredType->dump(llvm::dbgs());); + if (!ReferredType.isLocalConstQualified()) { + // A non-const reference doesn't bind with the same power as a "normal" + // by-value argument. + LLVM_DEBUG(llvm::dbgs() << "referred type is not const qualified.\n";); + return MIXUP_None; + } + + if (const TypedefType *TypedefTy = + ReferredType.getTypePtr()->getAs()) { + // If the referred type is a typedef, try checking the mixup-chance on the + // desugared type. + LLVM_DEBUG(llvm::dbgs() << "Reference to a typedef, calling back...\n";); + return howPossibleToMixUpAtCallSite(TypedefTy->desugar(), QualType{T, 0}, + TypedefTy->getDecl()->getASTContext(), + CVRMixPossible); + } + + LLVM_DEBUG(llvm::dbgs() << "is referred type type_ptr() and T the same? " + << (ReferredType.getTypePtr() == T) << '\n';); + return ReferredType.getTypePtr() == T ? MIXUP_Trivial : MIXUP_None; +} + +using AdjacentParamsMixup = + llvm::SmallVector, 4>; + +/// Gets the type-equal range of the parameters of F starting with the param +/// at index i. The return value will always have at least 1 element, the ith +/// parameter itself. +/// \returns In the returned param mixup vector, each mixup tag refers to the +/// reasoning behind mixing up the nth parameter with the first in the adjacent +/// range. +AdjacentParamsMixup paramEqualTypeRange(const FunctionDecl *F, unsigned int i, + const bool CVRMixPossible) { + assert(i <= F->getNumParams() && "invalid index given"); + + AdjacentParamsMixup APR; + const ParmVarDecl *First = F->getParamDecl(i); + LLVM_DEBUG(llvm::dbgs() << "Start " << i << "th param:\n"; + First->dump(llvm::dbgs()); llvm::dbgs() << '\n';); + APR.emplace_back(First, MIXUP_Trivial); + + const ASTContext &Ctx = F->getASTContext(); + const unsigned int ParamCount = F->getNumParams(); + for (++i; i < ParamCount; ++i) { + const ParmVarDecl *Ith = F->getParamDecl(i); + LLVM_DEBUG(llvm::dbgs() << "Check " << i << "th param:\n"; + Ith->dump(llvm::dbgs()); llvm::dbgs() << '\n';); + MixupTag Mixup = howPossibleToMixUpAtCallSite( + First->getType(), Ith->getType(), Ctx, CVRMixPossible); + LLVM_DEBUG(llvm::dbgs() << "\tMixup? " << static_cast(Mixup) << "\n";); + Mixup = sanitiseMixup(Mixup); + LLVM_DEBUG(llvm::dbgs() << "\tMixup (after sanitise)? " + << static_cast(Mixup) << "\n";); + + if (Mixup == MIXUP_None) + break; + + APR.emplace_back(Ith, Mixup); + } + + if (APR.size() > 1 && + llvm::any_of(APR, [](const AdjacentParamsMixup::value_type &E) { + return E.second & MIXUP_Typedef; + })) { + // If any of the mixups for argument pairs (1, 2), (1, 3), ... involve a + // typedef match, the first argument might be a typedef too. Set a bit to + // emit a "type alias resolution" diagnostic for the first argument of the + // range. + APR.front().second = MIXUP_Typedef; + } + + return APR; +} + +const Type *stripType(const Type *T) { + LLVM_DEBUG(llvm::dbgs() << "Stripper handling:\n"; T->dump(llvm::dbgs()); + llvm::dbgs() << '\n';); + if (const auto *PointerTy = T->getAs()) + return stripType(PointerTy->getPointeeOrArrayElementType()); + if (const auto *RefTy = T->getAs()) + return stripType(RefTy->getPointeeType().getUnqualifiedType().getTypePtr()); + if (const auto *TagTy = T->getAs()) + return TagTy; + if (const auto *Builtin = T->getAs()) + return Builtin; + if (const auto *TypedefTy = T->getAs()) + return stripType(TypedefTy->desugar().getTypePtr()); + if (const auto *ParenTy = T->getAs()) + return stripType(ParenTy->desugar().getTypePtr()); + if (const auto *PackTy = T->getAs()) + return stripType(PackTy->getPattern().getTypePtr()); + + // In any other case, assume we can't reasonably strip any further. + return T; +} + +/// Prints the type's textual representation to the output stream. This printer +/// discards as many sugar as it can, for example removing typedefs and printing +/// the underlying type. +void putTypeName(const QualType QT, llvm::raw_ostream &OS, + const PrintingPolicy &PP) { + LLVM_DEBUG(llvm::dbgs() << "putTypeName called for\n"; QT.dump(llvm::dbgs()); + llvm::dbgs() << '\n';); + SplitQualType SQT = QT.split(); + const Type *Ty = SQT.Ty; + + if (const auto *DecayTy = dyn_cast(Ty)) + return putTypeName(DecayTy->getPointeeType(), OS, PP); + + if (const auto *PointerTy = dyn_cast(Ty)) { + putTypeName(PointerTy->getPointeeType(), OS, PP); + // Note: this might print types with weird grammar (function pointers, etc.) + // in a weird way. + OS << " *"; + + // Qualifications of a pointer has to be after the '*'. + // (The member is qualified in a subsequent recursive call.) + if (SQT.Quals.hasConst()) + OS << " const"; + if (SQT.Quals.hasVolatile()) + OS << " volatile"; + if (SQT.Quals.hasRestrict()) + OS << " restrict"; + return; + } + + // Qualifications of everything else can be written at the front. + if (SQT.Quals.hasConst()) + OS << "const "; + if (SQT.Quals.hasVolatile()) + OS << "volatile "; + if (SQT.Quals.hasRestrict()) + OS << "restrict "; + + if (const auto *RefTy = dyn_cast(Ty)) { + putTypeName(RefTy->getPointeeType(), OS, PP); + if (RefTy->isLValueReferenceType()) + OS << " &"; + else if (RefTy->isRValueReferenceType()) + OS << " &&"; + } else if (const auto *BuiltinTy = dyn_cast(Ty)) + OS << BuiltinTy->getName(PP); + else if (const TagDecl *TagDeclTy = Ty->getAsTagDecl()) { + StringRef Name = TagDeclTy->getName(); + if (Name.empty()) + Name = TagDeclTy->getTypedefNameForAnonDecl()->getName(); + + if (!Name.empty()) + OS << Name; + else + OS << "(unknown " + << static_cast(TagDeclTy)->getDeclKindName() + << ")"; + } else if (const auto *TypedefTy = dyn_cast(Ty)) + putTypeName(TypedefTy->desugar(), OS, PP); + else if (const auto *TemplateTy = dyn_cast(Ty)) + OS << TemplateTy->getDecl()->getName(); + else if (const auto *DependentNTy = dyn_cast(Ty)) { + OS << DependentNTy->getIdentifier()->getName(); + LLVM_DEBUG(llvm::dbgs() << "Dependent Name Type\n";); + } else if (const auto *FunctionPtrTy = dyn_cast(Ty)) { + LLVM_DEBUG(llvm::dbgs() << "Printing function prototype..."; + FunctionPtrTy->dump(llvm::dbgs()); llvm::dbgs() << '\n';); + + putTypeName(FunctionPtrTy->getReturnType(), OS, PP); + OS << " ("; + for (unsigned Idx = 0; Idx < FunctionPtrTy->getNumParams(); ++Idx) { + putTypeName(FunctionPtrTy->getParamType(Idx), OS, PP); + if (Idx < FunctionPtrTy->getNumParams() - 1) + OS << ", "; + } + OS << ')'; + } else if (const auto *SpecialisationTy = + dyn_cast(Ty)) { + SpecialisationTy->getTemplateName().print(OS, PP, /* SuppressNNS =*/true); + OS << '<'; + for (const TemplateArgument &Arg : SpecialisationTy->template_arguments()) { + Arg.print(PP, OS); + } + OS << '>'; + } else if (const auto *ParenTy = dyn_cast(Ty)) + putTypeName(ParenTy->getInnerType(), OS, PP); + else if (const auto *PackTy = dyn_cast(Ty)) { + putTypeName(PackTy->getPattern(), OS, PP); + OS << "..."; + } else { + // There are things like "GCC Vector type" and such that who knows how + // to print properly? + OS << "getTypeClassName() << '>'; + } +} + +// For convenience, ignore a few well-known parameter variable and type names +// which usually show up with repeated parameters. +const char *IgnoredParmNames[] = {"iterator", "Iterator", "begin", "Begin", + "end", "End", "first", "First", + "last", "Last"}; + +const char *IgnoredTypeNames[] = {"it", + "It", + "iterator", + "Iterator", + "inputit", + "InputIt", + "forwardit", + "ForwardIt", + "bidirit", + "BidirIt", + "constiterator", + "const_iterator", + "Const_Iterator", + "Constiterator", + "ConstIterator"}; + +/// Matches function parameters which type or variable name are not on the +/// ignore list. +bool hasIgnoredName(const ParmVarDecl *Node) { + if (!Node->getIdentifier()) + return true; + StringRef NodeName = Node->getName(); + if (llvm::any_of(IgnoredParmNames, [&NodeName](const char *IgnoredName) { + if (NodeName == IgnoredName) { + LLVM_DEBUG(llvm::dbgs() << NodeName << " is on ignore list.\n";); + return true; + } + return false; + })) + return true; + + const Type *NodeType = + Node->getType().getDesugaredType(Node->getASTContext()).getTypePtr(); + assert(NodeType && "parameter without a type?"); + + std::string NodeTypeName = Node->getType().getAsString(); + if (!NodeTypeName.empty()) { + if (llvm::any_of(IgnoredTypeNames, [&NodeName, &NodeTypeName]( + const char *IgnoredName) { + if (NodeTypeName == IgnoredName) { + LLVM_DEBUG(llvm::dbgs() << NodeName << " has ignored type name " + << NodeTypeName << "\n";); + return true; + } + + std::size_t IgnoredNameLen = strlen(IgnoredName); + if (NodeTypeName.length() > IgnoredNameLen && + NodeTypeName.compare(NodeTypeName.length() - IgnoredNameLen, + IgnoredNameLen, IgnoredName) == 0) { + LLVM_DEBUG(llvm::dbgs() + << NodeName << " ends with ignored type name " + << IgnoredName << " in " << NodeTypeName << "\n";); + return true; + } + return false; + })) + return true; + } + + return false; +} + +AST_MATCHER_P(FunctionDecl, hasAtLeastNumArguments, unsigned int, N) { + return Node.getNumParams() >= N; +} + +} // namespace + +AdjacentArgumentsOfSameTypeCheck::AdjacentArgumentsOfSameTypeCheck( + StringRef Name, ClangTidyContext *Context) + : ClangTidyCheck(Name, Context), + MinimumLength(Options.getLocalOrGlobal("MinimumLength", 0) < 2 + ? 3 + : Options.getLocalOrGlobal("MinimumLength", 3)), + CVRMixPossible(Options.getLocalOrGlobal("CVRMixPossible", false)) {} + +void AdjacentArgumentsOfSameTypeCheck::storeOptions( + ClangTidyOptions::OptionMap &Opts) { + Options.store(Opts, "MinimumLength", MinimumLength); + Options.store(Opts, "CVRMixPossible", CVRMixPossible); +} + +void AdjacentArgumentsOfSameTypeCheck::registerMatchers(MatchFinder *Finder) { + if (!getLangOpts().CPlusPlus) + return; + + Finder->addMatcher(functionDecl( + // Only report for definitions as the user only has + // chance to fix if they can change the def. + isDefinition(), hasAtLeastNumArguments(MinimumLength), + unless(ast_matchers::isTemplateInstantiation())) + .bind("fun"), + this); + + Finder->addMatcher(functionDecl( + // Only report for definitions as the user only has + // chance to fix if they can change the def. + isDefinition(), hasAtLeastNumArguments(MinimumLength), + isExplicitTemplateSpecialization()) + .bind("fun"), + this); +} + +void AdjacentArgumentsOfSameTypeCheck::check( + const MatchFinder::MatchResult &Result) { + const auto *Fun = Result.Nodes.getNodeAs("fun"); + LLVM_DEBUG(llvm::dbgs() << "\n#############################################" + << "\nHandling match for:\n"; + Fun->dump(llvm::dbgs()); llvm::dbgs() << "\n\n";); + unsigned int ParamEqRangeStartIdx = 0; + const unsigned int NumArgs = Fun->getNumParams(); + while (ParamEqRangeStartIdx < NumArgs) { + if (hasIgnoredName(Fun->getParamDecl(ParamEqRangeStartIdx))) { + // If the current argument's name or type name is ignored, don't try + // creating a range from it. + LLVM_DEBUG(llvm::dbgs() + << ParamEqRangeStartIdx << "th argument ignored.\n";); + ++ParamEqRangeStartIdx; + continue; + } + + AdjacentParamsMixup APR = + paramEqualTypeRange(Fun, ParamEqRangeStartIdx, CVRMixPossible); + ParamEqRangeStartIdx += APR.size(); + if (APR.size() < MinimumLength) + continue; + + // A function with an adjacent argument range of sufficient length found. + std::string FunName = Fun->getDeclName().getAsString(); + if (FunName.empty()) + FunName = ""; + + const PrintingPolicy &PP{Fun->getASTContext().getLangOpts()}; + std::string FirstArgType = APR.front().first->getType().getAsString(PP); + + const auto HasMixupTagWarrantingTypePrint = + [](const AdjacentParamsMixup::value_type &E) { + return E.second & (MIXUP_Typedef | MIXUP_RefBind | MIXUP_CVR); + }; + + if (llvm::any_of(APR, HasMixupTagWarrantingTypePrint)) { + // If any of the arguments in the range is annotated with a tag that will + // warrant printing type names, the primary diagnostic shouldn't have + // a type name printed. + diag(APR.front().first->getOuterLocStart(), + "%0 adjacent arguments for '%1' of similar type are easily swapped " + "by mistake") + << static_cast(APR.size()) << FunName; + } else { + diag(APR.front().first->getOuterLocStart(), + "%0 adjacent arguments for '%1' of similar type ('%2') are easily " + "swapped by mistake") + << static_cast(APR.size()) << FunName << FirstArgType; + } + + // FIXME: ClangTidy doesn't seem to support emitting a custom source range + // highlight without a fixit. This check can't produce useful automatic + // fixits, but highlighting the 'CharSourceRange' would be good to have. + diag(APR.back().first->getLocation(), + "last argument in the adjacent range here", DiagnosticIDs::Note); + + for (const auto &ParmPair : APR) { + if (ParmPair.second < MIXUP_Trivial) + llvm_unreachable("Invalid mixup type in result when emitting diags."); + // MIXUP_Trivial: no extra diagnostics required. + + if (HasMixupTagWarrantingTypePrint(ParmPair)) { + std::string ParmType; + { + llvm::raw_string_ostream OS{ParmType}; + LLVM_DEBUG(llvm::dbgs() + << "\n\nGenerating type name for diagnostic...\n\n";); + putTypeName(ParmPair.first->getType(), OS, PP); + } + LLVM_DEBUG(llvm::dbgs() + << "Type name generated: " << ParmType << '\n';); + + if (ParmPair.second & MIXUP_Typedef) { + diag(ParmPair.first->getOuterLocStart(), + "after resolving type aliases, type of argument '%0' is '%1'", + DiagnosticIDs::Note) + << ParmPair.first->getName() << ParmType; + } + + if (ParmPair.second & (MIXUP_RefBind | MIXUP_CVR)) { + diag(ParmPair.first->getOuterLocStart(), + "at a call site, '%0' might bind with same force as '%1'", + DiagnosticIDs::Note) + << ParmType << FirstArgType; + } + } + } + } +} + +} // namespace cppcoreguidelines +} // namespace tidy +} // namespace clang diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CMakeLists.txt @@ -1,6 +1,7 @@ set(LLVM_LINK_COMPONENTS support) add_clang_library(clangTidyCppCoreGuidelinesModule + AdjacentArgumentsOfSameTypeCheck.cpp AvoidGotoCheck.cpp CppCoreGuidelinesTidyModule.cpp InitVariablesCheck.cpp diff --git a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp --- a/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp +++ b/clang-tools-extra/clang-tidy/cppcoreguidelines/CppCoreGuidelinesTidyModule.cpp @@ -14,6 +14,7 @@ #include "../modernize/AvoidCArraysCheck.h" #include "../modernize/UseOverrideCheck.h" #include "../readability/MagicNumbersCheck.h" +#include "AdjacentArgumentsOfSameTypeCheck.h" #include "AvoidGotoCheck.h" #include "InitVariablesCheck.h" #include "InterfacesGlobalInitCheck.h" @@ -42,6 +43,8 @@ class CppCoreGuidelinesModule : public ClangTidyModule { public: void addCheckFactories(ClangTidyCheckFactories &CheckFactories) override { + CheckFactories.registerCheck( + "cppcoreguidelines-avoid-adjacent-arguments-of-same-type"); CheckFactories.registerCheck( "cppcoreguidelines-avoid-c-arrays"); CheckFactories.registerCheck( diff --git a/clang-tools-extra/docs/ReleaseNotes.rst b/clang-tools-extra/docs/ReleaseNotes.rst --- a/clang-tools-extra/docs/ReleaseNotes.rst +++ b/clang-tools-extra/docs/ReleaseNotes.rst @@ -88,6 +88,13 @@ Without the null terminator it can result in undefined behaviour when the string is read. +- New :doc:`cppcoreguidelines-avoid-adjacent-arguments-of-same-type + ` check. + + This check finds function definitions where arguments of the same type follow + each other directly, making call sites prone to calling the function with + swapped or badly ordered arguments. + - New :doc:`cppcoreguidelines-init-variables ` check. diff --git a/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-avoid-adjacent-arguments-of-same-type.rst b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-avoid-adjacent-arguments-of-same-type.rst new file mode 100644 --- /dev/null +++ b/clang-tools-extra/docs/clang-tidy/checks/cppcoreguidelines-avoid-adjacent-arguments-of-same-type.rst @@ -0,0 +1,124 @@ +.. title:: clang-tidy - cppcoreguidelines-avoid-adjacent-arguments-of-same-type + +cppcoreguidelines-avoid-adjacent-arguments-of-same-type +======================================================= + +This check finds function definitions where arguments of the same type follow +each other directly, making call sites prone to calling the function with +swapped or badly ordered arguments. + +This rule is part of the "Interfaces" profile of the C++ Core Guidelines, see +`I.24 `_. + +.. code-block:: c++ + + void draw_point(int X, int Y) {} + FILE *open_path(const char *Dir, const char *Name, Flags Mode) {} + +A potential call like ``draw(-2, 5)`` or +``open_path("a.txt", "/tmp", Read | Write)`` is perfectly legal from the +language's perspective, but might not be what the developer of the function +intended. + +The C++ Core Guidelines recommend using more elaborate types for arguments, +such as + +.. code-block:: c++ + + struct Coords2D { int X; int Y; }; + void draw_point(const Coords2D Point) {} + + FILE *open_path(const Path &Dir, const Filename &Name, Flags Mode) {} + +Due to the elaborate refactoring and API-breaking requirements posed by fixing +the issues diagnosed by this check, **no automatic fix-its** are proposed. + +Currently, functions that take pairs of iterators (where the suggested option +would be moving to, e.g., the +`Ranges `_ library) are hard-coded to +not produce a diagnostic. + + - The following *argument names* and their Uppercase-initial variants are + ignored: + ``iterator``, ``begin``, ``end``, ``first``, ``last``. + - The following *type names* and their lowercase-initial variants (for types + of any argument found) are ignored: + ``It``, ``Iterator``, ``InputIt``, ``ForwardIt``, ``BidirIt``, + ``const_iterator``, ``ConstIterator`` + +Options +------- + +.. option:: MinimumLength + + The minimum length of the adjacent argument sequence required before a + diagnostic is emitted. + Defaults to ``3`` for sake of user-friendliness. + Can be any (positive integer) number. + To get the strict requirement present in C++ Core Guidelines, set to ``2``. + +.. option:: CVRMixPossible + + Whether to consider arguments of type ``T`` and the qualified + ``const T``/``volatile T`` counterpart forming a common "adjacent argument" + sequence. + If ``0`` (default value), the check assumes such arguments cannot be mixed + up at a potential call site. + A non-zero value turns this assumption **off**. + A non-zero value is generally expected to produce a broader set of results. + + The following example will not produce a diagnostic unless + *CVRMixPossible* is set to a non-zero value. + + .. code-block:: c++ + + struct T {}; + void f(T *tp, const T *ctp) {} + +Limitations +----------- + +This check does not investigate functions that were instantiated from function +templates, only the primary template definitions and explicit specialisations +are checked. + +Furthermore, this check is not able to find if some arguments of +template-dependent types might end up referring to the same type in the end, +creating an adjacent range that could be mixed up. + +The following example will not be warned about. + +.. code-block:: c++ + + template + struct list_node { + T value; + list_node *prev, *next; + }; + + template + list_node *create(const T& value, + const list_node *prev, + const list_node *prev) { /* ... */ } + +The following example will not be warned about, despite it should be, as both +arguments to the function in the example are of type ``const T &``. + +.. code-block:: c++ + + template + struct vector { + typename T element_type; + typename const T const_element_type; + typename T & reference_type; + typename const T & const_reference_type; + }; + + // Finds the longest occurrence's length between elements "RightEnd" + // and "LeftBegin". For example, in a vector of + // <1, 2, 3, 4, 2, 8, 8, 8, 8, 4> + // findLongestOccurrenceLengthBetween(2, 4) should return 4 (the 4 8s). + template + unsigned int findLongestOccurrenceLengthBetween( + typename vector::const_reference_type RightEnd, + const typename vector::element_type & LeftBegin) { /* ... */ } \ No newline at end of file diff --git a/clang-tools-extra/docs/clang-tidy/checks/list.rst b/clang-tools-extra/docs/clang-tidy/checks/list.rst --- a/clang-tools-extra/docs/clang-tidy/checks/list.rst +++ b/clang-tools-extra/docs/clang-tidy/checks/list.rst @@ -190,6 +190,7 @@ clang-analyzer-valist.CopyToSelf clang-analyzer-valist.Uninitialized clang-analyzer-valist.Unterminated + cppcoreguidelines-avoid-adjacent-arguments-of-same-type cppcoreguidelines-avoid-c-arrays (redirects to modernize-avoid-c-arrays) cppcoreguidelines-avoid-goto cppcoreguidelines-avoid-magic-numbers (redirects to readability-magic-numbers) diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-avoid-adjacent-arguments-of-same-type-cvr-on.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-avoid-adjacent-arguments-of-same-type-cvr-on.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-avoid-adjacent-arguments-of-same-type-cvr-on.cpp @@ -0,0 +1,56 @@ +// RUN: %check_clang_tidy %s \ +// RUN: cppcoreguidelines-avoid-adjacent-arguments-of-same-type %t \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: cppcoreguidelines-avoid-adjacent-arguments-of-same-type.MinimumLength, value: 3}, \ +// RUN: {key: cppcoreguidelines-avoid-adjacent-arguments-of-same-type.CVRMixPossible, value: 1} \ +// RUN: ]}' -- + +struct T {}; + +void mov(T *dst, const T *src) {} // NO-WARN: Length requirement is 3. + +void merge(T *dst, const T *src1, const T *src2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: 3 adjacent arguments for 'merge' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:44: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:20: note: at a call site, 'const T *' might bind with same force as 'T *' +// CHECK-MESSAGES: :[[@LINE-4]]:35: note: at a call site, 'const T *' might bind with same force as 'T *' + +void multi_bind(T t, const T t2, const T &t3, T &&t4) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 3 adjacent arguments for 'multi_bind' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:43: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:22: note: at a call site, 'const T' might bind with same force as 'T' +// CHECK-MESSAGES: :[[@LINE-4]]:34: note: at a call site, 'const T &' might bind with same force as 'T' + +void ptr_quals(int *ip, const int *cip, volatile int *vip, int *const ipc, volatile int *volatile vipv) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 5 adjacent arguments for 'ptr_quals' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:99: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:25: note: at a call site, 'const int *' might bind with same force as 'int *' +// CHECK-MESSAGES: :[[@LINE-4]]:41: note: at a call site, 'volatile int *' might bind with same force as 'int *' +// CHECK-MESSAGES: :[[@LINE-5]]:60: note: at a call site, 'int * const' might bind with same force as 'int *' +// CHECK-MESSAGES: :[[@LINE-6]]:76: note: at a call site, 'volatile int * volatile' might bind with same force as 'int *' + +void value_quals(T t, const T ct, volatile T vt, const volatile T cvt) {} +// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: 4 adjacent arguments for 'value_quals' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:67: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:23: note: at a call site, 'const T' might bind with same force as 'T' +// CHECK-MESSAGES: :[[@LINE-4]]:35: note: at a call site, 'volatile T' might bind with same force as 'T' +// CHECK-MESSAGES: :[[@LINE-5]]:50: note: at a call site, 'const volatile T' might bind with same force as 'T' + +void value_ptr_quals(T *tp, const T *ctp, volatile T *vtp, const volatile T *cvtp, T *const tpc, T *volatile tpv) {} +// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 6 adjacent arguments for 'value_ptr_quals' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:110: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:29: note: at a call site, 'const T *' might bind with same force as 'T *' +// CHECK-MESSAGES: :[[@LINE-4]]:43: note: at a call site, 'volatile T *' might bind with same force as 'T *' +// CHECK-MESSAGES: :[[@LINE-5]]:60: note: at a call site, 'const volatile T *' might bind with same force as 'T *' +// CHECK-MESSAGES: :[[@LINE-6]]:84: note: at a call site, 'T * const' might bind with same force as 'T *' +// CHECK-MESSAGES: :[[@LINE-7]]:98: note: at a call site, 'T * volatile' might bind with same force as 'T *' + +typedef int Numeral; +void set_lerped(const Numeral *const min, + const Numeral *const max, + Numeral *const value) {} +// CHECK-MESSAGES: :[[@LINE-3]]:17: warning: 3 adjacent arguments for 'set_lerped' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:32: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-5]]:17: note: after resolving type aliases, type of argument 'min' is 'const int * const' +// CHECK-MESSAGES: :[[@LINE-4]]:17: note: after resolving type aliases, type of argument 'value' is 'int * const' +// CHECK-MESSAGES: :[[@LINE-5]]:17: note: at a call site, 'int * const' might bind with same force as 'const Numeral *const' diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-avoid-adjacent-arguments-of-same-type-default.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-avoid-adjacent-arguments-of-same-type-default.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-avoid-adjacent-arguments-of-same-type-default.cpp @@ -0,0 +1,193 @@ +// RUN: %check_clang_tidy %s \ +// RUN: cppcoreguidelines-avoid-adjacent-arguments-of-same-type %t \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: cppcoreguidelines-avoid-adjacent-arguments-of-same-type.MinimumLength, value: 3}, \ +// RUN: {key: cppcoreguidelines-avoid-adjacent-arguments-of-same-type.CVRMixPossible, value: 0} \ +// RUN: ]}' -- + +void library(void *vp, void *vp2, void *vp3, int n, int m); +// NO-WARN: The user has no chance to change only declared (usually library) +// functions, so no diagnostic is made. + +// NO-WARN for any of these: only 2 arguments present (default is 3). + +struct T {}; + +void create(T **out_t) {} +void copy(T *p, T *q, int n) {} +void mov(T *dst, const T *src) {} + +void compare01(T t1, T t2) {} +void compare02(const T t1, T t2) {} +void compare03(T t1, const T t2) {} +void compare04(const T &t1, T t2) {} +void compare05(T t1, const T &t2) {} +void compare06(const T &t1, const T &t2) {} +void compare07(const T &t1, const T t2) {} +void compare08(const T t1, const T &t2) {} + +void compare09(T &&t1, T t2) {} +void compare0A(T t1, T &&t2) {} +void compare0B(T &&t1, T &&t2) {} + +struct U {}; +void compare0C(T t, U u) {} +void compare0D(const T t, U u) {} +void compare0E(T t, const U u) {} +void compare0F(const U &u, T t) {} +void compare10(T t, const U &u) {} +void compare11(const T &t, const U &u) {} +void compare12(const T &t, const U u) {} +void compare13(const U u, const T &t) {} +void compare14(T &&t, U u) {} +void compare15(T t, U &&u) {} + +typedef int I1; +typedef int I2; +void multi_through_typedef(I1 i1, I2 i2) {} + +void multi_ptr_through_typedef(I1 *p1, I2 *p2) {} + +typedef I2 myInt; +typedef I2 myOtherInt; +void typedef_chain(myInt mi, myOtherInt moi) {} + +void bind_power_and_typedef(int Read, const myOtherInt &Write) {} + +typedef myOtherInt *IP; +typedef const IP &cipr; +void bind_power_and_multi_layer_typedef(int *RP, cipr WP) {} + +void entirely_different(int i, long l) {} + +struct StringRef { + char *Data; +}; +typedef bool Toggle; + +void multi_pack(StringRef Path, StringRef Name, int Size, Toggle Read, Toggle Write) {} + +void defined(int a, int b); +void defined(int i, int j) {} + +template +void ptr_pair(T *p, T *q) {} + +template +void cptr_pair(const T *p, const T *q) {} + +void ref_and_typedef(int i, I2 i2) {} +void ref_and_typedef2(I1 i, int i2) {} + +int strcmp(const char *A, const char *B) {} + +using str = char *; +using strlit = const str; +int str_compare(strlit a, strlit b) {} + +void append(str s, char *s2) {} + +void append_lit(str s, strlit s2) {} + +void swap(str *a, char **b) {} + +// END of NO-WARN. + +// NO-WARN: CVR-qualification-mixup is turned off in this test file. + +void merge(T *dst, const T *src1, const T *src2) {} + +void multi_bind(T t, const T t2, const T &t3, T &&t4) {} + +void ptr_quals(int *ip, const int *cip, volatile int *vip, int *const ipc, volatile int *volatile vipv) {} + +void value_quals(T t, const T ct, volatile T vt, const volatile T cvt) {} + +void value_ptr_quals(T *tp, const T *ctp, volatile T *vtp, const volatile T *cvtp, T *const tpc, T *volatile tpv) {} + +// NO-WARN: const T* and T* should not mix, even if coming through a typedef. +// (min and max could be mixed up, but length requirement in this test is 3.) +typedef int Numeral; +void set_lerped(const Numeral *const min, + const Numeral *const max, + Numeral *const value) {} + +// END of NO-WARN. + +void protochain(int, int, int); +void protochain(int i, int, int); +void protochain(int, int j, int); +void protochain(int i, int j, int k) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 3 adjacent arguments for 'protochain' of similar type ('int') are +// CHECK-MESSAGES: :[[@LINE-2]]:35: note: last argument in the adjacent range + +void protochain2(I1, I2, myInt); +void protochain2(I1 i, I2, myInt); +void protochain2(I1, I2 j, int); +void protochain2(I1 i, I2 j, myOtherInt k) {} +// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: 3 adjacent arguments for 'protochain2' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:41: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:18: note: after resolving type aliases, type of argument 'i' is 'int' +// CHECK-MESSAGES: :[[@LINE-4]]:24: note: after resolving type aliases, type of argument 'j' is 'int' +// CHECK-MESSAGES: :[[@LINE-5]]:30: note: after resolving type aliases, type of argument 'k' is 'int' + +void len3(const StringRef Drive, const StringRef Dirs, const StringRef FileName) {} +// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: 3 adjacent arguments for 'len3' of similar type ('const StringRef') are +// CHECK-MESSAGES: :[[@LINE-2]]:72: note: last argument in the adjacent range + +void len4(const StringRef Drive, const StringRef Dirs, const StringRef FileName, const StringRef Extension) {} +// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: 4 adjacent arguments for 'len4' of similar type ('const StringRef') are +// CHECK-MESSAGES: :[[@LINE-2]]:98: note: last argument in the adjacent range + +void multi_packs(StringRef Path, StringRef Name, StringRef Occupation, + int Size, int Width, + Toggle Read, Toggle Write, Toggle Execute) {} +// CHECK-MESSAGES: :[[@LINE-3]]:18: warning: 3 adjacent arguments for 'multi_packs' of similar type ('StringRef') are +// CHECK-MESSAGES: :[[@LINE-4]]:60: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:18: warning: 3 adjacent arguments for 'multi_packs' of similar type ('Toggle') are +// CHECK-MESSAGES: :[[@LINE-4]]:52: note: last argument in the adjacent range + +// CVRMixupPossible only concerns 'T' and 'c/v/r T', or pointers thereof, not +// references. +void lifetime_extension(T t, const T &tr1, const T &t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:25: warning: 3 adjacent arguments for 'lifetime_extension' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:53: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:30: note: at a call site, 'const T &' might bind with same force as 'T' +// CHECK-MESSAGES: :[[@LINE-4]]:44: note: at a call site, 'const T &' might bind with same force as 'T' + +enum OldSchoolTermColour { + Black, + Blue, + Green, + Cyan, + Red, + Purple, + BrownOrange, + LightGreyWhite, + Gray, + LightBlue, + LightGreen, + LightCyan, + LightRed, + LightPurple, + YellowLightOrange, + WhiteLightWhite +}; + +void setColouredOutput(OldSchoolTermColour Background, + OldSchoolTermColour Foreground, + OldSchoolTermColour Border) {} +// CHECK-MESSAGES: :[[@LINE-3]]:24: warning: 3 adjacent arguments for 'setColouredOutput' of similar type ('OldSchoolTermColour') are +// CHECK-MESSAGES: :[[@LINE-2]]:44: note: last argument in the adjacent range + +typedef OldSchoolTermColour BackColour; +typedef OldSchoolTermColour ForeColour; +typedef OldSchoolTermColour BorderColour; +void setColouredOutput2(BackColour Background, + ForeColour Foreground, + BorderColour Border) {} +// CHECK-MESSAGES: :[[@LINE-3]]:25: warning: 3 adjacent arguments for 'setColouredOutput2' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:38: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-5]]:25: note: after resolving type aliases, type of argument 'Background' is 'OldSchoolTermColour' +// CHECK-MESSAGES: :[[@LINE-5]]:25: note: after resolving type aliases, type of argument 'Foreground' is 'OldSchoolTermColour' +// CHECK-MESSAGES: :[[@LINE-5]]:25: note: after resolving type aliases, type of argument 'Border' is 'OldSchoolTermColour' \ No newline at end of file diff --git a/clang-tools-extra/test/clang-tidy/cppcoreguidelines-avoid-adjacent-arguments-of-same-type-verbose.cpp b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-avoid-adjacent-arguments-of-same-type-verbose.cpp new file mode 100644 --- /dev/null +++ b/clang-tools-extra/test/clang-tidy/cppcoreguidelines-avoid-adjacent-arguments-of-same-type-verbose.cpp @@ -0,0 +1,420 @@ +// RUN: %check_clang_tidy %s \ +// RUN: cppcoreguidelines-avoid-adjacent-arguments-of-same-type %t \ +// RUN: -config='{CheckOptions: [ \ +// RUN: {key: cppcoreguidelines-avoid-adjacent-arguments-of-same-type.MinimumLength, value: 2}, \ +// RUN: {key: cppcoreguidelines-avoid-adjacent-arguments-of-same-type.CVRMixPossible, value: 1} \ +// RUN: ]}' -- + +void library(void *vp, void *vp2, void *vp3, int n, int m); +// NO-WARN: The user has no chance to change only declared (usually library) +// functions, so no diagnostic is made. + +struct T {}; + +void create(T **out_t) {} // NO-WARN + +void copy(T *p, T *q, int n) {} +// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: 2 adjacent arguments for 'copy' of similar type ('T *') are easily swapped by mistake [cppcoreguidelines-avoid-adjacent-arguments-of-same-type] +// CHECK-MESSAGES: :[[@LINE-2]]:20: note: last argument in the adjacent range here + +void mov(T *dst, const T *src) {} +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: 2 adjacent arguments for 'mov' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:27: note: last argument in the adjacent range here +// CHECK-MESSAGES: :[[@LINE-3]]:18: note: at a call site, 'const T *' might bind with same force as 'T *' + +void compare01(T t1, T t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent arguments for 'compare01' of similar type ('T') are +// CHECK-MESSAGES: :[[@LINE-2]]:24: note: last argument in the adjacent range + +void compare02(const T t1, T t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent arguments for 'compare02' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:30: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:28: note: at a call site, 'T' might bind with same force as 'const T' + +void compare03(T t1, const T t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent arguments for 'compare03' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:30: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:22: note: at a call site, 'const T' might bind with same force as 'T' + +void compare04(const T &t1, T t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent arguments for 'compare04' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:31: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:29: note: at a call site, 'T' might bind with same force as 'const T &' + +void compare05(T t1, const T &t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent arguments for 'compare05' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:31: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:22: note: at a call site, 'const T &' might bind with same force as 'T' + +void compare06(const T &t1, const T &t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent arguments for 'compare06' of similar type ('const T &') are +// CHECK-MESSAGES: :[[@LINE-2]]:38: note: last argument in the adjacent range + +void compare07(const T &t1, const T t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent arguments for 'compare07' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:37: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:29: note: at a call site, 'const T' might bind with same force as 'const T &' + +void compare08(const T t1, const T &t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent arguments for 'compare08' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:37: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:28: note: at a call site, 'const T &' might bind with same force as 'const T' + +// In general, rvalue references and copies are hard to mess up at call sites. +void compare09(T &&t1, T t2) {} // NO-WARN. +void compare0A(T t1, T &&t2) {} // NO-WARN. + +void compare0B(T &&t1, T &&t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent arguments for 'compare0B' of similar type ('T &&') are +// CHECK-MESSAGES: :[[@LINE-2]]:28: note: last argument in the adjacent range + +struct U {}; +void compare0C(T t, U u) {} // NO-WARN. +void compare0D(const T t, U u) {} // NO-WARN. +void compare0E(T t, const U u) {} // NO-WARN. +void compare0F(const U &u, T t) {} // NO-WARN. +void compare10(T t, const U &u) {} // NO-WARN. +void compare11(const T &t, const U &u) {} // NO-WARN. +void compare12(const T &t, const U u) {} // NO-WARN. +void compare13(const U u, const T &t) {} // NO-WARN. +void compare14(T &&t, U u) {} // NO-WARN. +void compare15(T t, U &&u) {} // NO-WARN. + +void multi_bind(T t, const T t2, const T &t3, T &&t4) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 3 adjacent arguments for 'multi_bind' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:43: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:22: note: at a call site, 'const T' might bind with same force as 'T' +// CHECK-MESSAGES: :[[@LINE-4]]:34: note: at a call site, 'const T &' might bind with same force as 'T' + +void ptr_quals(int *ip, const int *cip, volatile int *vip, int *const ipc, volatile int *volatile vipv) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 5 adjacent arguments for 'ptr_quals' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:99: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:25: note: at a call site, 'const int *' might bind with same force as 'int *' +// CHECK-MESSAGES: :[[@LINE-4]]:41: note: at a call site, 'volatile int *' might bind with same force as 'int *' +// CHECK-MESSAGES: :[[@LINE-5]]:60: note: at a call site, 'int * const' might bind with same force as 'int *' +// CHECK-MESSAGES: :[[@LINE-6]]:76: note: at a call site, 'volatile int * volatile' might bind with same force as 'int *' + +void value_quals(T t, const T ct, volatile T vt, const volatile T cvt) {} +// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: 4 adjacent arguments for 'value_quals' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:67: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:23: note: at a call site, 'const T' might bind with same force as 'T' +// CHECK-MESSAGES: :[[@LINE-4]]:35: note: at a call site, 'volatile T' might bind with same force as 'T' +// CHECK-MESSAGES: :[[@LINE-5]]:50: note: at a call site, 'const volatile T' might bind with same force as 'T' + +void value_ptr_quals(T *tp, const T *ctp, volatile T *vtp, const volatile T *cvtp, T *const tpc, T *volatile tpv) {} +// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 6 adjacent arguments for 'value_ptr_quals' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:110: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:29: note: at a call site, 'const T *' might bind with same force as 'T *' +// CHECK-MESSAGES: :[[@LINE-4]]:43: note: at a call site, 'volatile T *' might bind with same force as 'T *' +// CHECK-MESSAGES: :[[@LINE-5]]:60: note: at a call site, 'const volatile T *' might bind with same force as 'T *' +// CHECK-MESSAGES: :[[@LINE-6]]:84: note: at a call site, 'T * const' might bind with same force as 'T *' +// CHECK-MESSAGES: :[[@LINE-7]]:98: note: at a call site, 'T * volatile' might bind with same force as 'T *' + +typedef int I1; +typedef int I2; +void multi_through_typedef(I1 i1, I2 i2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:28: warning: 2 adjacent arguments for 'multi_through_typedef' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:38: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:28: note: after resolving type aliases, type of argument 'i1' is 'int' +// CHECK-MESSAGES: :[[@LINE-4]]:35: note: after resolving type aliases, type of argument 'i2' is 'int' + +void multi_ptr_through_typedef(I1 *p1, I2 *p2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:32: warning: 2 adjacent arguments for 'multi_ptr_through_typedef' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:44: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:32: note: after resolving type aliases, type of argument 'p1' is 'int *' +// CHECK-MESSAGES: :[[@LINE-4]]:40: note: after resolving type aliases, type of argument 'p2' is 'int *' + +typedef I2 myInt; +typedef I2 myOtherInt; +void typedef_chain(myInt mi, myOtherInt moi) {} +// CHECK-MESSAGES: :[[@LINE-1]]:20: warning: 2 adjacent arguments for 'typedef_chain' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:41: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:20: note: after resolving type aliases, type of argument 'mi' is 'int' +// CHECK-MESSAGES: :[[@LINE-4]]:30: note: after resolving type aliases, type of argument 'moi' is 'int' + +void bind_power_and_typedef(int Read, const myOtherInt &Write) {} +// CHECK-MESSAGES: :[[@LINE-1]]:29: warning: 2 adjacent arguments for 'bind_power_and_typedef' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:57: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:39: note: after resolving type aliases, type of argument 'Write' is 'const int &' +// CHECK-MESSAGES: :[[@LINE-4]]:39: note: at a call site, 'const int &' might bind with same force as 'int' + +typedef myOtherInt *IP; +typedef const IP &cipr; +void bind_power_and_multi_layer_typedef(int *RP, cipr WP) {} +// CHECK-MESSAGES: :[[@LINE-1]]:41: warning: 2 adjacent arguments for 'bind_power_and_multi_layer_typedef' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:55: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:50: note: after resolving type aliases, type of argument 'WP' is 'const int * &' +// CHECK-MESSAGES: :[[@LINE-4]]:50: note: at a call site, 'const int * &' might bind with same force as 'int *' + +void entirely_different(int i, long l) {} // NO-WARN. + +struct StringRef { + char *Data; +}; +typedef bool Toggle; + +void multi_pack(StringRef Path, StringRef Name, int Size, Toggle Read, Toggle Write) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent arguments for 'multi_pack' of similar type ('StringRef') are +// CHECK-MESSAGES: :[[@LINE-2]]:43: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:59: warning: 2 adjacent arguments for 'multi_pack' of similar type ('Toggle') are +// CHECK-MESSAGES: :[[@LINE-4]]:79: note: last argument in the adjacent range + +void protochain(int, int, int); +void protochain(int i, int, int); +void protochain(int, int j, int); +void protochain(int i, int j, int k) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 3 adjacent arguments for 'protochain' of similar type ('int') are +// CHECK-MESSAGES: :[[@LINE-2]]:35: note: last argument in the adjacent range + +void protochain2(I1, I2, myInt); +void protochain2(I1 i, I2, myInt); +void protochain2(I1, I2 j, int); +void protochain2(I1 i, I2 j, myOtherInt k) {} +// CHECK-MESSAGES: :[[@LINE-1]]:18: warning: 3 adjacent arguments for 'protochain2' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:41: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:18: note: after resolving type aliases, type of argument 'i' is 'int' +// CHECK-MESSAGES: :[[@LINE-4]]:24: note: after resolving type aliases, type of argument 'j' is 'int' +// CHECK-MESSAGES: :[[@LINE-5]]:30: note: after resolving type aliases, type of argument 'k' is 'int' + +void defined(int a, int b); +void defined(int i, int j) {} +// CHECK-MESSAGES: :[[@LINE-1]]:14: warning: 2 adjacent arguments for 'defined' of similar type ('int') are +// CHECK-MESSAGES: :[[@LINE-2]]:25: note: last argument in the adjacent range + +template +void ptr_pair(T *p, T *q) {} +// CHECK-MESSAGES: :[[@LINE-1]]:15: warning: 2 adjacent arguments for 'ptr_pair' of similar type ('T *') are +// CHECK-MESSAGES: :[[@LINE-2]]:24: note: last argument in the adjacent range + +template +void cptr_pair(const T *p, const T *q) {} +// CHECK-MESSAGES: :[[@LINE-1]]:16: warning: 2 adjacent arguments for 'cptr_pair' of similar type ('const T *') are +// CHECK-MESSAGES: :[[@LINE-2]]:37: note: last argument in the adjacent range + +void ref_and_typedef(int i, I2 i2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:22: warning: 2 adjacent arguments for 'ref_and_typedef' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:32: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:22: note: after resolving type aliases, type of argument 'i' is 'int' +// CHECK-MESSAGES: :[[@LINE-4]]:29: note: after resolving type aliases, type of argument 'i2' is 'int' + +void ref_and_typedef2(I1 i, int i2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:23: warning: 2 adjacent arguments for 'ref_and_typedef2' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:33: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:23: note: after resolving type aliases, type of argument 'i' is 'int' +// CHECK-MESSAGES: :[[@LINE-4]]:29: note: after resolving type aliases, type of argument 'i2' is 'int' + +int strcmp(const char *A, const char *B) {} +// CHECK-MESSAGES: :[[@LINE-1]]:12: warning: 2 adjacent arguments for 'strcmp' of similar type ('const char *') are +// CHECK-MESSAGES: :[[@LINE-2]]:39: note: last argument in the adjacent range + +using str = char *; +using strliteral = const char *; +int str_compare(strliteral a, strliteral b) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent arguments for 'str_compare' of similar type ('strliteral') are +// CHECK-MESSAGES: :[[@LINE-2]]:42: note: last argument in the adjacent range + +void append(str s, char *s2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: 2 adjacent arguments for 'append' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:26: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:13: note: after resolving type aliases, type of argument 's' is 'char *' +// CHECK-MESSAGES: :[[@LINE-4]]:20: note: after resolving type aliases, type of argument 's2' is 'char *' + +void append_lit(str s, strliteral s2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent arguments for 'append_lit' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:35: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:17: note: after resolving type aliases, type of argument 's' is 'char *' +// CHECK-MESSAGES: :[[@LINE-4]]:24: note: after resolving type aliases, type of argument 's2' is 'const char *' + +void swap(str *a, char **b) {} +// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: 2 adjacent arguments for 'swap' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:26: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:11: note: after resolving type aliases, type of argument 'a' is 'char * *' +// CHECK-MESSAGES: :[[@LINE-4]]:19: note: after resolving type aliases, type of argument 'b' is 'char * *' + +template // 'I' as template name not on ignore list. +void shortthing(I a, I b) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent arguments for 'shortthing' of similar type ('I') are +// CHECK-MESSAGES: :[[@LINE-2]]:24: note: last argument in the adjacent range + +// FIXME: Suggest Ranges library alternative? +// NO-WARN: Ignore common type packs, like iterators. +template +void something(InputIt a, InputIt b) {} + +// NO-WARN: Ignore common names, like for iterators. +template +void find(InputIt first, InputIt last, const E &Element) {} + +template +struct vector { + T *Data; + unsigned N; + T operator[](unsigned Idx) { return *(Data + N); } + + typedef T *iterator; + typedef T element; + typedef const T &element_cref; +}; + +// NO-WARN: Argument has "iterator" as type. +template +typename vector::iterator find2( + typename vector::iterator from, + typename vector::iterator to, + const T &E) {} + +template +struct map { + K *Keys; + V *Values; + + struct map_iterator { + K *first; + V *second; + }; + typedef map_iterator iterator; +}; + +// NO-WARN: Argument has "iterator" as type. +template +typename map::iterator find_m( + typename map::iterator left, // Purposefully named as such so param + typename map::iterator right, // name doesn't match, only type name. + const K &k, const V &v) {} + +// NO-WARN: Arguments are dependent types, it could be that for a certain +// 'T' they refer to (essentially) the same thing, and for other T, they don't. +// (Think of std::vector, perhaps?) +// It would be very expensive and tangy logic to deduce that in this small +// test example, one argument is 'T' and the 'const T &'. +template +vector::iterator> findOccurrenceCases( + typename vector::element E, typename vector::element_cref ER) {} + +// FIXME: This case should be diagnosed (are `DependentNameTypes` equatable?) +template +vector> equalRangesBetween( + const typename vector::element &F, + typename vector::element L) {} + +// NO-WARN: Totally different types. +struct Decl; +struct Expr; +struct Stmt; +void HandleASTNodes(Decl *D, Expr *E, Stmt *S) {} + +// NO-WARN: Different template specialisations of the same template. +void concatenate_many_strings(StringRef Out, + vector InRef, + vector InStr, + vector InLiterals) {} + +// NO-WARN: Different template arguments. +template +int compare(const X &x, const Y &y, Comp C) { return C(x, y); } + +// WARN: Explicit specialisation's definition with similar arguments. +template +int compare(int x, int y, Comp C) { return C(x, y); } +// CHECK-MESSAGES: :[[@LINE-1]]:13: warning: 2 adjacent arguments for 'compare' of similar type ('int') are +// CHECK-MESSAGES: :[[@LINE-2]]:24: note: last argument in the adjacent range + +void compare_test() { + // NO-WARN: Function call, not defintion. + compare("A", "B", &strcmp); +} + +int definition_inside_another(vector V) { + struct Sum { + Sum(int _i, int _j) : i(_i), j(_j) {} + // CHECK-MESSAGES: :[[@LINE-1]]:9: warning: 2 adjacent arguments for 'Sum' of similar type ('int') are + // CHECK-MESSAGES: :[[@LINE-2]]:21: note: last argument in the adjacent range + + int operator()() const { return i + j; } + + int i, j; + }; + Sum S{V[0], V[1]}; + + return S(); +} + +void len3(const StringRef Drive, const StringRef Dirs, const StringRef FileName) {} +// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: 3 adjacent arguments for 'len3' of similar type ('const StringRef') are +// CHECK-MESSAGES: :[[@LINE-2]]:72: note: last argument in the adjacent range + +void len4(const StringRef Drive, const StringRef Dirs, const StringRef FileName, const StringRef Extension) {} +// CHECK-MESSAGES: :[[@LINE-1]]:11: warning: 4 adjacent arguments for 'len4' of similar type ('const StringRef') are +// CHECK-MESSAGES: :[[@LINE-2]]:98: note: last argument in the adjacent range + +using MainT = int(int argc, strliteral argv, strliteral envp); +int call_two_programs(MainT one, MainT two) {} +// CHECK-MESSAGES: :[[@LINE-1]]:23: warning: 2 adjacent arguments for 'call_two_programs' of similar type ('MainT *') are +// CHECK-MESSAGES: :[[@LINE-2]]:40: note: last argument in the adjacent range + +using MainT2 = int(int argc, strliteral argv, strliteral envp); +int call_two_programs2(MainT p1, MainT2 p2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:24: warning: 2 adjacent arguments for 'call_two_programs2' of similar type are +// CHECK-MESSAGES: :[[@LINE-2]]:41: note: last argument in the adjacent range +// CHECK-MESSAGES: :[[@LINE-3]]:24: note: after resolving type aliases, type of argument 'p1' is 'int (int, const char *, const char *)' +// CHECK-MESSAGES: :[[@LINE-4]]:34: note: after resolving type aliases, type of argument 'p2' is 'int (int, const char *, const char *)' + +typedef struct { + int i; +} Integer; +bool int_equals(Integer i1, Integer i2) { return i1.i == i2.i; } +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent arguments for 'int_equals' of similar type ('Integer') are +// CHECK-MESSAGES: :[[@LINE-2]]:37: note: last argument in the adjacent range + +typedef enum { + Exclamation, + Question, + Dot +} Token; +bool tok_equals(Token t1, Token t2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:17: warning: 2 adjacent arguments for 'tok_equals' of similar type ('Token') are +// CHECK-MESSAGES: :[[@LINE-2]]:33: note: last argument in the adjacent range + +enum E : char { + A = 'A', + B = 'B', + C = 'C' +}; + +// QUESTION: Should this warn? +// Conversion checks in general may not belong to this check in particular. +bool isSameLetter(E e, const char &c) {} + +template +struct doubly_linked_list_node { + T val; + doubly_linked_list_node *prev; + doubly_linked_list_node *next; +}; + +// FIXME: doubly_linked_list_node is a dependent type, and thus doesn't warn. +template +doubly_linked_list_node *list_insert(const T &element, + const doubly_linked_list_node *prev, + const doubly_linked_list_node *next) {} + +template +void fwd(Args &&... args, Args &&... args2) {} +// CHECK-MESSAGES: :[[@LINE-1]]:10: warning: 2 adjacent arguments for 'fwd' of similar type ('Args &&...') are +// CHECK-MESSAGES: :[[@LINE-2]]:38: note: last argument in the adjacent range + +// Make sure the logic for the check doesn't crash... + +// Matched case from '/usr/include/c++/7/bits/gthr-default.h'. +typedef unsigned long int gthread_t; +static inline int gthread_create(gthread_t *threadid, + void *(*func)(void *), + void *args) {} + +// A modification of the above function that should warn. +int gthread_create_random(gthread_t *threadid, + void *(*func_1)(void *), + void *(*func_2)(void *), + void *args) {} +// CHECK-MESSAGES: :[[@LINE-3]]:27: warning: 2 adjacent arguments for 'gthread_create_random' of similar type ('void *(*)(void *)') are +// CHECK-MESSAGES: :[[@LINE-3]]:35: note: last argument in the adjacent range