Skip to content

[libc++] Update polymorphic_allocator to never contain a nullptr #148423

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

Merged
merged 1 commit into from
Jul 16, 2025

Conversation

philnik777
Copy link
Contributor

@philnik777 philnik777 commented Jul 13, 2025

According to [mem.poly.allocator.ctor] the pointer contained in polymorphic_allocator can never be null. The default constructor uses get_default_resource(), which never returns null and the constructor taking a pointer explicitly has a precondition that the pointer is non-null.

This patch adds a warning and an assertion in case a user passes a null pointer to polymorphic_allocator as well as marking resource() to never return null.

This also fixes some tests which contained UB.

Fixes #148420

@philnik777 philnik777 marked this pull request as ready for review July 15, 2025 09:42
@philnik777 philnik777 requested a review from a team as a code owner July 15, 2025 09:42
@llvmbot llvmbot added the libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi. label Jul 15, 2025
@llvmbot
Copy link
Member

llvmbot commented Jul 15, 2025

@llvm/pr-subscribers-libcxx

Author: Nikolas Klauser (philnik777)

Changes

According to [mem.poly.allocator.ctor] the pointer contained in polymorphic_allocator can never be null. The default constructor uses get_default_resource(), which never returns null and the constructor taking a pointer explicitly has a precondition that the pointer is non-null.

This patch adds a warning and an assertion in case a user passes a null pointer to polymorphic_allocator as well as marking resource() to never return null.

This also fixes some tests which contained UB.

Fixes #148420


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

5 Files Affected:

  • (modified) libcxx/include/__memory_resource/polymorphic_allocator.h (+4-2)
  • (added) libcxx/test/libcxx/mem/mem.res/assert.pass.cpp (+30)
  • (added) libcxx/test/libcxx/mem/mem.res/nonnull.verify.cpp (+17)
  • (modified) libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/resource.pass.cpp (+15-12)
  • (modified) libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/select_on_container_copy_construction.pass.cpp (+13-15)
