@@ -1193,13 +1193,20 @@ public:
return *p;
return NULL_TREE;
}
- tree *get_value_ptr (tree t)
+ tree *get_value_ptr (tree t, bool initializing)
{
if (modifiable && !modifiable->contains (t))
return nullptr;
if (tree *p = values.get (t))
- if (*p != void_node)
- return p;
+ {
+ if (*p != void_node)
+ return p;
+ else if (initializing)
+ {
+ *p = NULL_TREE;
+ return p;
+ }
+ }
return nullptr;
}
void put_value (tree t, tree v)
@@ -1208,13 +1215,19 @@ public:
if (!already_in_map && modifiable)
modifiable->add (t);
}
- void remove_value (tree t)
+ void destroy_value (tree t)
{
- if (DECL_P (t))
+ if (TREE_CODE (t) == VAR_DECL
+ || TREE_CODE (t) == PARM_DECL
+ || TREE_CODE (t) == RESULT_DECL)
values.put (t, void_node);
else
values.remove (t);
}
+ void clear_value (tree t)
+ {
+ values.remove (t);
+ }
};
/* Helper class for constexpr_global_ctx. In some cases we want to avoid
@@ -1238,7 +1251,7 @@ public:
~modifiable_tracker ()
{
for (tree t: set)
- global->remove_value (t);
+ global->clear_value (t);
global->modifiable = nullptr;
}
};
@@ -1278,6 +1291,40 @@ struct constexpr_ctx {
mce_value manifestly_const_eval;
};
+/* Remove T from the global values map, checking for attempts to destroy
+ a value that has already finished its lifetime. */
+
+static void
+destroy_value_checked (const constexpr_ctx* ctx, tree t, bool *non_constant_p)
+{
+ if (t == error_mark_node || TREE_TYPE (t) == error_mark_node)
+ return;
+
+ /* Don't error again here if we've already reported a problem. */
+ if (!*non_constant_p
+ && DECL_P (t)
+ /* Non-trivial destructors have their lifetimes ended explicitly
+ with a clobber, so don't worry about it here. */
+ && (!TYPE_HAS_NONTRIVIAL_DESTRUCTOR (TREE_TYPE (t))
+ /* ...except parameters are remapped in cxx_eval_call_expression,
+ and the destructor call during cleanup won't be able to tell that
+ this value has already been destroyed, so complain now. This is
+ not quite unobservable, but is extremely unlikely to crop up in
+ practice; see g++.dg/cpp2a/constexpr-lifetime2.C. */
+ || TREE_CODE (t) == PARM_DECL)
+ && ctx->global->is_outside_lifetime (t))
+ {
+ if (!ctx->quiet)
+ {
+ auto_diagnostic_group d;
+ error ("destroying %qE outside its lifetime", t);
+ inform (DECL_SOURCE_LOCATION (t), "declared here");
+ }
+ *non_constant_p = true;
+ }
+ ctx->global->destroy_value (t);
+}
+
/* This internal flag controls whether we should avoid doing anything during
constexpr evaluation that would cause extra DECL_UID generation, such as
template instantiation and function body copying. */
@@ -2806,6 +2853,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
&& (CALL_FROM_NEW_OR_DELETE_P (t)
|| is_std_allocator_allocate (ctx->call)))
{
+ const bool new_op_p = IDENTIFIER_NEW_OP_P (DECL_NAME (fun));
const int nargs = call_expr_nargs (t);
tree arg0 = NULL_TREE;
for (int i = 0; i < nargs; ++i)
@@ -2813,12 +2861,15 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
tree arg = CALL_EXPR_ARG (t, i);
arg = cxx_eval_constant_expression (ctx, arg, vc_prvalue,
non_constant_p, overflow_p);
- VERIFY_CONSTANT (arg);
+ /* Deleting a non-constant pointer has a better error message
+ below. */
+ if (new_op_p || i != 0)
+ VERIFY_CONSTANT (arg);
if (i == 0)
arg0 = arg;
}
gcc_assert (arg0);
- if (IDENTIFIER_NEW_OP_P (DECL_NAME (fun)))
+ if (new_op_p)
{
tree type = build_array_type_nelts (char_type_node,
tree_to_uhwi (arg0));
@@ -2867,7 +2918,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
return t;
}
DECL_NAME (var) = heap_deleted_identifier;
- ctx->global->remove_value (var);
+ ctx->global->destroy_value (var);
ctx->global->heap_dealloc_count++;
return void_node;
}
@@ -2890,7 +2941,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
return t;
}
DECL_NAME (var) = heap_deleted_identifier;
- ctx->global->remove_value (var);
+ ctx->global->destroy_value (var);
ctx->global->heap_dealloc_count++;
return void_node;
}
@@ -3255,9 +3306,9 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
non_constant_p, overflow_p);
/* Remove the parms/result from the values map. */
- ctx->global->remove_value (res);
+ destroy_value_checked (ctx, res, non_constant_p);
for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
- ctx->global->remove_value (parm);
+ destroy_value_checked (ctx, parm, non_constant_p);
/* Free any parameter CONSTRUCTORs we aren't returning directly. */
while (!ctors->is_empty ())
@@ -5657,6 +5708,10 @@ cxx_fold_indirect_ref_1 (const constexpr_ctx *ctx, location_t loc, tree type,
}
}
+ /* Handle conversion to "as base" type. */
+ if (CLASSTYPE_AS_BASE (optype) == type)
+ return op;
+
/* Handle conversion to an empty base class, which is represented with a
NOP_EXPR. Do this before spelunking into the non-empty subobjects,
which is likely to be a waste of time (109678). */
@@ -5908,7 +5963,7 @@ outside_lifetime_error (location_t loc, tree r)
}
else
{
- error_at (loc, "accessing object outside its lifetime");
+ error_at (loc, "accessing %qE outside its lifetime", r);
inform (DECL_SOURCE_LOCATION (r), "declared here");
}
}
@@ -6125,8 +6180,10 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
constexpr_ctx new_ctx = *ctx;
tree init = TREE_OPERAND (t, 1);
- if (TREE_CLOBBER_P (init))
- /* Just ignore clobbers. */
+
+ if (TREE_CLOBBER_P (init)
+ && CLOBBER_KIND (init) < CLOBBER_OBJECT_END)
+ /* Only handle clobbers ending the lifetime of objects. */
return void_node;
/* First we figure out where we're storing to. */
@@ -6136,7 +6193,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
tree type = TREE_TYPE (target);
bool preeval = SCALAR_TYPE_P (type) || TREE_CODE (t) == MODIFY_EXPR;
- if (preeval)
+ if (preeval && !TREE_CLOBBER_P (init))
{
/* Evaluate the value to be stored without knowing what object it will be
stored in, so that any side-effects happen first. */
@@ -6244,11 +6301,18 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
&& const_object_being_modified == NULL_TREE)
const_object_being_modified = object;
+ if (DECL_P (object)
+ && TREE_CLOBBER_P (init)
+ && DECL_NAME (object) == heap_deleted_identifier)
+ /* Ignore clobbers of deleted allocations for now; we'll get a better error
+ message later when operator delete is called. */
+ return void_node;
+
/* And then find/build up our initializer for the path to the subobject
we're initializing. */
tree *valp;
if (DECL_P (object))
- valp = ctx->global->get_value_ptr (object);
+ valp = ctx->global->get_value_ptr (object, TREE_CODE (t) == INIT_EXPR);
else
valp = NULL;
if (!valp)
@@ -6256,10 +6320,45 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
/* A constant-expression cannot modify objects from outside the
constant-expression. */
if (!ctx->quiet)
- error ("modification of %qE is not a constant expression", object);
+ {
+ auto_diagnostic_group d;
+ if (DECL_P (object) && DECL_NAME (object) == heap_deleted_identifier)
+ {
+ error ("modification of allocated storage after deallocation "
+ "is not a constant expression");
+ inform (DECL_SOURCE_LOCATION (object), "allocated here");
+ }
+ else if (DECL_P (object) && ctx->global->is_outside_lifetime (object))
+ {
+ if (TREE_CLOBBER_P (init))
+ error ("destroying %qE outside its lifetime", object);
+ else
+ error ("modification of %qE outside its lifetime "
+ "is not a constant expression", object);
+ inform (DECL_SOURCE_LOCATION (object), "declared here");
+ }
+ else
+ {
+ if (TREE_CLOBBER_P (init))
+ error ("destroying %qE from outside current evaluation "
+ "is not a constant expression", object);
+ else
+ error ("modification of %qE from outside current evaluation "
+ "is not a constant expression", object);
+ }
+ }
*non_constant_p = true;
return t;
}
+
+ /* Handle explicit end-of-lifetime. */
+ if (TREE_CLOBBER_P (init))
+ {
+ if (refs->is_empty ())
+ ctx->global->destroy_value (object);
+ return void_node;
+ }
+
type = TREE_TYPE (object);
bool no_zero_init = true;
@@ -6533,7 +6632,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
/* The hash table might have moved since the get earlier, and the
initializer might have mutated the underlying CONSTRUCTORs, so we must
recompute VALP. */
- valp = ctx->global->get_value_ptr (object);
+ valp = ctx->global->get_value_ptr (object, TREE_CODE (t) == INIT_EXPR);
for (unsigned i = 0; i < vec_safe_length (indexes); i++)
{
ctors[i] = valp;
@@ -7650,7 +7749,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
/* Forget SAVE_EXPRs and TARGET_EXPRs created by this
full-expression. */
for (tree save_expr : save_exprs)
- ctx->global->remove_value (save_expr);
+ destroy_value_checked (ctx, save_expr, non_constant_p);
}
break;
@@ -8203,13 +8302,18 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
non_constant_p, overflow_p, jump_target);
case BIND_EXPR:
+ /* Pre-emptively clear the vars declared by this BIND_EXPR from the value
+ map, so that when checking whether they're already destroyed later we
+ don't get confused by remnants of previous calls. */
+ for (tree decl = BIND_EXPR_VARS (t); decl; decl = DECL_CHAIN (decl))
+ ctx->global->clear_value (decl);
r = cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (t),
lval,
non_constant_p, overflow_p,
jump_target);
for (tree decl = BIND_EXPR_VARS (t); decl; decl = DECL_CHAIN (decl))
- ctx->global->remove_value (decl);
- return r;
+ destroy_value_checked (ctx, decl, non_constant_p);
+ break;
case PREINCREMENT_EXPR:
case POSTINCREMENT_EXPR:
@@ -10,4 +10,4 @@ constexpr const int& test() {
auto local = S{}; // { dg-message "note: declared here" }
return local.get();
}
-constexpr int x = test(); // { dg-error "accessing object outside its lifetime" }
+constexpr int x = test(); // { dg-error "accessing .local. outside its lifetime" }
@@ -8,7 +8,7 @@ struct S {
constexpr int error() {
const auto& local = S{}.get(); // { dg-message "note: declared here" }
- return local; // { dg-error "accessing object outside its lifetime" }
+ return local; // { dg-error "accessing '\[^'\]+' outside its lifetime" }
}
constexpr int x = error(); // { dg-message "in .constexpr. expansion" }
@@ -7,7 +7,7 @@ constexpr int f(int i) {
int j = 123; // { dg-message "note: declared here" }
p = &j;
}
- return *p; // { dg-error "accessing object outside its lifetime" }
+ return *p; // { dg-error "accessing 'j' outside its lifetime" }
}
constexpr int i = f(0); // { dg-message "in .constexpr. expansion" }
@@ -5,7 +5,7 @@ constexpr const double& test() {
return local;
}
-static_assert(test() == 3.0, ""); // { dg-error "constant|accessing object outside its lifetime" }
+static_assert(test() == 3.0, ""); // { dg-error "constant|accessing '\[^'\]+' outside its lifetime" }
// no deference, shouldn't error
static_assert((test(), true), "");
new file mode 100644
@@ -0,0 +1,93 @@
+// PR c++/71093
+// { dg-do compile { target c++14 } }
+
+constexpr int f (const int *p)
+{
+ typedef int T;
+ p->~T (); // { dg-error "destroying" }
+ return *p;
+}
+
+constexpr int i = 0;
+constexpr int j = f (&i);
+
+
+template <typename T>
+constexpr bool test_access() {
+ T x {};
+ x.~T();
+ T y = x; // { dg-error "lifetime" }
+ return true;
+}
+
+template <typename T>
+constexpr bool test_modification() {
+ T x {};
+ x.~T();
+ x = T(); // { dg-error "lifetime" }
+ return true;
+}
+
+template <typename T>
+constexpr bool test_scope() {
+ {
+ T x {};
+ x.~T();
+ } // { dg-error "destroying" }
+ return true;
+}
+
+template <typename T>
+constexpr bool test_destroy_temp() {
+ T{}.~T(); // { dg-error "destroying" }
+ return true;
+}
+
+template <typename T>
+constexpr bool test_parameter(T t) {
+ // note: error message occurs at point of call
+ t.~T();
+ return true;
+}
+
+template <typename T>
+constexpr void test_bindings_impl(int n) {
+ if (n == 0) return;
+ T a {};
+ if (n == 1) return;
+ T b {};
+}
+
+template <typename T>
+constexpr bool test_bindings() {
+ test_bindings_impl<T>(1);
+ test_bindings_impl<T>(0);
+ test_bindings_impl<T>(2);
+ return true;
+}
+
+constexpr bool i1 = test_access<int>(); // { dg-message "in .constexpr." }
+constexpr bool i2 = test_modification<int>(); // { dg-message "in .constexpr." }
+constexpr bool i3 = test_scope<int>(); // { dg-message "in .constexpr." }
+constexpr bool i4 = test_destroy_temp<int>(); // { dg-message "in .constexpr." "" { xfail *-*-* } }
+constexpr bool i5 = test_parameter(int{}); // { dg-error "destroying" }
+constexpr bool i6 = test_bindings<int>();
+
+struct Trivial { int x; };
+constexpr bool t1 = test_access<Trivial>(); // { dg-message "in .constexpr." }
+constexpr bool t2 = test_modification<Trivial>(); // { dg-message "in .constexpr." }
+constexpr bool t3 = test_scope<Trivial>(); // { dg-message "in .constexpr." }
+constexpr bool t4 = test_destroy_temp<Trivial>(); // { dg-message "in .constexpr." }
+constexpr bool t5 = test_parameter(Trivial{}); // { dg-error "destroying" }
+constexpr bool t6 = test_bindings<Trivial>();
+
+#if __cplusplus >= 202002L
+struct NonTrivial { int x; constexpr ~NonTrivial() {} }; // { dg-error "destroying" "" { target c++20 } }
+constexpr bool n1 = test_access<NonTrivial>(); // { dg-message "in .constexpr." "" { target c++20 } }
+constexpr bool n2 = test_modification<NonTrivial>(); // { dg-message "in .constexpr." "" { target c++20 } }
+constexpr bool n3 = test_scope<NonTrivial>(); // { dg-message "in .constexpr." "" { target c++20 } }
+constexpr bool n4 = test_destroy_temp<NonTrivial>(); // { dg-message "in .constexpr." "" { target c++20 } }
+constexpr bool n5 = test_parameter(NonTrivial{}); // { dg-error "destroying" "" { target c++20 } }
+constexpr bool n6 = test_bindings<NonTrivial>();
+#endif
+
@@ -13,7 +13,7 @@ template <bool V, int W>
struct U {
int j : W = 7; // { dg-warning "default member initializers for bit-fields only available with" "" { target c++17_down } }
int k : W { 8 }; // { dg-warning "default member initializers for bit-fields only available with" "" { target c++17_down } }
- int l : V ? 7 : a = 3; // { dg-error "modification of .a. is not a constant expression" }
+ int l : V ? 7 : a = 3; // { dg-error "modification of .a. from outside current evaluation is not a constant expression" }
// { dg-error "width not an integer constant" "" { target *-*-* } .-1 }
int m : (V ? W : b) = 9; // { dg-warning "default member initializers for bit-fields only available with" "" { target c++17_down } }
// { dg-error "zero width for bit-field" "" { target *-*-* } .-1 }
new file mode 100644
@@ -0,0 +1,21 @@
+// { dg-do compile { target c++20 } }
+
+#include "construct_at.h"
+
+struct S { int x; };
+constexpr int f() {
+ S s;
+ s.~S();
+ std::construct_at(&s, 5);
+ return s.x;
+}
+static_assert(f() == 5);
+
+struct T { int x; constexpr ~T() {} };
+constexpr int g() {
+ T t;
+ t.~T();
+ std::construct_at(&t, 12);
+ return t.x;
+}
+static_assert(g() == 12);
new file mode 100644
@@ -0,0 +1,23 @@
+// { dg-do compile { target c++20 } }
+
+#include "construct_at.h"
+
+struct S { int x; };
+
+constexpr bool foo(S s, S*& p) {
+ p = &s;
+ s.~S();
+ return true;
+}
+
+constexpr bool bar() {
+ // This is, strictly speaking, implementation-defined behaviour;
+ // see [expr.call] p6. However, in all other cases we destroy
+ // at the end of the full-expression, so the below should be fixed.
+ S* p;
+ foo(S{}, p), std::construct_at(p); // { dg-bogus "destroying" "" { xfail *-*-* } }
+
+ return true;
+}
+
+constexpr bool x = bar();
@@ -34,7 +34,7 @@ constexpr auto v3 = f3 (); // { dg-message "in 'constexpr' expansion of" }
constexpr bool
f4 (int *p)
{
- delete p; // { dg-error "deallocation of storage that was not previously allocated" }
+ delete p; // { dg-error "destroying 'q' from outside current evaluation" }
return false;
}
@@ -70,3 +70,18 @@ f7 ()
}
constexpr auto v7 = f7 ();
+
+constexpr bool
+f8_impl (int *p)
+{
+ delete p; // { dg-error "deallocation of storage that was not previously allocated" }
+ return false;
+}
+
+constexpr bool
+f8 ()
+{
+ int q = 0;
+ return f8_impl (&q);
+}
+constexpr auto v8 = f8 (); // { dg-message "in 'constexpr' expansion of" }