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)
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
~modifiable_tracker ()
{
for (tree t: set)
- global->remove_value (t);
+ global->clear_value (t);
global->modifiable = nullptr;
}
};
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. */
&& (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)
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));
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;
}
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;
}
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 ())
}
}
+ /* 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). */
}
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");
}
}
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. */
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. */
&& 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)
/* 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;
/* 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;
/* 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;
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:
--- /dev/null
+// 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
+