EricWF created this revision. EricWF added reviewers: aaron.ballman, rsmith, majnemer. EricWF added a subscriber: cfe-commits.
Safe static initialization is hard to do correctly in C++. The static initialization order fiasco often causes bugs when variables are dynamically initialized. However some variables are 'constant initialized', which guarantees they will not have these issues. Unfortunately there is no easy way to check if 'constant initialization' actually takes place. This patch adds a `__has_constant_initializer(<expr>)` expression trait, which returns 'true' if `<expr>` names a VarDecl which has a constant initializer. `__has_constant_initializer(<expr>)` requires `<expr>` be a DeclRefExpr, Anything else is diagnosed as a usage error. Example Usage: ``` struct NonLit { constexpr NonLit(int x) : value(x) {} ~NonLit() {} int value; }; NonLit global_obj = 42; static_assert(__has_constant_initializer(global_obj), "dynamic initialization is not acceptable!"); ``` https://reviews.llvm.org/D23385 Files: include/clang/AST/Expr.h include/clang/Basic/DiagnosticSemaKinds.td include/clang/Basic/ExpressionTraits.h include/clang/Basic/TokenKinds.def lib/AST/Expr.cpp lib/AST/StmtPrinter.cpp lib/Parse/ParseDeclCXX.cpp lib/Parse/ParseExpr.cpp lib/Parse/ParseExprCXX.cpp lib/Sema/SemaExprCXX.cpp test/SemaCXX/expression-traits.cpp
Index: test/SemaCXX/expression-traits.cpp =================================================================== --- test/SemaCXX/expression-traits.cpp +++ test/SemaCXX/expression-traits.cpp @@ -1,5 +1,5 @@ // RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions %s - +// RUN: %clang_cc1 -fsyntax-only -verify -fcxx-exceptions -std=c++14 %s // // Tests for "expression traits" intrinsics such as __is_lvalue_expr. // @@ -55,6 +55,12 @@ "the presence of parentheses should have" \ " no effect on lvalueness (expr.prim/5)") +#define ASSERT_CIT(expr) \ + static_assert(__has_constant_initializer(expr), "should be constant") + +#define ASSERT_NOT_CIT(expr) \ + static_assert(!__has_constant_initializer(expr), "should not be constant"); + enum Enum { Enumerator }; int ReturnInt(); @@ -617,3 +623,219 @@ { check_temp_param_6<3,AnInt>(); } + +//===========================================================================// +// __has_constant_initializer +//===========================================================================// +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 + +// Test for diagnostics when the argument does reference a named identifier +void check_is_constant_init_bogus() +{ + (void)__has_constant_initializer(42); // expected-error {{does not reference a named variable}} + (void)__has_constant_initializer(ReturnInt()); // expected-error {{does not reference a named variable}} + (void)__has_constant_initializer(42, 43); // expected-error {{does not reference a named variable}} +} + +// [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(); +const int& glvalue_ref = glvalue_int; +const int& glvalue_ref2 = glvalue_int2; +static_assert(__has_constant_initializer(glvalue_ref), ""); +static_assert(__has_constant_initializer(glvalue_ref2), ""); + +__thread const int& glvalue_ref_tl = glvalue_int; +static_assert(__has_constant_initializer(glvalue_ref_tl), ""); + +void test_basic_start_static_2_1() { + const int non_global = 42; + const int& non_global_ref = non_global; + static_assert(!__has_constant_initializer(non_global), "automatic variables never have const init"); + static_assert(!__has_constant_initializer(non_global_ref), "automatic variables never have const init"); + + static const int& local_init = non_global; + static_assert(!__has_constant_initializer(local_init), "init must be static glvalue"); + static const int& global_init = glvalue_int; + static_assert(__has_constant_initializer(global_init), ""); + static const int& temp_init = 42; + static_assert(__has_constant_initializer(temp_init), ""); +#if 0 + /// FIXME: Why is this failing? + __thread const int& tl_init = 42; + static_assert(__has_constant_initializer(tl_init), ""); +#endif +} + +const int& temp_ref = 42; +const int& temp_ref2 = ReturnInt(); +static_assert(__has_constant_initializer(temp_ref), ""); +static_assert(!__has_constant_initializer(temp_ref2), ""); + +const NonLit& nl_temp_ref = 42; +static_assert(!__has_constant_initializer(nl_temp_ref), ""); + +#if __cplusplus >= 201103L +const LitType& lit_temp_ref = 42; +static_assert(__has_constant_initializer(lit_temp_ref), ""); + +const int& subobj_ref = LitType{}.value; +static_assert(__has_constant_initializer(subobj_ref), ""); +#endif + +const int& nl_subobj_ref = NonLit().value; +static_assert(!__has_constant_initializer(nl_subobj_ref), ""); + +// [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; +#if __cplusplus >= 201103L +void test_basic_start_static_2_2() +{ + constexpr LitType l; + static_assert(!__has_constant_initializer(l), "non-static objects don't have const init"); + + static LitType static_lit = l; + static_assert(__has_constant_initializer(static_lit), ""); + + static LitType static_lit2 = (void*)0; + static_assert(!__has_constant_initializer(static_lit2), "constructor not constexpr"); + + static LitType static_lit3 = ReturnInt(); + static_assert(!__has_constant_initializer(static_lit3), "initializer not constexpr"); + +#if __cplusplus >= 201103L + thread_local LitType tls = 42; + static_assert(__has_constant_initializer(tls), ""); +#endif +} + +LitType lit_ctor; +LitType lit_ctor2{}; +LitType lit_ctor3 = {}; +__thread LitType lit_ctor_tl = {}; +static_assert(__has_constant_initializer(lit_ctor), ""); +static_assert(__has_constant_initializer(lit_ctor2), ""); +static_assert(__has_constant_initializer(lit_ctor3), ""); +static_assert(__has_constant_initializer(lit_ctor_tl), ""); + +NonLit nl_ctor; +NonLit nl_ctor2{}; +NonLit nl_ctor3 = {}; +thread_locaare NonLit nl_ctor_tl = {}; +static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor), ""); +static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor2), ""); +static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor3), ""); +static_assert(NonLitHasConstInit == __has_constant_initializer(nl_ctor_tl), ""); + +StoresNonLit snl; +static_assert(NonLitHasConstInit == __has_constant_initializer(snl), ""); + +// Non-literal types cannot appear in the initializer of a non-literal type. +int nl_in_init = NonLit{42}.value; +static_assert(!__has_constant_initializer(nl_in_init), ""); + +int lit_in_init = LitType{42}.value; +static_assert(__has_constant_initializer(lit_in_init), ""); +#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() +{ + const int local = 42; + static_assert(!__has_constant_initializer(local), "automatic variable does not have const init"); + + static int static_local = 42; + static_assert(__has_constant_initializer(static_local), ""); + + static int static_local2; + static_assert(!__has_constant_initializer(static_local2), "no init"); + +#if __cplusplus >= 201103L + thread_local int tl_local = 42; + static_assert(__has_constant_initializer(tl_local), ""); +#endif +} + +int no_init; +static_assert(!__has_constant_initializer(no_init), ""); + +int arg_init = 42; +static_assert(__has_constant_initializer(arg_init), ""); + +PODType pod_init = {}; +static_assert(__has_constant_initializer(pod_init), ""); + +PODType pod_missing_init = {42 /* should have second arg */}; +static_assert(__has_constant_initializer(pod_missing_init), ""); + +PODType pod_full_init = {1, 2}; +static_assert(__has_constant_initializer(pod_full_init), ""); + +PODType pod_non_constexpr_init = {1, ReturnInt()}; +static_assert(!__has_constant_initializer(pod_non_constexpr_init), ""); + +#if __cplusplus >= 201103L +int val_init{}; +static_assert(__has_constant_initializer(val_init), ""); + +int brace_init = {}; +static_assert(__has_constant_initializer(brace_init), ""); +#endif + +__thread int tl_init = 0; +static_assert(__has_constant_initializer(tl_init), ""); Index: lib/Sema/SemaExprCXX.cpp =================================================================== --- lib/Sema/SemaExprCXX.cpp +++ lib/Sema/SemaExprCXX.cpp @@ -4749,10 +4749,34 @@ return Result; } -static bool EvaluateExpressionTrait(ExpressionTrait ET, Expr *E) { +static bool EvaluateExpressionTrait(Sema &Self, ExpressionTrait ET, + SourceLocation KWLoc, Expr *E, + SourceLocation RParen) { switch (ET) { case ET_IsLValueExpr: return E->isLValue(); case ET_IsRValueExpr: return E->isRValue(); + case ET_HasConstantInitializer: { + DeclRefExpr *DRE = dyn_cast<DeclRefExpr>(E->IgnoreImpCasts()); + if (!DRE) { + // It is a usage error to specify and expression that does not reference + // a named variable. + Self.Diag(KWLoc, diag::err_has_constant_init_expression_trait_invalid_arg) + << E->getSourceRange(); + return false; + } + if (VarDecl *VD = dyn_cast<VarDecl>(DRE->getDecl())) { + // Thread local objects have TSL_Static if they have a constant + // initializer + if (VD->getTLSKind() != VarDecl::TLS_None) + return VD->getTLSKind() == VarDecl::TLS_Static; + else if (VD->hasGlobalStorage() && VD->hasInit()) { + QualType baseType = Self.Context.getBaseElementType(VD->getType()); + return VD->getInit()->isConstantInitializer(Self.Context, + baseType->isReferenceType(), nullptr, /*AllowNonLiteral=*/true); + } + } + return false; + } } llvm_unreachable("Expression trait not covered by switch"); } @@ -4769,7 +4793,7 @@ return BuildExpressionTrait(ET, KWLoc, PE.get(), RParen); } - bool Value = EvaluateExpressionTrait(ET, Queried); + bool Value = EvaluateExpressionTrait(*this, ET, KWLoc, Queried, RParen); return new (Context) ExpressionTraitExpr(KWLoc, ET, Queried, Value, RParen, Context.BoolTy); Index: lib/Parse/ParseExprCXX.cpp =================================================================== --- lib/Parse/ParseExprCXX.cpp +++ lib/Parse/ParseExprCXX.cpp @@ -2946,9 +2946,11 @@ } } + static ExpressionTrait ExpressionTraitFromTokKind(tok::TokenKind kind) { switch(kind) { default: llvm_unreachable("Not a known unary expression trait."); + case tok::kw___has_constant_initializer: return ET_HasConstantInitializer; case tok::kw___is_lvalue_expr: return ET_IsLValueExpr; case tok::kw___is_rvalue_expr: return ET_IsRValueExpr; } @@ -3083,6 +3085,7 @@ if (T.expectAndConsume()) return ExprError(); + EnterExpressionEvaluationContext Unevaluated(Actions, Sema::Unevaluated); ExprResult Expr = ParseExpression(); T.consumeClose(); Index: lib/Parse/ParseExpr.cpp =================================================================== --- lib/Parse/ParseExpr.cpp +++ lib/Parse/ParseExpr.cpp @@ -677,11 +677,14 @@ /// '__trivially_copyable' /// /// binary-type-trait: -/// [GNU] '__is_base_of' +/// [GNU] '__is_base_of' /// [MS] '__is_convertible_to' /// '__is_convertible' /// '__is_same' /// +/// [Clang] expression-trait: +/// '__has_constant_initializer' +/// /// [Embarcadero] array-type-trait: /// '__array_rank' /// '__array_extent' @@ -797,6 +800,7 @@ RevertibleTypeTraits[PP.getIdentifierInfo(#Name)] \ = RTT_JOIN(tok::kw_,Name) + REVERTIBLE_TYPE_TRAIT(__has_constant_initializer); REVERTIBLE_TYPE_TRAIT(__is_abstract); REVERTIBLE_TYPE_TRAIT(__is_arithmetic); REVERTIBLE_TYPE_TRAIT(__is_array); @@ -1321,10 +1325,11 @@ case tok::kw___array_extent: return ParseArrayTypeTrait(); + case tok::kw___has_constant_initializer: case tok::kw___is_lvalue_expr: case tok::kw___is_rvalue_expr: return ParseExpressionTrait(); - + case tok::at: { SourceLocation AtLoc = ConsumeToken(); return ParseObjCAtExpression(AtLoc); Index: lib/Parse/ParseDeclCXX.cpp =================================================================== --- lib/Parse/ParseDeclCXX.cpp +++ lib/Parse/ParseDeclCXX.cpp @@ -1273,7 +1273,8 @@ Tok.isNot(tok::identifier) && !Tok.isAnnotation() && Tok.getIdentifierInfo() && - Tok.isOneOf(tok::kw___is_abstract, + Tok.isOneOf(tok::kw___has_constant_initializer, + tok::kw___is_abstract, tok::kw___is_arithmetic, tok::kw___is_array, tok::kw___is_assignable, @@ -3667,8 +3668,8 @@ return true; case AttributeList::AT_WarnUnusedResult: return !ScopeName && AttrName->getName().equals("nodiscard"); - case AttributeList::AT_Unused: - return !ScopeName && AttrName->getName().equals("maybe_unused"); + case AttributeList::AT_Unused: + return !ScopeName && AttrName->getName().equals("maybe_unused"); default: return false; } Index: lib/AST/StmtPrinter.cpp =================================================================== --- lib/AST/StmtPrinter.cpp +++ lib/AST/StmtPrinter.cpp @@ -2321,8 +2321,9 @@ static const char *getExpressionTraitName(ExpressionTrait ET) { switch (ET) { - case ET_IsLValueExpr: return "__is_lvalue_expr"; - case ET_IsRValueExpr: return "__is_rvalue_expr"; + case ET_IsLValueExpr: return "__is_lvalue_expr"; + case ET_IsRValueExpr: return "__is_rvalue_expr"; + case ET_HasConstantInitializer: return "__has_constant_initializer"; } llvm_unreachable("Expression type trait not covered by switch"); } Index: lib/AST/Expr.cpp =================================================================== --- lib/AST/Expr.cpp +++ lib/AST/Expr.cpp @@ -2612,7 +2612,8 @@ } bool Expr::isConstantInitializer(ASTContext &Ctx, bool IsForRef, - const Expr **Culprit) const { + const Expr **Culprit, + bool AllowNonLiteral) const { // This function is attempting whether an expression is an initializer // which can be evaluated at compile-time. It very closely parallels // ConstExprEmitter in CGExprConstant.cpp; if they don't match, it @@ -2649,7 +2650,17 @@ assert(CE->getNumArgs() == 1 && "trivial ctor with > 1 argument"); return CE->getArg(0)->isConstantInitializer(Ctx, false, Culprit); } - + if (CE->getConstructor()->isConstexpr() && + (CE->getConstructor()->getParent()->hasTrivialDestructor() || + AllowNonLiteral)) { + if (!CE->getNumArgs()) return true; + unsigned numArgs = CE->getNumArgs(); + for (unsigned i = 0; i < numArgs; i++) { + if (!CE->getArg(i)->isConstantInitializer(Ctx, false, Culprit)) + return false; + } + return true; + } break; } case CompoundLiteralExprClass: { Index: include/clang/Basic/TokenKinds.def =================================================================== --- include/clang/Basic/TokenKinds.def +++ include/clang/Basic/TokenKinds.def @@ -454,6 +454,9 @@ TYPE_TRAIT_2(__is_trivially_assignable, IsTriviallyAssignable, KEYCXX) KEYWORD(__underlying_type , KEYCXX) +// Clang-only C++ Expression Traits +KEYWORD(__has_constant_initializer , KEYCXX) + // Embarcadero Expression Traits KEYWORD(__is_lvalue_expr , KEYCXX) KEYWORD(__is_rvalue_expr , KEYCXX) Index: include/clang/Basic/ExpressionTraits.h =================================================================== --- include/clang/Basic/ExpressionTraits.h +++ include/clang/Basic/ExpressionTraits.h @@ -19,7 +19,8 @@ enum ExpressionTrait { ET_IsLValueExpr, - ET_IsRValueExpr + ET_IsRValueExpr, + ET_HasConstantInitializer }; } Index: include/clang/Basic/DiagnosticSemaKinds.td =================================================================== --- include/clang/Basic/DiagnosticSemaKinds.td +++ include/clang/Basic/DiagnosticSemaKinds.td @@ -6800,7 +6800,10 @@ def err_incomplete_type_used_in_type_trait_expr : Error< "incomplete type %0 used in type trait expression">; - + +def err_has_constant_init_expression_trait_invalid_arg : Error< + "expression does not reference a named variable">; + def err_dimension_expr_not_constant_integer : Error< "dimension expression does not evaluate to a constant unsigned int">; Index: include/clang/AST/Expr.h =================================================================== --- include/clang/AST/Expr.h +++ include/clang/AST/Expr.h @@ -526,7 +526,8 @@ /// If this expression is not constant and Culprit is non-null, /// it is used to store the address of first non constant expr. bool isConstantInitializer(ASTContext &Ctx, bool ForRef, - const Expr **Culprit = nullptr) const; + const Expr **Culprit = nullptr, + bool AllowNonLiteral = false) const; /// EvalStatus is a struct with detailed info about an evaluation in progress. struct EvalStatus {
_______________________________________________ cfe-commits mailing list cfe-commits@lists.llvm.org http://lists.llvm.org/cgi-bin/mailman/listinfo/cfe-commits