https://gcc.gnu.org/g:621661f7d2e9157b7e5d55fc493e1b6956a35aac

commit r17-2200-g621661f7d2e9157b7e5d55fc493e1b6956a35aac
Author: Vladislav Semykin <[email protected]>
Date:   Tue Jul 7 12:52:08 2026 -0400

    c++: fix unevaluated operand context for typeid [PR125886]
    
    Per [expr.typeid]/4-5, a typeid operand is unevaluated by default and
    is evaluated only for a glvalue of polymorphic class type whose dynamic
    type is not known at compile time.  Previously GCC always parsed the
    operand in an evaluated context, which broke unevaluated uses (declval,
    non-static data members per DR613, function parameters) and missed
    lambda capture diagnostics for evaluated polymorphic operands.
    
    Implement a two-pass parse: first under cp_unevaluated, then - if
    typeid_evaluated_p says the operand is evaluated - roll back and
    re-parse under cp_evaluated.  Share the evaluated/unevaluated predicate
    via typeid_evaluated_p in rtti.cc, used from the parser, tsubst_expr,
    and build_typeid.
    
    Also fixes PR c++/68604 and PR c++/116385, and removes a now-stale xfail
    in g++.dg/coroutines/unevaluated.C.
    
            PR c++/125886
            PR c++/116385
            PR c++/68604
    
    gcc/cp/ChangeLog:
            * parser.cc (cp_parser_postfix_expression): Two-pass typeid parse.
            * pt.cc (tsubst_expr): Same for TYPEID_EXPR.
            * rtti.cc (typeid_evaluated_p, build_typeid): Shared predicate.
            * cp-tree.h: Declare typeid_evaluated_p.
    
    gcc/testsuite/ChangeLog:
            * g++.dg/cpp0x/pr125886.C: New test.
            * g++.dg/rtti/typeid14.C, g++.dg/rtti/typeid15.C: New tests.
            * g++.dg/coroutines/unevaluated.C: Drop stale xfail.
    
    Signed-off-by: Vladislav Semykin <[email protected]>

Diff:
---
 gcc/cp/cp-tree.h                              |  1 +
 gcc/cp/parser.cc                              | 31 +++++++++-
 gcc/cp/pt.cc                                  | 21 ++++++-
 gcc/cp/rtti.cc                                | 48 ++++++++++++----
 gcc/testsuite/g++.dg/coroutines/unevaluated.C |  3 +-
 gcc/testsuite/g++.dg/cpp0x/pr125886.C         | 82 +++++++++++++++++++++++++++
 gcc/testsuite/g++.dg/rtti/typeid14.C          | 19 +++++++
 gcc/testsuite/g++.dg/rtti/typeid15.C          | 23 ++++++++
 8 files changed, 210 insertions(+), 18 deletions(-)

diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h
index a0fa19a9c37d..a3be24b4fb54 100644
--- a/gcc/cp/cp-tree.h
+++ b/gcc/cp/cp-tree.h
@@ -7486,6 +7486,7 @@ extern tree current_nonlambda_class_type  (void);
 extern tree finish_struct                      (tree, tree);
 extern void finish_struct_1                    (tree);
 extern int resolves_to_fixed_type_p            (tree, int * = NULL);
+extern bool typeid_evaluated_p (tree);
 extern void init_class_processing              (void);
 extern int is_empty_class                      (tree);
 extern bool is_really_empty_class              (tree, bool);
