Skip to content

Commit

Permalink
Change bslma::Allocator to return non-null ptr when using `memory_r…
Browse files Browse the repository at this point in the history
…esource` interface - DRQS 175442282 (#5030)

* Add `bslma_memoryresourceimpsupport` component with `singularPointer` utility function.
* Return singular pointer from zero-sized allocations using `bslma::Allocator::do_allocate`
  • Loading branch information
phalpern authored and GitHub Enterprise committed Nov 6, 2024
1 parent c34fde5 commit acb0240
Show file tree
Hide file tree
Showing 6 changed files with 771 additions and 5 deletions.
22 changes: 20 additions & 2 deletions groups/bsl/bslma/bslma_allocator.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#include <bsls_ident.h>
BSLS_IDENT("$Id$ $CSID$")

#include <bslma_memoryresourceimpsupport.h>

#include <bsls_assert.h>
#include <bsls_bslexceptionutil.h>
#include <bsls_keyword.h>
Expand Down Expand Up @@ -35,11 +37,27 @@ Allocator::~Allocator()
// PROTECTED MANIPULATORS
void *Allocator::do_allocate(std::size_t bytes, std::size_t)
{
return this->allocate(bytes);
void *p = this->allocate(bytes);

if (0 == bytes && 0 == p) {
// `do_allocate` is not allowed to return null, so return special ptr.
return bslma::MemoryResourceImpSupport::singularPointer();
}

return p;
}

void Allocator::do_deallocate(void *p, std::size_t, std::size_t)
void Allocator::do_deallocate(void *p, std::size_t bytes, std::size_t)
{
(void) bytes; // Not used in an OPT build

if (bslma::MemoryResourceImpSupport::singularPointer() == p) {
// Special pointer indicates zero-byte `memory_resource` allocation.
BSLS_ASSERT(0 == bytes);
p = 0; // Change pointer to zero-byte expected value for `Allocator`.
}

// Forward to `Allocator` interface.
this->deallocate(p);
}

Expand Down
42 changes: 39 additions & 3 deletions groups/bsl/bslma/bslma_allocator.t.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,7 @@ class my_Allocator : public bslma::Allocator {
d_fun = 1;
d_arg = s;
++d_allocateCount;
return this;
return s ? this : 0;
}

// MANIPULATORS
Expand Down Expand Up @@ -246,21 +246,31 @@ class my_NewDeleteAllocator : public bslma::Allocator {
~my_NewDeleteAllocator() BSLS_KEYWORD_OVERRIDE { }

void *allocate(size_type size) BSLS_KEYWORD_OVERRIDE {
++d_count;

if (0 == size) {
return 0;
}

unsigned *p = (unsigned *) operator new(
size + bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT);
*p = MAGIC;

++d_count;
return (char *) p + bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT;
}

void deallocate(void *address) BSLS_KEYWORD_OVERRIDE {
++d_count;

if (0 == address) {
return;
}

unsigned *p = (unsigned *)
((bsls::AlignmentUtil::MaxAlignedType *) address - 1);
ASSERT(MAGIC == *p);
*p = DELETED;

++d_count;
operator delete(p);
}

Expand Down Expand Up @@ -1131,6 +1141,32 @@ int main(int argc, char *argv[])
// The default implementation of `do_is_equal` cannot be tested because
// it does call a virtual function that we can intercept.

// Test zero-byte allocations.
// TBD: This is a very basic and undocumented test. A more
// sophisticated test will be added as part of the fix for DRQS
// 176364960.
my_NewDeleteAllocator myA;

// Zero-byte allocation through `bslma::Allocator` interface.
void *p1 = myA.allocate(0);
ASSERT(0 == p1);
ASSERT(1 == myA.getCount()); // Increments even for zero bytes

myA.deallocate(p1); // Can deallocate null pointer
ASSERT(2 == myA.getCount()); // Increments even for zero bytes

// Zero-byte allocation through `bsl::memory_resource` interface.
bsl::memory_resource& myR = myA;
void *p2 = myR.allocate(0);
ASSERT(0 != p2); // Non-zero return
ASSERT(3 == myA.getCount()); // Increments even for zero bytes

ASSERT(myR.allocate(0) == p2); // Returns same pointer every time
ASSERT(4 == myA.getCount());

myR.deallocate(p2, 0); // Can deallocate zero-size block
ASSERT(5 == myA.getCount()); // Increments even for zero bytes

} break;

default: {
Expand Down
40 changes: 40 additions & 0 deletions groups/bsl/bslma/bslma_memoryresourceimpsupport.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
// bslma_memoryresourceimpsupport.cpp -*-C++-*-

#include <bslma_memoryresourceimpsupport.h>

#include <bslma_memoryresource.h> // for testing only

#include <bsls_ident.h>
BSLS_IDENT_RCSID(bslma_memoryresourceimpsupport_cpp, "$Id$ $CSID$")

namespace BloombergLP {
namespace bslma {

// CLASS DATA

// The `d_data` member of the singular object contains a bit pattern that is
// easily recognized as *bad* data in the debugger.
const MemoryResourceImpSupport_AlignedData
MemoryResourceImpSupport::s_singularObject = {
0xDeadBeefCafeF00dULL,
bsls::AlignmentUtil::MaxAlignedType()
};

} // close package namespace
} // close enterprise namespace

// ----------------------------------------------------------------------------
// Copyright 2024 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
241 changes: 241 additions & 0 deletions groups/bsl/bslma/bslma_memoryresourceimpsupport.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,241 @@
// bslma_memoryresourceimpsupport.h -*-C++-*-
#ifndef INCLUDED_BSLMA_MEMORYRESOURCEIMPSUPPORT
#define INCLUDED_BSLMA_MEMORYRESOURCEIMPSUPPORT

#include <bsls_ident.h>
BSLS_IDENT("$Id: $")

//@PURPOSE: Provide support for implementing memory resources
//
//@CLASSES:
// bslma::MemoryResourceImpSupport - namespace for resource support functions
//
//@SEE_ALSO: bslma_memoryresource, bslma_allocator
//
//@DESCRIPTION: This component provides support functions and types for
// implementing a class derived from `bsl::memory_resource`. Currently, it
// provides a utility function, `singularPointer`, that provides a pointer
// suitable for an `allocate` function to return when a zero-sized allocation
// is requested.
//
///Usage
///-----
// This section illustrates intended use of this component.
//
///Example 1: Write a `memory_resource` using `singularPointer`
/// - - - - - - - - - - - - - - - - - - - - - - - - - - - - - -
// In this example, we derive a `NewDeleteResource` class, derived from
// `bsl::memory_resource`, that allocates and deallocate using `operator new`
// and `operator delete`, respectively, and uses `singularPointer()` as a
// special value when allocating and allocating zero bytes.
//
// First, we define the class interface, which implements the protected virtual
// functions `do_allocate`, `do_deallocate`, and `do_is_equal`:
// ```
// #include <bslma_memoryresource.h>
// #include <bslma_memoryresourceimpsupport.h>
// #include <bsls_alignmentutil.h>
// #include <bsls_assert.h>
//
// /// Memory resource that allocates using `operator new`.
// class NewDeleteResource : public bsl::memory_resource {
// protected:
// // PROTECTED MANIPULATORS
//
// /// Return the specified `size` bytes with the specified `alignment`
// /// allocated from the global heap using `operator new`. The behavior
// /// is undefined unless the specified `alignment` is less than or equal
// /// to the maximum platform alignment.
// void *do_allocate(std::size_t size, std::size_t alignment)
// BSLS_KEYWORD_OVERRIDE;
//
// /// Deallocate the memory block specified by `p` having the specified
// /// `size` and `alignment` from the global heap using `operator
// /// delete`. The behavior is undefined unless `p` was returned from a
// /// previous call to `allocate`, using the same `size` and `alignment`.
// void do_deallocate(void *p, std::size_t size, std::size_t alignment)
// BSLS_KEYWORD_OVERRIDE;
//
// /// Return `true` if `x` is a `NewDeleteResource` and `false`
// /// otherwise.
// bool do_is_equal(const bsl::memory_resource& x) const
// BSLS_KEYWORD_NOEXCEPT BSLS_KEYWORD_OVERRIDE;
// };
// ```
// Next, we implement the `do_allocate` method, which forwards most requests to
// `operator new`. Section [basic.stc.dynamic.allocation] of the C++ standard,
// however states that the return value of an allocation function when the
// requested size is zero is a *non-null* pointer to a suitably aligned block
// of storage, so we use `singularPointer` to provide the address in such
// circumstances:
// ```
// #include <new> // `align_val_t` and aligned `new`
//
// void *NewDeleteResource::do_allocate(std::size_t size,
// std::size_t alignment)
// {
// if (0 == size) {
// return bslma::MemoryResourceImpSupport::singularPointer();
// }
//
// #ifdef __cpp_aligned_new
// return ::operator new(size, std::align_val_t(alignment));
// #else
// (void) alignment;
// BSLS_ASSERT(alignment <= bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT);
// return ::operator new(size);
// #endif
// }
// ```
// Next, we implement the `do_deallocate` method, which forwards most requests
// to `operator delete`, but does nothing if the incoming pointer was the
// result of a zero-sized allocation:
// ```
// void NewDeleteResource::do_deallocate(void *p,
// std::size_t size,
// std::size_t alignment)
// {
// (void) size; // Not used in OPT build
//
// if (bslma::MemoryResourceImpSupport::singularPointer() == p) {
// BSLS_ASSERT(0 == size);
// return;
// }
//
// BSLS_ASSERT(0 < size);
// #ifdef __cpp_aligned_new
// ::operator delete(p, std::align_val_t(alignment));
// #else
// (void) (size, alignment);
// BSLS_ASSERT(alignment <= bsls::AlignmentUtil::BSLS_MAX_ALIGNMENT);
// ::operator delete(p);
// #endif
// }
// ```
// Next, we complete the implementation with `do_is_equal`. All instances of
// `NewDeleteResource` compare equal, so we need only check that the argument
// is a `NewDeleteResource`. As a short-cut, we check if the address of `b` is
// the same as `this`, to quickly catch the common case that they are both
// pointers to the singleton object:
// ```
// bool NewDeleteResource::do_is_equal(const bsl::memory_resource& b) const
// BSLS_KEYWORD_NOEXCEPT
// {
// return this == &b || 0 != dynamic_cast<const NewDeleteResource *>(&b);
// }
// ```
// Now, when we call `allocate`, we observe that each non-zero allocation
// yields a different pointer, whereas all of the zero allocations yield the
// same address.
// ```
// int main()
// {
// NewDeleteResource r;
// void *p1 = r.allocate(1, 1);
// void *p2 = r.allocate(8);
// assert(p1 != p2);
//
// void *p3 = r.allocate(0, 1);
// void *p4 = r.allocate(0, 4);
// assert(p3 != p1);
// assert(p3 != p2);
// assert(p3 == p4);
// ```
// Finally, when we deallocate memory, we would expect nothing to happen when
// deallocating the zero-sized blocks `p3` and `p4`:
// ```
// r.deallocate(p1, 1, 1);
// r.deallocate(p2, 8);
// r.deallocate(p3, 0, 1);
// r.deallocate(p4, 0, 4);
// }
// ```

#include <bslscm_version.h>

#include <bsls_alignmentutil.h>
#include <bsls_keyword.h>

namespace BloombergLP {
namespace bslma {

// ===========================================
// struct MemoryResourceImpSupport_AlignedData
// ===========================================

/// COMPONENT-PRIVATE struct -- DO NOT USE. Maximally aligned `struct` holding
/// an unsigned 64-bit integer. This `struct` is exposed in the component
/// header so that `MemoryResourceImpSupport::singularPointer` can be
/// implemented as `constexpr` and `inline`.
struct MemoryResourceImpSupport_AlignedData {
unsigned long long d_data;
bsls::AlignmentUtil::MaxAlignedType d_alignment;
};

// ==============================
// class MemoryResourceImpSupport
// ==============================

/// Namespace for functions that support implementing a memory resource.
struct MemoryResourceImpSupport {

private:

// PRIVATE CLASS DATA

/// Singleton used to produce singular address distinct from all other
/// addresses.
static const MemoryResourceImpSupport_AlignedData s_singularObject;

public:
// CLASS METHODS

/// Return a maximally aligned, non-null pointer that cannnot organically
/// result from any operation other than calling this function; i.e., it is
/// a *singular* pointer. Within a single program execution, no other
/// current or future object, of any storage duration, will have an address
/// matching this function's return value. The returned address may be
/// used by an allocation function to indicate a *non-allocation*, e.g.,
/// the result of allocating zero bytes. The behavior is undefined if the
/// returned pointer is dereferenced. Note that the same address is
/// returned each time this function is called.
BSLS_KEYWORD_CONSTEXPR static void *singularPointer();
};

// ============================================================================
// TEMPLATE AND INLINE FUNCTION IMPLEMENTATIONS
// ============================================================================

// CLASS METHODS

inline BSLS_KEYWORD_CONSTEXPR void *MemoryResourceImpSupport::singularPointer()
{
// The singleton object is 'const', so any attempt to construct an object
// through the returned pointer should result in a protection error.
return const_cast<unsigned long long *>(&s_singularObject.d_data);
}

} // close package namespace


// FREE OPERATORS

} // close enterprise namespace

#endif // ! defined(INCLUDED_BSLMA_MEMORYRESOURCEIMPSUPPORT)

// ----------------------------------------------------------------------------
// Copyright 2024 Bloomberg Finance L.P.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
// ----------------------------- END-OF-FILE ----------------------------------
Loading

0 comments on commit acb0240

Please sign in to comment.