Bug 1746699 - Avoid UB when allocating JSStrings and BigInts r=tcampbell

Add emplace methods to JSString and BigInt types to reduce amount of C++
undefined-behaviour by running the constructor of the concrete derived type.
This is in contrast to previous behaviour of allocating a gc::Cell and then
simply casting to the final type. We still first allocate as a Cell from the GC
and then use placement-new to initialize as the correct type.

Differential Revision: https://phabricator.services.mozilla.com/D134095
This commit is contained in:
Steve Fink 2022-08-25 23:26:51 +00:00
Родитель 23fc627ed6
Коммит 876f784fce
10 изменённых файлов: 155 добавлений и 76 удалений

Просмотреть файл

@ -6,6 +6,8 @@
#include "gc/Allocator.h"
#include "mozilla/DebugOnly.h"
#include "mozilla/OperatorNewExtensions.h"
#include "mozilla/TimeStamp.h"
#include "gc/GCInternals.h"
@ -150,25 +152,27 @@ JSObject* GCRuntime::tryNewTenuredObject(JSContext* cx, AllocKind kind,
Debug_SetSlotRangeToCrashOnTouch(slotsHeader->slots(), nDynamicSlots);
}
JSObject* obj = tryNewTenuredThing<JSObject, allowGC>(cx, kind, thingSize);
if (obj) {
if (nDynamicSlots) {
static_cast<NativeObject*>(obj)->initSlots(slotsHeader->slots());
AddCellMemory(obj, ObjectSlots::allocSize(nDynamicSlots),
MemoryUse::ObjectSlots);
}
} else {
TenuredCell* cell = tryNewTenuredThing<allowGC>(cx, kind, thingSize);
if (!cell) {
js_free(slotsHeader);
return nullptr;
}
return obj;
if (nDynamicSlots) {
NativeObject* nobj = new (mozilla::KnownNotNull, cell) NativeObject();
nobj->initSlots(slotsHeader->slots());
AddCellMemory(nobj, ObjectSlots::allocSize(nDynamicSlots),
MemoryUse::ObjectSlots);
return nobj;
}
return new (mozilla::KnownNotNull, cell) JSObject();
}
// Attempt to allocate a new string out of the nursery. If there is not enough
// room in the nursery or there is an OOM, this method will return nullptr.
template <AllowGC allowGC>
JSString* GCRuntime::tryNewNurseryString(JSContext* cx, size_t thingSize,
Cell* GCRuntime::tryNewNurseryStringCell(JSContext* cx, size_t thingSize,
AllocKind kind) {
MOZ_ASSERT(IsNurseryAllocable(kind));
MOZ_ASSERT(cx->isNurseryAllocAllowed());
@ -178,7 +182,7 @@ JSString* GCRuntime::tryNewNurseryString(JSContext* cx, size_t thingSize,
AllocSite* site = cx->zone()->unknownAllocSite();
Cell* cell = cx->nursery().allocateString(site, thingSize);
if (cell) {
return static_cast<JSString*>(cell);
return cell;
}
if (allowGC && !cx->suppressGC) {
@ -187,16 +191,15 @@ JSString* GCRuntime::tryNewNurseryString(JSContext* cx, size_t thingSize,
// Exceeding gcMaxBytes while tenuring can disable the Nursery, and
// other heuristics can disable nursery strings for this zone.
if (cx->nursery().isEnabled() && cx->zone()->allocNurseryStrings) {
return static_cast<JSString*>(
cx->nursery().allocateString(site, thingSize));
return cx->nursery().allocateString(site, thingSize);
}
}
return nullptr;
}
template <AllowGC allowGC /* = CanGC */>
JSString* js::AllocateStringImpl(JSContext* cx, AllocKind kind, size_t size,
InitialHeap heap) {
Cell* js::AllocateStringCell(JSContext* cx, AllocKind kind, size_t size,
InitialHeap heap) {
MOZ_ASSERT(!cx->isHelperThreadContext());
MOZ_ASSERT(size == Arena::thingSize(kind));
MOZ_ASSERT(size == sizeof(JSString) || size == sizeof(JSFatInlineString));
@ -205,12 +208,11 @@ JSString* js::AllocateStringImpl(JSContext* cx, AllocKind kind, size_t size,
// Off-thread alloc cannot trigger GC or make runtime assertions.
if (cx->isNurseryAllocSuppressed()) {
JSString* str =
GCRuntime::tryNewTenuredThing<JSString, NoGC>(cx, kind, size);
if (MOZ_UNLIKELY(allowGC && !str)) {
TenuredCell* cell = GCRuntime::tryNewTenuredThing<NoGC>(cx, kind, size);
if (MOZ_UNLIKELY(allowGC && !cell)) {
ReportOutOfMemory(cx);
}
return str;
return cell;
}
JSRuntime* rt = cx->runtime();
@ -220,10 +222,9 @@ JSString* js::AllocateStringImpl(JSContext* cx, AllocKind kind, size_t size,
if (cx->nursery().isEnabled() && heap != TenuredHeap &&
cx->nursery().canAllocateStrings() && cx->zone()->allocNurseryStrings) {
auto* str = static_cast<JSString*>(
rt->gc.tryNewNurseryString<allowGC>(cx, size, kind));
if (str) {
return str;
Cell* cell = rt->gc.tryNewNurseryStringCell<allowGC>(cx, size, kind);
if (cell) {
return cell;
}
// Our most common non-jit allocation path is NoGC; thus, if we fail the
@ -236,13 +237,13 @@ JSString* js::AllocateStringImpl(JSContext* cx, AllocKind kind, size_t size,
}
}
return GCRuntime::tryNewTenuredThing<JSString, allowGC>(cx, kind, size);
return GCRuntime::tryNewTenuredThing<allowGC>(cx, kind, size);
}
template JSString* js::AllocateStringImpl<NoGC>(JSContext*, AllocKind, size_t,
InitialHeap);
template JSString* js::AllocateStringImpl<CanGC>(JSContext*, AllocKind, size_t,
InitialHeap);
template Cell* js::AllocateStringCell<NoGC>(JSContext*, AllocKind, size_t,
InitialHeap);
template Cell* js::AllocateStringCell<CanGC>(JSContext*, AllocKind, size_t,
InitialHeap);
// Attempt to allocate a new BigInt out of the nursery. If there is not enough
// room in the nursery or there is an OOM, this method will return nullptr.
@ -257,7 +258,7 @@ JS::BigInt* GCRuntime::tryNewNurseryBigInt(JSContext* cx, size_t thingSize,
AllocSite* site = cx->zone()->unknownAllocSite();
Cell* cell = cx->nursery().allocateBigInt(site, thingSize);
if (cell) {
return static_cast<JS::BigInt*>(cell);
return JS::BigInt::emplace(cell);
}
if (allowGC && !cx->suppressGC) {
@ -266,8 +267,10 @@ JS::BigInt* GCRuntime::tryNewNurseryBigInt(JSContext* cx, size_t thingSize,
// Exceeding gcMaxBytes while tenuring can disable the Nursery, and
// other heuristics can disable nursery BigInts for this zone.
if (cx->nursery().isEnabled() && cx->zone()->allocNurseryBigInts) {
return static_cast<JS::BigInt*>(
cx->nursery().allocateBigInt(site, thingSize));
Cell* cell = cx->nursery().allocateBigInt(site, thingSize);
if (cell) {
return JS::BigInt::emplace(cell);
}
}
}
return nullptr;
@ -283,12 +286,12 @@ JS::BigInt* js::AllocateBigInt(JSContext* cx, InitialHeap heap) {
// Off-thread alloc cannot trigger GC or make runtime assertions.
if (cx->isNurseryAllocSuppressed()) {
JS::BigInt* bi =
GCRuntime::tryNewTenuredThing<JS::BigInt, NoGC>(cx, kind, size);
if (MOZ_UNLIKELY(allowGC && !bi)) {
TenuredCell* cell = GCRuntime::tryNewTenuredThing<NoGC>(cx, kind, size);
if (MOZ_UNLIKELY(allowGC && !cell)) {
ReportOutOfMemory(cx);
return nullptr;
}
return bi;
return JS::BigInt::emplace(cell);
}
JSRuntime* rt = cx->runtime();
@ -298,8 +301,7 @@ JS::BigInt* js::AllocateBigInt(JSContext* cx, InitialHeap heap) {
if (cx->nursery().isEnabled() && heap != TenuredHeap &&
cx->nursery().canAllocateBigInts() && cx->zone()->allocNurseryBigInts) {
auto* bi = static_cast<JS::BigInt*>(
rt->gc.tryNewNurseryBigInt<allowGC>(cx, size, kind));
auto* bi = rt->gc.tryNewNurseryBigInt<allowGC>(cx, size, kind);
if (bi) {
return bi;
}
@ -314,7 +316,12 @@ JS::BigInt* js::AllocateBigInt(JSContext* cx, InitialHeap heap) {
}
}
return GCRuntime::tryNewTenuredThing<JS::BigInt, allowGC>(cx, kind, size);
TenuredCell* cell = GCRuntime::tryNewTenuredThing<allowGC>(cx, kind, size);
if (!cell) {
return nullptr;
}
return JS::BigInt::emplace(cell);
}
template JS::BigInt* js::AllocateBigInt<NoGC>(JSContext* cx,
gc::InitialHeap heap);
@ -322,7 +329,8 @@ template JS::BigInt* js::AllocateBigInt<CanGC>(JSContext* cx,
gc::InitialHeap heap);
template <AllowGC allowGC /* = CanGC */>
Cell* js::AllocateTenuredImpl(JSContext* cx, gc::AllocKind kind, size_t size) {
TenuredCell* js::AllocateTenuredImpl(JSContext* cx, gc::AllocKind kind,
size_t size) {
MOZ_ASSERT(!cx->isHelperThreadContext());
MOZ_ASSERT(!IsNurseryAllocable(kind));
MOZ_ASSERT(size == Arena::thingSize(kind));
@ -334,46 +342,49 @@ Cell* js::AllocateTenuredImpl(JSContext* cx, gc::AllocKind kind, size_t size) {
return nullptr;
}
return GCRuntime::tryNewTenuredThing<Cell, allowGC>(cx, kind, size);
return GCRuntime::tryNewTenuredThing<allowGC>(cx, kind, size);
}
template Cell* js::AllocateTenuredImpl<NoGC>(JSContext*, AllocKind, size_t);
template Cell* js::AllocateTenuredImpl<CanGC>(JSContext*, AllocKind, size_t);
template TenuredCell* js::AllocateTenuredImpl<NoGC>(JSContext*, AllocKind,
size_t);
template TenuredCell* js::AllocateTenuredImpl<CanGC>(JSContext*, AllocKind,
size_t);
template <typename T, AllowGC allowGC>
template <AllowGC allowGC>
/* static */
T* GCRuntime::tryNewTenuredThing(JSContext* cx, AllocKind kind,
size_t thingSize) {
TenuredCell* GCRuntime::tryNewTenuredThing(JSContext* cx, AllocKind kind,
size_t thingSize) {
// Bump allocate in the arena's current free-list span.
Zone* zone = cx->zone();
auto* t = reinterpret_cast<T*>(zone->arenas.freeLists().allocate(kind));
void* t = zone->arenas.freeLists().allocate(kind);
if (MOZ_UNLIKELY(!t)) {
// Get the next available free list and allocate out of it. This may
// acquire a new arena, which will lock the chunk list. If there are no
// chunks available it may also allocate new memory directly.
t = reinterpret_cast<T*>(refillFreeList(cx, kind));
t = refillFreeList(cx, kind);
if (MOZ_UNLIKELY(!t)) {
if (allowGC) {
if constexpr (allowGC) {
cx->runtime()->gc.attemptLastDitchGC(cx);
t = tryNewTenuredThing<T, NoGC>(cx, kind, thingSize);
}
if (!t) {
if (allowGC) {
ReportOutOfMemory(cx);
TenuredCell* cell = tryNewTenuredThing<NoGC>(cx, kind, thingSize);
if (cell) {
return cell;
}
return nullptr;
ReportOutOfMemory(cx);
}
return nullptr;
}
}
checkIncrementalZoneState(cx, t);
gcprobes::TenuredAlloc(t, kind);
TenuredCell* cell = new (mozilla::KnownNotNull, t) TenuredCell();
checkIncrementalZoneState(cx, cell);
gcprobes::TenuredAlloc(cell, kind);
// We count this regardless of the profiler's state, assuming that it costs
// just as much to count it, as to check the profiler's state and decide not
// to count it.
zone->noteTenuredAlloc();
return t;
return cell;
}
void GCRuntime::attemptLastDitchGC(JSContext* cx) {

Просмотреть файл

@ -17,6 +17,7 @@ namespace js {
namespace gc {
class AllocSite;
struct Cell;
class TenuredCell;
} // namespace gc
// [SMDOC] AllowGC template parameter
@ -37,9 +38,10 @@ enum AllowGC { NoGC = 0, CanGC = 1 };
// Allocator implementation functions.
template <AllowGC allowGC = CanGC>
gc::Cell* AllocateTenuredImpl(JSContext* cx, gc::AllocKind kind, size_t size);
gc::TenuredCell* AllocateTenuredImpl(JSContext* cx, gc::AllocKind kind,
size_t size);
template <AllowGC allowGC = CanGC>
JSString* AllocateStringImpl(JSContext* cx, gc::AllocKind kind, size_t size,
gc::Cell* AllocateStringCell(JSContext* cx, gc::AllocKind kind, size_t size,
gc::InitialHeap heap);
// Allocate a new tenured GC thing that's not nursery-allocatable.
@ -74,9 +76,11 @@ template <typename StringT, AllowGC allowGC = CanGC>
StringT* AllocateString(JSContext* cx, gc::InitialHeap heap) {
static_assert(std::is_base_of_v<JSString, StringT>);
gc::AllocKind kind = gc::MapTypeToAllocKind<StringT>::kind;
JSString* string =
AllocateStringImpl<allowGC>(cx, kind, sizeof(StringT), heap);
return static_cast<StringT*>(string);
gc::Cell* cell = AllocateStringCell<allowGC>(cx, kind, sizeof(StringT), heap);
if (!cell) {
return nullptr;
}
return StringT::emplace(cell);
}
// Allocate a BigInt.

Просмотреть файл

@ -629,10 +629,11 @@ class GCRuntime {
template <AllowGC allowGC>
static JSObject* tryNewTenuredObject(JSContext* cx, AllocKind kind,
size_t thingSize, size_t nDynamicSlots);
template <typename T, AllowGC allowGC>
static T* tryNewTenuredThing(JSContext* cx, AllocKind kind, size_t thingSize);
template <AllowGC allowGC>
JSString* tryNewNurseryString(JSContext* cx, size_t thingSize,
static TenuredCell* tryNewTenuredThing(JSContext* cx, AllocKind kind,
size_t thingSize);
template <AllowGC allowGC>
Cell* tryNewNurseryStringCell(JSContext* cx, size_t thingSize,
AllocKind kind);
template <AllowGC allowGC>
JS::BigInt* tryNewNurseryBigInt(JSContext* cx, size_t thingSize,

Просмотреть файл

@ -103,7 +103,7 @@ namespace jit {
_(js::jit::AllocateAndInitTypedArrayBuffer) \
_(js::jit::AllocateBigIntNoGC) \
_(js::jit::AllocateFatInlineString) \
_(js::jit::AllocateString) \
_(js::jit::AllocateDependentString) \
_(js::jit::AssertMapObjectHash) \
_(js::jit::AssertSetObjectHash) \
_(js::jit::AssertValidBigIntPtr) \

Просмотреть файл

@ -2459,7 +2459,7 @@ void CreateDependentString::generateFallback(MacroAssembler& masm) {
if (kind == FallbackKind::FatInlineString) {
masm.callWithABI<Fn, AllocateFatInlineString>();
} else {
masm.callWithABI<Fn, AllocateString>();
masm.callWithABI<Fn, AllocateDependentString>();
}
masm.storeCallPointerResult(string_);

Просмотреть файл

@ -2081,9 +2081,9 @@ bool IsPossiblyWrappedTypedArray(JSContext* cx, JSObject* obj, bool* result) {
}
// Called from CreateDependentString::generateFallback.
void* AllocateString(JSContext* cx) {
void* AllocateDependentString(JSContext* cx) {
AutoUnsafeCallWithABI unsafe;
return js::AllocateString<JSString, NoGC>(cx, js::gc::DefaultHeap);
return js::AllocateString<JSDependentString, NoGC>(cx, js::gc::DefaultHeap);
}
void* AllocateFatInlineString(JSContext* cx) {
AutoUnsafeCallWithABI unsafe;

Просмотреть файл

@ -577,7 +577,7 @@ bool DoConcatStringObject(JSContext* cx, HandleValue lhs, HandleValue rhs,
bool IsPossiblyWrappedTypedArray(JSContext* cx, JSObject* obj, bool* result);
void* AllocateString(JSContext* cx);
void* AllocateDependentString(JSContext* cx);
void* AllocateFatInlineString(JSContext* cx);
void* AllocateBigIntNoGC(JSContext* cx, bool requestMinorGC);
void AllocateAndInitTypedArrayBuffer(JSContext* cx, TypedArrayObject* obj,

Просмотреть файл

@ -8,6 +8,7 @@
#define vm_BigIntType_h
#include "mozilla/Assertions.h"
#include "mozilla/OperatorNewExtensions.h"
#include "mozilla/Range.h"
#include "mozilla/Span.h"
@ -64,6 +65,10 @@ class BigInt final : public js::gc::CellWithLengthAndFlags {
public:
static const JS::TraceKind TraceKind = JS::TraceKind::BigInt;
static BigInt* emplace(js::gc::Cell* cell) {
return new (mozilla::KnownNotNull, cell) BigInt();
}
void fixupAfterMovingGC() {}
js::gc::AllocKind getAllocKind() const { return js::gc::AllocKind::BIGINT; }
@ -411,7 +416,6 @@ class BigInt final : public js::gc::CellWithLengthAndFlags {
friend struct ::JSStructuredCloneReader;
friend struct ::JSStructuredCloneWriter;
BigInt() = delete;
BigInt(const BigInt& other) = delete;
void operator=(const BigInt& other) = delete;
@ -436,6 +440,10 @@ class BigInt final : public js::gc::CellWithLengthAndFlags {
private:
friend class js::TenuringTracer;
protected:
// For calling by emplace().
BigInt() = default;
};
static_assert(

Просмотреть файл

@ -555,9 +555,13 @@ class JSObject
static constexpr size_t offsetOfShape() { return offsetOfHeaderPtr(); }
private:
JSObject() = delete;
JSObject(const JSObject& other) = delete;
void operator=(const JSObject& other) = delete;
protected:
// For the allocator only, to be used with placement new.
friend class js::gc::GCRuntime;
JSObject() = default;
};
template <>

Просмотреть файл

@ -706,9 +706,12 @@ class JSString : public js::gc::CellWithLengthAndFlags {
}
private:
JSString() = delete;
JSString(const JSString& other) = delete;
void operator=(const JSString& other) = delete;
protected:
// For calling by concrete subclasses' emplace() static methods.
JSString() = default;
};
class JSRope : public JSString {
@ -785,6 +788,9 @@ class JSRope : public JSString {
static size_t offsetOfLeft() { return offsetof(JSRope, d.s.u2.left); }
static size_t offsetOfRight() { return offsetof(JSRope, d.s.u3.right); }
public:
static JSRope* emplace(Cell* cell) { return new (cell) JSRope(); }
};
static_assert(sizeof(JSRope) == sizeof(JSString),
@ -829,6 +835,10 @@ class JSLinearString : public JSString {
void init(const char16_t* chars, size_t length);
void init(const JS::Latin1Char* chars, size_t length);
static JSLinearString* emplace(Cell* cell) {
return new (cell) JSLinearString();
}
template <js::AllowGC allowGC, typename CharT>
static inline JSLinearString* new_(
JSContext* cx, js::UniquePtr<CharT[], JS::FreePolicy> chars,
@ -988,6 +998,12 @@ class JSDependentString : public JSLinearString {
}
public:
static JSDependentString* emplace(Cell* cell) {
return new (cell) JSDependentString();
}
// This may return an inline string if the chars fit rather than a dependent
// string.
static inline JSLinearString* new_(JSContext* cx, JSLinearString* base,
size_t start, size_t length,
js::gc::InitialHeap heap);
@ -1036,6 +1052,10 @@ static_assert(sizeof(JSExtensibleString) == sizeof(JSString),
class JSInlineString : public JSLinearString {
public:
static JSInlineString* emplace(Cell* cell) {
return new (cell) JSInlineString();
}
MOZ_ALWAYS_INLINE
const JS::Latin1Char* latin1Chars(const JS::AutoRequireNoGC& nogc) const {
MOZ_ASSERT(JSString::isInline());
@ -1079,6 +1099,10 @@ class JSThinInlineString : public JSInlineString {
static const size_t MAX_LENGTH_LATIN1 = NUM_INLINE_CHARS_LATIN1;
static const size_t MAX_LENGTH_TWO_BYTE = NUM_INLINE_CHARS_TWO_BYTE;
static JSThinInlineString* emplace(Cell* cell) {
return new (cell) JSThinInlineString();
}
template <js::AllowGC allowGC>
static inline JSThinInlineString* new_(JSContext* cx,
js::gc::InitialHeap heap);
@ -1120,6 +1144,10 @@ class JSFatInlineString : public JSInlineString {
};
public:
static JSFatInlineString* emplace(Cell* cell) {
return new (cell) JSFatInlineString();
}
template <js::AllowGC allowGC>
static inline JSFatInlineString* new_(JSContext* cx,
js::gc::InitialHeap heap);
@ -1156,6 +1184,10 @@ class JSExternalString : public JSLinearString {
JSExternalString& asExternal() const = delete;
public:
static JSExternalString* emplace(Cell* cell) {
return new (cell) JSExternalString();
}
static inline JSExternalString* new_(
JSContext* cx, const char16_t* chars, size_t length,
const JSExternalStringCallbacks* callbacks);
@ -1242,6 +1274,8 @@ class NormalAtom : public JSAtom {
HashNumber hash_;
public:
static NormalAtom* emplace(Cell* cell) { return new (cell) NormalAtom(); }
HashNumber hash() const { return hash_; }
void initHash(HashNumber hash) { hash_ = hash; }
@ -1258,6 +1292,11 @@ class FatInlineAtom : public JSAtom {
HashNumber hash_;
public:
// For a fresh allocation.
static FatInlineAtom* emplace(Cell* cell) {
return new (cell) FatInlineAtom();
}
HashNumber hash() const { return hash_; }
void initHash(HashNumber hash) { hash_ = hash; }
@ -1289,11 +1328,18 @@ inline void JSAtom::initHash(js::HashNumber hash) {
return static_cast<js::NormalAtom*>(this)->initHash(hash);
}
// Note that any pre-existing pointers to this string are invalidated
// (UB, though they will most likely be fine.)
MOZ_ALWAYS_INLINE JSAtom* JSLinearString::morphAtomizedStringIntoAtom(
js::HashNumber hash) {
MOZ_ASSERT(!isAtom());
setFlagBit(ATOM_BIT);
JSAtom* atom = &asAtom();
if (isFatInline()) {
auto* atom = static_cast<js::FatInlineAtom*>(this);
atom->initHash(hash);
return atom;
}
auto* atom = static_cast<js::NormalAtom*>(this);
atom->initHash(hash);
return atom;
}
@ -1302,7 +1348,12 @@ MOZ_ALWAYS_INLINE JSAtom* JSLinearString::morphAtomizedStringIntoPermanentAtom(
js::HashNumber hash) {
MOZ_ASSERT(!isAtom());
setFlagBit(PERMANENT_ATOM_MASK);
JSAtom* atom = &asAtom();
if (isFatInline()) {
auto* atom = static_cast<js::FatInlineAtom*>(this);
atom->initHash(hash);
return atom;
}
auto* atom = static_cast<js::NormalAtom*>(this);
atom->initHash(hash);
return atom;
}