https://gcc.gnu.org/bugzilla/show_bug.cgi?id=127833
Bug ID: 127833
Summary: Null `typeid` on a final polymorphic glvalue
Product: gcc
Version: 17.0
Status: UNCONFIRMED
Severity: normal
Priority: P3
Component: c++
Assignee: unassigned at gcc dot gnu.org
Reporter: vladislav.semykin at gmail dot com
Target Milestone: ---
typeid(*p) does not throw std::bad_typeid when p is a null pointer
to a final polymorphic class. The same program throws on Clang 22.1 and
on MSVC 19.51. GCC 13.3.0 already returns without throwing, so this is not
a GCC 17 regression and not a consequence of the two-pass typeid parse in
r17-2200-g621661f7d2e9 (PR c++/125886).
#include <cstdio>
#include <typeinfo>
struct F final { virtual ~F(); };
int main() {
F* p = nullptr;
try {
(void) typeid(*p);
} catch (const std::bad_typeid&) {
std::puts("threw");
return 0;
}
std::puts("no throw");
return 1;
}
Observed before this report was split out of PR c++/127750:
- g++ 13.3.0 prints no throw and exits 1.
- clang++ 22.1.8 calls __cxa_bad_typeid and aborts.
- MSVC 19.51 throws via __RTtypeid.
- Godbolt: https://godbolt.org/z/6oG4ce8T4
[expr.typeid]/3 (https://eel.is/c++draft/expr.typeid#3): if the glvalue
is obtained by applying unary * to a pointer and that pointer is a
null pointer value, typeid throws std::bad_typeid.
[expr.typeid]/4 (https://eel.is/c++draft/expr.typeid#4): a glvalue of
polymorphic class type is potentially evaluated, and the result is the
std::type_info of the most derived object.
[class.virtual]/1 (https://eel.is/c++draft/class.virtual#1): a class
with a virtual function is polymorphic. final is not an exception.
It only means the dynamic type of a live object equals the static type.
On trunk the null check is skipped because the type is final:
- resolves_to_fixed_type_p (gcc/cp/class.cc) returns 1 as soon as
CLASSTYPE_FINAL is set. For *p with p a parameter,
fixed_type_or_null does not set *nonnull.
- typeid_evaluated_p (gcc/cp/rtti.cc) is then false.
- build_typeid emits throw_bad_typeid only when
typeid_evaluated_p is true and the glvalue is not known non-null.
Returning the static type_info for a live final object is a valid
as-if choice: the most derived type is the static type, so the vtable
need not be loaded. Skipping the throw is not. The null check has to
be emitted even when the dynamic type is known statically.
Please keep this separate from two neighboring issues:
- PR c++/127750: the unevaluated probe consumes a lambda (mangling
discriminator, and the pre-C++20 "lambda in unevaluated context"
diagnostic) when [expr.typeid]/4 does evaluate the operand. The fix
there restores discriminator state around the probe. It does not
change resolves_to_fixed_type_p or build_typeid.
- A final polymorphic glvalue is also treated as an unevaluated
operand (typeid(std::declval<F>()) is accepted, and
[] { typeid(r); } does not diagnose a missing capture). That is a
different bug: the operand is not potentially evaluated. The null
check above is required even if that model stays. I am filing it
as its own report and will add the number to See Also.
Please assign this report to me.