public inbox for gcc-patches@gcc.gnu.org
 help / color / mirror / Atom feed
* [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...]
@ 2023-03-29  2:32 Nathaniel Shead
  2023-03-29  2:32 ` [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331,PR96630,PR98675] Nathaniel Shead
                   ` (4 more replies)
  0 siblings, 5 replies; 13+ messages in thread
From: Nathaniel Shead @ 2023-03-29  2:32 UTC (permalink / raw)
  To: gcc-patches; +Cc: Nathaniel Shead

This is an update of the patch series at
https://gcc.gnu.org/pipermail/gcc-patches/2023-March/614759.html

The main change is modifying the first patch to store the "expired" flag
in the C++-specific lang_decl_base struct instead of tree_decl_common.
The second and third patches to improve diagnostic locations are
otherwise unchanged.

Bootstrapped and regression tested on x86_64 linux.

Nathaniel

---

Nathaniel Shead (3):
  c++: Track lifetimes in constant evaluation [PR70331,PR96630,PR98675]
  c++: Improve constexpr error for dangling local variables
  c++: Improve location information in constexpr evaluation

 gcc/cp/constexpr.cc                           | 152 ++++++++++++------
 gcc/cp/cp-tree.h                              |  10 +-
 gcc/cp/module.cc                              |   2 +
 gcc/cp/semantics.cc                           |   5 +-
 gcc/cp/typeck.cc                              |   5 +-
 gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C  |  10 +-
 gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C  |   2 +-
 gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |   2 +-
 gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C  |   3 +-
 .../g++.dg/cpp1y/constexpr-lifetime1.C        |  14 ++
 .../g++.dg/cpp1y/constexpr-lifetime2.C        |  20 +++
 .../g++.dg/cpp1y/constexpr-lifetime3.C        |  13 ++
 .../g++.dg/cpp1y/constexpr-lifetime4.C        |  11 ++
 .../g++.dg/cpp1y/constexpr-lifetime5.C        |  11 ++
 gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C |   4 +-
 gcc/testsuite/g++.dg/cpp1y/pr68180.C          |   4 +-
 .../g++.dg/cpp1z/constexpr-lambda6.C          |   4 +-
 gcc/testsuite/g++.dg/cpp2a/bit-cast11.C       |  10 +-
 gcc/testsuite/g++.dg/cpp2a/bit-cast12.C       |  10 +-
 gcc/testsuite/g++.dg/cpp2a/bit-cast14.C       |  14 +-
 gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C  |   4 +-
 .../g++.dg/cpp2a/constexpr-dynamic17.C        |   5 +-
 gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C  |   5 +-
 gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C  |   6 +-
 gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C   |  10 +-
 gcc/testsuite/g++.dg/ext/constexpr-vla2.C     |   4 +-
 gcc/testsuite/g++.dg/ext/constexpr-vla3.C     |   4 +-
 gcc/testsuite/g++.dg/ubsan/pr63956.C          |   4 +-
 .../g++.dg/warn/Wreturn-local-addr-6.C        |   3 -
 .../25_algorithms/equal/constexpr_neg.cc      |   7 +-
 30 files changed, 246 insertions(+), 112 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C

-- 
2.34.1


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331,PR96630,PR98675]
  2023-03-29  2:32 [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...] Nathaniel Shead
@ 2023-03-29  2:32 ` Nathaniel Shead
  2023-06-23 16:43   ` [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331, PR96630, PR98675] Patrick Palka
  2023-03-29  2:32 ` [PATCH v2 2/3] c++: Improve constexpr error for dangling local variables Nathaniel Shead
                   ` (3 subsequent siblings)
  4 siblings, 1 reply; 13+ messages in thread
From: Nathaniel Shead @ 2023-03-29  2:32 UTC (permalink / raw)
  To: gcc-patches; +Cc: Nathaniel Shead

This adds rudimentary lifetime tracking in C++ constexpr contexts,
allowing the compiler to report errors with using values after their
backing has gone out of scope. We don't yet handle other ways of ending
lifetimes (e.g. explicit destructor calls).

	PR c++/96630
	PR c++/98675
	PR c++/70331

gcc/cp/ChangeLog:

	* constexpr.cc (constexpr_global_ctx::put_value): Mark value as
	in lifetime.
	(constexpr_global_ctx::remove_value): Mark value as expired.
	(cxx_eval_call_expression): Remove comment that is no longer
	applicable.
	(non_const_var_error): Add check for expired values.
	(cxx_eval_constant_expression): Add checks for expired values. Forget
	local variables at end of bind expressions. Forget temporaries at end
	of cleanup points.
	* cp-tree.h (struct lang_decl_base): New flag to track expired values
	in constant evaluation.
	(DECL_EXPIRED_P): Access the new flag.
	(SET_DECL_EXPIRED_P): Modify the new flag.
	* module.cc (trees_out::lang_decl_bools): Write out the new flag.
	(trees_in::lang_decl_bools): Read in the new flag.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/constexpr-ice20.C: Update error raised by test.
	* g++.dg/cpp1y/constexpr-lifetime1.C: New test.
	* g++.dg/cpp1y/constexpr-lifetime2.C: New test.
	* g++.dg/cpp1y/constexpr-lifetime3.C: New test.
	* g++.dg/cpp1y/constexpr-lifetime4.C: New test.
	* g++.dg/cpp1y/constexpr-lifetime5.C: New test.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/constexpr.cc                           | 69 +++++++++++++++----
 gcc/cp/cp-tree.h                              | 10 ++-
 gcc/cp/module.cc                              |  2 +
 gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |  2 +-
 .../g++.dg/cpp1y/constexpr-lifetime1.C        | 13 ++++
 .../g++.dg/cpp1y/constexpr-lifetime2.C        | 20 ++++++
 .../g++.dg/cpp1y/constexpr-lifetime3.C        | 13 ++++
 .../g++.dg/cpp1y/constexpr-lifetime4.C        | 11 +++
 .../g++.dg/cpp1y/constexpr-lifetime5.C        | 11 +++
 9 files changed, 137 insertions(+), 14 deletions(-)
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
 create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index 3de60cfd0f8..bdbc12144a7 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -1185,10 +1185,22 @@ public:
   void put_value (tree t, tree v)
   {
     bool already_in_map = values.put (t, v);
+    if (!already_in_map && DECL_P (t))
+      {
+	if (!DECL_LANG_SPECIFIC (t))
+	  retrofit_lang_decl (t);
+	if (DECL_LANG_SPECIFIC (t))
+	  SET_DECL_EXPIRED_P (t, false);
+      }
     if (!already_in_map && modifiable)
       modifiable->add (t);
   }
-  void remove_value (tree t) { values.remove (t); }
+  void remove_value (tree t)
+  {
+    if (DECL_P (t) && DECL_LANG_SPECIFIC (t))
+      SET_DECL_EXPIRED_P (t, true);
+    values.remove (t);
+  }
 };
 
 /* Helper class for constexpr_global_ctx.  In some cases we want to avoid
@@ -3157,10 +3169,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
 	  for (tree save_expr : save_exprs)
 	    ctx->global->remove_value (save_expr);
 
-	  /* Remove the parms/result from the values map.  Is it worth
-	     bothering to do this when the map itself is only live for
-	     one constexpr evaluation?  If so, maybe also clear out
-	     other vars from call, maybe in BIND_EXPR handling?  */
+	  /* Remove the parms/result from the values map.  */
 	  ctx->global->remove_value (res);
 	  for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
 	    ctx->global->remove_value (parm);
@@ -5708,6 +5717,13 @@ non_const_var_error (location_t loc, tree r, bool fundef_p)
 	inform (DECL_SOURCE_LOCATION (r), "allocated here");
       return;
     }
+  if (DECL_EXPIRED_P (r))
+    {
+      if (constexpr_error (loc, fundef_p, "accessing object outside its "
+			   "lifetime"))
+	inform (DECL_SOURCE_LOCATION (r), "declared here");
+      return;
+    }
   if (!constexpr_error (loc, fundef_p, "the value of %qD is not usable in "
 			"a constant expression", r))
     return;
@@ -7048,6 +7064,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 	  r = build_constructor (TREE_TYPE (t), NULL);
 	  TREE_CONSTANT (r) = true;
 	}
+      else if (DECL_EXPIRED_P (t))
+	{
+	  if (!ctx->quiet)
+	    non_const_var_error (loc, r, /*fundef_p*/false);
+	  *non_constant_p = true;
+	  break;
+	}
       else if (ctx->strict)
 	r = decl_really_constant_value (t, /*unshare_p=*/false);
       else
@@ -7093,7 +7116,15 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
       else
 	{
 	  if (!ctx->quiet)
-	    error ("%qE is not a constant expression", t);
+	    {
+	      if (DECL_EXPIRED_P (r))
+		{
+		  error_at (loc, "accessing object outside its lifetime");
+		  inform (DECL_SOURCE_LOCATION (r), "declared here");
+		}
+	      else
+		error_at (loc, "%qE is not a constant expression", t);
+	    }
 	  *non_constant_p = true;
 	}
       break;
@@ -7315,17 +7346,28 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 	auto_vec<tree, 2> cleanups;
 	vec<tree> *prev_cleanups = ctx->global->cleanups;
 	ctx->global->cleanups = &cleanups;
-	r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0),
+
+	auto_vec<tree, 10> save_exprs;
+	constexpr_ctx new_ctx = *ctx;
+	new_ctx.save_exprs = &save_exprs;
+
+	r = cxx_eval_constant_expression (&new_ctx, TREE_OPERAND (t, 0),
 					  lval,
 					  non_constant_p, overflow_p,
 					  jump_target);
+
 	ctx->global->cleanups = prev_cleanups;
 	unsigned int i;
 	tree cleanup;
 	/* Evaluate the cleanups.  */
 	FOR_EACH_VEC_ELT_REVERSE (cleanups, i, cleanup)
-	  cxx_eval_constant_expression (ctx, cleanup, vc_discard,
+	  cxx_eval_constant_expression (&new_ctx, cleanup, vc_discard,
 					non_constant_p, overflow_p);
+
+	/* Forget SAVE_EXPRs and TARGET_EXPRs created by this
+	   full-expression.  */
+	for (tree save_expr : save_exprs)
+	  ctx->global->remove_value (save_expr);
       }
       break;
 
@@ -7831,10 +7873,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 				      non_constant_p, overflow_p, jump_target);
 
     case BIND_EXPR:
-      return cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (t),
-					   lval,
-					   non_constant_p, overflow_p,
-					   jump_target);
+      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;
 
     case PREINCREMENT_EXPR:
     case POSTINCREMENT_EXPR:
diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index b74c18b03ad..3cc08da816f 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -2860,6 +2860,7 @@ struct GTY(()) lang_decl_base {
   unsigned concept_p : 1;                  /* applies to vars and functions */
   unsigned var_declared_inline_p : 1;	   /* var */
   unsigned dependent_init_p : 1;	   /* var */
+  unsigned expired_p : 1;		   /* var or parm */
 
   /* The following apply to VAR, FUNCTION, TYPE, CONCEPT, & NAMESPACE
      decls.  */
@@ -2871,7 +2872,7 @@ struct GTY(()) lang_decl_base {
   /* VAR_DECL or FUNCTION_DECL has keyed decls.     */
   unsigned module_keyed_decls_p : 1;
 
-  /* 12 spare bits.  */
+  /* 11 spare bits.  */
 };
 
 /* True for DECL codes which have template info and access.  */
@@ -4366,6 +4367,13 @@ get_vec_init_expr (tree t)
 #define SET_DECL_DEPENDENT_INIT_P(NODE, X) \
   (DECL_LANG_SPECIFIC (VAR_DECL_CHECK (NODE))->u.base.dependent_init_p = (X))
 
+/* Nonzero if NODE is a VAR_DECL, PARM_DECL, or FIELD_DECL that is within
+   its lifetime for constant evaluation purposes.  */
+#define DECL_EXPIRED_P(NODE) \
+  (DECL_LANG_SPECIFIC (NODE) && DECL_LANG_SPECIFIC (NODE)->u.base.expired_p)
+#define SET_DECL_EXPIRED_P(NODE, X) \
+  (DECL_LANG_SPECIFIC (NODE)->u.base.expired_p = (X))
+
 /* Nonzero if NODE is an artificial VAR_DECL for a C++17 structured binding
    declaration or one of VAR_DECLs for the user identifiers in it.  */
 #define DECL_DECOMPOSITION_P(NODE) \
diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
index ac2fe66b080..7af43b5736d 100644
--- a/gcc/cp/module.cc
+++ b/gcc/cp/module.cc
@@ -5654,6 +5654,7 @@ trees_out::lang_decl_bools (tree t)
   WB (lang->u.base.concept_p);
   WB (lang->u.base.var_declared_inline_p);
   WB (lang->u.base.dependent_init_p);
+  WB (lang->u.base.expired_p);
   /* When building a header unit, everthing is marked as purview, (so
      we know which decls to write).  But when we import them we do not
      want to mark them as in module purview.  */
@@ -5728,6 +5729,7 @@ trees_in::lang_decl_bools (tree t)
   RB (lang->u.base.concept_p);
   RB (lang->u.base.var_declared_inline_p);
   RB (lang->u.base.dependent_init_p);
+  RB (lang->u.base.expired_p);
   RB (lang->u.base.module_purview_p);
   RB (lang->u.base.module_attach_p);
   if (VAR_OR_FUNCTION_DECL_P (t))
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
index e2d4853a284..ebaa95e5324 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
@@ -4,4 +4,4 @@
 typedef bool (*Function)(int);
 constexpr bool check(int x, Function p) { return p(x); }  // { dg-message "in .constexpr. expansion of" }
 
-static_assert(check(2, check), "");  // { dg-error "conversion|constant|in .constexpr. expansion of" }
+static_assert(check(2, check), "");  // { dg-error "conversion|constant|lifetime|in .constexpr. expansion of" }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
new file mode 100644
index 00000000000..43aa7c974c1
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
@@ -0,0 +1,13 @@
+// PR c++/96630
+// { dg-do compile { target c++14 } }
+
+struct S {
+  int x = 0;
+  constexpr const int& get() const { return x; }
+};
+
+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" }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
new file mode 100644
index 00000000000..22cd919fcda
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
@@ -0,0 +1,20 @@
+// PR c++/98675
+// { dg-do compile { target c++14 } }
+
+struct S {
+  int x = 0;
+  constexpr const int& get() const { return x; }
+};
+
+constexpr int error() {
+  const auto& local = S{}.get();  // { dg-message "note: declared here" }
+  return local;
+}
+constexpr int x = error();  // { dg-error "accessing object outside its lifetime" }
+
+constexpr int ok() {
+  // temporary should only be destroyed after end of full-expression
+  auto local = S{}.get();
+  return local;
+}
+constexpr int y = ok();
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
new file mode 100644
index 00000000000..6329f8cf6c6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
@@ -0,0 +1,13 @@
+// PR c++/70331
+// { dg-do compile { target c++14 } }
+
+constexpr int f(int i) {
+  int *p = &i;
+  if (i == 0) {
+    int j = 123;  // { dg-message "note: declared here" }
+    p = &j;
+  }
+  return *p;
+}
+
+constexpr int i = f(0);  // { dg-error "accessing object outside its lifetime" }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
new file mode 100644
index 00000000000..181a1201663
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
@@ -0,0 +1,11 @@
+// { dg-do compile { target c++14 } }
+
+constexpr const double& test() {
+  const double& local = 3.0;  // { dg-message "note: declared here" }
+  return local;
+}
+
+static_assert(test() == 3.0, "");  // { dg-error "constant|accessing object outside its lifetime" }
+
+// no deference, shouldn't error
+static_assert((test(), true), "");
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
new file mode 100644
index 00000000000..a4bc71d890a
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
@@ -0,0 +1,11 @@
+// { dg-do compile { target c++14 } }
+// { dg-options "-Wno-return-local-addr" }
+
+constexpr const int& id(int x) { return x; }
+
+constexpr bool test() {
+  const int& y = id(3);
+  return y == 3;
+}
+
+constexpr bool x = test();  // { dg-error "" }
-- 
2.34.1


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 2/3] c++: Improve constexpr error for dangling local variables
  2023-03-29  2:32 [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...] Nathaniel Shead
  2023-03-29  2:32 ` [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331,PR96630,PR98675] Nathaniel Shead
@ 2023-03-29  2:32 ` Nathaniel Shead
  2023-06-23 16:57   ` Patrick Palka
  2023-03-29  2:32 ` [PATCH v2 3/3] c++: Improve location information in constexpr evaluation Nathaniel Shead
                   ` (2 subsequent siblings)
  4 siblings, 1 reply; 13+ messages in thread
From: Nathaniel Shead @ 2023-03-29  2:32 UTC (permalink / raw)
  To: gcc-patches; +Cc: Nathaniel Shead

Currently, when typeck discovers that a return statement will refer to a
local variable it rewrites to return a null pointer. This causes the
error messages for using the return value in a constant expression to be
unhelpful, especially for reference return values.

This patch removes this "optimisation". Relying on this raises a warning
by default and causes UB anyway, so there should be no issue in doing
so. We also suppress additional warnings from later passes that detect
this as a dangling pointer, since we've already indicated this anyway.

gcc/cp/ChangeLog:

	* semantics.cc (finish_return_stmt): Suppress dangling pointer
        reporting on return statement if already reported.
	* typeck.cc (check_return_expr): Don't set return expression to
        zero for dangling addresses.

gcc/testsuite/ChangeLog:

        * g++.dg/cpp1y/constexpr-lifetime5.C: Test reported message is
        correct.
	* g++.dg/warn/Wreturn-local-addr-6.C: Remove check for return
        value optimisation.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/semantics.cc                              | 5 ++++-
 gcc/cp/typeck.cc                                 | 5 +++--
 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C | 4 ++--
 gcc/testsuite/g++.dg/warn/Wreturn-local-addr-6.C | 3 ---
 4 files changed, 9 insertions(+), 8 deletions(-)

diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
index 87c2e8a7111..14b4b7f4ce1 100644
--- a/gcc/cp/semantics.cc
+++ b/gcc/cp/semantics.cc
@@ -1246,7 +1246,10 @@ finish_return_stmt (tree expr)
 
   r = build_stmt (input_location, RETURN_EXPR, expr);
   if (no_warning)
-    suppress_warning (r, OPT_Wreturn_type);
+    {
+      suppress_warning (r, OPT_Wreturn_type);
+      suppress_warning (r, OPT_Wdangling_pointer_);
+    }
   r = maybe_cleanup_point_expr_void (r);
   r = add_stmt (r);
 
diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
index afb956087ce..a7d642e2029 100644
--- a/gcc/cp/typeck.cc
+++ b/gcc/cp/typeck.cc
@@ -11235,8 +11235,9 @@ check_return_expr (tree retval, bool *no_warning)
       else if (!processing_template_decl
 	       && maybe_warn_about_returning_address_of_local (retval, loc)
 	       && INDIRECT_TYPE_P (valtype))
-	retval = build2 (COMPOUND_EXPR, TREE_TYPE (retval), retval,
-			 build_zero_cst (TREE_TYPE (retval)));
+	/* Suppress the Wdangling-pointer warning in the return statement
+	   that would otherwise occur.  */
+	*no_warning = true;
     }
 
   if (processing_template_decl)
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
index a4bc71d890a..ad3ef579f63 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
@@ -1,11 +1,11 @@
 // { dg-do compile { target c++14 } }
 // { dg-options "-Wno-return-local-addr" }
 
-constexpr const int& id(int x) { return x; }
+constexpr const int& id(int x) { return x; }  // { dg-message "note: declared here" }
 
 constexpr bool test() {
   const int& y = id(3);
   return y == 3;
 }
 
-constexpr bool x = test();  // { dg-error "" }
+constexpr bool x = test();  // { dg-error "accessing object outside its lifetime" }
diff --git a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr-6.C b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr-6.C
index fae8b7e766f..ec8e241d83e 100644
--- a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr-6.C
+++ b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr-6.C
@@ -24,6 +24,3 @@ return_addr_local_as_intref (void)
 
   return (const intptr_t&)a;   // { dg-warning "\\\[-Wreturn-local-addr]" } */
 }
-
-/* Verify that the return value has been replaced with zero:
-  { dg-final { scan-tree-dump-times "return 0;" 2 "optimized" } } */
-- 
2.34.1


^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 3/3] c++: Improve location information in constexpr evaluation
  2023-03-29  2:32 [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...] Nathaniel Shead
  2023-03-29  2:32 ` [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331,PR96630,PR98675] Nathaniel Shead
  2023-03-29  2:32 ` [PATCH v2 2/3] c++: Improve constexpr error for dangling local variables Nathaniel Shead
@ 2023-03-29  2:32 ` Nathaniel Shead
  2023-06-23 17:09   ` Patrick Palka
  2023-05-08 10:48 ` [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...] Nathaniel Shead
  2023-06-13 10:11 ` Nathaniel Shead
  4 siblings, 1 reply; 13+ messages in thread
From: Nathaniel Shead @ 2023-03-29  2:32 UTC (permalink / raw)
  To: gcc-patches; +Cc: Nathaniel Shead

This patch caches the current expression's location information in the
constexpr_global_ctx struct, which allows subexpressions that have lost
location information to still provide accurate diagnostics. Also
rewrites a number of 'error' calls as 'error_at' to provide more
specific location information.

The primary effect of this change is that many errors within evaluation
of a constexpr function will now point at the offending expression (with
expansion tracing information) rather than just the outermost call.

gcc/cp/ChangeLog:

	* constexpr.cc (constexpr_global_ctx): New field for cached
	tree location, defaulting to input_location.
	(cxx_eval_internal_function): Fall back to ctx->global->loc
	rather than input_location.
	(modifying_const_object_error): Likewise.
	(cxx_eval_dynamic_cast_fn): Likewise.
	(eval_and_check_array_index): Likewise.
	(cxx_eval_array_reference): Likewise.
	(cxx_eval_bit_field_ref): Likewise.
	(cxx_eval_component_reference): Likewise.
	(cxx_eval_indirect_ref): Likewise.
	(cxx_eval_store_expression): Likewise.
	(cxx_eval_increment_expression): Likewise.
	(cxx_eval_loop_expr): Likewise.
	(cxx_eval_binary_expression): Likewise.
	(cxx_eval_constant_expression): Cache location of trees for use
        in errors, and prefer it instead of input_location.

gcc/testsuite/ChangeLog:

	* g++.dg/cpp0x/constexpr-48089.C: Updated diagnostic locations.
	* g++.dg/cpp0x/constexpr-diag3.C: Likewise.
	* g++.dg/cpp0x/constexpr-ice20.C: Likewise.
	* g++.dg/cpp1y/constexpr-89481.C: Likewise.
	* g++.dg/cpp1y/constexpr-lifetime1.C: Likewise.
	* g++.dg/cpp1y/constexpr-lifetime2.C: Likewise.
	* g++.dg/cpp1y/constexpr-lifetime3.C: Likewise.
	* g++.dg/cpp1y/constexpr-lifetime4.C: Likewise.
	* g++.dg/cpp1y/constexpr-lifetime5.C: Likewise.
	* g++.dg/cpp1y/constexpr-union5.C: Likewise.
	* g++.dg/cpp1y/pr68180.C: Likewise.
	* g++.dg/cpp1z/constexpr-lambda6.C: Likewise.
	* g++.dg/cpp2a/bit-cast11.C: Likewise.
	* g++.dg/cpp2a/bit-cast12.C: Likewise.
	* g++.dg/cpp2a/bit-cast14.C: Likewise.
	* g++.dg/cpp2a/constexpr-98122.C: Likewise.
	* g++.dg/cpp2a/constexpr-dynamic17.C: Likewise.
	* g++.dg/cpp2a/constexpr-init1.C: Likewise.
	* g++.dg/cpp2a/constexpr-new12.C: Likewise.
	* g++.dg/cpp2a/constexpr-new3.C: Likewise.
	* g++.dg/ext/constexpr-vla2.C: Likewise.
	* g++.dg/ext/constexpr-vla3.C: Likewise.
	* g++.dg/ubsan/pr63956.C: Likewise.

libstdc++/ChangeLog:

	* testsuite/25_algorithms/equal/constexpr_neg.cc: Updated
	diagnostics locations.

Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
---
 gcc/cp/constexpr.cc                           | 83 +++++++++++--------
 gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C  | 10 +--
 gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C  |  2 +-
 gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |  4 +-
 gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C  |  3 +-
 .../g++.dg/cpp1y/constexpr-lifetime1.C        |  1 +
 .../g++.dg/cpp1y/constexpr-lifetime2.C        |  4 +-
 .../g++.dg/cpp1y/constexpr-lifetime3.C        |  4 +-
 .../g++.dg/cpp1y/constexpr-lifetime4.C        |  2 +-
 .../g++.dg/cpp1y/constexpr-lifetime5.C        |  4 +-
 gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C |  4 +-
 gcc/testsuite/g++.dg/cpp1y/pr68180.C          |  4 +-
 .../g++.dg/cpp1z/constexpr-lambda6.C          |  4 +-
 gcc/testsuite/g++.dg/cpp2a/bit-cast11.C       | 10 +--
 gcc/testsuite/g++.dg/cpp2a/bit-cast12.C       | 10 +--
 gcc/testsuite/g++.dg/cpp2a/bit-cast14.C       | 14 ++--
 gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C  |  4 +-
 .../g++.dg/cpp2a/constexpr-dynamic17.C        |  5 +-
 gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C  |  5 +-
 gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C  |  6 +-
 gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C   | 10 +--
 gcc/testsuite/g++.dg/ext/constexpr-vla2.C     |  4 +-
 gcc/testsuite/g++.dg/ext/constexpr-vla3.C     |  4 +-
 gcc/testsuite/g++.dg/ubsan/pr63956.C          |  4 +-
 .../25_algorithms/equal/constexpr_neg.cc      |  7 +-
 25 files changed, 111 insertions(+), 101 deletions(-)

diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
index bdbc12144a7..74045477a92 100644
--- a/gcc/cp/constexpr.cc
+++ b/gcc/cp/constexpr.cc
@@ -1165,10 +1165,12 @@ public:
   hash_set<tree> *modifiable;
   /* Number of heap VAR_DECL deallocations.  */
   unsigned heap_dealloc_count;
+  /* Current location in case subtree has no location information.  */
+  location_t loc;
   /* Constructor.  */
   constexpr_global_ctx ()
     : constexpr_ops_count (0), cleanups (NULL), modifiable (nullptr),
-      heap_dealloc_count (0) {}
+      heap_dealloc_count (0), loc (input_location) {}
 
  tree get_value (tree t)
   {
@@ -2113,7 +2115,7 @@ cxx_eval_internal_function (const constexpr_ctx *ctx, tree t,
 
     default:
       if (!ctx->quiet)
-	error_at (cp_expr_loc_or_input_loc (t),
+	error_at (cp_expr_loc_or_loc (t, ctx->global->loc),
 		  "call to internal function %qE", t);
       *non_constant_p = true;
       return t;
@@ -2128,7 +2130,7 @@ cxx_eval_internal_function (const constexpr_ctx *ctx, tree t,
 
   if (TREE_CODE (arg0) == INTEGER_CST && TREE_CODE (arg1) == INTEGER_CST)
     {
-      location_t loc = cp_expr_loc_or_input_loc (t);
+      location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
       tree type = TREE_TYPE (TREE_TYPE (t));
       tree result = fold_binary_loc (loc, opcode, type,
 				     fold_convert_loc (loc, type, arg0),
@@ -2164,9 +2166,9 @@ clear_no_implicit_zero (tree ctor)
    EXPR is the MODIFY_EXPR expression performing the modification.  */
 
 static void
-modifying_const_object_error (tree expr, tree obj)
+modifying_const_object_error (const constexpr_ctx* ctx, tree expr, tree obj)
 {
-  location_t loc = cp_expr_loc_or_input_loc (expr);
+  location_t loc = cp_expr_loc_or_loc (expr, ctx->global->loc);
   auto_diagnostic_group d;
   error_at (loc, "modifying a const object %qE is not allowed in "
 	    "a constant expression", TREE_OPERAND (expr, 0));
@@ -2358,7 +2360,7 @@ cxx_eval_dynamic_cast_fn (const constexpr_ctx *ctx, tree call,
   tree obj = CALL_EXPR_ARG (call, 0);
   tree type = CALL_EXPR_ARG (call, 2);
   HOST_WIDE_INT hint = int_cst_value (CALL_EXPR_ARG (call, 3));
-  location_t loc = cp_expr_loc_or_input_loc (call);
+  location_t loc = cp_expr_loc_or_loc (call, ctx->global->loc);
 
   /* Get the target type of the dynamic_cast.  */
   gcc_assert (TREE_CODE (type) == ADDR_EXPR);
@@ -3656,7 +3658,7 @@ cxx_eval_binary_expression (const constexpr_ctx *ctx, tree t,
       && integer_zerop (lhs) && !integer_zerop (rhs))
     {
       if (!ctx->quiet)
-	error ("arithmetic involving a null pointer in %qE", lhs);
+	error_at (loc, "arithmetic involving a null pointer in %qE", lhs);
       *non_constant_p = true;
       return t;
     }
@@ -4149,7 +4151,7 @@ eval_and_check_array_index (const constexpr_ctx *ctx,
 			    tree t, bool allow_one_past,
 			    bool *non_constant_p, bool *overflow_p)
 {
-  location_t loc = cp_expr_loc_or_input_loc (t);
+  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
   tree ary = TREE_OPERAND (t, 0);
   t = TREE_OPERAND (t, 1);
   tree index = cxx_eval_constant_expression (ctx, t, vc_prvalue,
@@ -4187,6 +4189,7 @@ cxx_eval_array_reference (const constexpr_ctx *ctx, tree t,
 			  value_cat lval,
 			  bool *non_constant_p, bool *overflow_p)
 {
+  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
   tree oldary = TREE_OPERAND (t, 0);
   tree ary = cxx_eval_constant_expression (ctx, oldary,
 					   lval,
@@ -4274,7 +4277,7 @@ cxx_eval_array_reference (const constexpr_ctx *ctx, tree t,
 	 building; if there's no initializer for this element yet,
 	 that's an error.  */
       if (!ctx->quiet)
-	error ("accessing uninitialized array element");
+	error_at (loc, "accessing uninitialized array element");
       *non_constant_p = true;
       return t;
     }
@@ -4323,13 +4326,14 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
   tree whole = cxx_eval_constant_expression (ctx, orig_whole,
 					     lval,
 					     non_constant_p, overflow_p);
+  location_t loc = cp_expr_loc_or_loc (whole, ctx->global->loc);
   if (*non_constant_p)
     return t;
   if (INDIRECT_REF_P (whole)
       && integer_zerop (TREE_OPERAND (whole, 0)))
     {
       if (!ctx->quiet)
-	error ("dereferencing a null pointer in %qE", orig_whole);
+	error_at (loc, "dereferencing a null pointer in %qE", orig_whole);
       *non_constant_p = true;
       return t;
     }
@@ -4348,7 +4352,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
   if (TREE_CODE (whole) != CONSTRUCTOR)
     {
       if (!ctx->quiet)
-	error ("%qE is not a constant expression", orig_whole);
+	error_at (loc, "%qE is not a constant expression", orig_whole);
       *non_constant_p = true;
       return t;
     }
@@ -4356,7 +4360,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
       && DECL_MUTABLE_P (part))
     {
       if (!ctx->quiet)
-	error ("mutable %qD is not usable in a constant expression", part);
+	error_at (loc, "mutable %qD is not usable in a constant expression", part);
       *non_constant_p = true;
       return t;
     }
@@ -4386,10 +4390,10 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
 	{
 	  constructor_elt *cep = CONSTRUCTOR_ELT (whole, 0);
 	  if (cep->value == NULL_TREE)
-	    error ("accessing uninitialized member %qD", part);
+	    error_at (loc, "accessing uninitialized member %qD", part);
 	  else
-	    error ("accessing %qD member instead of initialized %qD member in "
-		   "constant expression", part, cep->index);
+	    error_at (loc, "accessing %qD member instead of initialized %qD member "
+		      "in constant expression", part, cep->index);
 	}
       *non_constant_p = true;
       return t;
@@ -4408,7 +4412,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
 	 building; if there's no initializer for this member yet, that's an
 	 error.  */
       if (!ctx->quiet)
-	error ("accessing uninitialized member %qD", part);
+	error_at (loc, "accessing uninitialized member %qD", part);
       *non_constant_p = true;
       return t;
     }
@@ -4436,6 +4440,7 @@ cxx_eval_bit_field_ref (const constexpr_ctx *ctx, tree t,
   tree whole = cxx_eval_constant_expression (ctx, orig_whole,
 					     lval,
 					     non_constant_p, overflow_p);
+  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
   tree start, field, value;
   unsigned HOST_WIDE_INT i;
 
@@ -4448,7 +4453,7 @@ cxx_eval_bit_field_ref (const constexpr_ctx *ctx, tree t,
       && TREE_CODE (whole) != CONSTRUCTOR)
     {
       if (!ctx->quiet)
-	error ("%qE is not a constant expression", orig_whole);
+	error_at (loc, "%qE is not a constant expression", orig_whole);
       *non_constant_p = true;
     }
   if (*non_constant_p)
@@ -4460,7 +4465,7 @@ cxx_eval_bit_field_ref (const constexpr_ctx *ctx, tree t,
 				 TREE_OPERAND (t, 1), TREE_OPERAND (t, 2)))
 	return r;
       if (!ctx->quiet)
-	error ("%qE is not a constant expression", orig_whole);
+	error_at (loc, "%qE is not a constant expression", orig_whole);
       *non_constant_p = true;
       return t;
     }
@@ -5609,6 +5614,7 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
 		       value_cat lval,
 		       bool *non_constant_p, bool *overflow_p)
 {
+  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
   tree orig_op0 = TREE_OPERAND (t, 0);
   bool empty_base = false;
 
@@ -5639,7 +5645,7 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
       if (!lval && integer_zerop (op0))
 	{
 	  if (!ctx->quiet)
-	    error ("dereferencing a null pointer");
+	    error_at (loc, "dereferencing a null pointer");
 	  *non_constant_p = true;
 	  return t;
 	}
@@ -5658,8 +5664,7 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
 			  (TREE_TYPE (TREE_TYPE (sub)), TREE_TYPE (t)));
 	      /* DR 1188 says we don't have to deal with this.  */
 	      if (!ctx->quiet)
-		error_at (cp_expr_loc_or_input_loc (t),
-			  "accessing value of %qE through a %qT glvalue in a "
+		error_at (loc, "accessing value of %qE through a %qT glvalue in a "
 			  "constant expression", build_fold_indirect_ref (sub),
 			  TREE_TYPE (t));
 	      *non_constant_p = true;
@@ -5906,6 +5911,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
 			   value_cat lval,
 			   bool *non_constant_p, bool *overflow_p)
 {
+  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
   constexpr_ctx new_ctx = *ctx;
 
   tree init = TREE_OPERAND (t, 1);
@@ -6030,7 +6036,7 @@ 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);
+	error_at (loc, "modification of %qE is not a constant expression", object);
       *non_constant_p = true;
       return t;
     }
@@ -6128,7 +6134,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
 	  if (cxx_dialect < cxx20)
 	    {
 	      if (!ctx->quiet)
-		error_at (cp_expr_loc_or_input_loc (t),
+		error_at (cp_expr_loc_or_loc (t, ctx->global->loc),
 			  "change of the active member of a union "
 			  "from %qD to %qD",
 			  CONSTRUCTOR_ELT (*valp, 0)->index,
@@ -6141,7 +6147,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
 	      /* Diagnose changing the active union member while the union
 		 is in the process of being initialized.  */
 	      if (!ctx->quiet)
-		error_at (cp_expr_loc_or_input_loc (t),
+		error_at (cp_expr_loc_or_loc (t, ctx->global->loc),
 			  "change of the active member of a union "
 			  "from %qD to %qD during initialization",
 			  CONSTRUCTOR_ELT (*valp, 0)->index,
@@ -6224,7 +6230,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
       if (fail)
 	{
 	  if (!ctx->quiet)
-	    modifying_const_object_error (t, const_object_being_modified);
+	    modifying_const_object_error (ctx, t, const_object_being_modified);
 	  *non_constant_p = true;
 	  return t;
 	}
@@ -6381,6 +6387,8 @@ cxx_eval_increment_expression (const constexpr_ctx *ctx, tree t,
   tree offset = TREE_OPERAND (t, 1);
   gcc_assert (TREE_CONSTANT (offset));
 
+  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
+
   /* OFFSET is constant, but perhaps not constant enough.  We need to
      e.g. bash FLOAT_EXPRs to REAL_CSTs.  */
   offset = fold_simple (offset);
@@ -6428,8 +6436,7 @@ cxx_eval_increment_expression (const constexpr_ctx *ctx, tree t,
     VERIFY_CONSTANT (mod);
 
   /* Storing the modified value.  */
-  tree store = build2_loc (cp_expr_loc_or_loc (t, input_location),
-			   MODIFY_EXPR, type, op, mod);
+  tree store = build2_loc (loc, MODIFY_EXPR, type, op, mod);
   mod = cxx_eval_constant_expression (ctx, store, lval,
 				      non_constant_p, overflow_p);
   ggc_free (store);
@@ -6602,6 +6609,7 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
 		    bool *non_constant_p, bool *overflow_p,
 		    tree *jump_target)
 {
+  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
   constexpr_ctx new_ctx = *ctx;
   tree local_target;
   if (!jump_target)
@@ -6691,7 +6699,7 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
       if (++count >= constexpr_loop_limit)
 	{
 	  if (!ctx->quiet)
-	    error_at (cp_expr_loc_or_input_loc (t),
+	    error_at (loc,
 		      "%<constexpr%> loop iteration count exceeds limit of %d "
 		      "(use %<-fconstexpr-loop-limit=%> to increase the limit)",
 		      constexpr_loop_limit);
@@ -6950,7 +6958,10 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
       return t;
     }
 
-  location_t loc = cp_expr_loc_or_input_loc (t);
+  /* Track current location, propagating down from parent calls
+     in case this expression has no location information.  */
+  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
+  ctx->global->loc = loc;
 
   STRIP_ANY_LOCATION_WRAPPER (t);
 
@@ -6973,8 +6984,8 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 	  && !integer_zerop (t))
 	{
 	  if (!ctx->quiet)
-	    error ("value %qE of type %qT is not a constant expression",
-		   t, TREE_TYPE (t));
+	    error_at (loc, "value %qE of type %qT is not a constant expression",
+		      t, TREE_TYPE (t));
 	  *non_constant_p = true;
 	}
 
@@ -7222,8 +7233,8 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 	    if (!ctx->quiet)
 	      {
 		auto_diagnostic_group d;
-		error ("temporary of non-literal type %qT in a "
-		       "constant expression", type);
+		error_at (loc, "temporary of non-literal type %qT in a "
+			  "constant expression", type);
 		explain_non_literal_class (type);
 	      }
 	    *non_constant_p = true;
@@ -8025,8 +8036,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
 	if (function_concept_p (tmpl))
 	  {
 	    if (!ctx->quiet)
-	      error_at (cp_expr_loc_or_input_loc (t),
-			"function concept must be called");
+	      error_at (loc, "function concept must be called");
 	    r = error_mark_node;
 	    break;
 	  }
@@ -8121,6 +8131,9 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
       break;
     }
 
+  /* Reset current location in case it was modified in child calls.  */
+  ctx->global->loc = loc;
+
   if (r == error_mark_node)
     *non_constant_p = true;
 
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C
index 4574eb83ff7..11630f26ffe 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C
@@ -10,11 +10,11 @@
 // R() is well-formed because i is initialized before j.
 
 struct s {
-  constexpr s() : v(v) { }
+  constexpr s() : v(v) { } // { dg-error "accessing uninitialized member" }
   int v;
 };
 
-constexpr s bang;		// { dg-error "|" }
+constexpr s bang;  // { dg-message "in .constexpr. expansion" }
 
 struct R {
   int i,j;
@@ -26,14 +26,14 @@ constexpr R r;			// { dg-bogus "" }
 // Ill-formed (no diagnostic required)
 struct T {
   int i;
-  constexpr int f() { return i; }
+  constexpr int f() { return i; }  // { dg-error "accessing uninitialized member" }
   constexpr T(): i(0) { }
-  constexpr T(const T& t) : i(f()) { } // { dg-message "" }
+  constexpr T(const T& t) : i(f()) { }  // { dg-message "in .constexpr. expansion" }
 };
 
 constexpr T t1;
 // Ill-formed (diagnostic required)
-constexpr T t2(t1);		// { dg-message "" }
+constexpr T t2(t1);		// { dg-message "in .constexpr. expansion" }
 
 // Well-formed
 struct U {
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
index 5eedf42ba36..50c676c56cd 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
@@ -16,7 +16,7 @@ int main()
 struct complex 			// { dg-message "no .constexpr. constructor" "" { target { ! implicit_constexpr } } }
 {
   complex(double r, double i) : re(r), im(i) { }
-  constexpr double real() const { return re; } // { dg-error "not a literal type" "" { target c++11_only } }
+  constexpr double real() const { return re; } // { dg-error "not a literal type|not usable in a constant expression" "" { target { ! implicit_constexpr } } }
   double imag() const { return im; }
 
 private:
diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
index ebaa95e5324..e4e3bf865cd 100644
--- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
+++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
@@ -2,6 +2,6 @@
 // { dg-do compile { target c++11 } }
 
 typedef bool (*Function)(int);
-constexpr bool check(int x, Function p) { return p(x); }  // { dg-message "in .constexpr. expansion of" }
+constexpr bool check(int x, Function p) { return p(x); }  // { dg-error "lifetime" }
 
-static_assert(check(2, check), "");  // { dg-error "conversion|constant|lifetime|in .constexpr. expansion of" }
+static_assert(check(2, check), "");  // { dg-error "conversion|constant|in .constexpr. expansion of" }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C
index 8ac4ef0fd36..6f8f6a8038e 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C
@@ -6,7 +6,7 @@ foo ()
 {
   union U { long long a; int b[2]; } u { 5LL };
   u.b[1] = 4;		// { dg-error "change of the active member of a union from" "" { target c++17_down } }
-  return u.b[0];
+  return u.b[0];	// { dg-error "accessing uninitialized array element" "" { target c++2a } }
 }
 
 constexpr int
@@ -19,6 +19,5 @@ bar ()
 
 static_assert (foo () == 0, "");	// { dg-error "non-constant condition for static assertion" }
 					// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1 }
-					// { dg-error "accessing uninitialized array element" "" { target c++2a } .-2 }
 static_assert (bar () == 4, "");	// { dg-error "non-constant condition for static assertion" "" { target c++17_down } }
 					// { dg-message "in 'constexpr' expansion of" "" { target c++17_down } .-1 }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
index 43aa7c974c1..f79f1611d5f 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
@@ -11,3 +11,4 @@ constexpr const int& test() {
   return local.get();
 }
 constexpr int x = test();  // { dg-error "accessing object outside its lifetime" }
+
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
index 22cd919fcda..2f5ae8db6d5 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
@@ -8,9 +8,9 @@ struct S {
 
 constexpr int error() {
   const auto& local = S{}.get();  // { dg-message "note: declared here" }
-  return local;
+  return local;  // { dg-error "accessing object outside its lifetime" }
 }
-constexpr int x = error();  // { dg-error "accessing object outside its lifetime" }
+constexpr int x = error();  // { dg-message "in .constexpr. expansion" }
 
 constexpr int ok() {
   // temporary should only be destroyed after end of full-expression
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
index 6329f8cf6c6..53785521d05 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
@@ -7,7 +7,7 @@ constexpr int f(int i) {
     int j = 123;  // { dg-message "note: declared here" }
     p = &j;
   }
-  return *p;
+  return *p;  // { dg-error "accessing object outside its lifetime" }
 }
 
-constexpr int i = f(0);  // { dg-error "accessing object outside its lifetime" }
+constexpr int i = f(0);  // { dg-message "in .constexpr. expansion" }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
index 181a1201663..4302da1eddc 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
@@ -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 "non-constant condition|accessing object outside its lifetime" }
 
 // no deference, shouldn't error
 static_assert((test(), true), "");
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
index ad3ef579f63..a12920c8fba 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
@@ -5,7 +5,7 @@ constexpr const int& id(int x) { return x; }  // { dg-message "note: declared he
 
 constexpr bool test() {
   const int& y = id(3);
-  return y == 3;
+  return y == 3;  // { dg-error "accessing object outside its lifetime" }
 }
 
-constexpr bool x = test();  // { dg-error "accessing object outside its lifetime" }
+constexpr bool x = test();  // { dg-message "in .constexpr. expansion" }
diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C
index 55fe9fa2f0b..3d76345d564 100644
--- a/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C
+++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C
@@ -8,8 +8,8 @@ union U {
 };
 
 constexpr int foo(U *up) {
-  up->a++;
+  up->a++; // { dg-error "accessing uninitialized member" }
   return {42};
 }
 
-extern constexpr U u = {}; // { dg-error "accessing uninitialized member" }
+extern constexpr U u = {}; // { dg-message "in .constexpr. expansion" }
diff --git a/gcc/testsuite/g++.dg/cpp1y/pr68180.C b/gcc/testsuite/g++.dg/cpp1y/pr68180.C
index 9e6e5e984f9..8de1ef3936b 100644
--- a/gcc/testsuite/g++.dg/cpp1y/pr68180.C
+++ b/gcc/testsuite/g++.dg/cpp1y/pr68180.C
@@ -6,11 +6,11 @@ typedef float __attribute__( ( vector_size( 16 ) ) ) float32x4_t;
 constexpr float32x4_t fill(float x) {
   float32x4_t v{0};
   constexpr auto vs = sizeof(v)/sizeof(v[0]);
-  for (auto i=0U; i<vs; ++i) v[i]=i;
+  for (auto i=0U; i<vs; ++i) v[i]=i; // { dg-error "not a constant" }
   return v+x;
 }
 
 float32x4_t foo(float32x4_t x) {
-  constexpr float32x4_t v = fill(1.f); // { dg-error "not a constant||in .constexpr. expansion of " }
+  constexpr float32x4_t v = fill(1.f); // { dg-message "in .constexpr. expansion of " }
   return x+v;
 }
diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C
index 214d3821299..c46c2d4c7fe 100644
--- a/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C
+++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C
@@ -1,7 +1,7 @@
 // Testcase from P0170R1
 // { dg-do compile { target c++17 } }
 
-auto monoid = [](auto v) { return [=] { return v; }; };
+auto monoid = [](auto v) { return [=] { return v; }; };  // { dg-error "not usable in a constant expression" }
 auto add = [](auto m1) constexpr {
   auto ret = m1();
   return [=](auto m2) mutable {
@@ -22,7 +22,7 @@ int main()
   // member function call operator can not perform an lvalue-to-rvalue conversion
   // on one of its subobjects (that represents its capture) in a constant
   // expression.
-  auto two = monoid(2);
+  auto two = monoid(2);  // { dg-message "not declared .constexpr." }
   if (!(two() == 2)) __builtin_abort(); // OK, not a constant expression.
   static_assert(add(one)(one)() == two()); // { dg-error "|in .constexpr. expansion of " } two() is not a constant expression
   static_assert(add(one)(one)() == monoid(2)()); // OK
diff --git a/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C b/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C
index a3eb31bc6c7..760c9ca40b4 100644
--- a/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C
+++ b/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C
@@ -28,7 +28,7 @@ f3 ()
 {
   T t = { 1, 2 };
   S s = __builtin_bit_cast (S, t);
-  return s.a[1] == 0;
+  return s.a[1] == 0;		// { dg-error "accessing uninitialized array element" }
 }
 
 constexpr bool
@@ -52,12 +52,12 @@ f6 ()
 {
   W t = { 1, 2 };
   V s = __builtin_bit_cast (V, t);
-  return s.b.a[1] == 1;
+  return s.b.a[1] == 1;		// { dg-error "accessing uninitialized array element" }
 }
 
 constexpr bool a = f1 ();
 constexpr bool b = f2 ();
-constexpr bool c = f3 ();	// { dg-error "accessing uninitialized array element" }
-constexpr bool d = f4 ();
+constexpr bool c = f3 ();	// { dg-message "in .constexpr. expansion" }
+constexpr bool d = f4 ();	// { dg-message "in .constexpr. expansion" }
 constexpr bool e = f5 ();
-constexpr bool f = f6 ();	// { dg-error "accessing uninitialized array element" }
+constexpr bool f = f6 ();	// { dg-message "in .constexpr. expansion" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C b/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C
index 9c699dd55f0..e205bc6a8c1 100644
--- a/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C
+++ b/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C
@@ -33,7 +33,7 @@ f3 ()
 {
   T t = { 1, 2 };
   S s = __builtin_bit_cast (S, t);
-  return s.a[1] == 0;
+  return s.a[1] == 0;		// { dg-error "accessing uninitialized array element" }
 }
 
 constexpr bool
@@ -57,12 +57,12 @@ f6 ()
 {
   W t = { 1, 2 };
   V s = __builtin_bit_cast (V, t);
-  return s.b.a[1] == 1;
+  return s.b.a[1] == 1;		// { dg-error "accessing uninitialized array element" }
 }
 
 constexpr bool a = f1 ();
 constexpr bool b = f2 ();
-constexpr bool c = f3 ();	// { dg-error "accessing uninitialized array element" }
-constexpr bool d = f4 ();
+constexpr bool c = f3 ();	// { dg-message "in .constexpr. expansion" }
+constexpr bool d = f4 ();	// { dg-message "in .constexpr. expansion" }
 constexpr bool e = f5 ();
-constexpr bool f = f6 ();	// { dg-error "accessing uninitialized array element" }
+constexpr bool f = f6 ();	// { dg-message "in .constexpr. expansion" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C b/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C
index 5e185919be4..e0cc9a39702 100644
--- a/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C
+++ b/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C
@@ -44,7 +44,7 @@ f5 ()
 {
   T1 t = { 0, 0, 0, 0, 0, 0, 0 };
   S s = __builtin_bit_cast (S, t);
-  unsigned char a = s.a;
+  unsigned char a = s.a;		// { dg-error "accessing uninitialized member" }
   return true;
 }
 
@@ -53,7 +53,7 @@ f6 ()
 {
   T2 t = { 0, 0, 0, 0, 0, 0, 0 };
   S s = __builtin_bit_cast (S, t);
-  unsigned char b = s.b;
+  unsigned char b = s.b;		// { dg-error "accessing uninitialized member" }
   return true;
 }
 
@@ -62,14 +62,14 @@ f7 ()
 {
   T3 t = { 0, 0, 0, 0, 0, 0, 0 };
   S s = __builtin_bit_cast (S, t);
-  unsigned char c = s.c;
+  unsigned char c = s.c;		// { dg-error "accessing uninitialized member" }
   return true;
 }
 
 constexpr bool a = f1 ();
 constexpr bool b = f2 ();
 constexpr bool c = f3 ();
-constexpr bool d = f4 ();
-constexpr bool e = f5 ();	// { dg-error "accessing uninitialized member" }
-constexpr bool f = f6 ();	// { dg-error "accessing uninitialized member" }
-constexpr bool g = f7 ();	// { dg-error "accessing uninitialized member" }
+constexpr bool d = f4 ();	// { dg-message "in .constexpr. expansion" }
+constexpr bool e = f5 ();	// { dg-message "in .constexpr. expansion" }
+constexpr bool f = f6 ();	// { dg-message "in .constexpr. expansion" }
+constexpr bool g = f7 ();	// { dg-message "in .constexpr. expansion" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C
index 01bdfa5bd4d..b0c91d5ef97 100644
--- a/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C
@@ -9,7 +9,7 @@ bar ()
 {
   V f { .b = 42 };
   constexpr auto m = &V::a;
-  return (f.*m) == 42;
+  return (f.*m) == 42;  // { dg-error "accessing 'V::a' member instead of initialized 'V::b' member in constant expression" }
 }
 
 constexpr bool
@@ -21,5 +21,5 @@ baz ()
 }
 
 static_assert (bar (), "");	// { dg-error "non-constant condition for static assertion" }
-				// { dg-error "accessing 'V::a' member instead of initialized 'V::b' member in constant expression" "" { target *-*-* } .-1 }
+				// { dg-message "in .constexpr. expansion" "" { target *-*-* } .-1 }
 static_assert (baz (), "");
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C
index a26678e6ed7..28facf192df 100644
--- a/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C
@@ -25,8 +25,7 @@ struct D : B, A {
 
 constexpr B::B(V* v, A* a)
 {
-  dynamic_cast<B*>(a);
+  dynamic_cast<B*>(a); // { dg-error "accessing uninitialized member" }
 }
 
-constexpr D d; // { dg-error "accessing uninitialized member" }
-// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1 }
+constexpr D d; // { dg-message "in 'constexpr' expansion of" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C
index e56ecfed48a..b4e39b6f928 100644
--- a/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C
@@ -52,11 +52,10 @@ constexpr int
 fn5 ()
 {
   struct S { int a = 9; int b; } s;
-  return s.b;
+  return s.b; // { dg-error "accessing uninitialized member" }
 }
 
-constexpr int b = fn5 (); // { dg-error "accessing uninitialized member" }
-// { dg-message "in .constexpr. expansion of" "" { target *-*-* } .-1 }
+constexpr int b = fn5 (); // { dg-message "in .constexpr. expansion of" }
 
 constexpr int
 fn6 ()
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C
index 5a3d06a5fab..832782e1427 100644
--- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C
@@ -17,11 +17,11 @@ struct B : A {
 constexpr int
 foo ()
 {
-  A *a = new B ();
+  A *a = new B ();  // { dg-message "allocated here" }
   a->a = 4;
   delete a;
-  int r = a->foo ();
+  int r = a->foo ();  // { dg-error "constant expression" }
   return r;
 }
 
-constexpr auto a = foo ();	// { dg-error "constant expression" }
+constexpr auto a = foo ();  // { dg-message "in .constexpr. expansion" }
diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
index 70b841208f8..3ba440fec53 100644
--- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
+++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
@@ -45,11 +45,10 @@ constexpr bool
 f5 ()
 {
   int *p = new int;		// { dg-message "allocated here" }
-  return *p == 1;
+  return *p == 1;		// { dg-error "the content of uninitialized storage is not usable in a constant expression" }
 }
 
-constexpr auto v5 = f5 ();	// { dg-error "the content of uninitialized storage is not usable in a constant expression" }
-				// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1 }
+constexpr auto v5 = f5 (); 	// { dg-message "in 'constexpr' expansion of" }
 
 constexpr bool
 f6 ()
@@ -57,11 +56,10 @@ f6 ()
   int *p = new int (2);		// { dg-message "allocated here" }
   int *q = p;
   delete p;
-  return *q == 2;
+  return *q == 2;		// { dg-error "use of allocated storage after deallocation in a constant expression" }
 }
 
-constexpr auto v6 = f6 ();	// { dg-error "use of allocated storage after deallocation in a constant expression" }
-				// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1  }
+constexpr auto v6 = f6 (); 	// { dg-message "in 'constexpr' expansion of" }
 
 constexpr int *
 f7 ()
diff --git a/gcc/testsuite/g++.dg/ext/constexpr-vla2.C b/gcc/testsuite/g++.dg/ext/constexpr-vla2.C
index d4ea7c58c0d..e09a27af3de 100644
--- a/gcc/testsuite/g++.dg/ext/constexpr-vla2.C
+++ b/gcc/testsuite/g++.dg/ext/constexpr-vla2.C
@@ -4,7 +4,7 @@
 constexpr int
 fn_bad (int n)
 {
-  __extension__ int a [n] = { 0 };
+  __extension__ int a [n] = { 0 };  // { dg-error "array subscript" }
   int z = a [0] + (n ? fn_bad (n - 1) : 0); // { dg-message "in .constexpr. expansion of " } 
   return z;
 }
@@ -18,4 +18,4 @@ fn_ok (int n)
 }
 
 constexpr int i1 = fn_ok (3);
-constexpr int i2 = fn_bad (3); // { dg-error "array subscript|in .constexpr. expansion of " }
+constexpr int i2 = fn_bad (3); // { dg-message "in .constexpr. expansion of " }
diff --git a/gcc/testsuite/g++.dg/ext/constexpr-vla3.C b/gcc/testsuite/g++.dg/ext/constexpr-vla3.C
index 538b576a825..6f9daa1897f 100644
--- a/gcc/testsuite/g++.dg/ext/constexpr-vla3.C
+++ b/gcc/testsuite/g++.dg/ext/constexpr-vla3.C
@@ -4,11 +4,11 @@
 constexpr int
 foo (int n)
 {
-  __extension__ int a[n] = { 1, 2, 3, 4, 5, 6 };
+  __extension__ int a[n] = { 1, 2, 3, 4, 5, 6 }; // { dg-error "array subscript" }
   int z = 0;
   for (int i = 0; i <= n; ++i)
     z += a[i];
   return z;
 }
 
-constexpr int n = foo (3); // { dg-error "array subscript|in .constexpr. expansion of " }
+constexpr int n = foo (3); // { dg-message "in .constexpr. expansion of " }
diff --git a/gcc/testsuite/g++.dg/ubsan/pr63956.C b/gcc/testsuite/g++.dg/ubsan/pr63956.C
index 3a1596e6e2e..0771732ef00 100644
--- a/gcc/testsuite/g++.dg/ubsan/pr63956.C
+++ b/gcc/testsuite/g++.dg/ubsan/pr63956.C
@@ -100,13 +100,13 @@ constexpr int
 fn7 (const int *a, int b)
 {
   if (b != 3)
-    return fn6 (*a, b);
+    return fn6 (*a, b); // { dg-error "null pointer" }
   return 7;
 }
 
 constexpr int n1 = 7;
 constexpr int n2 = fn7 (&n1, 5);
-constexpr int n3 = fn7 ((const int *) 0, 8);  // { dg-error "null pointer|in .constexpr. expansion of " }
+constexpr int n3 = fn7 ((const int *) 0, 8);  // { dg-message "in .constexpr. expansion of " }
 
 constexpr int
 fn8 (int i)
diff --git a/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc b/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc
index 34ca5c4805c..fd89ac0e166 100644
--- a/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc
+++ b/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc
@@ -32,7 +32,7 @@ test01()
   return outa;
 }
 
-static_assert(test01()); // { dg-error "outside the bounds" }
+static_assert(test01()); // { dg-error "non-constant condition" }
 
 constexpr bool
 test02()
@@ -44,7 +44,8 @@ test02()
   return outa;
 }
 
-static_assert(test02()); // { dg-error "outside the bounds" }
+static_assert(test02()); // { dg-error "non-constant condition" }
 
-// { dg-prune-output "non-constant condition" }
+// Errors occuring within <algorithm> internals:
+// { dg-error "outside the bounds of array" "" { target *-*-* } 0 }
 // { dg-prune-output "in 'constexpr'" }
-- 
2.34.1


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...]
  2023-03-29  2:32 [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...] Nathaniel Shead
                   ` (2 preceding siblings ...)
  2023-03-29  2:32 ` [PATCH v2 3/3] c++: Improve location information in constexpr evaluation Nathaniel Shead
@ 2023-05-08 10:48 ` Nathaniel Shead
  2023-06-13 10:11 ` Nathaniel Shead
  4 siblings, 0 replies; 13+ messages in thread
From: Nathaniel Shead @ 2023-05-08 10:48 UTC (permalink / raw)
  To: gcc-patches

Just pinging in case this fix has fallen through the cracks.

https://gcc.gnu.org/pipermail/gcc-patches/2023-March/614811.html

On Wed, Mar 29, 2023 at 1:33 PM Nathaniel Shead
<nathanieloshead@gmail.com> wrote:
>
> This is an update of the patch series at
> https://gcc.gnu.org/pipermail/gcc-patches/2023-March/614759.html
>
> The main change is modifying the first patch to store the "expired" flag
> in the C++-specific lang_decl_base struct instead of tree_decl_common.
> The second and third patches to improve diagnostic locations are
> otherwise unchanged.
>
> Bootstrapped and regression tested on x86_64 linux.
>
> Nathaniel
>
> ---
>
> Nathaniel Shead (3):
>   c++: Track lifetimes in constant evaluation [PR70331,PR96630,PR98675]
>   c++: Improve constexpr error for dangling local variables
>   c++: Improve location information in constexpr evaluation
>
>  gcc/cp/constexpr.cc                           | 152 ++++++++++++------
>  gcc/cp/cp-tree.h                              |  10 +-
>  gcc/cp/module.cc                              |   2 +
>  gcc/cp/semantics.cc                           |   5 +-
>  gcc/cp/typeck.cc                              |   5 +-
>  gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C  |  10 +-
>  gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C  |   2 +-
>  gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |   2 +-
>  gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C  |   3 +-
>  .../g++.dg/cpp1y/constexpr-lifetime1.C        |  14 ++
>  .../g++.dg/cpp1y/constexpr-lifetime2.C        |  20 +++
>  .../g++.dg/cpp1y/constexpr-lifetime3.C        |  13 ++
>  .../g++.dg/cpp1y/constexpr-lifetime4.C        |  11 ++
>  .../g++.dg/cpp1y/constexpr-lifetime5.C        |  11 ++
>  gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C |   4 +-
>  gcc/testsuite/g++.dg/cpp1y/pr68180.C          |   4 +-
>  .../g++.dg/cpp1z/constexpr-lambda6.C          |   4 +-
>  gcc/testsuite/g++.dg/cpp2a/bit-cast11.C       |  10 +-
>  gcc/testsuite/g++.dg/cpp2a/bit-cast12.C       |  10 +-
>  gcc/testsuite/g++.dg/cpp2a/bit-cast14.C       |  14 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C  |   4 +-
>  .../g++.dg/cpp2a/constexpr-dynamic17.C        |   5 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C  |   5 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C  |   6 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C   |  10 +-
>  gcc/testsuite/g++.dg/ext/constexpr-vla2.C     |   4 +-
>  gcc/testsuite/g++.dg/ext/constexpr-vla3.C     |   4 +-
>  gcc/testsuite/g++.dg/ubsan/pr63956.C          |   4 +-
>  .../g++.dg/warn/Wreturn-local-addr-6.C        |   3 -
>  .../25_algorithms/equal/constexpr_neg.cc      |   7 +-
>  30 files changed, 246 insertions(+), 112 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
>
> --
> 2.34.1
>

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...]
  2023-03-29  2:32 [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...] Nathaniel Shead
                   ` (3 preceding siblings ...)
  2023-05-08 10:48 ` [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...] Nathaniel Shead
@ 2023-06-13 10:11 ` Nathaniel Shead
  4 siblings, 0 replies; 13+ messages in thread
