The patch commit itself quite nicely explains the bug itself. It’s about the slow/fast path for property access mechanism which is quite common vulnerable pattern for JS engine.
Let us explain with an example.
let o = {}
o.p1 = 0x1337;
o.p1;let o = {}
o.p1 = 0x1337;
o.p1;let o = {}
o.p1 = 0x1337;
o.p1;In the above example, o.p1 doesn't have any security problem. But we can add some custom handler for property access.
let o = {};
o.__defineGetter__("p1", () => {
console.log("Getter is called");
})let o = {};
o.__defineGetter__("p1", () => {
console.log("Getter is called");
})let o = {};
o.__defineGetter__("p1", () => {
console.log("Getter is called");
})Unlike the first example, when we access to p1, we can invoke custom handler to get the property value. So why is this harmful for JS engine's security? Because it can violate JS engine's assumption for both Runtime/JIT context. The bible for this kind of vulnerability is CVE-2016-4622.
Anyway, now move our focus to the patch log.
diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp
index 32473bf2a38e..75916e3c1bfe 100644
--- a/Source/JavaScriptCore/runtime/JSObject.cpp
+++ b/Source/JavaScriptCore/runtime/JSObject.cpp
@@ -1162,6 +1162,11 @@ void JSObject::enterDictionaryIndexingMode(VM& vm)
void JSObject::notifyPresenceOfIndexedAccessors(VM& vm)
{
+ if (UNLIKELY(isGlobalObject())) {
+ jsCast<JSGlobalObject*>(this)->globalThis()->notifyPresenceOfIndexedAccessors(vm);
+ return;
+ }
+
if (mayInterceptIndexedAccesses())
return;
diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp
index 1c33fffa9022..73a9e5e82a54 100644
--- a/Source/JavaScriptCore/runtime/JSObject.cpp
+++ b/Source/JavaScriptCore/runtime/JSObject.cpp
@@ -859,6 +859,18 @@ bool JSObject::putInlineSlow(JSGlobalObject* globalObject, PropertyName property
return putInlineFast(globalObject, propertyName, value, slot);
}
+static bool canDefinePropertyOnReceiverFast(VM& vm, JSObject* receiver, PropertyName propertyName)
+{
+ switch (receiver->type()) {
+ case ArrayType:
+ return propertyName != vm.propertyNames->length;
+ case JSFunctionType:
+ return propertyName != vm.propertyNames->length && propertyName != vm.propertyNames->name && propertyName != vm.propertyNames->prototype;
+ default:
+ return false;
+ }
+}
+
static NEVER_INLINE bool definePropertyOnReceiverSlow(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, JSObject* receiver, bool shouldThrow)
{
VM& vm = globalObject->vm();
@@ -903,8 +915,8 @@ bool JSObject::definePropertyOnReceiver(JSGlobalObject* globalObject, PropertyNa
if (receiver->type() == GlobalProxyType)
receiver = jsCast<JSGlobalProxy*>(receiver)->target();
- if (slot.isTaintedByOpaqueObject() || slot.context() == PutPropertySlot::ReflectSet) {
- if (receiver->methodTable()->defineOwnProperty != JSObject::defineOwnProperty)
+ if (slot.isTaintedByOpaqueObject() || receiver->methodTable()->defineOwnProperty != JSObject::defineOwnProperty) {
+ if (!canDefinePropertyOnReceiverFast(vm, receiver, propertyName))
return definePropertyOnReceiverSlow(globalObject, propertyName, value, receiver, slot.isStrictMode());
}diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp
index 32473bf2a38e..75916e3c1bfe 100644
--- a/Source/JavaScriptCore/runtime/JSObject.cpp
+++ b/Source/JavaScriptCore/runtime/JSObject.cpp
@@ -1162,6 +1162,11 @@ void JSObject::enterDictionaryIndexingMode(VM& vm)
void JSObject::notifyPresenceOfIndexedAccessors(VM& vm)
{
+ if (UNLIKELY(isGlobalObject())) {
+ jsCast<JSGlobalObject*>(this)->globalThis()->notifyPresenceOfIndexedAccessors(vm);
+ return;
+ }
+
if (mayInterceptIndexedAccesses())
return;
diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp
index 1c33fffa9022..73a9e5e82a54 100644
--- a/Source/JavaScriptCore/runtime/JSObject.cpp
+++ b/Source/JavaScriptCore/runtime/JSObject.cpp
@@ -859,6 +859,18 @@ bool JSObject::putInlineSlow(JSGlobalObject* globalObject, PropertyName property
return putInlineFast(globalObject, propertyName, value, slot);
}
+static bool canDefinePropertyOnReceiverFast(VM& vm, JSObject* receiver, PropertyName propertyName)
+{
+ switch (receiver->type()) {
+ case ArrayType:
+ return propertyName != vm.propertyNames->length;
+ case JSFunctionType:
+ return propertyName != vm.propertyNames->length && propertyName != vm.propertyNames->name && propertyName != vm.propertyNames->prototype;
+ default:
+ return false;
+ }
+}
+
static NEVER_INLINE bool definePropertyOnReceiverSlow(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, JSObject* receiver, bool shouldThrow)
{
VM& vm = globalObject->vm();
@@ -903,8 +915,8 @@ bool JSObject::definePropertyOnReceiver(JSGlobalObject* globalObject, PropertyNa
if (receiver->type() == GlobalProxyType)
receiver = jsCast<JSGlobalProxy*>(receiver)->target();
- if (slot.isTaintedByOpaqueObject() || slot.context() == PutPropertySlot::ReflectSet) {
- if (receiver->methodTable()->defineOwnProperty != JSObject::defineOwnProperty)
+ if (slot.isTaintedByOpaqueObject() || receiver->methodTable()->defineOwnProperty != JSObject::defineOwnProperty) {
+ if (!canDefinePropertyOnReceiverFast(vm, receiver, propertyName))
return definePropertyOnReceiverSlow(globalObject, propertyName, value, receiver, slot.isStrictMode());
}diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp
index 32473bf2a38e..75916e3c1bfe 100644
--- a/Source/JavaScriptCore/runtime/JSObject.cpp
+++ b/Source/JavaScriptCore/runtime/JSObject.cpp
@@ -1162,6 +1162,11 @@ void JSObject::enterDictionaryIndexingMode(VM& vm)
void JSObject::notifyPresenceOfIndexedAccessors(VM& vm)
{
+ if (UNLIKELY(isGlobalObject())) {
+ jsCast<JSGlobalObject*>(this)->globalThis()->notifyPresenceOfIndexedAccessors(vm);
+ return;
+ }
+
if (mayInterceptIndexedAccesses())
return;
diff --git a/Source/JavaScriptCore/runtime/JSObject.cpp b/Source/JavaScriptCore/runtime/JSObject.cpp
index 1c33fffa9022..73a9e5e82a54 100644
--- a/Source/JavaScriptCore/runtime/JSObject.cpp
+++ b/Source/JavaScriptCore/runtime/JSObject.cpp
@@ -859,6 +859,18 @@ bool JSObject::putInlineSlow(JSGlobalObject* globalObject, PropertyName property
return putInlineFast(globalObject, propertyName, value, slot);
}
+static bool canDefinePropertyOnReceiverFast(VM& vm, JSObject* receiver, PropertyName propertyName)
+{
+ switch (receiver->type()) {
+ case ArrayType:
+ return propertyName != vm.propertyNames->length;
+ case JSFunctionType:
+ return propertyName != vm.propertyNames->length && propertyName != vm.propertyNames->name && propertyName != vm.propertyNames->prototype;
+ default:
+ return false;
+ }
+}
+
static NEVER_INLINE bool definePropertyOnReceiverSlow(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, JSObject* receiver, bool shouldThrow)
{
VM& vm = globalObject->vm();
@@ -903,8 +915,8 @@ bool JSObject::definePropertyOnReceiver(JSGlobalObject* globalObject, PropertyNa
if (receiver->type() == GlobalProxyType)
receiver = jsCast<JSGlobalProxy*>(receiver)->target();
- if (slot.isTaintedByOpaqueObject() || slot.context() == PutPropertySlot::ReflectSet) {
- if (receiver->methodTable()->defineOwnProperty != JSObject::defineOwnProperty)
+ if (slot.isTaintedByOpaqueObject() || receiver->methodTable()->defineOwnProperty != JSObject::defineOwnProperty) {
+ if (!canDefinePropertyOnReceiverFast(vm, receiver, propertyName))
return definePropertyOnReceiverSlow(globalObject, propertyName, value, receiver, slot.isStrictMode());
}Not that hard to understand. There are a few points to notice.
They add indexed accessor check for global object.
They prevent for defineProperty to configure Array and Function type’s length or prototype property.
So, based on patch log, attacker can break JS engine’s assumption that some properties like above things are unconfigurable. But through the bug, we can make them configurable.
Before diving into how this is possible, there is very simple poc for this.
class MyFunction extends Function {
constructor() {
super();
super.prototype = 1;
}
}
function test1() {
const f = new MyFunction();
f.__defineGetter__("prototype", () => {});
}
function test2(i) {
const f = new MyFunction();
try { f.__defineGetter__("prototype", () => {}); } catch {}
f.prototype.x = i;
}class MyFunction extends Function {
constructor() {
super();
super.prototype = 1;
}
}
function test1() {
const f = new MyFunction();
f.__defineGetter__("prototype", () => {});
}
function test2(i) {
const f = new MyFunction();
try { f.__defineGetter__("prototype", () => {}); } catch {}
f.prototype.x = i;
}class MyFunction extends Function {
constructor() {
super();
super.prototype = 1;
}
}
function test1() {
const f = new MyFunction();
f.__defineGetter__("prototype", () => {});
}
function test2(i) {
const f = new MyFunction();
try { f.__defineGetter__("prototype", () => {}); } catch {}
f.prototype.x = i;
}According to MDN, super is defined like following.
The super keyword is used to access properties on an object literal or class's [[Prototype]], or invoke a superclass's constructor.
...
The super keyword is used to access properties on an object literal or class's [[Prototype]], or invoke a superclass's constructor.
...
The super keyword is used to access properties on an object literal or class's [[Prototype]], or invoke a superclass's constructor.
...
When executing super.prototype = 1, it's handled by slow_path_put_by_id_with_this and calls JSObject::putInlineSlow.
JSC_DEFINE_COMMON_SLOW_PATH(slow_path_put_by_id_with_this)
{
BEGIN();
auto bytecode = pc->as<OpPutByIdWithThis>();
const Identifier& ident = codeBlock->identifier(bytecode.m_property);
JSValue baseValue = GET_C(bytecode.m_base).jsValue();
JSValue thisVal = GET_C(bytecode.m_thisValue).jsValue();
JSValue putValue = GET_C(bytecode.m_value).jsValue();
PutPropertySlot slot(thisVal, bytecode.m_ecmaMode.isStrict(), codeBlock->putByIdContext());
baseValue.putInline(globalObject, ident, putValue, slot);
END();
}
JSC_DEFINE_COMMON_SLOW_PATH(slow_path_put_by_id_with_this)
{
BEGIN();
auto bytecode = pc->as<OpPutByIdWithThis>();
const Identifier& ident = codeBlock->identifier(bytecode.m_property);
JSValue baseValue = GET_C(bytecode.m_base).jsValue();
JSValue thisVal = GET_C(bytecode.m_thisValue).jsValue();
JSValue putValue = GET_C(bytecode.m_value).jsValue();
PutPropertySlot slot(thisVal, bytecode.m_ecmaMode.isStrict(), codeBlock->putByIdContext());
baseValue.putInline(globalObject, ident, putValue, slot);
END();
}
bool JSObject::putInlineSlow(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
JSObject* obj = this;
for (;;) {
Structure* structure = obj->structure();
if (obj != this && structure->typeInfo().overridesPut())
RELEASE_AND_RETURN(scope, obj->methodTable()->put(obj, globalObject, propertyName, value, slot));
bool hasProperty = false;
unsigned attributes;
PutValueFunc customSetter = nullptr;
PropertyOffset offset = structure->get(vm, propertyName, attributes); <-- [1]
if (isValidOffset(offset)) { <-- [2]
hasProperty = true;
if (attributes & PropertyAttribute::CustomAccessorOrValue)
customSetter = jsCast<CustomGetterSetter*>(obj->getDirect(offset))->setter();
} else if (structure->hasNonReifiedStaticProperties()) { <-- [3]
if (auto entry = structure->findPropertyHashEntry(propertyName)) {
hasProperty = true;
attributes = entry->value->attributes();
// FIXME: Remove this after we stop defaulting to CustomValue in static hash tables.
if (!(attributes & (PropertyAttribute::CustomAccessor | PropertyAttribute::BuiltinOrFunctionOrAccessorOrLazyPropertyOrConstant)))
attributes |= PropertyAttribute::CustomValue;
if (attributes & PropertyAttribute::CustomAccessorOrValue)
customSetter = entry->value->propertyPutter();
}
}
...
JSValue prototype = obj->getPrototype(vm, globalObject); <-- [4]
RETURN_IF_EXCEPTION(scope, false);
if (prototype.isNull())
break;
obj = asObject(prototype);
}
...
if (UNLIKELY(isThisValueAltered(slot, this))) <
JSC_DEFINE_COMMON_SLOW_PATH(slow_path_put_by_id_with_this)
{
BEGIN();
auto bytecode = pc->as<OpPutByIdWithThis>();
const Identifier& ident = codeBlock->identifier(bytecode.m_property);
JSValue baseValue = GET_C(bytecode.m_base).jsValue();
JSValue thisVal = GET_C(bytecode.m_thisValue).jsValue();
JSValue putValue = GET_C(bytecode.m_value).jsValue();
PutPropertySlot slot(thisVal, bytecode.m_ecmaMode.isStrict(), codeBlock->putByIdContext());
baseValue.putInline(globalObject, ident, putValue, slot);
END();
}
JSC_DEFINE_COMMON_SLOW_PATH(slow_path_put_by_id_with_this)
{
BEGIN();
auto bytecode = pc->as<OpPutByIdWithThis>();
const Identifier& ident = codeBlock->identifier(bytecode.m_property);
JSValue baseValue = GET_C(bytecode.m_base).jsValue();
JSValue thisVal = GET_C(bytecode.m_thisValue).jsValue();
JSValue putValue = GET_C(bytecode.m_value).jsValue();
PutPropertySlot slot(thisVal, bytecode.m_ecmaMode.isStrict(), codeBlock->putByIdContext());
baseValue.putInline(globalObject, ident, putValue, slot);
END();
}
bool JSObject::putInlineSlow(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
JSObject* obj = this;
for (;;) {
Structure* structure = obj->structure();
if (obj != this && structure->typeInfo().overridesPut())
RELEASE_AND_RETURN(scope, obj->methodTable()->put(obj, globalObject, propertyName, value, slot));
bool hasProperty = false;
unsigned attributes;
PutValueFunc customSetter = nullptr;
PropertyOffset offset = structure->get(vm, propertyName, attributes); <-- [1]
if (isValidOffset(offset)) { <-- [2]
hasProperty = true;
if (attributes & PropertyAttribute::CustomAccessorOrValue)
customSetter = jsCast<CustomGetterSetter*>(obj->getDirect(offset))->setter();
} else if (structure->hasNonReifiedStaticProperties()) { <-- [3]
if (auto entry = structure->findPropertyHashEntry(propertyName)) {
hasProperty = true;
attributes = entry->value->attributes();
// FIXME: Remove this after we stop defaulting to CustomValue in static hash tables.
if (!(attributes & (PropertyAttribute::CustomAccessor | PropertyAttribute::BuiltinOrFunctionOrAccessorOrLazyPropertyOrConstant)))
attributes |= PropertyAttribute::CustomValue;
if (attributes & PropertyAttribute::CustomAccessorOrValue)
customSetter = entry->value->propertyPutter();
}
}
...
JSValue prototype = obj->getPrototype(vm, globalObject); <-- [4]
RETURN_IF_EXCEPTION(scope, false);
if (prototype.isNull())
break;
obj = asObject(prototype);
}
...
if (UNLIKELY(isThisValueAltered(slot, this))) <
JSC_DEFINE_COMMON_SLOW_PATH(slow_path_put_by_id_with_this)
{
BEGIN();
auto bytecode = pc->as<OpPutByIdWithThis>();
const Identifier& ident = codeBlock->identifier(bytecode.m_property);
JSValue baseValue = GET_C(bytecode.m_base).jsValue();
JSValue thisVal = GET_C(bytecode.m_thisValue).jsValue();
JSValue putValue = GET_C(bytecode.m_value).jsValue();
PutPropertySlot slot(thisVal, bytecode.m_ecmaMode.isStrict(), codeBlock->putByIdContext());
baseValue.putInline(globalObject, ident, putValue, slot);
END();
}
JSC_DEFINE_COMMON_SLOW_PATH(slow_path_put_by_id_with_this)
{
BEGIN();
auto bytecode = pc->as<OpPutByIdWithThis>();
const Identifier& ident = codeBlock->identifier(bytecode.m_property);
JSValue baseValue = GET_C(bytecode.m_base).jsValue();
JSValue thisVal = GET_C(bytecode.m_thisValue).jsValue();
JSValue putValue = GET_C(bytecode.m_value).jsValue();
PutPropertySlot slot(thisVal, bytecode.m_ecmaMode.isStrict(), codeBlock->putByIdContext());
baseValue.putInline(globalObject, ident, putValue, slot);
END();
}
bool JSObject::putInlineSlow(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
JSObject* obj = this;
for (;;) {
Structure* structure = obj->structure();
if (obj != this && structure->typeInfo().overridesPut())
RELEASE_AND_RETURN(scope, obj->methodTable()->put(obj, globalObject, propertyName, value, slot));
bool hasProperty = false;
unsigned attributes;
PutValueFunc customSetter = nullptr;
PropertyOffset offset = structure->get(vm, propertyName, attributes); <-- [1]
if (isValidOffset(offset)) { <-- [2]
hasProperty = true;
if (attributes & PropertyAttribute::CustomAccessorOrValue)
customSetter = jsCast<CustomGetterSetter*>(obj->getDirect(offset))->setter();
} else if (structure->hasNonReifiedStaticProperties()) { <-- [3]
if (auto entry = structure->findPropertyHashEntry(propertyName)) {
hasProperty = true;
attributes = entry->value->attributes();
// FIXME: Remove this after we stop defaulting to CustomValue in static hash tables.
if (!(attributes & (PropertyAttribute::CustomAccessor | PropertyAttribute::BuiltinOrFunctionOrAccessorOrLazyPropertyOrConstant)))
attributes |= PropertyAttribute::CustomValue;
if (attributes & PropertyAttribute::CustomAccessorOrValue)
customSetter = entry->value->propertyPutter();
}
}
...
JSValue prototype = obj->getPrototype(vm, globalObject); <-- [4]
RETURN_IF_EXCEPTION(scope, false);
if (prototype.isNull())
break;
obj = asObject(prototype);
}
...
if (UNLIKELY(isThisValueAltered(slot, this))) <
When put property into JSObject, it checks several things.
At [1], it checks whether property exists in current JSObject scope. PropertyOffset is returned if exist. JSC's JSEngine stores property information in structure object. There are 2 important members in structure object - m_seenProperties, m_propertyTableUnsafe. If it returns valid PropertyOffset at [2], it checks current offset's attributes like CustomAccessor, ReadOnly and etc.
If property does not exist in property table, it checks whether current property comes from static property table at [3]. You can see easy example in following link.
At [4], if above cases fail, it traverses JSObject’s prototype chain, and starts property lookup from [1].
If it fails to find property, then it tries to define property as new one. At [5], based on result of isThisValueAltered, it calls definePropertyOnReceiver or putInlineFast.
Now we should remind that before entering JSObject::putInlineSlow, JSC Runtime creates some base JSValue in slow_path_put_by_id_with_this. These are very important in isThisValueAltered, so we should know each JSValue.
baseValue stands for Function.prototype or Function.__proto__. thisValue stands for this in constructor context. putValue is 1 in our example.
ALWAYS_INLINE bool isThisValueAltered(const PutPropertySlot& slot, JSObject* baseObject)
{
JSValue thisValue = slot.thisValue();
if (LIKELY(thisValue == baseObject))
return false;
if (!thisValue.isObject())
return true;
JSObject* thisObject = asObject(thisValue);
if (thisObject->type() == GlobalProxyType && jsCast<JSGlobalProxy*>(thisObject)->target() == baseObject)
return false;
return true;
}ALWAYS_INLINE bool isThisValueAltered(const PutPropertySlot& slot, JSObject* baseObject)
{
JSValue thisValue = slot.thisValue();
if (LIKELY(thisValue == baseObject))
return false;
if (!thisValue.isObject())
return true;
JSObject* thisObject = asObject(thisValue);
if (thisObject->type() == GlobalProxyType && jsCast<JSGlobalProxy*>(thisObject)->target() == baseObject)
return false;
return true;
}ALWAYS_INLINE bool isThisValueAltered(const PutPropertySlot& slot, JSObject* baseObject)
{
JSValue thisValue = slot.thisValue();
if (LIKELY(thisValue == baseObject))
return false;
if (!thisValue.isObject())
return true;
JSObject* thisObject = asObject(thisValue);
if (thisObject->type() == GlobalProxyType && jsCast<JSGlobalProxy*>(thisObject)->target() == baseObject)
return false;
return true;
}Therefore, as thisValue and baseObject are different, isThisValueAltered returns true, we fallthrough definePropertyOnReceiver. In JSObject::definePropertyOnReceiver, as similar to property lookup routine, it checks several things to take slow path.
bool JSObject::definePropertyOnReceiver(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
ASSERT(!parseIndex(propertyName));
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSObject* receiver = slot.thisValue().getObject();
if (!receiver)
return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
scope.release();
if (receiver->type() == GlobalProxyType)
receiver = jsCast<JSGlobalProxy*>(receiver)->target();
if (slot.isTaintedByOpaqueObject() || receiver->methodTable()->defineOwnProperty != JSObject::defineOwnProperty) {
if (mightBeSpecialProperty(vm, receiver->type(), propertyName.uid()))
return definePropertyOnReceiverSlow(globalObject, propertyName, value, receiver, slot.isStrictMode());
}
if (receiver->structure()->hasAnyKindOfGetterSetterProperties()) {
unsigned attributes;
if (receiver->getDirectOffset(vm, propertyName, attributes) != invalidOffset && (attributes & PropertyAttribute::CustomValue))
return definePropertyOnReceiverSlow(globalObject, propertyName, value, receiver, slot.isStrictMode());
}
if (UNLIKELY(receiver->hasNonReifiedStaticProperties()))
return receiver->putInlineFastReplacingStaticPropertyIfNeeded(globalObject, propertyName, value, slot);
return receiver->putInlineFast(globalObject, propertyName, value, slot);
}
bool JSObject::definePropertyOnReceiver(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
ASSERT(!parseIndex(propertyName));
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSObject* receiver = slot.thisValue().getObject();
if (!receiver)
return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
scope.release();
if (receiver->type() == GlobalProxyType)
receiver = jsCast<JSGlobalProxy*>(receiver)->target();
if (slot.isTaintedByOpaqueObject() || receiver->methodTable()->defineOwnProperty != JSObject::defineOwnProperty) {
if (mightBeSpecialProperty(vm, receiver->type(), propertyName.uid()))
return definePropertyOnReceiverSlow(globalObject, propertyName, value, receiver, slot.isStrictMode());
}
if (receiver->structure()->hasAnyKindOfGetterSetterProperties()) {
unsigned attributes;
if (receiver->getDirectOffset(vm, propertyName, attributes) != invalidOffset && (attributes & PropertyAttribute::CustomValue))
return definePropertyOnReceiverSlow(globalObject, propertyName, value, receiver, slot.isStrictMode());
}
if (UNLIKELY(receiver->hasNonReifiedStaticProperties()))
return receiver->putInlineFastReplacingStaticPropertyIfNeeded(globalObject, propertyName, value, slot);
return receiver->putInlineFast(globalObject, propertyName, value, slot);
}
bool JSObject::definePropertyOnReceiver(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
ASSERT(!parseIndex(propertyName));
VM& vm = globalObject->vm();
auto scope = DECLARE_THROW_SCOPE(vm);
JSObject* receiver = slot.thisValue().getObject();
if (!receiver)
return typeError(globalObject, scope, slot.isStrictMode(), ReadonlyPropertyWriteError);
scope.release();
if (receiver->type() == GlobalProxyType)
receiver = jsCast<JSGlobalProxy*>(receiver)->target();
if (slot.isTaintedByOpaqueObject() || receiver->methodTable()->defineOwnProperty != JSObject::defineOwnProperty) {
if (mightBeSpecialProperty(vm, receiver->type(), propertyName.uid()))
return definePropertyOnReceiverSlow(globalObject, propertyName, value, receiver, slot.isStrictMode());
}
if (receiver->structure()->hasAnyKindOfGetterSetterProperties()) {
unsigned attributes;
if (receiver->getDirectOffset(vm, propertyName, attributes) != invalidOffset && (attributes & PropertyAttribute::CustomValue))
return definePropertyOnReceiverSlow(globalObject, propertyName, value, receiver, slot.isStrictMode());
}
if (UNLIKELY(receiver->hasNonReifiedStaticProperties()))
return receiver->putInlineFastReplacingStaticPropertyIfNeeded(globalObject, propertyName, value, slot);
return receiver->putInlineFast(globalObject, propertyName, value, slot);
}Check that type of receiver is GlobalProxyType to get the correct receiver.
Check whether the receiver has any kinds of GetterSetter.
None of them are matched, now we reach fast path to put property. Since we don’t have any static property table, JSObject::putInlineFast is called.
ALWAYS_INLINE bool JSObject::putInlineFast(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
VM& vm = getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto error = putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot);
if (!error.isNull())
return typeError(globalObject, scope, slot.isStrictMode(), error);
return true;
}
template<JSObject::PutMode mode>
ALWAYS_INLINE ASCIILiteral JSObject::putDirectInternal(VM& vm, PropertyName propertyName, JSValue value, unsigned newAttributes, PutPropertySlot& slot)
{
...
StructureID structureID = this->structureID();
Structure* structure = structureID.decode();
if (structure->isDictionary()) {
...
}
...
unsigned currentAttributes;
PropertyOffset offset = structure->get(vm, propertyName, currentAttributes);
if (offset != invalidOffset) {
...
}
...
DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure);
Structure* newStructure = Structure::addNewPropertyTransition(vm, structure, propertyName, newAttributes, offset, slot.context(), &deferredWatchpointFire);
...
size_t oldCapacity = structure->outOfLineCapacity();
size_t newCapacity = newStructure->outOfLineCapacity();
...
if (oldCapacity != newCapacity) {
Butterfly* newButterfly = allocateMoreOutOfLineStorage(vm, oldCapacity, newCapacity);
nukeStructureAndSetButterfly(vm, structureID, newButterfly);
}
...
putDirectOffset(vm, offset, value);
setStructure(vm, newStructure);
slot.setNewProperty(this, offset);
if (newAttributes & PropertyAttribute::ReadOnly)
newStructure->setContainsReadOnlyProperties();
if (UNLIKELY(mayBePrototype()))
vm.invalidateStructureChainIntegrity(VM::StructureChainIntegrityEvent::Add);
return { };ALWAYS_INLINE bool JSObject::putInlineFast(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
VM& vm = getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto error = putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot);
if (!error.isNull())
return typeError(globalObject, scope, slot.isStrictMode(), error);
return true;
}
template<JSObject::PutMode mode>
ALWAYS_INLINE ASCIILiteral JSObject::putDirectInternal(VM& vm, PropertyName propertyName, JSValue value, unsigned newAttributes, PutPropertySlot& slot)
{
...
StructureID structureID = this->structureID();
Structure* structure = structureID.decode();
if (structure->isDictionary()) {
...
}
...
unsigned currentAttributes;
PropertyOffset offset = structure->get(vm, propertyName, currentAttributes);
if (offset != invalidOffset) {
...
}
...
DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure);
Structure* newStructure = Structure::addNewPropertyTransition(vm, structure, propertyName, newAttributes, offset, slot.context(), &deferredWatchpointFire);
...
size_t oldCapacity = structure->outOfLineCapacity();
size_t newCapacity = newStructure->outOfLineCapacity();
...
if (oldCapacity != newCapacity) {
Butterfly* newButterfly = allocateMoreOutOfLineStorage(vm, oldCapacity, newCapacity);
nukeStructureAndSetButterfly(vm, structureID, newButterfly);
}
...
putDirectOffset(vm, offset, value);
setStructure(vm, newStructure);
slot.setNewProperty(this, offset);
if (newAttributes & PropertyAttribute::ReadOnly)
newStructure->setContainsReadOnlyProperties();
if (UNLIKELY(mayBePrototype()))
vm.invalidateStructureChainIntegrity(VM::StructureChainIntegrityEvent::Add);
return { };ALWAYS_INLINE bool JSObject::putInlineFast(JSGlobalObject* globalObject, PropertyName propertyName, JSValue value, PutPropertySlot& slot)
{
VM& vm = getVM(globalObject);
auto scope = DECLARE_THROW_SCOPE(vm);
auto error = putDirectInternal<PutModePut>(vm, propertyName, value, 0, slot);
if (!error.isNull())
return typeError(globalObject, scope, slot.isStrictMode(), error);
return true;
}
template<JSObject::PutMode mode>
ALWAYS_INLINE ASCIILiteral JSObject::putDirectInternal(VM& vm, PropertyName propertyName, JSValue value, unsigned newAttributes, PutPropertySlot& slot)
{
...
StructureID structureID = this->structureID();
Structure* structure = structureID.decode();
if (structure->isDictionary()) {
...
}
...
unsigned currentAttributes;
PropertyOffset offset = structure->get(vm, propertyName, currentAttributes);
if (offset != invalidOffset) {
...
}
...
DeferredStructureTransitionWatchpointFire deferredWatchpointFire(vm, structure);
Structure* newStructure = Structure::addNewPropertyTransition(vm, structure, propertyName, newAttributes, offset, slot.context(), &deferredWatchpointFire);
...
size_t oldCapacity = structure->outOfLineCapacity();
size_t newCapacity = newStructure->outOfLineCapacity();
...
if (oldCapacity != newCapacity) {
Butterfly* newButterfly = allocateMoreOutOfLineStorage(vm, oldCapacity, newCapacity);
nukeStructureAndSetButterfly(vm, structureID, newButterfly);
}
...
putDirectOffset(vm, offset, value);
setStructure(vm, newStructure);
slot.setNewProperty(this, offset);
if (newAttributes & PropertyAttribute::ReadOnly)
newStructure->setContainsReadOnlyProperties();
if (UNLIKELY(mayBePrototype()))
vm.invalidateStructureChainIntegrity(VM::StructureChainIntegrityEvent::Add);
return { };In JSObject::putDirectInternal, as it adds prototype property to OOL (Out of line) property named butterfly, JSC engine trigger structure transition for this JSObject and fire StructureTransitionWatchpoint. You can see the details for concept about WatchPoint in the offical WebKit blog!
So, normally prototype property is unconfigurable, but through this way, we make prototype property as OOL property which is configurable.
But the main question is still unclear.