//===--- ForwardingReferenceOverloadCheck.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 "ForwardingReferenceOverloadCheck.h" #include "clang/AST/ASTContext.h" #include "clang/ASTMatchers/ASTMatchFinder.h" #include using namespace clang::ast_matchers; namespace clang { namespace tidy { namespace bugprone { namespace { // Check if the given type is related to std::enable_if. AST_MATCHER(QualType, isEnableIf) { auto CheckTemplate = [](const TemplateSpecializationType *Spec) { if (!Spec || !Spec->getTemplateName().getAsTemplateDecl()) { return false; } const NamedDecl *TypeDecl = Spec->getTemplateName().getAsTemplateDecl()->getTemplatedDecl(); return TypeDecl->isInStdNamespace() && (TypeDecl->getName().equals("enable_if") || TypeDecl->getName().equals("enable_if_t")); }; const Type *BaseType = Node.getTypePtr(); // Case: pointer or reference to enable_if. while (BaseType->isPointerType() || BaseType->isReferenceType()) { BaseType = BaseType->getPointeeType().getTypePtr(); } // Case: type parameter dependent (enable_if>). if (const auto *Dependent = BaseType->getAs()) { BaseType = Dependent->getQualifier()->getAsType(); } if (!BaseType) return false; if (CheckTemplate(BaseType->getAs())) { return true; // Case: enable_if_t< >. } else if (const auto *Elaborated = BaseType->getAs()) { if (const auto *Qualifier = Elaborated->getQualifier()->getAsType()) { if (CheckTemplate(Qualifier->getAs())) { return true; // Case: enable_if< >::type. } } } return false; } AST_MATCHER_P(TemplateTypeParmDecl, hasDefaultArgument, clang::ast_matchers::internal::Matcher, TypeMatcher) { return Node.hasDefaultArgument() && TypeMatcher.matches(Node.getDefaultArgument(), Finder, Builder); } } // namespace void ForwardingReferenceOverloadCheck::registerMatchers(MatchFinder *Finder) { // Forwarding references require C++11 or later. if (!getLangOpts().CPlusPlus11) return; auto ForwardingRefParm = parmVarDecl( hasType(qualType(rValueReferenceType(), references(templateTypeParmType(hasDeclaration( templateTypeParmDecl().bind("type-parm-decl")))), unless(references(isConstQualified()))))) .bind("parm-var"); DeclarationMatcher findOverload = cxxConstructorDecl( hasParameter(0, ForwardingRefParm), unless(hasAnyParameter( // No warning: enable_if as constructor parameter. parmVarDecl(hasType(isEnableIf())))), unless(hasParent(functionTemplateDecl(has(templateTypeParmDecl( // No warning: enable_if as type parameter. hasDefaultArgument(isEnableIf()))))))) .bind("ctor"); Finder->addMatcher(findOverload, this); } void ForwardingReferenceOverloadCheck::check( const MatchFinder::MatchResult &Result) { const auto *ParmVar = Result.Nodes.getNodeAs("parm-var"); const auto *TypeParmDecl = Result.Nodes.getNodeAs("type-parm-decl"); // Get the FunctionDecl and FunctionTemplateDecl containing the function // parameter. const auto *FuncForParam = dyn_cast(ParmVar->getDeclContext()); if (!FuncForParam) return; const FunctionTemplateDecl *FuncTemplate = FuncForParam->getDescribedFunctionTemplate(); if (!FuncTemplate) return; // Check that the template type parameter belongs to the same function // template as the function parameter of that type. (This implies that type // deduction will happen on the type.) const TemplateParameterList *Params = FuncTemplate->getTemplateParameters(); if (std::find(Params->begin(), Params->end(), TypeParmDecl) == Params->end()) return; // Every parameter after the first must have a default value. const auto *Ctor = Result.Nodes.getNodeAs("ctor"); for (auto Iter = Ctor->param_begin() + 1; Iter != Ctor->param_end(); ++Iter) { if (!(*Iter)->hasDefaultArg()) return; } bool EnabledCopy = false, DisabledCopy = false, EnabledMove = false, DisabledMove = false; for (const auto *OtherCtor : Ctor->getParent()->ctors()) { if (OtherCtor->isCopyOrMoveConstructor()) { if (OtherCtor->isDeleted() || OtherCtor->getAccess() == AS_private) (OtherCtor->isCopyConstructor() ? DisabledCopy : DisabledMove) = true; else (OtherCtor->isCopyConstructor() ? EnabledCopy : EnabledMove) = true; } } bool Copy = (!EnabledMove && !DisabledMove && !DisabledCopy) || EnabledCopy; bool Move = !DisabledMove || EnabledMove; if (!Copy && !Move) return; diag(Ctor->getLocation(), "constructor accepting a forwarding reference can " "hide the %select{copy|move|copy and move}0 constructor%s1") << (Copy && Move ? 2 : (Copy ? 0 : 1)) << Copy + Move; for (const auto *OtherCtor : Ctor->getParent()->ctors()) { if (OtherCtor->isCopyOrMoveConstructor() && !OtherCtor->isDeleted() && OtherCtor->getAccess() != AS_private) { diag(OtherCtor->getLocation(), "%select{copy|move}0 constructor declared here", DiagnosticIDs::Note) << OtherCtor->isMoveConstructor(); } } } } // namespace bugprone } // namespace tidy } // namespace clang