A while back a customer complained about this program crashing, which I
explained was because the behavior is undefined; it crashes specifically
because when we try to do the dynamic_cast the F vptr is pointing to a
construction vtable for E-in-F which doesn't have a vbase offset entry
for C, so when dynamic_cast goes looking for it it instead loads some
random value from whatever happens to be just before that vtable. But
while looking at Jakub's -fsanitize=vptr work, it occurred to me that it
would be easy and pretty cheap to catch this situation in dynamic_cast:
if the whole object disagrees with the original subobject about what
type it is, just fail.
Jakub, I think ubsan could use the same approach to check the argument.
Tested x86_64-pc-linux-gnu, applying to trunk.
commit e5f1ca3b03c352380dda95474e06f525ff9849be
Author: Jason Merrill <ja...@redhat.com>
Date: Wed Sep 17 14:47:20 2014 -0400
* libsupc++/dyncast.cc (__dynamic_cast): Handle mid-destruction
dynamic_cast more gracefully.
diff --git a/gcc/testsuite/g++.dg/rtti/dyncast7.C b/gcc/testsuite/g++.dg/rtti/dyncast7.C
new file mode 100644
index 0000000..deb4397
--- /dev/null
+++ b/gcc/testsuite/g++.dg/rtti/dyncast7.C
@@ -0,0 +1,28 @@
+// I think this dynamic_cast has undefined behavior when destroying E::o
+// because we're the F period of destruction has started and ap doesn't
+// point to the object currently being destroyed--but the reasonable
+// options are success or failure, not SEGV.
+
+// { dg-do run }
+
+extern "C" void abort();
+
+struct A { virtual ~A(); };
+struct B { virtual ~B() { } };
+struct C : B, A { };
+struct E : virtual B { A o; };
+struct F : virtual C, virtual E { };
+
+A* ap;
+C* cp;
+
+A::~A() {
+ C* cp2 = dynamic_cast<C*>(ap);
+ if (cp2 != cp && cp2 != 0)
+ abort();
+}
+
+int main() {
+ F f;
+ ap = cp = &f;
+}
diff --git a/libstdc++-v3/libsupc++/dyncast.cc b/libstdc++-v3/libsupc++/dyncast.cc
index 2bcb7dd..9f6adef 100644
--- a/libstdc++-v3/libsupc++/dyncast.cc
+++ b/libstdc++-v3/libsupc++/dyncast.cc
@@ -55,6 +55,18 @@ __dynamic_cast (const void *src_ptr, // object started from
adjust_pointer <void> (src_ptr, prefix->whole_object);
const __class_type_info *whole_type = prefix->whole_type;
__class_type_info::__dyncast_result result;
+
+ // If the whole object vptr doesn't refer to the whole object type, we're
+ // in the middle of constructing a primary base, and src is a separate
+ // base. This has undefined behavior and we can't find anything outside
+ // of the base we're actually constructing, so fail now rather than
+ // segfault later trying to use a vbase offset that doesn't exist.
+ const void *whole_vtable = *static_cast <const void *const *> (whole_ptr);
+ const vtable_prefix *whole_prefix =
+ adjust_pointer <vtable_prefix> (whole_vtable,
+ -offsetof (vtable_prefix, origin));
+ if (whole_prefix->whole_type != whole_type)
+ return NULL;
whole_type->__do_dyncast (src2dst, __class_type_info::__contained_public,
dst_type, whole_ptr, src_type, src_ptr, result);