Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[libc++abi] Handle catch null pointer-to-object #68076

Merged
merged 2 commits into from
Jan 8, 2024

Conversation

iains
Copy link
Contributor

@iains iains commented Oct 3, 2023

This addresses cases (currently failing) where we throw a null pointer-to- object and is a proposed fix for
#64953

We are trying to satisfy the following bullet from the C++ ABI 15.3:

  • 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 existing implementation assesses the ambiguity of bases by computing the offsets to them; ambiguous cases are then when the same base appears at different offsets. The computation of offset includes indirecting through the vtables to find the offsets to virtual bases.

When the thrown pointer points to a real object, this is quite efficient since, if the base is found, and it is not ambiguous and on a public path, the offset is needed to return the adjusted pointer (and the indirections are not particularly expensive to compute).

However, when we throw a null pointer-to-object, this scheme is no longer applicable (and the code currently bypasses the relevant computations, leading to the incorrect catches reported in the issue).


The solution proposed here takes a composite approach:

  1. When the pointer-to-object points to a real instance (well, at least, it is determined to be non-null), we use the existing scheme.

  2. When the pointer-to-object is null:

  • We note that there is no real object.
  • When we are processing non-virtual bases, we continue to compute the offsets, but for a notional dummy object based at 0. This is OK, since we never need to access the object content for non-virtual bases.
  • When we are processing a path with one or more virtual bases, we remember a cookie corresponding to the inner-most virtual base found so far (and set the notional offset to 0). Offsets to inner non-virtual bases are then computed as normal.

A base is then ambiguous iff:

  • There is a recorded virtual base cookie and that is different from the current one or,
  • The non-virtual base offsets differ.

When a handler for a pointer succeeds in catching a base pointer for a thrown null pointer-to-object, we still return a nullptr (so the adjustment to the pointer is not required and need not be computed).

Since we noted that there was no object when starting the search for ambiguous bases, we know that we can skip the pointer adjustment.

This was : Differential Revision: https://reviews.llvm.org/D158769

@iains iains requested a review from a team as a code owner October 3, 2023 08:25
@llvmbot llvmbot added the libc++abi libc++abi C++ Runtime Library. Not libc++. label Oct 3, 2023
@github-actions
Copy link

github-actions bot commented Oct 3, 2023

✅ With the latest revision this PR passed the C/C++ code formatter.

@llvmbot
Copy link
Collaborator

llvmbot commented Oct 3, 2023

@llvm/pr-subscribers-libcxxabi

Changes

This addresses cases (currently failing) where we throw a null pointer-to- object and is a proposed fix for
#64953

We are trying to satisfy the following bullet from the C++ ABI 15.3:

  • 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 existing implementation assesses the ambiguity of bases by computing the offsets to them; ambiguous cases are then when the same base appears at different offsets. The computation of offset includes indirecting through the vtables to find the offsets to virtual bases.

When the thrown pointer points to a real object, this is quite efficient since, if the base is found, and it is not ambiguous and on a public path, the offset is needed to return the adjusted pointer (and the indirections are not particularly expensive to compute).

However, when we throw a null pointer-to-object, this scheme is no longer applicable (and the code currently bypasses the relevant computations, leading to the incorrect catches reported in the issue).


The solution proposed here takes a composite approach:

  1. When the pointer-to-object points to a real instance (well, at least, it is determined to be non-null), we use the existing scheme.

  2. When the pointer-to-object is null:

  • We note that there is no real object.
  • When we are processing non-virtual bases, we continue to compute the offsets, but for a notional dummy object based at 0. This is OK, since we never need to access the object content for non-virtual bases.
  • When we are processing a path with one or more virtual bases, we remember a cookie corresponding to the inner-most virtual base found so far (and set the notional offset to 0). Offsets to inner non-virtual bases are then computed as normal.

A base is then ambiguous iff:

  • There is a recorded virtual base cookie and that is different from the current one or,
  • The non-virtual base offsets differ.

When a handler for a pointer succeeds in catching a base pointer for a thrown null pointer-to-object, we still return a nullptr (so the adjustment to the pointer is not required and need not be computed).

Since we noted that there was no object when starting the search for ambiguous bases, we know that we can skip the pointer adjustment.

This was : Differential Revision: https://reviews.llvm.org/D158769


Full diff: https://github.com/llvm/llvm-project/pull/68076.diff

