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.
 
 
 
 
 
 

598 lines
22 KiB

===============================
Exposed Object.defineProperty()
===============================
Original algorithm
==================
The algorithm is specified in E5 Section 15.2.3.6:
1. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception.
2. Let ``name`` be ``ToString(P)``.
(Note: this may have side effects.)
3. Let ``desc`` be the result of calling ``ToPropertyDescriptor`` with
``Attributes`` as the argument.
4. Call the ``[[DefineOwnProperty]]`` internal method of ``O`` with
arguments ``name``, ``desc``, and ``true``.
(Note: the last argument, ``true``, is the ``Throw`` flag.)
5. Return ``O``.
The algorithm returns the object, which allows chaining; for instance::
var o = {};
Object.defineProperty(o, 'foo',
{ value: 'bar' }
).seal();
``ToPropertyDescriptor()`` is a helper called only from
``Object.defineProperty()`` and ``Object.defineProperties()``. It
converts a property descriptor expressed as an ECMAScript object into
a "specification descriptor", doing boolean coercions and cross checking
the descriptor. For instance, ``ToPropertyDescriptor()`` will reject
any property descriptor which contains fields indicating it is both
a data property descriptor and an accessor property descriptor.
Example from Node / V8::
> var o = {};
> Object.defineProperty(o, 'foo',
... { value: 'bar', set: function() {} });
TypeError: Invalid property. A property cannot both
have accessors and be writable or have a value, #<Object>
The result of the property descriptor conversion is an "internal descriptor".
Note that unlike when dealing with existing object properties, this descriptor
may not be fully populated, i.e. may be missing fields. From an implementation
perspective this means that the descriptor needs to be represented differently.
The current implementation doesn't have an explicit representation for the
"internal descriptor" which exists for the duration of
``Object.defineProperty()``; the descriptor is represented by a bunch of local
variables indicating the presence and coerced values of the descriptor fields
(for instance: ``has_writable`` and ``is_writable`` are separate variables).
The ``ToPropertyDescriptor()`` algorithm is reformulated in the restatements
section.
Other notes:
* The key is coerced to a string leniently while the object is just checked
and never coerced.
* ``[[DefineOwnProperty]]`` is always called with ``Throw`` set to ``true``,
so the implementation doesn't need to expose a "throw flag".
First draft
===========
Starting from the original algorithm and inlining both
``ToPropertyDescriptor()`` and the ``[[DefineOwnProperty]]`` algorithm
with exotic behaviors, we get:
1. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception.
2. Let ``name`` be ``ToString(P)``.
(Note: this may have side effects.)
3. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception.
4. Let ``desc`` be a new, empty Property Descriptor.
5. If ``O.[[HasProperty]]("enumerable")`` === ``true``, then
set ``desc.[[Enumerable]]`` to ``ToBoolean(O.[[Get]]("enumerable"))``.
6. If ``O.[[HasProperty]]("configurable")`` === ``true``, then
set ``desc.[[Configurable]]`` to ``ToBoolean(O.[[Get]]("configurable"))``.
7. If ``O.[[HasProperty]]("value")`` === ``true``, then
set ``desc.[[Value]]`` to ``O.[[Get]]("value")``.
8. If ``O.[[HasProperty]]("writable")`` === ``true``, then
set ``desc.[[Writable]]`` to ``ToBoolean(O.[[Get]]("writable"))``.
9. 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.
10. 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.
11. 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.
12. Let ``Throw`` be ``true``.
13. Set ``pendingWriteProtect`` to ``false``.
14. If ``O`` is not an ``Array`` object, goto SKIPARRAY.
15. 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.
16. Let ``oldLen`` be ``oldLenDesc.[[Value]]``.
(Note that ``oldLen`` is guaranteed to be a unsigned 32-bit integer.)
17. If ``P`` is ``"length"``, then
a. If the ``[[Value]]`` field of ``Desc`` is absent, then goto SKIPARRAY.
b. Let ``newLen`` be ``ToUint32(Desc.[[Value]])``.
c. If ``newLen`` is not equal to ``ToNumber(Desc.[[Value]])``, goto
REJECTRANGE.
d. Set ``Desc.[[Value]]`` to ``newLen``.
e. If ``newLen`` >= ``oldLen``, then goto SKIPARRAY.
f. Goto REJECT if ``oldLenDesc.[[Writable]]`` is ``false``.
g. If ``Desc.[[Writable]]`` has the value ``false``:
1. Need to defer setting the ``[[Writable]]`` attribute to ``false``
in case any elements cannot be deleted.
2. Set ``pendingWriteProtect`` to ``true``.
3. Set ``Desc.[[Writable]]`` to ``true``.
h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.)
18. Else if ``P`` is an array index (E5 Section 15.4), then:
a. Let ``index`` be ``ToUint32(P)``.
b. Goto REJECT if ``index`` >= ``oldLen`` and ``oldLenDesc.[[Writable]]``
is ``false``.
c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.)
19. **SKIPARRAY**:
Let ``current`` be the result of calling the ``[[GetOwnProperty]]``
internal method of ``O`` with property name ``P``.
20. Let ``extensible`` be the value of the ``[[Extensible]]`` internal
property of ``O``.
21. If ``current`` is ``undefined``:
a. If ``extensible`` is ``false``, then goto REJECT.
b. If ``IsGenericDescriptor(Desc)`` or ``IsDataDescriptor(Desc)`` is
``true``, then
1. Create an own data property named ``P`` of object ``O`` whose
``[[Value]]``, ``[[Writable]]``, ``[[Enumerable]]`` and
``[[Configurable]]`` attribute values are described by ``Desc``.
If the value of an attribute field of ``Desc`` is absent, the
attribute of the newly created property is set to its default
value.
c. Else, ``Desc`` must be an accessor Property Descriptor so,
1. Create an own accessor property named ``P`` of object ``O`` whose
``[[Get]]``, ``[[Set]]``, ``[[Enumerable]]`` and ``[[Configurable]]``
attribute values are described by ``Desc``. If the value of an
attribute field of ``Desc`` is absent, the attribute of the newly
created property is set to its default value.
d. Goto SUCCESS.
22. Goto SUCCESS, if every field in ``Desc`` also occurs in ``current``
and the value of every field in ``Desc`` is the same value as the
corresponding field in ``current`` when compared using the ``SameValue``
algorithm (E5 Section 9.12). (This also covers the case where
every field in ``Desc`` is absent.)
23. If the ``[[Configurable]]`` field of ``current`` is ``false`` then
a. Goto REJECT, if the ``[[Configurable]]`` field of ``Desc`` is true.
b. Goto REJECT, if the ``[[Enumerable]]`` field of ``Desc`` is present
and the ``[[Enumerable]]`` fields of ``current`` and ``Desc`` are the
Boolean negation of each other.
24. If ``IsGenericDescriptor(Desc)`` is ``true``, then goto VALIDATED.
25. Else, if ``IsDataDescriptor(current)`` and ``IsDataDescriptor(Desc)``
have different results, then
a. Goto REJECT, if the ``[[Configurable]]`` field of ``current`` is
``false``.
b. If ``IsDataDescriptor(current)`` is true, then
1. Convert the property named ``P`` of object ``O`` from a data property
to an accessor property. Preserve the existing values of the
converted property's ``[[Configurable]]`` and ``[[Enumerable]]``
attributes and set the rest of the property's attributes to their
default values.
c. Else,
1. Convert the property named ``P`` of object ``O`` from an accessor
property to a data property. Preserve the existing values of the
converted property's ``[[Configurable]]`` and ``[[Enumerable]]``
attributes and set the rest of the property's attributes to their
default values.
d. Goto VALIDATED.
26. Else, if ``IsDataDescriptor(current)`` and ``IsDataDescriptor(Desc)``
are both true, then
a. If the ``[[Configurable]]`` field of ``current`` is ``false``, then
1. Goto REJECT, if the ``[[Writable]]`` field of ``current`` is
``false`` and the ``[[Writable]]`` field of ``Desc`` is ``true``.
2. Goto REJECT, If the ``[[Writable]]`` field of ``current`` is
``false``, and the ``[[Value]]`` field of ``Desc`` is present, and
``SameValue(Desc.[[Value]], current.[[Value]])`` is ``false``.
b. Goto VALIDATED.
27. Else, ``IsAccessorDescriptor(current)`` and ``IsAccessorDescriptor(Desc)``
are both ``true`` so,
a. If the ``[[Configurable]]`` field of ``current`` is ``false``, then
1. Goto REJECT, if the ``[[Set]]`` field of ``Desc`` is present and
``SameValue(Desc.[[Set]], current.[[Set]])`` is ``false``.
2. Goto REJECT, if the ``[[Get]]`` field of ``Desc`` is present and
``SameValue(Desc.[[Get]], current.[[Get]])`` is ``false``.
b. Goto VALIDATED.
28. **VALIDATED:**
For each attribute field of ``Desc`` that is present, set the
correspondingly named attribute of the property named ``P`` of object
``O`` to the value of the field.
29. **SUCCESS:**
If ``O`` is an ``Array`` object:
a. If ``P`` is ``"length"``, and ``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. If ``pendingWriteProtect`` is ``true``, update the property
(``"length"``) to have ``[[Writable]] = false``.
4. Goto REJECT, if ``shortenSucceeded`` is ``false``.
b. If ``P`` is an array index and ``index`` >= ``oldLen``:
1. 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.
30. 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. If ``IsAccessorDescriptor(Desc)`` is ``true``, then:
a. Call the ``[[Delete]]`` internal method of ``map`` passing ``P``,
and ``false`` as the arguments. (This removes the magic binding
for ``P``.)
2. Else (``Desc`` may be generic or data descriptor):
a. If ``Desc.[[Value]]`` is present, then:
1. Call the ``[[Put]]`` internal method of ``map`` passing ``P``,
``Desc.[[Value]]``, and ``Throw`` as the arguments. (This
updates the bound variable value.)
b. If ``Desc.[[Writable]]`` is present and its value is ``false``,
then:
1. Call the ``[[Delete]]`` internal method of ``map`` passing ``P``
and ``false`` as arguments. (This removes the magic binding
for ``P``, and must happen after a possible update of the
variable value.)
31. Return ``O``.
32. **REJECT**:
If ``Throw`` is ``true``, then throw a ``TypeError`` exception,
otherwise return ``false``.
33. **REJECTRANGE**:
Throw a ``RangeError`` exception. Note that this is unconditional
(thrown even if ``Throw`` is ``false``).
Notes:
* Step 3 is redundant (it comes from ``ToPropertyDescriptor()`` because
of step 1.
* Since ``Throw`` is always ``true``, step 12 can be removed and
step 32 changed to throw ``TypeError`` unconditionally. Note that
``Throw`` is also given as a parameter in step 30.b.2.1 as an
argument for an internal ``[[Put]]`` to the parameter map. This
actually has no effect on behavior (the internal setter will be
called, and the ``Throw`` flag is not visible to the setter).
Some cleanup
============
1. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception.
2. Let ``name`` be ``ToString(P)``.
(Note: this may have side effects.)
3. Let ``desc`` be a new, empty Property Descriptor.
4. If ``O.[[HasProperty]]("enumerable")`` === ``true``, then
set ``desc.[[Enumerable]]`` to ``ToBoolean(O.[[Get]]("enumerable"))``.
5. If ``O.[[HasProperty]]("configurable")`` === ``true``, then
set ``desc.[[Configurable]]`` to ``ToBoolean(O.[[Get]]("configurable"))``.
6. If ``O.[[HasProperty]]("value")`` === ``true``, then
set ``desc.[[Value]]`` to ``O.[[Get]]("value")``.
7. If ``O.[[HasProperty]]("writable")`` === ``true``, then
set ``desc.[[Writable]]`` to ``ToBoolean(O.[[Get]]("writable"))``.
8. 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.
9. 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.
10. 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.
11. Set ``pendingWriteProtect`` to ``false``.
12. If ``O`` is not an ``Array`` object, goto SKIPARRAY.
13. 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.
14. Let ``oldLen`` be ``oldLenDesc.[[Value]]``.
(Note that ``oldLen`` is guaranteed to be a unsigned 32-bit integer.)
15. If ``P`` is ``"length"``, then
a. If the ``[[Value]]`` field of ``Desc`` is absent, then goto SKIPARRAY.
b. Let ``newLen`` be ``ToUint32(Desc.[[Value]])``.
c. If ``newLen`` is not equal to ``ToNumber(Desc.[[Value]])``, goto
REJECTRANGE.
d. Set ``Desc.[[Value]]`` to ``newLen``.
e. If ``newLen`` >= ``oldLen``, then goto SKIPARRAY.
f. Goto REJECT if ``oldLenDesc.[[Writable]]`` is ``false``.
g. If ``Desc.[[Writable]]`` has the value ``false``:
1. Need to defer setting the ``[[Writable]]`` attribute to ``false``
in case any elements cannot be deleted.
2. Set ``pendingWriteProtect`` to ``true``.
3. Set ``Desc.[[Writable]]`` to ``true``.
h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.)
16. Else if ``P`` is an array index (E5 Section 15.4), then:
a. Let ``index`` be ``ToUint32(P)``.
b. Goto REJECT if ``index`` >= ``oldLen`` and ``oldLenDesc.[[Writable]]``
is ``false``.
c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.)
17. **SKIPARRAY**:
Let ``current`` be the result of calling the ``[[GetOwnProperty]]``
internal method of ``O`` with property name ``P``.
18. Let ``extensible`` be the value of the ``[[Extensible]]`` internal
property of ``O``.
19. If ``current`` is ``undefined``:
a. If ``extensible`` is ``false``, then goto REJECT.
b. If ``IsGenericDescriptor(Desc)`` or ``IsDataDescriptor(Desc)`` is
``true``, then
1. Create an own data property named ``P`` of object ``O`` whose
``[[Value]]``, ``[[Writable]]``, ``[[Enumerable]]`` and
``[[Configurable]]`` attribute values are described by ``Desc``.
If the value of an attribute field of ``Desc`` is absent, the
attribute of the newly created property is set to its default
value.
c. Else, ``Desc`` must be an accessor Property Descriptor so,
1. Create an own accessor property named ``P`` of object ``O`` whose
``[[Get]]``, ``[[Set]]``, ``[[Enumerable]]`` and ``[[Configurable]]``
attribute values are described by ``Desc``. If the value of an
attribute field of ``Desc`` is absent, the attribute of the newly
created property is set to its default value.
d. Goto SUCCESS.
20. Goto SUCCESS, if every field in ``Desc`` also occurs in ``current``
and the value of every field in ``Desc`` is the same value as the
corresponding field in ``current`` when compared using the ``SameValue``
algorithm (E5 Section 9.12). (This also covers the case where
every field in ``Desc`` is absent.)
21. If the ``[[Configurable]]`` field of ``current`` is ``false`` then
a. Goto REJECT, if the ``[[Configurable]]`` field of ``Desc`` is true.
b. Goto REJECT, if the ``[[Enumerable]]`` field of ``Desc`` is present
and the ``[[Enumerable]]`` fields of ``current`` and ``Desc`` are the
Boolean negation of each other.
22. If ``IsGenericDescriptor(Desc)`` is ``true``, then goto VALIDATED.
23. Else, if ``IsDataDescriptor(current)`` and ``IsDataDescriptor(Desc)``
have different results, then
a. Goto REJECT, if the ``[[Configurable]]`` field of ``current`` is
``false``.
b. If ``IsDataDescriptor(current)`` is true, then
1. Convert the property named ``P`` of object ``O`` from a data property
to an accessor property. Preserve the existing values of the
converted property's ``[[Configurable]]`` and ``[[Enumerable]]``
attributes and set the rest of the property's attributes to their
default values.
c. Else,
1. Convert the property named ``P`` of object ``O`` from an accessor
property to a data property. Preserve the existing values of the
converted property's ``[[Configurable]]`` and ``[[Enumerable]]``
attributes and set the rest of the property's attributes to their
default values.
d. Goto VALIDATED.
24. Else, if ``IsDataDescriptor(current)`` and ``IsDataDescriptor(Desc)``
are both true, then
a. If the ``[[Configurable]]`` field of ``current`` is ``false``, then
1. Goto REJECT, if the ``[[Writable]]`` field of ``current`` is
``false`` and the ``[[Writable]]`` field of ``Desc`` is ``true``.
2. Goto REJECT, If the ``[[Writable]]`` field of ``current`` is
``false``, and the ``[[Value]]`` field of ``Desc`` is present, and
``SameValue(Desc.[[Value]], current.[[Value]])`` is ``false``.
b. Goto VALIDATED.
25. Else, ``IsAccessorDescriptor(current)`` and ``IsAccessorDescriptor(Desc)``
are both ``true`` so,
a. If the ``[[Configurable]]`` field of ``current`` is ``false``, then
1. Goto REJECT, if the ``[[Set]]`` field of ``Desc`` is present and
``SameValue(Desc.[[Set]], current.[[Set]])`` is ``false``.
2. Goto REJECT, if the ``[[Get]]`` field of ``Desc`` is present and
``SameValue(Desc.[[Get]], current.[[Get]])`` is ``false``.
b. Goto VALIDATED.
26. **VALIDATED:**
For each attribute field of ``Desc`` that is present, set the
correspondingly named attribute of the property named ``P`` of object
``O`` to the value of the field.
27. **SUCCESS:**
If ``O`` is an ``Array`` object:
a. If ``P`` is ``"length"``, and ``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. If ``pendingWriteProtect`` is ``true``, update the property
(``"length"``) to have ``[[Writable]] = false``.
4. Goto REJECT, if ``shortenSucceeded`` is ``false``.
b. If ``P`` is an array index and ``index`` >= ``oldLen``:
1. 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.
28. 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. If ``IsAccessorDescriptor(Desc)`` is ``true``, then:
a. Call the ``[[Delete]]`` internal method of ``map`` passing ``P``,
and ``false`` as the arguments. (This removes the magic binding
for ``P``.)
2. Else (``Desc`` may be generic or data descriptor):
a. If ``Desc.[[Value]]`` is present, then:
1. Call the ``[[Put]]`` internal method of ``map`` passing ``P``,
``Desc.[[Value]]``, and ``true`` as the arguments.
(This updates the bound variable value. Note that the ``Throw``
flag is irrelevant, ``true`` used now.)
b. If ``Desc.[[Writable]]`` is present and its value is ``false``,
then:
1. Call the ``[[Delete]]`` internal method of ``map`` passing ``P``
and ``false`` as arguments. (This removes the magic binding
for ``P``, and must happen after a possible update of the
variable value.)
29. Return ``O``.
30. **REJECT**:
Throw a ``TypeError`` exception.
31. **REJECTRANGE**:
Throw a ``RangeError`` exception.