Skip to content

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:

  • O has P as own property, it is a plain property, and [[Writable]] is false
  • O has P as own property, it is an accessor property, and is missing the [[Set]] function
  • P is found in O's prototype chain (not in O), it is a plain property, and either O.[[Extensible]] or property [[Writable]] is false
  • P is found in O's prototype chain (not in O), it is an accessor property, and is missing the [[Set]] function
  • P is not found in O's prototype chain, and O.[[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:

  1. desc = O.[[GetProperty]](P).
  2. If desc is undefined, return O.[[Extensible]].
  3. If IsAccessorDescriptor(desc): a. If desc.[[Set]] is undefined, return false. b. Else, return true.
  4. Else, desc must be a data descriptor: a. (CHANGED:) If desc was not found in the original object O, and O.[[Extensible]] is false, return false. b. Return desc.[[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

  1. Let prop be the result of calling the [[GetOwnProperty]] internal method of O with property name P.
  2. If prop is not undefined, return prop.
  3. Let proto be the value of the [[Prototype]] internal property of O.
  4. If proto is null, return undefined.
  5. Return the result of calling the [[GetProperty]] internal method of proto with argument P.

Eliminating recursion

This is better unwound into a loop (using desc instead of prop, as it is more descriptive):

  1. Let curr be O.
  2. While curr is not null: a. Let desc be the result of calling the [[GetOwnProperty]] internal method of curr with property name P. b. If desc is not undefined, return desc. c. Let curr be the value of the [[Prototype]] internal property of curr.
  3. 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):

  1. Let curr be O.
  2. NEXT: Let desc be the result of calling the [[GetOwnProperty]] internal method of curr with property name P.
  3. If desc is not undefined, return desc.
  4. Let curr be the value of the [[Prototype]] internal property of curr.
  5. If curr is not null, goto NEXT.
  6. 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:

  1. curr = O
  2. NEXT: If curr has own property P: a. Let D be a newly created Property Descriptor with no fields. b. Let X be curr's own property named P. c. If X is a data property, then 1. Set D.[[Value]] to the value of X's [[Value]] attribute. 2. Set D.[[Writable]] to the value of X's [[Writable]] attribute. d. Else X is an accessor property, so 1. Set D.[[Get]] to the value of X's [[Get]] attribute. 2. Set D.[[Set]] to the value of X's [[Set]] attribute. e. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute. f. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute. g. Return D.
  3. Let curr be the value of the [[Prototype]] internal property of curr.
  4. If curr is not null, goto NEXT.
  5. 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:

  1. curr = O
  2. NEXT: Let X be curr's own property named P. If curr doesn't have an own property with name P: a. If curr is not a String instance, goto NOTFOUND. b. (String object exotic behavior.) Let str be the String value of the [[PrimitiveValue]] internal property of O and len be the number of characters in str. c. If P is "length": 1. Return a Property Descriptor with the values: - [[Value]]: len (a primitive number) - [[Enumerable]]: false - [[Writable]]: false - [[Configurable]]: false d. If P is an array index (E5 Section 15.4): 1. Let index be ToUint32(P). 2. If index < len, return a Property Descriptor with the values: - [[Value]]: a primitive string of length 1, containing one character from str at position index (zero based index) - [[Enumerable]]: true - [[Writable]]: false - [[Configurable]]: false e. Goto NOTFOUND.
  3. Let D be a newly created Property Descriptor filled as follows: a. If X is a data property: 1. Set D.[[Value]] to the value of X's [[Value]] attribute. 2. Set D.[[Writable]] to the value of X's [[Writable]] attribute. b. Else X is an accessor property: 1. Set D.[[Get]] to the value of X's [[Get]] attribute. 2. Set D.[[Set]] to the value of X's [[Set]] attribute. c. For either type of property: 1. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute. 2. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
  4. If curr is an arguments object which contains a [[ParameterMap]] internal property: a. (Arguments object exotic behavior.) Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. If the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument is not undefined, then: 1. Set D.[[Value]] to the result of calling the [[Get]] internal method of map passing P as the argument.
  5. Return D.
  6. NOTFOUND: Let curr be the value of the [[Prototype]] internal property of curr.
  7. If curr is not null, goto NEXT.
  8. 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:

  1. If O is an arguments object which contains a [[ParameterMap]] internal property: a. (Arguments object exotic behavior.) Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. If the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument is not undefined: 1. Return the result of calling the [[Get]] internal method of map passing P as the argument.
  2. curr = O
  3. NEXT: Let X be curr's own property named P. If curr doesn't have an own property with name P: a. If curr is not a String instance, goto NOTFOUND. b. (String object exotic behavior.) Let str be the String value of the [[PrimitiveValue]] internal property of O and len be the number of characters in str. c. If P is "length": 1. Return len (a primitive number). (No need to check for arguments object exotic behavior or "caller" property exotic behavior.) d. If P is an array index (E5 Section 15.4): 1. Let index be ToUint32(P). 2. If index < len: a. Return a primitive string of length 1, containing one character from str at position index (zero based index). (No need to check for arguments object exotic behavior or "caller" property exotic behavior.) e. Goto NOTFOUND.
  4. If X is a data property: a. Set res to the value of X's [[Value]] attribute. b. Goto FOUND1
  5. Else X is an accessor property: a. Let getter be X's [[Get]] attribute. b. If getter is undefined: 1. Return undefined. (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 result undefined is not a strict mode function. Thus, no "goto FOUND1" here.) c. Else let res be the result of calling the [[Call]] internal method of getter providing O as the this value 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.)
  6. FOUND1: If curr is an arguments object which contains a [[ParameterMap]] internal property: a. (Arguments object exotic behavior.) Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. If the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument is not undefined, then: 1. Set res to the result of calling the [[Get]] internal method of map passing P as the argument.
  7. FOUND2: If O is a Function object or an arguments object which contains a [[ParameterMap]] internal property: a. (Arguments or Function object exotic behavior.) If P is "caller" and res is a strict mode Function object, throw a TypeError exception.
  8. Return res.
  9. NOTFOUND: Let curr be the value of the [[Prototype]] internal property of curr.
  10. If curr is not null, goto NEXT.
  11. 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.3
  • Object.getOwnPropertyNames, E5 Section 15.2.3.4
  • Object.defineProperty, E5 Section 15.2.3.6
  • Object.seal, E5 Section 15.2.3.8
  • Object.freeze, E5 Section 15.2.3.9
  • Object.keys, E5 Section 15.2.3.14
  • Function.prototype.bind, E5 Section 15.3.4.5
  • Array.prototype.concat, E5 Section 15.4.4.4
  • Array.prototype.slice, E5 Section 15.4.4.10
  • Array.prototype.splice, E5 Section 15.4.4.12
  • Array.prototype.map, E5 Section 15.4.4.19
  • Array.prototype.filter, E5 Section 15.4.4.20
  • String.prototype.match, E5 Section 15.5.4.10
  • String.prototype.split, E5 Section 15.5.4.14
  • RegExp.prototype.exec, E5 Section 15.10.6.2
  • JSON.parse, E5 Section 15.12.2
  • JSON.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 length of an Array object 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:

  • current is not undefined
  • IsGenericDescriptor(current) is false
  • IsDataDescriptor(current) is true
  • IsAccessorDescriptor(current) is false
  • IsGenericDescriptor(Desc) is false
  • IsDataDescriptor(Desc) is true
  • IsAccessorDescriptor(Desc) is false

Taking the [[DefineOwnProperty]] with all exotic behaviors included, using the above assumptions, eliminating any unnecessary steps, cleaning up and clarifying, we get:

  1. If O is an Array object, and P is "length", then: a. Let newLen be ToUint32(Desc.[[Value]]). b. If newLen is not equal to ToNumber(Desc.[[Value]]), throw a RangeError exception. Note that this is unconditional (thrown even if Throw is false). c. Let oldLenDesc be the result of calling the [[GetOwnProperty]] internal method of O passing "length" as the argument. The result will never be undefined or an accessor descriptor because Array objects are created with a length data property that cannot be deleted or reconfigured. d. Let oldLen be oldLenDesc.[[Value]]. (Note that oldLen is guaranteed to be a unsigned 32-bit integer.) e. If newLen < oldLen, then: 1. Let shortenSucceeded, finalLen be the result of calling the internal helper ShortenArray() with oldLen and newLen. 2. Update the property ("length") value to finalLen. 3. Goto REJECT, if shortenSucceeded is false. 4. Return. f. Update the property ("length") value to newLen. g. Return.
  2. Set the [[Value]] attribute of the property named P of object O to the value of Desc.[[Value]]. (Since it is side effect free to update the value with the same value, no check for that case is needed.)
  3. If O is an arguments object which has a [[ParameterMap]] internal property: a. Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. If the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument is not undefined, then: 1. Call the [[Put]] internal method of map passing P, Desc.[[Value]], and Throw as the arguments. (This updates the bound variable value.)
  4. 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]]:

  1. If O is an Array object, and P is "length", then: a. Let newLen be ToUint32(val). b. If newLen is not equal to ToNumber(val), throw a RangeError exception. Note that this is unconditional (thrown even if Throw is false). c. Let oldLenDesc be the result of calling the [[GetOwnProperty]] internal method of O passing "length" as the argument. The result will never be undefined or an accessor descriptor because Array objects are created with a length data property that cannot be deleted or reconfigured. d. Let oldLen be oldLenDesc.[[Value]]. (Note that oldLen is guaranteed to be a unsigned 32-bit integer.) e. If newLen < oldLen, then: 1. Let shortenSucceeded, finalLen be the result of calling the internal helper ShortenArray() with oldLen and newLen. 2. Update the property ("length") value to finalLen. 3. Goto REJECT, if shortenSucceeded is false. 4. Return. f. Update the property ("length") value to newLen. g. Return.
  2. Set the [[Value]] attribute of the property named P of object O to val. (Since it is side effect free to update the value with the same value, no check for that case is needed.)
  3. If O is an arguments object which has a [[ParameterMap]] internal property: a. Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. If the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument is not undefined, then: 1. Call the [[Put]] internal method of map passing P, val, and Throw as the arguments. (This updates the bound variable value.)
  4. 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 name P cannot be "length" (as that would exist)

More specifically, we know that in the [[DefineOwnProperty]] algorithm:

  • current is undefined

Taking the [[DefineOwnProperty]] with all exotic behaviors included, using the above assumptions, and then eliminating any unnecessary steps, cleaning up and clarifying, we get:

  1. If O is an Array object and P is an array index (E5 Section 15.4), then: a. Let oldLenDesc be the result of calling the [[GetOwnProperty]] internal method of O passing "length" as the argument. The result will never be undefined or an accessor descriptor because Array objects are created with a length data property that cannot be deleted or reconfigured. b. Let oldLen be oldLenDesc.[[Value]]. (Note that oldLen is guaranteed to be a unsigned 32-bit integer.) c. Let index be ToUint32(P). d. Goto REJECT if index >= oldLen and oldLenDesc.[[Writable]] is false.

  2. Create an own data property named P of object O whose [[Value]], [[Writable]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc.

  3. If O is an Array object, P is an array index and index >= oldLen: a. Update the "length" property of O to the value index + 1. This always succeeds, because we've checked in the pre-step that the "length" is writable, and since P is an array index property, the length must still be writable here.

  4. If O is an arguments object which has a [[ParameterMap]] internal property: a. Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. If the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument is not undefined, then: 1. Call the [[Put]] internal method of map passing P, Desc.[[Value]], and Throw as the arguments. (This updates the bound variable value.)

  5. 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:

  1. If O is an Array object and P is an array index (E5 Section 15.4), then: a. Let oldLenDesc be the result of calling the [[GetOwnProperty]] internal method of O passing "length" as the argument. The result will never be undefined or an accessor descriptor because Array objects are created with a length data property that cannot be deleted or reconfigured. b. Let oldLen be oldLenDesc.[[Value]]. (Note that oldLen is guaranteed to be a unsigned 32-bit integer.) c. Let index be ToUint32(P). d. If index >= oldLen: 1. Goto REJECT oldLenDesc.[[Writable]] is false. 2. Update the "length" property of O to the value index + 1. This always succeeds.

  2. Create an own data property named P of object O whose [[Value]], [[Writable]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc.

  3. 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]]:

  1. If O is an Array object and P is an array index (E5 Section 15.4), then: a. Let oldLenDesc be the result of calling the [[GetOwnProperty]] internal method of O passing "length" as the argument. The result will never be undefined or an accessor descriptor because Array objects are created with a length data property that cannot be deleted or reconfigured. b. Let oldLen be oldLenDesc.[[Value]]. (Note that oldLen is guaranteed to be a unsigned 32-bit integer.) c. Let index be ToUint32(P). d. If index >= oldLen: 1. Goto REJECT oldLenDesc.[[Writable]] is false. 2. Update the "length" property of O to the value index + 1. This always succeeds.
  2. Create an own data property named P of object O whose attributes are:
    • [[Value]]: val
    • [[Writable]]: true
    • [[Enumerable]]: true
    • [[Configurable]]: true
  3. Return true.
  4. REJECT: If Throw is true, then throw a TypeError exception, otherwise return false.

Notes:

  • If step 2 fails due to an out-of-memory or other internal error, we may have updated length already. 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:

  1. Create an own data property named P of object O whose [[Value]], [[Writable]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc.
  2. 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 Throw is true, then throw a TypeError exception; else return.

Original algorithm

For object O, property P, and value V:

  1. If the result of calling the [[CanPut]] internal method of O with argument P is false, then a. If Throw is true, then throw a TypeError exception. b. Else return.
  2. Let ownDesc be the result of calling the [[GetOwnProperty]] internal method of O with argument P.
  3. If IsDataDescriptor(ownDesc) is true, then a. Let valueDesc be the Property Descriptor {[[Value]]: V}. b. Call the [[DefineOwnProperty]] internal method of O passing P, valueDesc, and Throw as arguments. c. Return.
  4. Let desc be the result of calling the [[GetProperty]] internal method of O with argument P. This may be either an own or inherited accessor property descriptor or an inherited data property descriptor.
  5. If IsAccessorDescriptor(desc) is true, then a. Let setter be desc.[[Set]] which cannot be undefined. b. Call the [[Call]] internal method of setter providing O as the this value and providing V as the sole argument.
  6. Else, create a named data property named P on object O as follows a. Let newDesc be the Property Descriptor: - [[Value]]: V - [[Writable]]: true - [[Enumerable]]: true - [[Configurable]]: true} b. Call the [[DefineOwnProperty]] internal method of O passing P, newDesc, and Throw as arguments.
  7. Return.

Notes:

  • Step 5.a: setter cannot be undefined at this point because [[CanPut]] has checked it (and throws an exception if it is undefined).

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):

  1. If the result of calling the [[CanPut]] internal method of O with argument P is false, then Reject.
  2. Let desc be the result of calling the [[GetProperty]] internal method of O with argument P. (Note: here we assume that we also get to know whether the property was found in O or in its ancestor.)
  3. If IsAccessorDescriptor(desc) is true, then: a. Call the [[Call]] internal method of desc.[[Set]] providing O as the this value and providing V as the sole argument. (Note: desc.[[Set]] cannot be undefined, as this is checked by [[CanPut]].)
  4. Else if desc was found in O directly (as an "own data property"), then: a. Let valueDesc be the Property Descriptor {[[Value]]: V}. b. Call the [[DefineOwnProperty]] internal method of O passing P, valueDesc, and Throw as arguments.
  5. Else desc is an inherited data property or undefined, then: a. Let newDesc be the Property Descriptor: - [[Value]]: V - [[Writable]]: true - [[Enumerable]]: true - [[Configurable]]: true} b. Call the [[DefineOwnProperty]] internal method of O passing P, newDesc, and Throw as arguments.
  6. 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:

  1. Let desc be the result of calling the [[GetProperty]] internal method of O with argument P. (Note: here we assume that we also get to know whether the property was found in O or in its ancestor.)
  2. If IsAccessorDescriptor(desc) is true, then: a. If desc.[[Set]] is undefined, Reject. b. Call the [[Call]] internal method of desc.[[Set]] providing O as the this value and providing V as the sole argument.
  3. Else if desc is an inherited (data) property, then: a. If O.[[Extensible]] is false, Reject. b. If desc.[[Writable]] is false, Reject. c. Let newDesc be the Property Descriptor: - [[Value]]: V - [[Writable]]: true - [[Enumerable]]: true - [[Configurable]]: true} d. Call the [[DefineOwnProperty]] internal method of O passing P, newDesc, and Throw as arguments.
  4. Else if desc was not found (is undefined): a. If O.[[Extensible]] is false, Reject. b. Let newDesc be the Property Descriptor: - [[Value]]: V - [[Writable]]: true - [[Enumerable]]: true - [[Configurable]]: true} c. Call the [[DefineOwnProperty]] internal method of O passing P, newDesc, and Throw as arguments.
  5. Else desc was found in O directly (as an "own data property"), then: a. If desc.[[Writable]] is false, Reject. b. Let valueDesc be the Property Descriptor {[[Value]]: V}. c. Call the [[DefineOwnProperty]] internal method of O passing P, valueDesc, and Throw as arguments.
  6. Return.

