Index: include/clang/Basic/Attr.td =================================================================== --- include/clang/Basic/Attr.td +++ include/clang/Basic/Attr.td @@ -1380,6 +1380,14 @@ let Documentation = [Undocumented]; } +def RequireConstantInit : InheritableAttr { + let Spellings = [GCC<"require_constant_initialization">, + CXX11<"clang", "require_constant_initialization">]; + let Subjects = SubjectList<[GlobalVar], ErrorDiag, + "ExpectedStaticOrTLSVar">; + let Documentation = [RequireConstantInitDocs]; +} + def WorkGroupSizeHint : InheritableAttr { let Spellings = [GNU<"work_group_size_hint">]; let Args = [UnsignedArgument<"XDim">, Index: include/clang/Basic/AttrDocs.td =================================================================== --- include/clang/Basic/AttrDocs.td +++ include/clang/Basic/AttrDocs.td @@ -829,6 +829,41 @@ }]; } + +def RequireConstantInitDocs : Documentation { + let Category = DocCatVariable; + let Content = [{ +This attribute specifies expectations about the initialization of static and +thread local variables. Specifically that the variable has a +`constant initializer `_ +according to the rules of [basic.start.static]. Failure to meet this expectation +will result in an error. + +Static objects with constant initializers avoid hard-to-find bugs caused by +the indeterminate order of dynamic initialization. They can also be safely +used by other static constructors across translation units. + +This attribute acts as a compile time assertion that the requirements +for constant initialization have been met. Since these requirements change +between dialects and have subtle pitfalls it's important to fail fast instead +of silently falling back on dynamic initialization. + +.. code-block:: c++ + // -std=c++14 + #define SAFE_STATIC __attribute__((require_constant_initialization)) static + struct T { + constexpr T(int) {} + ~T(); + }; + SAFE_STATIC T x = {42}; // OK. + SAFE_STATIC T y = 42; // error: variable does not have a constant initializer + // copy initialization is not a constant expression on a non-literal type. + +This attribute can only be applied to objects with static or thread-local storage +duration. + }]; +} + def WarnMaybeUnusedDocs : Documentation { let Category = DocCatVariable; let Heading = "maybe_unused, unused, gnu::unused"; @@ -845,12 +880,12 @@ enumerator, a non-static data member, or a label. .. code-block: c++ - #include - - [[maybe_unused]] void f([[maybe_unused]] bool thing1, - [[maybe_unused]] bool thing2) { - [[maybe_unused]] bool b = thing1 && thing2; - assert(b); + #include + + [[maybe_unused]] void f([[maybe_unused]] bool thing1, + [[maybe_unused]] bool thing2) { + [[maybe_unused]] bool b = thing1 && thing2; + assert(b); } }]; } @@ -867,15 +902,15 @@ `void`. .. code-block: c++ - struct [[nodiscard]] error_info { /*...*/ }; - error_info enable_missile_safety_mode(); - - void launch_missiles(); - void test_missiles() { - enable_missile_safety_mode(); // diagnoses - launch_missiles(); - } - error_info &foo(); + struct [[nodiscard]] error_info { /*...*/ }; + error_info enable_missile_safety_mode(); + + void launch_missiles(); + void test_missiles() { + enable_missile_safety_mode(); // diagnoses + launch_missiles(); + } + error_info &foo(); void f() { foo(); } // Does not diagnose, error_info is a reference. }]; } Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -2572,7 +2572,7 @@ "Objective-C instance methods|init methods of interface or class extension declarations|" "variables, functions and classes|" "functions, variables, classes, and Objective-C interfaces|" - "Objective-C protocols|" + "Objective-C protocols|variables with static or thread-local storage duration|" "functions and global variables|structs, unions, and typedefs|structs and typedefs|" "interface or protocol declarations|kernel functions|non-K&R-style functions|" "variables, enums, fields and typedefs|functions, methods, enums, and classes|" @@ -6837,7 +6837,10 @@ def err_incomplete_type_used_in_type_trait_expr : Error< "incomplete type %0 used in type trait expression">; - + +def err_require_constant_init_failed : Error< + "variable does not have a constant initializer">; + def err_dimension_expr_not_constant_integer : Error< "dimension expression does not evaluate to a constant unsigned int">; Index: include/clang/Sema/AttributeList.h =================================================================== --- include/clang/Sema/AttributeList.h +++ include/clang/Sema/AttributeList.h @@ -897,6 +897,7 @@ ExpectedFunctionVariableOrClass, ExpectedFunctionVariableClassOrObjCInterface, ExpectedObjectiveCProtocol, + ExpectedStaticOrTLSVar, ExpectedFunctionGlobalVarMethodOrProperty, ExpectedStructOrUnionOrTypedef, ExpectedStructOrTypedef, Index: lib/Sema/SemaDecl.cpp =================================================================== --- lib/Sema/SemaDecl.cpp +++ lib/Sema/SemaDecl.cpp @@ -10385,8 +10385,17 @@ Diag(var->getLocation(), diag::warn_missing_variable_declarations) << var; } + // Cache the result of checking for constant initialization. + Optional CacheHasConstInit; + const Expr *CacheCulprit; + auto checkConstInit = [&]() mutable { + if (!CacheHasConstInit) + CacheHasConstInit = var->getInit()->isConstantInitializer( + Context, var->getType()->isReferenceType(), &CacheCulprit); + return *CacheHasConstInit; + }; + if (var->getTLSKind() == VarDecl::TLS_Static) { - const Expr *Culprit; if (var->getType().isDestructedType()) { // GNU C++98 edits for __thread, [basic.start.term]p3: // The type of an object with thread storage duration shall not @@ -10394,17 +10403,17 @@ Diag(var->getLocation(), diag::err_thread_nontrivial_dtor); if (getLangOpts().CPlusPlus11) Diag(var->getLocation(), diag::note_use_thread_local); - } else if (getLangOpts().CPlusPlus && var->hasInit() && - !var->getInit()->isConstantInitializer( - Context, var->getType()->isReferenceType(), &Culprit)) { - // GNU C++98 edits for __thread, [basic.start.init]p4: - // An object of thread storage duration shall not require dynamic - // initialization. - // FIXME: Need strict checking here. - Diag(Culprit->getExprLoc(), diag::err_thread_dynamic_init) - << Culprit->getSourceRange(); - if (getLangOpts().CPlusPlus11) - Diag(var->getLocation(), diag::note_use_thread_local); + } else if (getLangOpts().CPlusPlus && var->hasInit()) { + if (!checkConstInit()) { + // GNU C++98 edits for __thread, [basic.start.init]p4: + // An object of thread storage duration shall not require dynamic + // initialization. + // FIXME: Need strict checking here. + Diag(CacheCulprit->getExprLoc(), diag::err_thread_dynamic_init) + << CacheCulprit->getSourceRange(); + if (getLangOpts().CPlusPlus11) + Diag(var->getLocation(), diag::note_use_thread_local); + } } } @@ -10478,18 +10487,6 @@ if (!var->getDeclContext()->isDependentContext() && Init && !Init->isValueDependent()) { - if (IsGlobal && !var->isConstexpr() && - !getDiagnostics().isIgnored(diag::warn_global_constructor, - var->getLocation())) { - // Warn about globals which don't have a constant initializer. Don't - // warn about globals with a non-trivial destructor because we already - // warned about them. - CXXRecordDecl *RD = baseType->getAsCXXRecordDecl(); - if (!(RD && !RD->hasTrivialDestructor()) && - !Init->isConstantInitializer(Context, baseType->isReferenceType())) - Diag(var->getLocation(), diag::warn_global_constructor) - << Init->getSourceRange(); - } if (var->isConstexpr()) { SmallVector Notes; @@ -10513,6 +10510,31 @@ // initialized by a constant expression if we check later. var->checkInitIsICE(); } + + // Don't emit further diagnostics about constexpr globals since they + // were just diagnosed. + if (!var->isConstexpr() && GlobalStorage && + var->hasAttr()) { + auto *CE = dyn_cast(Init); + bool DiagErr = (var->isInitKnownICE() || (CE && CE->getConstructor()->isConstexpr())) + ? !var->checkInitIsICE() : !checkConstInit(); + if (DiagErr) + Diag(var->getLocation(), diag::err_require_constant_init_failed) + << Init->getSourceRange(); + } + else if (!var->isConstexpr() && IsGlobal && + !getDiagnostics().isIgnored(diag::warn_global_constructor, + var->getLocation())) { + // Warn about globals which don't have a constant initializer. Don't + // warn about globals with a non-trivial destructor because we already + // warned about them. + CXXRecordDecl *RD = baseType->getAsCXXRecordDecl(); + if (!(RD && !RD->hasTrivialDestructor())) { + if (!checkConstInit()) + Diag(var->getLocation(), diag::warn_global_constructor) + << Init->getSourceRange(); + } + } } // Require the destructor. Index: lib/Sema/SemaDeclAttr.cpp =================================================================== --- lib/Sema/SemaDeclAttr.cpp +++ lib/Sema/SemaDeclAttr.cpp @@ -5629,6 +5629,9 @@ case AttributeList::AT_VecTypeHint: handleVecTypeHint(S, D, Attr); break; + case AttributeList::AT_RequireConstantInit: + handleSimpleAttribute(S, D, Attr); + break; case AttributeList::AT_InitPriority: handleInitPriorityAttr(S, D, Attr); break; Index: test/SemaCXX/attr-require-constant-initialization.cpp =================================================================== --- /dev/null +++ test/SemaCXX/attr-require-constant-initialization.cpp @@ -0,0 +1,254 @@ +// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -DTEST_ONE -std=c++03 %s +// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -DTEST_ONE -std=c++11 %s +// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -DTEST_ONE -std=c++14 %s +// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -DTEST_TWO \ +// RUN: -Wglobal-constructors -std=c++14 %s + +#if !__has_feature(cxx_static_assert) +# define CONCAT_(X_, Y_) CONCAT1_(X_, Y_) +# define CONCAT1_(X_, Y_) X_ ## Y_ + +// This emulation can be used multiple times on one line (and thus in +// a macro), except at class scope +# define static_assert(b_, m_) \ + typedef int CONCAT_(sa_, __LINE__)[b_ ? 1 : -1] +#endif + +#define ATTR __attribute__((require_constant_initialization)) + +// Test diagnostics when attribute is applied to non-static declarations. +void test_func_local(ATTR int param) { // expected-error {{only applies to variables with static or thread-local}} + ATTR int x = 42; // expected-error {{only applies to variables with static or thread-local}} + ATTR extern int y; +} +struct ATTR class_mem { // expected-error {{only applies to variables with static or thread-local}} + ATTR int x; // expected-error {{only applies to variables with static or thread-local}} +}; + +int ReturnInt(); + +struct PODType { + int value; + int value2; +}; +#if __cplusplus >= 201103L +struct LitType { + constexpr LitType() : value(0) {} + constexpr LitType(int x) : value(x) {} + LitType(void*) : value(-1) {} + int value; +}; +#endif + +struct NonLit { +#if __cplusplus >= 201402L + constexpr NonLit() : value(0) {} + constexpr NonLit(int x ) : value(x) {} +#else + NonLit() : value(0) {} + NonLit(int x) : value(x) {} +#endif + NonLit(void*) : value(-1) {} + ~NonLit() {} + int value; +}; + +struct StoresNonLit { +#if __cplusplus >= 201402L + constexpr StoresNonLit() : obj() {} + constexpr StoresNonLit(int x) : obj(x) {} +#else + StoresNonLit() : obj() {} + StoresNonLit(int x) : obj(x) {} +#endif + StoresNonLit(void* p) : obj(p) {} + NonLit obj; +}; + +const bool NonLitHasConstInit = +#if __cplusplus >= 201402L + true; +#else + false; +#endif + +#if defined(TEST_ONE) // Test semantics of attribute + +// [basic.start.static]p2.1 +// if each full-expression (including implicit conversions) that appears in +// the initializer of a reference with static or thread storage duration is +// a constant expression (5.20) and the reference is bound to a glvalue +// designating an object with static storage duration, to a temporary object +// (see 12.2) or subobject thereof, or to a function; + +// Test binding to a static glvalue +const int glvalue_int = 42; +const int glvalue_int2 = ReturnInt(); +ATTR const int& glvalue_ref ATTR = glvalue_int; +ATTR const int& glvalue_ref2 ATTR = glvalue_int2; +ATTR __thread const int& glvalue_ref_tl = glvalue_int; + +void test_basic_start_static_2_1() { + const int non_global = 42; + ATTR static const int& local_init = non_global; // expected-error {{variable does not have a constant initializer}} + ATTR static const int& global_init = glvalue_int; + ATTR static const int& temp_init = 42; +#if 0 + /// FIXME: Why is this failing? + __thread const int& tl_init = 42; + static_assert(__has_constant_initializer(tl_init), ""); +#endif +} + +ATTR const int& temp_ref = 42; +ATTR const int& temp_ref2 = ReturnInt(); // expected-error {{variable does not have a constant initializer}} +ATTR const NonLit& nl_temp_ref = 42; // expected-error {{variable does not have a constant initializer}} + +#if __cplusplus >= 201103L +ATTR const LitType& lit_temp_ref = 42; +ATTR const int& subobj_ref = LitType{}.value; +#endif + +ATTR const int& nl_subobj_ref = NonLit().value; // expected-error {{variable does not have a constant initializer}} + +struct TT1 { + ATTR static const int& no_init; + ATTR static const int& glvalue_init; + ATTR static const int& temp_init; + ATTR static const int& subobj_init; +#if __cplusplus >= 201103L + ATTR static thread_local const int& tl_glvalue_init; + ATTR static thread_local const int& tl_temp_init; +#endif +}; +const int& TT1::glvalue_init = glvalue_int; +const int& TT1::temp_init = 42; +const int& TT1::subobj_init = PODType().value; +#if __cplusplus >= 201103L +thread_local const int& TT1::tl_glvalue_init = glvalue_int; +thread_local const int& TT1::tl_temp_init = 42; // expected-error {{variable does not have a constant initializer}} +#endif + +// [basic.start.static]p2.2 +// if an object with static or thread storage duration is initialized by a +// constructor call, and if the initialization full-expression is a constant +// initializer for the object; + +void test_basic_start_static_2_2() +{ + ATTR static PODType pod; + ATTR static PODType pot2 = {ReturnInt()}; // expected-error {{variable does not have a constant initializer}} + +#if __cplusplus >= 201103L + constexpr LitType l; + ATTR static LitType static_lit = l; + ATTR static LitType static_lit2 = (void*)0; // expected-error {{variable does not have a constant initializer}} + ATTR static LitType static_lit3 = ReturnInt(); // expected-error {{variable does not have a constant initializer}} + ATTR thread_local LitType tls = 42; +#endif +} + +struct TT2 { + ATTR static PODType pod_noinit; + ATTR static PODType pod_copy_init; +#if __cplusplus >= 201103L + ATTR static constexpr LitType lit = {}; + ATTR static const NonLit non_lit; + ATTR static const NonLit non_lit_list_init; + ATTR static const NonLit non_lit_copy_init; +#endif +}; +PODType TT2::pod_noinit; +PODType TT2::pod_copy_init(TT2::pod_noinit); // expected-error {{variable does not have a constant initializer}} +#if __cplusplus >= 201402L +const NonLit TT2::non_lit(42); +const NonLit TT2::non_lit_list_init = {42}; +const NonLit TT2::non_lit_copy_init = 42; // expected-error {{variable does not have a constant initializer}} +#endif + +#if __cplusplus >= 201103L +ATTR LitType lit_ctor; +ATTR LitType lit_ctor2{}; +ATTR LitType lit_ctor3 = {}; +ATTR __thread LitType lit_ctor_tl = {}; + +#if __cplusplus >= 201402L +ATTR NonLit nl_ctor; +ATTR NonLit nl_ctor2{}; +ATTR NonLit nl_ctor3 = {}; +ATTR thread_local NonLit nl_ctor_tl = {}; +ATTR StoresNonLit snl; +#else +ATTR NonLit nl_ctor; // expected-error {{variable does not have a constant initializer}} +ATTR NonLit nl_ctor2{}; // expected-error {{variable does not have a constant initializer}} +ATTR NonLit nl_ctor3 = {}; // expected-error {{variable does not have a constant initializer}} +ATTR thread_local NonLit nl_ctor_tl = {}; // expected-error {{variable does not have a constant initializer}} +ATTR StoresNonLit snl; // expected-error {{variable does not have a constant initializer}} +#endif + +// Non-literal types cannot appear in the initializer of a non-literal type. +ATTR int nl_in_init = NonLit{42}.value; // expected-error {{variable does not have a constant initializer}} +ATTR int lit_in_init = LitType{42}.value; +#endif + +// [basic.start.static]p2.3 +// if an object with static or thread storage duration is not initialized by a +// constructor call and if either the object is value-initialized or every +// full-expression that appears in its initializer is a constant expression. +void test_basic_start_static_2_3() +{ + ATTR static int static_local = 42; + ATTR static int static_local2; // zero-initialization takes place +#if __cplusplus >= 201103L + ATTR thread_local int tl_local = 42; +#endif +} + +ATTR int no_init; // zero initialization takes place +ATTR int arg_init = 42; +ATTR PODType pod_init = {}; +ATTR PODType pod_missing_init = {42 /* should have second arg */}; +ATTR PODType pod_full_init = {1, 2}; +ATTR PODType pod_non_constexpr_init = {1, ReturnInt()}; // expected-error {{variable does not have a constant initializer}} + +#if __cplusplus >= 201103L +ATTR int val_init{}; +ATTR int brace_init = {}; +#endif + +ATTR __thread int tl_init = 0; + +#if __cplusplus >= 201103L + +// Test that the validity of the selected constructor is checked, not just the +// initializer +struct NotC { constexpr NotC(void*) {} NotC(int) {} }; +template +struct TestCtor { + constexpr TestCtor(int x) : value(x) {} + T value; +}; +ATTR TestCtor t(42); // expected-error {{variable does not have a constant initializer}} +#endif + +#elif defined(TEST_TWO) // Test for duplicate warnings +struct NotC { + constexpr NotC(void*) {} + NotC(int) {} // expected-note 2 {{declared here}} +}; +template +struct TestCtor { + constexpr TestCtor(int x) : value(x) {} // expected-note 2 {{non-constexpr constructor}} + T value; +}; + +ATTR NonLit non_const( (void*)0); // expected-error {{variable does not have a constant initializer}} +NonLit const_init{42}; // expected-warning {{declaration requires a global destructor}} +constexpr TestCtor inval_constexpr(42); // expected-error {{must be initialized by a constant expression}} +// expected-note@-1 {{in call to 'TestCtor(42)'}} +ATTR constexpr TestCtor inval_constexpr2(42); // expected-error {{must be initialized by a constant expression}} +// expected-note@-1 {{in call to 'TestCtor(42)'}} + +#else +#error No test case specified +#endif // defined(TEST_N)