c++/reflection: fix extract-ref [PR127232]
Checks
| Context |
Check |
Description |
| linaro-tcwg-bot/tcwg_simplebootstrap_build--master-arm-bootstrap |
success
|
Build passed
|
| linaro-tcwg-bot/tcwg_gcc_build--master-arm |
success
|
Build passed
|
Commit Message
Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
-- >8 --
A couple of problems here:
[meta.reflection.extract]/5.3 says "If r represents a variable, then
either that variable is usable in constant expressions or its lifetime
began within the core constant expression currently under evaluation."
but I was wrongly using is_constant_expression. That led to a crash
because we passed a type to build_address. For "is usable..." we want
decl_constant_var_p and for the "its lifetime began..." I'm adding a new
function. Using cxx_eval_constant_expression for non-reference
variables wouldn't work since evaluating a variable as a glvalue always
succeeds, and as a prvalue fails for e.g. an uninitialized variable
whose lifetime did begin.
With this patch, a variable that isn't usable in constant expressions
throws. Thus the extract2.C and extract3.C changes; one can use
object_of to get the object instead.
Also, DECL_INITIAL of a PARM_DECL is not an initializer but DECL_ARG_TYPE.
So we need cxx_eval_constant_expression to handle e.g.:
int g;
consteval void h (const int &p)
{
std::meta::extract<const int &> (^^p);
}
consteval { h (g); }
where we need to get "(const int &) &g" from PARM_DECL p.
This adds a new XFAIL because we don't have P2686 yet.
It seems that object_of will need similar tweaks but that's for another
patch.
PR c++/127232
gcc/cp/ChangeLog:
* constexpr.cc (constexpr_global_ctx::within_lifetime_p): New.
(cxx_constexpr_in_eval_lifetime_p): New.
* cp-tree.h (cxx_constexpr_in_eval_lifetime_p): Declare.
* reflect.cc (extract_ref): Check decl_constant_var_p and
cxx_constexpr_in_eval_lifetime_p instead of
is_constant_expression. Use cxx_eval_constant_expression
instead of DECL_INITIAL for references. Adjust the checking
assert.
gcc/testsuite/ChangeLog:
* g++.dg/reflect/extract2.C: Adjust dg-error.
* g++.dg/reflect/extract3.C: Add dg-error. Add tests using
object_of.
* g++.dg/reflect/extract13.C: New test.
* g++.dg/reflect/extract14.C: New test.
---
gcc/cp/constexpr.cc | 20 +++++++-
gcc/cp/cp-tree.h | 1 +
gcc/cp/reflect.cc | 15 ++++--
gcc/testsuite/g++.dg/reflect/extract13.C | 59 ++++++++++++++++++++++++
gcc/testsuite/g++.dg/reflect/extract14.C | 23 +++++++++
gcc/testsuite/g++.dg/reflect/extract2.C | 2 +-
gcc/testsuite/g++.dg/reflect/extract3.C | 21 ++++++---
7 files changed, 129 insertions(+), 12 deletions(-)
create mode 100644 gcc/testsuite/g++.dg/reflect/extract13.C
create mode 100644 gcc/testsuite/g++.dg/reflect/extract14.C
base-commit: b24418a22128668b21179dfcb73fa54ee3929ecf
Comments
On 10/1/26 9:55 PM, Marek Polacek wrote:
> Bootstrapped/regtested on x86_64-pc-linux-gnu, ok for trunk?
>
> -- >8 --
> A couple of problems here:
>
> [meta.reflection.extract]/5.3 says "If r represents a variable, then
> either that variable is usable in constant expressions or its lifetime
> began within the core constant expression currently under evaluation."
> but I was wrongly using is_constant_expression. That led to a crash
> because we passed a type to build_address. For "is usable..." we want
> decl_constant_var_p and for the "its lifetime began..." I'm adding a new
> function. Using cxx_eval_constant_expression for non-reference
> variables wouldn't work since evaluating a variable as a glvalue always
> succeeds, and as a prvalue fails for e.g. an uninitialized variable
> whose lifetime did begin.
>
> With this patch, a variable that isn't usable in constant expressions
> throws. Thus the extract2.C and extract3.C changes; one can use
> object_of to get the object instead.
>
> Also, DECL_INITIAL of a PARM_DECL is not an initializer but DECL_ARG_TYPE.
> So we need cxx_eval_constant_expression to handle e.g.:
>
> int g;
> consteval void h (const int &p)
> {
> std::meta::extract<const int &> (^^p);
> }
> consteval { h (g); }
>
> where we need to get "(const int &) &g" from PARM_DECL p.
>
> This adds a new XFAIL because we don't have P2686 yet.
>
> It seems that object_of will need similar tweaks but that's for another
> patch.
>
> PR c++/127232
>
> gcc/cp/ChangeLog:
>
> * constexpr.cc (constexpr_global_ctx::within_lifetime_p): New.
> (cxx_constexpr_in_eval_lifetime_p): New.
> * cp-tree.h (cxx_constexpr_in_eval_lifetime_p): Declare.
> * reflect.cc (extract_ref): Check decl_constant_var_p and
> cxx_constexpr_in_eval_lifetime_p instead of
> is_constant_expression. Use cxx_eval_constant_expression
> instead of DECL_INITIAL for references. Adjust the checking
> assert.
>
> gcc/testsuite/ChangeLog:
>
> * g++.dg/reflect/extract2.C: Adjust dg-error.
> * g++.dg/reflect/extract3.C: Add dg-error. Add tests using
> object_of.
> * g++.dg/reflect/extract13.C: New test.
> * g++.dg/reflect/extract14.C: New test.
> ---
> gcc/cp/constexpr.cc | 20 +++++++-
> gcc/cp/cp-tree.h | 1 +
> gcc/cp/reflect.cc | 15 ++++--
> gcc/testsuite/g++.dg/reflect/extract13.C | 59 ++++++++++++++++++++++++
> gcc/testsuite/g++.dg/reflect/extract14.C | 23 +++++++++
> gcc/testsuite/g++.dg/reflect/extract2.C | 2 +-
> gcc/testsuite/g++.dg/reflect/extract3.C | 21 ++++++---
> 7 files changed, 129 insertions(+), 12 deletions(-)
> create mode 100644 gcc/testsuite/g++.dg/reflect/extract13.C
> create mode 100644 gcc/testsuite/g++.dg/reflect/extract14.C
>
> diff --git a/gcc/cp/constexpr.cc b/gcc/cp/constexpr.cc
> index 31ef5bf1111..cf31c803be6 100644
> --- a/gcc/cp/constexpr.cc
> +++ b/gcc/cp/constexpr.cc
> @@ -1247,7 +1247,16 @@ public:
> return true;
> return false;
> }
> - tree get_value (tree t)
> + /* This differs from !is_outside_lifetime in that it returns false if
> + T is not in the map. */
The names are too similar for this subtle difference. Why not
_eval_lifetime_ like the other function?
> + bool within_lifetime_p (tree t)
> + {
> + if (tree *p = values.get (t))
> + if (*p != void_node && *p != void_list_node)
> + return true;
> + return false;
> + }
> + tree get_value (tree t)
> {
> if (tree *p = values.get (t))
> if (*p != void_node && *p != void_list_node)
> @@ -1404,6 +1413,15 @@ cxx_constexpr_consteval_block (const constexpr_ctx *ctx)
> return ctx->global->consteval_block;
> }
>
> +/* Return true if T's lifetime began within the core constant expression
> + currently under evaluation. */
> +
> +bool
> +cxx_constexpr_in_eval_lifetime_p (const constexpr_ctx *ctx, tree t)
> +{
> + return ctx->global->within_lifetime_p (t);
> +}
> +
> /* Predicates for the meaning of *jump_target. */
>
> static bool
> diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
> index 947bf62ff98..83a287471a5 100644
> --- a/gcc/cp/cp-tree.h
> +++ b/gcc/cp/cp-tree.h
> @@ -9486,6 +9486,7 @@ extern bool cxx_constexpr_quiet_p (const constexpr_ctx *);
> extern mce_value cxx_constexpr_manifestly_const_eval (const constexpr_ctx *);
> extern tree cxx_constexpr_caller (const constexpr_ctx *);
> extern tree cxx_constexpr_consteval_block (const constexpr_ctx *);
> +extern bool cxx_constexpr_in_eval_lifetime_p (const constexpr_ctx *, tree);
>
> /* An RAII sentinel used to restrict constexpr evaluation so that it
> doesn't do anything that causes extra DECL_UID generation. */
> diff --git a/gcc/cp/reflect.cc b/gcc/cp/reflect.cc
> index 8bf518df64f..8a4c5490bbf 100644
> --- a/gcc/cp/reflect.cc
> +++ b/gcc/cp/reflect.cc
> @@ -7686,18 +7686,27 @@ extract_ref (location_t loc, const constexpr_ctx *ctx, tree T, tree r,
> if (adju != error_mark_node
> && adjt != error_mark_node
> && is_convertible (adju, adjt)
> - && (!var_p || is_constant_expression (r)))
> + && (!var_p
> + || decl_constant_var_p (r)
> + || cxx_constexpr_in_eval_lifetime_p (ctx, r)))
> {
> if (TYPE_REF_P (TREE_TYPE (r)))
> {
> - r = DECL_INITIAL (r);
> + bool nc = false, ovf = false;
> + tree jt = NULL_TREE;
> + r = cxx_eval_constant_expression (ctx, r, vc_prvalue,
vc_prvalue seems wrong if we're trying to get a reference to r; there's
no need to fetch its value. Specifically, it should be OK if r has
erroneous value as long as it's within lifetime.
> + &nc, &ovf, &jt);
> + if (nc || jt)
> + goto throw_;
> r = maybe_get_reference_referent (r);
> - gcc_checking_assert (!TYPE_REF_P (TREE_TYPE (r)));
> + gcc_checking_assert (r && !TYPE_P (r)
> + && !TYPE_REF_P (TREE_TYPE (r)));
> }
> return build_address (r);
Incidentally, how is it OK that this returns a pointer rather than a
reference?
> }
> }
>
> +throw_:
> return throw_exception (loc, ctx, "value cannot be extracted", fun,
> non_constant_p, jump_target);
> }
> diff --git a/gcc/testsuite/g++.dg/reflect/extract13.C b/gcc/testsuite/g++.dg/reflect/extract13.C
> new file mode 100644
> index 00000000000..8b9ecdd10bf
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/reflect/extract13.C
> @@ -0,0 +1,59 @@
> +// PR c++/127232
> +// { dg-do compile { target c++26 } }
> +// { dg-additional-options "-freflection" }
> +
> +#include <meta>
> +
> +void
> +foo (int const& x)
> +{
> + constexpr std::meta::info y = ^^x;
> + constexpr auto e = extract<int const&>(y); // { dg-error "value cannot be extracted" }
> +}
> +
> +consteval bool
> +throws_p (std::meta::info r)
> +{
> + try { extract<const int &> (r); }
> + catch (std::meta::exception &) { return true; }
> + return false;
> +}
> +
> +int g;
> +constexpr const int &cr = g;
> +
> +void
> +f (const int &x, int y)
> +{
> + static_assert (throws_p (^^x));
> + static_assert (throws_p (^^y));
> + const int &lx = x;
> + static_assert (throws_p (^^lx));
> + int &lg = g;
> + static_assert (!throws_p (^^lg));
> + static_assert (throws_p (^^g));
> + int a = 42;
> + int &la = a;
> + static_assert (!throws_p (^^la)); // { dg-bogus "" "P2686" { xfail *-*-* } }
> +}
> +
> +consteval bool
> +lifetime ()
> +{
> + int i = 0;
> + int &ri = i;
> + return &extract<int &> (^^ri) == &i;
> +}
> +static_assert (lifetime ());
> +
> +consteval void
> +h (const int &p)
> +{
> + int i = 42;
> + const int &ri = i;
> + extract<const int &> (^^i);
> + extract<const int &> (^^ri);
> + extract<const int &> (^^p);
> +}
> +
> +consteval { h (g); }
> diff --git a/gcc/testsuite/g++.dg/reflect/extract14.C b/gcc/testsuite/g++.dg/reflect/extract14.C
> new file mode 100644
> index 00000000000..ede1a5b3c92
> --- /dev/null
> +++ b/gcc/testsuite/g++.dg/reflect/extract14.C
> @@ -0,0 +1,23 @@
> +// PR c++/127232
> +// { dg-do compile { target c++26 } }
> +// { dg-additional-options "-freflection" }
> +
> +#include <meta>
> +
> +// Check that reflections of locals/parameters refer to the current frame in
> +// recursive calls.
> +consteval bool
> +foo (int n, int *outer)
> +{
> + int i = n;
> + if (&std::meta::extract<int &> (^^i) != &i
> + || &std::meta::extract<int &> (^^n) != &n)
> + return false;
> + if (outer && &std::meta::extract<int &> (^^i) == outer)
> + return false;
> + int &ri = i;
> + if (&std::meta::extract<int &> (^^ri) != &i)
> + return false;
> + return n == 0 || foo (n - 1, &i);
> +}
> +static_assert (foo (2, nullptr));
> diff --git a/gcc/testsuite/g++.dg/reflect/extract2.C b/gcc/testsuite/g++.dg/reflect/extract2.C
> index 9de23d1e931..2d5f758ea96 100644
> --- a/gcc/testsuite/g++.dg/reflect/extract2.C
> +++ b/gcc/testsuite/g++.dg/reflect/extract2.C
> @@ -93,7 +93,7 @@ static_assert (!can_extract<const int &>(reflect_constant (i)));
> static_assert (!can_extract<int &>(^^i));
>
> int j;
> -static_assert (extract<int &>(^^j)); // { dg-error "non-constant|not usable" }
> +constexpr auto e = extract<int &>(^^j); // { dg-error "cannot be extracted" }
>
> consteval info
> bar ()
> diff --git a/gcc/testsuite/g++.dg/reflect/extract3.C b/gcc/testsuite/g++.dg/reflect/extract3.C
> index f8c173c0208..42e06fac342 100644
> --- a/gcc/testsuite/g++.dg/reflect/extract3.C
> +++ b/gcc/testsuite/g++.dg/reflect/extract3.C
> @@ -33,12 +33,16 @@ static_assert (extract<const int &>(^^rj) == j);
> static_assert (&extract<const int &>(^^rj) == &j);
>
> int k = 1;
> -static_assert (&extract<int &>(^^k) == &k);
> -static_assert (&extract<const int &>(^^k) == &k);
> +constexpr auto e1 = extract<int &>(^^k); // { dg-error "cannot be extracted" }
> +constexpr auto e2 = extract<const int &>(^^k); // { dg-error "cannot be extracted" }
> +static_assert (&extract<int &> (object_of (^^k)) == &k);
> +static_assert (&extract<const int &> (object_of (^^k)) == &k);
>
> int arr[5] {};
> -static_assert (&extract<int (&)[5]>(^^arr) == &arr);
> -static_assert (&extract<const int (&)[5]>(^^arr) == &arr);
> +constexpr auto e3 = extract<int (&)[5]>(^^arr); // { dg-error "cannot be extracted" }
> +constexpr auto e4 = extract<const int (&)[5]>(^^arr); // { dg-error "cannot be extracted" }
> +static_assert (&extract<int (&)[5]>(object_of (^^arr)) == &arr);
> +static_assert (&extract<const int (&)[5]>(object_of (^^arr)) == &arr);
>
> struct S { int m; };
> constexpr S s{42};
> @@ -54,9 +58,12 @@ same_type<decltype(extract<const S &>(reflect_constant (S{42}))), const S &> st7
> same_type<decltype(&extract<const S &>(reflect_constant (S{42}))), const S *> st8;
>
> S sr{42};
> -static_assert (&extract<S&>(^^sr) == &sr);
> -static_assert (&extract<const S&>(^^sr) == &sr);
> -static_assert (&extract<S&>(^^sr).m == &sr.m);
> +constexpr auto e5 = extract<S&>(^^sr); // { dg-error "cannot be extracted" }
> +constexpr auto e6 = extract<const S&>(^^sr); // { dg-error "cannot be extracted" }
> +constexpr auto e7 = extract<S&>(^^sr).m; // { dg-error "cannot be extracted" }
> +static_assert (&extract<S&>(object_of (^^sr)) == &sr);
> +static_assert (&extract<const S&>(object_of (^^sr)) == &sr);
> +static_assert (&extract<S&>(object_of (^^sr)).m == &sr.m);
> int &srm = sr.m;
> static_assert (&extract<int&>(^^srm) == &sr.m);
>
>
> base-commit: b24418a22128668b21179dfcb73fa54ee3929ecf
@@ -1247,7 +1247,16 @@ public:
return true;
return false;
}
- tree get_value (tree t)
+ /* This differs from !is_outside_lifetime in that it returns false if
+ T is not in the map. */
+ bool within_lifetime_p (tree t)
+ {
+ if (tree *p = values.get (t))
+ if (*p != void_node && *p != void_list_node)
+ return true;
+ return false;
+ }
+ tree get_value (tree t)
{
if (tree *p = values.get (t))
if (*p != void_node && *p != void_list_node)
@@ -1404,6 +1413,15 @@ cxx_constexpr_consteval_block (const constexpr_ctx *ctx)
return ctx->global->consteval_block;
}
+/* Return true if T's lifetime began within the core constant expression
+ currently under evaluation. */
+
+bool
+cxx_constexpr_in_eval_lifetime_p (const constexpr_ctx *ctx, tree t)
+{
+ return ctx->global->within_lifetime_p (t);
+}
+
/* Predicates for the meaning of *jump_target. */
static bool
@@ -9486,6 +9486,7 @@ extern bool cxx_constexpr_quiet_p (const constexpr_ctx *);
extern mce_value cxx_constexpr_manifestly_const_eval (const constexpr_ctx *);
extern tree cxx_constexpr_caller (const constexpr_ctx *);
extern tree cxx_constexpr_consteval_block (const constexpr_ctx *);
+extern bool cxx_constexpr_in_eval_lifetime_p (const constexpr_ctx *, tree);
/* An RAII sentinel used to restrict constexpr evaluation so that it
doesn't do anything that causes extra DECL_UID generation. */
@@ -7686,18 +7686,27 @@ extract_ref (location_t loc, const constexpr_ctx *ctx, tree T, tree r,
if (adju != error_mark_node
&& adjt != error_mark_node
&& is_convertible (adju, adjt)
- && (!var_p || is_constant_expression (r)))
+ && (!var_p
+ || decl_constant_var_p (r)
+ || cxx_constexpr_in_eval_lifetime_p (ctx, r)))
{
if (TYPE_REF_P (TREE_TYPE (r)))
{
- r = DECL_INITIAL (r);
+ bool nc = false, ovf = false;
+ tree jt = NULL_TREE;
+ r = cxx_eval_constant_expression (ctx, r, vc_prvalue,
+ &nc, &ovf, &jt);
+ if (nc || jt)
+ goto throw_;
r = maybe_get_reference_referent (r);
- gcc_checking_assert (!TYPE_REF_P (TREE_TYPE (r)));
+ gcc_checking_assert (r && !TYPE_P (r)
+ && !TYPE_REF_P (TREE_TYPE (r)));
}
return build_address (r);
}
}
+throw_:
return throw_exception (loc, ctx, "value cannot be extracted", fun,
non_constant_p, jump_target);
}
new file mode 100644
@@ -0,0 +1,59 @@
+// PR c++/127232
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+void
+foo (int const& x)
+{
+ constexpr std::meta::info y = ^^x;
+ constexpr auto e = extract<int const&>(y); // { dg-error "value cannot be extracted" }
+}
+
+consteval bool
+throws_p (std::meta::info r)
+{
+ try { extract<const int &> (r); }
+ catch (std::meta::exception &) { return true; }
+ return false;
+}
+
+int g;
+constexpr const int &cr = g;
+
+void
+f (const int &x, int y)
+{
+ static_assert (throws_p (^^x));
+ static_assert (throws_p (^^y));
+ const int &lx = x;
+ static_assert (throws_p (^^lx));
+ int &lg = g;
+ static_assert (!throws_p (^^lg));
+ static_assert (throws_p (^^g));
+ int a = 42;
+ int &la = a;
+ static_assert (!throws_p (^^la)); // { dg-bogus "" "P2686" { xfail *-*-* } }
+}
+
+consteval bool
+lifetime ()
+{
+ int i = 0;
+ int &ri = i;
+ return &extract<int &> (^^ri) == &i;
+}
+static_assert (lifetime ());
+
+consteval void
+h (const int &p)
+{
+ int i = 42;
+ const int &ri = i;
+ extract<const int &> (^^i);
+ extract<const int &> (^^ri);
+ extract<const int &> (^^p);
+}
+
+consteval { h (g); }
new file mode 100644
@@ -0,0 +1,23 @@
+// PR c++/127232
+// { dg-do compile { target c++26 } }
+// { dg-additional-options "-freflection" }
+
+#include <meta>
+
+// Check that reflections of locals/parameters refer to the current frame in
+// recursive calls.
+consteval bool
+foo (int n, int *outer)
+{
+ int i = n;
+ if (&std::meta::extract<int &> (^^i) != &i
+ || &std::meta::extract<int &> (^^n) != &n)
+ return false;
+ if (outer && &std::meta::extract<int &> (^^i) == outer)
+ return false;
+ int &ri = i;
+ if (&std::meta::extract<int &> (^^ri) != &i)
+ return false;
+ return n == 0 || foo (n - 1, &i);
+}
+static_assert (foo (2, nullptr));
@@ -93,7 +93,7 @@ static_assert (!can_extract<const int &>(reflect_constant (i)));
static_assert (!can_extract<int &>(^^i));
int j;
-static_assert (extract<int &>(^^j)); // { dg-error "non-constant|not usable" }
+constexpr auto e = extract<int &>(^^j); // { dg-error "cannot be extracted" }
consteval info
bar ()
@@ -33,12 +33,16 @@ static_assert (extract<const int &>(^^rj) == j);
static_assert (&extract<const int &>(^^rj) == &j);
int k = 1;
-static_assert (&extract<int &>(^^k) == &k);
-static_assert (&extract<const int &>(^^k) == &k);
+constexpr auto e1 = extract<int &>(^^k); // { dg-error "cannot be extracted" }
+constexpr auto e2 = extract<const int &>(^^k); // { dg-error "cannot be extracted" }
+static_assert (&extract<int &> (object_of (^^k)) == &k);
+static_assert (&extract<const int &> (object_of (^^k)) == &k);
int arr[5] {};
-static_assert (&extract<int (&)[5]>(^^arr) == &arr);
-static_assert (&extract<const int (&)[5]>(^^arr) == &arr);
+constexpr auto e3 = extract<int (&)[5]>(^^arr); // { dg-error "cannot be extracted" }
+constexpr auto e4 = extract<const int (&)[5]>(^^arr); // { dg-error "cannot be extracted" }
+static_assert (&extract<int (&)[5]>(object_of (^^arr)) == &arr);
+static_assert (&extract<const int (&)[5]>(object_of (^^arr)) == &arr);
struct S { int m; };
constexpr S s{42};
@@ -54,9 +58,12 @@ same_type<decltype(extract<const S &>(reflect_constant (S{42}))), const S &> st7
same_type<decltype(&extract<const S &>(reflect_constant (S{42}))), const S *> st8;
S sr{42};
-static_assert (&extract<S&>(^^sr) == &sr);
-static_assert (&extract<const S&>(^^sr) == &sr);
-static_assert (&extract<S&>(^^sr).m == &sr.m);
+constexpr auto e5 = extract<S&>(^^sr); // { dg-error "cannot be extracted" }
+constexpr auto e6 = extract<const S&>(^^sr); // { dg-error "cannot be extracted" }
+constexpr auto e7 = extract<S&>(^^sr).m; // { dg-error "cannot be extracted" }
+static_assert (&extract<S&>(object_of (^^sr)) == &sr);
+static_assert (&extract<const S&>(object_of (^^sr)) == &sr);
+static_assert (&extract<S&>(object_of (^^sr)).m == &sr.m);
int &srm = sr.m;
static_assert (&extract<int&>(^^srm) == &sr.m);