EricWF retitled this revision from "Implement __has_constant_initializer(obj) 
expression traits." to "Implement 
__attribute__((require_constant_initialization)) attribute".
EricWF updated the summary for this revision.
EricWF updated this revision to Diff 67821.
EricWF added a comment.

Switch to an attribute based implementation as suggested by @rsmith.


https://reviews.llvm.org/D23385

Files:
  include/clang/Basic/Attr.td
  include/clang/Basic/AttrDocs.td
  include/clang/Basic/DiagnosticSemaKinds.td
  lib/Sema/SemaDecl.cpp
  lib/Sema/SemaDeclAttr.cpp
  test/Lexer/has_extension_cxx.cpp
  test/SemaCXX/attr-require-constant-initialization.cpp

Index: test/SemaCXX/attr-require-constant-initialization.cpp
===================================================================
--- /dev/null
+++ test/SemaCXX/attr-require-constant-initialization.cpp
@@ -0,0 +1,221 @@
+// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -std=c++03 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -std=c++11 %s
+// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -std=c++14 %s
+//
+// Tests for "expression traits" intrinsics such as __is_lvalue_expr.
+//
+
+#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
+
+int ReturnInt();
+
+#define ATTR __attribute__((require_constant_initialization))
+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
+
+// [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; // expected-error {{variable does not have a constant initializer}}
+#if __cplusplus >= 201103L
+    ATTR thread_local int tl_local = 42;
+#endif
+}
+
+ATTR int no_init; // expected-error {{variable does not have a constant initializer}}
+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 <class T>
+struct TestCtor {
+  constexpr TestCtor(int x) : value(x) {}
+  T value;
+};
+ATTR TestCtor<NotC> t(42); // expected-error {{variable does not have a constant initializer}}
+#endif
Index: test/Lexer/has_extension_cxx.cpp
===================================================================
--- test/Lexer/has_extension_cxx.cpp
+++ test/Lexer/has_extension_cxx.cpp
@@ -66,3 +66,8 @@
 #if __has_extension(cxx_init_captures)
 int has_init_captures();
 #endif
+
+// CHECK: has_constant_initializer
+#if __has_extension(has_constant_initializer)
+int has_constant_initializer();
+#endif
Index: lib/Sema/SemaDeclAttr.cpp
===================================================================
--- lib/Sema/SemaDeclAttr.cpp
+++ lib/Sema/SemaDeclAttr.cpp
@@ -2819,6 +2819,17 @@
     .Default(InvalidFormat);
 }
 
+static void handleRequireConstantInitAttr(Sema &S, Decl *D,
+                                          const AttributeList &Attr) {
+  if (!cast<VarDecl>(D)->hasGlobalStorage()) {
+    S.Diag(Attr.getLoc(), diag::err_require_constant_init_var_not_static_tls)
+     << D->getSourceRange();
+    return;
+  }
+  D->addAttr(::new (S.Context) RequireConstantInitAttr(
+      Attr.getRange(), S.Context, Attr.getAttributeSpellingListIndex()));
+}
+
 /// Handle __attribute__((init_priority(priority))) attributes based on
 /// http://gcc.gnu.org/onlinedocs/gcc/C_002b_002b-Attributes.html
 static void handleInitPriorityAttr(Sema &S, Decl *D,
@@ -5629,6 +5640,9 @@
   case AttributeList::AT_VecTypeHint:
     handleVecTypeHint(S, D, Attr);
     break;
+  case AttributeList::AT_RequireConstantInit:
+    handleRequireConstantInitAttr(S, D, Attr);
+    break;
   case AttributeList::AT_InitPriority:
     handleInitPriorityAttr(S, D, Attr);
     break;
Index: lib/Sema/SemaDecl.cpp
===================================================================
--- lib/Sema/SemaDecl.cpp
+++ lib/Sema/SemaDecl.cpp
@@ -10385,6 +10385,9 @@
       Diag(var->getLocation(), diag::warn_missing_variable_declarations) << var;
   }
 
+  // Cache the result of checking for constant initialization.
+  Optional<bool> HasConstInit;
+
   if (var->getTLSKind() == VarDecl::TLS_Static) {
     const Expr *Culprit;
     if (var->getType().isDestructedType()) {
@@ -10394,17 +10397,19 @@
       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()) {
+      HasConstInit = var->getInit()->isConstantInitializer(
+                   Context, var->getType()->isReferenceType(), &Culprit);
+      if (!*HasConstInit) {
+        // 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);
+      }
     }
   }
 
