diff --git a/libcxxabi/src/private_typeinfo.h b/libcxxabi/src/private_typeinfo.h --- a/libcxxabi/src/private_typeinfo.h +++ b/libcxxabi/src/private_typeinfo.h @@ -110,6 +110,11 @@ bool found_any_static_type; // Set whenever a search can be stopped bool search_done; + + // Data that modifies the search mechanism. + + // There is no object (seen when we throw a null pointer to object). + bool have_object; }; // Has no base class @@ -122,8 +127,7 @@ const void *, int) const; _LIBCXXABI_HIDDEN void process_static_type_below_dst(__dynamic_cast_info *, const void *, int) const; - _LIBCXXABI_HIDDEN void process_found_base_class(__dynamic_cast_info *, void *, - int) const; + _LIBCXXABI_HIDDEN void process_found_base_class(__dynamic_cast_info*, void*, const void*, int) const; _LIBCXXABI_HIDDEN virtual void search_above_dst(__dynamic_cast_info *, const void *, const void *, int, bool) const; @@ -131,8 +135,7 @@ search_below_dst(__dynamic_cast_info *, const void *, int, bool) const; _LIBCXXABI_HIDDEN virtual bool can_catch(const __shim_type_info *, void *&) const; - _LIBCXXABI_HIDDEN virtual void - has_unambiguous_public_base(__dynamic_cast_info *, void *, int) const; + _LIBCXXABI_HIDDEN virtual void has_unambiguous_public_base(__dynamic_cast_info*, void*, const void*, int) const; }; // Has one non-virtual public base class at offset zero @@ -147,8 +150,7 @@ int, bool) const; _LIBCXXABI_HIDDEN virtual void search_below_dst(__dynamic_cast_info *, const void *, int, bool) const; - _LIBCXXABI_HIDDEN virtual void - has_unambiguous_public_base(__dynamic_cast_info *, void *, int) const; + _LIBCXXABI_HIDDEN virtual void has_unambiguous_public_base(__dynamic_cast_info*, void*, const void*, int) const; }; struct _LIBCXXABI_HIDDEN __base_class_type_info @@ -166,7 +168,7 @@ void search_above_dst(__dynamic_cast_info*, const void*, const void*, int, bool) const; void search_below_dst(__dynamic_cast_info*, const void*, int, bool) const; - void has_unambiguous_public_base(__dynamic_cast_info*, void*, int) const; + void has_unambiguous_public_base(__dynamic_cast_info*, void*, const void*, int) const; }; // Has one or more base classes @@ -190,8 +192,7 @@ int, bool) const; _LIBCXXABI_HIDDEN virtual void search_below_dst(__dynamic_cast_info *, const void *, int, bool) const; - _LIBCXXABI_HIDDEN virtual void - has_unambiguous_public_base(__dynamic_cast_info *, void *, int) const; + _LIBCXXABI_HIDDEN virtual void has_unambiguous_public_base(__dynamic_cast_info*, void*, const void*, int) const; }; class _LIBCXXABI_TYPE_VIS __pbase_type_info : public __shim_type_info { diff --git a/libcxxabi/src/private_typeinfo.cpp b/libcxxabi/src/private_typeinfo.cpp --- a/libcxxabi/src/private_typeinfo.cpp +++ b/libcxxabi/src/private_typeinfo.cpp @@ -42,6 +42,7 @@ // is_equal() with use_strcmp=false so the string names are not compared. #include +#include #include #ifdef _LIBCXXABI_FORGIVING_DYNAMIC_CAST @@ -233,9 +234,11 @@ if (thrown_class_type == 0) return false; // bullet 2 - __dynamic_cast_info info = {thrown_class_type, 0, this, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; + assert (adjustedPtr && "catching a class without an object?"); + __dynamic_cast_info info = {thrown_class_type, 0, this, + -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, true}; info.number_of_dst_type = 1; - thrown_class_type->has_unambiguous_public_base(&info, adjustedPtr, public_path); + thrown_class_type->has_unambiguous_public_base(&info, adjustedPtr, nullptr, public_path); if (info.path_dst_ptr_to_static_ptr == public_path) { adjustedPtr = const_cast(info.dst_ptr_leading_to_static_ptr); @@ -248,19 +251,39 @@ #pragma clang diagnostic pop #endif +// When we have an object to inspect - we just pass the pointer to the sub- +// object that matched the static_type we just checked. If that is different +// from any previously recorded pointer to that object type, then we have +// an ambiguous case. + +// When we have no object to inspect, we need to account for virtual bases +// explicitly. +// virtualBase is the pointer to the name of the innermost virtual base +// type, or nullptr if there is no virtual base on the path so far. +// adjustedPtr points to the subobject we just found. +// If virtualBase != any previously recorded (including the case of nullptr +// representing an already-found static sub-object) then we have an ambiguous +// case. Assuming that the virtualBase values agree; if then we have a +// different offset (adjustedPtr) from any previously recorded, this indicates +// an ambiguous case within the virtual base. + void __class_type_info::process_found_base_class(__dynamic_cast_info* info, void* adjustedPtr, + const void* virtualBase, int path_below) const { - if (info->dst_ptr_leading_to_static_ptr == 0) + if (info->number_to_static_ptr == 0) { // First time here info->dst_ptr_leading_to_static_ptr = adjustedPtr; info->path_dst_ptr_to_static_ptr = path_below; + // re-purpose this pointer. + info->dst_ptr_not_leading_to_static_ptr = virtualBase; info->number_to_static_ptr = 1; } - else if (info->dst_ptr_leading_to_static_ptr == adjustedPtr) + else if (info->dst_ptr_not_leading_to_static_ptr == virtualBase && + info->dst_ptr_leading_to_static_ptr == adjustedPtr) { // We've been here before. Update path to "most public" if (info->path_dst_ptr_to_static_ptr == not_public_path) @@ -279,62 +302,83 @@ void __class_type_info::has_unambiguous_public_base(__dynamic_cast_info* info, void* adjustedPtr, + const void* virtualBase, int path_below) const { if (is_equal(this, info->static_type, false)) - process_found_base_class(info, adjustedPtr, path_below); + process_found_base_class(info, adjustedPtr, virtualBase, path_below); } void __si_class_type_info::has_unambiguous_public_base(__dynamic_cast_info* info, void* adjustedPtr, + const void* virtualBase, int path_below) const { if (is_equal(this, info->static_type, false)) - process_found_base_class(info, adjustedPtr, path_below); + process_found_base_class(info, adjustedPtr, virtualBase, path_below); else - __base_type->has_unambiguous_public_base(info, adjustedPtr, path_below); + __base_type->has_unambiguous_public_base(info, adjustedPtr, virtualBase, path_below); } void __base_class_type_info::has_unambiguous_public_base(__dynamic_cast_info* info, void* adjustedPtr, + const void* virtualBase, int path_below) const { + bool is_virtual = __offset_flags & __virtual_mask; ptrdiff_t offset_to_base = 0; - if (adjustedPtr != nullptr) + if (info->have_object) { + /* We have an object to inspect, we can look through its vtables to + find the layout. */ offset_to_base = __offset_flags >> __offset_shift; - if (__offset_flags & __virtual_mask) + if (is_virtual) { const char* vtable = *static_cast(adjustedPtr); offset_to_base = update_offset_to_base(vtable, offset_to_base); } + } else if (! is_virtual) { + /* We have no object - so we cannot use it for determining layout when + we have a virtual base (since we cannot indirect through the vtable + to find the actual object offset). However, for non-virtual bases, + we can pretend to have an object based at '0' */ + offset_to_base = __offset_flags >> __offset_shift; + } else { + // no object to inspect, and the next base is virtual. + // we want to update virtualBase to the new innermost virtual base. + // using the pointer to the typeinfo name as a key. + virtualBase = static_cast(__base_type->name ()); + // .. and reset the pointer. + adjustedPtr = nullptr; } __base_type->has_unambiguous_public_base( info, static_cast(adjustedPtr) + offset_to_base, + virtualBase, (__offset_flags & __public_mask) ? path_below : not_public_path); } void __vmi_class_type_info::has_unambiguous_public_base(__dynamic_cast_info* info, void* adjustedPtr, + const void* virtualBase, int path_below) const { if (is_equal(this, info->static_type, false)) - process_found_base_class(info, adjustedPtr, path_below); + process_found_base_class(info, adjustedPtr, virtualBase, path_below); else { typedef const __base_class_type_info* Iter; const Iter e = __base_info + __base_count; Iter p = __base_info; - p->has_unambiguous_public_base(info, adjustedPtr, path_below); + p->has_unambiguous_public_base(info, adjustedPtr, virtualBase, path_below); if (++p < e) { do { - p->has_unambiguous_public_base(info, adjustedPtr, path_below); + p->has_unambiguous_public_base(info, adjustedPtr, virtualBase, path_below); if (info->search_done) break; } while (++p < e); @@ -431,13 +475,22 @@ dynamic_cast(thrown_pointer_type->__pointee); if (thrown_class_type == 0) return false; - __dynamic_cast_info info = {thrown_class_type, 0, catch_class_type, -1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0,}; + bool have_object = adjustedPtr != nullptr; + __dynamic_cast_info info = {thrown_class_type, 0, catch_class_type, -1, + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, + have_object }; info.number_of_dst_type = 1; - thrown_class_type->has_unambiguous_public_base(&info, adjustedPtr, public_path); + thrown_class_type->has_unambiguous_public_base(&info, adjustedPtr, nullptr, public_path); if (info.path_dst_ptr_to_static_ptr == public_path) { - if (adjustedPtr != NULL) + // In the case of a thrown null pointer, we have no object but we might + // well have computed the offset to where a public sub-object would be. + // However, we do not want to return that offset to the user; we still + // want them to catch a null ptr. + if (have_object) adjustedPtr = const_cast(info.dst_ptr_leading_to_static_ptr); + else + adjustedPtr = nullptr; return true; } return false; diff --git a/libcxxabi/test/catch_null_pointer_to_object_pr64953.pass.cpp b/libcxxabi/test/catch_null_pointer_to_object_pr64953.pass.cpp new file mode 100644 --- /dev/null +++ b/libcxxabi/test/catch_null_pointer_to_object_pr64953.pass.cpp @@ -0,0 +1,182 @@ +//===----------------------------------------------------------------------===// +// +// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions. +// See https://llvm.org/LICENSE.txt for license information. +// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception +// +//===----------------------------------------------------------------------===// +// +// This test case checks specifically the cases under bullet 3.3: +// +// C++ ABI 15.3: +// A handler is a match for an exception object of type E if +// * The handler is of type cv T or cv T& and E and T are the same type +// (ignoring the top-level cv-qualifiers), or +// * the handler is of type cv T or cv T& and T is an unambiguous base +// class of E, or +// > * the handler is of type cv1 T* cv2 and E is a pointer type that can < +// > be converted to the type of the handler by either or both of < +// > o a standard pointer conversion (4.10 [conv.ptr]) not involving < +// > conversions to private or protected or ambiguous classes < +// > o a qualification conversion < +// * the handler is a pointer or pointer to member type and E is +// std::nullptr_t +// +//===----------------------------------------------------------------------===// + +// UNSUPPORTED: no-exceptions + +#include +#include +#include +#include + +struct Base { + int b; +}; +struct Base2 { + int b; +}; +struct Derived1 : Base { + int b; +}; +struct Derived2 : Base { + int b; +}; +struct Derived3 : Base2 { + int b; +}; +struct Private : private Base { + int b; +}; +struct Protected : protected Base { + int b; +}; +struct Virtual1 : virtual Base { + int b; +}; +struct Virtual2 : virtual Base { + int b; +}; + +struct Ambiguous1 : Derived1, Derived2 { + int b; +}; +struct Ambiguous2 : Derived1, Private { + int b; +}; +struct Ambiguous3 : Derived1, Protected { + int b; +}; + +struct NoPublic1 : Private, Base2 { + int b; +}; +struct NoPublic2 : Protected, Base2 { + int b; +}; + +struct Catchable1 : Derived3, Derived1 { + int b; +}; +struct Catchable2 : Virtual1, Virtual2 { + int b; +}; +struct Catchable3 : virtual Base, Virtual2 { + int b; +}; + +// Check that, when we have a null pointer-to-object that we catch a nullptr. +template +void assert_catches() { + try { + throw static_cast(0); + printf("%s\n", __PRETTY_FUNCTION__); + assert(false && "Statements after throw must be unreachable"); + } catch (T t) { + assert(t == nullptr); + return; + } catch (...) { + printf("%s\n", __PRETTY_FUNCTION__); + assert(false && "Should not have entered catch-all"); + } + + printf("%s\n", __PRETTY_FUNCTION__); + assert(false && "The catch should have returned"); +} + +template +void assert_cannot_catch() { + try { + throw static_cast(0); + printf("%s\n", __PRETTY_FUNCTION__); + assert(false && "Statements after throw must be unreachable"); + } catch (T t) { + printf("%s\n", __PRETTY_FUNCTION__); + assert(false && "Should not have entered the catch"); + } catch (...) { + assert(true); + return; + } + + printf("%s\n", __PRETTY_FUNCTION__); + assert(false && "The catch-all should have returned"); +} + +// Check that when we have a pointer-to-actual-object we, in fact, get the +// adjusted pointer to the base class. +template +void assert_catches_bp() { + O* o = new (O); + try { + throw o; + printf("%s\n", __PRETTY_FUNCTION__); + assert(false && "Statements after throw must be unreachable"); + } catch (T t) { + assert(t == static_cast(o)); + //__builtin_printf("o = %p t = %p\n", o, t); + return; + } catch (...) { + printf("%s\n", __PRETTY_FUNCTION__); + assert(false && "Should not have entered catch-all"); + } + + printf("%s\n", __PRETTY_FUNCTION__); + assert(false && "The catch should have returned"); +} + +void f1() { + assert_catches(); + assert_catches(); + assert_catches(); +} + +void f2() { + assert_cannot_catch(); + assert_cannot_catch(); + assert_cannot_catch(); + assert_cannot_catch(); + assert_cannot_catch(); +} + +void f3() { + assert_catches_bp(); + assert_catches_bp(); + assert_catches_bp(); +} + +int main(int, char**) { + f1(); + f2(); + f3(); + return 0; +}