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]]
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)
:- If
desc.[[Set]]
isundefined
, returnfalse
. - Else, return
true
.
- If
- Else,
desc
must be a data descriptor:- (CHANGED:) If
desc
was not found in the original objectO
, andO.[[Extensible]]
isfalse
, returnfalse
. - Return
desc.[[Writable]]
.
- (CHANGED:) If
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]]
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]]
.
- 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
.
This is better unwound into a loop (using desc
instead of prop
, as
it is more descriptive):
- Let
curr
beO
. - While
curr
is notnull
:- 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
.
- Let
- Return
undefined
.
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
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.
[[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
:- Let
D
be a newly created Property Descriptor with no fields. - Let
X
becurr
's own property named P. - If
X
is a data property, then- Set
D.[[Value]]
to the value ofX
's[[Value]]
attribute. - Set
D.[[Writable]]
to the value ofX
's[[Writable]]
attribute.
- Set
- Else
X
is an accessor property, so- Set
D.[[Get]]
to the value ofX
's[[Get]]
attribute. - Set
D.[[Set]]
to the value ofX
's[[Set]]
attribute.
- Set
- Set
D.[[Enumerable]]
to the value ofX
's[[Enumerable]]
attribute. - Set
D.[[Configurable]]
to the value ofX
's[[Configurable]]
attribute. - Return
D
.
- Let
- 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.
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
:- If
curr
is not aString
instance, goto NOTFOUND. - (
String
object exotic behavior.) Letstr
be the String value of the[[PrimitiveValue]]
internal property ofO
andlen
be the number of characters instr
. - If
P
is"length"
:- Return a Property Descriptor with the values:
[[Value]]: len
(a primitive number)[[Enumerable]]: false
[[Writable]]: false
[[Configurable]]: false
- Return a Property Descriptor with the values:
- If
P
is an array index (E5 Section 15.4):- Let
index
beToUint32(P)
. - If
index
<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
- Let
- Goto NOTFOUND.
- If
- Let
D
be a newly created Property Descriptor filled as follows:- If
X
is a data property:- Set
D.[[Value]]
to the value ofX
's[[Value]]
attribute. - Set
D.[[Writable]]
to the value ofX
's[[Writable]]
attribute.
- Set
- Else
X
is an accessor property:- Set
D.[[Get]]
to the value ofX
's[[Get]]
attribute. - Set
D.[[Set]]
to the value ofX
's[[Set]]
attribute.
- Set
- For either type of property:
- Set
D.[[Enumerable]]
to the value ofX
's[[Enumerable]]
attribute. - Set
D.[[Configurable]]
to the value ofX
's[[Configurable]]
attribute.
- Set
- If
- If
curr
is anarguments
object which contains a[[ParameterMap]]
internal property:- (Arguments object exotic behavior.) Let
map
be the value of the[[ParameterMap]]
internal property of the arguments object. - If the result of calling the
[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then:- Set
D.[[Value]]
to the result of calling the[[Get]]
internal method ofmap
passingP
as the argument.
- Set
- (Arguments object exotic behavior.) Let
- Return
D
. - NOTFOUND:
Let
curr
be the value of the[[Prototype]]
internal property ofcurr
. - If
curr
is notnull
, goto NEXT. - Return
undefined
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]]
is straightforward; it gets a property descriptor with
[[GetProperty]]
and then coerces it to a value.
[[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:- (Arguments object exotic behavior.) Let
map
be the value of the[[ParameterMap]]
internal property of the arguments object. - If the result of calling the
[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
:- Return the result of calling the
[[Get]]
internal method ofmap
passingP
as the argument.
- Return the result of calling the
- (Arguments object exotic behavior.) Let
curr
=O
- NEXT:
Let
X
becurr
's own property namedP
. Ifcurr
doesn't have an own property with nameP
:- If
curr
is not aString
instance, goto NOTFOUND. - (
String
object exotic behavior.) Letstr
be the String value of the[[PrimitiveValue]]
internal property ofO
andlen
be the number of characters instr
. - If
P
is"length"
:- Return
len
(a primitive number). (No need to check for arguments object exotic behavior or"caller"
property exotic behavior.)
- Return
- If
P
is an array index (E5 Section 15.4):- Let
index
beToUint32(P)
. - If
index
<len
:- Return a primitive string of length 1, containing one character
from
str
at positionindex
(zero based index). (No need to check for arguments object exotic behavior or"caller"
property exotic behavior.)
- Return a primitive string of length 1, containing one character
from
- Let
- Goto NOTFOUND.
- If
- If
X
is a data property:- Set
res
to the value ofX
's[[Value]]
attribute. - Goto FOUND1
- Set
- Else
X
is an accessor property:- Let
getter
beX
's[[Get]]
attribute. - If
getter
isundefined
:- 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 resultundefined
is not a strict mode function. Thus, no "goto FOUND1" here.)
- Return
- Else let
res
be the result of calling the[[Call]]
internal method ofgetter
providingO
as thethis
value and providing no arguments. - 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.)
- Let
- FOUND1:
If
curr
is anarguments
object which contains a[[ParameterMap]]
internal property:- (Arguments object exotic behavior.) Let
map
be the value of the[[ParameterMap]]
internal property of the arguments object. - If the result of calling the
[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then:- Set
res
to the result of calling the[[Get]]
internal method ofmap
passingP
as the argument.
- Set
- (Arguments object exotic behavior.) Let
- FOUND2:
If
O
is aFunction
object or anarguments
object which contains a[[ParameterMap]]
internal property:- (Arguments or Function object exotic behavior.)
If
P
is"caller"
andres
is a strict modeFunction
object, throw aTypeError
exception.
- (Arguments or Function object exotic behavior.)
If
- 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
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]]
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
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:- Let
newLen
beToUint32(Desc.[[Value]])
. - If
newLen
is not equal toToNumber(Desc.[[Value]])
, throw aRangeError
exception. Note that this is unconditional (thrown even ifThrow
isfalse
). - Let
oldLenDesc
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. - Let
oldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) - If
newLen
<oldLen
, then:- Let
shortenSucceeded
,finalLen
be the result of calling the internal helperShortenArray()
witholdLen
andnewLen
. - Update the property (
"length"
) value tofinalLen
. - Goto REJECT, if
shortenSucceeded
isfalse
. - Return.
- Let
- Update the property (
"length"
) value tonewLen
. - Return.
- Let
- 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:- Let
map
be the value of the[[ParameterMap]]
internal property of the arguments object. - If the result of calling the
[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then:- Call the
[[Put]]
internal method ofmap
passingP
,Desc.[[Value]]
, andThrow
as the arguments. (This updates the bound variable value.)
- Call the
- Let
- 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:- Let
newLen
beToUint32(val)
. - If
newLen
is not equal toToNumber(val)
, throw aRangeError
exception. Note that this is unconditional (thrown even ifThrow
isfalse
). - Let
oldLenDesc
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. - Let
oldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) - If
newLen
<oldLen
, then:- Let
shortenSucceeded
,finalLen
be the result of calling the internal helperShortenArray()
witholdLen
andnewLen
. - Update the property (
"length"
) value tofinalLen
. - Goto REJECT, if
shortenSucceeded
isfalse
. - Return.
- Let
- Update the property (
"length"
) value tonewLen
. - Return.
- Let
- 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:- Let
map
be the value of the[[ParameterMap]]
internal property of the arguments object. - If the result of calling the
[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then:- Call the
[[Put]]
internal method ofmap
passingP
,val
, andThrow
as the arguments. (This updates the bound variable value.)
- Call the
- Let
- Return
true
.
We'll need this variant later when creating an inlined version for the full property write processing.
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:- Let
oldLenDesc
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. - Let
oldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) - Let
index
beToUint32(P)
. - Goto REJECT if
index
>=oldLen
andoldLenDesc.[[Writable]]
isfalse
.
- Let
- 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
:- 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.
- Update the
- If
O
is an arguments object which has a[[ParameterMap]]
internal property:- Let
map
be the value of the[[ParameterMap]]
internal property of the arguments object. - If the result of calling the
[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then:- Call the
[[Put]]
internal method ofmap
passingP
,Desc.[[Value]]
, andThrow
as the arguments. (This updates the bound variable value.)
- Call the
- Let
- Return
true
. - REJECT:
- If
Throw
istrue
, then throw aTypeError
exception, otherwise returnfalse
.
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:- Let
oldLenDesc
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. - Let
oldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) - Let
index
beToUint32(P)
. - If
index
>=oldLen
:- Goto REJECT
oldLenDesc.[[Writable]]
isfalse
. - Update the
"length"
property ofO
to the valueindex + 1
. This always succeeds.
- Goto REJECT
- Let
- 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
istrue
, then throw aTypeError
exception, otherwise returnfalse
.
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:- Let
oldLenDesc
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. - Let
oldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) - Let
index
beToUint32(P)
. - If
index
>=oldLen
:- Goto REJECT
oldLenDesc.[[Writable]]
isfalse
. - Update the
"length"
property ofO
to the valueindex + 1
. This always succeeds.
- Goto REJECT
- Let
- 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.
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.
"Reject" below is shorthand for:
- If
Throw
istrue
, then throw aTypeError
exception; else return.
For object O
, property P
, and value V
:
- If the result of calling the
[[CanPut]]
internal method ofO
with argumentP
is false, then- If
Throw
istrue
, then throw aTypeError
exception. - Else return.
- If
- Let
ownDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
with argumentP
. - If
IsDataDescriptor(ownDesc)
istrue
, then- Let
valueDesc
be the Property Descriptor{[[Value]]: V}
. - Call the
[[DefineOwnProperty]]
internal method ofO
passingP
,valueDesc
, andThrow
as arguments. - Return.
- Let
- 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- Let
setter
bedesc.[[Set]]
which cannot beundefined
. - Call the
[[Call]]
internal method of setter providingO
as thethis
value and providingV
as the sole argument.
- Let
- Else, create a named data property named
P
on objectO
as follows- Let
newDesc
be the Property Descriptor:[[Value]]: V
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true}
- Call the
[[DefineOwnProperty]]
internal method ofO
passingP
,newDesc
, andThrow
as arguments.
- Let
- Return.
Notes:
- Step 5.a:
setter
cannot beundefined
at this point because[[CanPut]]
has checked it (and throws an exception if it isundefined
).
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:- 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]]
.)
- Call the
- Else if
desc
was found inO
directly (as an "own data property"), then:- Let
valueDesc
be the Property Descriptor{[[Value]]: V}
. - Call the
[[DefineOwnProperty]]
internal method ofO
passingP
,valueDesc
, andThrow
as arguments.
- Let
- Else
desc
is an inherited data property orundefined
, then:- Let
newDesc
be the Property Descriptor:[[Value]]: V
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true}
- Call the
[[DefineOwnProperty]]
internal method ofO
passingP
,newDesc
, andThrow
as arguments.
- Let
- 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:- If
desc.[[Set]]
isundefined
, Reject. - Call the
[[Call]]
internal method ofdesc.[[Set]]
providingO
as thethis
value and providingV
as the sole argument.
- If
Else if
desc
is an inherited (data) property, then:- If
O.[[Extensible]]
isfalse
, Reject. - If
desc.[[Writable]]
isfalse
, Reject. - Let
newDesc
be the Property Descriptor:[[Value]]: V
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true}
- Call the
[[DefineOwnProperty]]
internal method ofO
passingP
,newDesc
, andThrow
as arguments.
- If
Else if
desc
was not found (isundefined
):- If
O.[[Extensible]]
isfalse
, Reject. - Let
newDesc
be the Property Descriptor:[[Value]]: V
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true}
- Call the
[[DefineOwnProperty]]
internal method ofO
passingP
,newDesc
, andThrow
as arguments.
- If
Else
desc
was found inO
directly (as an "own data property"), then:- If
desc.[[Writable]]
isfalse
, Reject. - Let
valueDesc
be the Property Descriptor{[[Value]]: V}
.
- Call the
[[DefineOwnProperty]]
internal method ofO
passingP
,valueDesc
, andThrow
as arguments.
- If
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:- If
desc.[[Set]]
isundefined
, Reject. - Call the
[[Call]]
internal method ofdesc.[[Set]]
providingO
as thethis
value and providingV
as the sole argument.
- If
Else if
desc
is notundefined
andinherited
isfalse
(own data property), then:- If
desc.[[Writable]]
isfalse
, Reject. - Let
valueDesc
be the Property Descriptor{[[Value]]: V}
.
- Call the
[[DefineOwnProperty]]
internal method ofO
passingP
,valueDesc
, andThrow
as arguments.
- If
- Else
desc
is an inherited (data) property orundefined
:- If
O.[[Extensible]]
isfalse
, Reject. - If
desc
is notundefined
anddesc.[[Writable]]
isfalse
, Reject. (In other words:desc
was inherited and is non-writable.) - Let
newDesc
be the Property Descriptor:[[Value]]: V
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true}
- Call the
[[DefineOwnProperty]]
internal method ofO
passingP
,newDesc
, andThrow
as arguments.
- If
- Return.
This can be further improved in actual C code.
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
:If
O
does not have own propertyP
:- Set
curr
tocurr.[[Prototype]]
- Continue (while loop)
- Set
Let
desc
be the descriptor for own propertyP
If
IsDataDescriptor(desc)
:- If
curr
!=O
(property is an inherited data property): (Note: assumes there are no prototype loops.)- If
O.[[Extensible]
isfalse
, Reject. - If
desc.[[Writable]]
isfalse
, Reject. - Let
newDesc
be a property descriptor with values:[[Value]]: V
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true}
- Call
O.[[DefineOwnProperty]](P, newDesc, Throw)
.
- If
- Else (property is an own data property):
- If
desc.[[Writable]]
isfalse
, Reject. - Let
valueDesc
be{ [[Value]]: V }
. - Call
O.[[DefineOwnProperty]](P, valueDesc, Throw)
.
- If
- If
- Else (property is an accessor):
- If
desc.[[Set]]
isundefined
, Reject. - Call the
[[Call]]
internal method ofdesc.[[Set]]
providingO
as thethis
value and providingV
as the sole argument.
- If
- Return.
Property was not found in the prototype chain:
- If
O.[[Extensible]]
isfalse
, Reject. - Let
newDesc
be a property descriptor with values:[[Value]]: V
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true}
- Call
O.[[DefineOwnProperty]](P, newDesc, Throw)
.
- If
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
:- Let
curr
be the value of the[[Prototype]]
internal property ofcurr
. - If
curr
is notnull
, goto NEXT. - If
O.[[Extensible]]
isfalse
, Reject. - Let
newDesc
be a property descriptor with values:[[Value]]: V
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true}
- Call
O.[[DefineOwnProperty]](P, newDesc, Throw)
. - Return.
- Let
- If
IsDataDescriptor(desc)
:- If
curr
!=O
(property is an inherited data property): (Note: assumes there are no prototype loops.)- If
O.[[Extensible]
isfalse
, Reject. - If
desc.[[Writable]]
isfalse
, Reject. - Let
newDesc
be a property descriptor with values:[[Value]]: V
[[Writable]]: true
[[Enumerable]]: true
[[Configurable]]: true}
- Call
O.[[DefineOwnProperty]](P, newDesc, Throw)
.
- If
- Else (property is an own data property):
- If
desc.[[Writable]]
isfalse
, Reject. - Let
valueDesc
be{ [[Value]]: V }
. - Call
O.[[DefineOwnProperty]](P, valueDesc, Throw)
.
- If
- If
- Else (property is an accessor):
- If
desc.[[Set]]
isundefined
, Reject. - Call the
[[Call]]
internal method ofdesc.[[Set]]
providingO
as thethis
value and providingV
as the sole argument.
- If
- Return.
Note that PutValue()
has a [[Put]]
variant with two exotic
behaviors related to object coercion. The above algorithm does not
take those into account.
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).
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:- Let
enum
be the result of calling the[[Get]]
internal method ofObj
with"enumerable"
. - Set the
[[Enumerable]]
field ofdesc
toToBoolean(enum)
.
- Let
- If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"configurable"
istrue
, then:- Let
conf
be the result of calling the[[Get]]
internal method ofObj
with argument"configurable"
. - Set the
[[Configurable]]
field ofdesc
toToBoolean(conf)
.
- Let
- If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"value"
istrue
, then:- Let
value
be the result of calling the[[Get]]
internal method ofObj
with argument"value"
. - Set the
[[Value]]
field ofdesc
tovalue
.
- Let
- If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"writable"
istrue
, then:- Let
writable
be the result of calling the[[Get]]
internal method ofObj
with argument"writable"
. - Set the
[[Writable]]
field ofdesc
toToBoolean(writable)
.
- Let
- If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"get"
istrue
, then:- Let
getter
be the result of calling the[[Get]]
internal method ofObj
with argument"get"
. - If
IsCallable(getter)
isfalse
andgetter
is notundefined
, then throw aTypeError
exception. - Set the
[[Get]]
field ofdesc
togetter
.
- Let
- If the result of calling the
[[HasProperty]]
internal method ofObj
with argument"set"
istrue
, then:- Let
setter
be the result of calling the[[Get]]
internal method ofObj
with argument"set"
. - If
IsCallable(setter)
isfalse
andsetter
is notundefined
, then throw a TypeError exception. - Set the
[[Set]]
field ofdesc
tosetter
.
- Let
- If either
desc.[[Get]]
ordesc.[[Set]]
are present, then:- If either
desc.[[Value]]
ordesc.[[Writable]]
are present, then throw aTypeError
exception.
- If either
- 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:- Set
desc.[[Get]]
toO.[[Get]]("get")
. - If
desc.[[Get]]
!==undefined
andIsCallable(desc.[[Get]])
===false
, then throw aTypeError
exception.
- Set
- If
O.[[HasProperty]]("set")
===true
, then:- Set
desc.[[Set]]
toO.[[Get]]("set")
. - If
desc.[[Set]]
!==undefined
andIsCallable(desc.[[Set]])
===false
, then throw aTypeError
exception.
- Set
- If either
desc.[[Get]]
ordesc.[[Set]]
are present, then:- If either
desc.[[Value]]
ordesc.[[Writable]]
are present, then throw aTypeError
exception.
- If either
- Return
desc
.
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:- Let
getter
beO.[[Get]]("get")
. - If
getter
!==undefined
andIsCallable(getter)
===false
, then throw aTypeError
exception. - Call
desc.[[Put]]
with the arguments"get"
,getter
andtrue
.
- Let
- If
O.[[HasProperty]]("set")
===true
, then:- Let
setter
beO.[[Get]]("set")
. - If
setter
!==undefined
andIsCallable(setter)
===false
, then throw aTypeError
exception. - Call
desc.[[Put]]
with the arguments"set"
,setter
andtrue
.
- Let
- Validation:
- Let
g
bedesc.[[HasProperty]]("get")
. - Let
s
bedesc.[[HasProperty]]("set")
. - Let
v
bedesc.[[HasProperty]]("value")
. - Let
w
bedesc.[[HasProperty]]("writable")
. - If
(g || s) && (v || w)
then throw aTypeError
exception.
- Let
- Return
desc
.
Notes:
- The third argument to
desc.[[Put]]
is theThrow
flag. The value is irrelevant as the[[Put]]
calls cannot fail.