From 9c37e66c315764b4ac0460bc17d04ab60ff728de Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 20 Oct 2016 17:57:33 +0000 Subject: [PATCH] [c++1z] Teach composite pointer type computation how to compute the composite pointer type of two function pointers with different noexcept specifications. While I'm here, also teach it how to merge dynamic exception specifications. llvm-svn: 284753 --- clang/include/clang/Sema/Overload.h | 3 +- clang/include/clang/Sema/Sema.h | 10 +- clang/lib/Sema/SemaExprCXX.cpp | 190 +++++++++++++++++++++++++++++++----- clang/lib/Sema/SemaOverload.cpp | 54 +++++----- clang/test/CXX/expr/p13.cpp | 45 +++++++++ 5 files changed, 246 insertions(+), 56 deletions(-) create mode 100644 clang/test/CXX/expr/p13.cpp diff --git a/clang/include/clang/Sema/Overload.h b/clang/include/clang/Sema/Overload.h index 83c6554..f677afe 100644 --- a/clang/include/clang/Sema/Overload.h +++ b/clang/include/clang/Sema/Overload.h @@ -145,7 +145,8 @@ namespace clang { /// pointer-to-member conversion, or boolean conversion. ImplicitConversionKind Second : 8; - /// Third - The third conversion can be a qualification conversion. + /// Third - The third conversion can be a qualification conversion + /// or a function conversion. ImplicitConversionKind Third : 8; /// \brief Whether this is the deprecated conversion of a diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 1dd415d..08e6a29 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -8954,13 +8954,15 @@ public: ExprResult &cond, ExprResult &lhs, ExprResult &rhs, ExprValueKind &VK, ExprObjectKind &OK, SourceLocation questionLoc); QualType FindCompositePointerType(SourceLocation Loc, Expr *&E1, Expr *&E2, - bool *NonStandardCompositeType = nullptr); + bool *NonStandardCompositeType = nullptr, + bool ConvertArgs = true); QualType FindCompositePointerType(SourceLocation Loc, ExprResult &E1, ExprResult &E2, - bool *NonStandardCompositeType = nullptr) { + bool *NonStandardCompositeType = nullptr, + bool ConvertArgs = true) { Expr *E1Tmp = E1.get(), *E2Tmp = E2.get(); - QualType Composite = FindCompositePointerType(Loc, E1Tmp, E2Tmp, - NonStandardCompositeType); + QualType Composite = FindCompositePointerType( + Loc, E1Tmp, E2Tmp, NonStandardCompositeType, ConvertArgs); E1 = E1Tmp; E2 = E2Tmp; return Composite; diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index b660bd3..ba38049 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -3610,16 +3610,6 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType, // Nothing else to do. break; - case ICK_Function_Conversion: - // If both sides are functions (or pointers/references to them), there could - // be incompatible exception declarations. - if (CheckExceptionSpecCompatibility(From, ToType)) - return ExprError(); - - From = ImpCastExprToType(From, ToType, CK_NoOp, - VK_RValue, /*BasePath=*/nullptr, CCK).get(); - break; - case ICK_Integral_Promotion: case ICK_Integral_Conversion: if (ToType->isBooleanType()) { @@ -3866,6 +3856,7 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType, case ICK_Lvalue_To_Rvalue: case ICK_Array_To_Pointer: case ICK_Function_To_Pointer: + case ICK_Function_Conversion: case ICK_Qualification: case ICK_Num_Conversion_Kinds: case ICK_C_Only_Conversion: @@ -3878,6 +3869,16 @@ Sema::PerformImplicitConversion(Expr *From, QualType ToType, // Nothing to do. break; + case ICK_Function_Conversion: + // If both sides are functions (or pointers/references to them), there could + // be incompatible exception declarations. + if (CheckExceptionSpecCompatibility(From, ToType)) + return ExprError(); + + From = ImpCastExprToType(From, ToType, CK_NoOp, + VK_RValue, /*BasePath=*/nullptr, CCK).get(); + break; + case ICK_Qualification: { // The qualification keeps the category of the inner expression, unless the // target type isn't a reference. @@ -5393,6 +5394,17 @@ QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS, if (LHS.get()->getObjectKind() == OK_BitField || RHS.get()->getObjectKind() == OK_BitField) OK = OK_BitField; + + // If we have function pointer types, unify them anyway to unify their + // exception specifications, if any. + if (LTy->isFunctionPointerType() || LTy->isMemberFunctionPointerType()) { + LTy = FindCompositePointerType(QuestionLoc, LHS, RHS, nullptr, + /*ConvertArgs*/false); + assert(!LTy.isNull() && "failed to find composite pointer type for " + "canonically equivalent function ptr types"); + assert(Context.hasSameType(LTy, RTy) && "bad composite pointer type"); + } + return LTy; } @@ -5447,6 +5459,14 @@ QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS, RHS = RHSCopy; } + // If we have function pointer types, unify them anyway to unify their + // exception specifications, if any. + if (LTy->isFunctionPointerType() || LTy->isMemberFunctionPointerType()) { + LTy = FindCompositePointerType(QuestionLoc, LHS, RHS); + assert(!LTy.isNull() && "failed to find composite pointer type for " + "canonically equivalent function ptr types"); + } + return LTy; } @@ -5517,6 +5537,78 @@ QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS, return QualType(); } +static FunctionProtoType::ExceptionSpecInfo +mergeExceptionSpecs(Sema &S, FunctionProtoType::ExceptionSpecInfo ESI1, + FunctionProtoType::ExceptionSpecInfo ESI2, + SmallVectorImpl &ExceptionTypeStorage) { + ExceptionSpecificationType EST1 = ESI1.Type; + ExceptionSpecificationType EST2 = ESI2.Type; + + // If either of them can throw anything, that is the result. + if (EST1 == EST_None) return ESI1; + if (EST2 == EST_None) return ESI2; + if (EST1 == EST_MSAny) return ESI1; + if (EST2 == EST_MSAny) return ESI2; + + // If either of them is non-throwing, the result is the other. + if (EST1 == EST_DynamicNone) return ESI2; + if (EST2 == EST_DynamicNone) return ESI1; + if (EST1 == EST_BasicNoexcept) return ESI2; + if (EST2 == EST_BasicNoexcept) return ESI1; + + // If either of them is a non-value-dependent computed noexcept, that + // determines the result. + if (EST2 == EST_ComputedNoexcept && ESI2.NoexceptExpr && + !ESI2.NoexceptExpr->isValueDependent()) + return !ESI2.NoexceptExpr->EvaluateKnownConstInt(S.Context) ? ESI2 : ESI1; + if (EST1 == EST_ComputedNoexcept && ESI1.NoexceptExpr && + !ESI1.NoexceptExpr->isValueDependent()) + return !ESI1.NoexceptExpr->EvaluateKnownConstInt(S.Context) ? ESI1 : ESI2; + // If we're left with value-dependent computed noexcept expressions, we're + // stuck. Before C++17, we can just drop the exception specification entirely, + // since it's not actually part of the canonical type. And this should never + // happen in C++17, because it would mean we were computing the composite + // pointer type of dependent types, which should never happen. + if (EST1 == EST_ComputedNoexcept || EST2 == EST_ComputedNoexcept) { + assert(!S.getLangOpts().CPlusPlus1z && + "computing composite pointer type of dependent types"); + return FunctionProtoType::ExceptionSpecInfo(); + } + + // Switch over the possibilities so that people adding new values know to + // update this function. + switch (EST1) { + case EST_None: + case EST_DynamicNone: + case EST_MSAny: + case EST_BasicNoexcept: + case EST_ComputedNoexcept: + llvm_unreachable("handled above"); + + case EST_Dynamic: { + // This is the fun case: both exception specifications are dynamic. Form + // the union of the two lists. + assert(EST2 == EST_Dynamic && "other cases should already be handled"); + llvm::SmallPtrSet Found; + for (auto &Exceptions : {ESI1.Exceptions, ESI2.Exceptions}) + for (QualType E : Exceptions) + if (Found.insert(S.Context.getCanonicalType(E)).second) + ExceptionTypeStorage.push_back(E); + + FunctionProtoType::ExceptionSpecInfo Result(EST_Dynamic); + Result.Exceptions = ExceptionTypeStorage; + return Result; + } + + case EST_Unevaluated: + case EST_Uninstantiated: + case EST_Unparsed: + llvm_unreachable("shouldn't see unresolved exception specifications here"); + } + + llvm_unreachable("invalid ExceptionSpecificationType"); +} + /// \brief Find a merged pointer type and convert the two expressions to it. /// /// This finds the composite pointer type (or member pointer type) for @p E1 @@ -5531,9 +5623,12 @@ QualType Sema::CXXCheckConditionalOperands(ExprResult &Cond, ExprResult &LHS, /// a non-standard (but still sane) composite type to which both expressions /// can be converted. When such a type is chosen, \c *NonStandardCompositeType /// will be set true. +/// +/// \param ConvertArgs If \c false, do not convert E1 and E2 to the target type. QualType Sema::FindCompositePointerType(SourceLocation Loc, Expr *&E1, Expr *&E2, - bool *NonStandardCompositeType) { + bool *NonStandardCompositeType, + bool ConvertArgs) { if (NonStandardCompositeType) *NonStandardCompositeType = false; @@ -5560,16 +5655,18 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc, // - if either p1 or p2 is a null pointer constant, T2 or T1, respectively; if (T1IsPointerLike && E2->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNull)) { - E2 = ImpCastExprToType(E2, T1, T1->isMemberPointerType() - ? CK_NullToMemberPointer - : CK_NullToPointer).get(); + if (ConvertArgs) + E2 = ImpCastExprToType(E2, T1, T1->isMemberPointerType() + ? CK_NullToMemberPointer + : CK_NullToPointer).get(); return T1; } if (T2IsPointerLike && E1->isNullPointerConstant(Context, Expr::NPC_ValueDependentIsNull)) { - E1 = ImpCastExprToType(E1, T2, T2->isMemberPointerType() - ? CK_NullToMemberPointer - : CK_NullToPointer).get(); + if (ConvertArgs) + E1 = ImpCastExprToType(E1, T2, T2->isMemberPointerType() + ? CK_NullToMemberPointer + : CK_NullToPointer).get(); return T2; } @@ -5615,8 +5712,8 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc, // exists. SmallVector QualifierUnion; SmallVector, 4> MemberOfClass; - QualType Composite1 = Context.getCanonicalType(T1); - QualType Composite2 = Context.getCanonicalType(T2); + QualType Composite1 = T1; + QualType Composite2 = T2; unsigned NeedConstBefore = 0; while (true) { const PointerType *Ptr1, *Ptr2; @@ -5662,6 +5759,41 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc, break; } + // Apply the function pointer conversion to unify the types. We've already + // unwrapped down to the function types, and we want to merge rather than + // just convert, so do this ourselves rather than calling + // IsFunctionConversion. + // + // FIXME: In order to match the standard wording as closely as possible, we + // currently only do this under a single level of pointers. Ideally, we would + // allow this in general, and set NeedConstBefore to the relevant depth on + // the side(s) where we changed anything. + if (QualifierUnion.size() == 1) { + if (auto *FPT1 = Composite1->getAs()) { + if (auto *FPT2 = Composite2->getAs()) { + FunctionProtoType::ExtProtoInfo EPI1 = FPT1->getExtProtoInfo(); + FunctionProtoType::ExtProtoInfo EPI2 = FPT2->getExtProtoInfo(); + + // The result is noreturn if both operands are. + bool Noreturn = + EPI1.ExtInfo.getNoReturn() && EPI2.ExtInfo.getNoReturn(); + EPI1.ExtInfo = EPI1.ExtInfo.withNoReturn(Noreturn); + EPI2.ExtInfo = EPI2.ExtInfo.withNoReturn(Noreturn); + + // The result is nothrow if both operands are. + SmallVector ExceptionTypeStorage; + EPI1.ExceptionSpec = EPI2.ExceptionSpec = + mergeExceptionSpecs(*this, EPI1.ExceptionSpec, EPI2.ExceptionSpec, + ExceptionTypeStorage); + + Composite1 = Context.getFunctionType(FPT1->getReturnType(), + FPT1->getParamTypes(), EPI1); + Composite2 = Context.getFunctionType(FPT2->getReturnType(), + FPT2->getParamTypes(), EPI2); + } + } + } + if (NeedConstBefore && NonStandardCompositeType) { // Extension: Add 'const' to qualifiers that come before the first qualifier // mismatch, so that our (non-standard!) composite type meets the @@ -5711,25 +5843,28 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc, E1ToC(S, Entity, Kind, E1), E2ToC(S, Entity, Kind, E2), Viable(E1ToC && E2ToC) {} - QualType perform() { + bool perform() { ExprResult E1Result = E1ToC.Perform(S, Entity, Kind, E1); if (E1Result.isInvalid()) - return QualType(); + return true; E1 = E1Result.getAs(); ExprResult E2Result = E2ToC.Perform(S, Entity, Kind, E2); if (E2Result.isInvalid()) - return QualType(); + return true; E2 = E2Result.getAs(); - return Composite; + return false; } }; // Try to convert to each composite pointer type. Conversion C1(*this, Loc, E1, E2, Composite1); - if (C1.Viable && Context.hasSameType(Composite1, Composite2)) - return C1.perform(); + if (C1.Viable && Context.hasSameType(Composite1, Composite2)) { + if (ConvertArgs && C1.perform()) + return QualType(); + return C1.Composite; + } Conversion C2(*this, Loc, E1, E2, Composite2); if (C1.Viable == C2.Viable) { @@ -5740,7 +5875,10 @@ QualType Sema::FindCompositePointerType(SourceLocation Loc, } // Convert to the chosen type. - return (C1.Viable ? C1 : C2).perform(); + if (ConvertArgs && (C1.Viable ? C1 : C2).perform()) + return QualType(); + + return C1.Viable ? C1.Composite : C2.Composite; } ExprResult Sema::MaybeBindToTemporary(Expr *E) { diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index db97f3d..8cc592e 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -1405,6 +1405,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType, // - a pointer // - a member pointer // - a block pointer + // Changes here need matching changes in FindCompositePointerType. CanQualType CanTo = Context.getCanonicalType(ToType); CanQualType CanFrom = Context.getCanonicalType(FromType); Type::TypeClass TyClass = CanTo->getTypeClass(); @@ -1417,8 +1418,13 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType, CanTo = CanTo.getAs()->getPointeeType(); CanFrom = CanFrom.getAs()->getPointeeType(); } else if (TyClass == Type::MemberPointer) { - CanTo = CanTo.getAs()->getPointeeType(); - CanFrom = CanFrom.getAs()->getPointeeType(); + auto ToMPT = CanTo.getAs(); + auto FromMPT = CanFrom.getAs(); + // A function pointer conversion cannot change the class of the function. + if (ToMPT->getClass() != FromMPT->getClass()) + return false; + CanTo = ToMPT->getPointeeType(); + CanFrom = FromMPT->getPointeeType(); } else { return false; } @@ -1757,10 +1763,6 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType, // Compatible conversions (Clang extension for C function overloading) SCS.Second = ICK_Compatible_Conversion; FromType = ToType.getUnqualifiedType(); - } else if (S.IsFunctionConversion(FromType, ToType, FromType)) { - // Function pointer conversions (removing 'noexcept') including removal of - // 'noreturn' (Clang extension). - SCS.Second = ICK_Function_Conversion; } else if (IsTransparentUnionStandardConversion(S, From, ToType, InOverloadResolution, SCS, CStyle)) { @@ -1782,34 +1784,36 @@ static bool IsStandardConversion(Sema &S, Expr* From, QualType ToType, } SCS.setToType(1, FromType); - QualType CanonFrom; - QualType CanonTo; - // The third conversion can be a qualification conversion (C++ 4p1). + // The third conversion can be a function pointer conversion or a + // qualification conversion (C++ [conv.fctptr], [conv.qual]). bool ObjCLifetimeConversion; - if (S.IsQualificationConversion(FromType, ToType, CStyle, - ObjCLifetimeConversion)) { + if (S.IsFunctionConversion(FromType, ToType, FromType)) { + // Function pointer conversions (removing 'noexcept') including removal of + // 'noreturn' (Clang extension). + SCS.Third = ICK_Function_Conversion; + } else if (S.IsQualificationConversion(FromType, ToType, CStyle, + ObjCLifetimeConversion)) { SCS.Third = ICK_Qualification; SCS.QualificationIncludesObjCLifetime = ObjCLifetimeConversion; FromType = ToType; - CanonFrom = S.Context.getCanonicalType(FromType); - CanonTo = S.Context.getCanonicalType(ToType); } else { // No conversion required SCS.Third = ICK_Identity; + } - // C++ [over.best.ics]p6: - // [...] Any difference in top-level cv-qualification is - // subsumed by the initialization itself and does not constitute - // a conversion. [...] - CanonFrom = S.Context.getCanonicalType(FromType); - CanonTo = S.Context.getCanonicalType(ToType); - if (CanonFrom.getLocalUnqualifiedType() - == CanonTo.getLocalUnqualifiedType() && - CanonFrom.getLocalQualifiers() != CanonTo.getLocalQualifiers()) { - FromType = ToType; - CanonFrom = CanonTo; - } + // C++ [over.best.ics]p6: + // [...] Any difference in top-level cv-qualification is + // subsumed by the initialization itself and does not constitute + // a conversion. [...] + QualType CanonFrom = S.Context.getCanonicalType(FromType); + QualType CanonTo = S.Context.getCanonicalType(ToType); + if (CanonFrom.getLocalUnqualifiedType() + == CanonTo.getLocalUnqualifiedType() && + CanonFrom.getLocalQualifiers() != CanonTo.getLocalQualifiers()) { + FromType = ToType; + CanonFrom = CanonTo; } + SCS.setToType(2, FromType); if (CanonFrom == CanonTo) diff --git a/clang/test/CXX/expr/p13.cpp b/clang/test/CXX/expr/p13.cpp new file mode 100644 index 0000000..b3489fa --- /dev/null +++ b/clang/test/CXX/expr/p13.cpp @@ -0,0 +1,45 @@ +// RUN: %clang_cc1 -std=c++1z -verify %s -fexceptions -fcxx-exceptions + +struct X {}; +struct Y : X {}; + +using A = void (*)() noexcept; +using B = void (*)(); +using C = void (X::*)() noexcept; +using D = void (X::*)(); +using E = void (Y::*)() noexcept; +using F = void (Y::*)(); + +void f(A a, B b, C c, D d, E e, F f, bool k) { + a = k ? a : b; // expected-error {{different exception specifications}} + b = k ? a : b; + + c = k ? c : d; // expected-error {{different exception specifications}} + d = k ? c : d; + + e = k ? c : f; // expected-error {{different exception specifications}} + e = k ? d : e; // expected-error {{different exception specifications}} + f = k ? c : f; + f = k ? d : e; +} + +namespace dynamic_exception_spec { + // Prior to P0012, we had: + // "[...] the target entity shall allow at least the exceptions allowed + // by the source value in the assignment or initialization" + // + // There's really only one way we can coherently apply this to conditional + // expressions: this must hold no matter which branch was taken. + using X = void (*)() throw(int); + using Y = void (*)() throw(float); + using Z = void (*)() throw(int, float); + void g(X x, Y y, Z z, bool k) { + x = k ? X() : Y(); // expected-error {{not superset}} + y = k ? X() : Y(); // expected-error {{not superset}} + z = k ? X() : Y(); + + x = k ? x : y; // expected-error {{not superset}} + y = k ? x : y; // expected-error {{not superset}} + z = k ? x : y; + } +} -- 2.7.4