diff --git a/libcxx/include/__memory_resource/polymorphic_allocator.h b/libcxx/include/__memory_resource/polymorphic_allocator.h
index b95c6a37c5c11..1b8711f10811d 100644
--- a/libcxx/include/__memory_resource/polymorphic_allocator.h
+++ b/libcxx/include/__memory_resource/polymorphic_allocator.h
@@ -50,7 +50,9 @@ class _LIBCPP_AVAILABILITY_PMR polymorphic_allocator {
 
   _LIBCPP_HIDE_FROM_ABI polymorphic_allocator() noexcept : __res_(std::pmr::get_default_resource()) {}
 
-  _LIBCPP_HIDE_FROM_ABI polymorphic_allocator(memory_resource* __r) noexcept : __res_(__r) {}
+  _LIBCPP_HIDE_FROM_ABI polymorphic_allocator(memory_resource* _LIBCPP_DIAGNOSE_NULLPTR __r) noexcept : __res_(__r) {
+    _LIBCPP_ASSERT_NON_NULL(__r, "Attempted to pass a nullptr resource to polymorphic_alloator");
+  }
 
   _LIBCPP_HIDE_FROM_ABI polymorphic_allocator(const polymorphic_allocator&) = default;
 
@@ -174,7 +176,7 @@ class _LIBCPP_AVAILABILITY_PMR polymorphic_allocator {
     return polymorphic_allocator();
   }
 
-  _LIBCPP_HIDE_FROM_ABI memory_resource* resource() const noexcept { return __res_; }
+  [[__gnu__::__returns_nonnull__]] _LIBCPP_HIDE_FROM_ABI memory_resource* resource() const noexcept { return __res_; }
 
   _LIBCPP_HIDE_FROM_ABI friend bool
   operator==(const polymorphic_allocator& __lhs, const polymorphic_allocator& __rhs) noexcept {
diff --git a/libcxx/test/libcxx/mem/mem.res/assert.pass.cpp b/libcxx/test/libcxx/mem/mem.res/assert.pass.cpp
new file mode 100644
index 0000000000000..831985f2ce37f
--- /dev/null
+++ b/libcxx/test/libcxx/mem/mem.res/assert.pass.cpp
@@ -0,0 +1,30 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// <memory_resource>
+
+// Test hardening assertions for std::pmr::polymorphic_allocator.
+
+// REQUIRES: has-unix-headers
+// REQUIRES: libcpp-hardening-mode={{extensive|debug}}
+// UNSUPPORTED: c++03, c++11, c++14
+// XFAIL: libcpp-hardening-mode=debug && availability-verbose_abort-missing
+
+// We're testing nullptr assertions
+// ADDITIONAL_COMPILE_FLAGS: -Wno-nonnull
+
+#include <memory_resource>
+
+#include "check_assertion.h"
+
+int main(int, char**) {
+  TEST_LIBCPP_ASSERT_FAILURE(
+      std::pmr::polymorphic_allocator<int>(nullptr), "Attempted to pass a nullptr resource to polymorphic_alloator");
+
+  return 0;
+}
diff --git a/libcxx/test/libcxx/mem/mem.res/nonnull.verify.cpp b/libcxx/test/libcxx/mem/mem.res/nonnull.verify.cpp
new file mode 100644
index 0000000000000..c094a484595ab
--- /dev/null
+++ b/libcxx/test/libcxx/mem/mem.res/nonnull.verify.cpp
@@ -0,0 +1,17 @@
+//===----------------------------------------------------------------------===//
+//
+// 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
+//
+//===----------------------------------------------------------------------===//
+
+// UNSUPPORTED: c++03, c++11, c++14
+
+// Ensure that passing a nullptr to polymorphic_alloator is diagnosed
+
+#include <memory_resource>
+
+void test() {
+  std::pmr::polymorphic_allocator<int> alloc(nullptr); // expected-warning {{null passed}}
+}
diff --git a/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/resource.pass.cpp b/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/resource.pass.cpp
index 23865f67a694b..22cb72414e975 100644
--- a/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/resource.pass.cpp
+++ b/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/resource.pass.cpp
@@ -17,11 +17,19 @@
 // memory_resource *
 // polymorphic_allocator<T>::resource() const
 
-#include <memory_resource>
 #include <cassert>
+#include <cstddef>
+#include <memory_resource>
+#include <new>
 
 #include "test_macros.h"
 
+struct resource : std::pmr::memory_resource {
+  void* do_allocate(size_t, size_t) override { TEST_THROW(std::bad_alloc()); }
+  void do_deallocate(void*, size_t, size_t) override { assert(false); }
+  bool do_is_equal(const std::pmr::memory_resource&) const noexcept override { return false; }
+};
+
 int main(int, char**) {
   typedef std::pmr::polymorphic_allocator<void> A;
   {
@@ -29,24 +37,19 @@ int main(int, char**) {
     ASSERT_SAME_TYPE(decltype(a.resource()), std::pmr::memory_resource*);
   }
   {
-    std::pmr::memory_resource* mptr = (std::pmr::memory_resource*)42;
-    A const a(mptr);
-    assert(a.resource() == mptr);
-  }
-  {
-    A const a(nullptr);
-    assert(a.resource() == nullptr);
-    assert(a.resource() == nullptr);
+    resource res;
+    A const a(&res);
+    assert(a.resource() == &res);
   }
   {
     A const a;
     assert(a.resource() == std::pmr::get_default_resource());
   }
   {
-    std::pmr::memory_resource* mptr = (std::pmr::memory_resource*)42;
-    std::pmr::set_default_resource(mptr);
+    resource res;
+    std::pmr::set_default_resource(&res);
     A const a;
-    assert(a.resource() == mptr);
+    assert(a.resource() == &res);
     assert(a.resource() == std::pmr::get_default_resource());
   }
 
diff --git a/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/select_on_container_copy_construction.pass.cpp b/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/select_on_container_copy_construction.pass.cpp
index 58d6cccd244cf..ccac1178fe516 100644
--- a/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/select_on_container_copy_construction.pass.cpp
+++ b/libcxx/test/std/utilities/utility/mem.res/mem.poly.allocator.class/mem.poly.allocator.mem/select_on_container_copy_construction.pass.cpp
@@ -17,11 +17,19 @@
 // polymorphic_allocator
 // polymorphic_allocator<T>::select_on_container_copy_construction() const
 
-#include <memory_resource>
 #include <cassert>
+#include <cstddef>
+#include <memory_resource>
+#include <new>
 
 #include "test_macros.h"
 
+struct resource : std::pmr::memory_resource {
+  void* do_allocate(size_t, size_t) override { TEST_THROW(std::bad_alloc()); }
+  void do_deallocate(void*, size_t, size_t) override { assert(false); }
+  bool do_is_equal(const std::pmr::memory_resource&) const noexcept override { return false; }
+};
+
 int main(int, char**) {
   typedef std::pmr::polymorphic_allocator<void> A;
   {
@@ -29,22 +37,12 @@ int main(int, char**) {
     ASSERT_SAME_TYPE(decltype(a.select_on_container_copy_construction()), A);
   }
   {
-    std::pmr::memory_resource* mptr = (std::pmr::memory_resource*)42;
-    A const a(mptr);
-    assert(a.resource() == mptr);
-    A const other = a.select_on_container_copy_construction();
-    assert(other.resource() == std::pmr::get_default_resource());
-    assert(a.resource() == mptr);
-  }
-  {
-    std::pmr::memory_resource* mptr = (std::pmr::memory_resource*)42;
-    std::pmr::set_default_resource(mptr);
-    A const a(nullptr);
-    assert(a.resource() == nullptr);
+    resource res;
+    A const a(&res);
+    assert(a.resource() == &res);
     A const other = a.select_on_container_copy_construction();
     assert(other.resource() == std::pmr::get_default_resource());
-    assert(other.resource() == mptr);
-    assert(a.resource() == nullptr);
+    assert(a.resource() == &res);
   }
 
   return 0;

Copy link
Member

@ldionne ldionne left a comment

Choose a reason for hiding this comment

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

LGTM with minor comments, thanks for fixing!

@philnik777 philnik777 force-pushed the pmr_handle_nullptrs branch from bc071fc to f8cfef0 Compare July 16, 2025 08:03
@philnik777 philnik777 merged commit ffcb0a4 into llvm:main Jul 16, 2025
11 of 15 checks passed
@philnik777 philnik777 deleted the pmr_handle_nullptrs branch July 16, 2025 08:04
Copy link
Contributor

@jwakely jwakely left a comment

Choose a reason for hiding this comment

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

Thanks!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
libc++ libc++ C++ Standard Library. Not GNU libstdc++. Not libc++abi.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Precondition violations in libc++ tests for std::pmr::polymorphic_allocator
4 participants