8.12.9 [[DefineOwnProperty]]

2010-05-04


undefined [[DefineOwnProperty]](P:String, Desc:PD, Throw:Boolean) throws TypeError

Attempt to create an own property named P on this object O using given property descriptor.

Desc does not need to have all the tested fields. Absent fields are considered to have a false value.

Code: (Meta Ecma)
function [[DefineOwnProperty]](P, Desc, Throw){
var current = this.[[GetOwnProperty]](P);
var extensible = this.[[Extensible]];
if (current === undefined && !extensible) {
if (Throw) throw TypeError;
return false;
}
if (current === undefined) { // extensible must be true
if (IsGenericDescriptor(Desc) || IsDataDescriptor(Desc)) {
this[P] = {[[Value]]:desc.[[Value]], [[Writable]]:desc.[[Writable]], [[Enumerable]]:desc.[[Enumerable]], [[Configurable]]:desc.[[Configurable]]}; // create property using given attributes. Absent attributes will default to their default values (false).
} else { // must be an accessor descriptor
this[P] = {[[Get]]:desc.[[Get]], [[Set]]:desc.[[Set]], [[Enumerable]]:desc.[[Enumerable]], [[Configurable]]:desc.[[Configurable]]}; // create property using given attributes. Absent attributes will default to their default values (false).
}
return true;
}
// "return true if every field in desc is absent": check for the 6 property descriptor attributes
if (!desc.[[HasOwnProperty]]("[[Value]]") && !desc.[[HasOwnProperty]]("[[Writable]]") && !desc.[[HasOwnProperty]]("[[Get]]") && !desc.[[HasOwnProperty]]("[[Set]]") && !desc.[[HasOwnProperty]]("[[Extensible]]") && !desc.[[HasOwnProperty]]("[[Configurable]]")) return true;
// "return true if every field in desc also occurs in current and their values are the same according to the SameValue algorithm (9.12)
if (
(desc.[[HasOwnProperty]]("[[Value]]") === current.[[HasOwnProperty]]("[[Value]]")) &&
(desc.[[HasOwnProperty]]("[[Writable]]") === current.[[HasOwnProperty]]("[[Writable]]")) &&
(desc.[[HasOwnProperty]]("[[Get]]") === current.[[HasOwnProperty]]("[[Get]]")) &&
(desc.[[HasOwnProperty]]("[[Set]]") === current.[[HasOwnProperty]]("[[Set]]")) &&
(desc.[[HasOwnProperty]]("[[Extensible]]") === current.[[HasOwnProperty]]("[[Extensible]]")) &&
(desc.[[HasOwnProperty]]("[[Configurable]]") === current.[[HasOwnProperty]]("[[Configurable]]")) &&
SameValue(current, Desc)
) return true;
if (!current.[[Configurable]]) {
// reject if currents properties cannot be changed and descs can, or and their enumerable value differs (because we can't change them).
if (Desc.[[Configurable]] || (Desc.[[HasProperty]]("[[Enumerable]]") && Desc.[[Enumerable]] != current.[[Enumerable]])) {
if (Throw) throw TypeError;
return false;
}
}
if (IsGenericDescriptor(Desc)) {
// do nothing, no further validation required
} else if (IsDataDescriptor(current) != IsDataDescriptor(Desc)) { // different types of descriptors?
// if we cant change them, this is doomed
if (!current.[[Configurable]]) {
if (Throw) throw TypeError;
return false;
}
// switch types
if (IsDataDescriptor(current)) {
// um. Convert O[P] to an accessor property. Preserve O[P].[[Configurable]] and O[P].[[Enumerable]] and default all the others (false).

} else { // current must be Accessor Descriptor (why not generic?)
// Convert O[P] to data property. Preserve O[P].[[Configurable]] and O[P].[[Enumerable]] and default all the others (false).

}
} else if (IsDataDescriptor(current) && IsDataDescriptor(Desc)) { // both data descriptors
// if configurable is true, any change is allowed. otherwise, reject if not writable and Descs [[Value]] is different from currents [[Value]]
if (!current.[[Configurable]] && !current.[[Writable]] && (
Desc.[[Writable]] || // 10.a.i
(Desc.[[HasProperty]]("[[Value]]") && !SameValue(Desc.[[Value]], current.[[Value]])) // 10.a.ii && 10.a.ii.1
)) {
if (Throw) throw TypeError;
return false;
}
// so if configurable is true all is fine (we may change the property)
// if it is not and descs writable is true, we're fine as well
// if both writable and configurable are false, we're only okay if we
// dont want to change the value (eg: Desc has no [[Value]] or its
// [[Value]] is equal to that of current.[[Value]]
} else { // IsAccessorDescriptor(current) and IsAccessorDescriptor(Desc) are true
if (!current.[[Configurable]]) {
if ((Desc.[[HasProperty]]("[[Get]]") && !SameValue(Desc.[[Get]], current.[[Get]])) || (Desc.[[HasProperty]]("[[Set]]") && !SameValue(Desc.[[Set]], current.[[Set]]))) {
if (Throw) throw TypeError;
return false;
}
}
}
// now we copy every attribute of Desc to O[P] ... Somehow.
// since the attributes aren't properties per se and dont have an [[Enumerable]] property, we assume they can all be enumerated with some kind of internal enumerator. for in is go!
for (var atr in Desc) O[P][atr] = Desc[atr];

}

Yes, there are a few loopholes to implement this in Ecmascript :) Even if it's just meta Ecma.
The steps are basically required to make sure no properties get changed if their attributes say you can't.

There seems to be a possible loop hole here though, in the following case:

Code: (Meta Ecma)
var desc = PD{[[Value]]:"whatever", [[Writable]]:false, [[Extensible]]:true, [[Configurable]]:false};
var O = {prop: PD{[[Value]]:"whatever", [[Writable]]:true, [[Extensible]]:false, [[Configurable]]:false}};
O.[[DefineOwnProperty]]("prop", desc, true);


This results in O.whatever.[[Writable]] being changed from false to true, even when O.whatever.[[Configurable]] and O.whatever.[[Configurable]] is set to false. According to 8.6.1, having [[Configurable]] set to false only allows you to change [[Value]] (unless [[Writable]] is false as well, of course).
Most probable fix? Just return true if the value is determined to be equal and nothing else would cause a reject.