3 Files Affected:

  • (modified) libcxxabi/src/private_typeinfo.cpp (+73-20)
  • (modified) libcxxabi/src/private_typeinfo.h (+10-9)
  • (added) libcxxabi/test/catch_null_pointer_to_object_pr64953.pass.cpp (+182)
diff --git a/libcxxabi/src/private_typeinfo.cpp b/libcxxabi/src/private_typeinfo.cpp
index 82db4bbec1ada2e..197ea2ab8bccc25 100644
--- 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 <cstdint>
+#include <cassert>
 #include <string.h>
 
 #ifdef _LIBCXXABI_FORGIVING_DYNAMIC_CAST
@@ -167,7 +168,7 @@ const void* dyn_cast_to_derived(const void* static_ptr,
         src2dst_offset,
         0, 0, 0, 0, 0, 0, 0, 0,
         1,  // number_of_dst_type
-        false, false, false
+        false, false, false, true
     };
     // Do the  search
     dst_type->search_above_dst(&info, dynamic_ptr, dynamic_ptr, public_path, false);
@@ -192,7 +193,7 @@ const void* dyn_cast_to_derived(const void* static_ptr,
             static_ptr,
             static_type,
             src2dst_offset,
-            0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false
+            0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, true
         };
         info.number_of_dst_type = 1;
         dst_type->search_above_dst(&info, dynamic_ptr, dynamic_ptr, public_path, true);
@@ -239,7 +240,7 @@ const void* dyn_cast_try_downcast(const void* static_ptr,
         src2dst_offset,
         0, 0, 0, 0, 0, 0, 0, 0,
         1,  // number_of_dst_type
-        false, false, false
+        false, false, false, true
     };
     dynamic_type->search_above_dst(&dynamic_to_dst_info, dynamic_ptr, dynamic_ptr, public_path, false);
     if (dynamic_to_dst_info.path_dst_ptr_to_static_ptr != unknown) {
@@ -266,7 +267,7 @@ const void* dyn_cast_slow(const void* static_ptr,
         static_ptr,
         static_type,
         src2dst_offset,
-        0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false
+        0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, true
     };
 
     dynamic_type->search_below_dst(&info, dynamic_ptr, public_path, false);
@@ -292,7 +293,7 @@ const void* dyn_cast_slow(const void* static_ptr,
             static_ptr,
             static_type,
             src2dst_offset,
-            0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false
+            0, 0, 0, 0, 0, 0, 0, 0, 0, false, false, false, true
         };
         dynamic_type->search_below_dst(&info, dynamic_ptr, public_path, true);
     }
