Bug 1715512 part 11 - Add fast path for sealing/freezing properties. r=jonco

This corresponds to the fast path we currently have in SetIntegrityLevel, but
makes it more of a core operation.

The old Shape-based implementation doesn't support dictionary objects, but this
patch adds code for dictionary maps because it's easy to implement and is much
faster than the generic seal/freeze code.

Differential Revision: https://phabricator.services.mozilla.com/D117311
This commit is contained in:
Jan de Mooij 2021-06-17 16:51:44 +00:00
Родитель 26c07b486d
Коммит e10ed74d26
3 изменённых файлов: 141 добавлений и 0 удалений

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

@ -0,0 +1,26 @@
let sym = Symbol();
let o = {x: 1, y: 2, z: 3, a: 4, b: 5, 12345678: 6, [sym]: 7};
for (let i = 0; i < 100; i++) {
o["foo" + i] = 1;
}
delete o.x;
Object.seal(o);
assertEq(Object.getOwnPropertyNames(o).length, 105);
assertEq(Object.getOwnPropertySymbols(o).length, 1);
assertEq(Object.isSealed(o), true);
assertEq(Object.isFrozen(o), false);
let desc = Object.getOwnPropertyDescriptor(o, "y");
assertEq(desc.writable, true);
assertEq(desc.configurable, false);
Object.freeze(o);
assertEq(Object.isSealed(o), true);
assertEq(Object.isFrozen(o), true);
desc = Object.getOwnPropertyDescriptor(o, "y");
assertEq(desc.writable, false);
assertEq(desc.configurable, false);

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

@ -8,6 +8,7 @@
#include "gc/Allocator.h"
#include "gc/HashUtil.h"
#include "js/GCVector.h"
#include "vm/JSObject.h"
#include "vm/ObjectFlags-inl.h"
@ -432,6 +433,83 @@ bool SharedPropMap::addPropertyInternal(JSContext* cx,
return true;
}
static PropertyFlags ComputeFlagsForSealOrFreeze(PropertyKey key,
PropertyFlags flags,
IntegrityLevel level) {
// Private fields are not visible to SetIntegrityLevel.
if (key.isSymbol() && key.toSymbol()->isPrivateName()) {
return flags;
}
// Make all properties non-configurable; if freezing, make data properties
// read-only.
flags.clearFlag(PropertyFlag::Configurable);
if (level == IntegrityLevel::Frozen && flags.isDataDescriptor()) {
flags.clearFlag(PropertyFlag::Writable);
}
return flags;
}
// static
bool SharedPropMap::freezeOrSealProperties(JSContext* cx, IntegrityLevel level,
const JSClass* clasp,
MutableHandle<SharedPropMap*> map,
uint32_t mapLength,
ObjectFlags* objectFlags) {
// Add all maps to a Vector so we can iterate over them in reverse order
// (property definition order).
JS::RootedVector<SharedPropMap*> maps(cx);
{
SharedPropMap* curMap = map;
while (true) {
if (!maps.append(curMap)) {
return false;
}
if (!curMap->hasPrevious()) {
break;
}
curMap = curMap->asNormal()->previous();
}
}
// Build a new SharedPropMap by adding each property with the changed
// attributes.
Rooted<SharedPropMap*> newMap(cx);
uint32_t newMapLength = 0;
Rooted<PropertyKey> key(cx);
Rooted<SharedPropMap*> curMap(cx);
for (size_t i = maps.length(); i > 0; i--) {
curMap = maps[i - 1];
uint32_t len = (i == 1) ? mapLength : PropMap::Capacity;
for (uint32_t j = 0; j < len; j++) {
key = curMap->getKey(j);
PropertyInfo prop = curMap->getPropertyInfo(j);
PropertyFlags flags =
ComputeFlagsForSealOrFreeze(key, prop.flags(), level);
if (prop.isCustomDataProperty()) {
if (!addCustomDataProperty(cx, clasp, &newMap, &newMapLength, key,
flags, objectFlags)) {
return false;
}
} else {
if (!addPropertyWithKnownSlot(cx, clasp, &newMap, &newMapLength, key,
flags, prop.slot(), objectFlags)) {
return false;
}
}
}
}
map.set(newMap);
MOZ_ASSERT(newMapLength == mapLength);
return true;
}
void LinkedPropMap::handOffTableTo(LinkedPropMap* next) {
MOZ_ASSERT(hasTable());
MOZ_ASSERT(!next->hasTable());
@ -518,6 +596,27 @@ void DictionaryPropMap::changeProperty(JSContext* cx, const JSClass* clasp,
linkedData_.propInfos[index] = PropertyInfo(flags, slot);
}
void DictionaryPropMap::freezeOrSealProperties(JSContext* cx,
IntegrityLevel level,
const JSClass* clasp,
uint32_t mapLength,
ObjectFlags* objectFlags) {
DictionaryPropMap* curMap = this;
do {
for (uint32_t i = 0; i < mapLength; i++) {
if (!curMap->hasKey(i)) {
continue;
}
PropertyKey key = curMap->getKey(i);
PropertyFlags flags = curMap->getPropertyInfo(i).flags();
flags = ComputeFlagsForSealOrFreeze(key, flags, level);
curMap->changePropertyFlags(cx, clasp, i, flags, objectFlags);
}
curMap = curMap->previous();
mapLength = PropMap::Capacity;
} while (curMap);
}
// static
void DictionaryPropMap::skipTrailingHoles(MutableHandle<DictionaryPropMap*> map,
uint32_t* mapLength) {

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

@ -205,6 +205,8 @@
namespace js {
enum class IntegrityLevel;
class DictionaryPropMap;
class SharedPropMap;
class LinkedPropMap;
@ -647,6 +649,14 @@ class SharedPropMap : public PropMap {
PropertyFlags flags,
ObjectFlags* objectFlags);
// Freeze or seal all properties by creating a new shared map. Returns the new
// map and object flags.
static bool freezeOrSealProperties(JSContext* cx, IntegrityLevel level,
const JSClass* clasp,
MutableHandle<SharedPropMap*> map,
uint32_t mapLength,
ObjectFlags* objectFlags);
// Create a new dictionary map as copy of this map.
static DictionaryPropMap* toDictionaryMap(JSContext* cx,
Handle<SharedPropMap*> map,
@ -930,6 +940,12 @@ class DictionaryPropMap final : public PropMap {
MutableHandle<DictionaryPropMap*> map,
uint32_t* mapLength, NativeObject* obj);
// Freeze or seal all properties in this map. Returns the new object flags.
// The caller is responsible for generating a new shape for the object.
void freezeOrSealProperties(JSContext* cx, IntegrityLevel level,
const JSClass* clasp, uint32_t mapLength,
ObjectFlags* objectFlags);
// Change a property's slot number and/or flags and return the new object
// flags. The caller is responsible for generating a new shape.
void changeProperty(JSContext* cx, const JSClass* clasp, uint32_t index,