Bug 1283334 - Part 1: Do not sparsify dense arrays when freezing - Interpreter. r=jandem

--HG--
extra : rebase_source : 8a7a05f735a7a320a2368845a2d06b349696a785
This commit is contained in:
Leo Gaspard 2016-08-29 15:00:35 -07:00
parent 513c6f555b
commit 2db25444e0
7 changed files with 80 additions and 30 deletions

View File

@ -2941,6 +2941,15 @@ JS_FreezeObject(JSContext* cx, HandleObject obj)
return FreezeObject(cx, obj);
}
static bool
DeepFreezeSlot(JSContext* cx, const Value& v)
{
if (v.isPrimitive())
return true;
RootedObject obj(cx, &v.toObject());
return JS_DeepFreezeObject(cx, obj);
}
JS_PUBLIC_API(bool)
JS_DeepFreezeObject(JSContext* cx, HandleObject obj)
{
@ -2960,12 +2969,13 @@ JS_DeepFreezeObject(JSContext* cx, HandleObject obj)
/* Walk slots in obj and if any value is a non-null object, seal it. */
if (obj->isNative()) {
for (uint32_t i = 0, n = obj->as<NativeObject>().slotSpan(); i < n; ++i) {
const Value& v = obj->as<NativeObject>().getSlot(i);
if (v.isPrimitive())
continue;
RootedObject obj(cx, &v.toObject());
if (!JS_DeepFreezeObject(cx, obj))
RootedNativeObject nobj(cx, &obj->as<NativeObject>());
for (uint32_t i = 0, n = nobj->slotSpan(); i < n; ++i) {
if (!DeepFreezeSlot(cx, nobj->getSlot(i)))
return false;
}
for (uint32_t i = 0, n = nobj->getDenseInitializedLength(); i < n; ++i) {
if (!DeepFreezeSlot(cx, nobj->getDenseElement(i)))
return false;
}
}

View File

@ -476,7 +476,7 @@ js::SetIntegrityLevel(JSContext* cx, HandleObject obj, IntegrityLevel level)
assertSameCompartment(cx, obj);
// Steps 3-5. (Steps 1-2 are redundant assertions.)
if (!PreventExtensions(cx, obj))
if (!PreventExtensions(cx, obj, level))
return false;
// Steps 6-7.
@ -484,10 +484,6 @@ js::SetIntegrityLevel(JSContext* cx, HandleObject obj, IntegrityLevel level)
if (!GetPropertyKeys(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY | JSITER_SYMBOLS, &keys))
return false;
// PreventExtensions must sparsify dense objects, so we can assign to holes
// without checks.
MOZ_ASSERT_IF(obj->isNative(), obj->as<NativeObject>().getDenseCapacity() == 0);
// Steps 8-9, loosely interpreted.
if (obj->isNative() && !obj->as<NativeObject>().inDictionaryMode() &&
!obj->is<TypedArrayObject>())
@ -2640,7 +2636,7 @@ js::SetPrototype(JSContext* cx, HandleObject obj, HandleObject proto)
}
bool
js::PreventExtensions(JSContext* cx, HandleObject obj, ObjectOpResult& result)
js::PreventExtensions(JSContext* cx, HandleObject obj, ObjectOpResult& result, IntegrityLevel level)
{
if (obj->is<ProxyObject>())
return js::Proxy::preventExtensions(cx, obj, result);
@ -2656,13 +2652,19 @@ js::PreventExtensions(JSContext* cx, HandleObject obj, ObjectOpResult& result)
if (!js::GetPropertyKeys(cx, obj, JSITER_HIDDEN | JSITER_OWNONLY, &props))
return false;
// Convert all dense elements to sparse properties. This will shrink the
// initialized length and capacity of the object to zero and ensure that no
// new dense elements can be added without calling growElements(), which
// checks isExtensible().
// Actually prevent extension. If the object is being frozen, do it by
// setting the frozen flag on both the object and the object group.
// Otherwise, fallback to sparsifying the object, which makes sure no
// element can be added without a call to isExtensible, at the cost of
// performance.
if (obj->isNative()) {
if (!NativeObject::sparsifyDenseElements(cx, obj.as<NativeObject>()))
if (level == IntegrityLevel::Frozen) {
MarkObjectGroupFlags(cx, obj, OBJECT_FLAG_FROZEN);
if (!ObjectElements::FreezeElements(cx, obj.as<NativeObject>()))
return false;
} else if (!NativeObject::sparsifyDenseElements(cx, obj.as<NativeObject>())) {
return false;
}
}
if (!obj->setFlags(cx, BaseShape::NOT_EXTENSIBLE, JSObject::GENERATE_SHAPE))
@ -2671,10 +2673,10 @@ js::PreventExtensions(JSContext* cx, HandleObject obj, ObjectOpResult& result)
}
bool
js::PreventExtensions(JSContext* cx, HandleObject obj)
js::PreventExtensions(JSContext* cx, HandleObject obj, IntegrityLevel level)
{
ObjectOpResult result;
return PreventExtensions(cx, obj, result) && result.checkStrict(cx, obj);
return PreventExtensions(cx, obj, result, level) && result.checkStrict(cx, obj);
}
bool

View File

@ -75,8 +75,13 @@ extern const Class MathClass;
class GlobalObject;
class NewObjectCache;
enum class IntegrityLevel {
Sealed,
Frozen
};
// Forward declarations, required for later friend declarations.
bool PreventExtensions(JSContext* cx, JS::HandleObject obj, JS::ObjectOpResult& result);
bool PreventExtensions(JSContext* cx, JS::HandleObject obj, JS::ObjectOpResult& result, IntegrityLevel level = IntegrityLevel::Sealed);
bool SetImmutablePrototype(js::ExclusiveContext* cx, JS::HandleObject obj, bool* succeeded);
} /* namespace js */
@ -106,7 +111,7 @@ class JSObject : public js::gc::Cell
friend class js::NewObjectCache;
friend class js::Nursery;
friend class js::gc::RelocationOverlay;
friend bool js::PreventExtensions(JSContext* cx, JS::HandleObject obj, JS::ObjectOpResult& result);
friend bool js::PreventExtensions(JSContext* cx, JS::HandleObject obj, JS::ObjectOpResult& result, js::IntegrityLevel level);
friend bool js::SetImmutablePrototype(js::ExclusiveContext* cx, JS::HandleObject obj,
bool* succeeded);
@ -748,13 +753,16 @@ IsExtensible(ExclusiveContext* cx, HandleObject obj, bool* extensible);
* ES6 [[PreventExtensions]]. Attempt to change the [[Extensible]] bit on |obj|
* to false. Indicate success or failure through the |result| outparam, or
* actual error through the return value.
*
* The `level` argument is SM-specific. `obj` should have an integrity level of
* at least `level`.
*/
extern bool
PreventExtensions(JSContext* cx, HandleObject obj, ObjectOpResult& result);
PreventExtensions(JSContext* cx, HandleObject obj, ObjectOpResult& result, IntegrityLevel level);
/* Convenience function. As above, but throw on failure. */
extern bool
PreventExtensions(JSContext* cx, HandleObject obj);
PreventExtensions(JSContext* cx, HandleObject obj, IntegrityLevel level = IntegrityLevel::Sealed);
/*
* ES6 [[GetOwnProperty]]. Get a description of one of obj's own properties.
@ -1339,11 +1347,6 @@ Throw(JSContext* cx, jsid id, unsigned errorNumber);
extern bool
Throw(JSContext* cx, JSObject* obj, unsigned errorNumber);
enum class IntegrityLevel {
Sealed,
Frozen
};
/*
* ES6 rev 29 (6 Dec 2014) 7.3.13. Mark obj as non-extensible, and adjust each
* of obj's own properties' attributes appropriately: each property becomes

View File

@ -101,6 +101,24 @@ ObjectElements::MakeElementsCopyOnWrite(ExclusiveContext* cx, NativeObject* obj)
return true;
}
/* static */ bool
ObjectElements::FreezeElements(ExclusiveContext* cx, HandleNativeObject obj)
{
if (!obj->maybeCopyElementsForWrite(cx))
return false;
if (obj->hasEmptyElements())
return true;
ObjectElements* header = obj->getElementsHeader();
// Note: this method doesn't update type information to indicate that the
// elements might be frozen. Handling this is left to the caller.
header->freeze();
return true;
}
#ifdef DEBUG
void
js::NativeObject::checkShapeConsistency()
@ -2305,7 +2323,9 @@ SetExistingProperty(JSContext* cx, HandleNativeObject obj, HandleId id, HandleVa
{
// Step 5 for dense elements.
if (IsImplicitDenseOrTypedArrayElement(shape)) {
// Step 5.a is a no-op: all dense elements are writable.
// Step 5.a.
if (obj->getElementsHeader()->isFrozen())
return result.fail(JSMSG_READ_ONLY);
// Pure optimization for the common case:
if (receiver.isObject() && pobj == &receiver.toObject())

View File

@ -182,6 +182,9 @@ class ObjectElements
// memory. This is a static property of the TypedArray, set when it
// is created and never changed.
SHARED_MEMORY = 0x8,
// These elements are set to integrity level "frozen".
FROZEN = 0x10,
};
private:
@ -286,6 +289,15 @@ class ObjectElements
static bool ConvertElementsToDoubles(JSContext* cx, uintptr_t elements);
static bool MakeElementsCopyOnWrite(ExclusiveContext* cx, NativeObject* obj);
static bool FreezeElements(ExclusiveContext* cx, HandleNativeObject obj);
bool isFrozen() const {
return flags & FROZEN;
}
void freeze() {
MOZ_ASSERT(!isFrozen());
flags |= FROZEN;
}
// This is enough slots to store an object of this class. See the static
// assertion below.

View File

@ -175,6 +175,8 @@ GetShapeAttributes(JSObject* obj, Shape* shape)
if (IsImplicitDenseOrTypedArrayElement(shape)) {
if (obj->is<TypedArrayObject>())
return JSPROP_ENUMERATE | JSPROP_PERMANENT;
if (obj->as<NativeObject>().getElementsHeader()->isFrozen())
return JSPROP_ENUMERATE | JSPROP_PERMANENT | JSPROP_READONLY;
return JSPROP_ENUMERATE;
}

View File

@ -145,7 +145,8 @@ enum : uint32_t {
/* Whether any objects have been iterated over. */
OBJECT_FLAG_ITERATED = 0x00080000,
/* 0x00100000 is not used. */
/* Whether any object this represents may be frozen. */
OBJECT_FLAG_FROZEN = 0x00100000,
/*
* For the function on a run-once script, whether the function has actually