Preliminary algorithm work
In this section we look at the internal algorithms and do some preliminary work of restating them by: inlining algorithms, merging algorithms, looking at algorithm behavior with some fixed parameters, etc. Tricky issues of algorithms are also discussed to some extent.
The purpose of this section is to provide raw material for the sections dealing with actual exposed algorithms.
CanPut
[[CanPut]] indicates whether a [[Put]] would cause an error or not. An error is possible in the following cases for object O, property P:
OhasPas own property, it is a plain property, and[[Writable]]is falseOhasPas own property, it is an accessor property, and is missing the[[Set]]functionPis found inO's prototype chain (not inO), it is a plain property, and eitherO.[[Extensible]]or property[[Writable]]is falsePis found inO's prototype chain (not inO), it is an accessor property, and is missing the[[Set]]functionPis not found inO's prototype chain, andO.[[Extensible]]is false
The algorithm in E5 Section 8.12.4 deals with the "own property" case first and then looks up the property again from the prototype chain. If a property is found, the only difference is between steps 2.b and 8.a: the [[Extensible]] property of the original object O must be checked if the property is found in an ancestor, as a [[Put]] would actually go into O, extending its set of properties.
The following simplified (and restated) variant should be equivalent and requires only one prototype chain lookup:
desc=O.[[GetProperty]](P).- If
descisundefined, returnO.[[Extensible]]. - If
IsAccessorDescriptor(desc): a. Ifdesc.[[Set]]isundefined, returnfalse. b. Else, returntrue. - Else,
descmust be a data descriptor: a. (CHANGED:) Ifdescwas not found in the original objectO, andO.[[Extensible]]isfalse, returnfalse. b. Returndesc.[[Writable]].
The step denoted with CHANGED reconciles steps 2.b and 8.a of the original algorithm. The "found in the original object O" part can be implemented in many ways:
- Compare object pointers of original object vs. object where property was found: works if an object occurs at most once in a prototype chain (which should always be the case)
- The prototype chain lookup
[[GetProperty]]also returns an "inherited" flag
GetProperty
[[GetProperty]] is a very straightforward wrapper over [[GetOwnProperty]] which follows the prototype chain. Like [[GetOwnProperty]], it returns a descriptor.
There is no exotic behavior for [[GetProperty]], the exotic behaviors only affect [[GetOwnProperty]] which is called during [[GetProperty]].
Original algorithm
- Let
propbe the result of calling the[[GetOwnProperty]]internal method ofOwith property nameP. - If
propis notundefined, returnprop. - Let
protobe the value of the[[Prototype]]internal property ofO. - If
protoisnull, returnundefined. - Return the result of calling the
[[GetProperty]]internal method ofprotowith argumentP.
Eliminating recursion
This is better unwound into a loop (using desc instead of prop, as it is more descriptive):
- Let
currbeO. - While
curris notnull: a. Letdescbe the result of calling the[[GetOwnProperty]]internal method ofcurrwith property nameP. b. Ifdescis notundefined, returndesc. c. Letcurrbe the value of the[[Prototype]]internal property ofcurr. - Return
undefined.
Less nested form
The following is a less "nested" form (note that curr is guaranteed to be non-null in the first loop):
- Let
currbeO. - NEXT: Let
descbe the result of calling the[[GetOwnProperty]]internal method ofcurrwith property nameP. - If
descis notundefined, returndesc. - Let
currbe the value of the[[Prototype]]internal property ofcurr. - If
curris notnull, goto NEXT. - Return
undefined
::: note ::: title Note :::
A maximum prototype chain depth should be imposed as a safeguard against loops. Note that while it should be impossible to create prototype loops with ECMAScript code alone, creating them from C code is possible. :::
GetProperty with default GetOwnProperty inlined
[[GetOwnProperty]] is just creating the descriptor from whatever form properties are stored. It has exotic behaviors, so the resulting function is a bit complicated.
The inlined form for default [[GetOwnProperty]] is essentially:
curr=O- NEXT: If
currhas own propertyP: a. LetDbe a newly created Property Descriptor with no fields. b. LetXbecurr's own property named P. c. IfXis a data property, then 1. SetD.[[Value]]to the value ofX's[[Value]]attribute. 2. SetD.[[Writable]]to the value ofX's[[Writable]]attribute. d. ElseXis an accessor property, so 1. SetD.[[Get]]to the value ofX's[[Get]]attribute. 2. SetD.[[Set]]to the value ofX's[[Set]]attribute. e. SetD.[[Enumerable]]to the value ofX's[[Enumerable]]attribute. f. SetD.[[Configurable]]to the value ofX's[[Configurable]]attribute. g. ReturnD. - Let
currbe the value of the[[Prototype]]internal property ofcurr. - If
curris notnull, goto NEXT. - Return
undefined
This is a relatively useless form, because exotic behaviors are missing.
GetProperty with complete GetOwnProperty inlined
The following inlines [[GetOwnProperty]] with all exotic behaviors:
curr=O- NEXT: Let
Xbecurr's own property namedP. Ifcurrdoesn't have an own property with nameP: a. Ifcurris not aStringinstance, goto NOTFOUND. b. (Stringobject exotic behavior.) Letstrbe the String value of the[[PrimitiveValue]]internal property ofOandlenbe the number of characters instr. c. IfPis"length": 1. Return a Property Descriptor with the values: -[[Value]]: len(a primitive number) -[[Enumerable]]: false-[[Writable]]: false-[[Configurable]]: falsed. IfPis an array index (E5 Section 15.4): 1. LetindexbeToUint32(P). 2. Ifindex<len, return a Property Descriptor with the values: -[[Value]]:a primitive string of length 1, containing one character fromstrat positionindex(zero based index) -[[Enumerable]]: true-[[Writable]]: false-[[Configurable]]: falsee. Goto NOTFOUND. - Let
Dbe a newly created Property Descriptor filled as follows: a. IfXis a data property: 1. SetD.[[Value]]to the value ofX's[[Value]]attribute. 2. SetD.[[Writable]]to the value ofX's[[Writable]]attribute. b. ElseXis an accessor property: 1. SetD.[[Get]]to the value ofX's[[Get]]attribute. 2. SetD.[[Set]]to the value ofX's[[Set]]attribute. c. For either type of property: 1. SetD.[[Enumerable]]to the value ofX's[[Enumerable]]attribute. 2. SetD.[[Configurable]]to the value ofX's[[Configurable]]attribute. - If
curris anargumentsobject which contains a[[ParameterMap]]internal property: a. (Arguments object exotic behavior.) Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined, then: 1. SetD.[[Value]]to the result of calling the[[Get]]internal method ofmappassingPas the argument. - Return
D. - NOTFOUND: Let
currbe the value of the[[Prototype]]internal property ofcurr. - If
curris notnull, goto NEXT. - Return
undefined
::: note ::: title Note :::
This implementation is currently not used. The implementation for [[GetOwnProperty]] is a separate helper. See duk_hobject_props.c, helper functions: get_own_property_desc() and get_property_desc(). :::
Get
[[Get]] is straightforward; it gets a property descriptor with [[GetProperty]] and then coerces it to a value.
Get with GetProperty inlined
[[Get]] was covered above when discussion exotic behaviors, so we'll skip discussing it again here.
[[Get]] is essentially a [[GetProperty]] followed by coercion of the descriptor into a value. For a data descriptor, simply return its [[Value]]. For a property accessor, simply call its [[Get]] function. The descriptor does not need to be created at all, as we're just interested in the final value.
The following combines both [[GetOwnProperty]] and [[Get]] with exotic behaviors:
- If
Ois anargumentsobject which contains a[[ParameterMap]]internal property: a. (Arguments object exotic behavior.) Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined: 1. Return the result of calling the[[Get]]internal method ofmappassingPas the argument. curr=O- NEXT: Let
Xbecurr's own property namedP. Ifcurrdoesn't have an own property with nameP: a. Ifcurris not aStringinstance, goto NOTFOUND. b. (Stringobject exotic behavior.) Letstrbe the String value of the[[PrimitiveValue]]internal property ofOandlenbe the number of characters instr. c. IfPis"length": 1. Returnlen(a primitive number). (No need to check for arguments object exotic behavior or"caller"property exotic behavior.) d. IfPis an array index (E5 Section 15.4): 1. LetindexbeToUint32(P). 2. Ifindex<len: a. Return a primitive string of length 1, containing one character fromstrat positionindex(zero based index). (No need to check for arguments object exotic behavior or"caller"property exotic behavior.) e. Goto NOTFOUND. - If
Xis a data property: a. Setresto the value ofX's[[Value]]attribute. b. Goto FOUND1 - Else
Xis an accessor property: a. LetgetterbeX's[[Get]]attribute. b. Ifgetterisundefined: 1. Returnundefined. (Note: arguments object exotic behavior for mapped variables cannot apply: if the property is an accessor, it can never be in the arguments object[[ParameterMap]]. Also, the"caller"exotic behavior does not apply, since the resultundefinedis not a strict mode function. Thus, no "goto FOUND1" here.) c. Else letresbe the result of calling the[[Call]]internal method ofgetterprovidingOas thethisvalue and providing no arguments. d. Goto FOUND2. (Note: arguments object exotic behavior for mapped variables cannot apply: if the property is an accessor, it can never be in the arguments object[[ParameterMap]]. However, the"caller"exotic behavior might apply, at FOUND2.) - FOUND1: If
curris anargumentsobject which contains a[[ParameterMap]]internal property: a. (Arguments object exotic behavior.) Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined, then: 1. Setresto the result of calling the[[Get]]internal method ofmappassingPas the argument. - FOUND2: If
Ois aFunctionobject or anargumentsobject which contains a[[ParameterMap]]internal property: a. (Arguments or Function object exotic behavior.) IfPis"caller"andresis a strict modeFunctionobject, throw aTypeErrorexception. - Return
res. - NOTFOUND: Let
currbe the value of the[[Prototype]]internal property ofcurr. - If
curris notnull, goto NEXT. - Return
undefined. (Note: no need for exotic behavior checks here; e.g. result is not a strict mode function.)
::: note ::: title Note :::
The step 5.c gives the object as the this binding for the getter call. When properties are actually accessed from ECMAScript code, the wrappers (property accessor evaluation, GetValue()) have a different behavior: the primitive (uncoerced) object is given as the this binding. :::
DefineOwnProperty callers
[[DefineOwnProperty]] is defined in E5 Section 8.12.9. It is a complex algorithm which allows the value and attributes of property P of object O to be changed. It is used for [[Put]] which is performance relevant and should thus be "inlined" to the extent possible (see special case analysis below). It is also used generically when initializing newly created objects etc, which can also use a simplified version.
Note: [[DefineOwnProperty]] allows some counterintuitive property attributes changes to be made. The callers in the specification are supposed to "guard" against these. For instance:
- A property which is non-configurable but writable can be changed to non-writable (but not vice versa). Non-configurability does not guarantee that changes cannot be made.
- A property which is configurable but not writable can have its value changed by a
[[DefineOwnProperty]]call. This is allowed because a caller could simply change the property to writable, change its value, and then change it back to non-writable (this is possible because the property is configurable). The[[Put]]algorithms prevents writing to a non-writable but configurable property with an explicit check,[[CanPut]].
[[DefineOwnProperty]] is referenced by the following property-related internal algorithms:
FromPropertyDescriptor, E5 Section 8.10.4[[Put]], E5 Section 8.12.5- Array's exotic
[[DefineOwnProperty]]relies on the default one, E5 Section 15.4.5.1 - Argument object's exotic
[[DefineOwnProperty]]relies on the default one, E5 Section 10.6
It is used less fundamentally in many places, e.g. to initialize values (list probably not complete):
CreateMutableBinding, E5 Section 10.2.1.2.2- Arguments object setup, E5 Section 10.6
- Array initializer, E5 Section 11.1.4
- Object initializer, E5 Section 11.1.5
- Function object creation, E5 Section 13.2
[[ThrowTypeError]]function object, E5 Section 13.2.3Object.getOwnPropertyNames, E5 Section 15.2.3.4Object.defineProperty, E5 Section 15.2.3.6Object.seal, E5 Section 15.2.3.8Object.freeze, E5 Section 15.2.3.9Object.keys, E5 Section 15.2.3.14Function.prototype.bind, E5 Section 15.3.4.5Array.prototype.concat, E5 Section 15.4.4.4Array.prototype.slice, E5 Section 15.4.4.10Array.prototype.splice, E5 Section 15.4.4.12Array.prototype.map, E5 Section 15.4.4.19Array.prototype.filter, E5 Section 15.4.4.20String.prototype.match, E5 Section 15.5.4.10String.prototype.split, E5 Section 15.5.4.14RegExp.prototype.exec, E5 Section 15.10.6.2JSON.parse, E5 Section 15.12.2JSON.stringify, E5 Section 15.12.3
DefineOwnProperty for an existing property in Put
This case arises when a [[Put]] is performed and the property already exists. The property value is updated with a call to [[DefineOwnProperty]] with a property descriptor only containing [[Value]]. See E5 Section 8.12.5, step 3.
We can assume that:
- The property exists (checked by
[[Put]]) - The property is a data property (checked by
[[Put]]) - The property cannot be non-writable (checked by
[[Put]], using[[CanPut]]) - The property descriptor is a data descriptor
- The property descriptor is of the form:
{ [[Value]]: val } - Because the property exists, the
lengthof anArrayobject cannot change by a write to an array index; however, a write to"length"may delete array elements
More specifically, we know that in the [[DefineOwnProperty]] algorithm:
currentis notundefinedIsGenericDescriptor(current)isfalseIsDataDescriptor(current)istrueIsAccessorDescriptor(current)isfalseIsGenericDescriptor(Desc)isfalseIsDataDescriptor(Desc)istrueIsAccessorDescriptor(Desc)isfalse
Taking the [[DefineOwnProperty]] with all exotic behaviors included, using the above assumptions, eliminating any unnecessary steps, cleaning up and clarifying, we get:
- If
Ois anArrayobject, andPis"length", then: a. LetnewLenbeToUint32(Desc.[[Value]]). b. IfnewLenis not equal toToNumber(Desc.[[Value]]), throw aRangeErrorexception. Note that this is unconditional (thrown even ifThrowisfalse). c. LetoldLenDescbe the result of calling the[[GetOwnProperty]]internal method ofOpassing"length"as the argument. The result will never beundefinedor an accessor descriptor becauseArrayobjects are created with alengthdata property that cannot be deleted or reconfigured. d. LetoldLenbeoldLenDesc.[[Value]]. (Note thatoldLenis guaranteed to be a unsigned 32-bit integer.) e. IfnewLen<oldLen, then: 1. LetshortenSucceeded,finalLenbe the result of calling the internal helperShortenArray()witholdLenandnewLen. 2. Update the property ("length") value tofinalLen. 3. Goto REJECT, ifshortenSucceededisfalse. 4. Return. f. Update the property ("length") value tonewLen. g. Return. - Set the
[[Value]]attribute of the property namedPof objectOto the value ofDesc.[[Value]]. (Since it is side effect free to update the value with the same value, no check for that case is needed.) - If
Ois an arguments object which has a[[ParameterMap]]internal property: a. Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined, then: 1. Call the[[Put]]internal method ofmappassingP,Desc.[[Value]], andThrowas the arguments. (This updates the bound variable value.) - Return
true.
Note that step 1 combines the pre-step and post-step for an Array object length exotic behavior. This is only possible if we know beforehand that the "length" property is writable (so that the write never fails and we always reach the post-step).
We'll refine one more time, by eliminating references to Desc and using val to refer to Desc.[[Value]]:
- If
Ois anArrayobject, andPis"length", then: a. LetnewLenbeToUint32(val). b. IfnewLenis not equal toToNumber(val), throw aRangeErrorexception. Note that this is unconditional (thrown even ifThrowisfalse). c. LetoldLenDescbe the result of calling the[[GetOwnProperty]]internal method ofOpassing"length"as the argument. The result will never beundefinedor an accessor descriptor becauseArrayobjects are created with alengthdata property that cannot be deleted or reconfigured. d. LetoldLenbeoldLenDesc.[[Value]]. (Note thatoldLenis guaranteed to be a unsigned 32-bit integer.) e. IfnewLen<oldLen, then: 1. LetshortenSucceeded,finalLenbe the result of calling the internal helperShortenArray()witholdLenandnewLen. 2. Update the property ("length") value tofinalLen. 3. Goto REJECT, ifshortenSucceededisfalse. 4. Return. f. Update the property ("length") value tonewLen. g. Return. - Set the
[[Value]]attribute of the property namedPof objectOtoval. (Since it is side effect free to update the value with the same value, no check for that case is needed.) - If
Ois an arguments object which has a[[ParameterMap]]internal property: a. Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined, then: 1. Call the[[Put]]internal method ofmappassingP,val, andThrowas the arguments. (This updates the bound variable value.) - Return
true.
We'll need this variant later when creating an inlined version for the full property write processing.
DefineOwnProperty for a non-existent property in Put
This case arises when a [[Put]] is performed and the property does not already exist as an "own property", and no setter in an ancestor captured the write. The property is created with a call to [[DefineOwnProperty]] with a property descriptor containing a [[Value]], and the following set to true: [[Writable]], [[Enumerable]], [[Configurable]]. See E5 Section 8.12.5, step 6.
We can assume that:
- The property does not exist (checked by
[[Put]]) - The object is extensible (checked by
[[Put]]) - The property descriptor is a data descriptor
- The property descriptor has the fields:
[[Value]]: val[[Writable]]: true[[Enumerable]]: true[[Configurable]]: true
- If the object is an
Array, the property namePcannot be"length"(as that would exist)
More specifically, we know that in the [[DefineOwnProperty]] algorithm:
currentisundefined
Taking the [[DefineOwnProperty]] with all exotic behaviors included, using the above assumptions, and then eliminating any unnecessary steps, cleaning up and clarifying, we get:
If
Ois anArrayobject andPis an array index (E5 Section 15.4), then: a. LetoldLenDescbe the result of calling the[[GetOwnProperty]]internal method ofOpassing"length"as the argument. The result will never beundefinedor an accessor descriptor becauseArrayobjects are created with a length data property that cannot be deleted or reconfigured. b. LetoldLenbeoldLenDesc.[[Value]]. (Note thatoldLenis guaranteed to be a unsigned 32-bit integer.) c. LetindexbeToUint32(P). d. Goto REJECT ifindex>=oldLenandoldLenDesc.[[Writable]]isfalse.Create an own data property named
Pof objectOwhose[[Value]],[[Writable]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc.If
Ois anArrayobject,Pis an array index andindex>=oldLen: a. Update the"length"property ofOto the valueindex + 1. This always succeeds, because we've checked in the pre-step that the"length"is writable, and sincePis an array index property, the length must still be writable here.If
Ois an arguments object which has a[[ParameterMap]]internal property: a. Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined, then: 1. Call the[[Put]]internal method ofmappassingP,Desc.[[Value]], andThrowas the arguments. (This updates the bound variable value.)Return
true.
**REJECT**:
: If `Throw` is `true`, then throw a `TypeError` exception,
otherwise return `false`.
This can be refined further by noticing that the arguments object exotic behavior cannot be triggered if the property does not exist: all magically bound properties exist initially, and if they are deleted, the magic variable binding is also deleted.
We can also change the order of property creation and the postponed array length write because they are both guaranteed to succeed.
So, we get:
If
Ois anArrayobject andPis an array index (E5 Section 15.4), then: a. LetoldLenDescbe the result of calling the[[GetOwnProperty]]internal method ofOpassing"length"as the argument. The result will never beundefinedor an accessor descriptor becauseArrayobjects are created with a length data property that cannot be deleted or reconfigured. b. LetoldLenbeoldLenDesc.[[Value]]. (Note thatoldLenis guaranteed to be a unsigned 32-bit integer.) c. LetindexbeToUint32(P). d. Ifindex>=oldLen: 1. Goto REJECToldLenDesc.[[Writable]]isfalse. 2. Update the"length"property ofOto the valueindex + 1. This always succeeds.Create an own data property named
Pof objectOwhose[[Value]],[[Writable]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc.Return
true.
**REJECT**:
: If `Throw` is `true`, then throw a `TypeError` exception,
otherwise return `false`.
We'll refine one more time, by eliminating references to Desc and using val to refer to Desc.[[Value]]:
- If
Ois anArrayobject andPis an array index (E5 Section 15.4), then: a. LetoldLenDescbe the result of calling the[[GetOwnProperty]]internal method ofOpassing"length"as the argument. The result will never beundefinedor an accessor descriptor becauseArrayobjects are created with a length data property that cannot be deleted or reconfigured. b. LetoldLenbeoldLenDesc.[[Value]]. (Note thatoldLenis guaranteed to be a unsigned 32-bit integer.) c. LetindexbeToUint32(P). d. Ifindex>=oldLen: 1. Goto REJECToldLenDesc.[[Writable]]isfalse. 2. Update the"length"property ofOto the valueindex + 1. This always succeeds. - Create an own data property named
Pof objectOwhose attributes are:[[Value]]: val[[Writable]]: true[[Enumerable]]: true[[Configurable]]: true
- Return
true. - REJECT: If
Throwistrue, then throw aTypeErrorexception, otherwise returnfalse.
Notes:
- If step 2 fails due to an out-of-memory or other internal error, we may have updated
lengthalready. So, switching steps 2 and 1.d.2 might be prudent (the check in step 1.d.1 must be executed before writing anything though).
We'll need this variant later when creating an inlined version for the full property write processing.
DefineOwnProperty for (some) internal object initialization
This case occurs when internal objects or results objects are created by the implementation. We can't simply use a normal property write internally, because we need to set the property attributes to whatever combination is required by the context (many different property attribute variants are used throughout the specification).
Because user code has not had any access to the object, we can narrow down the possibilities a great deal. Here we assume that:
- Object is extensible
- Property does not exist
- Property does not have exotic behavior and is not virtual
- Property descriptor is a data descriptor, which is fully populated
With these assumptions, eliminating any unnecessary steps, the algorithm is simply:
- Create an own data property named
Pof objectOwhose[[Value]],[[Writable]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. - Return
true.
This doesn't cover all the initialization cases, but simply illustraes that very constrained cases are very simple.
Put
"Reject" below is shorthand for:
- If
Throwistrue, then throw aTypeErrorexception; else return.
Original algorithm
For object O, property P, and value V:
- If the result of calling the
[[CanPut]]internal method ofOwith argumentPis false, then a. IfThrowistrue, then throw aTypeErrorexception. b. Else return. - Let
ownDescbe the result of calling the[[GetOwnProperty]]internal method ofOwith argumentP. - If
IsDataDescriptor(ownDesc)istrue, then a. LetvalueDescbe the Property Descriptor{[[Value]]: V}. b. Call the[[DefineOwnProperty]]internal method ofOpassingP,valueDesc, andThrowas arguments. c. Return. - Let
descbe the result of calling the[[GetProperty]]internal method ofOwith argumentP. This may be either an own or inherited accessor property descriptor or an inherited data property descriptor. - If
IsAccessorDescriptor(desc)istrue, then a. Letsetterbedesc.[[Set]]which cannot beundefined. b. Call the[[Call]]internal method of setter providingOas thethisvalue and providingVas the sole argument. - Else, create a named data property named
Pon objectOas follows a. LetnewDescbe the Property Descriptor: -[[Value]]: V-[[Writable]]: true-[[Enumerable]]: true-[[Configurable]]: true}b. Call the[[DefineOwnProperty]]internal method ofOpassingP,newDesc, andThrowas arguments. - Return.
Notes:
- Step 5.a:
settercannot beundefinedat this point because[[CanPut]]has checked it (and throws an exception if it isundefined).
Minimizing prototype traversal
The ownDesc check is necessary because a [[Put]] on an existing own property is a change of value; a [[Put]] on an inherited plain property is an addition of a new property on the original target object (not the ancestor where the inherited property was found).
To minimize prototype traversal, these can be combined as follows (with some cleanup):
- If the result of calling the
[[CanPut]]internal method ofOwith argumentPis false, then Reject. - Let
descbe the result of calling the[[GetProperty]]internal method ofOwith argumentP. (Note: here we assume that we also get to know whether the property was found inOor in its ancestor.) - If
IsAccessorDescriptor(desc)istrue, then: a. Call the[[Call]]internal method ofdesc.[[Set]]providingOas thethisvalue and providingVas the sole argument. (Note:desc.[[Set]]cannot beundefined, as this is checked by[[CanPut]].) - Else if
descwas found inOdirectly (as an "own data property"), then: a. LetvalueDescbe the Property Descriptor{[[Value]]: V}. b. Call the[[DefineOwnProperty]]internal method ofOpassingP,valueDesc, andThrowas arguments. - Else
descis an inherited data property orundefined, then: a. LetnewDescbe the Property Descriptor: -[[Value]]: V-[[Writable]]: true-[[Enumerable]]: true-[[Configurable]]: true}b. Call the[[DefineOwnProperty]]internal method ofOpassingP,newDesc, andThrowas arguments. - Return.
This still travels the prototype chain twice: once for [[CanPut]], and a second time for the actual [[Put]]. [[CanPut]] can be inlined quite easily, as it does very similar checks as [[Put]].
The result is:
- Let
descbe the result of calling the[[GetProperty]]internal method ofOwith argumentP. (Note: here we assume that we also get to know whether the property was found inOor in its ancestor.) - If
IsAccessorDescriptor(desc)istrue, then: a. Ifdesc.[[Set]]isundefined, Reject. b. Call the[[Call]]internal method ofdesc.[[Set]]providingOas thethisvalue and providingVas the sole argument. - Else if
descis an inherited (data) property, then: a. IfO.[[Extensible]]isfalse, Reject. b. Ifdesc.[[Writable]]isfalse, Reject. c. LetnewDescbe the Property Descriptor: -[[Value]]: V-[[Writable]]: true-[[Enumerable]]: true-[[Configurable]]: true}d. Call the[[DefineOwnProperty]]internal method ofOpassingP,newDesc, andThrowas arguments. - Else if
descwas not found (isundefined): a. IfO.[[Extensible]]isfalse, Reject. b. LetnewDescbe the Property Descriptor: -[[Value]]: V-[[Writable]]: true-[[Enumerable]]: true-[[Configurable]]: true}c. Call the[[DefineOwnProperty]]internal method ofOpassingP,newDesc, andThrowas arguments. - Else
descwas found inOdirectly (as an "own data property"), then: a. Ifdesc.[[Writable]]isfalse, Reject. b. LetvalueDescbe the Property Descriptor{[[Value]]: V}. c. Call the[[DefineOwnProperty]]internal method ofOpassingP,valueDesc, andThrowas arguments. - Return.
The above can be further refined to (making also the modification required to [[GetProperty]] explicit):
- Let
descandinheritedbe the result of calling the[[GetProperty]]internal method ofOwith argumentP. - If
IsAccessorDescriptor(desc)istrue, then: a. Ifdesc.[[Set]]isundefined, Reject. b. Call the[[Call]]internal method ofdesc.[[Set]]providingOas thethisvalue and providingVas the sole argument. - Else if
descis notundefinedandinheritedisfalse(own data property), then: a. Ifdesc.[[Writable]]isfalse, Reject. b. LetvalueDescbe the Property Descriptor{[[Value]]: V}. c. Call the[[DefineOwnProperty]]internal method ofOpassingP,valueDesc, andThrowas arguments. - Else
descis an inherited (data) property orundefined: a. IfO.[[Extensible]]isfalse, Reject. b. Ifdescis notundefinedanddesc.[[Writable]]isfalse, Reject. (In other words:descwas inherited and is non-writable.) c. LetnewDescbe the Property Descriptor: -[[Value]]: V-[[Writable]]: true-[[Enumerable]]: true-[[Configurable]]: true}d. Call the[[DefineOwnProperty]]internal method ofOpassingP,newDesc, andThrowas arguments. - Return.
This can be further improved in actual C code.
Inlining GetProperty
When actually implementing, it's useful to "inline" the [[GetProperty]] loop, which changes the code structure quite a bit:
- Set
currtoO. - While
curr!==null: a. IfOdoes not have own propertyP: 1. Setcurrtocurr.[[Prototype]]2. Continue (while loop) b. Letdescbe the descriptor for own propertyPc. IfIsDataDescriptor(desc): 1. Ifcurr!=O(property is an inherited data property): (Note: assumes there are no prototype loops.) a. IfO.[[Extensible]isfalse, Reject. b. Ifdesc.[[Writable]]isfalse, Reject. c. LetnewDescbe a property descriptor with values: -[[Value]]: V-[[Writable]]: true-[[Enumerable]]: true-[[Configurable]]: true}d. CallO.[[DefineOwnProperty]](P, newDesc, Throw). 2. Else (property is an own data property): a. Ifdesc.[[Writable]]isfalse, Reject. b. LetvalueDescbe{ [[Value]]: V }. c. CallO.[[DefineOwnProperty]](P, valueDesc, Throw). d. Else (property is an accessor): 1. Ifdesc.[[Set]]isundefined, Reject. 2. Call the[[Call]]internal method ofdesc.[[Set]]providingOas thethisvalue and providingVas the sole argument. e. Return. - Property was not found in the prototype chain: a. If
O.[[Extensible]]isfalse, Reject. b. LetnewDescbe a property descriptor with values: -[[Value]]: V-[[Writable]]: true-[[Enumerable]]: true-[[Configurable]]: true}c. CallO.[[DefineOwnProperty]](P, newDesc, Throw).
Less nested form
The following is a less "nested" form (note that curr is guaranteed to be non-null in the first loop):
- Let
currbeO. - NEXT: Let
descbe the result of calling the[[GetOwnProperty]]internal method ofcurrwith property nameP. - If
descisundefined: a. Letcurrbe the value of the[[Prototype]]internal property ofcurr. b. Ifcurris notnull, goto NEXT. c. IfO.[[Extensible]]isfalse, Reject. d. LetnewDescbe a property descriptor with values: -[[Value]]: V-[[Writable]]: true-[[Enumerable]]: true-[[Configurable]]: true}e. CallO.[[DefineOwnProperty]](P, newDesc, Throw). f. Return. - If
IsDataDescriptor(desc): a. Ifcurr!=O(property is an inherited data property): (Note: assumes there are no prototype loops.) 1. IfO.[[Extensible]isfalse, Reject. 2. Ifdesc.[[Writable]]isfalse, Reject. 3. LetnewDescbe a property descriptor with values: -[[Value]]: V-[[Writable]]: true-[[Enumerable]]: true-[[Configurable]]: true}4. CallO.[[DefineOwnProperty]](P, newDesc, Throw). b. Else (property is an own data property): 1. Ifdesc.[[Writable]]isfalse, Reject. 2. LetvalueDescbe{ [[Value]]: V }. 3. CallO.[[DefineOwnProperty]](P, valueDesc, Throw). - Else (property is an accessor): a. If
desc.[[Set]]isundefined, Reject. b. Call the[[Call]]internal method ofdesc.[[Set]]providingOas thethisvalue and providingVas the sole argument. - Return.
Note about PutValue
Note that PutValue() has a [[Put]] variant with two exotic behaviors related to object coercion. The above algorithm does not take those into account.
Property descriptor algorithms
E5 Section 8.10 describes descriptor related algorithms:
IsAccessorDescriptor(desc):true, ifdesccontains either[[Set]]or[[Get]]IsDataDescriptor(desc):true, ifdesccontains either[[Value]]or[[Writable]]IsGenericDescriptor(desc):trueif bothIsAccessorDescriptor(desc)andIsGenericDescriptorarefalse; concretely:desccontains none of the following:[[Set]],[[Get]],[[Value]],[[Writable]]descmay contain:[[Enumerable]],[[Configurable]]
A property descriptor may be fully populated or not. If fully populated, it is either a data descriptor or an access descriptor, not a generic descriptor.
A property descriptor may not be both a data descriptor and access descriptor (this is stated in E5 Section 8.10). However, an argument to e.g. Object.defineProperty() may naturally contain e.g. "set" and "value" keys. In this case:
defineProperty()usesToPropertyDescriptor()to convert the ECMAScript object into an internal property descriptorToPropertyDescriptor()creates a property descriptor and throws aTypeErrorif the descriptor contains conflicting fields
ToPropertyDescriptor() also coerces the values in its argument ECMAScript object (e.g. it uses ToBoolean() for the flags). The behavior of ToPropertyDescriptor() is probably easiest to "inline" into wherever it is needed. The E5 specification refers to ToPropertyDescriptor only in Object.defineProperty() and Object.defineProperties().
The current implementation does not have partial internal property descriptors (internal property value and attributes are always fully populated).
ToPropertyDescriptor
The ToPropertyDescriptor() algorithm is specified in E5 Section 8.10.5 and is as follows:
- If
Type(Obj)is notObjectthrow aTypeErrorexception. - Let
descbe the result of creating a new Property Descriptor that initially has no fields. - If the result of calling the
[[HasProperty]]internal method ofObjwith argument"enumerable"istrue, then: a. Letenumbe the result of calling the[[Get]]internal method ofObjwith"enumerable". b. Set the[[Enumerable]]field ofdesctoToBoolean(enum). - If the result of calling the
[[HasProperty]]internal method ofObjwith argument"configurable"istrue, then: a. Letconfbe the result of calling the[[Get]]internal method ofObjwith argument"configurable". b. Set the[[Configurable]]field ofdesctoToBoolean(conf). - If the result of calling the
[[HasProperty]]internal method ofObjwith argument"value"istrue, then: a. Letvaluebe the result of calling the[[Get]]internal method ofObjwith argument"value". b. Set the[[Value]]field ofdesctovalue. - If the result of calling the
[[HasProperty]]internal method ofObjwith argument"writable"istrue, then: a. Letwritablebe the result of calling the[[Get]]internal method ofObjwith argument"writable". b. Set the[[Writable]]field ofdesctoToBoolean(writable). - If the result of calling the
[[HasProperty]]internal method ofObjwith argument"get"istrue, then: a. Letgetterbe the result of calling the[[Get]]internal method ofObjwith argument"get". b. IfIsCallable(getter)isfalseandgetteris notundefined, then throw aTypeErrorexception. c. Set the[[Get]]field ofdesctogetter. - If the result of calling the
[[HasProperty]]internal method ofObjwith argument"set"istrue, then: a. Letsetterbe the result of calling the[[Get]]internal method ofObjwith argument"set". b. IfIsCallable(setter)isfalseandsetteris notundefined, then throw a TypeError exception. c. Set the[[Set]]field ofdesctosetter. - If either
desc.[[Get]]ordesc.[[Set]]are present, then: a. If eitherdesc.[[Value]]ordesc.[[Writable]]are present, then throw aTypeErrorexception. - Return
desc.
Notes:
- Since
[[Get]]is used to read the descriptor value fields, they can be inherited from a parent object, and they can also be accessors. - Setter/getter values must be either callable or
undefinedif they are present. In particular,nullis not an allowed value. - Any call to
[[Get]]may cause an exception (e.g. if the property is an accessor with a throwing getter). In addition, there are explicit exceptions for object type check and setter/getter check. The order of checking and coercion thus matters, at least if the errors thrown have a message indicating the failing check. All the exceptions are of the same type (TypeError), so a chance in ordering is not strictly a compliance issue (there are no guaranteed error messages). ToBoolean()has no side effects and is guaranteed to succeed.
The algorithm in the specification is expressed quite verbosely; the following is a reformulation with less text, the target object has also been renamed to O:
- If
Type(O)is notObjectthrow aTypeErrorexception. - Let
descbe a new, empty Property Descriptor. - If
O.[[HasProperty]]("enumerable")===true, then setdesc.[[Enumerable]]toToBoolean(O.[[Get]]("enumerable")). - If
O.[[HasProperty]]("configurable")===true, then setdesc.[[Configurable]]toToBoolean(O.[[Get]]("configurable")). - If
O.[[HasProperty]]("value")===true, then setdesc.[[Value]]toO.[[Get]]("value"). - If
O.[[HasProperty]]("writable")===true, then setdesc.[[Writable]]toToBoolean(O.[[Get]]("writable")). - If
O.[[HasProperty]]("get")===true, then: a. Setdesc.[[Get]]toO.[[Get]]("get"). b. Ifdesc.[[Get]]!==undefinedandIsCallable(desc.[[Get]])===false, then throw aTypeErrorexception. - If
O.[[HasProperty]]("set")===true, then: a. Setdesc.[[Set]]toO.[[Get]]("set"). b. Ifdesc.[[Set]]!==undefinedandIsCallable(desc.[[Set]])===false, then throw aTypeErrorexception. - If either
desc.[[Get]]ordesc.[[Set]]are present, then: a. If eitherdesc.[[Value]]ordesc.[[Writable]]are present, then throw aTypeErrorexception. - Return
desc.
NormalizePropertyDescriptor
This algorithm is not defined in the E5 specification, but is used as an internal helper for implementing Object.defineProperties() and Object.defineProperty().
The algorithm is a variant of ToPropertyDescriptor() which, instead of an internal descriptor, outputs an equivalent ECMAScript property descriptor which has been fully validated, and contains only "own" data properties. If the resulting ECMAScript object, desc, is later given to ToPropertyDescriptor():
- The call cannot fail.
- The call will yield the same internal descriptor as if given the original object.
- There can be no user visible side effects, because
desconly contains plain (own) values.
For instance, if the input property descriptor were:
{
get value() { return "test"; },
writable: 0.0,
configurable: "nonempty",
enumerable: new Date(),
additional: "ignored" // ignored, not relevant to a descriptor
}
the normalized descriptor would be:
{
value: "test",
writable: false,
configurable: true,
enumerable: true
}
(The example doesn't illustrate the fact that inherited properties are converted to "own" properties.)
The algorithm is as follows:
- If
Type(O)is notObjectthrow aTypeErrorexception. - Let
descbe a new, empty Object. - If
O.[[HasProperty]]("enumerable")===true, then calldesc.[[Put]]with the arguments"enumerable",ToBoolean(O.[[Get]]("enumerable"))andtrue. - If
O.[[HasProperty]]("configurable")===true, then calldesc.[[Put]]with the arguments"configurable",ToBoolean(O.[[Get]]("configurable"))andtrue. - If
O.[[HasProperty]]("value")===true, then calldesc.[[Put]]with the arguments"value",O.[[Get]]("value")andtrue. - If
O.[[HasProperty]]("writable")===true, then calldesc.[[Put]]with the arguments"writable",ToBoolean(O.[[Get]]("writable"))andtrue. - If
O.[[HasProperty]]("get")===true, then: a. LetgetterbeO.[[Get]]("get"). b. Ifgetter!==undefinedandIsCallable(getter)===false, then throw aTypeErrorexception. c. Calldesc.[[Put]]with the arguments"get",getterandtrue. - If
O.[[HasProperty]]("set")===true, then: a. LetsetterbeO.[[Get]]("set"). b. Ifsetter!==undefinedandIsCallable(setter)===false, then throw aTypeErrorexception. c. Calldesc.[[Put]]with the arguments"set",setterandtrue. - Validation: a. Let
gbedesc.[[HasProperty]]("get"). b. Letsbedesc.[[HasProperty]]("set"). c. Letvbedesc.[[HasProperty]]("value"). d. Letwbedesc.[[HasProperty]]("writable"). e. If(g || s) && (v || w)then throw aTypeErrorexception. - Return
desc.
Notes:
- The third argument to
desc.[[Put]]is theThrowflag. The value is irrelevant as the[[Put]]calls cannot fail.