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 }
