Exceptions at construction time of dynamic objects

This patch implements the support needed to handle exceptions that occur
during the construction of objects dynamically allocated via the
'Allocator' interface. In this case, the compiler automatically invokes
a special delete operator that takes the allocator type (as supplied to
'new') as second argument. The implementation of this delete operator
has been added to the 'cxx' library. Because the operator delete is
called without the size of the object, we can use only those allocators
that ignore the size argument of the free function and print a warning
otherwise. The added 'Allocator::need_size_for_free()' function is used
to distinguish safe and unsafe allocators.
This commit is contained in:
Norman Feske 2012-01-26 21:03:49 +01:00
parent 759e789ddd
commit a107c89a8e
5 changed files with 63 additions and 5 deletions

View File

@ -75,6 +75,21 @@ namespace Genode {
*/
virtual size_t overhead(size_t size) = 0;
/**
* Return true if the size argument of 'free' is required
*
* The generic 'Allocator' interface requires the caller of 'free'
* to supply a valid size argument but not all implementations make
* use of this argument. If this function returns false, it is safe
* to call 'free' with an invalid size.
*
* Allocators that rely on the size argument must not be used for
* constructing objects whose constructors may throw exceptions.
* See the documentation of 'operator delete(void *, Allocator *)'
* below for more details.
*/
virtual bool need_size_for_free() const { return true; }
/***************************
** Convenience functions **
@ -146,8 +161,9 @@ namespace Genode {
/**
* Free a previously allocated block
*
* NOTE: We have to declare the 'Allocator::free' function here
* as well to make gcc happy.
* NOTE: We have to declare the 'Allocator::free(void *)' function
* here as well to make the compiler happy. Otherwise the C++
* overload resolution would not find 'Allocator::free(void *)'.
*/
virtual void free(void *addr) = 0;
virtual void free(void *addr, size_t size) = 0;
@ -202,4 +218,32 @@ namespace Genode {
void *operator new (Genode::size_t size, Genode::Allocator *allocator);
void *operator new [] (Genode::size_t size, Genode::Allocator *allocator);
/**
* Delete operator invoked when an exception occurs during the construction of
* a dynamically allocated object
*
* When an exception occurs during the construction of a dynamically allocated
* object, the C++ standard devises the automatic invocation of the global
* operator delete. When passing an allocator as argument to the new operator
* (the typical case for Genode), the compiler magically calls the operator
* delete taking the allocator type as second argument. This is how we end up
* here.
*
* There is one problem though: We get the pointer of the to-be-deleted object
* but not its size. But Genode's 'Allocator' interface requires the object
* size to be passed as argument to 'Allocator::free()'.
*
* Even though in the general case, we cannot assume all 'Allocator'
* implementations to remember the size of each allocated object, the commonly
* used 'Heap', 'Sliced_heap', 'Allocator_avl', and 'Slab' do so and ignore the
* size argument. When using either of those allocators, we are fine. Otherwise
* we print a warning and pass the zero size argument anyway.
*
* :Warning: Never use an allocator that depends on the size argument of the
* 'free()' function for the allocation of objects that may throw exceptions
* at their construction time!
*/
void operator delete (void *, Genode::Allocator *);
#endif /* _INCLUDE__BASE__ALLOCATOR_H_ */

View File

@ -242,6 +242,8 @@ namespace Genode {
* slab allocator.
*/
size_t overhead(size_t size) { return sizeof(Block) + sizeof(umword_t); }
bool need_size_for_free() const { return false; }
};

View File

@ -137,6 +137,7 @@ namespace Genode {
void free(void *, size_t);
size_t consumed() { return _quota_used; }
size_t overhead(size_t size) { return _alloc.overhead(size); }
bool need_size_for_free() const { return false; }
};
@ -176,6 +177,7 @@ namespace Genode {
void free(void *, size_t);
size_t consumed() { return _consumed; }
size_t overhead(size_t size);
bool need_size_for_free() const { return false; }
};
}

View File

@ -249,6 +249,7 @@ namespace Genode {
void free(void *addr, size_t) { free(addr); }
size_t consumed();
size_t overhead(size_t size) { return _block_size/_num_elem; }
bool need_size_for_free() const { return false; }
};
}

View File

@ -15,9 +15,6 @@
#include <base/allocator.h>
/**
* New operator for allocating from a specified allocator
*/
void *operator new(Genode::size_t size, Genode::Allocator *allocator)
{
if (!allocator)
@ -33,3 +30,15 @@ void *operator new [] (Genode::size_t size, Genode::Allocator *allocator)
return allocator->alloc(size);
}
void operator delete (void *ptr, Genode::Allocator *alloc)
{
/*
* Warn on the attempt to use an allocator that relies on the size
* argument.
*/
if (alloc->need_size_for_free())
PERR("C++ runtime: delete called with unsafe allocator, leaking memory");
}