| //===--- ForwardingReferenceOverloadCheck.cpp - clang-tidy-----------------===// |
| // |
| // The LLVM Compiler Infrastructure |
| // |
| // This file is distributed under the University of Illinois Open Source |
| // License. See LICENSE.TXT for details. |
| // |
| //===----------------------------------------------------------------------===// |
| |
| #include "ForwardingReferenceOverloadCheck.h" |
| #include "clang/AST/ASTContext.h" |
| #include "clang/ASTMatchers/ASTMatchFinder.h" |
| #include <algorithm> |
| |
| 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<is_integral<T>>). |
| if (const auto *Dependent = BaseType->getAs<DependentNameType>()) { |
| BaseType = Dependent->getQualifier()->getAsType(); |
| } |
| if (!BaseType) |
| return false; |
| if (CheckTemplate(BaseType->getAs<TemplateSpecializationType>())) { |
| return true; // Case: enable_if_t< >. |
| } else if (const auto *Elaborated = BaseType->getAs<ElaboratedType>()) { |
| if (const auto *Qualifier = Elaborated->getQualifier()->getAsType()) { |
| if (CheckTemplate(Qualifier->getAs<TemplateSpecializationType>())) { |
| return true; // Case: enable_if< >::type. |
| } |
| } |
| } |
| return false; |
| } |
| AST_MATCHER_P(TemplateTypeParmDecl, hasDefaultArgument, |
| clang::ast_matchers::internal::Matcher<QualType>, 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<ParmVarDecl>("parm-var"); |
| const auto *TypeParmDecl = |
| Result.Nodes.getNodeAs<TemplateTypeParmDecl>("type-parm-decl"); |
| |
| // Get the FunctionDecl and FunctionTemplateDecl containing the function |
| // parameter. |
| const auto *FuncForParam = dyn_cast<FunctionDecl>(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<CXXConstructorDecl>("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 |