diff --git a/gcc/cp/parser.cc b/gcc/cp/parser.cc
index aeff45353372..b57acc6cba59 100644
--- a/gcc/cp/parser.cc
+++ b/gcc/cp/parser.cc
@@ -292,7 +292,8 @@ static void missing_template_diag
 static FILE *cp_lexer_debug_stream;
 
 /* Nonzero if we are parsing an unevaluated operand: an operand to
-   sizeof, typeof, or alignof.  */
+   sizeof, typeof, or alignof.  This is a count since operands to
+   sizeof can be nested.  */
 int cp_unevaluated_operand;
 
 /* Nonzero if we are parsing a reflect-expression and shouldn't strip
@@ -8570,9 +8571,33 @@ cp_parser_postfix_expression (cp_parser *parser, bool 
address_p, bool cast_p,
        else
          {
            tree expression;
+           /* [expr.typeid]/4-5: parse the operand unevaluated first; if it is
+              a polymorphic glvalue, roll back and re-parse it evaluated,
+              since an evaluated parse has irreversible side-effects
+              (mark_used -> instantiation; lambda capture).  */
+           cp_lexer_save_tokens (parser->lexer);
+           {
+             cp_unevaluated u;
+             expression = cp_parser_expression (parser, &idk);
+           }
+           /* If we're already within an unevaluated operand, everything
+              in the subtree stays not potentially evaluated regardless
+              of [expr.typeid]/4 ([basic.def.odr]/3), so the evaluated
+              re-parse below can have nothing to do; skip it.  */
+           if (expression != error_mark_node
+               && processing_template_decl == 0
+               && !cp_unevaluated_operand
+               && typeid_evaluated_p (expression))
+             {
+               /* Re-parse the operand evaluated so the /4 side-effects occur.
+                  The unevaluated pass above called no mark_used and captured
+                  nothing, so rolling back has nothing to undo.  */
+               cp_lexer_rollback_tokens (parser->lexer);
+               expression = cp_parser_expression (parser, &idk);
+             }
+           else
+             cp_lexer_commit_tokens (parser->lexer);
 
-           /* Look for an expression.  */
-           expression = cp_parser_expression (parser, & idk);
            /* Compute its typeid.  */
            postfix_expression = build_typeid (expression, tf_warning_or_error);
            /* Look for the `)' token.  */
diff --git a/gcc/cp/pt.cc b/gcc/cp/pt.cc
index 458ba7efae1f..3c110d7fa1c0 100644
--- a/gcc/cp/pt.cc
+++ b/gcc/cp/pt.cc
@@ -23011,8 +23011,25 @@ tsubst_expr (tree t, tree args, tsubst_flags_t 
complain, tree in_decl)
          }
        else
          {
-           operand_0 = RECUR (operand_0);
-           RETURN (build_typeid (operand_0, complain));
+           /* [expr.typeid]/4-5: substitute the operand unevaluated first, then
+              again evaluated if it is a polymorphic glvalue, so the /4
+              side-effects occur.  The unevaluated pass instantiates nothing,
+              so re-substituting has nothing to undo (PR c++/125886).  */
+           tree operand;
+           tree uneval;
+           {
+             cp_unevaluated u;
+             uneval = RECUR (operand_0);
+           }
+           /* If we're already within an unevaluated operand, everything
+              in the subtree stays not potentially evaluated regardless
+              of [expr.typeid]/4 ([basic.def.odr]/3), so the evaluated
+              re-parse below can have nothing to do; skip it.  */
+           if (!cp_unevaluated_operand && typeid_evaluated_p (uneval))
+             operand = RECUR (operand_0);
+           else
+             operand = uneval;
+           RETURN (build_typeid (operand, complain));
          }
       }
 
diff --git a/gcc/cp/rtti.cc b/gcc/cp/rtti.cc
index 7e6fa51936ab..48e4dfde458c 100644
--- a/gcc/cp/rtti.cc
+++ b/gcc/cp/rtti.cc
@@ -340,6 +340,32 @@ typeid_ok_p (void)
   return true;
 }
 
+/* True if EXP is a glvalue expression of polymorphic class type whose
+   dynamic type is not known statically, so that typeid (EXP) must be
+   evaluated per ([expr.typeid]/4).  */
+
+bool
+typeid_evaluated_p (tree exp)
+{
+  if (exp == error_mark_node)
+    return false;
+  tree t = TREE_TYPE (exp);
+  if (!t || t == error_mark_node)
+    return false;
+  if (TYPE_REF_P (t))
+    t = TREE_TYPE (t);
+  if (TREE_CODE (t) != RECORD_TYPE && TREE_CODE (t) != UNION_TYPE)
+    return false;
+  int nonnull = 0;
+  return (TYPE_POLYMORPHIC_P (t)
+         && !resolves_to_fixed_type_p (exp, &nonnull)
+         /* Only a glvalue operand is evaluated ([expr.typeid]/4).
+            The following check is only necessary because
+            resolves_to_fixed_type_p does not handle all
+            prvalue cases such as COMPOUND_EXPR.  */
+         && glvalue_p (exp));
+}
+
 /* Return an expression for "typeid(EXP)".  The expression returned is
    an lvalue of type "const std::type_info".  */
 
