Bug 1207519 - Prevent HashTable shrink from ignoring allocation failures that may have been reported r=Waldo

This commit is contained in:
Jon Coppeard 2015-09-30 11:34:49 +01:00
parent c728d272be
commit 53861afa80
9 changed files with 209 additions and 59 deletions

View File

@ -1124,11 +1124,24 @@ class HashTable : private AllocPolicy
return keyHash & ~sCollisionBit;
}
static Entry* createTable(AllocPolicy& alloc, uint32_t capacity)
enum FailureBehavior { DontReportFailure = false, ReportFailure = true };
static Entry* createTable(AllocPolicy& alloc, uint32_t capacity,
FailureBehavior reportFailure = ReportFailure)
{
static_assert(sFreeKey == 0,
"newly-calloc'd tables have to be considered empty");
return alloc.template pod_calloc<Entry>(capacity);
if (reportFailure)
return alloc.template pod_calloc<Entry>(capacity);
return alloc.template maybe_pod_calloc<Entry>(capacity);
}
static Entry* maybeCreateTable(AllocPolicy& alloc, uint32_t capacity)
{
static_assert(sFreeKey == 0,
"newly-calloc'd tables have to be considered empty");
return alloc.template maybe_pod_calloc<Entry>(capacity);
}
static void destroyTable(AllocPolicy& alloc, Entry* oldTable, uint32_t capacity)
@ -1367,7 +1380,7 @@ class HashTable : private AllocPolicy
enum RebuildStatus { NotOverloaded, Rehashed, RehashFailed };
RebuildStatus changeTableSize(int deltaLog2)
RebuildStatus changeTableSize(int deltaLog2, FailureBehavior reportFailure = ReportFailure)
{
// Look, but don't touch, until we succeed in getting new entry store.
Entry* oldTable = table;
@ -1375,11 +1388,12 @@ class HashTable : private AllocPolicy
uint32_t newLog2 = sHashBits - hashShift + deltaLog2;
uint32_t newCapacity = JS_BIT(newLog2);
if (MOZ_UNLIKELY(newCapacity > sMaxCapacity)) {
this->reportAllocOverflow();
if (reportFailure)
this->reportAllocOverflow();
return RehashFailed;
}
Entry* newTable = createTable(*this, newCapacity);
Entry* newTable = createTable(*this, newCapacity, reportFailure);
if (!newTable)
return RehashFailed;
@ -1405,14 +1419,19 @@ class HashTable : private AllocPolicy
return Rehashed;
}
RebuildStatus checkOverloaded()
bool shouldCompressTable()
{
// Compress if a quarter or more of all entries are removed.
return removedCount >= (capacity() >> 2);
}
RebuildStatus checkOverloaded(FailureBehavior reportFailure = ReportFailure)
{
if (!overloaded())
return NotOverloaded;
// Compress if a quarter or more of all entries are removed.
int deltaLog2;
if (removedCount >= (capacity() >> 2)) {
if (shouldCompressTable()) {
METER(stats.compresses++);
deltaLog2 = 0;
} else {
@ -1420,14 +1439,14 @@ class HashTable : private AllocPolicy
deltaLog2 = 1;
}
return changeTableSize(deltaLog2);
return changeTableSize(deltaLog2, reportFailure);
}
// Infallibly rehash the table if we are overloaded with removals.
void checkOverRemoved()
{
if (overloaded()) {
if (checkOverloaded() == RehashFailed)
if (checkOverloaded(DontReportFailure) == RehashFailed)
rehashTableInPlace();
}
}
@ -1454,7 +1473,7 @@ class HashTable : private AllocPolicy
{
if (underloaded()) {
METER(stats.shrinks++);
(void) changeTableSize(-1);
(void) changeTableSize(-1, DontReportFailure);
}
}
@ -1470,9 +1489,8 @@ class HashTable : private AllocPolicy
resizeLog2--;
}
if (resizeLog2 != 0) {
changeTableSize(resizeLog2);
}
if (resizeLog2 != 0)
(void) changeTableSize(resizeLog2, DontReportFailure);
}
// This is identical to changeTableSize(currentSize), but without requiring