From: Nathaniel Shead @ 2023-06-13 10:11 UTC (permalink / raw)
  To: gcc-patches

(Another) ping. I also have some more changes on top of this patch set
as well if this looks good as it is.

https://gcc.gnu.org/pipermail/gcc-patches/2023-March/614811.html

Thanks!

On Wed, Mar 29, 2023 at 01:32:55PM +1100, Nathaniel Shead wrote:
> This is an update of the patch series at
> https://gcc.gnu.org/pipermail/gcc-patches/2023-March/614759.html
> 
> The main change is modifying the first patch to store the "expired" flag
> in the C++-specific lang_decl_base struct instead of tree_decl_common.
> The second and third patches to improve diagnostic locations are
> otherwise unchanged.
> 
> Bootstrapped and regression tested on x86_64 linux.
> 
> Nathaniel
> 
> ---
> 
> Nathaniel Shead (3):
>   c++: Track lifetimes in constant evaluation [PR70331,PR96630,PR98675]
>   c++: Improve constexpr error for dangling local variables
>   c++: Improve location information in constexpr evaluation
> 
>  gcc/cp/constexpr.cc                           | 152 ++++++++++++------
>  gcc/cp/cp-tree.h                              |  10 +-
>  gcc/cp/module.cc                              |   2 +
>  gcc/cp/semantics.cc                           |   5 +-
>  gcc/cp/typeck.cc                              |   5 +-
>  gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C  |  10 +-
>  gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C  |   2 +-
>  gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |   2 +-
>  gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C  |   3 +-
>  .../g++.dg/cpp1y/constexpr-lifetime1.C        |  14 ++
>  .../g++.dg/cpp1y/constexpr-lifetime2.C        |  20 +++
>  .../g++.dg/cpp1y/constexpr-lifetime3.C        |  13 ++
>  .../g++.dg/cpp1y/constexpr-lifetime4.C        |  11 ++
>  .../g++.dg/cpp1y/constexpr-lifetime5.C        |  11 ++
>  gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C |   4 +-
>  gcc/testsuite/g++.dg/cpp1y/pr68180.C          |   4 +-
>  .../g++.dg/cpp1z/constexpr-lambda6.C          |   4 +-
>  gcc/testsuite/g++.dg/cpp2a/bit-cast11.C       |  10 +-
>  gcc/testsuite/g++.dg/cpp2a/bit-cast12.C       |  10 +-
>  gcc/testsuite/g++.dg/cpp2a/bit-cast14.C       |  14 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C  |   4 +-
>  .../g++.dg/cpp2a/constexpr-dynamic17.C        |   5 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C  |   5 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C  |   6 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C   |  10 +-
>  gcc/testsuite/g++.dg/ext/constexpr-vla2.C     |   4 +-
>  gcc/testsuite/g++.dg/ext/constexpr-vla3.C     |   4 +-
>  gcc/testsuite/g++.dg/ubsan/pr63956.C          |   4 +-
>  .../g++.dg/warn/Wreturn-local-addr-6.C        |   3 -
>  .../25_algorithms/equal/constexpr_neg.cc      |   7 +-
>  30 files changed, 246 insertions(+), 112 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> 
> -- 
> 2.34.1
> 

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331, PR96630, PR98675]
  2023-03-29  2:32 ` [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331,PR96630,PR98675] Nathaniel Shead
@ 2023-06-23 16:43   ` Patrick Palka
  2023-06-24 14:02     ` Nathaniel Shead
  0 siblings, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2023-06-23 16:43 UTC (permalink / raw)
  To: Nathaniel Shead; +Cc: gcc-patches

On Wed, 29 Mar 2023, Nathaniel Shead via Gcc-patches wrote:

> This adds rudimentary lifetime tracking in C++ constexpr contexts,
> allowing the compiler to report errors with using values after their
> backing has gone out of scope. We don't yet handle other ways of ending
> lifetimes (e.g. explicit destructor calls).

Awesome!

> 
> 	PR c++/96630
> 	PR c++/98675
> 	PR c++/70331
> 
> gcc/cp/ChangeLog:
> 
> 	* constexpr.cc (constexpr_global_ctx::put_value): Mark value as
> 	in lifetime.
> 	(constexpr_global_ctx::remove_value): Mark value as expired.
> 	(cxx_eval_call_expression): Remove comment that is no longer
> 	applicable.
> 	(non_const_var_error): Add check for expired values.
> 	(cxx_eval_constant_expression): Add checks for expired values. Forget
> 	local variables at end of bind expressions. Forget temporaries at end
> 	of cleanup points.
> 	* cp-tree.h (struct lang_decl_base): New flag to track expired values
> 	in constant evaluation.
> 	(DECL_EXPIRED_P): Access the new flag.
> 	(SET_DECL_EXPIRED_P): Modify the new flag.
> 	* module.cc (trees_out::lang_decl_bools): Write out the new flag.
> 	(trees_in::lang_decl_bools): Read in the new flag.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp0x/constexpr-ice20.C: Update error raised by test.
> 	* g++.dg/cpp1y/constexpr-lifetime1.C: New test.
> 	* g++.dg/cpp1y/constexpr-lifetime2.C: New test.
> 	* g++.dg/cpp1y/constexpr-lifetime3.C: New test.
> 	* g++.dg/cpp1y/constexpr-lifetime4.C: New test.
> 	* g++.dg/cpp1y/constexpr-lifetime5.C: New test.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>  gcc/cp/constexpr.cc                           | 69 +++++++++++++++----
>  gcc/cp/cp-tree.h                              | 10 ++-
>  gcc/cp/module.cc                              |  2 +
>  gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |  2 +-
>  .../g++.dg/cpp1y/constexpr-lifetime1.C        | 13 ++++
>  .../g++.dg/cpp1y/constexpr-lifetime2.C        | 20 ++++++
>  .../g++.dg/cpp1y/constexpr-lifetime3.C        | 13 ++++
>  .../g++.dg/cpp1y/constexpr-lifetime4.C        | 11 +++
>  .../g++.dg/cpp1y/constexpr-lifetime5.C        | 11 +++
>  9 files changed, 137 insertions(+), 14 deletions(-)
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
>  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> 
> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> index 3de60cfd0f8..bdbc12144a7 100644
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -1185,10 +1185,22 @@ public:
>    void put_value (tree t, tree v)
>    {
>      bool already_in_map = values.put (t, v);
> +    if (!already_in_map && DECL_P (t))
> +      {
> +	if (!DECL_LANG_SPECIFIC (t))
> +	  retrofit_lang_decl (t);
> +	if (DECL_LANG_SPECIFIC (t))
> +	  SET_DECL_EXPIRED_P (t, false);
> +      }

Since this new flag would only be used only during constexpr evaluation,
could we instead use an on-the-side hash_set in constexpr_global_ctx for
tracking expired-ness?  That way we won't have to allocate a
DECL_LANG_SPECIFIC structure for decls that lack it, and won't have to
worry about the flag in other parts of the compiler.

>      if (!already_in_map && modifiable)
>        modifiable->add (t);
>    }
> -  void remove_value (tree t) { values.remove (t); }
> +  void remove_value (tree t)
> +  {
> +    if (DECL_P (t) && DECL_LANG_SPECIFIC (t))
> +      SET_DECL_EXPIRED_P (t, true);
> +    values.remove (t);
> +  }
>  };
>  
>  /* Helper class for constexpr_global_ctx.  In some cases we want to avoid
> @@ -3157,10 +3169,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
>  	  for (tree save_expr : save_exprs)
>  	    ctx->global->remove_value (save_expr);
>  
> -	  /* Remove the parms/result from the values map.  Is it worth
> -	     bothering to do this when the map itself is only live for
> -	     one constexpr evaluation?  If so, maybe also clear out
> -	     other vars from call, maybe in BIND_EXPR handling?  */
> +	  /* Remove the parms/result from the values map.  */
>  	  ctx->global->remove_value (res);
>  	  for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
>  	    ctx->global->remove_value (parm);
> @@ -5708,6 +5717,13 @@ non_const_var_error (location_t loc, tree r, bool fundef_p)
>  	inform (DECL_SOURCE_LOCATION (r), "allocated here");
>        return;
>      }
> +  if (DECL_EXPIRED_P (r))
> +    {
> +      if (constexpr_error (loc, fundef_p, "accessing object outside its "
> +			   "lifetime"))
> +	inform (DECL_SOURCE_LOCATION (r), "declared here");
> +      return;
> +    }
>    if (!constexpr_error (loc, fundef_p, "the value of %qD is not usable in "
>  			"a constant expression", r))
>      return;
> @@ -7048,6 +7064,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>  	  r = build_constructor (TREE_TYPE (t), NULL);
>  	  TREE_CONSTANT (r) = true;
>  	}
> +      else if (DECL_EXPIRED_P (t))
> +	{
> +	  if (!ctx->quiet)
> +	    non_const_var_error (loc, r, /*fundef_p*/false);
> +	  *non_constant_p = true;
> +	  break;
> +	}
>        else if (ctx->strict)
>  	r = decl_really_constant_value (t, /*unshare_p=*/false);
>        else
> @@ -7093,7 +7116,15 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>        else
>  	{
>  	  if (!ctx->quiet)
> -	    error ("%qE is not a constant expression", t);
> +	    {
> +	      if (DECL_EXPIRED_P (r))
> +		{
> +		  error_at (loc, "accessing object outside its lifetime");
> +		  inform (DECL_SOURCE_LOCATION (r), "declared here");
> +		}
> +	      else
> +		error_at (loc, "%qE is not a constant expression", t);
> +	    }
>  	  *non_constant_p = true;
>  	}
>        break;
> @@ -7315,17 +7346,28 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>  	auto_vec<tree, 2> cleanups;
>  	vec<tree> *prev_cleanups = ctx->global->cleanups;
>  	ctx->global->cleanups = &cleanups;
> -	r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0),
> +
> +	auto_vec<tree, 10> save_exprs;
> +	constexpr_ctx new_ctx = *ctx;
> +	new_ctx.save_exprs = &save_exprs;
> +
> +	r = cxx_eval_constant_expression (&new_ctx, TREE_OPERAND (t, 0),
>  					  lval,
>  					  non_constant_p, overflow_p,
>  					  jump_target);
> +
>  	ctx->global->cleanups = prev_cleanups;
>  	unsigned int i;
>  	tree cleanup;
>  	/* Evaluate the cleanups.  */
>  	FOR_EACH_VEC_ELT_REVERSE (cleanups, i, cleanup)
> -	  cxx_eval_constant_expression (ctx, cleanup, vc_discard,
> +	  cxx_eval_constant_expression (&new_ctx, cleanup, vc_discard,
>  					non_constant_p, overflow_p);
> +
> +	/* Forget SAVE_EXPRs and TARGET_EXPRs created by this
> +	   full-expression.  */
> +	for (tree save_expr : save_exprs)
> +	  ctx->global->remove_value (save_expr);
>        }
>        break;
>  
> @@ -7831,10 +7873,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>  				      non_constant_p, overflow_p, jump_target);
>  
>      case BIND_EXPR:
> -      return cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (t),
> -					   lval,
> -					   non_constant_p, overflow_p,
> -					   jump_target);
> +      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;
>  
>      case PREINCREMENT_EXPR:
>      case POSTINCREMENT_EXPR:
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index b74c18b03ad..3cc08da816f 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -2860,6 +2860,7 @@ struct GTY(()) lang_decl_base {
>    unsigned concept_p : 1;                  /* applies to vars and functions */
>    unsigned var_declared_inline_p : 1;	   /* var */
>    unsigned dependent_init_p : 1;	   /* var */
> +  unsigned expired_p : 1;		   /* var or parm */
>  
>    /* The following apply to VAR, FUNCTION, TYPE, CONCEPT, & NAMESPACE
>       decls.  */
> @@ -2871,7 +2872,7 @@ struct GTY(()) lang_decl_base {
>    /* VAR_DECL or FUNCTION_DECL has keyed decls.     */
>    unsigned module_keyed_decls_p : 1;
>  
> -  /* 12 spare bits.  */
> +  /* 11 spare bits.  */
>  };
>  
>  /* True for DECL codes which have template info and access.  */
> @@ -4366,6 +4367,13 @@ get_vec_init_expr (tree t)
>  #define SET_DECL_DEPENDENT_INIT_P(NODE, X) \
>    (DECL_LANG_SPECIFIC (VAR_DECL_CHECK (NODE))->u.base.dependent_init_p = (X))
>  
> +/* Nonzero if NODE is a VAR_DECL, PARM_DECL, or FIELD_DECL that is within
> +   its lifetime for constant evaluation purposes.  */
> +#define DECL_EXPIRED_P(NODE) \
> +  (DECL_LANG_SPECIFIC (NODE) && DECL_LANG_SPECIFIC (NODE)->u.base.expired_p)
> +#define SET_DECL_EXPIRED_P(NODE, X) \
> +  (DECL_LANG_SPECIFIC (NODE)->u.base.expired_p = (X))
> +
>  /* Nonzero if NODE is an artificial VAR_DECL for a C++17 structured binding
>     declaration or one of VAR_DECLs for the user identifiers in it.  */
>  #define DECL_DECOMPOSITION_P(NODE) \
> diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> index ac2fe66b080..7af43b5736d 100644
> --- a/gcc/cp/module.cc
> +++ b/gcc/cp/module.cc
> @@ -5654,6 +5654,7 @@ trees_out::lang_decl_bools (tree t)
>    WB (lang->u.base.concept_p);
>    WB (lang->u.base.var_declared_inline_p);
>    WB (lang->u.base.dependent_init_p);
> +  WB (lang->u.base.expired_p);
>    /* When building a header unit, everthing is marked as purview, (so
>       we know which decls to write).  But when we import them we do not
>       want to mark them as in module purview.  */
> @@ -5728,6 +5729,7 @@ trees_in::lang_decl_bools (tree t)
>    RB (lang->u.base.concept_p);
>    RB (lang->u.base.var_declared_inline_p);
>    RB (lang->u.base.dependent_init_p);
> +  RB (lang->u.base.expired_p);
>    RB (lang->u.base.module_purview_p);
>    RB (lang->u.base.module_attach_p);
>    if (VAR_OR_FUNCTION_DECL_P (t))
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> index e2d4853a284..ebaa95e5324 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> @@ -4,4 +4,4 @@
>  typedef bool (*Function)(int);
>  constexpr bool check(int x, Function p) { return p(x); }  // { dg-message "in .constexpr. expansion of" }
>  
> -static_assert(check(2, check), "");  // { dg-error "conversion|constant|in .constexpr. expansion of" }
> +static_assert(check(2, check), "");  // { dg-error "conversion|constant|lifetime|in .constexpr. expansion of" }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> new file mode 100644
> index 00000000000..43aa7c974c1
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> @@ -0,0 +1,13 @@
> +// PR c++/96630
> +// { dg-do compile { target c++14 } }
> +
> +struct S {
> +  int x = 0;
> +  constexpr const int& get() const { return x; }
> +};
> +
> +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" }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> new file mode 100644
> index 00000000000..22cd919fcda
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> @@ -0,0 +1,20 @@
> +// PR c++/98675
> +// { dg-do compile { target c++14 } }
> +
> +struct S {
> +  int x = 0;
> +  constexpr const int& get() const { return x; }
> +};
> +
> +constexpr int error() {
> +  const auto& local = S{}.get();  // { dg-message "note: declared here" }
> +  return local;
> +}
> +constexpr int x = error();  // { dg-error "accessing object outside its lifetime" }
> +
> +constexpr int ok() {
> +  // temporary should only be destroyed after end of full-expression
> +  auto local = S{}.get();
> +  return local;
> +}
> +constexpr int y = ok();
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> new file mode 100644
> index 00000000000..6329f8cf6c6
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> @@ -0,0 +1,13 @@
> +// PR c++/70331
> +// { dg-do compile { target c++14 } }
> +
> +constexpr int f(int i) {
> +  int *p = &i;
> +  if (i == 0) {
> +    int j = 123;  // { dg-message "note: declared here" }
> +    p = &j;
> +  }
> +  return *p;
> +}
> +
> +constexpr int i = f(0);  // { dg-error "accessing object outside its lifetime" }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> new file mode 100644
> index 00000000000..181a1201663
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> @@ -0,0 +1,11 @@
> +// { dg-do compile { target c++14 } }
> +
> +constexpr const double& test() {
> +  const double& local = 3.0;  // { dg-message "note: declared here" }
> +  return local;
> +}
> +
> +static_assert(test() == 3.0, "");  // { dg-error "constant|accessing object outside its lifetime" }
> +
> +// no deference, shouldn't error
> +static_assert((test(), true), "");
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> new file mode 100644
> index 00000000000..a4bc71d890a
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> @@ -0,0 +1,11 @@
> +// { dg-do compile { target c++14 } }
> +// { dg-options "-Wno-return-local-addr" }
> +
> +constexpr const int& id(int x) { return x; }
> +
> +constexpr bool test() {
> +  const int& y = id(3);
> +  return y == 3;
> +}
> +
> +constexpr bool x = test();  // { dg-error "" }
> -- 
> 2.34.1
> 
> 


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 2/3] c++: Improve constexpr error for dangling local variables
  2023-03-29  2:32 ` [PATCH v2 2/3] c++: Improve constexpr error for dangling local variables Nathaniel Shead
@ 2023-06-23 16:57   ` Patrick Palka
  0 siblings, 0 replies; 13+ messages in thread
From: Patrick Palka @ 2023-06-23 16:57 UTC (permalink / raw)
  To: Nathaniel Shead; +Cc: gcc-patches

On Wed, 29 Mar 2023, Nathaniel Shead via Gcc-patches wrote:

> Currently, when typeck discovers that a return statement will refer to a
> local variable it rewrites to return a null pointer. This causes the
> error messages for using the return value in a constant expression to be
> unhelpful, especially for reference return values.
> 
> This patch removes this "optimisation". Relying on this raises a warning
> by default and causes UB anyway, so there should be no issue in doing
> so. We also suppress additional warnings from later passes that detect
> this as a dangling pointer, since we've already indicated this anyway.

LGTM.  It seems the original motivation for returning a null pointer
here was to avoid issuing duplicate warnings
(https://gcc.gnu.org/legacy-ml/gcc-patches/2014-04/msg00269.html)
which your patch addresses.

> 
> gcc/cp/ChangeLog:
> 
> 	* semantics.cc (finish_return_stmt): Suppress dangling pointer
>         reporting on return statement if already reported.
> 	* typeck.cc (check_return_expr): Don't set return expression to
>         zero for dangling addresses.
> 
> gcc/testsuite/ChangeLog:
> 
>         * g++.dg/cpp1y/constexpr-lifetime5.C: Test reported message is
>         correct.
> 	* g++.dg/warn/Wreturn-local-addr-6.C: Remove check for return
>         value optimisation.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>  gcc/cp/semantics.cc                              | 5 ++++-
>  gcc/cp/typeck.cc                                 | 5 +++--
>  gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C | 4 ++--
>  gcc/testsuite/g++.dg/warn/Wreturn-local-addr-6.C | 3 ---
>  4 files changed, 9 insertions(+), 8 deletions(-)
> 
> diff --git a/gcc/cp/semantics.cc b/gcc/cp/semantics.cc
> index 87c2e8a7111..14b4b7f4ce1 100644
> --- a/gcc/cp/semantics.cc
> +++ b/gcc/cp/semantics.cc
> @@ -1246,7 +1246,10 @@ finish_return_stmt (tree expr)
>  
>    r = build_stmt (input_location, RETURN_EXPR, expr);
>    if (no_warning)
> -    suppress_warning (r, OPT_Wreturn_type);
> +    {
> +      suppress_warning (r, OPT_Wreturn_type);
> +      suppress_warning (r, OPT_Wdangling_pointer_);
> +    }
>    r = maybe_cleanup_point_expr_void (r);
>    r = add_stmt (r);
>  
> diff --git a/gcc/cp/typeck.cc b/gcc/cp/typeck.cc
> index afb956087ce..a7d642e2029 100644
> --- a/gcc/cp/typeck.cc
> +++ b/gcc/cp/typeck.cc
> @@ -11235,8 +11235,9 @@ check_return_expr (tree retval, bool *no_warning)
>        else if (!processing_template_decl
>  	       && maybe_warn_about_returning_address_of_local (retval, loc)
>  	       && INDIRECT_TYPE_P (valtype))
> -	retval = build2 (COMPOUND_EXPR, TREE_TYPE (retval), retval,
> -			 build_zero_cst (TREE_TYPE (retval)));
> +	/* Suppress the Wdangling-pointer warning in the return statement
> +	   that would otherwise occur.  */
> +	*no_warning = true;
>      }
>  
>    if (processing_template_decl)
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> index a4bc71d890a..ad3ef579f63 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> @@ -1,11 +1,11 @@
>  // { dg-do compile { target c++14 } }
>  // { dg-options "-Wno-return-local-addr" }
>  
> -constexpr const int& id(int x) { return x; }
> +constexpr const int& id(int x) { return x; }  // { dg-message "note: declared here" }
>  
>  constexpr bool test() {
>    const int& y = id(3);
>    return y == 3;
>  }
>  
> -constexpr bool x = test();  // { dg-error "" }
> +constexpr bool x = test();  // { dg-error "accessing object outside its lifetime" }
> diff --git a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr-6.C b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr-6.C
> index fae8b7e766f..ec8e241d83e 100644
> --- a/gcc/testsuite/g++.dg/warn/Wreturn-local-addr-6.C
> +++ b/gcc/testsuite/g++.dg/warn/Wreturn-local-addr-6.C
> @@ -24,6 +24,3 @@ return_addr_local_as_intref (void)
>  
>    return (const intptr_t&)a;   // { dg-warning "\\\[-Wreturn-local-addr]" } */
>  }
> -
> -/* Verify that the return value has been replaced with zero:
> -  { dg-final { scan-tree-dump-times "return 0;" 2 "optimized" } } */
> -- 
> 2.34.1
> 
> 


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 3/3] c++: Improve location information in constexpr evaluation
  2023-03-29  2:32 ` [PATCH v2 3/3] c++: Improve location information in constexpr evaluation Nathaniel Shead
