Good day! My mistake was that I continued talking in the same previous email thread, sorry about that.
Changes since v2: - Apply the same two-pass (unevaluated-then-evaluated) logic to template-dependent operands in tsubst_expr (TYPEID_EXPR), as Jason requested. - Fix lambda capture: a polymorphic glvalue typeid operand inside a lambda without a capture-default is now correctly diagnosed as "not captured". Root cause was typeid_evaluated_p rejecting reference types and being gated on the "nonnull" flag, which is unrelated to the evaluated/unevaluated decision. - Factor the polymorphic-glvalue predicate out into a shared typeid_evaluated_p helper used by the parser, tsubst, and build_typeid (Jason's request - avoids the checks drifting out of sync). - Drop the cxx_dialect >= cxx11 gate: per Jason, DR613 applies in all modes, since finish_non_static_data_member doesn't check cxx_dialect either. - Also fixes PR c++/68604 and PR c++/116385, and removes a now-stale xfail in g++.dg/coroutines/unevaluated.C (PR c++/68604). Tested full CI on: x86_64 GNU/Linux 6.17.0-35-generic Ubuntu 24.04. FYI, these tests are broken before my changes (the full test log via 'make -C gcc -k check-c++-all' attached too): FAIL: c-c++-common/analyzer/flex-without-call-summaries.c -std=c++11 at line 885 (test for warnings, line 884) XPASS: c-c++-common/analyzer/flex-without-call-summaries.c -std=c++11 PR analyzer/103546 (test for bogus messages, line 892) FAIL: c-c++-common/analyzer/flex-without-call-summaries.c -std=c++11 (test for excess errors) FAIL: g++.dg/guality/pr55665.C -O2 -flto -fno-use-linker-plugin -flto-partition=none line 23 p == 40 FAIL: g++.dg/modules/compile-std1.C -std=c++26 -fimplicit-constexpr (test for excess errors) FAIL: g++.dg/modules/compile-std1.C -std=c++26 -fimplicit-constexpr module-cmi std (gcm.cache/std.gcm) FAIL: g++.dg/modules/compile-std1.C -std=c++26 -fimplicit-constexpr module-cmi std.compat (gcm.cache/std.compat.gcm) FAIL: g++.dg/modules/xtreme-header-8.C -std=c++26 -fimplicit-constexpr (test for excess errors) FAIL: g++.dg/modules/xtreme-header-2_a.H -std=c++26 -fimplicit-constexpr (test for excess errors) FAIL: g++.dg/modules/xtreme-header-2_c.C -std=c++26 -fimplicit-constexpr (test for excess errors) FAIL: g++.dg/modules/xtreme-header-7_a.H -std=c++26 -fimplicit-constexpr (test for excess errors) FAIL: g++.dg/modules/xtreme-header_a.H -std=c++26 -fimplicit-constexpr (test for excess errors) FAIL: g++.dg/plugin/std-module-exports-c++20.C -fplugin=./std_module_exports_plugin.so (test for excess errors) FAIL: g++.dg/plugin/std-module-exports-c++23.C -fplugin=./std_module_exports_plugin.so (test for excess errors) FAIL: g++.dg/plugin/std-module-exports-c++26.C -fplugin=./std_module_exports_plugin.so (test for excess errors) === g++ Summary === # of expected passes 598206 # of unexpected failures 14 # of unexpected successes 1 # of expected failures 5989 # of unresolved testcases 2 # of unsupported tests 21885 /home/loveit0/Develop/gcc-build/gcc/xg++ version 17.0.0 20260624 (experimental) (GCC)
From acc669f9364264ecf93f1341e7037e916a798c6b Mon Sep 17 00:00:00 2001 From: Vladislav Semykin <[email protected]> Date: Tue, 30 Jun 2026 19:13:14 +0300 Subject: [PATCH] c++: fix unevaluated operand context for typeid [PR125886] Signed-off-by: Vladislav Semykin <[email protected]> --- gcc/cp/cp-tree.h | 4 +- gcc/cp/parser.cc | 29 +++++++- gcc/cp/pt.cc | 17 ++++- gcc/cp/rtti.cc | 44 +++++++++--- gcc/testsuite/g++.dg/coroutines/unevaluated.C | 3 +- gcc/testsuite/g++.dg/cpp0x/pr125886.C | 67 +++++++++++++++++++ gcc/testsuite/g++.dg/rtti/typeid14.C | 19 ++++++ gcc/testsuite/g++.dg/rtti/typeid15.C | 23 +++++++ 8 files changed, 187 insertions(+), 19 deletions(-) create mode 100644 gcc/testsuite/g++.dg/cpp0x/pr125886.C create mode 100644 gcc/testsuite/g++.dg/rtti/typeid14.C create mode 100644 gcc/testsuite/g++.dg/rtti/typeid15.C diff --git a/gcc/cp/cp-tree.h b/gcc/cp/cp-tree.h index 132139f9d..090d3aee8 100644 --- a/gcc/cp/cp-tree.h +++ b/gcc/cp/cp-tree.h @@ -6340,7 +6340,8 @@ extern bool cp_preserve_using_decl; /* Nonzero if we are parsing an unevaluated operand: an operand to sizeof, typeof, or alignof. This is a count since operands to - sizeof can be nested. */ + sizeof can be nested. For typeid the operand is processed + unevaluated, then re-processed evaluated if polymorphic ([expr.typeid]/4-5). */ extern int cp_unevaluated_operand; @@ -7486,6 +7487,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 8194106c6..06514bd7e 100644 --- a/gcc/cp/parser.cc +++ b/gcc/cp/parser.cc @@ -292,7 +292,9 @@ 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. For typeid the operand is processed + unevaluated, then re-processed evaluated if polymorphic ([expr.typeid]/4-5). */ int cp_unevaluated_operand; /* Nonzero if we are parsing a reflect-expression and shouldn't strip @@ -8570,9 +8572,30 @@ 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_operand; + ++c_inhibit_evaluation_warnings; + expression = cp_parser_expression (parser, &idk); + --c_inhibit_evaluation_warnings; + --cp_unevaluated_operand; + + if (expression != error_mark_node + && processing_template_decl == 0 + && 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 f7aa10226..40ec4d800 100644 --- a/gcc/cp/pt.cc +++ b/gcc/cp/pt.cc @@ -23003,8 +23003,21 @@ 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 (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 7e6fa5193..9d3b65fe3 100644 --- a/gcc/cp/rtti.cc +++ b/gcc/cp/rtti.cc @@ -340,6 +340,28 @@ 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) + && glvalue_p (exp)); +} + /* Return an expression for "typeid(EXP)". The expression returned is an lvalue of type "const std::type_info". */ @@ -347,7 +369,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 +376,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 63dae38de..f763b208c 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 000000000..cc494d5c5 --- /dev/null +++ b/gcc/testsuite/g++.dg/cpp0x/pr125886.C @@ -0,0 +1,67 @@ +// 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" } +} diff --git a/gcc/testsuite/g++.dg/rtti/typeid14.C b/gcc/testsuite/g++.dg/rtti/typeid14.C new file mode 100644 index 000000000..422a81fa2 --- /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 000000000..76478bed3 --- /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 } -- 2.43.0
check-c++-all-pr125886-patchv2.log
Description: Binary data
