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.
  • [Bug c++/127833] New: Null... vladislav.semykin at gmail dot com via Gcc-bugs

Reply via email to