From 434bc3478f4a1c49e18cc881b8135b9d073a23c9 Mon Sep 17 00:00:00 2001 From: Richard Smith Date: Wed, 31 Aug 2016 02:15:21 +0000 Subject: [PATCH] PR12298 et al: don't recursively instantiate a template specialization from within the instantiation of that same specialization. This could previously happen for eagerly-instantiated function templates, variable templates, exception specifications, default arguments, and a handful of other cases. We still have an issue here for default template arguments that recursively make use of themselves and likewise for substitution into the type of a non-type template parameter, but in those cases we're producing a different entity each time, so they should instead be caught by the instantiation depth limit. However, currently we will typically run out of stack before we reach it. :( git-svn-id: https://llvm.org/svn/llvm-project/cfe/trunk@280190 91177308-0d34-0410-b5e6-96231b3b80d8 --- include/clang/AST/DeclTemplate.h | 10 ++ include/clang/Basic/DiagnosticSemaKinds.td | 4 + include/clang/Sema/Sema.h | 23 ++-- lib/Sema/SemaDecl.cpp | 3 +- lib/Sema/SemaExpr.cpp | 5 + lib/Sema/SemaTemplate.cpp | 13 ++- lib/Sema/SemaTemplateInstantiate.cpp | 37 +++++-- lib/Sema/SemaTemplateInstantiateDecl.cpp | 35 ++++-- test/SemaTemplate/instantiate-self.cpp | 103 ++++++++++++++++-- .../instantiation-depth-exception-spec.cpp | 15 ++- test/SemaTemplate/instantiation-depth.cpp | 9 +- 11 files changed, 206 insertions(+), 51 deletions(-) diff --git a/include/clang/AST/DeclTemplate.h b/include/clang/AST/DeclTemplate.h index 8671d95c10..2af95c02c4 100644 --- a/include/clang/AST/DeclTemplate.h +++ b/include/clang/AST/DeclTemplate.h @@ -43,6 +43,8 @@ class VarTemplatePartialSpecializationDecl; typedef llvm::PointerUnion3 TemplateParameter; +NamedDecl *getAsNamedDecl(TemplateParameter P); + /// \brief Stores a list of template parameters for a TemplateDecl and its /// derived classes. class TemplateParameterList final @@ -2937,6 +2939,14 @@ public: friend class ASTDeclWriter; }; +inline NamedDecl *getAsNamedDecl(TemplateParameter P) { + if (auto *PD = P.dyn_cast()) + return PD; + if (auto *PD = P.dyn_cast()) + return PD; + return P.get(); +} + } /* end of namespace clang */ #endif diff --git a/include/clang/Basic/DiagnosticSemaKinds.td b/include/clang/Basic/DiagnosticSemaKinds.td index ff98590057..7d0b9d7741 100644 --- a/include/clang/Basic/DiagnosticSemaKinds.td +++ b/include/clang/Basic/DiagnosticSemaKinds.td @@ -7001,6 +7001,10 @@ def err_in_class_initializer_not_yet_parsed def err_in_class_initializer_not_yet_parsed_outer_class : Error<"cannot use defaulted default constructor of %0 within " "%1 outside of member functions because %2 has an initializer">; +def err_in_class_initializer_cycle + : Error<"default member initializer for %0 uses itself">; +def err_exception_spec_cycle + : Error<"exception specification of %0 uses itself">; def ext_in_class_initializer_non_constant : Extension< "in-class initializer for static data member is not a constant expression; " diff --git a/include/clang/Sema/Sema.h b/include/clang/Sema/Sema.h index 668399bf84..9dbef558f0 100644 --- a/include/clang/Sema/Sema.h +++ b/include/clang/Sema/Sema.h @@ -18,6 +18,7 @@ #include "clang/AST/Attr.h" #include "clang/AST/Availability.h" #include "clang/AST/DeclarationName.h" +#include "clang/AST/DeclTemplate.h" #include "clang/AST/Expr.h" #include "clang/AST/ExprObjC.h" #include "clang/AST/ExternalASTSource.h" @@ -6668,10 +6669,10 @@ public: TemplateInstantiation, /// We are instantiating a default argument for a template - /// parameter. The Entity is the template, and - /// TemplateArgs/NumTemplateArguments provides the template - /// arguments as specified. - /// FIXME: Use a TemplateArgumentList + /// parameter. The Entity is the template parameter whose argument is + /// being instantiated, the Template is the template, and the + /// TemplateArgs/NumTemplateArguments provide the template arguments as + /// specified. DefaultTemplateArgumentInstantiation, /// We are instantiating a default argument for a function. @@ -6786,6 +6787,9 @@ public: SmallVector ActiveTemplateInstantiations; + /// Specializations whose definitions are currently being instantiated. + llvm::DenseSet> InstantiatingSpecializations; + /// \brief Extra modules inspected when performing a lookup during a template /// instantiation. Computed lazily. SmallVector ActiveTemplateInstantiationLookupModules; @@ -6892,12 +6896,12 @@ public: /// \brief Note that we are instantiating a default argument in a /// template-id. InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation, - TemplateDecl *Template, + TemplateParameter Param, TemplateDecl *Template, ArrayRef TemplateArgs, SourceRange InstantiationRange = SourceRange()); - /// \brief Note that we are instantiating a default argument in a - /// template-id. + /// \brief Note that we are substituting either explicitly-specified or + /// deduced template arguments during function template argument deduction. InstantiatingTemplate(Sema &SemaRef, SourceLocation PointOfInstantiation, FunctionTemplateDecl *FunctionTemplate, ArrayRef TemplateArgs, @@ -6964,9 +6968,14 @@ public: /// recursive template instantiations. bool isInvalid() const { return Invalid; } + /// \brief Determine whether we are already instantiating this + /// specialization in some surrounding active instantiation. + bool isAlreadyInstantiating() const { return AlreadyInstantiating; } + private: Sema &SemaRef; bool Invalid; + bool AlreadyInstantiating; bool SavedInNonInstantiationSFINAEContext; bool CheckInstantiationDepth(SourceLocation PointOfInstantiation, SourceRange InstantiationRange); diff --git a/lib/Sema/SemaDecl.cpp b/lib/Sema/SemaDecl.cpp index c756d1f6ab..9df9c63cfd 100644 --- a/lib/Sema/SemaDecl.cpp +++ b/lib/Sema/SemaDecl.cpp @@ -9645,7 +9645,8 @@ void Sema::AddInitializerToDecl(Decl *RealDecl, Expr *Init, } VarDecl *Def; - if ((Def = VDecl->getDefinition()) && Def != VDecl) { + if ((Def = VDecl->getDefinition()) && Def != VDecl && + (!VDecl->isStaticDataMember() || VDecl->isOutOfLine())) { NamedDecl *Hidden = nullptr; if (!hasVisibleDefinition(Def, &Hidden) && (VDecl->getFormalLinkage() == InternalLinkage || diff --git a/lib/Sema/SemaExpr.cpp b/lib/Sema/SemaExpr.cpp index 4bf17a6d88..ce371e023c 100644 --- a/lib/Sema/SemaExpr.cpp +++ b/lib/Sema/SemaExpr.cpp @@ -4545,6 +4545,11 @@ ExprResult Sema::BuildCXXDefaultArgExpr(SourceLocation CallLoc, MutiLevelArgList.getInnermost()); if (Inst.isInvalid()) return ExprError(); + if (Inst.isAlreadyInstantiating()) { + Diag(Param->getLocStart(), diag::err_recursive_default_argument) << FD; + Param->setInvalidDecl(); + return ExprError(); + } ExprResult Result; { diff --git a/lib/Sema/SemaTemplate.cpp b/lib/Sema/SemaTemplate.cpp index ea0357a335..ab5f545181 100644 --- a/lib/Sema/SemaTemplate.cpp +++ b/lib/Sema/SemaTemplate.cpp @@ -3329,7 +3329,7 @@ SubstDefaultTemplateArgument(Sema &SemaRef, // on the previously-computed template arguments. if (ArgType->getType()->isDependentType()) { Sema::InstantiatingTemplate Inst(SemaRef, TemplateLoc, - Template, Converted, + Param, Template, Converted, SourceRange(TemplateLoc, RAngleLoc)); if (Inst.isInvalid()) return nullptr; @@ -3381,7 +3381,7 @@ SubstDefaultTemplateArgument(Sema &SemaRef, NonTypeTemplateParmDecl *Param, SmallVectorImpl &Converted) { Sema::InstantiatingTemplate Inst(SemaRef, TemplateLoc, - Template, Converted, + Param, Template, Converted, SourceRange(TemplateLoc, RAngleLoc)); if (Inst.isInvalid()) return ExprError(); @@ -3432,8 +3432,9 @@ SubstDefaultTemplateArgument(Sema &SemaRef, TemplateTemplateParmDecl *Param, SmallVectorImpl &Converted, NestedNameSpecifierLoc &QualifierLoc) { - Sema::InstantiatingTemplate Inst(SemaRef, TemplateLoc, Template, Converted, - SourceRange(TemplateLoc, RAngleLoc)); + Sema::InstantiatingTemplate Inst( + SemaRef, TemplateLoc, TemplateParameter(Param), Template, Converted, + SourceRange(TemplateLoc, RAngleLoc)); if (Inst.isInvalid()) return TemplateName(); @@ -4054,7 +4055,9 @@ bool Sema::CheckTemplateArgumentList(TemplateDecl *Template, } // Introduce an instantiation record that describes where we are using - // the default template argument. + // the default template argument. We're not actually instantiating a + // template here, we just create this object to put a note into the + // context stack. InstantiatingTemplate Inst(*this, RAngleLoc, Template, *Param, Converted, SourceRange(TemplateLoc, RAngleLoc)); if (Inst.isInvalid()) diff --git a/lib/Sema/SemaTemplateInstantiate.cpp b/lib/Sema/SemaTemplateInstantiate.cpp index 4749962d04..50663f9f4b 100644 --- a/lib/Sema/SemaTemplateInstantiate.cpp +++ b/lib/Sema/SemaTemplateInstantiate.cpp @@ -226,6 +226,10 @@ Sema::InstantiatingTemplate::InstantiatingTemplate( Inst.NumTemplateArgs = TemplateArgs.size(); Inst.DeductionInfo = DeductionInfo; Inst.InstantiationRange = InstantiationRange; + AlreadyInstantiating = + !SemaRef.InstantiatingSpecializations + .insert(std::make_pair(Inst.Entity->getCanonicalDecl(), Inst.Kind)) + .second; SemaRef.InNonInstantiationSFINAEContext = false; SemaRef.ActiveTemplateInstantiations.push_back(Inst); if (!Inst.isInstantiationRecord()) @@ -248,13 +252,14 @@ Sema::InstantiatingTemplate::InstantiatingTemplate( PointOfInstantiation, InstantiationRange, Entity) {} Sema::InstantiatingTemplate::InstantiatingTemplate( - Sema &SemaRef, SourceLocation PointOfInstantiation, TemplateDecl *Template, - ArrayRef TemplateArgs, SourceRange InstantiationRange) + Sema &SemaRef, SourceLocation PointOfInstantiation, TemplateParameter Param, + TemplateDecl *Template, ArrayRef TemplateArgs, + SourceRange InstantiationRange) : InstantiatingTemplate( SemaRef, ActiveTemplateInstantiation::DefaultTemplateArgumentInstantiation, - PointOfInstantiation, InstantiationRange, Template, nullptr, - TemplateArgs) {} + PointOfInstantiation, InstantiationRange, getAsNamedDecl(Param), + Template, TemplateArgs) {} Sema::InstantiatingTemplate::InstantiatingTemplate( Sema &SemaRef, SourceLocation PointOfInstantiation, @@ -264,7 +269,11 @@ Sema::InstantiatingTemplate::InstantiatingTemplate( sema::TemplateDeductionInfo &DeductionInfo, SourceRange InstantiationRange) : InstantiatingTemplate(SemaRef, Kind, PointOfInstantiation, InstantiationRange, FunctionTemplate, nullptr, - TemplateArgs, &DeductionInfo) {} + TemplateArgs, &DeductionInfo) { + assert( + Kind == ActiveTemplateInstantiation::ExplicitTemplateArgumentSubstitution || + Kind == ActiveTemplateInstantiation::DeducedTemplateArgumentSubstitution); +} Sema::InstantiatingTemplate::InstantiatingTemplate( Sema &SemaRef, SourceLocation PointOfInstantiation, @@ -328,7 +337,8 @@ Sema::InstantiatingTemplate::InstantiatingTemplate( void Sema::InstantiatingTemplate::Clear() { if (!Invalid) { - if (!SemaRef.ActiveTemplateInstantiations.back().isInstantiationRecord()) { + auto &Active = SemaRef.ActiveTemplateInstantiations.back(); + if (!Active.isInstantiationRecord()) { assert(SemaRef.NonInstantiationEntries > 0); --SemaRef.NonInstantiationEntries; } @@ -346,6 +356,10 @@ void Sema::InstantiatingTemplate::Clear() { SemaRef.ActiveTemplateInstantiationLookupModules.pop_back(); } + if (!AlreadyInstantiating) + SemaRef.InstantiatingSpecializations.erase( + std::make_pair(Active.Entity, Active.Kind)); + SemaRef.ActiveTemplateInstantiations.pop_back(); Invalid = true; } @@ -444,7 +458,7 @@ void Sema::PrintInstantiationStack() { } case ActiveTemplateInstantiation::DefaultTemplateArgumentInstantiation: { - TemplateDecl *Template = cast(Active->Entity); + TemplateDecl *Template = cast(Active->Template); SmallVector TemplateArgsStr; llvm::raw_svector_ostream OS(TemplateArgsStr); Template->printName(OS); @@ -1895,6 +1909,7 @@ Sema::InstantiateClass(SourceLocation PointOfInstantiation, InstantiatingTemplate Inst(*this, PointOfInstantiation, Instantiation); if (Inst.isInvalid()) return true; + assert(!Inst.isAlreadyInstantiating() && "should have been caught by caller"); PrettyDeclStackTraceEntry CrashInfo(*this, Instantiation, SourceLocation(), "instantiating class definition"); @@ -2120,6 +2135,8 @@ bool Sema::InstantiateEnum(SourceLocation PointOfInstantiation, InstantiatingTemplate Inst(*this, PointOfInstantiation, Instantiation); if (Inst.isInvalid()) return true; + if (Inst.isAlreadyInstantiating()) + return false; PrettyDeclStackTraceEntry CrashInfo(*this, Instantiation, SourceLocation(), "instantiating enum definition"); @@ -2194,6 +2211,12 @@ bool Sema::InstantiateInClassInitializer( InstantiatingTemplate Inst(*this, PointOfInstantiation, Instantiation); if (Inst.isInvalid()) return true; + if (Inst.isAlreadyInstantiating()) { + // Error out if we hit an instantiation cycle for this initializer. + Diag(PointOfInstantiation, diag::err_in_class_initializer_cycle) + << Instantiation; + return true; + } PrettyDeclStackTraceEntry CrashInfo(*this, Instantiation, SourceLocation(), "instantiating default member init"); diff --git a/lib/Sema/SemaTemplateInstantiateDecl.cpp b/lib/Sema/SemaTemplateInstantiateDecl.cpp index f7d9787fbe..d686798be1 100644 --- a/lib/Sema/SemaTemplateInstantiateDecl.cpp +++ b/lib/Sema/SemaTemplateInstantiateDecl.cpp @@ -3396,6 +3396,13 @@ void Sema::InstantiateExceptionSpec(SourceLocation PointOfInstantiation, UpdateExceptionSpec(Decl, EST_None); return; } + if (Inst.isAlreadyInstantiating()) { + // This exception specification indirectly depends on itself. Reject. + // FIXME: Corresponding rule in the standard? + Diag(PointOfInstantiation, diag::err_exception_spec_cycle) << Decl; + UpdateExceptionSpec(Decl, EST_None); + return; + } // Enter the scope of this instantiation. We don't use // PushDeclContext because we don't have a scope. @@ -3647,7 +3654,7 @@ void Sema::InstantiateFunctionDefinition(SourceLocation PointOfInstantiation, } InstantiatingTemplate Inst(*this, PointOfInstantiation, Function); - if (Inst.isInvalid()) + if (Inst.isInvalid() || Inst.isAlreadyInstantiating()) return; PrettyDeclStackTraceEntry CrashInfo(*this, Function, SourceLocation(), "instantiating function definition"); @@ -3914,10 +3921,6 @@ void Sema::InstantiateVariableInitializer( else if (OldVar->isInline()) Var->setImplicitlyInline(); - if (Var->getAnyInitializer()) - // We already have an initializer in the class. - return; - if (OldVar->getInit()) { if (Var->isStaticDataMember() && !OldVar->isOutOfLine()) PushExpressionEvaluationContext(Sema::ConstantEvaluated, OldVar); @@ -3953,9 +3956,23 @@ void Sema::InstantiateVariableInitializer( } PopExpressionEvaluationContext(); - } else if ((!Var->isStaticDataMember() || Var->isOutOfLine()) && - !Var->isCXXForRangeDecl()) + } else { + if (Var->isStaticDataMember()) { + if (!Var->isOutOfLine()) + return; + + // If the declaration inside the class had an initializer, don't add + // another one to the out-of-line definition. + if (OldVar->getFirstDecl()->hasInit()) + return; + } + + // We'll add an initializer to a for-range declaration later. + if (Var->isCXXForRangeDecl()) + return; + ActOnUninitializedDecl(Var, false); + } } /// \brief Instantiate the definition of the given variable from its @@ -4045,7 +4062,7 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation, // FIXME: Factor out the duplicated instantiation context setup/tear down // code here. InstantiatingTemplate Inst(*this, PointOfInstantiation, Var); - if (Inst.isInvalid()) + if (Inst.isInvalid() || Inst.isAlreadyInstantiating()) return; PrettyDeclStackTraceEntry CrashInfo(*this, Var, SourceLocation(), "instantiating variable initializer"); @@ -4174,7 +4191,7 @@ void Sema::InstantiateVariableDefinition(SourceLocation PointOfInstantiation, } InstantiatingTemplate Inst(*this, PointOfInstantiation, Var); - if (Inst.isInvalid()) + if (Inst.isInvalid() || Inst.isAlreadyInstantiating()) return; PrettyDeclStackTraceEntry CrashInfo(*this, Var, SourceLocation(), "instantiating variable definition"); diff --git a/test/SemaTemplate/instantiate-self.cpp b/test/SemaTemplate/instantiate-self.cpp index cfe902509f..916a01e63f 100644 --- a/test/SemaTemplate/instantiate-self.cpp +++ b/test/SemaTemplate/instantiate-self.cpp @@ -1,4 +1,4 @@ -// RUN: %clang_cc1 -std=c++11 -verify %s +// RUN: %clang_cc1 -std=c++1z -verify -pedantic-errors %s // Check that we deal with cases where the instantiation of a class template // recursively requires the instantiation of the same template. @@ -47,9 +47,8 @@ namespace test4 { A a; // expected-note {{in instantiation of}} } -// FIXME: PR12298: Recursive constexpr function template instantiation leads to +// PR12298: Recursive constexpr function template instantiation leads to // stack overflow. -#if 0 namespace test5 { template struct A { constexpr T f(T k) { return g(k); } @@ -57,22 +56,20 @@ namespace test5 { return k ? f(k-1)+1 : 0; } }; - // This should be accepted. - constexpr int x = A().f(5); + constexpr int x = A().f(5); // ok } namespace test6 { template constexpr T f(T); template constexpr T g(T t) { - typedef int arr[f(T())]; + typedef int arr[f(T())]; // expected-error {{variable length array}} return t; } template constexpr T f(T t) { - typedef int arr[g(T())]; + typedef int arr[g(T())]; // expected-error {{zero size array}} expected-note {{instantiation of}} return t; } - // This should be ill-formed. - int n = f(0); + int n = f(0); // expected-note 2{{instantiation of}} } namespace test7 { @@ -80,10 +77,94 @@ namespace test7 { return t; } template constexpr T f(T t) { - typedef int arr[g(T())]; + typedef int arr[g(T() + 1)]; return t; } - // This should be accepted. int n = f(0); } + +namespace test8 { + template struct A { + int n = A{}.n; // expected-error {{default member initializer for 'n' uses itself}} expected-note {{instantiation of default member init}} + }; + A ai = {}; // expected-note {{instantiation of default member init}} +} + +namespace test9 { + template struct A { enum class B; }; + // FIXME: It'd be nice to give the "it has not yet been instantiated" diagnostic here. + template enum class A::B { k = A::B::k2, k2 = k }; // expected-error {{no member named 'k2'}} + auto k = A::B::k; // expected-note {{in instantiation of}} +} + +namespace test10 { + template struct A { + void f() noexcept(noexcept(f())); // expected-error {{exception specification of 'f' uses itself}} expected-note {{instantiation of}} + }; + bool b = noexcept(A().f()); // expected-note {{instantiation of}} +} + +namespace test11 { + template const int var = var; + int k = var; + + template struct X { + static const int k = X::k; + }; + template const int X::k; + int q = X::k; + + template struct Y { + static const int k; + }; + template const int Y::k = Y::k; + int r = Y::k; +} + +namespace test12 { + template int f(T t, int = f(T())) {} // expected-error {{recursive evaluation of default argument}} expected-note {{instantiation of}} + struct X {}; + int q = f(X()); // expected-note {{instantiation of}} +} + +namespace test13 { + struct A { + // Cycle via type of non-type template parameter. + template::type U = 0> struct W { using type = int; }; + // Cycle via default template argument. + template> struct X {}; + template::value> struct Y { static const int value = 0; }; + template typename U = T::template Z::template nested> struct Z { template struct nested; }; + }; + template struct Wrap { + template struct W : A::W {}; + template struct X : A::X {}; + template struct Y : A::Y {}; + template struct Z : A::Z {}; + }; + struct B { + template struct W { using type = int; }; + template struct X {}; + template struct Y { static const int value = 0; }; + template struct Z { template struct nested; }; + }; + + A::W awb; + A::X axb; + A::Y ayb; + A::Z azb; + + A::W>> awwwb; + A::X>> axwwb; + A::Y>> aywwb; + A::Z>> azwwb; + + // FIXME: These tests cause us to use too much stack and crash on a self-hosted debug build. + // FIXME: Check for recursion here and give a better diagnostic. +#if 0 + A::W awa; + A::X axa; + A::Y aya; + A::Z aza; #endif +} diff --git a/test/SemaTemplate/instantiation-depth-exception-spec.cpp b/test/SemaTemplate/instantiation-depth-exception-spec.cpp index 6caa4a60e6..3f64811bd1 100644 --- a/test/SemaTemplate/instantiation-depth-exception-spec.cpp +++ b/test/SemaTemplate/instantiation-depth-exception-spec.cpp @@ -1,11 +1,14 @@ // RUN: %clang_cc1 -fsyntax-only -verify -std=c++11 -ftemplate-depth 16 -fcxx-exceptions -fexceptions %s -template T go(T a) noexcept(noexcept(go(a))); // \ -// expected-error 16{{call to function 'go' that is neither visible}} \ -// expected-note 16{{'go' should be declared prior to the call site}} \ -// expected-error {{recursive template instantiation exceeded maximum depth of 16}} +template struct X { + static int go(int a) noexcept(noexcept(X::go(a))); // \ +// expected-error {{recursive template instantiation exceeded maximum depth of 16}} \ +// expected-note 9{{in instantiation of exception specification}} \ +// expected-note {{skipping 7 context}} \ +// expected-note {{use -ftemplate-depth}} +}; void f() { - int k = go(0); // \ - // expected-note {{in instantiation of exception specification for 'go' requested here}} + int k = X<0>::go(0); // \ + // expected-note {{in instantiation of exception specification for 'go' requested here}} } diff --git a/test/SemaTemplate/instantiation-depth.cpp b/test/SemaTemplate/instantiation-depth.cpp index c0b8bb2a12..17f84c170c 100644 --- a/test/SemaTemplate/instantiation-depth.cpp +++ b/test/SemaTemplate/instantiation-depth.cpp @@ -19,13 +19,12 @@ void test() { // RUN: %clang_cc1 -fsyntax-only -verify -ftemplate-depth 5 -ftemplate-backtrace-limit 4 -std=c++11 -DNOEXCEPT %s template struct S { - S() noexcept(noexcept(T())); -}; -struct T : S {}; \ + S() noexcept(noexcept(S())); \ // expected-error{{recursive template instantiation exceeded maximum depth of 5}} \ -// expected-note 4 {{in instantiation of exception spec}} \ +// expected-note 3 {{in instantiation of exception spec}} \ // expected-note {{skipping 2 contexts in backtrace}} \ // expected-note {{use -ftemplate-depth=N to increase recursive template instantiation depth}} -T t; // expected-note {{implicit default constructor for 'T' first required here}} +}; +S t; // expected-note {{in instantiation of exception spec}} #endif -- 2.40.0