@@ -347,7 +373,6 @@ tree
 build_typeid (tree exp, tsubst_flags_t complain)
 {
   tree cond = NULL_TREE, initial_expr = exp;
-  int nonnull = 0;
 
   if (exp == error_mark_node || !typeid_ok_p ())
     return error_mark_node;
@@ -355,17 +380,18 @@ build_typeid (tree exp, tsubst_flags_t complain)
   if (processing_template_decl)
     return build_min (TYPEID_EXPR, const_type_info_type_node, exp);
 
-  if (CLASS_TYPE_P (TREE_TYPE (exp))
-      && TYPE_POLYMORPHIC_P (TREE_TYPE (exp))
-      && ! resolves_to_fixed_type_p (exp, &nonnull)
-      && ! nonnull)
+  if (typeid_evaluated_p (exp))
     {
-      /* So we need to look into the vtable of the type of exp.
-         Make sure it isn't a null lvalue.  */
-      exp = cp_build_addr_expr (exp, complain);
-      exp = save_expr (exp);
-      cond = cp_convert (boolean_type_node, exp, complain);
-      exp = cp_build_fold_indirect_ref (exp);
+      int nonnull = 0;
+      resolves_to_fixed_type_p (exp, &nonnull);
+      if (!nonnull)
+       {
+         /* Make sure it isn't a null lvalue; evaluate it once.  */
+         exp = cp_build_addr_expr (exp, complain);
+         exp = save_expr (exp);
+         cond = cp_convert (boolean_type_node, exp, complain);
+         exp = cp_build_fold_indirect_ref (exp);
+       }
     }
 
   exp = get_tinfo_ptr_dynamic (exp, complain);
diff --git a/gcc/testsuite/g++.dg/coroutines/unevaluated.C 
b/gcc/testsuite/g++.dg/coroutines/unevaluated.C
index 63dae38dea39..f763b208cc9b 100644
--- a/gcc/testsuite/g++.dg/coroutines/unevaluated.C
+++ b/gcc/testsuite/g++.dg/coroutines/unevaluated.C
@@ -17,8 +17,7 @@ struct Task {
 // We do not permit co_await, co_yield outside a function, and so uses in
 // noexcept or requirements are covered by that.
 Task foo()  {
-    /* This one will currently fail - see PR68604.  */
-    const std::type_info& ti1 = typeid (co_await std::suspend_never{}); // { 
dg-error {'co_await' cannot be used in an unevaluated context} "" { xfail *-*-* 
} }
+    const std::type_info& ti1 = typeid (co_await std::suspend_never{}); // { 
dg-error {'co_await' cannot be used in an unevaluated context} }
     std::size_t x = sizeof (co_yield (19)); // { dg-error {'co_yield' cannot 
be used in an unevaluated context} }
     decltype (co_await std::suspend_never{}) A; // { dg-error {'co_await' 
cannot be used in an unevaluated context} }
     co_return;