@@ -481,9 +482,11 @@ __class_type_info::can_catch(const __shim_type_info* thrown_type,
     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<void*>(info.dst_ptr_leading_to_static_ptr);
@@ -496,19 +499,39 @@ __class_type_info::can_catch(const __shim_type_info* thrown_type,
 #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)
@@ -527,62 +550,83 @@ __class_type_info::process_found_base_class(__dynamic_cast_info* info,
 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<const char*const*>(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<const void*>(__base_type->name ());
+      // .. and reset the pointer.
+      adjustedPtr = nullptr;
     }
     __base_type->has_unambiguous_public_base(
             info,
             static_cast<char*>(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);
@@ -679,13 +723,22 @@ __pointer_type_info::can_catch(const __shim_type_info* thrown_type,
         dynamic_cast<const __class_type_info*>(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<void*>(info.dst_ptr_leading_to_static_ptr);
+        else
+            adjustedPtr = nullptr;
         return true;
     }
     return false;
diff --git a/libcxxabi/src/private_typeinfo.h b/libcxxabi/src/private_typeinfo.h
index 622e09cc24217f3..68b02cbc885b100 100644
--- a/libcxxabi/src/private_typeinfo.h
+++ b/libcxxabi/src/private_typeinfo.h
@@ -110,6 +110,11 @@ struct _LIBCXXABI_HIDDEN __dynamic_cast_info
     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 @@ class _LIBCXXABI_TYPE_VIS __class_type_info : public __shim_type_info {
                                                        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 @@ class _LIBCXXABI_TYPE_VIS __class_type_info : public __shim_type_info {
   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 @@ class _LIBCXXABI_TYPE_VIS __si_class_type_info : public __class_type_info {
                                                   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 @@ struct _LIBCXXABI_HIDDEN __base_class_type_info
 
     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 @@ class _LIBCXXABI_TYPE_VIS __vmi_class_type_info : public __class_type_info {
                                                   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/test/catch_null_pointer_to_object_pr64953.pass.cpp b/libcxxabi/test/catch_null_pointer_to_object_pr64953.pass.cpp
new file mode 100644
index 000000000000000..b06f15b697e3163
--- /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 <exception>
+#include <stdlib.h>
+#include <assert.h>
+#include <stdio.h>
+
+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 <typename T // Handler type
+          ,
+          typename E // Thrown exception type
+          >
+void assert_catches() {
+  try {
+    throw static_cast<E>(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 <typename T // Handler type
+          ,
+          typename E // Thrown exception type
+          >
+void assert_cannot_catch() {
+  try {
+    throw static_cast<E>(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 <typename T // Handler type
+          ,
+          typename O // Object type
+          >
+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<T>(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<Base*, Catchable1*>();
+  assert_catches<Base*, Catchable2*>();
+  assert_catches<Base*, Catchable3*>();
+}
+
+void f2() {
+  assert_cannot_catch<Base*, Ambiguous1*>();
+  assert_cannot_catch<Base*, Ambiguous2*>();
+  assert_cannot_catch<Base*, Ambiguous3*>();
+  assert_cannot_catch<Base*, NoPublic1*>();
+  assert_cannot_catch<Base*, NoPublic2*>();
+}
+
+void f3() {
+  assert_catches_bp<Base*, Catchable1>();
+  assert_catches_bp<Base*, Catchable2>();
+  assert_catches_bp<Base*, Catchable3>();
+}
+
+int main(int, char**) {
+  f1();
+  f2();
+  f3();
+  return 0;
+}

@iains
Copy link
Contributor Author

iains commented Oct 3, 2023

ran git clang-format with clang-format from llvm-17.

Copy link
Contributor

@rjmccall rjmccall left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm... not sure it's okay to change v-tables in the std::type_info hierarchy like this. In theory it's non-ABI and purely an internal implementation detail of the C++ runtime, but in practice there are some other subclasses out there, like in the ObjC runtime.

I know that the bot is telling you to reformat, but that is making it a lot more difficult to review this patch; please just ignore the bot or do the reformat in a separate PR.

Let me try to understand the basic problem here. We have an exception that's dynamically a null pointer statically typed as Base*, and we're testing whether it's caught by Derived*. This should succeed only if there is a unique public Base subobject in Derived. Catching is built on top of the machinery for dynamic_cast, but the exception path can't be handled the same way it is for dynamic_cast for two reasons:

  • First, the dynamic_cast operator on a null pointer just produces a null pointer unconditionally; it is not meaningful to ask whether the cast "succeeded". That is not true with EH, because we have to decide whether we enter the catch block (or pass the EH filter).
  • Second, in dynamic_cast we know both types statically and could therefore evaluate the unique-public-subclass condition statically if we needed to. This is not possible with EH because the type relationship is dynamic.

So EH needs a way to check the has-unique-public-subclass condition without looking at actual objects. Makes sense.

@iains
Copy link
Contributor Author

iains commented Oct 3, 2023

I'm... not sure it's okay to change v-tables in the std::type_info hierarchy like this. In theory it's non-ABI and purely an internal implementation detail of the C++ runtime, but in practice there are some other subclasses out there, like in the ObjC runtime.

My, perhaps flawed, understanding is that Objective-C is supposed to inherit from the parent language and thus, absent a specification to the contrary, a bug in the exception handling here would also be a bug in the Objective-C exception handling, thus it should also import the fix)?

I know that the bot is telling you to reformat, but that is making it a lot more difficult to review this patch; please just ignore the bot or do the reformat in a separate PR.

(agreed that the layout change obfuscates some of the change purpose - most of it is just initialising the new bool in __dynamic_cast_info). However, if you like I can find my original patch (without the layout changes) and force-push that here?

Let me try to understand the basic problem here. We have an exception that's dynamically a null pointer statically typed as Base*, and we're testing whether it's caught by Derived*. This should succeed only if there is a unique public Base subobject in Derived. Catching is built on top of the machinery for dynamic_cast, but the exception path can't be handled the same way it is for dynamic_cast for two reasons:

  • First, the dynamic_cast operator on a null pointer just produces a null pointer unconditionally; it is not meaningful to ask whether the cast "succeeded". That is not true with EH, because we have to decide whether we enter the catch block (or pass the EH filter).
  • Second, in dynamic_cast we know both types statically and could therefore evaluate the unique-public-subclass condition statically if we needed to. This is not possible with EH because the type relationship is dynamic.

So EH needs a way to check the has-unique-public-subclass condition without looking at actual objects. Makes sense.

Right, exactly.
Two additional points:

  1. there should be NFC to dynamic casts - for them, the __dynamic_cast_info object is initialised to say "we have a pointee object". If that is untrue in some relevant way it's a different bug.

  2. At the moment, I cannot see a way to fix this that does not involve changing the private implementation - and thus potentially impacting Objective-C.

@rjmccall
Copy link
Contributor

rjmccall commented Oct 3, 2023

I'm... not sure it's okay to change v-tables in the std::type_info hierarchy like this. In theory it's non-ABI and purely an internal implementation detail of the C++ runtime, but in practice there are some other subclasses out there, like in the ObjC runtime.

My, perhaps flawed, understanding is that Objective-C is supposed to inherit from the parent language and thus, absent a specification to the contrary, a bug in the exception handling here would also be a bug in the Objective-C exception handling, thus it should also import the fix)?

It's not that Objective-C doesn't want the fix, it's that Apple's Objective-C runtime (at least) has its own subclass of std::type_info so that it can properly interoperate with C++ exceptions (e.g. ObjC exceptions can be caught with catch (...)).

(agreed that the layout change obfuscates some of the change purpose - most of it is just initialising the new bool in __dynamic_cast_info). However, if you like I can find my original patch (without the layout changes) and force-push that here?

I think that would be better, yeah.

  1. At the moment, I cannot see a way to fix this that does not involve changing the private implementation - and thus potentially impacting Objective-C.

Let me tag in @mikeash for this.

@iains
Copy link
Contributor Author

iains commented Oct 4, 2023

I'm... not sure it's okay to change v-tables in the std::type_info hierarchy like this. In theory it's non-ABI and purely an internal implementation detail of the C++ runtime, but in practice there are some other subclasses out there, like in the ObjC runtime.

My, perhaps flawed, understanding is that Objective-C is supposed to inherit from the parent language and thus, absent a specification to the contrary, a bug in the exception handling here would also be a bug in the Objective-C exception handling, thus it should also import the fix)?

It's not that Objective-C doesn't want the fix, it's that Apple's Objective-C runtime (at least) has its own subclass of std::type_info so that it can properly interoperate with C++ exceptions (e.g. ObjC exceptions can be caught with catch (...)).

Right, that was what I expected; It would be good for me to understand the constraints better (in case it opens up other opportunities for fixes). e.g. It seems to me that pushing all state into the __dynamic_cast_info might allow us to keep the private API unchanged - but it would not alter that the private implementation has changed (and the __dynamic_cast_info would not be interoperable with an older version).

(agreed that the layout change obfuscates some of the change purpose - most of it is just initialising the new bool in __dynamic_cast_info). However, if you like I can find my original patch (without the layout changes) and force-push that here?

I think that would be better, yeah.

Done, and rebased/retested (so we expect the formatting check to fail for now).

@mikeash
Copy link

mikeash commented Oct 4, 2023

libobjc has this wacky hand-written vtable: https://github.com/apple-oss-distributions/objc4/blob/objc4-906/runtime/objc-exception.mm#L183

It's mostly no-ops and relies on the fact that the calling conventions allow passing more parameters than the function actually consumes. There's one entry that actually does anything, which is can_catch, and that's unchanged here.

ARM64e ptrauth adds a wrinkle. Changing the signature of these methods will change the ptrauth discriminator, which will no longer match libobjc's hardcoded discriminators. That's not a big deal for has_unambiguous_public_base, as our version of that function just raises a fatal error anyway. Crashing due to a ptrauth mismatch would be slightly less friendly, but shouldn't happen anyway.

That's the only virtual method that changed, so I think we're all good here. libobjc might want to update its discriminator at some point, but that's just a nice-to-have, and it doesn't have to be coordinated with this change.

@rjmccall Anything I might have missed here?

@iains
Copy link
Contributor Author

iains commented Oct 4, 2023

apropos the build kite fails:

  1. It looks like the asan one is a silly mistake in the testcase (missing delete o).
  2. [edit] @ldionne I am not sure exactly what the Apple back-deployment tests are intended to demonstrate;
    • If I build this branch on macOS 10.15 or 10.13 the testcase passes.
    • What is the intention of the test?
    • (and how how do I reproduce? - I work primarily on macOS and build regularly on older OS versions).

@ldionne
Copy link
Member

ldionne commented Oct 5, 2023

apropos the build kite fails:

  1. It looks like the asan one is a silly mistake in the testcase (missing delete o).

Ah, I was going to ask about that. Adding the missing delete should solve the issue.

  1. [edit] @ldionne I am not sure exactly what the Apple back-deployment tests are intended to demonstrate;

    • If I build this branch on macOS 10.15 or 10.13 the testcase passes.
    • What is the intention of the test?
    • (and how how do I reproduce? - I work primarily on macOS and build regularly on older OS versions).

The back-deployment jobs are testing the case where you build an application (here your test) against a recent SDK (i.e. a recent version of libc++), but you specify that you will be deploying to an older version of macOS. And then you run your freshly-built application against an older dylib. So it's building against a recent libc++, but running against an old one.

This is meant to capture the use case of someone building an application on a recent system, but the application is meant to work on various older devices. In practice, we sometimes find subtle ABI breaks with these jobs.

In this case, since you are fixing an issue in libc++abi.dylib and testing the fix to that issue, it is expected that the test will fail if you run it against an older libc++abi.dylib (which doesn't contain the fix). So what I would recommend here is that you add the following annotation to your test:

// This test requires the fix to https://github.com/llvm/llvm-project/issues/64953, which is in libc++abi.dylib.
// The fix is not contained in older macOS system dylibs.
// XFAIL: stdlib=apple-libc++ && target={{.+}}-apple-macosx10.{{9|10|11|12|13|14|15}}
// XFAIL: stdlib=apple-libc++ && target={{.+}}-apple-macosx{{11|12|13|14}}

Then we will evolve the test, and when we eventually drop support for these older macOS versions, we will remove the annotation (it will take a while).

If you want to reproduce this issue, you need to be on macOS (which seems to be your case). Then you should be able to just run ./libcxx/utils/ci/run-buildbot apple-system-backdeployment-10.13 from the root of the monorepo -- that should basically mirror the job that is being run on the CI system. You can change the version from 10.13 to anything else you want (although we don't support some of the more recent versions cause I need to update our collection of old dylibs).

@iains
Copy link
Contributor Author

iains commented Oct 6, 2023

@mikeash , @rjmccall

OK:

  • So I modified this now such that the function signatures are unchanged (the virtual base cookie is passed in the __dynamic_cast_info object instead). The asm actually on ly mentions the function name - but I'm expecting that means 'mangled function name'.

  • the additions to the __dynamic_cast_info are trailing so that a callee passed a pointer to an object, but that does not know about them would not be affected (I do not think this actually matters, in reality, it's just a bit of defensive programming)

@ldionne

Thanks for the pointers.

I was unable to reproduce the asan fail on either x86_64 macOS 14 or arm64 macOS 12.7 - so I went ahead and made a conservative change - we will have to see if the problem clears on the bot.

The other cases are fixed (only testable on x86_64 AFAICT), I see some ld64 warnings about mismatched build versions on arm64 macOS 12,7 - and when the library is missing - we then get caught be the changes that mean there's no lib in /usr/lib/ any more. edit: we needed a slight change to the regex to allow for 11.0 etc.

(we still expect the formatting complaints)

@iains
Copy link
Contributor Author

iains commented Oct 7, 2023

update:
so I've rebased and done the (clang-) reformatting where I actually touched the code (and, in fact, the two places are pretty much re-written, so it should not obfuscate). With the revised impl. there are no changes to the interfaces.

  • split out the somewhat gratuitous reformatting of the initialisers to __dynamic_cast_info objects [TBH, the reformatted looks a lot less readable than the original] as a second patch because they do make the patch less readable.

  • could not repeat the fail to check-generated-output that appeared on the last push (or really see exactly how it might relate to this patch). edit : and it's repeated, but I'm still unclear as to why (this patch does not touch any libcxx sources).

  • If I do:
    make -C build/runtimes/runtimes-bins libcxx-generate-files there are no changed sources.

@ldionne
Copy link
Member

ldionne commented Oct 10, 2023

@iains Can you rebase onto main? I think the CI failure was caused by something unrelated which has been fixed now (according to the Buildkite pipeline status).

@iains
Copy link
Contributor Author

iains commented Oct 10, 2023

sure; done

@ldionne
Copy link
Member

ldionne commented Oct 10, 2023

I added @rjmccall and @urnathan to @llvm/reviewers-libcxxabi -- I'll let you folks determine when this is ready to be merged but I have no objection. Ping me again if you encounter issues with the CI and I'll be glad to assist.

@iains
Copy link
Contributor Author

iains commented Oct 10, 2023

@ldionne well, it seems we still have one issue with the CI. The apple-system test seems to install the newly-built libc++ and libc++abi, but then it runs the tests with apple-libc++abi-shared.cfg.in which declares: stdlib=apple-libc++, stdlib=libc++, and therefore the new test XPASSes (which counts as a fail here).

Perhaps that test should really be declaring llvm-libc++? (but I cannot see right now where to change that).
edit : confirmed with

DYLD_PRINT_LIBRARIES=1 ... 
dyld[7249]: <8B44D440-5382-3E6B-8E80-E99B55C9610E> /scratch/12-mon/llvm-main/bot-test/arm64/test/Output/catch_null_pointer_to_object_pr64953.pass.cpp.dir/t.tmp.exe
dyld[7249]: <81FF5365-8AA4-323D-98F7-F7A0972C6650> /scratch/12-mon/llvm-main/bot-test/arm64/lib/libc++.999.99.dylib
dyld[7249]: <4719C61F-9515-3D02-9931-9191EBAED4A0> /scratch/12-mon/llvm-main/bot-test/arm64/lib/libc++abi.999.99.dylib
<snip>

@ldionne
Copy link
Member

ldionne commented Oct 10, 2023

Interesting. This actually highlights a blind spot we have in the expressiveness of our Lit features. We don't have a way to express "libc++ configured as Apple system library, targeting , but using the tip-of-trunk version of the library". In other words, we generally assume that apple-system + target=<XYZ> implies that we are back-deploying (i.e. running against the system library that was shipped on <XYZ>). I'm not sure why that never bit us, but I suspect this is because these fixes are somewhat rare.

To avoid blocking you on this (which really has nothing to do with this patch), you could just use UNSUPPORTED: instead of XFAIL, that way the test just won't run in that configuration.

@iains
Copy link
Contributor Author

iains commented Oct 10, 2023

Interesting. This actually highlights a blind spot we have in the expressiveness of our Lit features. We don't have a way to express "libc++ configured as Apple system library, targeting , but using the tip-of-trunk version of the library". In other words, we generally assume that apple-system + target=<XYZ> implies that we are back-deploying (i.e. running against the system library that was shipped on <XYZ>).

in this case it was just the plain apple-system i.e. running natively on the box. Given that it intentionally installs libc++ and libc++abi, (and then points to them with DYLD_LIBRARY_PATH) I guess it is intentional that this exercises the built tip of trunk.

I'm not sure why that never bit us, but I suspect this is because these fixes are somewhat rare.

I suppose libc++abi moves slowly, not sure why it does not bite more with libc++ tho - if the assumption of the lit config is that it's the installed libs being tested, but actually we're testing the new ones.

To avoid blocking you on this (which really has nothing to do with this patch), you could just use UNSUPPORTED: instead of XFAIL, that way the test just won't run in that configuration.

OK - I guess that means that the test will not run in any CI - but should still run when people test the library locally?

@ldionne
Copy link
Member

ldionne commented Oct 10, 2023

I suppose libc++abi moves slowly, not sure why it does not bite more with libc++ tho - if the assumption of the lit config is that it's the installed libs being tested, but actually we're testing the new ones.

We're compiling and linking against the new library, but we are running against the installed system one.

OK - I guess that means that the test will not run in any CI - but should still run when people test the library locally?

The test will only be skipped when we build for the Apple-system configuration, which is not most configurations. If you just take libc++ and build it out of the box on Apple platforms, you get the LLVM configuration, not the Apple System configuration (so the test would run).

@iains
Copy link
Contributor Author

iains commented Oct 11, 2023

I suppose libc++abi moves slowly, not sure why it does not bite more with libc++ tho - if the assumption of the lit config is that it's the installed libs being tested, but actually we're testing the new ones.

We're compiling and linking against the new library, but we are running against the installed system one.

For the apple-system-backdeployment* that appears to be the case.

but for the apple-system case we have:

$ ls .../bot-test/arm64/lib
libc++.1.dylib		libc++.999.99.dylib	libc++.a		libc++.dylib		libc++abi.1.dylib	libc++abi.999.99.dylib	libc++abi.a		libc++abi.dylib		libc++experimental.a

and the test execution line is (long paths trimmed):

.../libcxx/utils/run.py --execdir .../bot-test/arm64/test/Output/catch_null_pointer_to_object_pr64953.pass.cpp.dir --env DYLD_LIBRARY_PATH=.../bot-test/arm64/lib --  .../bot-test/arm64/test/Output/catch_null_pointer_to_object_pr64953.pass.cpp.dir/t.tmp.exe

So, in this case, the test is executed with new libraries.

edit: and, in fact, I do not think it will work with the installed libs because there are no /usr/lib/libc++* any more - all moved to the shared cache, but the linked exe has references to @rpath/libc++*. I guess that means we have to have a downloaded version of the libs for each OS version, and point DYLD_LIBRARY_PATH at that (even when we are in a 'native' configuration).

I'll do the UNSUPPORTED change later.

@iains
Copy link
Contributor Author

iains commented Oct 12, 2023

rebase, added the UNSUPPORTED, slight amendment to the title (removed the Issue # reference). Cannot figure out why the CI now thinks there are formatting errors:

llvm-issue-64953$ git clang-format
no modified files to format
llvm-issue-64953$ clang-format --version
clang-format version 17.0.2 (git@github.com:llvm/llvm-project.git 098e653a5bed2e5fb2af6ccaedf2268d7d3935fa)

edit: let's try this again....

This addresses cases (currently failing) where we throw a null pointer-to-
object and is a proposed fix for
llvm#64953

We are trying to satisfy the following bullet from the C++ ABI 15.3:

 *  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 existing implementation assesses the ambiguity of bases by computing the
offsets to them; ambiguous cases are then when the same base appears at
different offsets.  The computation of offset  includes indirecting through
the vtables to find the offsets to virtual bases.

When the thrown pointer points to a real object, this is quite efficient since,
if the base is found, and it is not ambiguous and on a public path, the offset
is needed to return the adjusted pointer (and the indirections are not
particularly expensive to compute).

However, when we throw a null pointer-to-object, this scheme is no longer
applicable (and the code currently bypasses the relevant computations, leading
to the incorrect catches reported in the issue).

-----

The solution proposed here takes a composite approach:

1. When the pointer-to-object points to a real instance (well, at least, it is
   determined to be non-null), we use the existing scheme.

2. When the pointer-to-object is null:

 * We note that there is no real object.
 * When we are processing non-virtual bases, we continue to compute the offsets,
   but for a notional dummy object based at 0.  This is OK, since we never need
   to access the object content for non-virtual bases.
 * When we are processing a path with one or more virtual bases, we remember a
   cookie corresponding to the inner-most virtual base found so far (and set
   the notional offset to 0).  Offsets to inner non-virtual bases are then
   computed as normal.

A base is then ambiguous iff:
  * There is a recorded virtual base cookie and that is different from the
    current one or,
  * The non-virtual base offsets differ.

When a handler for a pointer succeeds in catching a base pointer for a thrown
null pointer-to-object, we still return a nullptr (so the adjustment to the
pointer is not required and need not be computed).

Since we noted that there was no object when starting the search for ambiguous
bases, we know that we can skip the pointer adjustment.

This was : Differential Revision: https://reviews.llvm.org/D158769
@iains iains changed the title [libc++abi] Handle null pointer-to-object: Issue #64593 [libc++abi] Handle catch null pointer-to-object Oct 12, 2023
@ldionne
Copy link
Member

ldionne commented Nov 23, 2023

It seems like the CI passed on this a while back. Are we waiting on anything to merge this?

@iains
Copy link
Contributor Author

iains commented Nov 23, 2023

It seems like the CI passed on this a while back. Are we waiting on anything to merge this?

an ack from the ABI owners, I think :)

@rjmccall
Copy link
Contributor

If the function signatures aren't changing, this definitely ought to be okay.

@iains
Copy link
Contributor Author

iains commented Dec 12, 2023

I'll rebase / squash and retest - is the process then to use the "squash and merge" or is it better to commit the "old way"?

@ldionne
Copy link
Member

ldionne commented Dec 12, 2023

Actually, we can just squash and merge right away here. If there are no merge conflicts, I don't think this needs to go through CI again.

Pressing "squash and merge" and then editing the resulting message appropriately (if it needs to be edited) is the right way to proceed nowadays.

@ldionne
Copy link
Member

ldionne commented Jan 8, 2024

It looks like this applies cleanly onto main, so I'll squash and merge now.

@ldionne ldionne merged commit d5f84e6 into llvm:main Jan 8, 2024
3 checks passed
justinfargnoli pushed a commit to justinfargnoli/llvm-project that referenced this pull request Jan 28, 2024
This addresses cases (currently failing) where we throw a null
pointer-to-object and fixes llvm#64953.

We are trying to satisfy the following bullet from the C++ ABI 15.3:

* 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:

  - a standard pointer conversion (4.10 [conv.ptr]) not involving
    conversions to private or protected or ambiguous classes.

  - a qualification conversion.

The existing implementation assesses the ambiguity of bases by computing
the offsets to them; ambiguous cases are then when the same base appears
at different offsets. The computation of offset includes indirecting
through the vtables to find the offsets to virtual bases.

When the thrown pointer points to a real object, this is quite efficient
since, if the base is found, and it is not ambiguous and on a public
path, the offset is needed to return the adjusted pointer (and the
indirections are not particularly expensive to compute).

However, when we throw a null pointer-to-object, this scheme is no
longer applicable (and the code currently bypasses the relevant
computations, leading to the incorrect catches reported in the issue).

-----

The solution proposed here takes a composite approach:

1. When the pointer-to-object points to a real instance (well, at least,
it is determined to be non-null), we use the existing scheme.

2. When the pointer-to-object is null:

  * We note that there is no real object.
  * When we are processing non-virtual bases, we continue to compute the
    offsets, but for a notional dummy object based at 0. This is OK, since
    we never need to access the object content for non-virtual bases.
  * When we are processing a path with one or more virtual bases, we
    remember a cookie corresponding to the inner-most virtual base found so
    far (and set the notional offset to 0). Offsets to inner non-virtual
    bases are then computed as normal.

A base is then ambiguous iff:
* There is a recorded virtual base cookie and that is different from the
  current one or,
* The non-virtual base offsets differ.

When a handler for a pointer succeeds in catching a base pointer for a
thrown null pointer-to-object, we still return a nullptr (so the
adjustment to the pointer is not required and need not be computed).

Since we noted that there was no object when starting the search for
ambiguous bases, we know that we can skip the pointer adjustment.

This was originally uploaded as https://reviews.llvm.org/D158769.
Fixes llvm#64953
kovdan01 pushed a commit to kovdan01/llvm-project that referenced this pull request May 23, 2024
This addresses cases (currently failing) where we throw a null
pointer-to-object and fixes llvm#64953.

We are trying to satisfy the following bullet from the C++ ABI 15.3:

* 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:

  - a standard pointer conversion (4.10 [conv.ptr]) not involving
    conversions to private or protected or ambiguous classes.

  - a qualification conversion.

The existing implementation assesses the ambiguity of bases by computing
the offsets to them; ambiguous cases are then when the same base appears
at different offsets. The computation of offset includes indirecting
through the vtables to find the offsets to virtual bases.

When the thrown pointer points to a real object, this is quite efficient
since, if the base is found, and it is not ambiguous and on a public
path, the offset is needed to return the adjusted pointer (and the
indirections are not particularly expensive to compute).

However, when we throw a null pointer-to-object, this scheme is no
longer applicable (and the code currently bypasses the relevant
computations, leading to the incorrect catches reported in the issue).

-----

The solution proposed here takes a composite approach:

1. When the pointer-to-object points to a real instance (well, at least,
it is determined to be non-null), we use the existing scheme.

2. When the pointer-to-object is null:

  * We note that there is no real object.
  * When we are processing non-virtual bases, we continue to compute the
    offsets, but for a notional dummy object based at 0. This is OK, since
    we never need to access the object content for non-virtual bases.
  * When we are processing a path with one or more virtual bases, we
    remember a cookie corresponding to the inner-most virtual base found so
    far (and set the notional offset to 0). Offsets to inner non-virtual
    bases are then computed as normal.

A base is then ambiguous iff:
* There is a recorded virtual base cookie and that is different from the
  current one or,
* The non-virtual base offsets differ.

When a handler for a pointer succeeds in catching a base pointer for a
thrown null pointer-to-object, we still return a nullptr (so the
adjustment to the pointer is not required and need not be computed).

Since we noted that there was no object when starting the search for
ambiguous bases, we know that we can skip the pointer adjustment.

This was originally uploaded as https://reviews.llvm.org/D158769.
Fixes llvm#64953
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++abi libc++abi C++ Runtime Library. Not libc++.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants