mirror of https://github.com/svaarala/duktape.git
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
1540 lines
50 KiB
1540 lines
50 KiB
==========================
|
|
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:: 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:: 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:: 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``.
|
|
|
|
6. **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``.
|
|
|
|
4. **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}``.
|
|
|
|
b. 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}``.
|
|
|
|
b. Call the ``[[DefineOwnProperty]]`` internal method of ``O`` passing
|
|
``P``, ``valueDesc``, and ``Throw`` as arguments.
|
|
|
|
3. 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.
|
|
|
|
4. 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]]``
|
|
|
|
1. 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)``.
|
|
|
|
e. 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.
|
|
|
|
f. 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.
|
|
|