diff --git a/gcc/testsuite/g++.dg/cpp0x/pr125886.C 
b/gcc/testsuite/g++.dg/cpp0x/pr125886.C
new file mode 100644
index 000000000000..96a7ec3cff12
--- /dev/null
+++ b/gcc/testsuite/g++.dg/cpp0x/pr125886.C
@@ -0,0 +1,82 @@
+// PR c++/125886
+// PR c++/68604
+// PR c++/116385
+// { dg-do compile { target c++11 } }
+
+#include <typeinfo>
+#include <utility>
+
+// PR c++/125886: a non-polymorphic operand is an unevaluated operand
+// ([expr.typeid]/5), so std::declval<A>() is not instantiated and its
+// static_assert does not fire.
+struct A {};
+void
+non_poly_declval ()
+{
+  (void) typeid (std::declval<A> ());
+}
+
+// PR c++/125886 (template): the evaluated/unevaluated decision is taken
+// at instantiation time in tsubst; a non-polymorphic operand is
+// unevaluated, so tmpl<A> is well-formed.
+template <typename T>
+void
+tmpl ()
+{
+  (void) typeid (std::declval<T> ());
+}
+
+template void tmpl<A> ();
+
+// PR c++/68604: an id-expression denoting a non-static data member is
+// valid in an unevaluated operand (DR613 / N2253, C++11+).
+struct C { int i; };
+void
+nsm ()
+{
+  (void) typeid (C::i);
+}
+
+// PR c++/116385: function parameters are not odr-used in an unevaluated
+// typeid operand, so they need not be captured and may appear in local
+// classes and default arguments.
+void
+params (int n)
+{
+  [&] { (void) typeid (n); };
+  struct Local { void g () { (void) typeid (n); } };
+  void g (const std::type_info& = typeid (n));
+}
+
+// A final-class polymorphic glvalue resolves to a fixed (static) type,
+// so typeid is unevaluated: no vtable lookup, no odr-use.
+struct F final { virtual ~F (); };
+void
+final_glvalue (F& f)
+{
+  (void) typeid (f);
+}
+
+// Polymorphic glvalue in lambda without capture-default must be captured,
+// since typeid is evaluated.
+struct B { virtual ~B (); };
+void
+lambda_poly_capture (B& b)
+{
+  [] { (void) typeid (b); };  // { dg-error "not captured" }
+}
+
+// typeid itself inside an unevaluated operand: the polymorphic glvalue
+// re-parse must not force evaluation here.
+void
+nested_unevaluated(B &b)
+{
+  (void) sizeof (typeid (b)); // no odr-use of b expected
+}
+
+// lambda variant of nested_unevaluated.
+void
+nested_unevaluated_lambda(B &b)
+{
+  [] { (void) sizeof (typeid (b)); }; // OK: no capture required
+}
diff --git a/gcc/testsuite/g++.dg/rtti/typeid14.C 
b/gcc/testsuite/g++.dg/rtti/typeid14.C
new file mode 100644
index 000000000000..422a81fa209b
--- /dev/null
+++ b/gcc/testsuite/g++.dg/rtti/typeid14.C
@@ -0,0 +1,19 @@
+// PR c++/125886
+// { dg-do compile { target c++11 } }
+
+#include <typeinfo>
+#include <utility>
+
+// [expr.typeid]/4: a glvalue expression of a polymorphic class type is
+// evaluated, so std::declval<B>() is instantiated and
+// __declval_protector's static_assert fires (the non-template case:
+// the operand is parsed evaluated after the unevaluated probe).
+
+struct B { virtual ~B(); };
+
+void
+non_template ()
+{
+  (void) typeid (std::declval<B> ());
+}
+// { dg-error "static assertion failed: declval" "" { target *-*-* } 0 }
diff --git a/gcc/testsuite/g++.dg/rtti/typeid15.C 
b/gcc/testsuite/g++.dg/rtti/typeid15.C
new file mode 100644
index 000000000000..76478bed3ef6
--- /dev/null
+++ b/gcc/testsuite/g++.dg/rtti/typeid15.C
@@ -0,0 +1,23 @@
+// PR c++/125886
+// { dg-do compile { target c++11 } }
+
+#include <typeinfo>
+#include <utility>
+
+// [expr.typeid]/4 for a template-dependent operand: the evaluated/
+// unevaluated decision is taken at instantiation time in tsubst.  For
+// a polymorphic operand the operand is re-substituted evaluated, so
+// std::declval<C>() is instantiated and __declval_protector's
+// static_assert fires.
+
+struct C { virtual ~C(); };
+
+template <typename T>
+void
+tmpl ()
+{
+  (void) typeid (std::declval<T> ());
+}
+
+template void tmpl<C> ();
+// { dg-error "static assertion failed: declval" "" { target *-*-* } 0 }

Reply via email to