diff options
-rw-r--r-- | clang/include/clang/Basic/DiagnosticASTKinds.td | 44 | ||||
-rw-r--r-- | clang/lib/AST/ExprConstant.cpp | 107 | ||||
-rw-r--r-- | clang/lib/AST/Interp/State.h | 1 | ||||
-rw-r--r-- | clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp | 83 |
4 files changed, 201 insertions, 34 deletions
diff --git a/clang/include/clang/Basic/DiagnosticASTKinds.td b/clang/include/clang/Basic/DiagnosticASTKinds.td index 69d30b4eb3f..63207a0e225 100644 --- a/clang/include/clang/Basic/DiagnosticASTKinds.td +++ b/clang/include/clang/Basic/DiagnosticASTKinds.td @@ -38,8 +38,8 @@ def note_constexpr_pure_virtual_call : Note< "pure virtual function %q0 called">; def note_constexpr_polymorphic_unknown_dynamic_type : Note< "%select{|||||virtual function called on|dynamic_cast applied to|" - "typeid applied to|destruction of}0 object '%1' whose dynamic type " - "is not constant">; + "typeid applied to|construction of|destruction of}0 object '%1' " + "whose dynamic type is not constant">; def note_constexpr_dynamic_cast_to_reference_failed : Note< "reference dynamic_cast failed: %select{" "static type %1 of operand is a non-public base class of dynamic type %2|" @@ -121,11 +121,12 @@ def note_constexpr_this : Note< "evaluation of a call to a 'constexpr' member function">; def note_constexpr_lifetime_ended : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|destruction of}0 " - "%select{temporary|variable}1 whose lifetime has ended">; + "member call on|dynamic_cast of|typeid applied to|construction of|" + "destruction of}0 %select{temporary|variable}1 whose " + "%plural{8:storage duration|:lifetime}0 has ended">; def note_constexpr_access_uninit : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|destruction of}0 " + "member call on|dynamic_cast of|typeid applied to|<ERRPR>|destruction of}0 " "%select{object outside its lifetime|uninitialized object}1 " "is not allowed in a constant expression">; def note_constexpr_use_uninit_reference : Note< @@ -136,18 +137,19 @@ def note_constexpr_modify_const_type : Note< "in a constant expression">; def note_constexpr_access_volatile_type : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "<ERROR>|<ERROR>|<ERROR>}0 " + "<ERROR>|<ERROR>|<ERROR>|<ERROR>}0 " "volatile-qualified type %1 is not allowed in a constant expression">; def note_constexpr_access_volatile_obj : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "<ERROR>|<ERROR>|<ERROR>}0 " + "<ERROR>|<ERROR>|<ERROR>|<ERROR>}0 " "volatile %select{temporary|object %2|member %2}1 is not allowed in " "a constant expression">; def note_constexpr_volatile_here : Note< "volatile %select{temporary created|object declared|member declared}0 here">; def note_constexpr_access_mutable : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|destruction of}0 " + "member call on|dynamic_cast of|typeid applied to|construction of|" + "destruction of}0 " "mutable member %1 is not allowed in a constant expression">; def note_constexpr_ltor_non_const_int : Note< "read of non-const variable %0 is not allowed in a constant expression">; @@ -157,35 +159,42 @@ def note_constexpr_ltor_incomplete_type : Note< "read of incomplete type %0 is not allowed in a constant expression">; def note_constexpr_access_null : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|destruction of}0 " + "member call on|dynamic_cast of|typeid applied to|construction of|" + "destruction of}0 " "dereferenced null pointer is not allowed in a constant expression">; def note_constexpr_access_past_end : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|destruction of}0 " + "member call on|dynamic_cast of|typeid applied to|construction of|" + "destruction of}0 " "dereferenced one-past-the-end pointer is not allowed " "in a constant expression">; def note_constexpr_access_unsized_array : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|destruction of}0 " + "member call on|dynamic_cast of|typeid applied to|construction of|" + "destruction of}0 " "element of array without known bound " "is not allowed in a constant expression">; def note_constexpr_access_inactive_union_member : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|destruction of}0 " + "member call on|dynamic_cast of|typeid applied to|" + "construction of subobject of|destruction of}0 " "member %1 of union with %select{active member %3|no active member}2 " "is not allowed in a constant expression">; def note_constexpr_access_static_temporary : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|destruction of}0 temporary " + "member call on|dynamic_cast of|typeid applied to|reconstruction of|" + "destruction of}0 temporary " "is not allowed in a constant expression outside the expression that " "created the temporary">; def note_constexpr_access_unreadable_object : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|destruction of}0 " + "member call on|dynamic_cast of|typeid applied to|construction of|" + "destruction of}0 " "object '%1' whose value is not known">; def note_constexpr_access_deleted_object : Note< "%select{read of|read of|assignment to|increment of|decrement of|" - "member call on|dynamic_cast of|typeid applied to|destruction of}0 " + "member call on|dynamic_cast of|typeid applied to|construction of|" + "destruction of}0 " "heap allocated object that has been deleted">; def note_constexpr_modify_global : Note< "a constant expression cannot modify an object that is visible outside " @@ -255,6 +264,9 @@ def note_constexpr_bit_cast_indet_dest : Note< def note_constexpr_pseudo_destructor : Note< "pseudo-destructor call is not permitted in constant expressions " "until C++20">; +def note_constexpr_construct_complex_elem : Note< + "construction of individual component of complex number is not yet supported " + "in constant expressions">; def note_constexpr_destroy_complex_elem : Note< "destruction of individual component of complex number is not yet supported " "in constant expressions">; @@ -265,6 +277,8 @@ def note_constexpr_new_non_replaceable : Note< "call to %select{placement|class-specific}0 %1">; def note_constexpr_new_placement : Note< "this placement new expression is not yet supported in constant expressions">; +def note_constexpr_placement_new_wrong_type : Note< + "placement new would change type of storage from %0 to %1">; def note_constexpr_new_negative : Note< "cannot allocate array; evaluated array bound %0 is negative">; def note_constexpr_new_too_large : Note< diff --git a/clang/lib/AST/ExprConstant.cpp b/clang/lib/AST/ExprConstant.cpp index 669acd3a666..33b0380fd60 100644 --- a/clang/lib/AST/ExprConstant.cpp +++ b/clang/lib/AST/ExprConstant.cpp @@ -594,6 +594,13 @@ namespace { Frame *getCaller() const override { return Caller; } SourceLocation getCallLocation() const override { return CallLoc; } const FunctionDecl *getCallee() const override { return Callee; } + + bool isStdFunction() const { + for (const DeclContext *DC = Callee; DC; DC = DC->getParent()) + if (DC->isStdNamespace()) + return true; + return false; + } }; /// Temporarily override 'this'. @@ -1395,6 +1402,7 @@ static bool isModification(AccessKinds AK) { case AK_Assign: case AK_Increment: case AK_Decrement: + case AK_Construct: case AK_Destroy: return true; } @@ -1407,7 +1415,7 @@ static bool isAnyAccess(AccessKinds AK) { /// Is this an access per the C++ definition? static bool isFormalAccess(AccessKinds AK) { - return isAnyAccess(AK) && AK != AK_Destroy; + return isAnyAccess(AK) && AK != AK_Construct && AK != AK_Destroy; } namespace { @@ -3170,8 +3178,9 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, // Walk the designator's path to find the subobject. for (unsigned I = 0, N = Sub.Entries.size(); /**/; ++I) { // Reading an indeterminate value is undefined, but assigning over one is OK. - if (O->isAbsent() || - (O->isIndeterminate() && handler.AccessKind != AK_Assign && + if ((O->isAbsent() && handler.AccessKind != AK_Construct) || + (O->isIndeterminate() && handler.AccessKind != AK_Construct && + handler.AccessKind != AK_Assign && handler.AccessKind != AK_ReadObjectRepresentation)) { if (!Info.checkingPotentialConstantExpression()) Info.FFDiag(E, diag::note_constexpr_access_uninit) @@ -3311,13 +3320,18 @@ findSubobject(EvalInfo &Info, const Expr *E, const CompleteObject &Obj, const FieldDecl *UnionField = O->getUnionField(); if (!UnionField || UnionField->getCanonicalDecl() != Field->getCanonicalDecl()) { - // FIXME: If O->getUnionValue() is absent, report that there's no - // active union member rather than reporting the prior active union - // member. We'll need to fix nullptr_t to not use APValue() as its - // representation first. - Info.FFDiag(E, diag::note_constexpr_access_inactive_union_member) - << handler.AccessKind << Field << !UnionField << UnionField; - return handler.failed(); + if (I == N - 1 && handler.AccessKind == AK_Construct) { + // Placement new onto an inactive union member makes it active. + O->setUnion(Field, APValue()); + } else { + // FIXME: If O->getUnionValue() is absent, report that there's no + // active union member rather than reporting the prior active union + // member. We'll need to fix nullptr_t to not use APValue() as its + // representation first. + Info.FFDiag(E, diag::note_constexpr_access_inactive_union_member) + << handler.AccessKind << Field << !UnionField << UnionField; + return handler.failed(); + } } O = &O->getUnionValue(); } else @@ -8438,14 +8452,23 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) { return false; FunctionDecl *OperatorNew = E->getOperatorNew(); - if (!OperatorNew->isReplaceableGlobalAllocationFunction()) { + + bool IsNothrow = false; + bool IsPlacement = false; + if (OperatorNew->isReservedGlobalPlacementOperator() && + Info.CurrentCall->isStdFunction() && !E->isArray()) { + // FIXME Support array placement new. + assert(E->getNumPlacementArgs() == 1); + if (!EvaluatePointer(E->getPlacementArg(0), Result, Info)) + return false; + if (Result.Designator.Invalid) + return false; + IsPlacement = true; + } else if (!OperatorNew->isReplaceableGlobalAllocationFunction()) { Info.FFDiag(E, diag::note_constexpr_new_non_replaceable) << isa<CXXMethodDecl>(OperatorNew) << OperatorNew; return false; - } - - bool IsNothrow = false; - if (E->getNumPlacementArgs()) { + } else if (E->getNumPlacementArgs()) { // The only new-placement list we support is of the form (std::nothrow). // // FIXME: There is no restriction on this, but it's not clear that any @@ -8543,10 +8566,56 @@ bool PointerExprEvaluator::VisitCXXNewExpr(const CXXNewExpr *E) { "array allocation with non-array new"); } - // Perform the allocation and obtain a pointer to the resulting object. - APValue *Val = Info.createHeapAlloc(E, AllocType, Result); - if (!Val) - return false; + APValue *Val; + if (IsPlacement) { + AccessKinds AK = AK_Construct; + struct FindObjectHandler { + EvalInfo &Info; + const Expr *E; + QualType AllocType; + const AccessKinds AccessKind; + APValue *Value; + + typedef bool result_type; + bool failed() { return false; } + bool found(APValue &Subobj, QualType SubobjType) { + // FIXME: Reject the cases where [basic.life]p8 would not permit the + // old name of the object to be used to name the new object. + if (!Info.Ctx.hasSameUnqualifiedType(SubobjType, AllocType)) { + Info.FFDiag(E, diag::note_constexpr_placement_new_wrong_type) << + SubobjType << AllocType; + return false; + } + Value = &Subobj; + return true; + } + bool found(APSInt &Value, QualType SubobjType) { + Info.FFDiag(E, diag::note_constexpr_construct_complex_elem); + return false; + } + bool found(APFloat &Value, QualType SubobjType) { + Info.FFDiag(E, diag::note_constexpr_construct_complex_elem); + return false; + } + } Handler = {Info, E, AllocType, AK, nullptr}; + + CompleteObject Obj = findCompleteObject(Info, E, AK, Result, AllocType); + if (!Obj || !findSubobject(Info, E, Obj, Result.Designator, Handler)) + return false; + + Val = Handler.Value; + + // [basic.life]p1: + // The lifetime of an object o of type T ends when [...] the storage + // which the object occupies is [...] reused by an object that is not + // nested within o (6.6.2). + *Val = APValue(); + } else { + // Perform the allocation and obtain a pointer to the resulting object. + Val = Info.createHeapAlloc(E, AllocType, Result); + if (!Val) + return false; + } if (ResizedArrayILE) { if (!EvaluateArrayNewInitList(Info, Result, *Val, ResizedArrayILE, diff --git a/clang/lib/AST/Interp/State.h b/clang/lib/AST/Interp/State.h index d02751693f6..d9a645a3eb3 100644 --- a/clang/lib/AST/Interp/State.h +++ b/clang/lib/AST/Interp/State.h @@ -32,6 +32,7 @@ enum AccessKinds { AK_MemberCall, AK_DynamicCast, AK_TypeId, + AK_Construct, AK_Destroy, }; diff --git a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp index 5a39b33a87c..23582f2e302 100644 --- a/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp +++ b/clang/test/SemaCXX/cxx2a-constexpr-dynalloc.cpp @@ -83,3 +83,86 @@ static_assert(mismatched(2, 2)); constexpr int *escape = std::allocator<int>().allocate(3); // expected-error {{constant expression}} expected-note {{pointer to subobject of heap-allocated}} constexpr int leak = (std::allocator<int>().allocate(3), 0); // expected-error {{constant expression}} constexpr int no_lifetime_start = (*std::allocator<int>().allocate(1) = 1); // expected-error {{constant expression}} expected-note {{assignment to object outside its lifetime}} + +void *operator new(std::size_t, void *p) { return p; } +constexpr bool no_placement_new_in_user_code() { // expected-error {{never produces a constant expression}} + int a; + new (&a) int(42); // expected-note {{call to placement 'operator new'}} + return a == 42; +} + +namespace std { + constexpr bool placement_new_in_stdlib() { + int a; + new (&a) int(42); + return a == 42; + } +} +static_assert(std::placement_new_in_stdlib()); + +namespace std { + template<typename T, typename ...Args> + constexpr void construct_at(void *p, Args &&...args) { + new (p) T((Args&&)args...); // #new + } +} + +constexpr bool call_std_construct_at() { + int *p = std::allocator<int>().allocate(3); + std::construct_at<int>(p, 1); + std::construct_at<int>(p + 1, 2); + std::construct_at<int>(p + 2, 3); + bool good = p[0] + p[1] + p[2] == 6; + std::allocator<int>().deallocate(p); + return good; +} +static_assert(call_std_construct_at()); + +constexpr bool bad_construct_at_type() { + int a; + // expected-note@#new {{placement new would change type of storage from 'int' to 'float'}} + std::construct_at<float>(&a, 1.0f); // expected-note {{in call}} + return true; +} +static_assert(bad_construct_at_type()); // expected-error{{}} expected-note {{in call}} + +constexpr bool bad_construct_at_subobject() { + struct X { int a, b; }; + union A { + int a; + X x; + }; + A a = {1}; + // expected-note@#new {{construction of subobject of member 'x' of union with active member 'a' is not allowed in a constant expression}} + std::construct_at<int>(&a.x.a, 1); // expected-note {{in call}} + return true; +} +static_assert(bad_construct_at_subobject()); // expected-error{{}} expected-note {{in call}} + +constexpr bool change_union_member() { + union U { + int a; + int b; + }; + U u = {.a = 1}; + std::construct_at<int>(&u.b, 2); + return u.b == 2; +} +static_assert(change_union_member()); + +int external; +// expected-note@#new {{visible outside}} +static_assert((std::construct_at<int>(&external, 1), true)); // expected-error{{}} expected-note {{in call}} + +constexpr int &&temporary = 0; // expected-note {{created here}} +// expected-note@#new {{construction of temporary is not allowed in a constant expression outside the expression that created the temporary}} +static_assert((std::construct_at<int>(&temporary, 1), true)); // expected-error{{}} expected-note {{in call}} + +constexpr bool construct_after_lifetime() { + int *p = new int; + delete p; + // expected-note@#new {{construction of heap allocated object that has been deleted}} + std::construct_at<int>(p); // expected-note {{in call}} + return true; +} +static_assert(construct_after_lifetime()); // expected-error {{}} expected-note {{in call}} |