From eaf11ad70904679593d3c7c100bcbcc395c91a89 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Thu, 3 May 2018 03:58:32 +0000 Subject: [PATCH] Track the result of evaluating a computed noexcept specification on the FunctionProtoType. We previously re-evaluated the expression each time we wanted to know whether the type is noexcept or not. We now evaluate the expression exactly once. This is not quite "no functional change": it fixes a crasher bug during AST deserialization where we would try to evaluate the noexcept specification in a situation where we have not deserialized sufficient portions of the AST to permit such evaluation. llvm-svn: 331428 --- clang/include/clang/AST/Type.h | 71 +++--- clang/include/clang/ASTMatchers/ASTMatchers.h | 2 +- .../clang/Basic/ExceptionSpecificationType.h | 11 +- clang/include/clang/Sema/Sema.h | 9 +- clang/lib/AST/ASTContext.cpp | 48 ++-- clang/lib/AST/ASTStructuralEquivalence.cpp | 2 +- clang/lib/AST/DeclPrinter.cpp | 2 +- clang/lib/AST/ExprCXX.cpp | 4 +- clang/lib/AST/ItaniumMangle.cpp | 4 +- clang/lib/AST/Type.cpp | 77 +++---- clang/lib/AST/TypePrinter.cpp | 4 +- clang/lib/Analysis/CFG.cpp | 2 +- clang/lib/CodeGen/CGCall.cpp | 2 +- clang/lib/CodeGen/CGException.cpp | 14 +- clang/lib/CodeGen/ItaniumCXXABI.cpp | 2 +- clang/lib/Parse/ParseDeclCXX.cpp | 8 +- clang/lib/Sema/AnalysisBasedWarnings.cpp | 2 +- clang/lib/Sema/DeclSpec.cpp | 4 +- clang/lib/Sema/SemaChecking.cpp | 2 +- clang/lib/Sema/SemaCoroutine.cpp | 2 +- clang/lib/Sema/SemaDecl.cpp | 2 +- clang/lib/Sema/SemaDeclCXX.cpp | 77 +++---- clang/lib/Sema/SemaExceptionSpec.cpp | 248 +++++++++------------ clang/lib/Sema/SemaExprCXX.cpp | 26 +-- clang/lib/Sema/SemaOverload.cpp | 6 +- clang/lib/Sema/SemaTemplateDeduction.cpp | 2 +- clang/lib/Sema/SemaTemplateVariadic.cpp | 2 +- clang/lib/Sema/SemaType.cpp | 2 +- clang/lib/Sema/TreeTransform.h | 19 +- clang/lib/Serialization/ASTReader.cpp | 2 +- clang/lib/Serialization/ASTWriter.cpp | 2 +- clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp | 4 +- clang/test/Modules/cxx17-exception-spec.cpp | 34 +++ 33 files changed, 320 insertions(+), 378 deletions(-) create mode 100644 clang/test/Modules/cxx17-exception-spec.cpp diff --git a/clang/include/clang/AST/Type.h b/clang/include/clang/AST/Type.h index 95e4cad..65fdcae 100644 --- a/clang/include/clang/AST/Type.h +++ b/clang/include/clang/AST/Type.h @@ -3446,7 +3446,7 @@ public: /// Explicitly-specified list of exception types. ArrayRef Exceptions; - /// Noexcept expression, if this is EST_ComputedNoexcept. + /// Noexcept expression, if this is a computed noexcept specification. Expr *NoexceptExpr = nullptr; /// The function whose exception specification this is, for @@ -3549,20 +3549,35 @@ private: return reinterpret_cast(ptr); } - size_t getExceptionSpecSize() const { - switch (getExceptionSpecType()) { - case EST_None: return 0; - case EST_DynamicNone: return 0; - case EST_MSAny: return 0; - case EST_BasicNoexcept: return 0; - case EST_Unparsed: return 0; - case EST_Dynamic: return getNumExceptions() * sizeof(QualType); - case EST_ComputedNoexcept: return sizeof(Expr*); - case EST_Uninstantiated: return 2 * sizeof(FunctionDecl*); - case EST_Unevaluated: return sizeof(FunctionDecl*); + static size_t getExceptionSpecSize(ExceptionSpecificationType EST, + unsigned NumExceptions) { + switch (EST) { + case EST_None: + case EST_DynamicNone: + case EST_MSAny: + case EST_BasicNoexcept: + case EST_Unparsed: + return 0; + + case EST_Dynamic: + return NumExceptions * sizeof(QualType); + + case EST_DependentNoexcept: + case EST_NoexceptFalse: + case EST_NoexceptTrue: + return sizeof(Expr *); + + case EST_Uninstantiated: + return 2 * sizeof(FunctionDecl *); + + case EST_Unevaluated: + return sizeof(FunctionDecl *); } llvm_unreachable("bad exception specification kind"); } + size_t getExceptionSpecSize() const { + return getExceptionSpecSize(getExceptionSpecType(), getNumExceptions()); + } public: unsigned getNumParams() const { return NumParams; } @@ -3586,7 +3601,7 @@ public: EPI.RefQualifier = getRefQualifier(); if (EPI.ExceptionSpec.Type == EST_Dynamic) { EPI.ExceptionSpec.Exceptions = exceptions(); - } else if (EPI.ExceptionSpec.Type == EST_ComputedNoexcept) { + } else if (isComputedNoexcept(EPI.ExceptionSpec.Type)) { EPI.ExceptionSpec.NoexceptExpr = getNoexceptExpr(); } else if (EPI.ExceptionSpec.Type == EST_Uninstantiated) { EPI.ExceptionSpec.SourceDecl = getExceptionSpecDecl(); @@ -3626,33 +3641,13 @@ public: /// spec. bool hasInstantiationDependentExceptionSpec() const; - /// Result type of getNoexceptSpec(). - enum NoexceptResult { - /// There is no noexcept specifier. - NR_NoNoexcept, - - /// The noexcept specifier has a bad expression. - NR_BadNoexcept, - - /// The noexcept specifier is dependent. - NR_Dependent, - - /// The noexcept specifier evaluates to false. - NR_Throw, - - /// The noexcept specifier evaluates to true. - NR_Nothrow - }; - - /// Get the meaning of the noexcept spec on this function, if any. - NoexceptResult getNoexceptSpec(const ASTContext &Ctx) const; unsigned getNumExceptions() const { return NumExceptions; } QualType getExceptionType(unsigned i) const { assert(i < NumExceptions && "Invalid exception number!"); return exception_begin()[i]; } Expr *getNoexceptExpr() const { - if (getExceptionSpecType() != EST_ComputedNoexcept) + if (!isComputedNoexcept(getExceptionSpecType())) return nullptr; // NoexceptExpr sits where the arguments end. return *reinterpret_cast(param_type_end()); @@ -3681,14 +3676,14 @@ public: /// Determine whether this function type has a non-throwing exception /// specification. - CanThrowResult canThrow(const ASTContext &Ctx) const; + CanThrowResult canThrow() const; /// Determine whether this function type has a non-throwing exception /// specification. If this depends on template arguments, returns /// \c ResultIfDependent. - bool isNothrow(const ASTContext &Ctx, bool ResultIfDependent = false) const { - return ResultIfDependent ? canThrow(Ctx) != CT_Can - : canThrow(Ctx) == CT_Cannot; + bool isNothrow(bool ResultIfDependent = false) const { + return ResultIfDependent ? canThrow() != CT_Can + : canThrow() == CT_Cannot; } bool isVariadic() const { return Variadic; } diff --git a/clang/include/clang/ASTMatchers/ASTMatchers.h b/clang/include/clang/ASTMatchers/ASTMatchers.h index be9534f..321cec13 100644 --- a/clang/include/clang/ASTMatchers/ASTMatchers.h +++ b/clang/include/clang/ASTMatchers/ASTMatchers.h @@ -3760,7 +3760,7 @@ AST_POLYMORPHIC_MATCHER(isNoThrow, if (isUnresolvedExceptionSpec(FnTy->getExceptionSpecType())) return true; - return FnTy->isNothrow(Finder->getASTContext()); + return FnTy->isNothrow(); } /// \brief Matches constexpr variable and function declarations. diff --git a/clang/include/clang/Basic/ExceptionSpecificationType.h b/clang/include/clang/Basic/ExceptionSpecificationType.h index 132b5ba..ae89f80 100644 --- a/clang/include/clang/Basic/ExceptionSpecificationType.h +++ b/clang/include/clang/Basic/ExceptionSpecificationType.h @@ -24,7 +24,9 @@ enum ExceptionSpecificationType { EST_Dynamic, ///< throw(T1, T2) EST_MSAny, ///< Microsoft throw(...) extension EST_BasicNoexcept, ///< noexcept - EST_ComputedNoexcept, ///< noexcept(expression) + EST_DependentNoexcept,///< noexcept(expression), value-dependent + EST_NoexceptFalse, ///< noexcept(expression), evals to 'false' + EST_NoexceptTrue, ///< noexcept(expression), evals to 'true' EST_Unevaluated, ///< not evaluated yet, for special member function EST_Uninstantiated, ///< not instantiated yet EST_Unparsed ///< not parsed yet @@ -34,8 +36,13 @@ inline bool isDynamicExceptionSpec(ExceptionSpecificationType ESpecType) { return ESpecType >= EST_DynamicNone && ESpecType <= EST_MSAny; } +inline bool isComputedNoexcept(ExceptionSpecificationType ESpecType) { + return ESpecType >= EST_DependentNoexcept && + ESpecType <= EST_NoexceptTrue; +} + inline bool isNoexceptExceptionSpec(ExceptionSpecificationType ESpecType) { - return ESpecType == EST_BasicNoexcept || ESpecType == EST_ComputedNoexcept; + return ESpecType == EST_BasicNoexcept || isComputedNoexcept(ESpecType); } inline bool isUnresolvedExceptionSpec(ExceptionSpecificationType ESpecType) { diff --git a/clang/include/clang/Sema/Sema.h b/clang/include/clang/Sema/Sema.h index 2b4c335..417ac26 100644 --- a/clang/include/clang/Sema/Sema.h +++ b/clang/include/clang/Sema/Sema.h @@ -4716,7 +4716,7 @@ public: /// \brief Get the computed exception specification type. ExceptionSpecificationType getExceptionSpecType() const { - assert(ComputedEST != EST_ComputedNoexcept && + assert(!isComputedNoexcept(ComputedEST) && "noexcept(expr) should not be a possible result"); return ComputedEST; } @@ -4744,7 +4744,7 @@ public: /// C++11 [except.spec]p14: /// The exception-specification is noexcept(false) if the set of /// potential exceptions of the special member function contains "any" - ESI.Type = EST_ComputedNoexcept; + ESI.Type = EST_NoexceptFalse; ESI.NoexceptExpr = Self->ActOnCXXBoolLiteral(SourceLocation(), tok::kw_false).get(); } @@ -4795,6 +4795,11 @@ public: /// special member function. void EvaluateImplicitExceptionSpec(SourceLocation Loc, CXXMethodDecl *MD); + /// Check the given noexcept-specifier, convert its expression, and compute + /// the appropriate ExceptionSpecificationType. + ExprResult ActOnNoexceptSpec(SourceLocation NoexceptLoc, Expr *NoexceptExpr, + ExceptionSpecificationType &EST); + /// \brief Check the given exception-specification and update the /// exception specification information with the results. void checkExceptionSpecification(bool IsTopLevel, diff --git a/clang/lib/AST/ASTContext.cpp b/clang/lib/AST/ASTContext.cpp index d510714..bf79e07 100644 --- a/clang/lib/AST/ASTContext.cpp +++ b/clang/lib/AST/ASTContext.cpp @@ -3420,6 +3420,11 @@ static bool isCanonicalExceptionSpecification( if (ESI.Type == EST_BasicNoexcept) return true; + // A noexcept(expr) specification is (possibly) canonical if expr is + // value-dependent. + if (ESI.Type == EST_DependentNoexcept) + return true; + // A dynamic exception specification is canonical if it only contains pack // expansions (so we can't tell whether it's non-throwing) and all its // contained types are canonical. @@ -3434,11 +3439,6 @@ static bool isCanonicalExceptionSpecification( return AnyPackExpansions; } - // A noexcept(expr) specification is (possibly) canonical if expr is - // value-dependent. - if (ESI.Type == EST_ComputedNoexcept) - return ESI.NoexceptExpr && ESI.NoexceptExpr->isValueDependent(); - return false; } @@ -3466,7 +3466,7 @@ QualType ASTContext::getFunctionTypeInternal( // noexcept expression, or we're just looking for a canonical type. // Otherwise, we're going to need to create a type // sugar node to hold the concrete expression. - if (OnlyWantCanonical || EPI.ExceptionSpec.Type != EST_ComputedNoexcept || + if (OnlyWantCanonical || !isComputedNoexcept(EPI.ExceptionSpec.Type) || EPI.ExceptionSpec.NoexceptExpr == FPT->getNoexceptExpr()) return Existing; @@ -3513,7 +3513,7 @@ QualType ASTContext::getFunctionTypeInternal( // We don't know yet. It shouldn't matter what we pick here; no-one // should ever look at this. LLVM_FALLTHROUGH; - case EST_None: case EST_MSAny: + case EST_None: case EST_MSAny: case EST_NoexceptFalse: CanonicalEPI.ExceptionSpec.Type = EST_None; break; @@ -3535,24 +3535,12 @@ QualType ASTContext::getFunctionTypeInternal( break; } - case EST_DynamicNone: case EST_BasicNoexcept: + case EST_DynamicNone: case EST_BasicNoexcept: case EST_NoexceptTrue: CanonicalEPI.ExceptionSpec.Type = EST_BasicNoexcept; break; - case EST_ComputedNoexcept: - llvm::APSInt Value(1); - auto *E = CanonicalEPI.ExceptionSpec.NoexceptExpr; - if (!E || !E->isIntegerConstantExpr(Value, *this, nullptr, - /*IsEvaluated*/false)) { - // This noexcept specification is invalid. - // FIXME: Should this be able to happen? - CanonicalEPI.ExceptionSpec.Type = EST_None; - break; - } - - CanonicalEPI.ExceptionSpec.Type = - Value.getBoolValue() ? EST_BasicNoexcept : EST_None; - break; + case EST_DependentNoexcept: + llvm_unreachable("dependent noexcept is already canonical"); } } else { CanonicalEPI.ExceptionSpec = FunctionProtoType::ExceptionSpecInfo(); @@ -3577,18 +3565,10 @@ QualType ASTContext::getFunctionTypeInternal( // Instead of the exception types, there could be a noexcept // expression, or information used to resolve the exception // specification. - size_t Size = sizeof(FunctionProtoType) + - NumArgs * sizeof(QualType); - - if (EPI.ExceptionSpec.Type == EST_Dynamic) { - Size += EPI.ExceptionSpec.Exceptions.size() * sizeof(QualType); - } else if (EPI.ExceptionSpec.Type == EST_ComputedNoexcept) { - Size += sizeof(Expr*); - } else if (EPI.ExceptionSpec.Type == EST_Uninstantiated) { - Size += 2 * sizeof(FunctionDecl*); - } else if (EPI.ExceptionSpec.Type == EST_Unevaluated) { - Size += sizeof(FunctionDecl*); - } + size_t Size = + sizeof(FunctionProtoType) + NumArgs * sizeof(QualType) + + FunctionProtoType::getExceptionSpecSize( + EPI.ExceptionSpec.Type, EPI.ExceptionSpec.Exceptions.size()); // Put the ExtParameterInfos last. If all were equal, it would make // more sense to put these before the exception specification, because diff --git a/clang/lib/AST/ASTStructuralEquivalence.cpp b/clang/lib/AST/ASTStructuralEquivalence.cpp index 05f414e..b543991 100644 --- a/clang/lib/AST/ASTStructuralEquivalence.cpp +++ b/clang/lib/AST/ASTStructuralEquivalence.cpp @@ -441,7 +441,7 @@ static bool IsStructurallyEquivalent(StructuralEquivalenceContext &Context, Proto2->getExceptionType(I))) return false; } - } else if (Proto1->getExceptionSpecType() == EST_ComputedNoexcept) { + } else if (isComputedNoexcept(Proto1->getExceptionSpecType())) { if (!IsStructurallyEquivalent(Context, Proto1->getNoexceptExpr(), Proto2->getNoexceptExpr())) return false; diff --git a/clang/lib/AST/DeclPrinter.cpp b/clang/lib/AST/DeclPrinter.cpp index b2c5303..c6c9d85 100644 --- a/clang/lib/AST/DeclPrinter.cpp +++ b/clang/lib/AST/DeclPrinter.cpp @@ -672,7 +672,7 @@ void DeclPrinter::VisitFunctionDecl(FunctionDecl *D) { Proto += ")"; } else if (FT && isNoexceptExceptionSpec(FT->getExceptionSpecType())) { Proto += " noexcept"; - if (FT->getExceptionSpecType() == EST_ComputedNoexcept) { + if (isComputedNoexcept(FT->getExceptionSpecType())) { Proto += "("; llvm::raw_string_ostream EOut(Proto); FT->getNoexceptExpr()->printPretty(EOut, nullptr, SubPolicy, diff --git a/clang/lib/AST/ExprCXX.cpp b/clang/lib/AST/ExprCXX.cpp index eb2e8c5..3a204c2 100644 --- a/clang/lib/AST/ExprCXX.cpp +++ b/clang/lib/AST/ExprCXX.cpp @@ -169,8 +169,8 @@ void CXXNewExpr::AllocateArgsArray(const ASTContext &C, bool isArray, } bool CXXNewExpr::shouldNullCheckAllocation(const ASTContext &Ctx) const { - return getOperatorNew()->getType()->castAs()->isNothrow( - Ctx) && + return getOperatorNew()->getType()->castAs() + ->isNothrow() && !getOperatorNew()->isReservedGlobalPlacementOperator(); } diff --git a/clang/lib/AST/ItaniumMangle.cpp b/clang/lib/AST/ItaniumMangle.cpp index 610400d..1a656b57 100644 --- a/clang/lib/AST/ItaniumMangle.cpp +++ b/clang/lib/AST/ItaniumMangle.cpp @@ -2702,7 +2702,7 @@ void CXXNameMangler::mangleType(const FunctionProtoType *T) { // Mangle instantiation-dependent exception-specification, if present, // per cxx-abi-dev proposal on 2016-10-11. if (T->hasInstantiationDependentExceptionSpec()) { - if (T->getExceptionSpecType() == EST_ComputedNoexcept) { + if (isComputedNoexcept(T->getExceptionSpecType())) { Out << "DO"; mangleExpression(T->getNoexceptExpr()); Out << "E"; @@ -2713,7 +2713,7 @@ void CXXNameMangler::mangleType(const FunctionProtoType *T) { mangleType(ExceptTy); Out << "E"; } - } else if (T->isNothrow(getASTContext())) { + } else if (T->isNothrow()) { Out << "Do"; } diff --git a/clang/lib/AST/Type.cpp b/clang/lib/AST/Type.cpp index 571dc2a..93c3f63 100644 --- a/clang/lib/AST/Type.cpp +++ b/clang/lib/AST/Type.cpp @@ -2800,19 +2800,21 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef params, exnSlot[I++] = ExceptionType; } - } else if (getExceptionSpecType() == EST_ComputedNoexcept) { + } else if (isComputedNoexcept(getExceptionSpecType())) { + assert(epi.ExceptionSpec.NoexceptExpr && "computed noexcept with no expr"); + assert((getExceptionSpecType() == EST_DependentNoexcept) == + epi.ExceptionSpec.NoexceptExpr->isValueDependent()); + // Store the noexcept expression and context. auto **noexSlot = reinterpret_cast(argSlot + NumParams); *noexSlot = epi.ExceptionSpec.NoexceptExpr; - if (epi.ExceptionSpec.NoexceptExpr) { - if (epi.ExceptionSpec.NoexceptExpr->isValueDependent() || - epi.ExceptionSpec.NoexceptExpr->isInstantiationDependent()) - setInstantiationDependent(); + if (epi.ExceptionSpec.NoexceptExpr->isValueDependent() || + epi.ExceptionSpec.NoexceptExpr->isInstantiationDependent()) + setInstantiationDependent(); - if (epi.ExceptionSpec.NoexceptExpr->containsUnexpandedParameterPack()) - setContainsUnexpandedParameterPack(); - } + if (epi.ExceptionSpec.NoexceptExpr->containsUnexpandedParameterPack()) + setContainsUnexpandedParameterPack(); } else if (getExceptionSpecType() == EST_Uninstantiated) { // Store the function decl from which we will resolve our // exception specification. @@ -2832,7 +2834,7 @@ FunctionProtoType::FunctionProtoType(QualType result, ArrayRef params, // then it's a dependent type. This only happens in C++17 onwards. if (isCanonicalUnqualified()) { if (getExceptionSpecType() == EST_Dynamic || - getExceptionSpecType() == EST_ComputedNoexcept) { + getExceptionSpecType() == EST_DependentNoexcept) { assert(hasDependentExceptionSpec() && "type should not be canonical"); setDependent(); } @@ -2870,52 +2872,36 @@ bool FunctionProtoType::hasInstantiationDependentExceptionSpec() const { return false; } -FunctionProtoType::NoexceptResult -FunctionProtoType::getNoexceptSpec(const ASTContext &ctx) const { - ExceptionSpecificationType est = getExceptionSpecType(); - if (est == EST_BasicNoexcept) - return NR_Nothrow; - - if (est != EST_ComputedNoexcept) - return NR_NoNoexcept; - - Expr *noexceptExpr = getNoexceptExpr(); - if (!noexceptExpr) - return NR_BadNoexcept; - if (noexceptExpr->isValueDependent()) - return NR_Dependent; - - llvm::APSInt value; - bool isICE = noexceptExpr->isIntegerConstantExpr(value, ctx, nullptr, - /*evaluated*/false); - (void)isICE; - assert(isICE && "AST should not contain bad noexcept expressions."); +CanThrowResult FunctionProtoType::canThrow() const { + switch (getExceptionSpecType()) { + case EST_Unparsed: + case EST_Unevaluated: + case EST_Uninstantiated: + llvm_unreachable("should not call this with unresolved exception specs"); - return value.getBoolValue() ? NR_Nothrow : NR_Throw; -} - -CanThrowResult FunctionProtoType::canThrow(const ASTContext &Ctx) const { - ExceptionSpecificationType EST = getExceptionSpecType(); - assert(EST != EST_Unevaluated && EST != EST_Uninstantiated); - if (EST == EST_DynamicNone || EST == EST_BasicNoexcept) + case EST_DynamicNone: + case EST_BasicNoexcept: + case EST_NoexceptTrue: return CT_Cannot; - if (EST == EST_Dynamic) { + case EST_None: + case EST_MSAny: + case EST_NoexceptFalse: + return CT_Can; + + case EST_Dynamic: // A dynamic exception specification is throwing unless every exception // type is an (unexpanded) pack expansion type. for (unsigned I = 0, N = NumExceptions; I != N; ++I) if (!getExceptionType(I)->getAs()) return CT_Can; return CT_Dependent; - } - if (EST != EST_ComputedNoexcept) - return CT_Can; - - NoexceptResult NR = getNoexceptSpec(Ctx); - if (NR == NR_Dependent) + case EST_DependentNoexcept: return CT_Dependent; - return NR == NR_Nothrow ? CT_Cannot : CT_Can; + } + + llvm_unreachable("unexpected exception specification kind"); } bool FunctionProtoType::isTemplateVariadic() const { @@ -2965,8 +2951,7 @@ void FunctionProtoType::Profile(llvm::FoldingSetNodeID &ID, QualType Result, if (epi.ExceptionSpec.Type == EST_Dynamic) { for (QualType Ex : epi.ExceptionSpec.Exceptions) ID.AddPointer(Ex.getAsOpaquePtr()); - } else if (epi.ExceptionSpec.Type == EST_ComputedNoexcept && - epi.ExceptionSpec.NoexceptExpr) { + } else if (isComputedNoexcept(epi.ExceptionSpec.Type)) { epi.ExceptionSpec.NoexceptExpr->Profile(ID, Context, Canonical); } else if (epi.ExceptionSpec.Type == EST_Uninstantiated || epi.ExceptionSpec.Type == EST_Unevaluated) { diff --git a/clang/lib/AST/TypePrinter.cpp b/clang/lib/AST/TypePrinter.cpp index c99148e..c278dc9 100644 --- a/clang/lib/AST/TypePrinter.cpp +++ b/clang/lib/AST/TypePrinter.cpp @@ -672,7 +672,9 @@ FunctionProtoType::printExceptionSpecification(raw_ostream &OS, OS << ')'; } else if (isNoexceptExceptionSpec(getExceptionSpecType())) { OS << " noexcept"; - if (getExceptionSpecType() == EST_ComputedNoexcept) { + // FIXME:Is it useful to print out the expression for a non-dependent + // noexcept specification? + if (isComputedNoexcept(getExceptionSpecType())) { OS << '('; if (getNoexceptExpr()) getNoexceptExpr()->printPretty(OS, nullptr, Policy); diff --git a/clang/lib/Analysis/CFG.cpp b/clang/lib/Analysis/CFG.cpp index 8a5c2e1..f53fc0f 100644 --- a/clang/lib/Analysis/CFG.cpp +++ b/clang/lib/Analysis/CFG.cpp @@ -2350,7 +2350,7 @@ static bool CanThrow(Expr *E, ASTContext &Ctx) { if (FT) { if (const FunctionProtoType *Proto = dyn_cast(FT)) if (!isUnresolvedExceptionSpec(Proto->getExceptionSpecType()) && - Proto->isNothrow(Ctx)) + Proto->isNothrow()) return false; } return true; diff --git a/clang/lib/CodeGen/CGCall.cpp b/clang/lib/CodeGen/CGCall.cpp index 11bfff1..4c3c083 100644 --- a/clang/lib/CodeGen/CGCall.cpp +++ b/clang/lib/CodeGen/CGCall.cpp @@ -1679,7 +1679,7 @@ static void AddAttributesFromFunctionProtoType(ASTContext &Ctx, return; if (!isUnresolvedExceptionSpec(FPT->getExceptionSpecType()) && - FPT->isNothrow(Ctx)) + FPT->isNothrow()) FuncAttrs.addAttribute(llvm::Attribute::NoUnwind); } diff --git a/clang/lib/CodeGen/CGException.cpp b/clang/lib/CodeGen/CGException.cpp index 2be3e25..b37ecaa 100644 --- a/clang/lib/CodeGen/CGException.cpp +++ b/clang/lib/CodeGen/CGException.cpp @@ -442,11 +442,9 @@ void CodeGenFunction::EmitStartEHSpec(const Decl *D) { return; ExceptionSpecificationType EST = Proto->getExceptionSpecType(); - if (isNoexceptExceptionSpec(EST)) { - if (Proto->getNoexceptSpec(getContext()) == FunctionProtoType::NR_Nothrow) { - // noexcept functions are simple terminate scopes. - EHStack.pushTerminate(); - } + if (isNoexceptExceptionSpec(EST) && Proto->canThrow() == CT_Cannot) { + // noexcept functions are simple terminate scopes. + EHStack.pushTerminate(); } else if (EST == EST_Dynamic || EST == EST_DynamicNone) { // TODO: Revisit exception specifications for the MS ABI. There is a way to // encode these in an object file but MSVC doesn't do anything with it. @@ -521,10 +519,8 @@ void CodeGenFunction::EmitEndEHSpec(const Decl *D) { return; ExceptionSpecificationType EST = Proto->getExceptionSpecType(); - if (isNoexceptExceptionSpec(EST)) { - if (Proto->getNoexceptSpec(getContext()) == FunctionProtoType::NR_Nothrow) { - EHStack.popTerminate(); - } + if (isNoexceptExceptionSpec(EST) && Proto->canThrow() == CT_Cannot) { + EHStack.popTerminate(); } else if (EST == EST_Dynamic || EST == EST_DynamicNone) { // TODO: Revisit exception specifications for the MS ABI. There is a way to // encode these in an object file but MSVC doesn't do anything with it. diff --git a/clang/lib/CodeGen/ItaniumCXXABI.cpp b/clang/lib/CodeGen/ItaniumCXXABI.cpp index 0e35633..bfabdf0 100644 --- a/clang/lib/CodeGen/ItaniumCXXABI.cpp +++ b/clang/lib/CodeGen/ItaniumCXXABI.cpp @@ -3476,7 +3476,7 @@ static unsigned extractPBaseFlags(ASTContext &Ctx, QualType &Type) { Flags |= ItaniumRTTIBuilder::PTI_Incomplete; if (auto *Proto = Type->getAs()) { - if (Proto->isNothrow(Ctx)) { + if (Proto->isNothrow()) { Flags |= ItaniumRTTIBuilder::PTI_Noexcept; Type = Ctx.getFunctionTypeWithExceptionSpec(Type, EST_None); } diff --git a/clang/lib/Parse/ParseDeclCXX.cpp b/clang/lib/Parse/ParseDeclCXX.cpp index ec1177a..1b0f341 100644 --- a/clang/lib/Parse/ParseDeclCXX.cpp +++ b/clang/lib/Parse/ParseDeclCXX.cpp @@ -3586,15 +3586,11 @@ Parser::tryParseExceptionSpecification(bool Delayed, // There is an argument. BalancedDelimiterTracker T(*this, tok::l_paren); T.consumeOpen(); - NoexceptType = EST_ComputedNoexcept; NoexceptExpr = ParseConstantExpression(); T.consumeClose(); - // The argument must be contextually convertible to bool. We use - // CheckBooleanCondition for this purpose. - // FIXME: Add a proper Sema entry point for this. if (!NoexceptExpr.isInvalid()) { - NoexceptExpr = - Actions.CheckBooleanCondition(KeywordLoc, NoexceptExpr.get()); + NoexceptExpr = Actions.ActOnNoexceptSpec(KeywordLoc, NoexceptExpr.get(), + NoexceptType); NoexceptRange = SourceRange(KeywordLoc, T.getCloseLocation()); } else { NoexceptType = EST_BasicNoexcept; diff --git a/clang/lib/Sema/AnalysisBasedWarnings.cpp b/clang/lib/Sema/AnalysisBasedWarnings.cpp index 2903f53..9842397 100644 --- a/clang/lib/Sema/AnalysisBasedWarnings.cpp +++ b/clang/lib/Sema/AnalysisBasedWarnings.cpp @@ -351,7 +351,7 @@ static void checkThrowInNonThrowingFunc(Sema &S, const FunctionDecl *FD, static bool isNoexcept(const FunctionDecl *FD) { const auto *FPT = FD->getType()->castAs(); - if (FPT->isNothrow(FD->getASTContext()) || FD->hasAttr()) + if (FPT->isNothrow() || FD->hasAttr()) return true; return false; } diff --git a/clang/lib/Sema/DeclSpec.cpp b/clang/lib/Sema/DeclSpec.cpp index 26d6202..dd2476e 100644 --- a/clang/lib/Sema/DeclSpec.cpp +++ b/clang/lib/Sema/DeclSpec.cpp @@ -251,7 +251,9 @@ DeclaratorChunk DeclaratorChunk::getFunction(bool hasProto, } break; - case EST_ComputedNoexcept: + case EST_DependentNoexcept: + case EST_NoexceptFalse: + case EST_NoexceptTrue: I.Fun.NoexceptExpr = NoexceptExpr; break; diff --git a/clang/lib/Sema/SemaChecking.cpp b/clang/lib/Sema/SemaChecking.cpp index 7b38612..b52ff97 100644 --- a/clang/lib/Sema/SemaChecking.cpp +++ b/clang/lib/Sema/SemaChecking.cpp @@ -8322,7 +8322,7 @@ Sema::CheckReturnValExpr(Expr *RetValExp, QualType lhsType, if (Op == OO_New || Op == OO_Array_New) { const FunctionProtoType *Proto = FD->getType()->castAs(); - if (!Proto->isNothrow(Context, /*ResultIfDependent*/true) && + if (!Proto->isNothrow(/*ResultIfDependent*/true) && CheckNonNullExpr(*this, RetValExp)) Diag(ReturnLoc, diag::warn_operator_new_returns_null) << FD << getLangOpts().CPlusPlus11; diff --git a/clang/lib/Sema/SemaCoroutine.cpp b/clang/lib/Sema/SemaCoroutine.cpp index 0d2d847..36d5523 100644 --- a/clang/lib/Sema/SemaCoroutine.cpp +++ b/clang/lib/Sema/SemaCoroutine.cpp @@ -1160,7 +1160,7 @@ bool CoroutineStmtBuilder::makeNewAndDeleteExpr() { if (RequiresNoThrowAlloc) { const auto *FT = OperatorNew->getType()->getAs(); - if (!FT->isNothrow(S.Context, /*ResultIfDependent*/ false)) { + if (!FT->isNothrow(/*ResultIfDependent*/ false)) { S.Diag(OperatorNew->getLocation(), diag::err_coroutine_promise_new_requires_nothrow) << OperatorNew; diff --git a/clang/lib/Sema/SemaDecl.cpp b/clang/lib/Sema/SemaDecl.cpp index 732853d..62f6583 100644 --- a/clang/lib/Sema/SemaDecl.cpp +++ b/clang/lib/Sema/SemaDecl.cpp @@ -9848,7 +9848,7 @@ bool Sema::CheckFunctionDeclaration(Scope *S, FunctionDecl *NewFD, else if (auto *MPT = T->getAs()) T = MPT->getPointeeType(); if (auto *FPT = T->getAs()) - if (FPT->isNothrow(Context)) + if (FPT->isNothrow()) return true; return false; }; diff --git a/clang/lib/Sema/SemaDeclCXX.cpp b/clang/lib/Sema/SemaDeclCXX.cpp index 4339c9a..9126f27 100644 --- a/clang/lib/Sema/SemaDeclCXX.cpp +++ b/clang/lib/Sema/SemaDeclCXX.cpp @@ -170,43 +170,40 @@ Sema::ImplicitExceptionSpecification::CalledDecl(SourceLocation CallLoc, if (EST == EST_None && Method->hasAttr()) EST = EST_BasicNoexcept; - switch(EST) { + switch (EST) { + case EST_Unparsed: + case EST_Uninstantiated: + case EST_Unevaluated: + llvm_unreachable("should not see unresolved exception specs here"); + // If this function can throw any exceptions, make a note of that. case EST_MSAny: case EST_None: + // FIXME: Whichever we see last of MSAny and None determines our result. + // We should make a consistent, order-independent choice here. ClearExceptions(); ComputedEST = EST; return; + case EST_NoexceptFalse: + ClearExceptions(); + ComputedEST = EST_None; + return; // FIXME: If the call to this decl is using any of its default arguments, we // need to search them for potentially-throwing calls. // If this function has a basic noexcept, it doesn't affect the outcome. case EST_BasicNoexcept: + case EST_NoexceptTrue: return; - // If we're still at noexcept(true) and there's a nothrow() callee, + // If we're still at noexcept(true) and there's a throw() callee, // change to that specification. case EST_DynamicNone: if (ComputedEST == EST_BasicNoexcept) ComputedEST = EST_DynamicNone; return; - // Check out noexcept specs. - case EST_ComputedNoexcept: - { - FunctionProtoType::NoexceptResult NR = - Proto->getNoexceptSpec(Self->Context); - assert(NR != FunctionProtoType::NR_NoNoexcept && - "Must have noexcept result for EST_ComputedNoexcept."); - assert(NR != FunctionProtoType::NR_Dependent && - "Should not generate implicit declarations for dependent cases, " - "and don't know how to handle them anyway."); - // noexcept(false) -> no spec on the new function - if (NR == FunctionProtoType::NR_Throw) { - ClearExceptions(); - ComputedEST = EST_None; - } - // noexcept(true) won't change anything either. - return; - } - default: + case EST_DependentNoexcept: + llvm_unreachable( + "should not generate implicit declarations for dependent cases"); + case EST_Dynamic: break; } assert(EST == EST_Dynamic && "EST case not considered earlier."); @@ -15040,7 +15037,9 @@ bool Sema::checkThisInStaticMemberFunctionExceptionSpec(CXXMethodDecl *Method) { case EST_None: break; - case EST_ComputedNoexcept: + case EST_DependentNoexcept: + case EST_NoexceptFalse: + case EST_NoexceptTrue: if (!Finder.TraverseStmt(Proto->getNoexceptExpr())) return true; LLVM_FALLTHROUGH; @@ -15137,31 +15136,17 @@ void Sema::checkExceptionSpecification( return; } - if (EST == EST_ComputedNoexcept) { - // If an error occurred, there's no expression here. - if (NoexceptExpr) { - assert((NoexceptExpr->isTypeDependent() || - NoexceptExpr->getType()->getCanonicalTypeUnqualified() == - Context.BoolTy) && - "Parser should have made sure that the expression is boolean"); - if (IsTopLevel && NoexceptExpr && - DiagnoseUnexpandedParameterPack(NoexceptExpr)) { - ESI.Type = EST_BasicNoexcept; - return; - } - - if (!NoexceptExpr->isValueDependent()) { - ExprResult Result = VerifyIntegerConstantExpression( - NoexceptExpr, nullptr, diag::err_noexcept_needs_constant_expression, - /*AllowFold*/ false); - if (Result.isInvalid()) { - ESI.Type = EST_BasicNoexcept; - return; - } - NoexceptExpr = Result.get(); - } - ESI.NoexceptExpr = NoexceptExpr; + if (isComputedNoexcept(EST)) { + assert((NoexceptExpr->isTypeDependent() || + NoexceptExpr->getType()->getCanonicalTypeUnqualified() == + Context.BoolTy) && + "Parser should have made sure that the expression is boolean"); + if (IsTopLevel && DiagnoseUnexpandedParameterPack(NoexceptExpr)) { + ESI.Type = EST_BasicNoexcept; + return; } + + ESI.NoexceptExpr = NoexceptExpr; return; } } diff --git a/clang/lib/Sema/SemaExceptionSpec.cpp b/clang/lib/Sema/SemaExceptionSpec.cpp index 21ca08f..a81c17b 100644 --- a/clang/lib/Sema/SemaExceptionSpec.cpp +++ b/clang/lib/Sema/SemaExceptionSpec.cpp @@ -76,6 +76,29 @@ bool Sema::isLibstdcxxEagerExceptionSpecHack(const Declarator &D) { .Default(false); } +ExprResult Sema::ActOnNoexceptSpec(SourceLocation NoexceptLoc, + Expr *NoexceptExpr, + ExceptionSpecificationType &EST) { + // FIXME: This is bogus, a noexcept expression is not a condition. + ExprResult Converted = CheckBooleanCondition(NoexceptLoc, NoexceptExpr); + if (Converted.isInvalid()) + return Converted; + + if (Converted.get()->isValueDependent()) { + EST = EST_DependentNoexcept; + return Converted; + } + + llvm::APSInt Result; + Converted = VerifyIntegerConstantExpression( + Converted.get(), &Result, + diag::err_noexcept_needs_constant_expression, + /*AllowFold*/ false); + if (!Converted.isInvalid()) + EST = !Result ? EST_NoexceptFalse : EST_NoexceptTrue; + return Converted; +} + /// CheckSpecifiedExceptionType - Check if the given type is valid in an /// exception specification. Incomplete types, or pointers to incomplete types /// other than void are not allowed. @@ -309,13 +332,19 @@ bool Sema::CheckEquivalentExceptionSpec(FunctionDecl *Old, FunctionDecl *New) { FunctionProtoType::ExceptionSpecInfo ESI = OldProto->getExceptionSpecType(); if (ESI.Type == EST_Dynamic) { + // FIXME: What if the exceptions are described in terms of the old + // prototype's parameters? ESI.Exceptions = OldProto->exceptions(); } - if (ESI.Type == EST_ComputedNoexcept) { - // For computed noexcept, we can't just take the expression from the old - // prototype. It likely contains references to the old prototype's - // parameters. + if (ESI.Type == EST_NoexceptFalse) + ESI.Type = EST_None; + if (ESI.Type == EST_NoexceptTrue) + ESI.Type = EST_BasicNoexcept; + + // For dependent noexcept, we can't just take the expression from the old + // prototype. It likely contains references to the old prototype's parameters. + if (ESI.Type == EST_DependentNoexcept) { New->setInvalidDecl(); } else { // Update the type of the function with the appropriate exception @@ -325,12 +354,12 @@ bool Sema::CheckEquivalentExceptionSpec(FunctionDecl *Old, FunctionDecl *New) { NewProto->getExtProtoInfo().withExceptionSpec(ESI))); } - if (getLangOpts().MicrosoftExt && ESI.Type != EST_ComputedNoexcept) { + if (getLangOpts().MicrosoftExt && ESI.Type != EST_DependentNoexcept) { // Allow missing exception specifications in redeclarations as an extension. DiagID = diag::ext_ms_missing_exception_specification; ReturnValueOnError = false; } else if (New->isReplaceableGlobalAllocationFunction() && - ESI.Type != EST_ComputedNoexcept) { + ESI.Type != EST_DependentNoexcept) { // Allow missing exception specifications in redeclarations as an extension, // when declaring a replaceable global allocation function. DiagID = diag::ext_missing_exception_specification; @@ -367,7 +396,9 @@ bool Sema::CheckEquivalentExceptionSpec(FunctionDecl *Old, FunctionDecl *New) { OS << "noexcept"; break; - case EST_ComputedNoexcept: + case EST_DependentNoexcept: + case EST_NoexceptFalse: + case EST_NoexceptTrue: OS << "noexcept("; assert(OldProto->getNoexceptExpr() != nullptr && "Expected non-null Expr"); OldProto->getNoexceptExpr()->printPretty(OS, nullptr, getPrintingPolicy()); @@ -478,63 +509,56 @@ static bool CheckEquivalentExceptionSpecImpl( !isUnresolvedExceptionSpec(NewEST) && "Shouldn't see unknown exception specifications here"); - // Shortcut the case where both have no spec. - if (OldEST == EST_None && NewEST == EST_None) - return false; + CanThrowResult OldCanThrow = Old->canThrow(); + CanThrowResult NewCanThrow = New->canThrow(); - FunctionProtoType::NoexceptResult OldNR = Old->getNoexceptSpec(S.Context); - FunctionProtoType::NoexceptResult NewNR = New->getNoexceptSpec(S.Context); - if (OldNR == FunctionProtoType::NR_BadNoexcept || - NewNR == FunctionProtoType::NR_BadNoexcept) + // Any non-throwing specifications are compatible. + if (OldCanThrow == CT_Cannot && NewCanThrow == CT_Cannot) return false; - // Dependent noexcept specifiers are compatible with each other, but nothing - // else. - // One noexcept is compatible with another if the argument is the same - if (OldNR == NewNR && - OldNR != FunctionProtoType::NR_NoNoexcept && - NewNR != FunctionProtoType::NR_NoNoexcept) - return false; - if (OldNR != NewNR && - OldNR != FunctionProtoType::NR_NoNoexcept && - NewNR != FunctionProtoType::NR_NoNoexcept) { - S.Diag(NewLoc, DiagID); - if (NoteID.getDiagID() != 0 && OldLoc.isValid()) - S.Diag(OldLoc, NoteID); - return true; + // Any throws-anything specifications are usually compatible. + if (OldCanThrow == CT_Can && OldEST != EST_Dynamic && + NewCanThrow == CT_Can && NewEST != EST_Dynamic) { + // The exception is that the absence of an exception specification only + // matches noexcept(false) for functions, as described above. + if (!AllowNoexceptAllMatchWithNoSpec && + ((OldEST == EST_None && NewEST == EST_NoexceptFalse) || + (OldEST == EST_NoexceptFalse && NewEST == EST_None))) { + // This is the disallowed case. + } else { + return false; + } } - // The MS extension throw(...) is compatible with itself. - if (OldEST == EST_MSAny && NewEST == EST_MSAny) + // FIXME: We treat dependent noexcept specifications as compatible even if + // their expressions are not equivalent. + if (OldEST == EST_DependentNoexcept && NewEST == EST_DependentNoexcept) return false; - // It's also compatible with no spec. - if ((OldEST == EST_None && NewEST == EST_MSAny) || - (OldEST == EST_MSAny && NewEST == EST_None)) - return false; - - // It's also compatible with noexcept(false). - if (OldEST == EST_MSAny && NewNR == FunctionProtoType::NR_Throw) - return false; - if (NewEST == EST_MSAny && OldNR == FunctionProtoType::NR_Throw) - return false; + // Dynamic exception specifications with the same set of adjusted types + // are compatible. + if (OldEST == EST_Dynamic && NewEST == EST_Dynamic) { + bool Success = true; + // Both have a dynamic exception spec. Collect the first set, then compare + // to the second. + llvm::SmallPtrSet OldTypes, NewTypes; + for (const auto &I : Old->exceptions()) + OldTypes.insert(S.Context.getCanonicalType(I).getUnqualifiedType()); + + for (const auto &I : New->exceptions()) { + CanQualType TypePtr = S.Context.getCanonicalType(I).getUnqualifiedType(); + if (OldTypes.count(TypePtr)) + NewTypes.insert(TypePtr); + else { + Success = false; + break; + } + } - // As described above, noexcept(false) matches no spec only for functions. - if (AllowNoexceptAllMatchWithNoSpec) { - if (OldEST == EST_None && NewNR == FunctionProtoType::NR_Throw) - return false; - if (NewEST == EST_None && OldNR == FunctionProtoType::NR_Throw) + if (Success && OldTypes.size() == NewTypes.size()) return false; } - // Any non-throwing specifications are compatible. - bool OldNonThrowing = OldNR == FunctionProtoType::NR_Nothrow || - OldEST == EST_DynamicNone; - bool NewNonThrowing = NewNR == FunctionProtoType::NR_Nothrow || - NewEST == EST_DynamicNone; - if (OldNonThrowing && NewNonThrowing) - return false; - // As a special compatibility feature, under C++0x we accept no spec and // throw(std::bad_alloc) as equivalent for operator new and operator new[]. // This is because the implicit declaration changed, but old code would break. @@ -560,54 +584,24 @@ static bool CheckEquivalentExceptionSpecImpl( } } - // At this point, the only remaining valid case is two matching dynamic - // specifications. We return here unless both specifications are dynamic. - if (OldEST != EST_Dynamic || NewEST != EST_Dynamic) { - if (MissingExceptionSpecification && Old->hasExceptionSpec() && - !New->hasExceptionSpec()) { - // The old type has an exception specification of some sort, but - // the new type does not. - *MissingExceptionSpecification = true; - - if (MissingEmptyExceptionSpecification && OldNonThrowing) { - // The old type has a throw() or noexcept(true) exception specification - // and the new type has no exception specification, and the caller asked - // to handle this itself. - *MissingEmptyExceptionSpecification = true; - } - - return true; + // If the caller wants to handle the case that the new function is + // incompatible due to a missing exception specification, let it. + if (MissingExceptionSpecification && OldEST != EST_None && + NewEST == EST_None) { + // The old type has an exception specification of some sort, but + // the new type does not. + *MissingExceptionSpecification = true; + + if (MissingEmptyExceptionSpecification && OldCanThrow == CT_Cannot) { + // The old type has a throw() or noexcept(true) exception specification + // and the new type has no exception specification, and the caller asked + // to handle this itself. + *MissingEmptyExceptionSpecification = true; } - S.Diag(NewLoc, DiagID); - if (NoteID.getDiagID() != 0 && OldLoc.isValid()) - S.Diag(OldLoc, NoteID); return true; } - assert(OldEST == EST_Dynamic && NewEST == EST_Dynamic && - "Exception compatibility logic error: non-dynamic spec slipped through."); - - bool Success = true; - // Both have a dynamic exception spec. Collect the first set, then compare - // to the second. - llvm::SmallPtrSet OldTypes, NewTypes; - for (const auto &I : Old->exceptions()) - OldTypes.insert(S.Context.getCanonicalType(I).getUnqualifiedType()); - - for (const auto &I : New->exceptions()) { - CanQualType TypePtr = S.Context.getCanonicalType(I).getUnqualifiedType(); - if (OldTypes.count(TypePtr)) - NewTypes.insert(TypePtr); - else - Success = false; - } - - Success = Success && OldTypes.size() == NewTypes.size(); - - if (Success) { - return false; - } S.Diag(NewLoc, DiagID); if (NoteID.getDiagID() != 0 && OldLoc.isValid()) S.Diag(OldLoc, NoteID); @@ -740,62 +734,32 @@ bool Sema::CheckExceptionSpecSubset(const PartialDiagnostic &DiagID, return false; ExceptionSpecificationType SuperEST = Superset->getExceptionSpecType(); - - // If superset contains everything, we're done. - if (SuperEST == EST_None || SuperEST == EST_MSAny) - return CheckParamExceptionSpec(NestedDiagID, NoteID, Superset, SuperLoc, - Subset, SubLoc); - - // If there are dependent noexcept specs, assume everything is fine. Unlike - // with the equivalency check, this is safe in this case, because we don't - // want to merge declarations. Checks after instantiation will catch any - // omissions we make here. - // We also shortcut checking if a noexcept expression was bad. - - FunctionProtoType::NoexceptResult SuperNR =Superset->getNoexceptSpec(Context); - if (SuperNR == FunctionProtoType::NR_BadNoexcept || - SuperNR == FunctionProtoType::NR_Dependent) - return false; - - // Another case of the superset containing everything. - if (SuperNR == FunctionProtoType::NR_Throw) - return CheckParamExceptionSpec(NestedDiagID, NoteID, Superset, SuperLoc, - Subset, SubLoc); - ExceptionSpecificationType SubEST = Subset->getExceptionSpecType(); - assert(!isUnresolvedExceptionSpec(SuperEST) && !isUnresolvedExceptionSpec(SubEST) && "Shouldn't see unknown exception specifications here"); - // It does not. If the subset contains everything, we've failed. - if (SubEST == EST_None || SubEST == EST_MSAny) { - Diag(SubLoc, DiagID); - if (NoteID.getDiagID() != 0) - Diag(SuperLoc, NoteID); - return true; - } - - FunctionProtoType::NoexceptResult SubNR = Subset->getNoexceptSpec(Context); - if (SubNR == FunctionProtoType::NR_BadNoexcept || - SubNR == FunctionProtoType::NR_Dependent) + // If there are dependent noexcept specs, assume everything is fine. Unlike + // with the equivalency check, this is safe in this case, because we don't + // want to merge declarations. Checks after instantiation will catch any + // omissions we make here. + if (SuperEST == EST_DependentNoexcept || SubEST == EST_DependentNoexcept) return false; - // Another case of the subset containing everything. - if (SubNR == FunctionProtoType::NR_Throw) { - Diag(SubLoc, DiagID); - if (NoteID.getDiagID() != 0) - Diag(SuperLoc, NoteID); - return true; - } + CanThrowResult SuperCanThrow = Superset->canThrow(); + CanThrowResult SubCanThrow = Subset->canThrow(); - // If the subset contains nothing, we're done. - if (SubEST == EST_DynamicNone || SubNR == FunctionProtoType::NR_Nothrow) + // If the superset contains everything or the subset contains nothing, we're + // done. + if ((SuperCanThrow == CT_Can && SuperEST != EST_Dynamic) || + SubCanThrow == CT_Cannot) return CheckParamExceptionSpec(NestedDiagID, NoteID, Superset, SuperLoc, Subset, SubLoc); - // Otherwise, if the superset contains nothing, we've failed. - if (SuperEST == EST_DynamicNone || SuperNR == FunctionProtoType::NR_Nothrow) { + // If the subset contains everything or the superset contains nothing, we've + // failed. + if ((SubCanThrow == CT_Can && SubEST != EST_Dynamic) || + SuperCanThrow == CT_Cannot) { Diag(SubLoc, DiagID); if (NoteID.getDiagID() != 0) Diag(SuperLoc, NoteID); @@ -1026,7 +990,7 @@ static CanThrowResult canCalleeThrow(Sema &S, const Expr *E, const Decl *D) { if (!FT) return CT_Can; - return FT->isNothrow(S.Context) ? CT_Cannot : CT_Can; + return FT->canThrow(); } static CanThrowResult canDynamicCastThrow(const CXXDynamicCastExpr *DC) { diff --git a/clang/lib/Sema/SemaExprCXX.cpp b/clang/lib/Sema/SemaExprCXX.cpp index 95ea841..1d56fc8 100644 --- a/clang/lib/Sema/SemaExprCXX.cpp +++ b/clang/lib/Sema/SemaExprCXX.cpp @@ -4381,7 +4381,7 @@ static bool HasNoThrowOperator(const RecordType *RT, OverloadedOperatorKind Op, const FunctionProtoType *CPT = Operator->getType()->getAs(); CPT = Self.ResolveExceptionSpec(KeyLoc, CPT); - if (!CPT || !CPT->isNothrow(C)) + if (!CPT || !CPT->isNothrow()) return false; } } @@ -4629,7 +4629,7 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT, const FunctionProtoType *CPT = Destructor->getType()->getAs(); CPT = Self.ResolveExceptionSpec(KeyLoc, CPT); - if (!CPT || !CPT->isNothrow(C)) + if (!CPT || !CPT->isNothrow()) return false; } } @@ -4722,7 +4722,7 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT, return false; // TODO: check whether evaluating default arguments can throw. // For now, we'll be conservative and assume that they can throw. - if (!CPT->isNothrow(C) || CPT->getNumParams() > 1) + if (!CPT->isNothrow() || CPT->getNumParams() > 1) return false; } } @@ -4761,7 +4761,7 @@ static bool EvaluateUnaryTypeTrait(Sema &Self, TypeTrait UTT, return false; // FIXME: check whether evaluating default arguments can throw. // For now, we'll be conservative and assume that they can throw. - if (!CPT->isNothrow(C) || CPT->getNumParams() > 0) + if (!CPT->isNothrow() || CPT->getNumParams() > 0) return false; } } @@ -5909,27 +5909,23 @@ mergeExceptionSpecs(Sema &S, FunctionProtoType::ExceptionSpecInfo ESI1, if (EST2 == EST_None) return ESI2; if (EST1 == EST_MSAny) return ESI1; if (EST2 == EST_MSAny) return ESI2; + if (EST1 == EST_NoexceptFalse) return ESI1; + if (EST2 == EST_NoexceptFalse) 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 (EST1 == EST_NoexceptTrue) return ESI2; + if (EST2 == EST_NoexceptTrue) 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) { + if (EST1 == EST_DependentNoexcept || EST2 == EST_DependentNoexcept) { assert(!S.getLangOpts().CPlusPlus17 && "computing composite pointer type of dependent types"); return FunctionProtoType::ExceptionSpecInfo(); @@ -5942,7 +5938,9 @@ mergeExceptionSpecs(Sema &S, FunctionProtoType::ExceptionSpecInfo ESI1, case EST_DynamicNone: case EST_MSAny: case EST_BasicNoexcept: - case EST_ComputedNoexcept: + case EST_DependentNoexcept: + case EST_NoexceptFalse: + case EST_NoexceptTrue: llvm_unreachable("handled above"); case EST_Dynamic: { diff --git a/clang/lib/Sema/SemaOverload.cpp b/clang/lib/Sema/SemaOverload.cpp index 4b58988..f534589 100644 --- a/clang/lib/Sema/SemaOverload.cpp +++ b/clang/lib/Sema/SemaOverload.cpp @@ -1481,7 +1481,7 @@ bool Sema::IsFunctionConversion(QualType FromType, QualType ToType, // Drop 'noexcept' if not present in target type. if (const auto *FromFPT = dyn_cast(FromFn)) { const auto *ToFPT = cast(ToFn); - if (FromFPT->isNothrow(Context) && !ToFPT->isNothrow(Context)) { + if (FromFPT->isNothrow() && !ToFPT->isNothrow()) { FromFn = cast( Context.getFunctionTypeWithExceptionSpec(QualType(FromFPT, 0), EST_None) @@ -2809,9 +2809,9 @@ void Sema::HandleFunctionTypeMismatch(PartialDiagnostic &PDiag, // Handle exception specification differences on canonical type (in C++17 // onwards). if (cast(FromFunction->getCanonicalTypeUnqualified()) - ->isNothrow(Context) != + ->isNothrow() != cast(ToFunction->getCanonicalTypeUnqualified()) - ->isNothrow(Context)) { + ->isNothrow()) { PDiag << ft_noexcept; return; } diff --git a/clang/lib/Sema/SemaTemplateDeduction.cpp b/clang/lib/Sema/SemaTemplateDeduction.cpp index e4a158f..dc3c3a6 100644 --- a/clang/lib/Sema/SemaTemplateDeduction.cpp +++ b/clang/lib/Sema/SemaTemplateDeduction.cpp @@ -1607,7 +1607,7 @@ DeduceTemplateArgumentsByTypeMatch(Sema &S, "saw non-type template parameter with wrong depth"); llvm::APSInt Noexcept(1); - switch (FunctionProtoArg->canThrow(S.Context)) { + switch (FunctionProtoArg->canThrow()) { case CT_Cannot: Noexcept = 1; LLVM_FALLTHROUGH; diff --git a/clang/lib/Sema/SemaTemplateVariadic.cpp b/clang/lib/Sema/SemaTemplateVariadic.cpp index 37a9c26..68a6909 100644 --- a/clang/lib/Sema/SemaTemplateVariadic.cpp +++ b/clang/lib/Sema/SemaTemplateVariadic.cpp @@ -882,7 +882,7 @@ bool Sema::containsUnexpandedParameterPacks(Declarator &D) { ->containsUnexpandedParameterPack()) return true; } - } else if (Chunk.Fun.getExceptionSpecType() == EST_ComputedNoexcept && + } else if (isComputedNoexcept(Chunk.Fun.getExceptionSpecType()) && Chunk.Fun.NoexceptExpr->containsUnexpandedParameterPack()) return true; diff --git a/clang/lib/Sema/SemaType.cpp b/clang/lib/Sema/SemaType.cpp index a6491e5..47b1659 100644 --- a/clang/lib/Sema/SemaType.cpp +++ b/clang/lib/Sema/SemaType.cpp @@ -4677,7 +4677,7 @@ static TypeSourceInfo *GetFullTypeForDeclarator(TypeProcessingState &state, DynamicExceptions.push_back(FTI.Exceptions[I].Ty); DynamicExceptionRanges.push_back(FTI.Exceptions[I].Range); } - } else if (FTI.getExceptionSpecType() == EST_ComputedNoexcept) { + } else if (isComputedNoexcept(FTI.getExceptionSpecType())) { NoexceptExpr = FTI.NoexceptExpr; } diff --git a/clang/lib/Sema/TreeTransform.h b/clang/lib/Sema/TreeTransform.h index 4de5550..ee38278 100644 --- a/clang/lib/Sema/TreeTransform.h +++ b/clang/lib/Sema/TreeTransform.h @@ -5264,30 +5264,23 @@ bool TreeTransform::TransformExceptionSpec( assert(ESI.Type != EST_Uninstantiated && ESI.Type != EST_Unevaluated); // Instantiate a dynamic noexcept expression, if any. - if (ESI.Type == EST_ComputedNoexcept) { + if (isComputedNoexcept(ESI.Type)) { EnterExpressionEvaluationContext Unevaluated( getSema(), Sema::ExpressionEvaluationContext::ConstantEvaluated); ExprResult NoexceptExpr = getDerived().TransformExpr(ESI.NoexceptExpr); if (NoexceptExpr.isInvalid()) return true; - // FIXME: This is bogus, a noexcept expression is not a condition. - NoexceptExpr = getSema().CheckBooleanCondition(Loc, NoexceptExpr.get()); + ExceptionSpecificationType EST = ESI.Type; + NoexceptExpr = + getSema().ActOnNoexceptSpec(Loc, NoexceptExpr.get(), EST); if (NoexceptExpr.isInvalid()) return true; - if (!NoexceptExpr.get()->isValueDependent()) { - NoexceptExpr = getSema().VerifyIntegerConstantExpression( - NoexceptExpr.get(), nullptr, - diag::err_noexcept_needs_constant_expression, - /*AllowFold*/false); - if (NoexceptExpr.isInvalid()) - return true; - } - - if (ESI.NoexceptExpr != NoexceptExpr.get()) + if (ESI.NoexceptExpr != NoexceptExpr.get() || EST != ESI.Type) Changed = true; ESI.NoexceptExpr = NoexceptExpr.get(); + ESI.Type = EST; } if (ESI.Type != EST_Dynamic) diff --git a/clang/lib/Serialization/ASTReader.cpp b/clang/lib/Serialization/ASTReader.cpp index 4b0220ab..19c2de3 100644 --- a/clang/lib/Serialization/ASTReader.cpp +++ b/clang/lib/Serialization/ASTReader.cpp @@ -6392,7 +6392,7 @@ void ASTReader::readExceptionSpec(ModuleFile &ModuleFile, for (unsigned I = 0, N = Record[Idx++]; I != N; ++I) Exceptions.push_back(readType(ModuleFile, Record, Idx)); ESI.Exceptions = Exceptions; - } else if (EST == EST_ComputedNoexcept) { + } else if (isComputedNoexcept(EST)) { ESI.NoexceptExpr = ReadExpr(ModuleFile); } else if (EST == EST_Uninstantiated) { ESI.SourceDecl = ReadDeclAs(ModuleFile, Record, Idx); diff --git a/clang/lib/Serialization/ASTWriter.cpp b/clang/lib/Serialization/ASTWriter.cpp index fb7276b..c60b4b2 100644 --- a/clang/lib/Serialization/ASTWriter.cpp +++ b/clang/lib/Serialization/ASTWriter.cpp @@ -294,7 +294,7 @@ static void addExceptionSpec(const FunctionProtoType *T, Record.push_back(T->getNumExceptions()); for (unsigned I = 0, N = T->getNumExceptions(); I != N; ++I) Record.AddTypeRef(T->getExceptionType(I)); - } else if (T->getExceptionSpecType() == EST_ComputedNoexcept) { + } else if (isComputedNoexcept(T->getExceptionSpecType())) { Record.AddStmt(T->getNoexceptExpr()); } else if (T->getExceptionSpecType() == EST_Uninstantiated) { Record.AddDeclRef(T->getExceptionSpecDecl()); diff --git a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp index 31d198e..9152b41 100644 --- a/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp +++ b/clang/lib/StaticAnalyzer/Core/ExprEngineCXX.cpp @@ -549,7 +549,7 @@ void ExprEngine::VisitCXXNewAllocatorCall(const CXXNewExpr *CNE, if (const FunctionDecl *FD = CNE->getOperatorNew()) { QualType Ty = FD->getType(); if (const auto *ProtoType = Ty->getAs()) - if (!ProtoType->isNothrow(getContext())) + if (!ProtoType->isNothrow()) State = State->assume(RetVal.castAs(), true); } @@ -622,7 +622,7 @@ void ExprEngine::VisitCXXNewExpr(const CXXNewExpr *CNE, ExplodedNode *Pred, if (FD) { QualType Ty = FD->getType(); if (const auto *ProtoType = Ty->getAs()) - if (!ProtoType->isNothrow(getContext())) + if (!ProtoType->isNothrow()) if (auto dSymVal = symVal.getAs()) State = State->assume(*dSymVal, true); } diff --git a/clang/test/Modules/cxx17-exception-spec.cpp b/clang/test/Modules/cxx17-exception-spec.cpp new file mode 100644 index 0000000..ba7be0e --- /dev/null +++ b/clang/test/Modules/cxx17-exception-spec.cpp @@ -0,0 +1,34 @@ +// RUN: %clang_cc1 -fmodules -fmodules-local-submodule-visibility -fno-modules-error-recovery -fno-spell-checking -verify -std=c++17 %s + +#pragma clang module build a +module a {} +#pragma clang module contents +#pragma clang module begin a +constexpr bool return_true() { return true; } +struct X { + static void f() noexcept(return_true()); +}; +#pragma clang module end +#pragma clang module endbuild + +#pragma clang module build b +module b {} +#pragma clang module contents +#pragma clang module begin b +#pragma clang module import a +using T = decltype(return_true() && noexcept(X::f())); +#pragma clang module end +#pragma clang module endbuild + +#pragma clang module import a +#pragma clang module import b + +// Trigger import of return_true and then X::f in the same pass. This causes +// the type of X::f to be loaded while we have a pending body for return_true, +// which means evaluation of its exception specification at that point would +// fail. +T t; + +static_assert(noexcept(X().f())); + +// expected-no-diagnostics -- 2.7.4