@@ -10476,6 +10481,9 @@
   bool IsGlobal = GlobalStorage && !var->isStaticLocal();
   QualType baseType = Context.getBaseElementType(type);
 
+  if (var->hasAttr<RequireConstantInitAttr>() && !Init)
+     Diag(var->getLocation(), diag::err_require_constant_init_failed);
+
   if (!var->getDeclContext()->isDependentContext() &&
       Init && !Init->isValueDependent()) {
     if (IsGlobal && !var->isConstexpr() &&
@@ -10485,10 +10493,13 @@
       // 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 (!(RD && !RD->hasTrivialDestructor())) {
+        if (!HasConstInit)
+          HasConstInit = Init->isConstantInitializer(Context, baseType->isReferenceType());
+        if (!*HasConstInit)
+          Diag(var->getLocation(), diag::warn_global_constructor)
+            << Init->getSourceRange();
+      }
     }
 
     if (var->isConstexpr()) {
@@ -10513,6 +10524,21 @@
       // initialized by a constant expression if we check later.
       var->checkInitIsICE();
     }
+
+    if (GlobalStorage && var->hasAttr<RequireConstantInitAttr>()) {
+      bool ShouldDiagnose = false;
+      auto *CE = dyn_cast<CXXConstructExpr>(Init);
+      if (var->isInitKnownICE() || (CE && CE->getConstructor()->isConstexpr())) {
+        ShouldDiagnose = !var->checkInitIsICE();
+      } else {
+          HasConstInit = HasConstInit ? *HasConstInit
+            :  Init->isConstantInitializer(Context, baseType->isReferenceType());
+        ShouldDiagnose = !*HasConstInit;
+      }
+      if (ShouldDiagnose)
+        Diag(var->getLocation(), diag::err_require_constant_init_failed)
+          << Init->getSourceRange();
+    }
   }
 
   // Require the destructor.
Index: include/clang/Basic/DiagnosticSemaKinds.td
===================================================================
--- include/clang/Basic/DiagnosticSemaKinds.td
+++ include/clang/Basic/DiagnosticSemaKinds.td
@@ -6837,7 +6837,13 @@
 
 def err_incomplete_type_used_in_type_trait_expr : Error<
   "incomplete type %0 used in type trait expression">;
-  
+
+def err_require_constant_init_var_not_static_tls : Error<
+  "'require_constant_initialization' attribute can only be applied to variables "
+  "static or thread-local storage duration">;
+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/Basic/AttrDocs.td
===================================================================
--- include/clang/Basic/AttrDocs.td
+++ include/clang/Basic/AttrDocs.td
@@ -829,6 +829,42 @@
   }];
 }
 
+
+def RequireConstantInitDocs : Documentation {
+  let Category = DocCatVariable;
+  let Heading = "require_constant_initialization";
+  let Content = [{
+This attribute specifies expectations about the initialization of static and
+thread local variables. Specifically that the variable has a
+`constant initializer <http://en.cppreference.com/w/cpp/language/constant_initialization>`_
+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 an 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 +881,12 @@
 enumerator, a non-static data member, or a label.
 
 .. code-block: c++
-  #include <cassert>
-
-  [[maybe_unused]] void f([[maybe_unused]] bool thing1,
-                          [[maybe_unused]] bool thing2) {
-    [[maybe_unused]] bool b = thing1 && thing2;
-    assert(b);
+  #include <cassert>
+
+  [[maybe_unused]] void f([[maybe_unused]] bool thing1,
+                          [[maybe_unused]] bool thing2) {
+    [[maybe_unused]] bool b = thing1 && thing2;
+    assert(b);
   }
   }];
 }
@@ -867,15 +903,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/Attr.td
===================================================================
--- include/clang/Basic/Attr.td
+++ include/clang/Basic/Attr.td
@@ -1380,6 +1380,12 @@
   let Documentation = [Undocumented];
 }
 
+def RequireConstantInit : InheritableAttr {
+  let Spellings = [GCC<"require_constant_initialization">];
+  let Subjects = SubjectList<[Var]>;
+  let Documentation = [RequireConstantInitDocs];
+}
+
 def WorkGroupSizeHint :  InheritableAttr {
   let Spellings = [GNU<"work_group_size_hint">];
   let Args = [UnsignedArgument<"XDim">, 
_______________________________________________
cfe-commits mailing list
cfe-commits@lists.llvm.org
http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits

Reply via email to