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
hasP
as own property, it is a plain property, and[[Writable]]
is falseO
hasP
as own property, it is an accessor property, and is missing the[[Set]]
functionP
is found inO
's prototype chain (not inO
), it is a plain property, and eitherO.[[Extensible]]
or property[[Writable]]
is falseP
is found inO
's prototype chain (not inO
), it is an accessor property, and is missing the[[Set]]
functionP
is 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
desc
isundefined
, returnO.[[Extensible]]
. - If
IsAccessorDescriptor(desc)
: a. Ifdesc.[[Set]]
isundefined
, returnfalse
. b. Else, returntrue
. - Else,
desc
must be a data descriptor: a. (CHANGED:) Ifdesc
was 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
prop
be the result of calling the[[GetOwnProperty]]
internal method ofO
with property nameP
. - If
prop
is notundefined
, returnprop
. - Let
proto
be the value of the[[Prototype]]
internal property ofO
. - If
proto
isnull
, returnundefined
. - Return the result of calling the
[[GetProperty]]
internal method ofproto
with argumentP
.
Eliminating recursion
This is better unwound into a loop (using desc
instead of prop
, as it is more descriptive):
- Let
curr
beO
. - While
curr
is notnull
: a. Letdesc
be the result of calling the[[GetOwnProperty]]
internal method ofcurr
with property nameP
. b. Ifdesc
is notundefined
, returndesc
. c. Letcurr
be 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
curr
beO
. - NEXT: Let
desc
be the result of calling the[[GetOwnProperty]]
internal method ofcurr
with property nameP
. - If
desc
is notundefined
, returndesc
. - Let
curr
be the value of the[[Prototype]]
internal property ofcurr
. - If
curr
is 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
curr
has own propertyP
: a. LetD
be a newly created Property Descriptor with no fields. b. LetX
becurr
's own property named P. c. IfX
is 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. ElseX
is 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
curr
be the value of the[[Prototype]]
internal property ofcurr
. - If
curr
is 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
X
becurr
's own property namedP
. Ifcurr
doesn't have an own property with nameP
: a. Ifcurr
is not aString
instance, goto NOTFOUND. b. (String
object exotic behavior.) Letstr
be the String value of the[[PrimitiveValue]]
internal property ofO
andlen
be the number of characters instr
. c. IfP
is"length"
: 1. Return a Property Descriptor with the values: -[[Value]]: len
(a primitive number) -[[Enumerable]]: false
-[[Writable]]: false
-[[Configurable]]: false
d. IfP
is an array index (E5 Section 15.4): 1. Letindex
beToUint32(P)
. 2. Ifindex
<len
, return a Property Descriptor with the values: -[[Value]]:
a primitive string of length 1, containing one character fromstr
at positionindex
(zero based index) -[[Enumerable]]: true
-[[Writable]]: false
-[[Configurable]]: false
e. Goto NOTFOUND. - Let
D
be a newly created Property Descriptor filled as follows: a. IfX
is a data property: 1. SetD.[[Value]]
to the value ofX
's[[Value]]
attribute. 2. SetD.[[Writable]]
to the value ofX
's[[Writable]]
attribute. b. ElseX
is 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
curr
is anarguments
object which contains a[[ParameterMap]]
internal property: a. (Arguments object exotic behavior.) Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then: 1. SetD.[[Value]]
to the result of calling the[[Get]]
internal method ofmap
passingP
as the argument. - Return
D
. - NOTFOUND: Let
curr
be the value of the[[Prototype]]
internal property ofcurr
. - If
curr
is 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
O
is anarguments
object which contains a[[ParameterMap]]
internal property: a. (Arguments object exotic behavior.) Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
: 1. Return the result of calling the[[Get]]
internal method ofmap
passingP
as the argument. curr
=O
- NEXT: Let
X
becurr
's own property namedP
. Ifcurr
doesn't have an own property with nameP
: a. Ifcurr
is not aString
instance, goto NOTFOUND. b. (String
object exotic behavior.) Letstr
be the String value of the[[PrimitiveValue]]
internal property ofO
andlen
be the number of characters instr
. c. IfP
is"length"
: 1. Returnlen
(a primitive number). (No need to check for arguments object exotic behavior or"caller"
property exotic behavior.) d. IfP
is an array index (E5 Section 15.4): 1. Letindex
beToUint32(P)
. 2. Ifindex
<len
: a. Return a primitive string of length 1, containing one character fromstr
at positionindex
(zero based index). (No need to check for arguments object exotic behavior or"caller"
property exotic behavior.) e. Goto NOTFOUND. - If
X
is a data property: a. Setres
to the value ofX
's[[Value]]
attribute. b. Goto FOUND1 - Else
X
is an accessor property: a. Letgetter
beX
's[[Get]]
attribute. b. Ifgetter
isundefined
: 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 resultundefined
is not a strict mode function. Thus, no "goto FOUND1" here.) c. Else letres
be the result of calling the[[Call]]
internal method ofgetter
providingO
as thethis
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.) - FOUND1: If
curr
is anarguments
object which contains a[[ParameterMap]]
internal property: a. (Arguments object exotic behavior.) Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then: 1. Setres
to the result of calling the[[Get]]
internal method ofmap
passingP
as the argument. - FOUND2: If
O
is aFunction
object or anarguments
object which contains a[[ParameterMap]]
internal property: a. (Arguments or Function object exotic behavior.) IfP
is"caller"
andres
is a strict modeFunction
object, throw aTypeError
exception. - Return
res
. - NOTFOUND: Let
curr
be the value of the[[Prototype]]
internal property ofcurr
. - If
curr
is 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
length
of anArray
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 notundefined
IsGenericDescriptor(current)
isfalse
IsDataDescriptor(current)
istrue
IsAccessorDescriptor(current)
isfalse
IsGenericDescriptor(Desc)
isfalse
IsDataDescriptor(Desc)
istrue
IsAccessorDescriptor(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
O
is anArray
object, andP
is"length"
, then: a. LetnewLen
beToUint32(Desc.[[Value]])
. b. IfnewLen
is not equal toToNumber(Desc.[[Value]])
, throw aRangeError
exception. Note that this is unconditional (thrown even ifThrow
isfalse
). c. LetoldLenDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
passing"length"
as the argument. The result will never beundefined
or an accessor descriptor becauseArray
objects are created with alength
data property that cannot be deleted or reconfigured. d. LetoldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) e. IfnewLen
<oldLen
, then: 1. LetshortenSucceeded
,finalLen
be the result of calling the internal helperShortenArray()
witholdLen
andnewLen
. 2. Update the property ("length"
) value tofinalLen
. 3. Goto REJECT, ifshortenSucceeded
isfalse
. 4. Return. f. Update the property ("length"
) value tonewLen
. g. Return. - Set the
[[Value]]
attribute of the property namedP
of objectO
to 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
O
is an arguments object which has a[[ParameterMap]]
internal property: a. Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then: 1. Call the[[Put]]
internal method ofmap
passingP
,Desc.[[Value]]
, andThrow
as 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
O
is anArray
object, andP
is"length"
, then: a. LetnewLen
beToUint32(val)
. b. IfnewLen
is not equal toToNumber(val)
, throw aRangeError
exception. Note that this is unconditional (thrown even ifThrow
isfalse
). c. LetoldLenDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
passing"length"
as the argument. The result will never beundefined
or an accessor descriptor becauseArray
objects are created with alength
data property that cannot be deleted or reconfigured. d. LetoldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) e. IfnewLen
<oldLen
, then: 1. LetshortenSucceeded
,finalLen
be the result of calling the internal helperShortenArray()
witholdLen
andnewLen
. 2. Update the property ("length"
) value tofinalLen
. 3. Goto REJECT, ifshortenSucceeded
isfalse
. 4. Return. f. Update the property ("length"
) value tonewLen
. g. Return. - Set the
[[Value]]
attribute of the property namedP
of objectO
toval
. (Since it is side effect free to update the value with the same value, no check for that case is needed.) - If
O
is an arguments object which has a[[ParameterMap]]
internal property: a. Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then: 1. Call the[[Put]]
internal method ofmap
passingP
,val
, andThrow
as 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 nameP
cannot be"length"
(as that would exist)
More specifically, we know that in the [[DefineOwnProperty]]
algorithm:
current
isundefined
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
O
is anArray
object andP
is an array index (E5 Section 15.4), then: a. LetoldLenDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
passing"length"
as the argument. The result will never beundefined
or an accessor descriptor becauseArray
objects are created with a length data property that cannot be deleted or reconfigured. b. LetoldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) c. Letindex
beToUint32(P)
. d. Goto REJECT ifindex
>=oldLen
andoldLenDesc.[[Writable]]
isfalse
.Create an own data property named
P
of objectO
whose[[Value]]
,[[Writable]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
.If
O
is anArray
object,P
is an array index andindex
>=oldLen
: a. Update the"length"
property ofO
to the valueindex + 1
. This always succeeds, because we've checked in the pre-step that the"length"
is writable, and sinceP
is an array index property, the length must still be writable here.If
O
is an arguments object which has a[[ParameterMap]]
internal property: a. Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then: 1. Call the[[Put]]
internal method ofmap
passingP
,Desc.[[Value]]
, andThrow
as 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
O
is anArray
object andP
is an array index (E5 Section 15.4), then: a. LetoldLenDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
passing"length"
as the argument. The result will never beundefined
or an accessor descriptor becauseArray
objects are created with a length data property that cannot be deleted or reconfigured. b. LetoldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) c. Letindex
beToUint32(P)
. d. Ifindex
>=oldLen
: 1. Goto REJECToldLenDesc.[[Writable]]
isfalse
. 2. Update the"length"
property ofO
to the valueindex + 1
. This always succeeds.Create an own data property named
P
of objectO
whose[[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
O
is anArray
object andP
is an array index (E5 Section 15.4), then: a. LetoldLenDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
passing"length"
as the argument. The result will never beundefined
or an accessor descriptor becauseArray
objects are created with a length data property that cannot be deleted or reconfigured. b. LetoldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) c. Letindex
beToUint32(P)
. d. Ifindex
>=oldLen
: 1. Goto REJECToldLenDesc.[[Writable]]
isfalse
. 2. Update the"length"
property ofO
to the valueindex + 1
. This always succeeds. - Create an own data property named
P
of objectO
whose attributes are:[[Value]]: val
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true
- Return
true
. - REJECT: If
Throw
istrue
, then throw aTypeError
exception, otherwise returnfalse
.
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:
- Create an own data property named
P
of objectO
whose[[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
Throw
istrue
, then throw aTypeError
exception; else return.
Original algorithm
For object O
, property P
, and value V
:
- If the result of calling the
[[CanPut]]
internal method ofO
with argumentP
is false, then a. IfThrow
istrue
, then throw aTypeError
exception. b. Else return. - Let
ownDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
with argumentP
. - If
IsDataDescriptor(ownDesc)
istrue
, then a. LetvalueDesc
be the Property Descriptor{[[Value]]: V}
. b. Call the[[DefineOwnProperty]]
internal method ofO
passingP
,valueDesc
, andThrow
as arguments. c. Return. - Let
desc
be the result of calling the[[GetProperty]]
internal method ofO
with argumentP
. This may be either an own or inherited accessor property descriptor or an inherited data property descriptor. - If
IsAccessorDescriptor(desc)
istrue
, then a. Letsetter
bedesc.[[Set]]
which cannot beundefined
. b. Call the[[Call]]
internal method of setter providingO
as thethis
value and providingV
as the sole argument. - Else, create a named data property named
P
on objectO
as follows a. LetnewDesc
be the Property Descriptor: -[[Value]]: V
-[[Writable]]: true
-[[Enumerable]]: true
-[[Configurable]]: true}
b. Call the[[DefineOwnProperty]]
internal method ofO
passingP
,newDesc
, andThrow
as arguments. - Return.
Notes:
- Step 5.a:
setter
cannot beundefined
at 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 ofO
with argumentP
is false, then Reject. - Let
desc
be the result of calling the[[GetProperty]]
internal method ofO
with argumentP
. (Note: here we assume that we also get to know whether the property was found inO
or in its ancestor.) - If
IsAccessorDescriptor(desc)
istrue
, then: a. Call the[[Call]]
internal method ofdesc.[[Set]]
providingO
as thethis
value and providingV
as the sole argument. (Note:desc.[[Set]]
cannot beundefined
, as this is checked by[[CanPut]]
.) - Else if
desc
was found inO
directly (as an "own data property"), then: a. LetvalueDesc
be the Property Descriptor{[[Value]]: V}
. b. Call the[[DefineOwnProperty]]
internal method ofO
passingP
,valueDesc
, andThrow
as arguments. - Else
desc
is an inherited data property orundefined
, then: a. LetnewDesc
be the Property Descriptor: -[[Value]]: V
-[[Writable]]: true
-[[Enumerable]]: true
-[[Configurable]]: true}
b. Call the[[DefineOwnProperty]]
internal method ofO
passingP
,newDesc
, andThrow
as 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
desc
be the result of calling the[[GetProperty]]
internal method ofO
with argumentP
. (Note: here we assume that we also get to know whether the property was found inO
or in its ancestor.) - If
IsAccessorDescriptor(desc)
istrue
, then: a. Ifdesc.[[Set]]
isundefined
, Reject. b. Call the[[Call]]
internal method ofdesc.[[Set]]
providingO
as thethis
value and providingV
as the sole argument. - Else if
desc
is an inherited (data) property, then: a. IfO.[[Extensible]]
isfalse
, Reject. b. Ifdesc.[[Writable]]
isfalse
, Reject. c. LetnewDesc
be the Property Descriptor: -[[Value]]: V
-[[Writable]]: true
-[[Enumerable]]: true
-[[Configurable]]: true}
d. Call the[[DefineOwnProperty]]
internal method ofO
passingP
,newDesc
, andThrow
as arguments. - Else if
desc
was not found (isundefined
): a. IfO.[[Extensible]]
isfalse
, Reject. b. LetnewDesc
be the Property Descriptor: -[[Value]]: V
-[[Writable]]: true
-[[Enumerable]]: true
-[[Configurable]]: true}
c. Call the[[DefineOwnProperty]]
internal method ofO
passingP
,newDesc
, andThrow
as arguments. - Else
desc
was found inO
directly (as an "own data property"), then: a. Ifdesc.[[Writable]]
isfalse
, Reject. b. LetvalueDesc
be the Property Descriptor{[[Value]]: V}
. c. Call the[[DefineOwnProperty]]
internal method ofO
passingP
,valueDesc
, andThrow
as arguments. - Return.
The above can be further refined to (making also the modification required to [[GetProperty]]
explicit):
- Let
desc
andinherited
be the result of calling the[[GetProperty]]
internal method ofO
with argumentP
. - If
IsAccessorDescriptor(desc)
istrue
, then: a. Ifdesc.[[Set]]
isundefined
, Reject. b. Call the[[Call]]
internal method ofdesc.[[Set]]
providingO
as thethis
value and providingV
as the sole argument. - Else if
desc
is notundefined
andinherited
isfalse
(own data property), then: a. Ifdesc.[[Writable]]
isfalse
, Reject. b. LetvalueDesc
be the Property Descriptor{[[Value]]: V}
. c. Call the[[DefineOwnProperty]]
internal method ofO
passingP
,valueDesc
, andThrow
as arguments. - Else
desc
is an inherited (data) property orundefined
: a. IfO.[[Extensible]]
isfalse
, Reject. b. Ifdesc
is notundefined
anddesc.[[Writable]]
isfalse
, Reject. (In other words:desc
was inherited and is non-writable.) c. LetnewDesc
be the Property Descriptor: -[[Value]]: V
-[[Writable]]: true
-[[Enumerable]]: true
-[[Configurable]]: true}
d. Call the[[DefineOwnProperty]]
internal method ofO
passingP
,newDesc
, andThrow
as 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
curr
toO
. - While
curr
!==null
: a. IfO
does not have own propertyP
: 1. Setcurr
tocurr.[[Prototype]]
2. Continue (while loop) b. Letdesc
be the descriptor for own propertyP
c. 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. LetnewDesc
be 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. LetvalueDesc
be{ [[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]]
providingO
as thethis
value and providingV
as the sole argument. e. Return. - Property was not found in the prototype chain: a. If
O.[[Extensible]]
isfalse
, Reject. b. LetnewDesc
be 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
curr
beO
. - NEXT: Let
desc
be the result of calling the[[GetOwnProperty]]
internal method ofcurr
with property nameP
. - If
desc
isundefined
: a. Letcurr
be the value of the[[Prototype]]
internal property ofcurr
. b. Ifcurr
is notnull
, goto NEXT. c. IfO.[[Extensible]]
isfalse
, Reject. d. LetnewDesc
be 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. LetnewDesc
be 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. LetvalueDesc
be{ [[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]]
providingO
as thethis
value and providingV
as 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
, ifdesc
contains either[[Set]]
or[[Get]]
IsDataDescriptor(desc)
:true
, ifdesc
contains either[[Value]]
or[[Writable]]
IsGenericDescriptor(desc)
:true
if bothIsAccessorDescriptor(desc)
andIsGenericDescriptor
arefalse
; 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()
usesToPropertyDescriptor()
to convert the ECMAScript object into an internal property descriptorToPropertyDescriptor()
creates a property descriptor and throws aTypeError
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:
- If
Type(Obj)
is notObject
throw aTypeError
exception. - Let
desc
be the result of creating a new Property Descriptor that initially has no fields. - If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"enumerable"
istrue
, then: a. Letenum
be the result of calling the[[Get]]
internal method ofObj
with"enumerable"
. b. Set the[[Enumerable]]
field ofdesc
toToBoolean(enum)
. - If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"configurable"
istrue
, then: a. Letconf
be the result of calling the[[Get]]
internal method ofObj
with argument"configurable"
. b. Set the[[Configurable]]
field ofdesc
toToBoolean(conf)
. - If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"value"
istrue
, then: a. Letvalue
be the result of calling the[[Get]]
internal method ofObj
with argument"value"
. b. Set the[[Value]]
field ofdesc
tovalue
. - If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"writable"
istrue
, then: a. Letwritable
be the result of calling the[[Get]]
internal method ofObj
with argument"writable"
. b. Set the[[Writable]]
field ofdesc
toToBoolean(writable)
. - If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"get"
istrue
, then: a. Letgetter
be the result of calling the[[Get]]
internal method ofObj
with argument"get"
. b. IfIsCallable(getter)
isfalse
andgetter
is notundefined
, then throw aTypeError
exception. c. Set the[[Get]]
field ofdesc
togetter
. - If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"set"
istrue
, then: a. Letsetter
be the result of calling the[[Get]]
internal method ofObj
with argument"set"
. b. IfIsCallable(setter)
isfalse
andsetter
is notundefined
, then throw a TypeError exception. c. Set the[[Set]]
field ofdesc
tosetter
. - If either
desc.[[Get]]
ordesc.[[Set]]
are present, then: a. If eitherdesc.[[Value]]
ordesc.[[Writable]]
are present, then throw aTypeError
exception. - 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
:
- If
Type(O)
is notObject
throw aTypeError
exception. - Let
desc
be 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]]
!==undefined
andIsCallable(desc.[[Get]])
===false
, then throw aTypeError
exception. - If
O.[[HasProperty]]("set")
===true
, then: a. Setdesc.[[Set]]
toO.[[Get]]("set")
. b. Ifdesc.[[Set]]
!==undefined
andIsCallable(desc.[[Set]])
===false
, then throw aTypeError
exception. - If either
desc.[[Get]]
ordesc.[[Set]]
are present, then: a. If eitherdesc.[[Value]]
ordesc.[[Writable]]
are present, then throw aTypeError
exception. - 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:
- If
Type(O)
is notObject
throw aTypeError
exception. - Let
desc
be 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. Letgetter
beO.[[Get]]("get")
. b. Ifgetter
!==undefined
andIsCallable(getter)
===false
, then throw aTypeError
exception. c. Calldesc.[[Put]]
with the arguments"get"
,getter
andtrue
. - If
O.[[HasProperty]]("set")
===true
, then: a. Letsetter
beO.[[Get]]("set")
. b. Ifsetter
!==undefined
andIsCallable(setter)
===false
, then throw aTypeError
exception. c. Calldesc.[[Put]]
with the arguments"set"
,setter
andtrue
. - Validation: a. Let
g
bedesc.[[HasProperty]]("get")
. b. Lets
bedesc.[[HasProperty]]("set")
. c. Letv
bedesc.[[HasProperty]]("value")
. d. Letw
bedesc.[[HasProperty]]("writable")
. e. If(g || s) && (v || w)
then throw aTypeError
exception. - Return
desc
.
Notes:
- The third argument to
desc.[[Put]]
is theThrow
flag. The value is irrelevant as the[[Put]]
calls cannot fail.