Skip to content

Commit

Permalink
Implement __attribute__((require_constant_initialization)) for safe s…
Browse files Browse the repository at this point in the history
…tatic initialization.

Summary:
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 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.

```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.

Reviewers: majnemer, rsmith, aaron.ballman

Subscribers: jroelofs, cfe-commits

Differential Revision: https://reviews.llvm.org/D23385

llvm-svn: 280525
  • Loading branch information
EricWF committed Sep 2, 2016
1 parent a27eecc commit 341e825
Show file tree
Hide file tree
Showing 8 changed files with 393 additions and 27 deletions.
10 changes: 10 additions & 0 deletions clang/include/clang/Basic/Attr.td
Expand Up @@ -241,6 +241,7 @@ def MicrosoftExt : LangOpt<"MicrosoftExt">;
def Borland : LangOpt<"Borland">;
def CUDA : LangOpt<"CUDA">;
def COnly : LangOpt<"CPlusPlus", 1>;
def CPlusPlus : LangOpt<"CPlusPlus">;
def OpenCL : LangOpt<"OpenCL">;
def RenderScript : LangOpt<"RenderScript">;

Expand Down Expand Up @@ -1380,6 +1381,15 @@ def ReqdWorkGroupSize : InheritableAttr {
let Documentation = [Undocumented];
}

def RequireConstantInit : InheritableAttr {
let Spellings = [GNU<"require_constant_initialization">,
CXX11<"clang", "require_constant_initialization">];
let Subjects = SubjectList<[GlobalVar], ErrorDiag,
"ExpectedStaticOrTLSVar">;
let Documentation = [RequireConstantInitDocs];
let LangOpts = [CPlusPlus];
}

def WorkGroupSizeHint : InheritableAttr {
let Spellings = [GNU<"work_group_size_hint">];
let Args = [UnsignedArgument<"XDim">,
Expand Down
37 changes: 37 additions & 0 deletions clang/include/clang/Basic/AttrDocs.td
Expand Up @@ -829,6 +829,43 @@ When one method overrides another, the overriding method can be more widely avai
}];
}


def RequireConstantInitDocs : Documentation {
let Category = DocCatVariable;
let Content = [{
This attribute specifies that the variable to which it is attached is intended
to have a `constant initializer <http://en.cppreference.com/w/cpp/language/constant_initialization>`_
according to the rules of [basic.start.static]. The variable is required to
have static or thread storage duration. If the initialization of the variable
is not a constant initializer, an error will be produced. This attribute may
only be used in C++.

Note that in C++03 strict constant expression checking is not done. Instead
the attribute reports if Clang can emit the the variable as a constant, even
if it's not technically a 'constant initializer'. This behavior is non-portable.

Static storage duration variables with constant initializers avoid hard-to-find
bugs caused by the indeterminate order of dynamic initialization. They can also
be safely used during dynamic initialization 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(); // non-trivial
};
SAFE_STATIC T x = {42}; // Initialization OK. Doesn't check destructor.
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.
}];
}

def WarnMaybeUnusedDocs : Documentation {
let Category = DocCatVariable;
let Heading = "maybe_unused, unused, gnu::unused";
Expand Down
9 changes: 7 additions & 2 deletions clang/include/clang/Basic/DiagnosticSemaKinds.td
Expand Up @@ -2572,7 +2572,7 @@ def warn_attribute_wrong_decl_type : Warning<
"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 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|"
Expand Down Expand Up @@ -6839,7 +6839,12 @@ def note_inequality_comparison_to_or_assign : Note<

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 note_declared_required_constant_init_here : Note<
"required by 'require_constant_initializer' attribute here">;

def err_dimension_expr_not_constant_integer : Error<
"dimension expression does not evaluate to a constant unsigned int">;

Expand Down
1 change: 1 addition & 0 deletions clang/include/clang/Sema/AttributeList.h
Expand Up @@ -897,6 +897,7 @@ enum AttributeDeclKind {
ExpectedFunctionVariableOrClass,
ExpectedFunctionVariableClassOrObjCInterface,
ExpectedObjectiveCProtocol,
ExpectedStaticOrTLSVar,
ExpectedFunctionGlobalVarMethodOrProperty,
ExpectedStructOrUnionOrTypedef,
ExpectedStructOrTypedef,
Expand Down
74 changes: 50 additions & 24 deletions clang/lib/Sema/SemaDecl.cpp
Expand Up @@ -10393,26 +10393,35 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
Diag(var->getLocation(), diag::warn_missing_variable_declarations) << var;
}

// Cache the result of checking for constant initialization.
Optional<bool> 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
// have a non-trivial destructor.
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);
}
}
}

Expand Down Expand Up @@ -10486,18 +10495,6 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {

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<PartialDiagnosticAt, 8> Notes;
Expand All @@ -10521,6 +10518,35 @@ void Sema::CheckCompleteVariableDeclaration(VarDecl *var) {
// 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<RequireConstantInitAttr>()) {
// FIXME: Need strict checking in C++03 here.
bool DiagErr = getLangOpts().CPlusPlus11
? !var->checkInitIsICE() : !checkConstInit();
if (DiagErr) {
auto attr = var->getAttr<RequireConstantInitAttr>();
Diag(var->getLocation(), diag::err_require_constant_init_failed)
<< Init->getSourceRange();
Diag(attr->getLocation(), diag::note_declared_required_constant_init_here)
<< attr->getRange();
}
}
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.
Expand Down
3 changes: 3 additions & 0 deletions clang/lib/Sema/SemaDeclAttr.cpp
Expand Up @@ -5630,6 +5630,9 @@ static void ProcessDeclAttribute(Sema &S, Scope *scope, Decl *D,
case AttributeList::AT_VecTypeHint:
handleVecTypeHint(S, D, Attr);
break;
case AttributeList::AT_RequireConstantInit:
handleSimpleAttribute<RequireConstantInitAttr>(S, D, Attr);
break;
case AttributeList::AT_InitPriority:
handleInitPriorityAttr(S, D, Attr);
break;
Expand Down

0 comments on commit 341e825

Please sign in to comment.