View File

@ -531,7 +531,7 @@ class LifoAllocPolicy
: alloc_(alloc)
{}
template <typename T>
T* pod_malloc(size_t numElems) {
T* maybe_pod_malloc(size_t numElems) {
if (MOZ_UNLIKELY(numElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value))
return nullptr;
size_t bytes = numElems * sizeof(T);
@ -539,7 +539,7 @@ class LifoAllocPolicy
return static_cast<T*>(p);
}
template <typename T>
T* pod_calloc(size_t numElems) {
T* maybe_pod_calloc(size_t numElems) {
T* p = pod_malloc<T>(numElems);
if (fb == Fallible && !p)
return nullptr;
@ -547,7 +547,7 @@ class LifoAllocPolicy
return p;
}
template <typename T>
T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) {
T* n = pod_malloc<T>(newSize);
if (fb == Fallible && !n)
return nullptr;
@ -555,6 +555,18 @@ class LifoAllocPolicy
memcpy(n, p, Min(oldSize * sizeof(T), newSize * sizeof(T)));
return n;
}
template <typename T>
T* pod_malloc(size_t numElems) {
return maybe_pod_malloc<T>(numElems);
}
template <typename T>
T* pod_calloc(size_t numElems) {
return maybe_pod_calloc<T>(numElems);
}
template <typename T>
T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
return maybe_pod_realloc<T>(p, oldSize, newSize);
}
void free_(void* p) {
}
void reportAllocOverflow() const {

View File

@ -78,20 +78,20 @@ class JitAllocPolicy
: alloc_(alloc)
{}
template <typename T>
T* pod_malloc(size_t numElems) {
T* maybe_pod_malloc(size_t numElems) {
if (MOZ_UNLIKELY(numElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value))
return nullptr;
return static_cast<T*>(alloc_.allocate(numElems * sizeof(T)));
}
template <typename T>
T* pod_calloc(size_t numElems) {
T* maybe_pod_calloc(size_t numElems) {
T* p = pod_malloc<T>(numElems);
if (MOZ_LIKELY(p))
memset(p, 0, numElems * sizeof(T));
return p;
}
template <typename T>
T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) {
T* n = pod_malloc<T>(newSize);
if (MOZ_UNLIKELY(!n))
return n;
@ -99,6 +99,18 @@ class JitAllocPolicy
memcpy(n, p, Min(oldSize * sizeof(T), newSize * sizeof(T)));
return n;
}
template <typename T>
T* pod_malloc(size_t numElems) {
return maybe_pod_malloc<T>(numElems);
}
template <typename T>
T* pod_calloc(size_t numElems) {
return maybe_pod_calloc<T>(numElems);
}
template <typename T>
T* pod_realloc(T* ptr, size_t oldSize, size_t newSize) {
return maybe_pod_realloc<T>(ptr, oldSize, newSize);
}
void free_(void* p) {
}
void reportAllocOverflow() const {
@ -114,11 +126,15 @@ class OldJitAllocPolicy
OldJitAllocPolicy()
{}
template <typename T>
T* pod_malloc(size_t numElems) {
T* maybe_pod_malloc(size_t numElems) {
if (MOZ_UNLIKELY(numElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value))
return nullptr;
return static_cast<T*>(GetJitContext()->temp->allocate(numElems * sizeof(T)));
}
template <typename T>
T* pod_malloc(size_t numElems) {
return maybe_pod_malloc<T>(numElems);
}
void free_(void* p) {
}
void reportAllocOverflow() const {

View File

@ -31,11 +31,16 @@ struct ContextFriendFields;
class SystemAllocPolicy
{
public:
template <typename T> T* pod_malloc(size_t numElems) { return js_pod_malloc<T>(numElems); }
template <typename T> T* pod_calloc(size_t numElems) { return js_pod_calloc<T>(numElems); }
template <typename T> T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
template <typename T> T* maybe_pod_malloc(size_t numElems) { return js_pod_malloc<T>(numElems); }
template <typename T> T* maybe_pod_calloc(size_t numElems) { return js_pod_calloc<T>(numElems); }
template <typename T> T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) {
return js_pod_realloc<T>(p, oldSize, newSize);
}
template <typename T> T* pod_malloc(size_t numElems) { return maybe_pod_malloc<T>(numElems); }
template <typename T> T* pod_calloc(size_t numElems) { return maybe_pod_calloc<T>(numElems); }
template <typename T> T* pod_realloc(T* p, size_t oldSize, size_t newSize) {
return maybe_pod_realloc<T>(p, oldSize, newSize);
}
void free_(void* p) { js_free(p); }
void reportAllocOverflow() const {}
bool checkSimulatedOOM() const {
@ -70,9 +75,24 @@ class TempAllocPolicy
MOZ_IMPLICIT TempAllocPolicy(JSContext* cx) : cx_((ContextFriendFields*) cx) {} // :(
MOZ_IMPLICIT TempAllocPolicy(ContextFriendFields* cx) : cx_(cx) {}
template <typename T>
T* maybe_pod_malloc(size_t numElems) {
return js_pod_malloc<T>(numElems);
}
template <typename T>
T* maybe_pod_calloc(size_t numElems) {
return js_pod_calloc<T>(numElems);
}
template <typename T>
T* maybe_pod_realloc(T* prior, size_t oldSize, size_t newSize) {
return js_pod_realloc<T>(prior, oldSize, newSize);
}
template <typename T>
T* pod_malloc(size_t numElems) {
T* p = js_pod_malloc<T>(numElems);
T* p = maybe_pod_malloc<T>(numElems);
if (MOZ_UNLIKELY(!p))
p = static_cast<T*>(onOutOfMemory(AllocFunction::Malloc, numElems * sizeof(T)));
return p;
@ -80,7 +100,7 @@ class TempAllocPolicy
template <typename T>
T* pod_calloc(size_t numElems) {
T* p = js_pod_calloc<T>(numElems);
T* p = maybe_pod_calloc<T>(numElems);
if (MOZ_UNLIKELY(!p))
p = static_cast<T*>(onOutOfMemory(AllocFunction::Calloc, numElems * sizeof(T)));
return p;
@ -88,7 +108,7 @@ class TempAllocPolicy
template <typename T>
T* pod_realloc(T* prior, size_t oldSize, size_t newSize) {
T* p2 = js_pod_realloc<T>(prior, oldSize, newSize);
T* p2 = maybe_pod_realloc<T>(prior, oldSize, newSize);
if (MOZ_UNLIKELY(!p2))
p2 = static_cast<T*>(onOutOfMemory(AllocFunction::Realloc, newSize * sizeof(T), prior));
return p2;

View File

@ -23,8 +23,6 @@
* - TempAllocPolicy: Adds automatic error reporting to the provided
* Context when allocations fail.
*
* - ContextAllocPolicy: forwards to the JSContext MallocProvider.
*
* - RuntimeAllocPolicy: forwards to the JSRuntime MallocProvider.
*
* - MallocProvider. A mixin base class that handles automatically updating
@ -52,6 +50,36 @@ namespace js {
template<class Client>
struct MallocProvider
{
template <class T>
T* maybe_pod_malloc(size_t numElems) {
size_t bytes = numElems * sizeof(T);
T* p = js_pod_malloc<T>(numElems);
if (MOZ_LIKELY(p))
client()->updateMallocCounter(bytes);
return p;
}
template <class T>
T* maybe_pod_calloc(size_t numElems) {
size_t bytes = numElems * sizeof(T);
T* p = js_pod_calloc<T>(numElems);
if (MOZ_LIKELY(p))
client()->updateMallocCounter(bytes);
return p;
}
template <class T>
T* maybe_pod_realloc(T* prior, size_t oldSize, size_t newSize) {
T* p = js_pod_realloc(prior, oldSize, newSize);
if (MOZ_LIKELY(p)) {
// For compatibility we do not account for realloc that decreases
// previously allocated memory.
if (newSize > oldSize)
client()->updateMallocCounter((newSize - oldSize) * sizeof(T));
}
return p;
}
template <class T>
T* pod_malloc() {
return pod_malloc<T>(1);
@ -59,16 +87,14 @@ struct MallocProvider
template <class T>
T* pod_malloc(size_t numElems) {
size_t bytes = numElems * sizeof(T);
T* p = js_pod_malloc<T>(numElems);
if (MOZ_LIKELY(p)) {
client()->updateMallocCounter(bytes);
T* p = maybe_pod_malloc<T>(numElems);
if (MOZ_LIKELY(p))
return p;
}
if (numElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
client()->reportAllocationOverflow();
return nullptr;
}
size_t bytes = numElems * sizeof(T);
p = (T*)client()->onOutOfMemory(AllocFunction::Malloc, bytes);
if (p)
client()->updateMallocCounter(bytes);
@ -110,16 +136,14 @@ struct MallocProvider
template <class T>
T* pod_calloc(size_t numElems) {
size_t bytes = numElems * sizeof(T);
T* p = js_pod_calloc<T>(numElems);
if (MOZ_LIKELY(p)) {
client()->updateMallocCounter(bytes);
T* p = maybe_pod_calloc<T>(numElems);
if (MOZ_LIKELY(p))
return p;
}
if (numElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
client()->reportAllocationOverflow();
return nullptr;
}
size_t bytes = numElems * sizeof(T);
p = (T*)client()->onOutOfMemory(AllocFunction::Calloc, bytes);
if (p)
client()->updateMallocCounter(bytes);
@ -157,14 +181,9 @@ struct MallocProvider
template <class T>
T* pod_realloc(T* prior, size_t oldSize, size_t newSize) {
T* p = js_pod_realloc(prior, oldSize, newSize);
if (MOZ_LIKELY(p)) {
// For compatibility we do not account for realloc that decreases
// previously allocated memory.
if (newSize > oldSize)
client()->updateMallocCounter((newSize - oldSize) * sizeof(T));
T* p = maybe_pod_realloc(prior, oldSize, newSize);
if (MOZ_LIKELY(p))
return p;
}
if (newSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
client()->reportAllocationOverflow();
return nullptr;

View File

@ -2070,6 +2070,21 @@ class RuntimeAllocPolicy
public:
MOZ_IMPLICIT RuntimeAllocPolicy(JSRuntime* rt) : runtime(rt) {}
template <typename T>
T* maybe_pod_malloc(size_t numElems) {
return runtime->maybe_pod_malloc<T>(numElems);
}
template <typename T>
T* maybe_pod_calloc(size_t numElems) {
return runtime->maybe_pod_calloc<T>(numElems);
}
template <typename T>
T* maybe_pod_realloc(T* p, size_t oldSize, size_t newSize) {
return runtime->maybe_pod_realloc<T>(p, oldSize, newSize);
}
template <typename T>
T* pod_malloc(size_t numElems) {
return runtime->pod_malloc<T>(numElems);

View File

@ -63,10 +63,15 @@ private:
void *malloc_(size_t bytes) { return ::malloc(bytes); }
template <typename T>
T *pod_calloc(size_t numElems) {
T *maybe_pod_calloc(size_t numElems) {
return static_cast<T *>(::calloc(numElems, sizeof(T)));
}
template <typename T>
T *pod_calloc(size_t numElems) {
return maybe_pod_calloc<T>(numElems);
}
void *realloc_(void *p, size_t bytes) { return ::realloc(p, bytes); }
void free_(void *p) { ::free(p); }
void reportAllocOverflow() const {}

View File

@ -134,6 +134,28 @@ class InfallibleAllocPolicy
static void ExitOnFailure(const void* aP);
public:
template <typename T>
static T* maybe_pod_malloc(size_t aNumElems)
{
if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value)
return nullptr;
return (T*)gMallocTable->malloc(aNumElems * sizeof(T));
}
template <typename T>
static T* maybe_pod_calloc(size_t aNumElems)
{
return (T*)gMallocTable->calloc(aNumElems, sizeof(T));
}
template <typename T>
static T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
{
if (aNewSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value)
return nullptr;
return (T*)gMallocTable->realloc(aPtr, aNewSize * sizeof(T));
}
static void* malloc_(size_t aSize)
{
void* p = gMallocTable->malloc(aSize);
@ -144,11 +166,9 @@ public:
template <typename T>
static T* pod_malloc(size_t aNumElems)
{
if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value)
return nullptr;
void* p = gMallocTable->malloc(aNumElems * sizeof(T));
T* p = maybe_pod_malloc<T>(aNumElems);
ExitOnFailure(p);
return (T*)p;
return p;
}
static void* calloc_(size_t aSize)
@ -161,9 +181,9 @@ public:
template <typename T>
static T* pod_calloc(size_t aNumElems)
{
void* p = gMallocTable->calloc(aNumElems, sizeof(T));
T* p = maybe_pod_calloc<T>(aNumElems);
ExitOnFailure(p);
return (T*)p;
return p;
}
// This realloc_ is the one we use for direct reallocs within DMD.
@ -178,9 +198,9 @@ public:
template <typename T>
static T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
{
if (aNewSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value)
return nullptr;
return (T*)InfallibleAllocPolicy::realloc_((void *)aPtr, aNewSize * sizeof(T));
T* p = maybe_pod_realloc(aPtr, aOldSize, aNewSize);
ExitOnFailure(p);
return p;
}
static void* memalign_(size_t aAlignment, size_t aSize)

View File

@ -26,6 +26,13 @@ namespace mozilla {
* mechanism when OOM occurs. The concept modeled here is as follows:
*
* - public copy constructor, assignment, destructor
* - template <typename T> T* maybe_pod_malloc(size_t)
* Fallible, but doesn't report an error on OOM.
* - template <typename T> T* maybe_pod_calloc(size_t)
* Fallible, but doesn't report an error on OOM.
* - template <typename T> T* maybe_pod_realloc(T*, size_t, size_t)
* Fallible, but doesn't report an error on OOM. The old allocation
* size is passed in, in addition to the new allocation size requested.
* - template <typename T> T* pod_malloc(size_t)
* Responsible for OOM reporting when null is returned.
* - template <typename T> T* pod_calloc(size_t)
@ -64,7 +71,7 @@ class MallocAllocPolicy
{
public:
template <typename T>
T* pod_malloc(size_t aNumElems)
T* maybe_pod_malloc(size_t aNumElems)
{
if (aNumElems & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
return nullptr;
@ -73,13 +80,13 @@ public:
}
template <typename T>
T* pod_calloc(size_t aNumElems)
T* maybe_pod_calloc(size_t aNumElems)
{
return static_cast<T*>(calloc(aNumElems, sizeof(T)));
}
template <typename T>
T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
T* maybe_pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
{
if (aNewSize & mozilla::tl::MulOverflowMask<sizeof(T)>::value) {
return nullptr;
@ -87,6 +94,24 @@ public:
return static_cast<T*>(realloc(aPtr, aNewSize * sizeof(T)));
}
template <typename T>
T* pod_malloc(size_t aNumElems)
{
return maybe_pod_malloc<T>(aNumElems);
}
template <typename T>
T* pod_calloc(size_t aNumElems)
{
return maybe_pod_calloc<T>(aNumElems);
}
template <typename T>
T* pod_realloc(T* aPtr, size_t aOldSize, size_t aNewSize)
{
return maybe_pod_realloc<T>(aPtr, aOldSize, aNewSize);
}
void free_(void* aPtr)
{
free(aPtr);