The above can be further refined to (making also the modification required to [[GetProperty]] explicit):

  1. Let desc and inherited be the result of calling the [[GetProperty]] internal method of O with argument P.
  2. If IsAccessorDescriptor(desc) is true, then: a. If desc.[[Set]] is undefined, Reject. b. Call the [[Call]] internal method of desc.[[Set]] providing O as the this value and providing V as the sole argument.
  3. Else if desc is not undefined and inherited is false (own data property), then: a. If desc.[[Writable]] is false, Reject. b. Let valueDesc be the Property Descriptor {[[Value]]: V}. c. Call the [[DefineOwnProperty]] internal method of O passing P, valueDesc, and Throw as arguments.
  4. Else desc is an inherited (data) property or undefined: a. If O.[[Extensible]] is false, Reject. b. If desc is not undefined and desc.[[Writable]] is false, Reject. (In other words: desc was inherited and is non-writable.) c. Let newDesc be the Property Descriptor: - [[Value]]: V - [[Writable]]: true - [[Enumerable]]: true - [[Configurable]]: true} d. Call the [[DefineOwnProperty]] internal method of O passing P, newDesc, and Throw as arguments.
  5. 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:

  1. Set curr to O.
  2. While curr !== null: a. If O does not have own property P: 1. Set curr to curr.[[Prototype]] 2. Continue (while loop) b. Let desc be the descriptor for own property P c. If IsDataDescriptor(desc): 1. If curr != O (property is an inherited data property): (Note: assumes there are no prototype loops.) a. If O.[[Extensible] is false, Reject. b. If desc.[[Writable]] is false, Reject. c. Let newDesc be a property descriptor with values: - [[Value]]: V - [[Writable]]: true - [[Enumerable]]: true - [[Configurable]]: true} d. Call O.[[DefineOwnProperty]](P, newDesc, Throw). 2. Else (property is an own data property): a. If desc.[[Writable]] is false, Reject. b. Let valueDesc be { [[Value]]: V }. c. Call O.[[DefineOwnProperty]](P, valueDesc, Throw). d. Else (property is an accessor): 1. If desc.[[Set]] is undefined, Reject. 2. Call the [[Call]] internal method of desc.[[Set]] providing O as the this value and providing V as the sole argument. e. Return.
  3. Property was not found in the prototype chain: a. If O.[[Extensible]] is false, Reject. b. Let newDesc be a property descriptor with values: - [[Value]]: V - [[Writable]]: true - [[Enumerable]]: true - [[Configurable]]: true} c. Call O.[[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):

  1. Let curr be O.
  2. NEXT: Let desc be the result of calling the [[GetOwnProperty]] internal method of curr with property name P.
  3. If desc is undefined: a. Let curr be the value of the [[Prototype]] internal property of curr. b. If curr is not null, goto NEXT. c. If O.[[Extensible]] is false, Reject. d. Let newDesc be a property descriptor with values: - [[Value]]: V - [[Writable]]: true - [[Enumerable]]: true - [[Configurable]]: true} e. Call O.[[DefineOwnProperty]](P, newDesc, Throw). f. Return.
  4. If IsDataDescriptor(desc): a. If curr != O (property is an inherited data property): (Note: assumes there are no prototype loops.) 1. If O.[[Extensible] is false, Reject. 2. If desc.[[Writable]] is false, Reject. 3. Let newDesc be a property descriptor with values: - [[Value]]: V - [[Writable]]: true - [[Enumerable]]: true - [[Configurable]]: true} 4. Call O.[[DefineOwnProperty]](P, newDesc, Throw). b. Else (property is an own data property): 1. If desc.[[Writable]] is false, Reject. 2. Let valueDesc be { [[Value]]: V }. 3. Call O.[[DefineOwnProperty]](P, valueDesc, Throw).
  5. Else (property is an accessor): a. If desc.[[Set]] is undefined, Reject. b. Call the [[Call]] internal method of desc.[[Set]] providing O as the this value and providing V as the sole argument.
  6. 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, if desc contains either[[Set]] or [[Get]]
  • IsDataDescriptor(desc): true, if desc contains either[[Value]] or [[Writable]]
  • IsGenericDescriptor(desc): true if both IsAccessorDescriptor(desc) and IsGenericDescriptor are false; concretely:
    • desc contains none of the following: [[Set]], [[Get]], [[Value]], [[Writable]]
    • desc may 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() uses ToPropertyDescriptor() to convert the ECMAScript object into an internal property descriptor
  • ToPropertyDescriptor() creates a property descriptor and throws a TypeError if 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:

  1. If Type(Obj) is not Object throw a TypeError exception.
  2. Let desc be the result of creating a new Property Descriptor that initially has no fields.
  3. If the result of calling the [[HasProperty]] internal method of Obj with argument "enumerable" is true, then: a. Let enum be the result of calling the [[Get]] internal method of Obj with "enumerable". b. Set the [[Enumerable]] field of desc to ToBoolean(enum).
  4. If the result of calling the [[HasProperty]] internal method of Obj with argument "configurable" is true, then: a. Let conf be the result of calling the [[Get]] internal method of Obj with argument "configurable". b. Set the [[Configurable]] field of desc to ToBoolean(conf).
  5. If the result of calling the [[HasProperty]] internal method of Obj with argument "value" is true, then: a. Let value be the result of calling the [[Get]] internal method of Obj with argument "value". b. Set the [[Value]] field of desc to value.
  6. If the result of calling the [[HasProperty]] internal method of Obj with argument "writable" is true, then: a. Let writable be the result of calling the [[Get]] internal method of Obj with argument "writable". b. Set the [[Writable]] field of desc to ToBoolean(writable).
  7. If the result of calling the [[HasProperty]] internal method of Obj with argument "get" is true, then: a. Let getter be the result of calling the [[Get]] internal method of Obj with argument "get". b. If IsCallable(getter) is false and getter is not undefined, then throw a TypeError exception. c. Set the [[Get]] field of desc to getter.
  8. If the result of calling the [[HasProperty]] internal method of Obj with argument "set" is true, then: a. Let setter be the result of calling the [[Get]] internal method of Obj with argument "set". b. If IsCallable(setter) is false and setter is not undefined, then throw a TypeError exception. c. Set the [[Set]] field of desc to setter.
  9. If either desc.[[Get]] or desc.[[Set]] are present, then: a. If either desc.[[Value]] or desc.[[Writable]] are present, then throw a TypeError exception.
  10. 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 undefined if they are present. In particular, null is 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:

  1. If Type(O) is not Object throw a TypeError exception.
  2. Let desc be a new, empty Property Descriptor.
  3. If O.[[HasProperty]]("enumerable") === true, then set desc.[[Enumerable]] to ToBoolean(O.[[Get]]("enumerable")).
  4. If O.[[HasProperty]]("configurable") === true, then set desc.[[Configurable]] to ToBoolean(O.[[Get]]("configurable")).
  5. If O.[[HasProperty]]("value") === true, then set desc.[[Value]] to O.[[Get]]("value").
  6. If O.[[HasProperty]]("writable") === true, then set desc.[[Writable]] to ToBoolean(O.[[Get]]("writable")).
  7. If O.[[HasProperty]]("get") === true, then: a. Set desc.[[Get]] to O.[[Get]]("get"). b. If desc.[[Get]] !== undefined and IsCallable(desc.[[Get]]) === false, then throw a TypeError exception.
  8. If O.[[HasProperty]]("set") === true, then: a. Set desc.[[Set]] to O.[[Get]]("set"). b. If desc.[[Set]] !== undefined and IsCallable(desc.[[Set]]) === false, then throw a TypeError exception.
  9. If either desc.[[Get]] or desc.[[Set]] are present, then: a. If either desc.[[Value]] or desc.[[Writable]] are present, then throw a TypeError exception.
  10. 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 desc only 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:

  1. If Type(O) is not Object throw a TypeError exception.
  2. Let desc be a new, empty Object.
  3. If O.[[HasProperty]]("enumerable") === true, then call desc.[[Put]] with the arguments "enumerable", ToBoolean(O.[[Get]]("enumerable")) and true.
  4. If O.[[HasProperty]]("configurable") === true, then call desc.[[Put]] with the arguments "configurable", ToBoolean(O.[[Get]]("configurable")) and true.
  5. If O.[[HasProperty]]("value") === true, then call desc.[[Put]] with the arguments "value", O.[[Get]]("value") and true.
  6. If O.[[HasProperty]]("writable") === true, then call desc.[[Put]] with the arguments "writable", ToBoolean(O.[[Get]]("writable")) and true.
  7. If O.[[HasProperty]]("get") === true, then: a. Let getter be O.[[Get]]("get"). b. If getter !== undefined and IsCallable(getter) === false, then throw a TypeError exception. c. Call desc.[[Put]] with the arguments "get", getter and true.
  8. If O.[[HasProperty]]("set") === true, then: a. Let setter be O.[[Get]]("set"). b. If setter !== undefined and IsCallable(setter) === false, then throw a TypeError exception. c. Call desc.[[Put]] with the arguments "set", setter and true.
  9. Validation: a. Let g be desc.[[HasProperty]]("get"). b. Let s be desc.[[HasProperty]]("set"). c. Let v be desc.[[HasProperty]]("value"). d. Let w be desc.[[HasProperty]]("writable"). e. If (g || s) && (v || w) then throw a TypeError exception.
  10. Return desc.

Notes:

  • The third argument to desc.[[Put]] is the Throw flag. The value is irrelevant as the [[Put]] calls cannot fail.