@ 2023-06-23 17:09   ` Patrick Palka
  2023-06-30 15:50     ` Nathaniel Shead
  0 siblings, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2023-06-23 17:09 UTC (permalink / raw)
  To: Nathaniel Shead; +Cc: gcc-patches

On Wed, 29 Mar 2023, Nathaniel Shead via Gcc-patches wrote:

> This patch caches the current expression's location information in the
> constexpr_global_ctx struct, which allows subexpressions that have lost
> location information to still provide accurate diagnostics. Also
> rewrites a number of 'error' calls as 'error_at' to provide more
> specific location information.
> 
> The primary effect of this change is that many errors within evaluation
> of a constexpr function will now point at the offending expression (with
> expansion tracing information) rather than just the outermost call.

This seems like a great improvement!

In other parts of the frontend, e.g. during substitution from
tsubst_expr or tsubst_copy_and_build, we do something similar by
setting/restoring input_location directly.  (We've since added the RAII
class iloc_sentinel for this.)  I wonder if that'd be preferable here?

> 
> gcc/cp/ChangeLog:
> 
> 	* constexpr.cc (constexpr_global_ctx): New field for cached
> 	tree location, defaulting to input_location.
> 	(cxx_eval_internal_function): Fall back to ctx->global->loc
> 	rather than input_location.
> 	(modifying_const_object_error): Likewise.
> 	(cxx_eval_dynamic_cast_fn): Likewise.
> 	(eval_and_check_array_index): Likewise.
> 	(cxx_eval_array_reference): Likewise.
> 	(cxx_eval_bit_field_ref): Likewise.
> 	(cxx_eval_component_reference): Likewise.
> 	(cxx_eval_indirect_ref): Likewise.
> 	(cxx_eval_store_expression): Likewise.
> 	(cxx_eval_increment_expression): Likewise.
> 	(cxx_eval_loop_expr): Likewise.
> 	(cxx_eval_binary_expression): Likewise.
> 	(cxx_eval_constant_expression): Cache location of trees for use
>         in errors, and prefer it instead of input_location.
> 
> gcc/testsuite/ChangeLog:
> 
> 	* g++.dg/cpp0x/constexpr-48089.C: Updated diagnostic locations.
> 	* g++.dg/cpp0x/constexpr-diag3.C: Likewise.
> 	* g++.dg/cpp0x/constexpr-ice20.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-89481.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-lifetime1.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-lifetime2.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-lifetime3.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-lifetime4.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-lifetime5.C: Likewise.
> 	* g++.dg/cpp1y/constexpr-union5.C: Likewise.
> 	* g++.dg/cpp1y/pr68180.C: Likewise.
> 	* g++.dg/cpp1z/constexpr-lambda6.C: Likewise.
> 	* g++.dg/cpp2a/bit-cast11.C: Likewise.
> 	* g++.dg/cpp2a/bit-cast12.C: Likewise.
> 	* g++.dg/cpp2a/bit-cast14.C: Likewise.
> 	* g++.dg/cpp2a/constexpr-98122.C: Likewise.
> 	* g++.dg/cpp2a/constexpr-dynamic17.C: Likewise.
> 	* g++.dg/cpp2a/constexpr-init1.C: Likewise.
> 	* g++.dg/cpp2a/constexpr-new12.C: Likewise.
> 	* g++.dg/cpp2a/constexpr-new3.C: Likewise.
> 	* g++.dg/ext/constexpr-vla2.C: Likewise.
> 	* g++.dg/ext/constexpr-vla3.C: Likewise.
> 	* g++.dg/ubsan/pr63956.C: Likewise.
> 
> libstdc++/ChangeLog:
> 
> 	* testsuite/25_algorithms/equal/constexpr_neg.cc: Updated
> 	diagnostics locations.
> 
> Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> ---
>  gcc/cp/constexpr.cc                           | 83 +++++++++++--------
>  gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C  | 10 +--
>  gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C  |  2 +-
>  gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |  4 +-
>  gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C  |  3 +-
>  .../g++.dg/cpp1y/constexpr-lifetime1.C        |  1 +
>  .../g++.dg/cpp1y/constexpr-lifetime2.C        |  4 +-
>  .../g++.dg/cpp1y/constexpr-lifetime3.C        |  4 +-
>  .../g++.dg/cpp1y/constexpr-lifetime4.C        |  2 +-
>  .../g++.dg/cpp1y/constexpr-lifetime5.C        |  4 +-
>  gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C |  4 +-
>  gcc/testsuite/g++.dg/cpp1y/pr68180.C          |  4 +-
>  .../g++.dg/cpp1z/constexpr-lambda6.C          |  4 +-
>  gcc/testsuite/g++.dg/cpp2a/bit-cast11.C       | 10 +--
>  gcc/testsuite/g++.dg/cpp2a/bit-cast12.C       | 10 +--
>  gcc/testsuite/g++.dg/cpp2a/bit-cast14.C       | 14 ++--
>  gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C  |  4 +-
>  .../g++.dg/cpp2a/constexpr-dynamic17.C        |  5 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C  |  5 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C  |  6 +-
>  gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C   | 10 +--
>  gcc/testsuite/g++.dg/ext/constexpr-vla2.C     |  4 +-
>  gcc/testsuite/g++.dg/ext/constexpr-vla3.C     |  4 +-
>  gcc/testsuite/g++.dg/ubsan/pr63956.C          |  4 +-
>  .../25_algorithms/equal/constexpr_neg.cc      |  7 +-
>  25 files changed, 111 insertions(+), 101 deletions(-)
> 
> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> index bdbc12144a7..74045477a92 100644
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -1165,10 +1165,12 @@ public:
>    hash_set<tree> *modifiable;
>    /* Number of heap VAR_DECL deallocations.  */
>    unsigned heap_dealloc_count;
> +  /* Current location in case subtree has no location information.  */
> +  location_t loc;
>    /* Constructor.  */
>    constexpr_global_ctx ()
>      : constexpr_ops_count (0), cleanups (NULL), modifiable (nullptr),
> -      heap_dealloc_count (0) {}
> +      heap_dealloc_count (0), loc (input_location) {}
>  
>   tree get_value (tree t)
>    {
> @@ -2113,7 +2115,7 @@ cxx_eval_internal_function (const constexpr_ctx *ctx, tree t,
>  
>      default:
>        if (!ctx->quiet)
> -	error_at (cp_expr_loc_or_input_loc (t),
> +	error_at (cp_expr_loc_or_loc (t, ctx->global->loc),
>  		  "call to internal function %qE", t);
>        *non_constant_p = true;
>        return t;
> @@ -2128,7 +2130,7 @@ cxx_eval_internal_function (const constexpr_ctx *ctx, tree t,
>  
>    if (TREE_CODE (arg0) == INTEGER_CST && TREE_CODE (arg1) == INTEGER_CST)
>      {
> -      location_t loc = cp_expr_loc_or_input_loc (t);
> +      location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
>        tree type = TREE_TYPE (TREE_TYPE (t));
>        tree result = fold_binary_loc (loc, opcode, type,
>  				     fold_convert_loc (loc, type, arg0),
> @@ -2164,9 +2166,9 @@ clear_no_implicit_zero (tree ctor)
>     EXPR is the MODIFY_EXPR expression performing the modification.  */
>  
>  static void
> -modifying_const_object_error (tree expr, tree obj)
> +modifying_const_object_error (const constexpr_ctx* ctx, tree expr, tree obj)
>  {
> -  location_t loc = cp_expr_loc_or_input_loc (expr);
> +  location_t loc = cp_expr_loc_or_loc (expr, ctx->global->loc);
>    auto_diagnostic_group d;
>    error_at (loc, "modifying a const object %qE is not allowed in "
>  	    "a constant expression", TREE_OPERAND (expr, 0));
> @@ -2358,7 +2360,7 @@ cxx_eval_dynamic_cast_fn (const constexpr_ctx *ctx, tree call,
>    tree obj = CALL_EXPR_ARG (call, 0);
>    tree type = CALL_EXPR_ARG (call, 2);
>    HOST_WIDE_INT hint = int_cst_value (CALL_EXPR_ARG (call, 3));
> -  location_t loc = cp_expr_loc_or_input_loc (call);
> +  location_t loc = cp_expr_loc_or_loc (call, ctx->global->loc);
>  
>    /* Get the target type of the dynamic_cast.  */
>    gcc_assert (TREE_CODE (type) == ADDR_EXPR);
> @@ -3656,7 +3658,7 @@ cxx_eval_binary_expression (const constexpr_ctx *ctx, tree t,
>        && integer_zerop (lhs) && !integer_zerop (rhs))
>      {
>        if (!ctx->quiet)
> -	error ("arithmetic involving a null pointer in %qE", lhs);
> +	error_at (loc, "arithmetic involving a null pointer in %qE", lhs);
>        *non_constant_p = true;
>        return t;
>      }
> @@ -4149,7 +4151,7 @@ eval_and_check_array_index (const constexpr_ctx *ctx,
>  			    tree t, bool allow_one_past,
>  			    bool *non_constant_p, bool *overflow_p)
>  {
> -  location_t loc = cp_expr_loc_or_input_loc (t);
> +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
>    tree ary = TREE_OPERAND (t, 0);
>    t = TREE_OPERAND (t, 1);
>    tree index = cxx_eval_constant_expression (ctx, t, vc_prvalue,
> @@ -4187,6 +4189,7 @@ cxx_eval_array_reference (const constexpr_ctx *ctx, tree t,
>  			  value_cat lval,
>  			  bool *non_constant_p, bool *overflow_p)
>  {
> +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
>    tree oldary = TREE_OPERAND (t, 0);
>    tree ary = cxx_eval_constant_expression (ctx, oldary,
>  					   lval,
> @@ -4274,7 +4277,7 @@ cxx_eval_array_reference (const constexpr_ctx *ctx, tree t,
>  	 building; if there's no initializer for this element yet,
>  	 that's an error.  */
>        if (!ctx->quiet)
> -	error ("accessing uninitialized array element");
> +	error_at (loc, "accessing uninitialized array element");
>        *non_constant_p = true;
>        return t;
>      }
> @@ -4323,13 +4326,14 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
>    tree whole = cxx_eval_constant_expression (ctx, orig_whole,
>  					     lval,
>  					     non_constant_p, overflow_p);
> +  location_t loc = cp_expr_loc_or_loc (whole, ctx->global->loc);
>    if (*non_constant_p)
>      return t;
>    if (INDIRECT_REF_P (whole)
>        && integer_zerop (TREE_OPERAND (whole, 0)))
>      {
>        if (!ctx->quiet)
> -	error ("dereferencing a null pointer in %qE", orig_whole);
> +	error_at (loc, "dereferencing a null pointer in %qE", orig_whole);
>        *non_constant_p = true;
>        return t;
>      }
> @@ -4348,7 +4352,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
>    if (TREE_CODE (whole) != CONSTRUCTOR)
>      {
>        if (!ctx->quiet)
> -	error ("%qE is not a constant expression", orig_whole);
> +	error_at (loc, "%qE is not a constant expression", orig_whole);
>        *non_constant_p = true;
>        return t;
>      }
> @@ -4356,7 +4360,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
>        && DECL_MUTABLE_P (part))
>      {
>        if (!ctx->quiet)
> -	error ("mutable %qD is not usable in a constant expression", part);
> +	error_at (loc, "mutable %qD is not usable in a constant expression", part);
>        *non_constant_p = true;
>        return t;
>      }
> @@ -4386,10 +4390,10 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
>  	{
>  	  constructor_elt *cep = CONSTRUCTOR_ELT (whole, 0);
>  	  if (cep->value == NULL_TREE)
> -	    error ("accessing uninitialized member %qD", part);
> +	    error_at (loc, "accessing uninitialized member %qD", part);
>  	  else
> -	    error ("accessing %qD member instead of initialized %qD member in "
> -		   "constant expression", part, cep->index);
> +	    error_at (loc, "accessing %qD member instead of initialized %qD member "
> +		      "in constant expression", part, cep->index);
>  	}
>        *non_constant_p = true;
>        return t;
> @@ -4408,7 +4412,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
>  	 building; if there's no initializer for this member yet, that's an
>  	 error.  */
>        if (!ctx->quiet)
> -	error ("accessing uninitialized member %qD", part);
> +	error_at (loc, "accessing uninitialized member %qD", part);
>        *non_constant_p = true;
>        return t;
>      }
> @@ -4436,6 +4440,7 @@ cxx_eval_bit_field_ref (const constexpr_ctx *ctx, tree t,
>    tree whole = cxx_eval_constant_expression (ctx, orig_whole,
>  					     lval,
>  					     non_constant_p, overflow_p);
> +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
>    tree start, field, value;
>    unsigned HOST_WIDE_INT i;
>  
> @@ -4448,7 +4453,7 @@ cxx_eval_bit_field_ref (const constexpr_ctx *ctx, tree t,
>        && TREE_CODE (whole) != CONSTRUCTOR)
>      {
>        if (!ctx->quiet)
> -	error ("%qE is not a constant expression", orig_whole);
> +	error_at (loc, "%qE is not a constant expression", orig_whole);
>        *non_constant_p = true;
>      }
>    if (*non_constant_p)
> @@ -4460,7 +4465,7 @@ cxx_eval_bit_field_ref (const constexpr_ctx *ctx, tree t,
>  				 TREE_OPERAND (t, 1), TREE_OPERAND (t, 2)))
>  	return r;
>        if (!ctx->quiet)
> -	error ("%qE is not a constant expression", orig_whole);
> +	error_at (loc, "%qE is not a constant expression", orig_whole);
>        *non_constant_p = true;
>        return t;
>      }
> @@ -5609,6 +5614,7 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
>  		       value_cat lval,
>  		       bool *non_constant_p, bool *overflow_p)
>  {
> +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
>    tree orig_op0 = TREE_OPERAND (t, 0);
>    bool empty_base = false;
>  
> @@ -5639,7 +5645,7 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
>        if (!lval && integer_zerop (op0))
>  	{
>  	  if (!ctx->quiet)
> -	    error ("dereferencing a null pointer");
> +	    error_at (loc, "dereferencing a null pointer");
>  	  *non_constant_p = true;
>  	  return t;
>  	}
> @@ -5658,8 +5664,7 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
>  			  (TREE_TYPE (TREE_TYPE (sub)), TREE_TYPE (t)));
>  	      /* DR 1188 says we don't have to deal with this.  */
>  	      if (!ctx->quiet)
> -		error_at (cp_expr_loc_or_input_loc (t),
> -			  "accessing value of %qE through a %qT glvalue in a "
> +		error_at (loc, "accessing value of %qE through a %qT glvalue in a "
>  			  "constant expression", build_fold_indirect_ref (sub),
>  			  TREE_TYPE (t));
>  	      *non_constant_p = true;
> @@ -5906,6 +5911,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
>  			   value_cat lval,
>  			   bool *non_constant_p, bool *overflow_p)
>  {
> +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
>    constexpr_ctx new_ctx = *ctx;
>  
>    tree init = TREE_OPERAND (t, 1);
> @@ -6030,7 +6036,7 @@ 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);
> +	error_at (loc, "modification of %qE is not a constant expression", object);
>        *non_constant_p = true;
>        return t;
>      }
> @@ -6128,7 +6134,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
>  	  if (cxx_dialect < cxx20)
>  	    {
>  	      if (!ctx->quiet)
> -		error_at (cp_expr_loc_or_input_loc (t),
> +		error_at (cp_expr_loc_or_loc (t, ctx->global->loc),
>  			  "change of the active member of a union "
>  			  "from %qD to %qD",
>  			  CONSTRUCTOR_ELT (*valp, 0)->index,
> @@ -6141,7 +6147,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
>  	      /* Diagnose changing the active union member while the union
>  		 is in the process of being initialized.  */
>  	      if (!ctx->quiet)
> -		error_at (cp_expr_loc_or_input_loc (t),
> +		error_at (cp_expr_loc_or_loc (t, ctx->global->loc),
>  			  "change of the active member of a union "
>  			  "from %qD to %qD during initialization",
>  			  CONSTRUCTOR_ELT (*valp, 0)->index,
> @@ -6224,7 +6230,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
>        if (fail)
>  	{
>  	  if (!ctx->quiet)
> -	    modifying_const_object_error (t, const_object_being_modified);
> +	    modifying_const_object_error (ctx, t, const_object_being_modified);
>  	  *non_constant_p = true;
>  	  return t;
>  	}
> @@ -6381,6 +6387,8 @@ cxx_eval_increment_expression (const constexpr_ctx *ctx, tree t,
>    tree offset = TREE_OPERAND (t, 1);
>    gcc_assert (TREE_CONSTANT (offset));
>  
> +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> +
>    /* OFFSET is constant, but perhaps not constant enough.  We need to
>       e.g. bash FLOAT_EXPRs to REAL_CSTs.  */
>    offset = fold_simple (offset);
> @@ -6428,8 +6436,7 @@ cxx_eval_increment_expression (const constexpr_ctx *ctx, tree t,
>      VERIFY_CONSTANT (mod);
>  
>    /* Storing the modified value.  */
> -  tree store = build2_loc (cp_expr_loc_or_loc (t, input_location),
> -			   MODIFY_EXPR, type, op, mod);
> +  tree store = build2_loc (loc, MODIFY_EXPR, type, op, mod);
>    mod = cxx_eval_constant_expression (ctx, store, lval,
>  				      non_constant_p, overflow_p);
>    ggc_free (store);
> @@ -6602,6 +6609,7 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
>  		    bool *non_constant_p, bool *overflow_p,
>  		    tree *jump_target)
>  {
> +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
>    constexpr_ctx new_ctx = *ctx;
>    tree local_target;
>    if (!jump_target)
> @@ -6691,7 +6699,7 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
>        if (++count >= constexpr_loop_limit)
>  	{
>  	  if (!ctx->quiet)
> -	    error_at (cp_expr_loc_or_input_loc (t),
> +	    error_at (loc,
>  		      "%<constexpr%> loop iteration count exceeds limit of %d "
>  		      "(use %<-fconstexpr-loop-limit=%> to increase the limit)",
>  		      constexpr_loop_limit);
> @@ -6950,7 +6958,10 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>        return t;
>      }
>  
> -  location_t loc = cp_expr_loc_or_input_loc (t);
> +  /* Track current location, propagating down from parent calls
> +     in case this expression has no location information.  */
> +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> +  ctx->global->loc = loc;
>  
>    STRIP_ANY_LOCATION_WRAPPER (t);
>  
> @@ -6973,8 +6984,8 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>  	  && !integer_zerop (t))
>  	{
>  	  if (!ctx->quiet)
> -	    error ("value %qE of type %qT is not a constant expression",
> -		   t, TREE_TYPE (t));
> +	    error_at (loc, "value %qE of type %qT is not a constant expression",
> +		      t, TREE_TYPE (t));
>  	  *non_constant_p = true;
>  	}
>  
> @@ -7222,8 +7233,8 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>  	    if (!ctx->quiet)
>  	      {
>  		auto_diagnostic_group d;
> -		error ("temporary of non-literal type %qT in a "
> -		       "constant expression", type);
> +		error_at (loc, "temporary of non-literal type %qT in a "
> +			  "constant expression", type);
>  		explain_non_literal_class (type);
>  	      }
>  	    *non_constant_p = true;
> @@ -8025,8 +8036,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>  	if (function_concept_p (tmpl))
>  	  {
>  	    if (!ctx->quiet)
> -	      error_at (cp_expr_loc_or_input_loc (t),
> -			"function concept must be called");
> +	      error_at (loc, "function concept must be called");
>  	    r = error_mark_node;
>  	    break;
>  	  }
> @@ -8121,6 +8131,9 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
>        break;
>      }
>  
> +  /* Reset current location in case it was modified in child calls.  */
> +  ctx->global->loc = loc;
> +
>    if (r == error_mark_node)
>      *non_constant_p = true;
>  
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C
> index 4574eb83ff7..11630f26ffe 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C
> @@ -10,11 +10,11 @@
>  // R() is well-formed because i is initialized before j.
>  
>  struct s {
> -  constexpr s() : v(v) { }
> +  constexpr s() : v(v) { } // { dg-error "accessing uninitialized member" }
>    int v;
>  };
>  
> -constexpr s bang;		// { dg-error "|" }
> +constexpr s bang;  // { dg-message "in .constexpr. expansion" }
>  
>  struct R {
>    int i,j;
> @@ -26,14 +26,14 @@ constexpr R r;			// { dg-bogus "" }
>  // Ill-formed (no diagnostic required)
>  struct T {
>    int i;
> -  constexpr int f() { return i; }
> +  constexpr int f() { return i; }  // { dg-error "accessing uninitialized member" }
>    constexpr T(): i(0) { }
> -  constexpr T(const T& t) : i(f()) { } // { dg-message "" }
> +  constexpr T(const T& t) : i(f()) { }  // { dg-message "in .constexpr. expansion" }
>  };
>  
>  constexpr T t1;
>  // Ill-formed (diagnostic required)
> -constexpr T t2(t1);		// { dg-message "" }
> +constexpr T t2(t1);		// { dg-message "in .constexpr. expansion" }
>  
>  // Well-formed
>  struct U {
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
> index 5eedf42ba36..50c676c56cd 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
> @@ -16,7 +16,7 @@ int main()
>  struct complex 			// { dg-message "no .constexpr. constructor" "" { target { ! implicit_constexpr } } }
>  {
>    complex(double r, double i) : re(r), im(i) { }
> -  constexpr double real() const { return re; } // { dg-error "not a literal type" "" { target c++11_only } }
> +  constexpr double real() const { return re; } // { dg-error "not a literal type|not usable in a constant expression" "" { target { ! implicit_constexpr } } }
>    double imag() const { return im; }
>  
>  private:
> diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> index ebaa95e5324..e4e3bf865cd 100644
> --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> @@ -2,6 +2,6 @@
>  // { dg-do compile { target c++11 } }
>  
>  typedef bool (*Function)(int);
> -constexpr bool check(int x, Function p) { return p(x); }  // { dg-message "in .constexpr. expansion of" }
> +constexpr bool check(int x, Function p) { return p(x); }  // { dg-error "lifetime" }
>  
> -static_assert(check(2, check), "");  // { dg-error "conversion|constant|lifetime|in .constexpr. expansion of" }
> +static_assert(check(2, check), "");  // { dg-error "conversion|constant|in .constexpr. expansion of" }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C
> index 8ac4ef0fd36..6f8f6a8038e 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C
> @@ -6,7 +6,7 @@ foo ()
>  {
>    union U { long long a; int b[2]; } u { 5LL };
>    u.b[1] = 4;		// { dg-error "change of the active member of a union from" "" { target c++17_down } }
> -  return u.b[0];
> +  return u.b[0];	// { dg-error "accessing uninitialized array element" "" { target c++2a } }
>  }
>  
>  constexpr int
> @@ -19,6 +19,5 @@ bar ()
>  
>  static_assert (foo () == 0, "");	// { dg-error "non-constant condition for static assertion" }
>  					// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1 }
> -					// { dg-error "accessing uninitialized array element" "" { target c++2a } .-2 }
>  static_assert (bar () == 4, "");	// { dg-error "non-constant condition for static assertion" "" { target c++17_down } }
>  					// { dg-message "in 'constexpr' expansion of" "" { target c++17_down } .-1 }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> index 43aa7c974c1..f79f1611d5f 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> @@ -11,3 +11,4 @@ constexpr const int& test() {
>    return local.get();
>  }
>  constexpr int x = test();  // { dg-error "accessing object outside its lifetime" }
> +
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> index 22cd919fcda..2f5ae8db6d5 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> @@ -8,9 +8,9 @@ struct S {
>  
>  constexpr int error() {
>    const auto& local = S{}.get();  // { dg-message "note: declared here" }
> -  return local;
> +  return local;  // { dg-error "accessing object outside its lifetime" }
>  }
> -constexpr int x = error();  // { dg-error "accessing object outside its lifetime" }
> +constexpr int x = error();  // { dg-message "in .constexpr. expansion" }
>  
>  constexpr int ok() {
>    // temporary should only be destroyed after end of full-expression
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> index 6329f8cf6c6..53785521d05 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> @@ -7,7 +7,7 @@ constexpr int f(int i) {
>      int j = 123;  // { dg-message "note: declared here" }
>      p = &j;
>    }
> -  return *p;
> +  return *p;  // { dg-error "accessing object outside its lifetime" }
>  }
>  
> -constexpr int i = f(0);  // { dg-error "accessing object outside its lifetime" }
> +constexpr int i = f(0);  // { dg-message "in .constexpr. expansion" }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> index 181a1201663..4302da1eddc 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> @@ -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 "non-constant condition|accessing object outside its lifetime" }
>  
>  // no deference, shouldn't error
>  static_assert((test(), true), "");
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> index ad3ef579f63..a12920c8fba 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> @@ -5,7 +5,7 @@ constexpr const int& id(int x) { return x; }  // { dg-message "note: declared he
>  
>  constexpr bool test() {
>    const int& y = id(3);
> -  return y == 3;
> +  return y == 3;  // { dg-error "accessing object outside its lifetime" }
>  }
>  
> -constexpr bool x = test();  // { dg-error "accessing object outside its lifetime" }
> +constexpr bool x = test();  // { dg-message "in .constexpr. expansion" }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C
> index 55fe9fa2f0b..3d76345d564 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C
> @@ -8,8 +8,8 @@ union U {
>  };
>  
>  constexpr int foo(U *up) {
> -  up->a++;
> +  up->a++; // { dg-error "accessing uninitialized member" }
>    return {42};
>  }
>  
> -extern constexpr U u = {}; // { dg-error "accessing uninitialized member" }
> +extern constexpr U u = {}; // { dg-message "in .constexpr. expansion" }
> diff --git a/gcc/testsuite/g++.dg/cpp1y/pr68180.C b/gcc/testsuite/g++.dg/cpp1y/pr68180.C
> index 9e6e5e984f9..8de1ef3936b 100644
> --- a/gcc/testsuite/g++.dg/cpp1y/pr68180.C
> +++ b/gcc/testsuite/g++.dg/cpp1y/pr68180.C
> @@ -6,11 +6,11 @@ typedef float __attribute__( ( vector_size( 16 ) ) ) float32x4_t;
>  constexpr float32x4_t fill(float x) {
>    float32x4_t v{0};
>    constexpr auto vs = sizeof(v)/sizeof(v[0]);
> -  for (auto i=0U; i<vs; ++i) v[i]=i;
> +  for (auto i=0U; i<vs; ++i) v[i]=i; // { dg-error "not a constant" }
>    return v+x;
>  }
>  
>  float32x4_t foo(float32x4_t x) {
> -  constexpr float32x4_t v = fill(1.f); // { dg-error "not a constant||in .constexpr. expansion of " }
> +  constexpr float32x4_t v = fill(1.f); // { dg-message "in .constexpr. expansion of " }
>    return x+v;
>  }
> diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C
> index 214d3821299..c46c2d4c7fe 100644
> --- a/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C
> +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C
> @@ -1,7 +1,7 @@
>  // Testcase from P0170R1
>  // { dg-do compile { target c++17 } }
>  
> -auto monoid = [](auto v) { return [=] { return v; }; };
> +auto monoid = [](auto v) { return [=] { return v; }; };  // { dg-error "not usable in a constant expression" }
>  auto add = [](auto m1) constexpr {
>    auto ret = m1();
>    return [=](auto m2) mutable {
> @@ -22,7 +22,7 @@ int main()
>    // member function call operator can not perform an lvalue-to-rvalue conversion
>    // on one of its subobjects (that represents its capture) in a constant
>    // expression.
> -  auto two = monoid(2);
> +  auto two = monoid(2);  // { dg-message "not declared .constexpr." }
>    if (!(two() == 2)) __builtin_abort(); // OK, not a constant expression.
>    static_assert(add(one)(one)() == two()); // { dg-error "|in .constexpr. expansion of " } two() is not a constant expression
>    static_assert(add(one)(one)() == monoid(2)()); // OK
> diff --git a/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C b/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C
> index a3eb31bc6c7..760c9ca40b4 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C
> @@ -28,7 +28,7 @@ f3 ()
>  {
>    T t = { 1, 2 };
>    S s = __builtin_bit_cast (S, t);
> -  return s.a[1] == 0;
> +  return s.a[1] == 0;		// { dg-error "accessing uninitialized array element" }
>  }
>  
>  constexpr bool
> @@ -52,12 +52,12 @@ f6 ()
>  {
>    W t = { 1, 2 };
>    V s = __builtin_bit_cast (V, t);
> -  return s.b.a[1] == 1;
> +  return s.b.a[1] == 1;		// { dg-error "accessing uninitialized array element" }
>  }
>  
>  constexpr bool a = f1 ();
>  constexpr bool b = f2 ();
> -constexpr bool c = f3 ();	// { dg-error "accessing uninitialized array element" }
> -constexpr bool d = f4 ();
> +constexpr bool c = f3 ();	// { dg-message "in .constexpr. expansion" }
> +constexpr bool d = f4 ();	// { dg-message "in .constexpr. expansion" }
>  constexpr bool e = f5 ();
> -constexpr bool f = f6 ();	// { dg-error "accessing uninitialized array element" }
> +constexpr bool f = f6 ();	// { dg-message "in .constexpr. expansion" }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C b/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C
> index 9c699dd55f0..e205bc6a8c1 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C
> @@ -33,7 +33,7 @@ f3 ()
>  {
>    T t = { 1, 2 };
>    S s = __builtin_bit_cast (S, t);
> -  return s.a[1] == 0;
> +  return s.a[1] == 0;		// { dg-error "accessing uninitialized array element" }
>  }
>  
>  constexpr bool
> @@ -57,12 +57,12 @@ f6 ()
>  {
>    W t = { 1, 2 };
>    V s = __builtin_bit_cast (V, t);
> -  return s.b.a[1] == 1;
> +  return s.b.a[1] == 1;		// { dg-error "accessing uninitialized array element" }
>  }
>  
>  constexpr bool a = f1 ();
>  constexpr bool b = f2 ();
> -constexpr bool c = f3 ();	// { dg-error "accessing uninitialized array element" }
> -constexpr bool d = f4 ();
> +constexpr bool c = f3 ();	// { dg-message "in .constexpr. expansion" }
> +constexpr bool d = f4 ();	// { dg-message "in .constexpr. expansion" }
>  constexpr bool e = f5 ();
> -constexpr bool f = f6 ();	// { dg-error "accessing uninitialized array element" }
> +constexpr bool f = f6 ();	// { dg-message "in .constexpr. expansion" }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C b/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C
> index 5e185919be4..e0cc9a39702 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C
> @@ -44,7 +44,7 @@ f5 ()
>  {
>    T1 t = { 0, 0, 0, 0, 0, 0, 0 };
>    S s = __builtin_bit_cast (S, t);
> -  unsigned char a = s.a;
> +  unsigned char a = s.a;		// { dg-error "accessing uninitialized member" }
>    return true;
>  }
>  
> @@ -53,7 +53,7 @@ f6 ()
>  {
>    T2 t = { 0, 0, 0, 0, 0, 0, 0 };
>    S s = __builtin_bit_cast (S, t);
> -  unsigned char b = s.b;
> +  unsigned char b = s.b;		// { dg-error "accessing uninitialized member" }
>    return true;
>  }
>  
> @@ -62,14 +62,14 @@ f7 ()
>  {
>    T3 t = { 0, 0, 0, 0, 0, 0, 0 };
>    S s = __builtin_bit_cast (S, t);
> -  unsigned char c = s.c;
> +  unsigned char c = s.c;		// { dg-error "accessing uninitialized member" }
>    return true;
>  }
>  
>  constexpr bool a = f1 ();
>  constexpr bool b = f2 ();
>  constexpr bool c = f3 ();
> -constexpr bool d = f4 ();
> -constexpr bool e = f5 ();	// { dg-error "accessing uninitialized member" }
> -constexpr bool f = f6 ();	// { dg-error "accessing uninitialized member" }
> -constexpr bool g = f7 ();	// { dg-error "accessing uninitialized member" }
> +constexpr bool d = f4 ();	// { dg-message "in .constexpr. expansion" }
> +constexpr bool e = f5 ();	// { dg-message "in .constexpr. expansion" }
> +constexpr bool f = f6 ();	// { dg-message "in .constexpr. expansion" }
> +constexpr bool g = f7 ();	// { dg-message "in .constexpr. expansion" }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C
> index 01bdfa5bd4d..b0c91d5ef97 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C
> @@ -9,7 +9,7 @@ bar ()
>  {
>    V f { .b = 42 };
>    constexpr auto m = &V::a;
> -  return (f.*m) == 42;
> +  return (f.*m) == 42;  // { dg-error "accessing 'V::a' member instead of initialized 'V::b' member in constant expression" }
>  }
>  
>  constexpr bool
> @@ -21,5 +21,5 @@ baz ()
>  }
>  
>  static_assert (bar (), "");	// { dg-error "non-constant condition for static assertion" }
> -				// { dg-error "accessing 'V::a' member instead of initialized 'V::b' member in constant expression" "" { target *-*-* } .-1 }
> +				// { dg-message "in .constexpr. expansion" "" { target *-*-* } .-1 }
>  static_assert (baz (), "");
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C
> index a26678e6ed7..28facf192df 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C
> @@ -25,8 +25,7 @@ struct D : B, A {
>  
>  constexpr B::B(V* v, A* a)
>  {
> -  dynamic_cast<B*>(a);
> +  dynamic_cast<B*>(a); // { dg-error "accessing uninitialized member" }
>  }
>  
> -constexpr D d; // { dg-error "accessing uninitialized member" }
> -// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1 }
> +constexpr D d; // { dg-message "in 'constexpr' expansion of" }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C
> index e56ecfed48a..b4e39b6f928 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C
> @@ -52,11 +52,10 @@ constexpr int
>  fn5 ()
>  {
>    struct S { int a = 9; int b; } s;
> -  return s.b;
> +  return s.b; // { dg-error "accessing uninitialized member" }
>  }
>  
> -constexpr int b = fn5 (); // { dg-error "accessing uninitialized member" }
> -// { dg-message "in .constexpr. expansion of" "" { target *-*-* } .-1 }
> +constexpr int b = fn5 (); // { dg-message "in .constexpr. expansion of" }
>  
>  constexpr int
>  fn6 ()
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C
> index 5a3d06a5fab..832782e1427 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C
> @@ -17,11 +17,11 @@ struct B : A {
>  constexpr int
>  foo ()
>  {
> -  A *a = new B ();
> +  A *a = new B ();  // { dg-message "allocated here" }
>    a->a = 4;
>    delete a;
> -  int r = a->foo ();
> +  int r = a->foo ();  // { dg-error "constant expression" }
>    return r;
>  }
>  
> -constexpr auto a = foo ();	// { dg-error "constant expression" }
> +constexpr auto a = foo ();  // { dg-message "in .constexpr. expansion" }
> diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
> index 70b841208f8..3ba440fec53 100644
> --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
> +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
> @@ -45,11 +45,10 @@ constexpr bool
>  f5 ()
>  {
>    int *p = new int;		// { dg-message "allocated here" }
> -  return *p == 1;
> +  return *p == 1;		// { dg-error "the content of uninitialized storage is not usable in a constant expression" }
>  }
>  
> -constexpr auto v5 = f5 ();	// { dg-error "the content of uninitialized storage is not usable in a constant expression" }
> -				// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1 }
> +constexpr auto v5 = f5 (); 	// { dg-message "in 'constexpr' expansion of" }
>  
>  constexpr bool
>  f6 ()
> @@ -57,11 +56,10 @@ f6 ()
>    int *p = new int (2);		// { dg-message "allocated here" }
>    int *q = p;
>    delete p;
> -  return *q == 2;
> +  return *q == 2;		// { dg-error "use of allocated storage after deallocation in a constant expression" }
>  }
>  
> -constexpr auto v6 = f6 ();	// { dg-error "use of allocated storage after deallocation in a constant expression" }
> -				// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1  }
> +constexpr auto v6 = f6 (); 	// { dg-message "in 'constexpr' expansion of" }
>  
>  constexpr int *
>  f7 ()
> diff --git a/gcc/testsuite/g++.dg/ext/constexpr-vla2.C b/gcc/testsuite/g++.dg/ext/constexpr-vla2.C
> index d4ea7c58c0d..e09a27af3de 100644
> --- a/gcc/testsuite/g++.dg/ext/constexpr-vla2.C
> +++ b/gcc/testsuite/g++.dg/ext/constexpr-vla2.C
> @@ -4,7 +4,7 @@
>  constexpr int
>  fn_bad (int n)
>  {
> -  __extension__ int a [n] = { 0 };
> +  __extension__ int a [n] = { 0 };  // { dg-error "array subscript" }
>    int z = a [0] + (n ? fn_bad (n - 1) : 0); // { dg-message "in .constexpr. expansion of " } 
>    return z;
>  }
> @@ -18,4 +18,4 @@ fn_ok (int n)
>  }
>  
>  constexpr int i1 = fn_ok (3);
> -constexpr int i2 = fn_bad (3); // { dg-error "array subscript|in .constexpr. expansion of " }
> +constexpr int i2 = fn_bad (3); // { dg-message "in .constexpr. expansion of " }
> diff --git a/gcc/testsuite/g++.dg/ext/constexpr-vla3.C b/gcc/testsuite/g++.dg/ext/constexpr-vla3.C
> index 538b576a825..6f9daa1897f 100644
> --- a/gcc/testsuite/g++.dg/ext/constexpr-vla3.C
> +++ b/gcc/testsuite/g++.dg/ext/constexpr-vla3.C
> @@ -4,11 +4,11 @@
>  constexpr int
>  foo (int n)
>  {
> -  __extension__ int a[n] = { 1, 2, 3, 4, 5, 6 };
> +  __extension__ int a[n] = { 1, 2, 3, 4, 5, 6 }; // { dg-error "array subscript" }
>    int z = 0;
>    for (int i = 0; i <= n; ++i)
>      z += a[i];
>    return z;
>  }
>  
> -constexpr int n = foo (3); // { dg-error "array subscript|in .constexpr. expansion of " }
> +constexpr int n = foo (3); // { dg-message "in .constexpr. expansion of " }
> diff --git a/gcc/testsuite/g++.dg/ubsan/pr63956.C b/gcc/testsuite/g++.dg/ubsan/pr63956.C
> index 3a1596e6e2e..0771732ef00 100644
> --- a/gcc/testsuite/g++.dg/ubsan/pr63956.C
> +++ b/gcc/testsuite/g++.dg/ubsan/pr63956.C
> @@ -100,13 +100,13 @@ constexpr int
>  fn7 (const int *a, int b)
>  {
>    if (b != 3)
> -    return fn6 (*a, b);
> +    return fn6 (*a, b); // { dg-error "null pointer" }
>    return 7;
>  }
>  
>  constexpr int n1 = 7;
>  constexpr int n2 = fn7 (&n1, 5);
> -constexpr int n3 = fn7 ((const int *) 0, 8);  // { dg-error "null pointer|in .constexpr. expansion of " }
> +constexpr int n3 = fn7 ((const int *) 0, 8);  // { dg-message "in .constexpr. expansion of " }
>  
>  constexpr int
>  fn8 (int i)
> diff --git a/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc b/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc
> index 34ca5c4805c..fd89ac0e166 100644
> --- a/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc
> +++ b/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc
> @@ -32,7 +32,7 @@ test01()
>    return outa;
>  }
>  
> -static_assert(test01()); // { dg-error "outside the bounds" }
> +static_assert(test01()); // { dg-error "non-constant condition" }
>  
>  constexpr bool
>  test02()
> @@ -44,7 +44,8 @@ test02()
>    return outa;
>  }
>  
> -static_assert(test02()); // { dg-error "outside the bounds" }
> +static_assert(test02()); // { dg-error "non-constant condition" }
>  
> -// { dg-prune-output "non-constant condition" }
> +// Errors occuring within <algorithm> internals:
> +// { dg-error "outside the bounds of array" "" { target *-*-* } 0 }
>  // { dg-prune-output "in 'constexpr'" }
> -- 
> 2.34.1
> 
> 


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331, PR96630, PR98675]
  2023-06-23 16:43   ` [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331, PR96630, PR98675] Patrick Palka
@ 2023-06-24 14:02     ` Nathaniel Shead
  2023-06-26 19:37       ` Patrick Palka
  0 siblings, 1 reply; 13+ messages in thread
From: Nathaniel Shead @ 2023-06-24 14:02 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On Fri, Jun 23, 2023 at 12:43:21PM -0400, Patrick Palka wrote:
> On Wed, 29 Mar 2023, Nathaniel Shead via Gcc-patches wrote:
> 
> > This adds rudimentary lifetime tracking in C++ constexpr contexts,
> > allowing the compiler to report errors with using values after their
> > backing has gone out of scope. We don't yet handle other ways of ending
> > lifetimes (e.g. explicit destructor calls).
> 
> Awesome!
> 
> > 
> > 	PR c++/96630
> > 	PR c++/98675
> > 	PR c++/70331
> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* constexpr.cc (constexpr_global_ctx::put_value): Mark value as
> > 	in lifetime.
> > 	(constexpr_global_ctx::remove_value): Mark value as expired.
> > 	(cxx_eval_call_expression): Remove comment that is no longer
> > 	applicable.
> > 	(non_const_var_error): Add check for expired values.
> > 	(cxx_eval_constant_expression): Add checks for expired values. Forget
> > 	local variables at end of bind expressions. Forget temporaries at end
> > 	of cleanup points.
> > 	* cp-tree.h (struct lang_decl_base): New flag to track expired values
> > 	in constant evaluation.
> > 	(DECL_EXPIRED_P): Access the new flag.
> > 	(SET_DECL_EXPIRED_P): Modify the new flag.
> > 	* module.cc (trees_out::lang_decl_bools): Write out the new flag.
> > 	(trees_in::lang_decl_bools): Read in the new flag.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/cpp0x/constexpr-ice20.C: Update error raised by test.
> > 	* g++.dg/cpp1y/constexpr-lifetime1.C: New test.
> > 	* g++.dg/cpp1y/constexpr-lifetime2.C: New test.
> > 	* g++.dg/cpp1y/constexpr-lifetime3.C: New test.
> > 	* g++.dg/cpp1y/constexpr-lifetime4.C: New test.
> > 	* g++.dg/cpp1y/constexpr-lifetime5.C: New test.
> > 
> > Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> > ---
> >  gcc/cp/constexpr.cc                           | 69 +++++++++++++++----
> >  gcc/cp/cp-tree.h                              | 10 ++-
> >  gcc/cp/module.cc                              |  2 +
> >  gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |  2 +-
> >  .../g++.dg/cpp1y/constexpr-lifetime1.C        | 13 ++++
> >  .../g++.dg/cpp1y/constexpr-lifetime2.C        | 20 ++++++
> >  .../g++.dg/cpp1y/constexpr-lifetime3.C        | 13 ++++
> >  .../g++.dg/cpp1y/constexpr-lifetime4.C        | 11 +++
> >  .../g++.dg/cpp1y/constexpr-lifetime5.C        | 11 +++
> >  9 files changed, 137 insertions(+), 14 deletions(-)
> >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > 
> > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > index 3de60cfd0f8..bdbc12144a7 100644
> > --- a/gcc/cp/constexpr.cc
> > +++ b/gcc/cp/constexpr.cc
> > @@ -1185,10 +1185,22 @@ public:
> >    void put_value (tree t, tree v)
> >    {
> >      bool already_in_map = values.put (t, v);
> > +    if (!already_in_map && DECL_P (t))
> > +      {
> > +	if (!DECL_LANG_SPECIFIC (t))
> > +	  retrofit_lang_decl (t);
> > +	if (DECL_LANG_SPECIFIC (t))
> > +	  SET_DECL_EXPIRED_P (t, false);
> > +      }
> 
> Since this new flag would only be used only during constexpr evaluation,
> could we instead use an on-the-side hash_set in constexpr_global_ctx for
> tracking expired-ness?  That way we won't have to allocate a
> DECL_LANG_SPECIFIC structure for decls that lack it, and won't have to
> worry about the flag in other parts of the compiler.

I've tried this but I haven't been able to get it to work well. The main
issue I'm running into is the caching of function calls in constant
evaluation. For example, consider the following:

    constexpr const double& test() {
      const double& local = 3.0;
      return local;
    }

    constexpr int foo(const double&) { return 5; }

    constexpr int a = foo(test());
    static_assert(test() == 3.0);

When constant-evaluating 'a', we evaluate 'test()'. It returns a value
that ends its lifetime immediately, so we mark this in 'ctx->global' as
expired. However, 'foo()' never actually evaluates this expired value,
so the initialisation of 'a' succeeds.

However, then when the static assertion attempts to constant evaluate a
second time, the result of 'test' has already been cached, and we just
get directly handed a value. This is a new constant evaluation, so
'ctx->global' has been reset, and because we just got the result of the
cached function we don't actually know whether this is expired or not
anymore, and so this compiles without any error in case it was valid.

I haven't yet been able to come up with a good way of avoiding this
issue without complicating the caching of call expressions overly much.
I suppose I could add an extra field to 'constexpr_call' to track if the
value has already been expired (which would solve this particular case),
but I'm worried that I'll overlook other cases that will sidestep this.

Do you have any other thoughts on the best approach here? Thanks.

> >      if (!already_in_map && modifiable)
> >        modifiable->add (t);
> >    }
> > -  void remove_value (tree t) { values.remove (t); }
> > +  void remove_value (tree t)
> > +  {
> > +    if (DECL_P (t) && DECL_LANG_SPECIFIC (t))
> > +      SET_DECL_EXPIRED_P (t, true);
> > +    values.remove (t);
> > +  }
> >  };
> >  
> >  /* Helper class for constexpr_global_ctx.  In some cases we want to avoid
> > @@ -3157,10 +3169,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
> >  	  for (tree save_expr : save_exprs)
> >  	    ctx->global->remove_value (save_expr);
> >  
> > -	  /* Remove the parms/result from the values map.  Is it worth
> > -	     bothering to do this when the map itself is only live for
> > -	     one constexpr evaluation?  If so, maybe also clear out
> > -	     other vars from call, maybe in BIND_EXPR handling?  */
> > +	  /* Remove the parms/result from the values map.  */
> >  	  ctx->global->remove_value (res);
> >  	  for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
> >  	    ctx->global->remove_value (parm);
> > @@ -5708,6 +5717,13 @@ non_const_var_error (location_t loc, tree r, bool fundef_p)
> >  	inform (DECL_SOURCE_LOCATION (r), "allocated here");
> >        return;
> >      }
> > +  if (DECL_EXPIRED_P (r))
> > +    {
> > +      if (constexpr_error (loc, fundef_p, "accessing object outside its "
> > +			   "lifetime"))
> > +	inform (DECL_SOURCE_LOCATION (r), "declared here");
> > +      return;
> > +    }
> >    if (!constexpr_error (loc, fundef_p, "the value of %qD is not usable in "
> >  			"a constant expression", r))
> >      return;
> > @@ -7048,6 +7064,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> >  	  r = build_constructor (TREE_TYPE (t), NULL);
> >  	  TREE_CONSTANT (r) = true;
> >  	}
> > +      else if (DECL_EXPIRED_P (t))
> > +	{
> > +	  if (!ctx->quiet)
> > +	    non_const_var_error (loc, r, /*fundef_p*/false);
> > +	  *non_constant_p = true;
> > +	  break;
> > +	}
> >        else if (ctx->strict)
> >  	r = decl_really_constant_value (t, /*unshare_p=*/false);
> >        else
> > @@ -7093,7 +7116,15 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> >        else
> >  	{
> >  	  if (!ctx->quiet)
> > -	    error ("%qE is not a constant expression", t);
> > +	    {
> > +	      if (DECL_EXPIRED_P (r))
> > +		{
> > +		  error_at (loc, "accessing object outside its lifetime");
> > +		  inform (DECL_SOURCE_LOCATION (r), "declared here");
> > +		}
> > +	      else
> > +		error_at (loc, "%qE is not a constant expression", t);
> > +	    }
> >  	  *non_constant_p = true;
> >  	}
> >        break;
> > @@ -7315,17 +7346,28 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> >  	auto_vec<tree, 2> cleanups;
> >  	vec<tree> *prev_cleanups = ctx->global->cleanups;
> >  	ctx->global->cleanups = &cleanups;
> > -	r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0),
> > +
> > +	auto_vec<tree, 10> save_exprs;
> > +	constexpr_ctx new_ctx = *ctx;
> > +	new_ctx.save_exprs = &save_exprs;
> > +
> > +	r = cxx_eval_constant_expression (&new_ctx, TREE_OPERAND (t, 0),
> >  					  lval,
> >  					  non_constant_p, overflow_p,
> >  					  jump_target);
> > +
> >  	ctx->global->cleanups = prev_cleanups;
> >  	unsigned int i;
> >  	tree cleanup;
> >  	/* Evaluate the cleanups.  */
> >  	FOR_EACH_VEC_ELT_REVERSE (cleanups, i, cleanup)
> > -	  cxx_eval_constant_expression (ctx, cleanup, vc_discard,
> > +	  cxx_eval_constant_expression (&new_ctx, cleanup, vc_discard,
> >  					non_constant_p, overflow_p);
> > +
> > +	/* Forget SAVE_EXPRs and TARGET_EXPRs created by this
> > +	   full-expression.  */
> > +	for (tree save_expr : save_exprs)
> > +	  ctx->global->remove_value (save_expr);
> >        }
> >        break;
> >  
> > @@ -7831,10 +7873,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> >  				      non_constant_p, overflow_p, jump_target);
> >  
> >      case BIND_EXPR:
> > -      return cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (t),
> > -					   lval,
> > -					   non_constant_p, overflow_p,
> > -					   jump_target);
> > +      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;
> >  
> >      case PREINCREMENT_EXPR:
> >      case POSTINCREMENT_EXPR:
> > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > index b74c18b03ad..3cc08da816f 100644
> > --- a/gcc/cp/cp-tree.h
> > +++ b/gcc/cp/cp-tree.h
> > @@ -2860,6 +2860,7 @@ struct GTY(()) lang_decl_base {
> >    unsigned concept_p : 1;                  /* applies to vars and functions */
> >    unsigned var_declared_inline_p : 1;	   /* var */
> >    unsigned dependent_init_p : 1;	   /* var */
> > +  unsigned expired_p : 1;		   /* var or parm */
> >  
> >    /* The following apply to VAR, FUNCTION, TYPE, CONCEPT, & NAMESPACE
> >       decls.  */
> > @@ -2871,7 +2872,7 @@ struct GTY(()) lang_decl_base {
> >    /* VAR_DECL or FUNCTION_DECL has keyed decls.     */
> >    unsigned module_keyed_decls_p : 1;
> >  
> > -  /* 12 spare bits.  */
> > +  /* 11 spare bits.  */
> >  };
> >  
> >  /* True for DECL codes which have template info and access.  */
> > @@ -4366,6 +4367,13 @@ get_vec_init_expr (tree t)
> >  #define SET_DECL_DEPENDENT_INIT_P(NODE, X) \
> >    (DECL_LANG_SPECIFIC (VAR_DECL_CHECK (NODE))->u.base.dependent_init_p = (X))
> >  
> > +/* Nonzero if NODE is a VAR_DECL, PARM_DECL, or FIELD_DECL that is within
> > +   its lifetime for constant evaluation purposes.  */
> > +#define DECL_EXPIRED_P(NODE) \
> > +  (DECL_LANG_SPECIFIC (NODE) && DECL_LANG_SPECIFIC (NODE)->u.base.expired_p)
> > +#define SET_DECL_EXPIRED_P(NODE, X) \
> > +  (DECL_LANG_SPECIFIC (NODE)->u.base.expired_p = (X))
> > +
> >  /* Nonzero if NODE is an artificial VAR_DECL for a C++17 structured binding
> >     declaration or one of VAR_DECLs for the user identifiers in it.  */
> >  #define DECL_DECOMPOSITION_P(NODE) \
> > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > index ac2fe66b080..7af43b5736d 100644
> > --- a/gcc/cp/module.cc
> > +++ b/gcc/cp/module.cc
> > @@ -5654,6 +5654,7 @@ trees_out::lang_decl_bools (tree t)
> >    WB (lang->u.base.concept_p);
> >    WB (lang->u.base.var_declared_inline_p);
> >    WB (lang->u.base.dependent_init_p);
> > +  WB (lang->u.base.expired_p);
> >    /* When building a header unit, everthing is marked as purview, (so
> >       we know which decls to write).  But when we import them we do not
> >       want to mark them as in module purview.  */
> > @@ -5728,6 +5729,7 @@ trees_in::lang_decl_bools (tree t)
> >    RB (lang->u.base.concept_p);
> >    RB (lang->u.base.var_declared_inline_p);
> >    RB (lang->u.base.dependent_init_p);
> > +  RB (lang->u.base.expired_p);
> >    RB (lang->u.base.module_purview_p);
> >    RB (lang->u.base.module_attach_p);
> >    if (VAR_OR_FUNCTION_DECL_P (t))
> > diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > index e2d4853a284..ebaa95e5324 100644
> > --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > @@ -4,4 +4,4 @@
> >  typedef bool (*Function)(int);
> >  constexpr bool check(int x, Function p) { return p(x); }  // { dg-message "in .constexpr. expansion of" }
> >  
> > -static_assert(check(2, check), "");  // { dg-error "conversion|constant|in .constexpr. expansion of" }
> > +static_assert(check(2, check), "");  // { dg-error "conversion|constant|lifetime|in .constexpr. expansion of" }
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > new file mode 100644
> > index 00000000000..43aa7c974c1
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > @@ -0,0 +1,13 @@
> > +// PR c++/96630
> > +// { dg-do compile { target c++14 } }
> > +
> > +struct S {
> > +  int x = 0;
> > +  constexpr const int& get() const { return x; }
> > +};
> > +
> > +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" }
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > new file mode 100644
> > index 00000000000..22cd919fcda
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > @@ -0,0 +1,20 @@
> > +// PR c++/98675
> > +// { dg-do compile { target c++14 } }
> > +
> > +struct S {
> > +  int x = 0;
> > +  constexpr const int& get() const { return x; }
> > +};
> > +
> > +constexpr int error() {
> > +  const auto& local = S{}.get();  // { dg-message "note: declared here" }
> > +  return local;
> > +}
> > +constexpr int x = error();  // { dg-error "accessing object outside its lifetime" }
> > +
> > +constexpr int ok() {
> > +  // temporary should only be destroyed after end of full-expression
> > +  auto local = S{}.get();
> > +  return local;
> > +}
> > +constexpr int y = ok();
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > new file mode 100644
> > index 00000000000..6329f8cf6c6
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > @@ -0,0 +1,13 @@
> > +// PR c++/70331
> > +// { dg-do compile { target c++14 } }
> > +
> > +constexpr int f(int i) {
> > +  int *p = &i;
> > +  if (i == 0) {
> > +    int j = 123;  // { dg-message "note: declared here" }
> > +    p = &j;
> > +  }
> > +  return *p;
> > +}
> > +
> > +constexpr int i = f(0);  // { dg-error "accessing object outside its lifetime" }
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > new file mode 100644
> > index 00000000000..181a1201663
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > @@ -0,0 +1,11 @@
> > +// { dg-do compile { target c++14 } }
> > +
> > +constexpr const double& test() {
> > +  const double& local = 3.0;  // { dg-message "note: declared here" }
> > +  return local;
> > +}
> > +
> > +static_assert(test() == 3.0, "");  // { dg-error "constant|accessing object outside its lifetime" }
> > +
> > +// no deference, shouldn't error
> > +static_assert((test(), true), "");
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > new file mode 100644
> > index 00000000000..a4bc71d890a
> > --- /dev/null
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > @@ -0,0 +1,11 @@
> > +// { dg-do compile { target c++14 } }
> > +// { dg-options "-Wno-return-local-addr" }
> > +
> > +constexpr const int& id(int x) { return x; }
> > +
> > +constexpr bool test() {
> > +  const int& y = id(3);
> > +  return y == 3;
> > +}
> > +
> > +constexpr bool x = test();  // { dg-error "" }
> > -- 
> > 2.34.1
> > 
> > 
> 

^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331, PR96630, PR98675]
  2023-06-24 14:02     ` Nathaniel Shead
@ 2023-06-26 19:37       ` Patrick Palka
  2023-06-30 15:45         ` Nathaniel Shead
  0 siblings, 1 reply; 13+ messages in thread
From: Patrick Palka @ 2023-06-26 19:37 UTC (permalink / raw)
  To: Nathaniel Shead; +Cc: Patrick Palka, gcc-patches

On Sun, 25 Jun 2023, Nathaniel Shead wrote:

> On Fri, Jun 23, 2023 at 12:43:21PM -0400, Patrick Palka wrote:
> > On Wed, 29 Mar 2023, Nathaniel Shead via Gcc-patches wrote:
> > 
> > > This adds rudimentary lifetime tracking in C++ constexpr contexts,
> > > allowing the compiler to report errors with using values after their
> > > backing has gone out of scope. We don't yet handle other ways of ending
> > > lifetimes (e.g. explicit destructor calls).
> > 
> > Awesome!
> > 
> > > 
> > > 	PR c++/96630
> > > 	PR c++/98675
> > > 	PR c++/70331
> > > 
> > > gcc/cp/ChangeLog:
> > > 
> > > 	* constexpr.cc (constexpr_global_ctx::put_value): Mark value as
> > > 	in lifetime.
> > > 	(constexpr_global_ctx::remove_value): Mark value as expired.
> > > 	(cxx_eval_call_expression): Remove comment that is no longer
> > > 	applicable.
> > > 	(non_const_var_error): Add check for expired values.
> > > 	(cxx_eval_constant_expression): Add checks for expired values. Forget
> > > 	local variables at end of bind expressions. Forget temporaries at end
> > > 	of cleanup points.
> > > 	* cp-tree.h (struct lang_decl_base): New flag to track expired values
> > > 	in constant evaluation.
> > > 	(DECL_EXPIRED_P): Access the new flag.
> > > 	(SET_DECL_EXPIRED_P): Modify the new flag.
> > > 	* module.cc (trees_out::lang_decl_bools): Write out the new flag.
> > > 	(trees_in::lang_decl_bools): Read in the new flag.
> > > 
> > > gcc/testsuite/ChangeLog:
> > > 
> > > 	* g++.dg/cpp0x/constexpr-ice20.C: Update error raised by test.
> > > 	* g++.dg/cpp1y/constexpr-lifetime1.C: New test.
> > > 	* g++.dg/cpp1y/constexpr-lifetime2.C: New test.
> > > 	* g++.dg/cpp1y/constexpr-lifetime3.C: New test.
> > > 	* g++.dg/cpp1y/constexpr-lifetime4.C: New test.
> > > 	* g++.dg/cpp1y/constexpr-lifetime5.C: New test.
> > > 
> > > Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> > > ---
> > >  gcc/cp/constexpr.cc                           | 69 +++++++++++++++----
> > >  gcc/cp/cp-tree.h                              | 10 ++-
> > >  gcc/cp/module.cc                              |  2 +
> > >  gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |  2 +-
> > >  .../g++.dg/cpp1y/constexpr-lifetime1.C        | 13 ++++
> > >  .../g++.dg/cpp1y/constexpr-lifetime2.C        | 20 ++++++
> > >  .../g++.dg/cpp1y/constexpr-lifetime3.C        | 13 ++++
> > >  .../g++.dg/cpp1y/constexpr-lifetime4.C        | 11 +++
> > >  .../g++.dg/cpp1y/constexpr-lifetime5.C        | 11 +++
> > >  9 files changed, 137 insertions(+), 14 deletions(-)
> > >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > > 
> > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > > index 3de60cfd0f8..bdbc12144a7 100644
> > > --- a/gcc/cp/constexpr.cc
> > > +++ b/gcc/cp/constexpr.cc
> > > @@ -1185,10 +1185,22 @@ public:
> > >    void put_value (tree t, tree v)
> > >    {
> > >      bool already_in_map = values.put (t, v);
> > > +    if (!already_in_map && DECL_P (t))
> > > +      {
> > > +	if (!DECL_LANG_SPECIFIC (t))
> > > +	  retrofit_lang_decl (t);
> > > +	if (DECL_LANG_SPECIFIC (t))
> > > +	  SET_DECL_EXPIRED_P (t, false);
> > > +      }
> > 
> > Since this new flag would only be used only during constexpr evaluation,
> > could we instead use an on-the-side hash_set in constexpr_global_ctx for
> > tracking expired-ness?  That way we won't have to allocate a
> > DECL_LANG_SPECIFIC structure for decls that lack it, and won't have to
> > worry about the flag in other parts of the compiler.
> 
> I've tried this but I haven't been able to get it to work well. The main
> issue I'm running into is the caching of function calls in constant
> evaluation. For example, consider the following:
> 
>     constexpr const double& test() {
>       const double& local = 3.0;
>       return local;
>     }
> 
>     constexpr int foo(const double&) { return 5; }
> 
>     constexpr int a = foo(test());
>     static_assert(test() == 3.0);
> 
> When constant-evaluating 'a', we evaluate 'test()'. It returns a value
> that ends its lifetime immediately, so we mark this in 'ctx->global' as
> expired. However, 'foo()' never actually evaluates this expired value,
> so the initialisation of 'a' succeeds.
> 
> However, then when the static assertion attempts to constant evaluate a
> second time, the result of 'test' has already been cached, and we just
> get directly handed a value. This is a new constant evaluation, so
> 'ctx->global' has been reset, and because we just got the result of the
> cached function we don't actually know whether this is expired or not
> anymore, and so this compiles without any error in case it was valid.

Ouch, good catch..

> 
> I haven't yet been able to come up with a good way of avoiding this
> issue without complicating the caching of call expressions overly much.
> I suppose I could add an extra field to 'constexpr_call' to track if the
> value has already been expired (which would solve this particular case),
> but I'm worried that I'll overlook other cases that will sidestep this.
> 
> Do you have any other thoughts on the best approach here? Thanks.

This situation seems similar to that of a constexpr call returning a delete'd
pointer, which we handle by preventing caching of the call:

From constexpr.cc (cxx_eval_call_expression):
3207         /* Also don't cache a call that returns a deallocated pointer.  */
3208         if (cacheable && (cp_walk_tree_without_duplicates
3209                           (&result, find_heap_var_refs, NULL)))
3210           cacheable = false;

Maybe we could also disable caching in this situation as well, i.e. whenever a
constexpr call returns a reference to an expired variable?

> 
> > >      if (!already_in_map && modifiable)
> > >        modifiable->add (t);
> > >    }
> > > -  void remove_value (tree t) { values.remove (t); }
> > > +  void remove_value (tree t)
> > > +  {
> > > +    if (DECL_P (t) && DECL_LANG_SPECIFIC (t))
> > > +      SET_DECL_EXPIRED_P (t, true);
> > > +    values.remove (t);
> > > +  }
> > >  };
> > >  
> > >  /* Helper class for constexpr_global_ctx.  In some cases we want to avoid
> > > @@ -3157,10 +3169,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
> > >  	  for (tree save_expr : save_exprs)
> > >  	    ctx->global->remove_value (save_expr);
> > >  
> > > -	  /* Remove the parms/result from the values map.  Is it worth
> > > -	     bothering to do this when the map itself is only live for
> > > -	     one constexpr evaluation?  If so, maybe also clear out
> > > -	     other vars from call, maybe in BIND_EXPR handling?  */
> > > +	  /* Remove the parms/result from the values map.  */
> > >  	  ctx->global->remove_value (res);
> > >  	  for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
> > >  	    ctx->global->remove_value (parm);
> > > @@ -5708,6 +5717,13 @@ non_const_var_error (location_t loc, tree r, bool fundef_p)
> > >  	inform (DECL_SOURCE_LOCATION (r), "allocated here");
> > >        return;
> > >      }
> > > +  if (DECL_EXPIRED_P (r))
> > > +    {
> > > +      if (constexpr_error (loc, fundef_p, "accessing object outside its "
> > > +			   "lifetime"))
> > > +	inform (DECL_SOURCE_LOCATION (r), "declared here");
> > > +      return;
> > > +    }
> > >    if (!constexpr_error (loc, fundef_p, "the value of %qD is not usable in "
> > >  			"a constant expression", r))
> > >      return;
> > > @@ -7048,6 +7064,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> > >  	  r = build_constructor (TREE_TYPE (t), NULL);
> > >  	  TREE_CONSTANT (r) = true;
> > >  	}
> > > +      else if (DECL_EXPIRED_P (t))
> > > +	{
> > > +	  if (!ctx->quiet)
> > > +	    non_const_var_error (loc, r, /*fundef_p*/false);
> > > +	  *non_constant_p = true;
> > > +	  break;
> > > +	}
> > >        else if (ctx->strict)
> > >  	r = decl_really_constant_value (t, /*unshare_p=*/false);
> > >        else
> > > @@ -7093,7 +7116,15 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> > >        else
> > >  	{
> > >  	  if (!ctx->quiet)
> > > -	    error ("%qE is not a constant expression", t);
> > > +	    {
> > > +	      if (DECL_EXPIRED_P (r))
> > > +		{
> > > +		  error_at (loc, "accessing object outside its lifetime");
> > > +		  inform (DECL_SOURCE_LOCATION (r), "declared here");
> > > +		}
> > > +	      else
> > > +		error_at (loc, "%qE is not a constant expression", t);
> > > +	    }
> > >  	  *non_constant_p = true;
> > >  	}
> > >        break;
> > > @@ -7315,17 +7346,28 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> > >  	auto_vec<tree, 2> cleanups;
> > >  	vec<tree> *prev_cleanups = ctx->global->cleanups;
> > >  	ctx->global->cleanups = &cleanups;
> > > -	r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0),
> > > +
> > > +	auto_vec<tree, 10> save_exprs;
> > > +	constexpr_ctx new_ctx = *ctx;
> > > +	new_ctx.save_exprs = &save_exprs;
> > > +
> > > +	r = cxx_eval_constant_expression (&new_ctx, TREE_OPERAND (t, 0),
> > >  					  lval,
> > >  					  non_constant_p, overflow_p,
> > >  					  jump_target);
> > > +
> > >  	ctx->global->cleanups = prev_cleanups;
> > >  	unsigned int i;
> > >  	tree cleanup;
> > >  	/* Evaluate the cleanups.  */
> > >  	FOR_EACH_VEC_ELT_REVERSE (cleanups, i, cleanup)
> > > -	  cxx_eval_constant_expression (ctx, cleanup, vc_discard,
> > > +	  cxx_eval_constant_expression (&new_ctx, cleanup, vc_discard,
> > >  					non_constant_p, overflow_p);
> > > +
> > > +	/* Forget SAVE_EXPRs and TARGET_EXPRs created by this
> > > +	   full-expression.  */
> > > +	for (tree save_expr : save_exprs)
> > > +	  ctx->global->remove_value (save_expr);
> > >        }
> > >        break;
> > >  
> > > @@ -7831,10 +7873,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> > >  				      non_constant_p, overflow_p, jump_target);
> > >  
> > >      case BIND_EXPR:
> > > -      return cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (t),
> > > -					   lval,
> > > -					   non_constant_p, overflow_p,
> > > -					   jump_target);
> > > +      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;
> > >  
> > >      case PREINCREMENT_EXPR:
> > >      case POSTINCREMENT_EXPR:
> > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > > index b74c18b03ad..3cc08da816f 100644
> > > --- a/gcc/cp/cp-tree.h
> > > +++ b/gcc/cp/cp-tree.h
> > > @@ -2860,6 +2860,7 @@ struct GTY(()) lang_decl_base {
> > >    unsigned concept_p : 1;                  /* applies to vars and functions */
> > >    unsigned var_declared_inline_p : 1;	   /* var */
> > >    unsigned dependent_init_p : 1;	   /* var */
> > > +  unsigned expired_p : 1;		   /* var or parm */
> > >  
> > >    /* The following apply to VAR, FUNCTION, TYPE, CONCEPT, & NAMESPACE
> > >       decls.  */
> > > @@ -2871,7 +2872,7 @@ struct GTY(()) lang_decl_base {
> > >    /* VAR_DECL or FUNCTION_DECL has keyed decls.     */
> > >    unsigned module_keyed_decls_p : 1;
> > >  
> > > -  /* 12 spare bits.  */
> > > +  /* 11 spare bits.  */
> > >  };
> > >  
> > >  /* True for DECL codes which have template info and access.  */
> > > @@ -4366,6 +4367,13 @@ get_vec_init_expr (tree t)
> > >  #define SET_DECL_DEPENDENT_INIT_P(NODE, X) \
> > >    (DECL_LANG_SPECIFIC (VAR_DECL_CHECK (NODE))->u.base.dependent_init_p = (X))
> > >  
> > > +/* Nonzero if NODE is a VAR_DECL, PARM_DECL, or FIELD_DECL that is within
> > > +   its lifetime for constant evaluation purposes.  */
> > > +#define DECL_EXPIRED_P(NODE) \
> > > +  (DECL_LANG_SPECIFIC (NODE) && DECL_LANG_SPECIFIC (NODE)->u.base.expired_p)
> > > +#define SET_DECL_EXPIRED_P(NODE, X) \
> > > +  (DECL_LANG_SPECIFIC (NODE)->u.base.expired_p = (X))
> > > +
> > >  /* Nonzero if NODE is an artificial VAR_DECL for a C++17 structured binding
> > >     declaration or one of VAR_DECLs for the user identifiers in it.  */
> > >  #define DECL_DECOMPOSITION_P(NODE) \
> > > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > > index ac2fe66b080..7af43b5736d 100644
> > > --- a/gcc/cp/module.cc
> > > +++ b/gcc/cp/module.cc
> > > @@ -5654,6 +5654,7 @@ trees_out::lang_decl_bools (tree t)
> > >    WB (lang->u.base.concept_p);
> > >    WB (lang->u.base.var_declared_inline_p);
> > >    WB (lang->u.base.dependent_init_p);
> > > +  WB (lang->u.base.expired_p);
> > >    /* When building a header unit, everthing is marked as purview, (so
> > >       we know which decls to write).  But when we import them we do not
> > >       want to mark them as in module purview.  */
> > > @@ -5728,6 +5729,7 @@ trees_in::lang_decl_bools (tree t)
> > >    RB (lang->u.base.concept_p);
> > >    RB (lang->u.base.var_declared_inline_p);
> > >    RB (lang->u.base.dependent_init_p);
> > > +  RB (lang->u.base.expired_p);
> > >    RB (lang->u.base.module_purview_p);
> > >    RB (lang->u.base.module_attach_p);
> > >    if (VAR_OR_FUNCTION_DECL_P (t))
> > > diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > > index e2d4853a284..ebaa95e5324 100644
> > > --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > > +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > > @@ -4,4 +4,4 @@
> > >  typedef bool (*Function)(int);
> > >  constexpr bool check(int x, Function p) { return p(x); }  // { dg-message "in .constexpr. expansion of" }
> > >  
> > > -static_assert(check(2, check), "");  // { dg-error "conversion|constant|in .constexpr. expansion of" }
> > > +static_assert(check(2, check), "");  // { dg-error "conversion|constant|lifetime|in .constexpr. expansion of" }
> > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > > new file mode 100644
> > > index 00000000000..43aa7c974c1
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > > @@ -0,0 +1,13 @@
> > > +// PR c++/96630
> > > +// { dg-do compile { target c++14 } }
> > > +
> > > +struct S {
> > > +  int x = 0;
> > > +  constexpr const int& get() const { return x; }
> > > +};
> > > +
> > > +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" }
> > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > > new file mode 100644
> > > index 00000000000..22cd919fcda
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > > @@ -0,0 +1,20 @@
> > > +// PR c++/98675
> > > +// { dg-do compile { target c++14 } }
> > > +
> > > +struct S {
> > > +  int x = 0;
> > > +  constexpr const int& get() const { return x; }
> > > +};
> > > +
> > > +constexpr int error() {
> > > +  const auto& local = S{}.get();  // { dg-message "note: declared here" }
> > > +  return local;
> > > +}
> > > +constexpr int x = error();  // { dg-error "accessing object outside its lifetime" }
> > > +
> > > +constexpr int ok() {
> > > +  // temporary should only be destroyed after end of full-expression
> > > +  auto local = S{}.get();
> > > +  return local;
> > > +}
> > > +constexpr int y = ok();
> > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > > new file mode 100644
> > > index 00000000000..6329f8cf6c6
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > > @@ -0,0 +1,13 @@
> > > +// PR c++/70331
> > > +// { dg-do compile { target c++14 } }
> > > +
> > > +constexpr int f(int i) {
> > > +  int *p = &i;
> > > +  if (i == 0) {
> > > +    int j = 123;  // { dg-message "note: declared here" }
> > > +    p = &j;
> > > +  }
> > > +  return *p;
> > > +}
> > > +
> > > +constexpr int i = f(0);  // { dg-error "accessing object outside its lifetime" }
> > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > > new file mode 100644
> > > index 00000000000..181a1201663
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > > @@ -0,0 +1,11 @@
> > > +// { dg-do compile { target c++14 } }
> > > +
> > > +constexpr const double& test() {
> > > +  const double& local = 3.0;  // { dg-message "note: declared here" }
> > > +  return local;
> > > +}
> > > +
> > > +static_assert(test() == 3.0, "");  // { dg-error "constant|accessing object outside its lifetime" }
> > > +
> > > +// no deference, shouldn't error
> > > +static_assert((test(), true), "");
> > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > > new file mode 100644
> > > index 00000000000..a4bc71d890a
> > > --- /dev/null
> > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > > @@ -0,0 +1,11 @@
> > > +// { dg-do compile { target c++14 } }
> > > +// { dg-options "-Wno-return-local-addr" }
> > > +
> > > +constexpr const int& id(int x) { return x; }
> > > +
> > > +constexpr bool test() {
> > > +  const int& y = id(3);
> > > +  return y == 3;
> > > +}
> > > +
> > > +constexpr bool x = test();  // { dg-error "" }
> > > -- 
> > > 2.34.1
> > > 
> > > 
> > 
> 
> 


^ permalink raw reply	[flat|nested] 13+ messages in thread

* Re: [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331, PR96630, PR98675]
  2023-06-26 19:37       ` Patrick Palka
@ 2023-06-30 15:45         ` Nathaniel Shead
  0 siblings, 0 replies; 13+ messages in thread
From: Nathaniel Shead @ 2023-06-30 15:45 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On Mon, Jun 26, 2023 at 03:37:32PM -0400, Patrick Palka wrote:
> On Sun, 25 Jun 2023, Nathaniel Shead wrote:
> 
> > On Fri, Jun 23, 2023 at 12:43:21PM -0400, Patrick Palka wrote:
> > > On Wed, 29 Mar 2023, Nathaniel Shead via Gcc-patches wrote:
> > > 
> > > > This adds rudimentary lifetime tracking in C++ constexpr contexts,
> > > > allowing the compiler to report errors with using values after their
> > > > backing has gone out of scope. We don't yet handle other ways of ending
> > > > lifetimes (e.g. explicit destructor calls).
> > > 
> > > Awesome!
> > > 
> > > > 
> > > > 	PR c++/96630
> > > > 	PR c++/98675
> > > > 	PR c++/70331
> > > > 
> > > > gcc/cp/ChangeLog:
> > > > 
> > > > 	* constexpr.cc (constexpr_global_ctx::put_value): Mark value as
> > > > 	in lifetime.
> > > > 	(constexpr_global_ctx::remove_value): Mark value as expired.
> > > > 	(cxx_eval_call_expression): Remove comment that is no longer
> > > > 	applicable.
> > > > 	(non_const_var_error): Add check for expired values.
> > > > 	(cxx_eval_constant_expression): Add checks for expired values. Forget
> > > > 	local variables at end of bind expressions. Forget temporaries at end
> > > > 	of cleanup points.
> > > > 	* cp-tree.h (struct lang_decl_base): New flag to track expired values
> > > > 	in constant evaluation.
> > > > 	(DECL_EXPIRED_P): Access the new flag.
> > > > 	(SET_DECL_EXPIRED_P): Modify the new flag.
> > > > 	* module.cc (trees_out::lang_decl_bools): Write out the new flag.
> > > > 	(trees_in::lang_decl_bools): Read in the new flag.
> > > > 
> > > > gcc/testsuite/ChangeLog:
> > > > 
> > > > 	* g++.dg/cpp0x/constexpr-ice20.C: Update error raised by test.
> > > > 	* g++.dg/cpp1y/constexpr-lifetime1.C: New test.
> > > > 	* g++.dg/cpp1y/constexpr-lifetime2.C: New test.
> > > > 	* g++.dg/cpp1y/constexpr-lifetime3.C: New test.
> > > > 	* g++.dg/cpp1y/constexpr-lifetime4.C: New test.
> > > > 	* g++.dg/cpp1y/constexpr-lifetime5.C: New test.
> > > > 
> > > > Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> > > > ---
> > > >  gcc/cp/constexpr.cc                           | 69 +++++++++++++++----
> > > >  gcc/cp/cp-tree.h                              | 10 ++-
> > > >  gcc/cp/module.cc                              |  2 +
> > > >  gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |  2 +-
> > > >  .../g++.dg/cpp1y/constexpr-lifetime1.C        | 13 ++++
> > > >  .../g++.dg/cpp1y/constexpr-lifetime2.C        | 20 ++++++
> > > >  .../g++.dg/cpp1y/constexpr-lifetime3.C        | 13 ++++
> > > >  .../g++.dg/cpp1y/constexpr-lifetime4.C        | 11 +++
> > > >  .../g++.dg/cpp1y/constexpr-lifetime5.C        | 11 +++
> > > >  9 files changed, 137 insertions(+), 14 deletions(-)
> > > >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > > >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > > >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > > >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > > >  create mode 100644 gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > > > 
> > > > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > > > index 3de60cfd0f8..bdbc12144a7 100644
> > > > --- a/gcc/cp/constexpr.cc
> > > > +++ b/gcc/cp/constexpr.cc
> > > > @@ -1185,10 +1185,22 @@ public:
> > > >    void put_value (tree t, tree v)
> > > >    {
> > > >      bool already_in_map = values.put (t, v);
> > > > +    if (!already_in_map && DECL_P (t))
> > > > +      {
> > > > +	if (!DECL_LANG_SPECIFIC (t))
> > > > +	  retrofit_lang_decl (t);
> > > > +	if (DECL_LANG_SPECIFIC (t))
> > > > +	  SET_DECL_EXPIRED_P (t, false);
> > > > +      }
> > > 
> > > Since this new flag would only be used only during constexpr evaluation,
> > > could we instead use an on-the-side hash_set in constexpr_global_ctx for
> > > tracking expired-ness?  That way we won't have to allocate a
> > > DECL_LANG_SPECIFIC structure for decls that lack it, and won't have to
> > > worry about the flag in other parts of the compiler.
> > 
> > I've tried this but I haven't been able to get it to work well. The main
> > issue I'm running into is the caching of function calls in constant
> > evaluation. For example, consider the following:
> > 
> >     constexpr const double& test() {
> >       const double& local = 3.0;
> >       return local;
> >     }
> > 
> >     constexpr int foo(const double&) { return 5; }
> > 
> >     constexpr int a = foo(test());
> >     static_assert(test() == 3.0);
> > 
> > When constant-evaluating 'a', we evaluate 'test()'. It returns a value
> > that ends its lifetime immediately, so we mark this in 'ctx->global' as
> > expired. However, 'foo()' never actually evaluates this expired value,
> > so the initialisation of 'a' succeeds.
> > 
> > However, then when the static assertion attempts to constant evaluate a
> > second time, the result of 'test' has already been cached, and we just
> > get directly handed a value. This is a new constant evaluation, so
> > 'ctx->global' has been reset, and because we just got the result of the
> > cached function we don't actually know whether this is expired or not
> > anymore, and so this compiles without any error in case it was valid.
> 
> Ouch, good catch..
> 
> > 
> > I haven't yet been able to come up with a good way of avoiding this
> > issue without complicating the caching of call expressions overly much.
> > I suppose I could add an extra field to 'constexpr_call' to track if the
> > value has already been expired (which would solve this particular case),
> > but I'm worried that I'll overlook other cases that will sidestep this.
> > 
> > Do you have any other thoughts on the best approach here? Thanks.
> 
> This situation seems similar to that of a constexpr call returning a delete'd
> pointer, which we handle by preventing caching of the call:
> 
> From constexpr.cc (cxx_eval_call_expression):
> 3207         /* Also don't cache a call that returns a deallocated pointer.  */
> 3208         if (cacheable && (cp_walk_tree_without_duplicates
> 3209                           (&result, find_heap_var_refs, NULL)))
> 3210           cacheable = false;
> 
> Maybe we could also disable caching in this situation as well, i.e. whenever a
> constexpr call returns a reference to an expired variable?

Thanks for the pointer, I tried this and it works well. I was initially
a little worried about something similar happening with parameters but
it looks like that's already handled, since non-constant arguments also
already disable caching.

I'm still bootstrapping/regtesting but I'll send out a new version of
this patch series tomorrow when it's done.

> > 
> > > >      if (!already_in_map && modifiable)
> > > >        modifiable->add (t);
> > > >    }
> > > > -  void remove_value (tree t) { values.remove (t); }
> > > > +  void remove_value (tree t)
> > > > +  {
> > > > +    if (DECL_P (t) && DECL_LANG_SPECIFIC (t))
> > > > +      SET_DECL_EXPIRED_P (t, true);
> > > > +    values.remove (t);
> > > > +  }
> > > >  };
> > > >  
> > > >  /* Helper class for constexpr_global_ctx.  In some cases we want to avoid
> > > > @@ -3157,10 +3169,7 @@ cxx_eval_call_expression (const constexpr_ctx *ctx, tree t,
> > > >  	  for (tree save_expr : save_exprs)
> > > >  	    ctx->global->remove_value (save_expr);
> > > >  
> > > > -	  /* Remove the parms/result from the values map.  Is it worth
> > > > -	     bothering to do this when the map itself is only live for
> > > > -	     one constexpr evaluation?  If so, maybe also clear out
> > > > -	     other vars from call, maybe in BIND_EXPR handling?  */
> > > > +	  /* Remove the parms/result from the values map.  */
> > > >  	  ctx->global->remove_value (res);
> > > >  	  for (tree parm = parms; parm; parm = TREE_CHAIN (parm))
> > > >  	    ctx->global->remove_value (parm);
> > > > @@ -5708,6 +5717,13 @@ non_const_var_error (location_t loc, tree r, bool fundef_p)
> > > >  	inform (DECL_SOURCE_LOCATION (r), "allocated here");
> > > >        return;
> > > >      }
> > > > +  if (DECL_EXPIRED_P (r))
> > > > +    {
> > > > +      if (constexpr_error (loc, fundef_p, "accessing object outside its "
> > > > +			   "lifetime"))
> > > > +	inform (DECL_SOURCE_LOCATION (r), "declared here");
> > > > +      return;
> > > > +    }
> > > >    if (!constexpr_error (loc, fundef_p, "the value of %qD is not usable in "
> > > >  			"a constant expression", r))
> > > >      return;
> > > > @@ -7048,6 +7064,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> > > >  	  r = build_constructor (TREE_TYPE (t), NULL);
> > > >  	  TREE_CONSTANT (r) = true;
> > > >  	}
> > > > +      else if (DECL_EXPIRED_P (t))
> > > > +	{
> > > > +	  if (!ctx->quiet)
> > > > +	    non_const_var_error (loc, r, /*fundef_p*/false);
> > > > +	  *non_constant_p = true;
> > > > +	  break;
> > > > +	}
> > > >        else if (ctx->strict)
> > > >  	r = decl_really_constant_value (t, /*unshare_p=*/false);
> > > >        else
> > > > @@ -7093,7 +7116,15 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> > > >        else
> > > >  	{
> > > >  	  if (!ctx->quiet)
> > > > -	    error ("%qE is not a constant expression", t);
> > > > +	    {
> > > > +	      if (DECL_EXPIRED_P (r))
> > > > +		{
> > > > +		  error_at (loc, "accessing object outside its lifetime");
> > > > +		  inform (DECL_SOURCE_LOCATION (r), "declared here");
> > > > +		}
> > > > +	      else
> > > > +		error_at (loc, "%qE is not a constant expression", t);
> > > > +	    }
> > > >  	  *non_constant_p = true;
> > > >  	}
> > > >        break;
> > > > @@ -7315,17 +7346,28 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> > > >  	auto_vec<tree, 2> cleanups;
> > > >  	vec<tree> *prev_cleanups = ctx->global->cleanups;
> > > >  	ctx->global->cleanups = &cleanups;
> > > > -	r = cxx_eval_constant_expression (ctx, TREE_OPERAND (t, 0),
> > > > +
> > > > +	auto_vec<tree, 10> save_exprs;
> > > > +	constexpr_ctx new_ctx = *ctx;
> > > > +	new_ctx.save_exprs = &save_exprs;
> > > > +
> > > > +	r = cxx_eval_constant_expression (&new_ctx, TREE_OPERAND (t, 0),
> > > >  					  lval,
> > > >  					  non_constant_p, overflow_p,
> > > >  					  jump_target);
> > > > +
> > > >  	ctx->global->cleanups = prev_cleanups;
> > > >  	unsigned int i;
> > > >  	tree cleanup;
> > > >  	/* Evaluate the cleanups.  */
> > > >  	FOR_EACH_VEC_ELT_REVERSE (cleanups, i, cleanup)
> > > > -	  cxx_eval_constant_expression (ctx, cleanup, vc_discard,
> > > > +	  cxx_eval_constant_expression (&new_ctx, cleanup, vc_discard,
> > > >  					non_constant_p, overflow_p);
> > > > +
> > > > +	/* Forget SAVE_EXPRs and TARGET_EXPRs created by this
> > > > +	   full-expression.  */
> > > > +	for (tree save_expr : save_exprs)
> > > > +	  ctx->global->remove_value (save_expr);
> > > >        }
> > > >        break;
> > > >  
> > > > @@ -7831,10 +7873,13 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> > > >  				      non_constant_p, overflow_p, jump_target);
> > > >  
> > > >      case BIND_EXPR:
> > > > -      return cxx_eval_constant_expression (ctx, BIND_EXPR_BODY (t),
> > > > -					   lval,
> > > > -					   non_constant_p, overflow_p,
> > > > -					   jump_target);
> > > > +      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;
> > > >  
> > > >      case PREINCREMENT_EXPR:
> > > >      case POSTINCREMENT_EXPR:
> > > > diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> > > > index b74c18b03ad..3cc08da816f 100644
> > > > --- a/gcc/cp/cp-tree.h
> > > > +++ b/gcc/cp/cp-tree.h
> > > > @@ -2860,6 +2860,7 @@ struct GTY(()) lang_decl_base {
> > > >    unsigned concept_p : 1;                  /* applies to vars and functions */
> > > >    unsigned var_declared_inline_p : 1;	   /* var */
> > > >    unsigned dependent_init_p : 1;	   /* var */
> > > > +  unsigned expired_p : 1;		   /* var or parm */
> > > >  
> > > >    /* The following apply to VAR, FUNCTION, TYPE, CONCEPT, & NAMESPACE
> > > >       decls.  */
> > > > @@ -2871,7 +2872,7 @@ struct GTY(()) lang_decl_base {
> > > >    /* VAR_DECL or FUNCTION_DECL has keyed decls.     */
> > > >    unsigned module_keyed_decls_p : 1;
> > > >  
> > > > -  /* 12 spare bits.  */
> > > > +  /* 11 spare bits.  */
> > > >  };
> > > >  
> > > >  /* True for DECL codes which have template info and access.  */
> > > > @@ -4366,6 +4367,13 @@ get_vec_init_expr (tree t)
> > > >  #define SET_DECL_DEPENDENT_INIT_P(NODE, X) \
> > > >    (DECL_LANG_SPECIFIC (VAR_DECL_CHECK (NODE))->u.base.dependent_init_p = (X))
> > > >  
> > > > +/* Nonzero if NODE is a VAR_DECL, PARM_DECL, or FIELD_DECL that is within
> > > > +   its lifetime for constant evaluation purposes.  */
> > > > +#define DECL_EXPIRED_P(NODE) \
> > > > +  (DECL_LANG_SPECIFIC (NODE) && DECL_LANG_SPECIFIC (NODE)->u.base.expired_p)
> > > > +#define SET_DECL_EXPIRED_P(NODE, X) \
> > > > +  (DECL_LANG_SPECIFIC (NODE)->u.base.expired_p = (X))
> > > > +
> > > >  /* Nonzero if NODE is an artificial VAR_DECL for a C++17 structured binding
> > > >     declaration or one of VAR_DECLs for the user identifiers in it.  */
> > > >  #define DECL_DECOMPOSITION_P(NODE) \
> > > > diff --git a/gcc/cp/module.cc b/gcc/cp/module.cc
> > > > index ac2fe66b080..7af43b5736d 100644
> > > > --- a/gcc/cp/module.cc
> > > > +++ b/gcc/cp/module.cc
> > > > @@ -5654,6 +5654,7 @@ trees_out::lang_decl_bools (tree t)
> > > >    WB (lang->u.base.concept_p);
> > > >    WB (lang->u.base.var_declared_inline_p);
> > > >    WB (lang->u.base.dependent_init_p);
> > > > +  WB (lang->u.base.expired_p);
> > > >    /* When building a header unit, everthing is marked as purview, (so
> > > >       we know which decls to write).  But when we import them we do not
> > > >       want to mark them as in module purview.  */
> > > > @@ -5728,6 +5729,7 @@ trees_in::lang_decl_bools (tree t)
> > > >    RB (lang->u.base.concept_p);
> > > >    RB (lang->u.base.var_declared_inline_p);
> > > >    RB (lang->u.base.dependent_init_p);
> > > > +  RB (lang->u.base.expired_p);
> > > >    RB (lang->u.base.module_purview_p);
> > > >    RB (lang->u.base.module_attach_p);
> > > >    if (VAR_OR_FUNCTION_DECL_P (t))
> > > > diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > > > index e2d4853a284..ebaa95e5324 100644
> > > > --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > > > +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > > > @@ -4,4 +4,4 @@
> > > >  typedef bool (*Function)(int);
> > > >  constexpr bool check(int x, Function p) { return p(x); }  // { dg-message "in .constexpr. expansion of" }
> > > >  
> > > > -static_assert(check(2, check), "");  // { dg-error "conversion|constant|in .constexpr. expansion of" }
> > > > +static_assert(check(2, check), "");  // { dg-error "conversion|constant|lifetime|in .constexpr. expansion of" }
> > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > > > new file mode 100644
> > > > index 00000000000..43aa7c974c1
> > > > --- /dev/null
> > > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > > > @@ -0,0 +1,13 @@
> > > > +// PR c++/96630
> > > > +// { dg-do compile { target c++14 } }
> > > > +
> > > > +struct S {
> > > > +  int x = 0;
> > > > +  constexpr const int& get() const { return x; }
> > > > +};
> > > > +
> > > > +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" }
> > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > > > new file mode 100644
> > > > index 00000000000..22cd919fcda
> > > > --- /dev/null
> > > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > > > @@ -0,0 +1,20 @@
> > > > +// PR c++/98675
> > > > +// { dg-do compile { target c++14 } }
> > > > +
> > > > +struct S {
> > > > +  int x = 0;
> > > > +  constexpr const int& get() const { return x; }
> > > > +};
> > > > +
> > > > +constexpr int error() {
> > > > +  const auto& local = S{}.get();  // { dg-message "note: declared here" }
> > > > +  return local;
> > > > +}
> > > > +constexpr int x = error();  // { dg-error "accessing object outside its lifetime" }
> > > > +
> > > > +constexpr int ok() {
> > > > +  // temporary should only be destroyed after end of full-expression
> > > > +  auto local = S{}.get();
> > > > +  return local;
> > > > +}
> > > > +constexpr int y = ok();
> > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > > > new file mode 100644
> > > > index 00000000000..6329f8cf6c6
> > > > --- /dev/null
> > > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > > > @@ -0,0 +1,13 @@
> > > > +// PR c++/70331
> > > > +// { dg-do compile { target c++14 } }
> > > > +
> > > > +constexpr int f(int i) {
> > > > +  int *p = &i;
> > > > +  if (i == 0) {
> > > > +    int j = 123;  // { dg-message "note: declared here" }
> > > > +    p = &j;
> > > > +  }
> > > > +  return *p;
> > > > +}
> > > > +
> > > > +constexpr int i = f(0);  // { dg-error "accessing object outside its lifetime" }
> > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > > > new file mode 100644
> > > > index 00000000000..181a1201663
> > > > --- /dev/null
> > > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > > > @@ -0,0 +1,11 @@
> > > > +// { dg-do compile { target c++14 } }
> > > > +
> > > > +constexpr const double& test() {
> > > > +  const double& local = 3.0;  // { dg-message "note: declared here" }
> > > > +  return local;
> > > > +}
> > > > +
> > > > +static_assert(test() == 3.0, "");  // { dg-error "constant|accessing object outside its lifetime" }
> > > > +
> > > > +// no deference, shouldn't error
> > > > +static_assert((test(), true), "");
> > > > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > > > new file mode 100644
> > > > index 00000000000..a4bc71d890a
> > > > --- /dev/null
> > > > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > > > @@ -0,0 +1,11 @@
> > > > +// { dg-do compile { target c++14 } }
> > > > +// { dg-options "-Wno-return-local-addr" }
> > > > +
> > > > +constexpr const int& id(int x) { return x; }
> > > > +
> > > > +constexpr bool test() {
> > > > +  const int& y = id(3);
> > > > +  return y == 3;
> > > > +}
> > > > +
> > > > +constexpr bool x = test();  // { dg-error "" }
> > > > -- 
> > > > 2.34.1
> > > > 
> > > > 
> > > 
> > 
> > 
> 

^ permalink raw reply	[flat|nested] 13+ messages in thread

* [PATCH v2 3/3] c++: Improve location information in constexpr evaluation
  2023-06-23 17:09   ` Patrick Palka
@ 2023-06-30 15:50     ` Nathaniel Shead
  0 siblings, 0 replies; 13+ messages in thread
From: Nathaniel Shead @ 2023-06-30 15:50 UTC (permalink / raw)
  To: Patrick Palka; +Cc: gcc-patches

On Fri, Jun 23, 2023 at 01:09:14PM -0400, Patrick Palka wrote:
> On Wed, 29 Mar 2023, Nathaniel Shead via Gcc-patches wrote:
> 
> > This patch caches the current expression's location information in the
> > constexpr_global_ctx struct, which allows subexpressions that have lost
> > location information to still provide accurate diagnostics. Also
> > rewrites a number of 'error' calls as 'error_at' to provide more
> > specific location information.
> > 
> > The primary effect of this change is that many errors within evaluation
> > of a constexpr function will now point at the offending expression (with
> > expansion tracing information) rather than just the outermost call.
> 
> This seems like a great improvement!
> 
> In other parts of the frontend, e.g. during substitution from
> tsubst_expr or tsubst_copy_and_build, we do something similar by
> setting/restoring input_location directly.  (We've since added the RAII
> class iloc_sentinel for this.)  I wonder if that'd be preferable here?

I didn't consider that; I've given it a try and I think it's nicer.
Doing it this way also updated a number of 'error' calls that I hadn't
fixed up in this version; generally this meant nicer error messages, but
I had to override it for a couple of cases where I felt the errors it
raised were worse (by adding context that made no sense).

I'm still bootstrapping/regtesting but I'll send out an updated version
of this sometime tomorrow when it's done. Thanks!

> > 
> > gcc/cp/ChangeLog:
> > 
> > 	* constexpr.cc (constexpr_global_ctx): New field for cached
> > 	tree location, defaulting to input_location.
> > 	(cxx_eval_internal_function): Fall back to ctx->global->loc
> > 	rather than input_location.
> > 	(modifying_const_object_error): Likewise.
> > 	(cxx_eval_dynamic_cast_fn): Likewise.
> > 	(eval_and_check_array_index): Likewise.
> > 	(cxx_eval_array_reference): Likewise.
> > 	(cxx_eval_bit_field_ref): Likewise.
> > 	(cxx_eval_component_reference): Likewise.
> > 	(cxx_eval_indirect_ref): Likewise.
> > 	(cxx_eval_store_expression): Likewise.
> > 	(cxx_eval_increment_expression): Likewise.
> > 	(cxx_eval_loop_expr): Likewise.
> > 	(cxx_eval_binary_expression): Likewise.
> > 	(cxx_eval_constant_expression): Cache location of trees for use
> >         in errors, and prefer it instead of input_location.
> > 
> > gcc/testsuite/ChangeLog:
> > 
> > 	* g++.dg/cpp0x/constexpr-48089.C: Updated diagnostic locations.
> > 	* g++.dg/cpp0x/constexpr-diag3.C: Likewise.
> > 	* g++.dg/cpp0x/constexpr-ice20.C: Likewise.
> > 	* g++.dg/cpp1y/constexpr-89481.C: Likewise.
> > 	* g++.dg/cpp1y/constexpr-lifetime1.C: Likewise.
> > 	* g++.dg/cpp1y/constexpr-lifetime2.C: Likewise.
> > 	* g++.dg/cpp1y/constexpr-lifetime3.C: Likewise.
> > 	* g++.dg/cpp1y/constexpr-lifetime4.C: Likewise.
> > 	* g++.dg/cpp1y/constexpr-lifetime5.C: Likewise.
> > 	* g++.dg/cpp1y/constexpr-union5.C: Likewise.
> > 	* g++.dg/cpp1y/pr68180.C: Likewise.
> > 	* g++.dg/cpp1z/constexpr-lambda6.C: Likewise.
> > 	* g++.dg/cpp2a/bit-cast11.C: Likewise.
> > 	* g++.dg/cpp2a/bit-cast12.C: Likewise.
> > 	* g++.dg/cpp2a/bit-cast14.C: Likewise.
> > 	* g++.dg/cpp2a/constexpr-98122.C: Likewise.
> > 	* g++.dg/cpp2a/constexpr-dynamic17.C: Likewise.
> > 	* g++.dg/cpp2a/constexpr-init1.C: Likewise.
> > 	* g++.dg/cpp2a/constexpr-new12.C: Likewise.
> > 	* g++.dg/cpp2a/constexpr-new3.C: Likewise.
> > 	* g++.dg/ext/constexpr-vla2.C: Likewise.
> > 	* g++.dg/ext/constexpr-vla3.C: Likewise.
> > 	* g++.dg/ubsan/pr63956.C: Likewise.
> > 
> > libstdc++/ChangeLog:
> > 
> > 	* testsuite/25_algorithms/equal/constexpr_neg.cc: Updated
> > 	diagnostics locations.
> > 
> > Signed-off-by: Nathaniel Shead <nathanieloshead@gmail.com>
> > ---
> >  gcc/cp/constexpr.cc                           | 83 +++++++++++--------
> >  gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C  | 10 +--
> >  gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C  |  2 +-
> >  gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C  |  4 +-
> >  gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C  |  3 +-
> >  .../g++.dg/cpp1y/constexpr-lifetime1.C        |  1 +
> >  .../g++.dg/cpp1y/constexpr-lifetime2.C        |  4 +-
> >  .../g++.dg/cpp1y/constexpr-lifetime3.C        |  4 +-
> >  .../g++.dg/cpp1y/constexpr-lifetime4.C        |  2 +-
> >  .../g++.dg/cpp1y/constexpr-lifetime5.C        |  4 +-
> >  gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C |  4 +-
> >  gcc/testsuite/g++.dg/cpp1y/pr68180.C          |  4 +-
> >  .../g++.dg/cpp1z/constexpr-lambda6.C          |  4 +-
> >  gcc/testsuite/g++.dg/cpp2a/bit-cast11.C       | 10 +--
> >  gcc/testsuite/g++.dg/cpp2a/bit-cast12.C       | 10 +--
> >  gcc/testsuite/g++.dg/cpp2a/bit-cast14.C       | 14 ++--
> >  gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C  |  4 +-
> >  .../g++.dg/cpp2a/constexpr-dynamic17.C        |  5 +-
> >  gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C  |  5 +-
> >  gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C  |  6 +-
> >  gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C   | 10 +--
> >  gcc/testsuite/g++.dg/ext/constexpr-vla2.C     |  4 +-
> >  gcc/testsuite/g++.dg/ext/constexpr-vla3.C     |  4 +-
> >  gcc/testsuite/g++.dg/ubsan/pr63956.C          |  4 +-
> >  .../25_algorithms/equal/constexpr_neg.cc      |  7 +-
> >  25 files changed, 111 insertions(+), 101 deletions(-)
> > 
> > diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> > index bdbc12144a7..74045477a92 100644
> > --- a/gcc/cp/constexpr.cc
> > +++ b/gcc/cp/constexpr.cc
> > @@ -1165,10 +1165,12 @@ public:
> >    hash_set<tree> *modifiable;
> >    /* Number of heap VAR_DECL deallocations.  */
> >    unsigned heap_dealloc_count;
> > +  /* Current location in case subtree has no location information.  */
> > +  location_t loc;
> >    /* Constructor.  */
> >    constexpr_global_ctx ()
> >      : constexpr_ops_count (0), cleanups (NULL), modifiable (nullptr),
> > -      heap_dealloc_count (0) {}
> > +      heap_dealloc_count (0), loc (input_location) {}
> >  
> >   tree get_value (tree t)
> >    {
> > @@ -2113,7 +2115,7 @@ cxx_eval_internal_function (const constexpr_ctx *ctx, tree t,
> >  
> >      default:
> >        if (!ctx->quiet)
> > -	error_at (cp_expr_loc_or_input_loc (t),
> > +	error_at (cp_expr_loc_or_loc (t, ctx->global->loc),
> >  		  "call to internal function %qE", t);
> >        *non_constant_p = true;
> >        return t;
> > @@ -2128,7 +2130,7 @@ cxx_eval_internal_function (const constexpr_ctx *ctx, tree t,
> >  
> >    if (TREE_CODE (arg0) == INTEGER_CST && TREE_CODE (arg1) == INTEGER_CST)
> >      {
> > -      location_t loc = cp_expr_loc_or_input_loc (t);
> > +      location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> >        tree type = TREE_TYPE (TREE_TYPE (t));
> >        tree result = fold_binary_loc (loc, opcode, type,
> >  				     fold_convert_loc (loc, type, arg0),
> > @@ -2164,9 +2166,9 @@ clear_no_implicit_zero (tree ctor)
> >     EXPR is the MODIFY_EXPR expression performing the modification.  */
> >  
> >  static void
> > -modifying_const_object_error (tree expr, tree obj)
> > +modifying_const_object_error (const constexpr_ctx* ctx, tree expr, tree obj)
> >  {
> > -  location_t loc = cp_expr_loc_or_input_loc (expr);
> > +  location_t loc = cp_expr_loc_or_loc (expr, ctx->global->loc);
> >    auto_diagnostic_group d;
> >    error_at (loc, "modifying a const object %qE is not allowed in "
> >  	    "a constant expression", TREE_OPERAND (expr, 0));
> > @@ -2358,7 +2360,7 @@ cxx_eval_dynamic_cast_fn (const constexpr_ctx *ctx, tree call,
> >    tree obj = CALL_EXPR_ARG (call, 0);
> >    tree type = CALL_EXPR_ARG (call, 2);
> >    HOST_WIDE_INT hint = int_cst_value (CALL_EXPR_ARG (call, 3));
> > -  location_t loc = cp_expr_loc_or_input_loc (call);
> > +  location_t loc = cp_expr_loc_or_loc (call, ctx->global->loc);
> >  
> >    /* Get the target type of the dynamic_cast.  */
> >    gcc_assert (TREE_CODE (type) == ADDR_EXPR);
> > @@ -3656,7 +3658,7 @@ cxx_eval_binary_expression (const constexpr_ctx *ctx, tree t,
> >        && integer_zerop (lhs) && !integer_zerop (rhs))
> >      {
> >        if (!ctx->quiet)
> > -	error ("arithmetic involving a null pointer in %qE", lhs);
> > +	error_at (loc, "arithmetic involving a null pointer in %qE", lhs);
> >        *non_constant_p = true;
> >        return t;
> >      }
> > @@ -4149,7 +4151,7 @@ eval_and_check_array_index (const constexpr_ctx *ctx,
> >  			    tree t, bool allow_one_past,
> >  			    bool *non_constant_p, bool *overflow_p)
> >  {
> > -  location_t loc = cp_expr_loc_or_input_loc (t);
> > +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> >    tree ary = TREE_OPERAND (t, 0);
> >    t = TREE_OPERAND (t, 1);
> >    tree index = cxx_eval_constant_expression (ctx, t, vc_prvalue,
> > @@ -4187,6 +4189,7 @@ cxx_eval_array_reference (const constexpr_ctx *ctx, tree t,
> >  			  value_cat lval,
> >  			  bool *non_constant_p, bool *overflow_p)
> >  {
> > +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> >    tree oldary = TREE_OPERAND (t, 0);
> >    tree ary = cxx_eval_constant_expression (ctx, oldary,
> >  					   lval,
> > @@ -4274,7 +4277,7 @@ cxx_eval_array_reference (const constexpr_ctx *ctx, tree t,
> >  	 building; if there's no initializer for this element yet,
> >  	 that's an error.  */
> >        if (!ctx->quiet)
> > -	error ("accessing uninitialized array element");
> > +	error_at (loc, "accessing uninitialized array element");
> >        *non_constant_p = true;
> >        return t;
> >      }
> > @@ -4323,13 +4326,14 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
> >    tree whole = cxx_eval_constant_expression (ctx, orig_whole,
> >  					     lval,
> >  					     non_constant_p, overflow_p);
> > +  location_t loc = cp_expr_loc_or_loc (whole, ctx->global->loc);
> >    if (*non_constant_p)
> >      return t;
> >    if (INDIRECT_REF_P (whole)
> >        && integer_zerop (TREE_OPERAND (whole, 0)))
> >      {
> >        if (!ctx->quiet)
> > -	error ("dereferencing a null pointer in %qE", orig_whole);
> > +	error_at (loc, "dereferencing a null pointer in %qE", orig_whole);
> >        *non_constant_p = true;
> >        return t;
> >      }
> > @@ -4348,7 +4352,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
> >    if (TREE_CODE (whole) != CONSTRUCTOR)
> >      {
> >        if (!ctx->quiet)
> > -	error ("%qE is not a constant expression", orig_whole);
> > +	error_at (loc, "%qE is not a constant expression", orig_whole);
> >        *non_constant_p = true;
> >        return t;
> >      }
> > @@ -4356,7 +4360,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
> >        && DECL_MUTABLE_P (part))
> >      {
> >        if (!ctx->quiet)
> > -	error ("mutable %qD is not usable in a constant expression", part);
> > +	error_at (loc, "mutable %qD is not usable in a constant expression", part);
> >        *non_constant_p = true;
> >        return t;
> >      }
> > @@ -4386,10 +4390,10 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
> >  	{
> >  	  constructor_elt *cep = CONSTRUCTOR_ELT (whole, 0);
> >  	  if (cep->value == NULL_TREE)
> > -	    error ("accessing uninitialized member %qD", part);
> > +	    error_at (loc, "accessing uninitialized member %qD", part);
> >  	  else
> > -	    error ("accessing %qD member instead of initialized %qD member in "
> > -		   "constant expression", part, cep->index);
> > +	    error_at (loc, "accessing %qD member instead of initialized %qD member "
> > +		      "in constant expression", part, cep->index);
> >  	}
> >        *non_constant_p = true;
> >        return t;
> > @@ -4408,7 +4412,7 @@ cxx_eval_component_reference (const constexpr_ctx *ctx, tree t,
> >  	 building; if there's no initializer for this member yet, that's an
> >  	 error.  */
> >        if (!ctx->quiet)
> > -	error ("accessing uninitialized member %qD", part);
> > +	error_at (loc, "accessing uninitialized member %qD", part);
> >        *non_constant_p = true;
> >        return t;
> >      }
> > @@ -4436,6 +4440,7 @@ cxx_eval_bit_field_ref (const constexpr_ctx *ctx, tree t,
> >    tree whole = cxx_eval_constant_expression (ctx, orig_whole,
> >  					     lval,
> >  					     non_constant_p, overflow_p);
> > +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> >    tree start, field, value;
> >    unsigned HOST_WIDE_INT i;
> >  
> > @@ -4448,7 +4453,7 @@ cxx_eval_bit_field_ref (const constexpr_ctx *ctx, tree t,
> >        && TREE_CODE (whole) != CONSTRUCTOR)
> >      {
> >        if (!ctx->quiet)
> > -	error ("%qE is not a constant expression", orig_whole);
> > +	error_at (loc, "%qE is not a constant expression", orig_whole);
> >        *non_constant_p = true;
> >      }
> >    if (*non_constant_p)
> > @@ -4460,7 +4465,7 @@ cxx_eval_bit_field_ref (const constexpr_ctx *ctx, tree t,
> >  				 TREE_OPERAND (t, 1), TREE_OPERAND (t, 2)))
> >  	return r;
> >        if (!ctx->quiet)
> > -	error ("%qE is not a constant expression", orig_whole);
> > +	error_at (loc, "%qE is not a constant expression", orig_whole);
> >        *non_constant_p = true;
> >        return t;
> >      }
> > @@ -5609,6 +5614,7 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
> >  		       value_cat lval,
> >  		       bool *non_constant_p, bool *overflow_p)
> >  {
> > +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> >    tree orig_op0 = TREE_OPERAND (t, 0);
> >    bool empty_base = false;
> >  
> > @@ -5639,7 +5645,7 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
> >        if (!lval && integer_zerop (op0))
> >  	{
> >  	  if (!ctx->quiet)
> > -	    error ("dereferencing a null pointer");
> > +	    error_at (loc, "dereferencing a null pointer");
> >  	  *non_constant_p = true;
> >  	  return t;
> >  	}
> > @@ -5658,8 +5664,7 @@ cxx_eval_indirect_ref (const constexpr_ctx *ctx, tree t,
> >  			  (TREE_TYPE (TREE_TYPE (sub)), TREE_TYPE (t)));
> >  	      /* DR 1188 says we don't have to deal with this.  */
> >  	      if (!ctx->quiet)
> > -		error_at (cp_expr_loc_or_input_loc (t),
> > -			  "accessing value of %qE through a %qT glvalue in a "
> > +		error_at (loc, "accessing value of %qE through a %qT glvalue in a "
> >  			  "constant expression", build_fold_indirect_ref (sub),
> >  			  TREE_TYPE (t));
> >  	      *non_constant_p = true;
> > @@ -5906,6 +5911,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
> >  			   value_cat lval,
> >  			   bool *non_constant_p, bool *overflow_p)
> >  {
> > +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> >    constexpr_ctx new_ctx = *ctx;
> >  
> >    tree init = TREE_OPERAND (t, 1);
> > @@ -6030,7 +6036,7 @@ 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);
> > +	error_at (loc, "modification of %qE is not a constant expression", object);
> >        *non_constant_p = true;
> >        return t;
> >      }
> > @@ -6128,7 +6134,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
> >  	  if (cxx_dialect < cxx20)
> >  	    {
> >  	      if (!ctx->quiet)
> > -		error_at (cp_expr_loc_or_input_loc (t),
> > +		error_at (cp_expr_loc_or_loc (t, ctx->global->loc),
> >  			  "change of the active member of a union "
> >  			  "from %qD to %qD",
> >  			  CONSTRUCTOR_ELT (*valp, 0)->index,
> > @@ -6141,7 +6147,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
> >  	      /* Diagnose changing the active union member while the union
> >  		 is in the process of being initialized.  */
> >  	      if (!ctx->quiet)
> > -		error_at (cp_expr_loc_or_input_loc (t),
> > +		error_at (cp_expr_loc_or_loc (t, ctx->global->loc),
> >  			  "change of the active member of a union "
> >  			  "from %qD to %qD during initialization",
> >  			  CONSTRUCTOR_ELT (*valp, 0)->index,
> > @@ -6224,7 +6230,7 @@ cxx_eval_store_expression (const constexpr_ctx *ctx, tree t,
> >        if (fail)
> >  	{
> >  	  if (!ctx->quiet)
> > -	    modifying_const_object_error (t, const_object_being_modified);
> > +	    modifying_const_object_error (ctx, t, const_object_being_modified);
> >  	  *non_constant_p = true;
> >  	  return t;
> >  	}
> > @@ -6381,6 +6387,8 @@ cxx_eval_increment_expression (const constexpr_ctx *ctx, tree t,
> >    tree offset = TREE_OPERAND (t, 1);
> >    gcc_assert (TREE_CONSTANT (offset));
> >  
> > +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> > +
> >    /* OFFSET is constant, but perhaps not constant enough.  We need to
> >       e.g. bash FLOAT_EXPRs to REAL_CSTs.  */
> >    offset = fold_simple (offset);
> > @@ -6428,8 +6436,7 @@ cxx_eval_increment_expression (const constexpr_ctx *ctx, tree t,
> >      VERIFY_CONSTANT (mod);
> >  
> >    /* Storing the modified value.  */
> > -  tree store = build2_loc (cp_expr_loc_or_loc (t, input_location),
> > -			   MODIFY_EXPR, type, op, mod);
> > +  tree store = build2_loc (loc, MODIFY_EXPR, type, op, mod);
> >    mod = cxx_eval_constant_expression (ctx, store, lval,
> >  				      non_constant_p, overflow_p);
> >    ggc_free (store);
> > @@ -6602,6 +6609,7 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
> >  		    bool *non_constant_p, bool *overflow_p,
> >  		    tree *jump_target)
> >  {
> > +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> >    constexpr_ctx new_ctx = *ctx;
> >    tree local_target;
> >    if (!jump_target)
> > @@ -6691,7 +6699,7 @@ cxx_eval_loop_expr (const constexpr_ctx *ctx, tree t,
> >        if (++count >= constexpr_loop_limit)
> >  	{
> >  	  if (!ctx->quiet)
> > -	    error_at (cp_expr_loc_or_input_loc (t),
> > +	    error_at (loc,
> >  		      "%<constexpr%> loop iteration count exceeds limit of %d "
> >  		      "(use %<-fconstexpr-loop-limit=%> to increase the limit)",
> >  		      constexpr_loop_limit);
> > @@ -6950,7 +6958,10 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> >        return t;
> >      }
> >  
> > -  location_t loc = cp_expr_loc_or_input_loc (t);
> > +  /* Track current location, propagating down from parent calls
> > +     in case this expression has no location information.  */
> > +  location_t loc = cp_expr_loc_or_loc (t, ctx->global->loc);
> > +  ctx->global->loc = loc;
> >  
> >    STRIP_ANY_LOCATION_WRAPPER (t);
> >  
> > @@ -6973,8 +6984,8 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> >  	  && !integer_zerop (t))
> >  	{
> >  	  if (!ctx->quiet)
> > -	    error ("value %qE of type %qT is not a constant expression",
> > -		   t, TREE_TYPE (t));
> > +	    error_at (loc, "value %qE of type %qT is not a constant expression",
> > +		      t, TREE_TYPE (t));
> >  	  *non_constant_p = true;
> >  	}
> >  
> > @@ -7222,8 +7233,8 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> >  	    if (!ctx->quiet)
> >  	      {
> >  		auto_diagnostic_group d;
> > -		error ("temporary of non-literal type %qT in a "
> > -		       "constant expression", type);
> > +		error_at (loc, "temporary of non-literal type %qT in a "
> > +			  "constant expression", type);
> >  		explain_non_literal_class (type);
> >  	      }
> >  	    *non_constant_p = true;
> > @@ -8025,8 +8036,7 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> >  	if (function_concept_p (tmpl))
> >  	  {
> >  	    if (!ctx->quiet)
> > -	      error_at (cp_expr_loc_or_input_loc (t),
> > -			"function concept must be called");
> > +	      error_at (loc, "function concept must be called");
> >  	    r = error_mark_node;
> >  	    break;
> >  	  }
> > @@ -8121,6 +8131,9 @@ cxx_eval_constant_expression (const constexpr_ctx *ctx, tree t,
> >        break;
> >      }
> >  
> > +  /* Reset current location in case it was modified in child calls.  */
> > +  ctx->global->loc = loc;
> > +
> >    if (r == error_mark_node)
> >      *non_constant_p = true;
> >  
> > diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C
> > index 4574eb83ff7..11630f26ffe 100644
> > --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C
> > +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-48089.C
> > @@ -10,11 +10,11 @@
> >  // R() is well-formed because i is initialized before j.
> >  
> >  struct s {
> > -  constexpr s() : v(v) { }
> > +  constexpr s() : v(v) { } // { dg-error "accessing uninitialized member" }
> >    int v;
> >  };
> >  
> > -constexpr s bang;		// { dg-error "|" }
> > +constexpr s bang;  // { dg-message "in .constexpr. expansion" }
> >  
> >  struct R {
> >    int i,j;
> > @@ -26,14 +26,14 @@ constexpr R r;			// { dg-bogus "" }
> >  // Ill-formed (no diagnostic required)
> >  struct T {
> >    int i;
> > -  constexpr int f() { return i; }
> > +  constexpr int f() { return i; }  // { dg-error "accessing uninitialized member" }
> >    constexpr T(): i(0) { }
> > -  constexpr T(const T& t) : i(f()) { } // { dg-message "" }
> > +  constexpr T(const T& t) : i(f()) { }  // { dg-message "in .constexpr. expansion" }
> >  };
> >  
> >  constexpr T t1;
> >  // Ill-formed (diagnostic required)
> > -constexpr T t2(t1);		// { dg-message "" }
> > +constexpr T t2(t1);		// { dg-message "in .constexpr. expansion" }
> >  
> >  // Well-formed
> >  struct U {
> > diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
> > index 5eedf42ba36..50c676c56cd 100644
> > --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
> > +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-diag3.C
> > @@ -16,7 +16,7 @@ int main()
> >  struct complex 			// { dg-message "no .constexpr. constructor" "" { target { ! implicit_constexpr } } }
> >  {
> >    complex(double r, double i) : re(r), im(i) { }
> > -  constexpr double real() const { return re; } // { dg-error "not a literal type" "" { target c++11_only } }
> > +  constexpr double real() const { return re; } // { dg-error "not a literal type|not usable in a constant expression" "" { target { ! implicit_constexpr } } }
> >    double imag() const { return im; }
> >  
> >  private:
> > diff --git a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > index ebaa95e5324..e4e3bf865cd 100644
> > --- a/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > +++ b/gcc/testsuite/g++.dg/cpp0x/constexpr-ice20.C
> > @@ -2,6 +2,6 @@
> >  // { dg-do compile { target c++11 } }
> >  
> >  typedef bool (*Function)(int);
> > -constexpr bool check(int x, Function p) { return p(x); }  // { dg-message "in .constexpr. expansion of" }
> > +constexpr bool check(int x, Function p) { return p(x); }  // { dg-error "lifetime" }
> >  
> > -static_assert(check(2, check), "");  // { dg-error "conversion|constant|lifetime|in .constexpr. expansion of" }
> > +static_assert(check(2, check), "");  // { dg-error "conversion|constant|in .constexpr. expansion of" }
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C
> > index 8ac4ef0fd36..6f8f6a8038e 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-89481.C
> > @@ -6,7 +6,7 @@ foo ()
> >  {
> >    union U { long long a; int b[2]; } u { 5LL };
> >    u.b[1] = 4;		// { dg-error "change of the active member of a union from" "" { target c++17_down } }
> > -  return u.b[0];
> > +  return u.b[0];	// { dg-error "accessing uninitialized array element" "" { target c++2a } }
> >  }
> >  
> >  constexpr int
> > @@ -19,6 +19,5 @@ bar ()
> >  
> >  static_assert (foo () == 0, "");	// { dg-error "non-constant condition for static assertion" }
> >  					// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1 }
> > -					// { dg-error "accessing uninitialized array element" "" { target c++2a } .-2 }
> >  static_assert (bar () == 4, "");	// { dg-error "non-constant condition for static assertion" "" { target c++17_down } }
> >  					// { dg-message "in 'constexpr' expansion of" "" { target c++17_down } .-1 }
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > index 43aa7c974c1..f79f1611d5f 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime1.C
> > @@ -11,3 +11,4 @@ constexpr const int& test() {
> >    return local.get();
> >  }
> >  constexpr int x = test();  // { dg-error "accessing object outside its lifetime" }
> > +
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > index 22cd919fcda..2f5ae8db6d5 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime2.C
> > @@ -8,9 +8,9 @@ struct S {
> >  
> >  constexpr int error() {
> >    const auto& local = S{}.get();  // { dg-message "note: declared here" }
> > -  return local;
> > +  return local;  // { dg-error "accessing object outside its lifetime" }
> >  }
> > -constexpr int x = error();  // { dg-error "accessing object outside its lifetime" }
> > +constexpr int x = error();  // { dg-message "in .constexpr. expansion" }
> >  
> >  constexpr int ok() {
> >    // temporary should only be destroyed after end of full-expression
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > index 6329f8cf6c6..53785521d05 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime3.C
> > @@ -7,7 +7,7 @@ constexpr int f(int i) {
> >      int j = 123;  // { dg-message "note: declared here" }
> >      p = &j;
> >    }
> > -  return *p;
> > +  return *p;  // { dg-error "accessing object outside its lifetime" }
> >  }
> >  
> > -constexpr int i = f(0);  // { dg-error "accessing object outside its lifetime" }
> > +constexpr int i = f(0);  // { dg-message "in .constexpr. expansion" }
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > index 181a1201663..4302da1eddc 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime4.C
> > @@ -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 "non-constant condition|accessing object outside its lifetime" }
> >  
> >  // no deference, shouldn't error
> >  static_assert((test(), true), "");
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > index ad3ef579f63..a12920c8fba 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-lifetime5.C
> > @@ -5,7 +5,7 @@ constexpr const int& id(int x) { return x; }  // { dg-message "note: declared he
> >  
> >  constexpr bool test() {
> >    const int& y = id(3);
> > -  return y == 3;
> > +  return y == 3;  // { dg-error "accessing object outside its lifetime" }
> >  }
> >  
> > -constexpr bool x = test();  // { dg-error "accessing object outside its lifetime" }
> > +constexpr bool x = test();  // { dg-message "in .constexpr. expansion" }
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C b/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C
> > index 55fe9fa2f0b..3d76345d564 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/constexpr-union5.C
> > @@ -8,8 +8,8 @@ union U {
> >  };
> >  
> >  constexpr int foo(U *up) {
> > -  up->a++;
> > +  up->a++; // { dg-error "accessing uninitialized member" }
> >    return {42};
> >  }
> >  
> > -extern constexpr U u = {}; // { dg-error "accessing uninitialized member" }
> > +extern constexpr U u = {}; // { dg-message "in .constexpr. expansion" }
> > diff --git a/gcc/testsuite/g++.dg/cpp1y/pr68180.C b/gcc/testsuite/g++.dg/cpp1y/pr68180.C
> > index 9e6e5e984f9..8de1ef3936b 100644
> > --- a/gcc/testsuite/g++.dg/cpp1y/pr68180.C
> > +++ b/gcc/testsuite/g++.dg/cpp1y/pr68180.C
> > @@ -6,11 +6,11 @@ typedef float __attribute__( ( vector_size( 16 ) ) ) float32x4_t;
> >  constexpr float32x4_t fill(float x) {
> >    float32x4_t v{0};
> >    constexpr auto vs = sizeof(v)/sizeof(v[0]);
> > -  for (auto i=0U; i<vs; ++i) v[i]=i;
> > +  for (auto i=0U; i<vs; ++i) v[i]=i; // { dg-error "not a constant" }
> >    return v+x;
> >  }
> >  
> >  float32x4_t foo(float32x4_t x) {
> > -  constexpr float32x4_t v = fill(1.f); // { dg-error "not a constant||in .constexpr. expansion of " }
> > +  constexpr float32x4_t v = fill(1.f); // { dg-message "in .constexpr. expansion of " }
> >    return x+v;
> >  }
> > diff --git a/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C b/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C
> > index 214d3821299..c46c2d4c7fe 100644
> > --- a/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C
> > +++ b/gcc/testsuite/g++.dg/cpp1z/constexpr-lambda6.C
> > @@ -1,7 +1,7 @@
> >  // Testcase from P0170R1
> >  // { dg-do compile { target c++17 } }
> >  
> > -auto monoid = [](auto v) { return [=] { return v; }; };
> > +auto monoid = [](auto v) { return [=] { return v; }; };  // { dg-error "not usable in a constant expression" }
> >  auto add = [](auto m1) constexpr {
> >    auto ret = m1();
> >    return [=](auto m2) mutable {
> > @@ -22,7 +22,7 @@ int main()
> >    // member function call operator can not perform an lvalue-to-rvalue conversion
> >    // on one of its subobjects (that represents its capture) in a constant
> >    // expression.
> > -  auto two = monoid(2);
> > +  auto two = monoid(2);  // { dg-message "not declared .constexpr." }
> >    if (!(two() == 2)) __builtin_abort(); // OK, not a constant expression.
> >    static_assert(add(one)(one)() == two()); // { dg-error "|in .constexpr. expansion of " } two() is not a constant expression
> >    static_assert(add(one)(one)() == monoid(2)()); // OK
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C b/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C
> > index a3eb31bc6c7..760c9ca40b4 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/bit-cast11.C
> > @@ -28,7 +28,7 @@ f3 ()
> >  {
> >    T t = { 1, 2 };
> >    S s = __builtin_bit_cast (S, t);
> > -  return s.a[1] == 0;
> > +  return s.a[1] == 0;		// { dg-error "accessing uninitialized array element" }
> >  }
> >  
> >  constexpr bool
> > @@ -52,12 +52,12 @@ f6 ()
> >  {
> >    W t = { 1, 2 };
> >    V s = __builtin_bit_cast (V, t);
> > -  return s.b.a[1] == 1;
> > +  return s.b.a[1] == 1;		// { dg-error "accessing uninitialized array element" }
> >  }
> >  
> >  constexpr bool a = f1 ();
> >  constexpr bool b = f2 ();
> > -constexpr bool c = f3 ();	// { dg-error "accessing uninitialized array element" }
> > -constexpr bool d = f4 ();
> > +constexpr bool c = f3 ();	// { dg-message "in .constexpr. expansion" }
> > +constexpr bool d = f4 ();	// { dg-message "in .constexpr. expansion" }
> >  constexpr bool e = f5 ();
> > -constexpr bool f = f6 ();	// { dg-error "accessing uninitialized array element" }
> > +constexpr bool f = f6 ();	// { dg-message "in .constexpr. expansion" }
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C b/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C
> > index 9c699dd55f0..e205bc6a8c1 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/bit-cast12.C
> > @@ -33,7 +33,7 @@ f3 ()
> >  {
> >    T t = { 1, 2 };
> >    S s = __builtin_bit_cast (S, t);
> > -  return s.a[1] == 0;
> > +  return s.a[1] == 0;		// { dg-error "accessing uninitialized array element" }
> >  }
> >  
> >  constexpr bool
> > @@ -57,12 +57,12 @@ f6 ()
> >  {
> >    W t = { 1, 2 };
> >    V s = __builtin_bit_cast (V, t);
> > -  return s.b.a[1] == 1;
> > +  return s.b.a[1] == 1;		// { dg-error "accessing uninitialized array element" }
> >  }
> >  
> >  constexpr bool a = f1 ();
> >  constexpr bool b = f2 ();
> > -constexpr bool c = f3 ();	// { dg-error "accessing uninitialized array element" }
> > -constexpr bool d = f4 ();
> > +constexpr bool c = f3 ();	// { dg-message "in .constexpr. expansion" }
> > +constexpr bool d = f4 ();	// { dg-message "in .constexpr. expansion" }
> >  constexpr bool e = f5 ();
> > -constexpr bool f = f6 ();	// { dg-error "accessing uninitialized array element" }
> > +constexpr bool f = f6 ();	// { dg-message "in .constexpr. expansion" }
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C b/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C
> > index 5e185919be4..e0cc9a39702 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/bit-cast14.C
> > @@ -44,7 +44,7 @@ f5 ()
> >  {
> >    T1 t = { 0, 0, 0, 0, 0, 0, 0 };
> >    S s = __builtin_bit_cast (S, t);
> > -  unsigned char a = s.a;
> > +  unsigned char a = s.a;		// { dg-error "accessing uninitialized member" }
> >    return true;
> >  }
> >  
> > @@ -53,7 +53,7 @@ f6 ()
> >  {
> >    T2 t = { 0, 0, 0, 0, 0, 0, 0 };
> >    S s = __builtin_bit_cast (S, t);
> > -  unsigned char b = s.b;
> > +  unsigned char b = s.b;		// { dg-error "accessing uninitialized member" }
> >    return true;
> >  }
> >  
> > @@ -62,14 +62,14 @@ f7 ()
> >  {
> >    T3 t = { 0, 0, 0, 0, 0, 0, 0 };
> >    S s = __builtin_bit_cast (S, t);
> > -  unsigned char c = s.c;
> > +  unsigned char c = s.c;		// { dg-error "accessing uninitialized member" }
> >    return true;
> >  }
> >  
> >  constexpr bool a = f1 ();
> >  constexpr bool b = f2 ();
> >  constexpr bool c = f3 ();
> > -constexpr bool d = f4 ();
> > -constexpr bool e = f5 ();	// { dg-error "accessing uninitialized member" }
> > -constexpr bool f = f6 ();	// { dg-error "accessing uninitialized member" }
> > -constexpr bool g = f7 ();	// { dg-error "accessing uninitialized member" }
> > +constexpr bool d = f4 ();	// { dg-message "in .constexpr. expansion" }
> > +constexpr bool e = f5 ();	// { dg-message "in .constexpr. expansion" }
> > +constexpr bool f = f6 ();	// { dg-message "in .constexpr. expansion" }
> > +constexpr bool g = f7 ();	// { dg-message "in .constexpr. expansion" }
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C
> > index 01bdfa5bd4d..b0c91d5ef97 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-98122.C
> > @@ -9,7 +9,7 @@ bar ()
> >  {
> >    V f { .b = 42 };
> >    constexpr auto m = &V::a;
> > -  return (f.*m) == 42;
> > +  return (f.*m) == 42;  // { dg-error "accessing 'V::a' member instead of initialized 'V::b' member in constant expression" }
> >  }
> >  
> >  constexpr bool
> > @@ -21,5 +21,5 @@ baz ()
> >  }
> >  
> >  static_assert (bar (), "");	// { dg-error "non-constant condition for static assertion" }
> > -				// { dg-error "accessing 'V::a' member instead of initialized 'V::b' member in constant expression" "" { target *-*-* } .-1 }
> > +				// { dg-message "in .constexpr. expansion" "" { target *-*-* } .-1 }
> >  static_assert (baz (), "");
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C
> > index a26678e6ed7..28facf192df 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-dynamic17.C
> > @@ -25,8 +25,7 @@ struct D : B, A {
> >  
> >  constexpr B::B(V* v, A* a)
> >  {
> > -  dynamic_cast<B*>(a);
> > +  dynamic_cast<B*>(a); // { dg-error "accessing uninitialized member" }
> >  }
> >  
> > -constexpr D d; // { dg-error "accessing uninitialized member" }
> > -// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1 }
> > +constexpr D d; // { dg-message "in 'constexpr' expansion of" }
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C
> > index e56ecfed48a..b4e39b6f928 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-init1.C
> > @@ -52,11 +52,10 @@ constexpr int
> >  fn5 ()
> >  {
> >    struct S { int a = 9; int b; } s;
> > -  return s.b;
> > +  return s.b; // { dg-error "accessing uninitialized member" }
> >  }
> >  
> > -constexpr int b = fn5 (); // { dg-error "accessing uninitialized member" }
> > -// { dg-message "in .constexpr. expansion of" "" { target *-*-* } .-1 }
> > +constexpr int b = fn5 (); // { dg-message "in .constexpr. expansion of" }
> >  
> >  constexpr int
> >  fn6 ()
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C
> > index 5a3d06a5fab..832782e1427 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new12.C
> > @@ -17,11 +17,11 @@ struct B : A {
> >  constexpr int
> >  foo ()
> >  {
> > -  A *a = new B ();
> > +  A *a = new B ();  // { dg-message "allocated here" }
> >    a->a = 4;
> >    delete a;
> > -  int r = a->foo ();
> > +  int r = a->foo ();  // { dg-error "constant expression" }
> >    return r;
> >  }
> >  
> > -constexpr auto a = foo ();	// { dg-error "constant expression" }
> > +constexpr auto a = foo ();  // { dg-message "in .constexpr. expansion" }
> > diff --git a/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C b/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
> > index 70b841208f8..3ba440fec53 100644
> > --- a/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
> > +++ b/gcc/testsuite/g++.dg/cpp2a/constexpr-new3.C
> > @@ -45,11 +45,10 @@ constexpr bool
> >  f5 ()
> >  {
> >    int *p = new int;		// { dg-message "allocated here" }
> > -  return *p == 1;
> > +  return *p == 1;		// { dg-error "the content of uninitialized storage is not usable in a constant expression" }
> >  }
> >  
> > -constexpr auto v5 = f5 ();	// { dg-error "the content of uninitialized storage is not usable in a constant expression" }
> > -				// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1 }
> > +constexpr auto v5 = f5 (); 	// { dg-message "in 'constexpr' expansion of" }
> >  
> >  constexpr bool
> >  f6 ()
> > @@ -57,11 +56,10 @@ f6 ()
> >    int *p = new int (2);		// { dg-message "allocated here" }
> >    int *q = p;
> >    delete p;
> > -  return *q == 2;
> > +  return *q == 2;		// { dg-error "use of allocated storage after deallocation in a constant expression" }
> >  }
> >  
> > -constexpr auto v6 = f6 ();	// { dg-error "use of allocated storage after deallocation in a constant expression" }
> > -				// { dg-message "in 'constexpr' expansion of" "" { target *-*-* } .-1  }
> > +constexpr auto v6 = f6 (); 	// { dg-message "in 'constexpr' expansion of" }
> >  
> >  constexpr int *
> >  f7 ()
> > diff --git a/gcc/testsuite/g++.dg/ext/constexpr-vla2.C b/gcc/testsuite/g++.dg/ext/constexpr-vla2.C
> > index d4ea7c58c0d..e09a27af3de 100644
> > --- a/gcc/testsuite/g++.dg/ext/constexpr-vla2.C
> > +++ b/gcc/testsuite/g++.dg/ext/constexpr-vla2.C
> > @@ -4,7 +4,7 @@
> >  constexpr int
> >  fn_bad (int n)
> >  {
> > -  __extension__ int a [n] = { 0 };
> > +  __extension__ int a [n] = { 0 };  // { dg-error "array subscript" }
> >    int z = a [0] + (n ? fn_bad (n - 1) : 0); // { dg-message "in .constexpr. expansion of " } 
> >    return z;
> >  }
> > @@ -18,4 +18,4 @@ fn_ok (int n)
> >  }
> >  
> >  constexpr int i1 = fn_ok (3);
> > -constexpr int i2 = fn_bad (3); // { dg-error "array subscript|in .constexpr. expansion of " }
> > +constexpr int i2 = fn_bad (3); // { dg-message "in .constexpr. expansion of " }
> > diff --git a/gcc/testsuite/g++.dg/ext/constexpr-vla3.C b/gcc/testsuite/g++.dg/ext/constexpr-vla3.C
> > index 538b576a825..6f9daa1897f 100644
> > --- a/gcc/testsuite/g++.dg/ext/constexpr-vla3.C
> > +++ b/gcc/testsuite/g++.dg/ext/constexpr-vla3.C
> > @@ -4,11 +4,11 @@
> >  constexpr int
> >  foo (int n)
> >  {
> > -  __extension__ int a[n] = { 1, 2, 3, 4, 5, 6 };
> > +  __extension__ int a[n] = { 1, 2, 3, 4, 5, 6 }; // { dg-error "array subscript" }
> >    int z = 0;
> >    for (int i = 0; i <= n; ++i)
> >      z += a[i];
> >    return z;
> >  }
> >  
> > -constexpr int n = foo (3); // { dg-error "array subscript|in .constexpr. expansion of " }
> > +constexpr int n = foo (3); // { dg-message "in .constexpr. expansion of " }
> > diff --git a/gcc/testsuite/g++.dg/ubsan/pr63956.C b/gcc/testsuite/g++.dg/ubsan/pr63956.C
> > index 3a1596e6e2e..0771732ef00 100644
> > --- a/gcc/testsuite/g++.dg/ubsan/pr63956.C
> > +++ b/gcc/testsuite/g++.dg/ubsan/pr63956.C
> > @@ -100,13 +100,13 @@ constexpr int
> >  fn7 (const int *a, int b)
> >  {
> >    if (b != 3)
> > -    return fn6 (*a, b);
> > +    return fn6 (*a, b); // { dg-error "null pointer" }
> >    return 7;
> >  }
> >  
> >  constexpr int n1 = 7;
> >  constexpr int n2 = fn7 (&n1, 5);
> > -constexpr int n3 = fn7 ((const int *) 0, 8);  // { dg-error "null pointer|in .constexpr. expansion of " }
> > +constexpr int n3 = fn7 ((const int *) 0, 8);  // { dg-message "in .constexpr. expansion of " }
> >  
> >  constexpr int
> >  fn8 (int i)
> > diff --git a/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc b/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc
> > index 34ca5c4805c..fd89ac0e166 100644
> > --- a/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc
> > +++ b/libstdc++-v3/testsuite/25_algorithms/equal/constexpr_neg.cc
> > @@ -32,7 +32,7 @@ test01()
> >    return outa;
> >  }
> >  
> > -static_assert(test01()); // { dg-error "outside the bounds" }
> > +static_assert(test01()); // { dg-error "non-constant condition" }
> >  
> >  constexpr bool
> >  test02()
> > @@ -44,7 +44,8 @@ test02()
> >    return outa;
> >  }
> >  
> > -static_assert(test02()); // { dg-error "outside the bounds" }
> > +static_assert(test02()); // { dg-error "non-constant condition" }
> >  
> > -// { dg-prune-output "non-constant condition" }
> > +// Errors occuring within <algorithm> internals:
> > +// { dg-error "outside the bounds of array" "" { target *-*-* } 0 }
> >  // { dg-prune-output "in 'constexpr'" }
> > -- 
> > 2.34.1
> > 
> > 
> 

^ permalink raw reply	[flat|nested] 13+ messages in thread

end of thread, other threads:[~2023-06-30 15:50 UTC | newest]

Thread overview: 13+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2023-03-29  2:32 [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...] Nathaniel Shead
2023-03-29  2:32 ` [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331,PR96630,PR98675] Nathaniel Shead
2023-06-23 16:43   ` [PATCH v2 1/3] c++: Track lifetimes in constant evaluation [PR70331, PR96630, PR98675] Patrick Palka
2023-06-24 14:02     ` Nathaniel Shead
2023-06-26 19:37       ` Patrick Palka
2023-06-30 15:45         ` Nathaniel Shead
2023-03-29  2:32 ` [PATCH v2 2/3] c++: Improve constexpr error for dangling local variables Nathaniel Shead
2023-06-23 16:57   ` Patrick Palka
2023-03-29  2:32 ` [PATCH v2 3/3] c++: Improve location information in constexpr evaluation Nathaniel Shead
2023-06-23 17:09   ` Patrick Palka
2023-06-30 15:50     ` Nathaniel Shead
2023-05-08 10:48 ` [PATCH v2 0/3] c++: Track lifetimes in constant evaluation [PR70331,...] Nathaniel Shead
2023-06-13 10:11 ` Nathaniel Shead

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).