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.

903 lines
29 KiB

=======================================
PUTPROP: exposed property put algorithm
=======================================
Background
==========
Properties are written in Ecmascript code in many contexts, e.g.::
foo.bar = "quux";
A property put expression in Ecmascript code involves:
* A property accessor reference (E5 Section 11.2.1)
* A ``PutValue()`` call (E5 Section 8.7.2)
* A ``[[Put]]`` call (or a ``PutValue()`` specific variant)
The property accessor coercions are the same as for ``GetValue``:
* The base reference is checked with ``CheckObjectCoercible()``
* The property name is coerced to a string
The ``PutValue()`` call is simple:
* If the base reference is primitive, it is coerced to an object, and a
exotic variant of ``[[Put]]`` is used.
* Otherwise, standard ``[[Put]]`` is used.
The variant ``[[Put]]`` for a primitive base value differs from the
standard ``[[Put]]`` as follows:
* If the coerced temporary object has a matching own data property,
the put is explicitly rejected (steps 3-4 of the variant algorithm),
regardless of the property attributes (especially, writability).
Compare this to the standard ``[[Put]]`` behavior in E5 Section
8.12.5, steps 2-3 which simply attempts to update the data property,
provided that the property is writable.
* If the property is found (either in the temporary object or its
ancestors) and is a setter, the setter call ``this`` binding is
the primitive value, not the coerced value. (An own accessor
property should never be found in practice, as the only possible
coerced object types as ``Boolean``, ``Number``, and ``String``.)
Like ``GetValue()``, we could skip creation of the coerced object, but
don't take advantage of this now.
Note: if the base reference is a primitive value, the coerced object is
temporary and never exposed to user code. Some implementations (like V8)
omit a property write entirely if the base value is primitive. This can
be observed by lack of side effects, e.g. no setter call occurs when it
should::
// add test getter
Object.defineProperty(String.prototype, 'test', {
get: function() { print(typeof this); },
set: function(x) { print(typeof this); },
});
"foo".test = "bar"; // prints 'string'
V8 will print nothing, while Rhino and Smjs print 'object' (which is also
not correct).
First draft
===========
The relevant part begins after that in steps 5-8, which first perform
some coercions and then create a property accessor. The accessor is
then acted upon by ``PutValue()``, and ultimately ``[[Put]]`` or its
variant.
Combining all of these, we get the first draft (for base value ``O``
and property name value ``P``):
1. Let ``orig`` be ``O``.
(Remember the uncoerced original for a possible setter call.)
2. Call ``CheckObjectCoercible`` with ``O`` as argument. In practice: if
``O`` is ``null`` or ``undefined``, throw a ``TypeError``.
(Note: this is unconditional.)
3. Let ``P`` be ``ToString(P)``.
(This may have side effects if ``P`` is an object.)
4. If ``O`` is not an object, let ``coerced`` be ``true``, else let
``coerced`` be ``false``.
5. Let ``O`` be ``ToObject(O)``.
(This is side effect free.)
6. Let ``curr`` be ``O``.
7. **NEXT:**
Let ``desc`` be the result of calling the ``[[GetOwnProperty]]``
internal method of ``curr`` with property name ``P``.
8. 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 ``coerced`` is ``true``, Reject.
d. If ``O.[[Extensible]]`` is ``false``, Reject.
e. Let ``newDesc`` be a property descriptor with values:
* ``[[Value]]: V``
* ``[[Writable]]: true``
* ``[[Enumerable]]: true``
* ``[[Configurable]]: true}``
f. Call ``O.[[DefineOwnProperty]](P, newDesc, Throw)``.
g. Return.
9. If ``IsDataDescriptor(desc)``:
a. If ``coerced`` is ``true``, Reject.
b. 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)``.
c. 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)``.
10. Else (property is an accessor):
a. If ``desc.[[Set]]`` is ``undefined``, Reject.
b. Call the ``[[Call]]`` internal method of ``desc.[[Set]]`` providing
``orig`` as the ``this`` value and providing ``V`` as the sole argument.
(Note: the difference to a basic ``[[Put]]`` is that the setter ``this``
binding is the original, uncoerced object.)
11. Return.
Notes:
* Steps 2-3 come from the property accessor evaluation rules in E5 Section
11.2.1. In particular, ``CheckObjectCoercible()`` is called before the
key is coerced to a string. Since the key string coercion may have side
effects, the order of evaluation matters.
Note that ``ToObject()`` has no side effects (this can be seen from a
case by case inspection), so steps 3 and 4-5 can be reversed.
* Step 10.b uses the original object (not the coerced object) as the setter
``this`` binding (E5 Section 8.7.2, step 6 of the variant ``[[Put]]``
algorithm).
* Steps 8.c and 9.a reject attempt to update or create a data property on
a temporary object (E5 Section 8.7.2, steps 4 and 7 of the variant
``[[Put]]`` algorithm). Note that the "coerced" check is not actually
needed to guard step 9.c (step 4 of the variant ``[[Put]]``) because the
only coerced object with own properties is the ``String`` object, and all
its own properties are non-writable and thus caught by step 9.c.1 anyway.
This might of course change in a future version, or be untrue for some
out-of-spec coercion behavior for custom types. The pre-check *is*
needed to avoid creating a new property on the temporary object, though.
* An explicit ``coerced`` flag is not needed: we can simply check whether
or not ``orig`` is an object.
* Since ``curr`` is used for prototype chain walking, we don't need to
store ``orig`` (``O`` can be used for that instead).
Cleaning up
===========
1. Call ``CheckObjectCoercible`` with ``O`` as argument. In practice: if
``O`` is ``null`` or ``undefined``, throw a ``TypeError``.
(Note: this is unconditional.)
2. Let ``curr`` be ``ToObject(O)``.
(This is side effect free.)
3. Let ``P`` be ``ToString(P)``.
(This may have side effects if ``P`` is an object.)
4. **NEXT:**
Let ``desc`` be the result of calling the ``[[GetOwnProperty]]``
internal method of ``curr`` with property name ``P``.
5. 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`` is not an object (was coerced), Reject.
d. If ``O.[[Extensible]]`` is ``false``, Reject.
e. Let ``newDesc`` be a property descriptor with values:
* ``[[Value]]: V``
* ``[[Writable]]: true``
* ``[[Enumerable]]: true``
* ``[[Configurable]]: true}``
f. Call ``O.[[DefineOwnProperty]](P, newDesc, Throw)``.
g. Return.
6. If ``IsDataDescriptor(desc)``:
a. If ``O`` is not an object (was coerced), Reject.
b. 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)``.
c. 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)``.
7. 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.
(Note: the difference to a basic ``[[Put]]`` is that the setter ``this``
binding is the original, uncoerced object.)
8. Return.
Inlining DefineOwnProperty calls
================================
The ``[[Put]]`` uses two different calls to ``[[DefineOwnProperty]]``: one to
update an existing property ``[[Value]]`` and another to create a brand new
data property. These can be inlined into the algorithm as follows (see
the section on preliminary algorithm work).
Before inlining, the cases for "update old property" and "create new property"
are isolated into goto labels (as there are two places where a new property
is created). The ``[[DefineOwnProperty]]`` calls with exotic behaviors
inlined are then substituted. "Reject" is also made an explicit label.
The resulting algorithm is:
1. Call ``CheckObjectCoercible`` with ``O`` as argument. In practice: if
``O`` is ``null`` or ``undefined``, throw a ``TypeError``.
(Note: this is unconditional.)
2. Let ``curr`` be ``ToObject(O)``.
(This is side effect free.)
3. Let ``P`` be ``ToString(P)``.
(This may have side effects if ``P`` is an object.)
4. **NEXT:**
Let ``desc`` be the result of calling the ``[[GetOwnProperty]]``
internal method of ``curr`` with property name ``P``.
5. 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`` is not an object (was coerced), goto REJECT.
d. If ``O.[[Extensible]]`` is ``false``, goto REJECT.
e. Goto NEWPROP.
6. If ``IsDataDescriptor(desc)``:
a. If ``O`` is not an object (was coerced), goto REJECT.
b. If ``curr`` != ``O`` (property is an inherited data property):
(Note: assumes there are no prototype loops.)
1. If ``O.[[Extensible]`` is ``false``, goto REJECT.
2. If ``desc.[[Writable]]`` is ``false``, goto REJECT.
3. Goto NEWPROP.
c. Else (property is an own data property):
1. If ``desc.[[Writable]]`` is ``false``, goto REJECT.
2. Goto UPDATEPROP.
7. Else (property is an accessor):
a. If ``desc.[[Set]]`` is ``undefined``, goto REJECT.
b. Call the ``[[Call]]`` internal method of ``desc.[[Set]]`` providing
``O`` as the ``this`` value and providing ``V`` as the sole argument.
(Note: the difference to a basic ``[[Put]]`` is that the setter ``this``
binding is the original, uncoerced object.)
c. Return.
8. **UPDATEPROP:**
(Inlined ``[[DefineOwnProperty]]`` call for existing property.)
If ``O`` is an ``Array`` object, and ``P`` is ``"length"``, then:
a. Let ``newLen`` be ``ToUint32(V)``.
b. If ``newLen`` is not equal to ``ToNumber(V)``, goto REJECTRANGE.
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.
9. Set the ``[[Value]]`` attribute of the property named ``P`` of object
``O`` to ``V``. (Since it is side effect free to update the value
with the same value, no check for that case is needed.)
10. 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``,
``V``, and ``Throw`` as the arguments. (This updates the bound
variable value.)
11. Return.
12. **NEWPROP:**
(Inlined ``[[DefineOwnProperty]]`` call for new property.)
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.
13. Create an own data property named ``P`` of object ``O`` whose attributes
are:
* ``[[Value]]: V``
* ``[[Writable]]: true``
* ``[[Enumerable]]: true``
* ``[[Configurable]]: true``
14. Return.
15. **REJECT**:
If ``Throw`` is ``true``, then throw a ``TypeError`` exception,
otherwise return.
16. **REJECTRANGE**:
Throw a ``RangeError`` exception. (This is unconditional.)
Notes:
* In step 8, we don't need to check for array index updates: the property
already exists, so array ``length`` will not need an update.
* In step 8, the original ``[[DefineOwnProperty]]`` exotic behavior is
split into a pre-step and a post-step because the ``"length"`` write
may fail. However, because we've inlined ``[[CanPut]]``, we know that
the write will succeed, so both the pre- and post-behaviors can be
handled in step 8 internally.
* In step 8, we don't need to check for arguments exotic behavior, as
only number-like indices have magic bindings (not ``"length"``).
* In steps 12-14, we don't need to check for arguments exotic behavior: any
"magically bound" property must always be present in the arguments
object. If a bound property is deleted, the binding is also deleted
from the argument parameter map.
* In step 12, we don't need to check for ``length`` exotic behavior: the
``length`` property always exists for arrays so we cannot get here with
arrays.
Avoiding temporary objects
==========================
As for ``GetValue()`` the only cases where temporary objects are created are
for ``Boolean``, ``Number``, and ``String``. The ``PutValue()`` algorithm
rejects a property write on a temporary object if a new data property were to
be created or an existing one updated.
For the possible coerced values, the own properties are:
* ``Boolean``: none
* ``Number``: none
* ``String``: ``"length"`` and index properties for string characters
These can be checked explicitly when coercing (and reject the attempt before
going forwards). However, ``PutValue()`` *does* allow a property write if an
ancestor contains a setter which "captures" the write so that the temporary
object would not be written to. Although the built-in prototype chains do not
contain such setters, they can be added by user code at run time, so they do
need to be checked for.
Avoiding temporaries altogether:
1. Check and/or coerce ``O`` as follows:
a. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``.
(This is the ``CheckObjectCoercible`` part; the throw is
unconditional.)
b. If ``O`` is a boolean: set ``curr`` to the built-in ``Boolean``
prototype object (skip creation of temporary)
c. Else if ``O`` is a number: set ``curr`` to the built-in ``Number``
prototype object (skip creation of temporary)
d. Else if ``O`` is a string:
1. Set ``P`` to ``ToString(P)``.
(This may have side effects if ``P`` is an object.)
2. If ``P`` is ``length``, goto REJECT.
3. If ``P`` is a valid array index within the string length,
goto REJECT.
4. Set ``curr`` to the built-in ``String`` prototype object
(skip creation of temporary)
5. Goto NEXT. (Avoid double coercion of ``P``.)
e. Else if ``O`` is an object: set ``curr`` to ``O``.
f. Else, Throw a ``TypeError``.
(Note that this case should not happen, as steps a-e are exhaustive.
However, this step is useful as a fallback, and for handling any
internal types.)
2. Let ``P`` be ``ToString(P)``.
(This may have side effects if ``P`` is an object.)
3. **NEXT:**
Let ``desc`` be the result of calling the ``[[GetOwnProperty]]``
internal method of ``curr`` with property name ``P``.
4. 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`` is not an object (was coerced), goto REJECT.
d. If ``O.[[Extensible]]`` is ``false``, goto REJECT.
e. Goto NEWPROP.
5. If ``IsDataDescriptor(desc)``:
a. If ``O`` is not an object (was coerced), goto REJECT.
b. If ``curr`` != ``O`` (property is an inherited data property):
(Note: assumes there are no prototype loops.)
1. If ``O.[[Extensible]`` is ``false``, goto REJECT.
2. If ``desc.[[Writable]]`` is ``false``, goto REJECT.
3. Goto NEWPROP.
c. Else (property is an own data property):
1. If ``desc.[[Writable]]`` is ``false``, goto REJECT.
2. Goto UPDATEPROP.
6. Else (property is an accessor):
a. If ``desc.[[Set]]`` is ``undefined``, goto REJECT.
b. Call the ``[[Call]]`` internal method of ``desc.[[Set]]`` providing
``O`` as the ``this`` value and providing ``V`` as the sole argument.
(Note: the difference to a basic ``[[Put]]`` is that the setter ``this``
binding is the original, uncoerced object.)
c. Return.
7. **UPDATEPROP:**
(Inlined ``[[DefineOwnProperty]]`` call for existing property.)
If ``O`` is an ``Array`` object, and ``P`` is ``"length"``, then:
a. Let ``newLen`` be ``ToUint32(V)``.
b. If ``newLen`` is not equal to ``ToNumber(V)``, goto REJECTRANGE.
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.
8. Set the ``[[Value]]`` attribute of the property named ``P`` of object
``O`` to ``V``. (Since it is side effect free to update the value
with the same value, no check for that case is needed.)
9. 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``,
``V``, and ``Throw`` as the arguments. (This updates the bound
variable value.)
10. Return.
11. **NEWPROP:**
(Inlined ``[[DefineOwnProperty]]`` call for new property.)
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.
12. Create an own data property named ``P`` of object ``O`` whose attributes
are:
* ``[[Value]]: V``
* ``[[Writable]]: true``
* ``[[Enumerable]]: true``
* ``[[Configurable]]: true``
13. Return.
14. **REJECT**:
If ``Throw`` is ``true``, then throw a ``TypeError`` exception,
otherwise return.
Notes:
* Step 7: if array exotic behavior exists, we can return right after
processing the ``length`` update; in particular, step 9 is not
necessary as an object cannot be simultaneously an array and an
arguments object.
* Step 11.d.2 (updating ``length``) is a bit dangerous because it happens
before step 12. Step 12 may fail due to an out-of-memory or other
internal condition, which leaves the ``length`` updated but the element
missing.
Minor improvements
==================
Addressing the array ``length`` issue:
1. Check and/or coerce ``O`` as follows:
a. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``.
(This is the ``CheckObjectCoercible`` part; the throw is
unconditional.)
b. If ``O`` is a boolean: set ``curr`` to the built-in ``Boolean``
prototype object (skip creation of temporary)
c. Else if ``O`` is a number: set ``curr`` to the built-in ``Number``
prototype object (skip creation of temporary)
d. Else if ``O`` is a string:
1. Set ``P`` to ``ToString(P)``.
(This may have side effects if ``P`` is an object.)
2. If ``P`` is ``length``, goto REJECT.
3. If ``P`` is a valid array index within the string length,
goto REJECT.
4. Set ``curr`` to the built-in ``String`` prototype object
(skip creation of temporary)
5. Goto NEXT. (Avoid double coercion of ``P``.)
e. Else if ``O`` is an object: set ``curr`` to ``O``.
f. Else, Throw a ``TypeError``.
(Note that this case should not happen, as steps a-e are exhaustive.
However, this step is useful as a fallback, and for handling any
internal types.)
2. Let ``P`` be ``ToString(P)``.
(This may have side effects if ``P`` is an object.)
3. **NEXT:**
Let ``desc`` be the result of calling the ``[[GetOwnProperty]]``
internal method of ``curr`` with property name ``P``.
4. 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`` is not an object (was coerced), goto REJECT.
d. If ``O.[[Extensible]]`` is ``false``, goto REJECT.
e. Goto NEWPROP.
5. If ``IsDataDescriptor(desc)``:
a. If ``O`` is not an object (was coerced), goto REJECT.
b. If ``curr`` != ``O`` (property is an inherited data property):
(Note: assumes there are no prototype loops.)
1. If ``O.[[Extensible]`` is ``false``, goto REJECT.
2. If ``desc.[[Writable]]`` is ``false``, goto REJECT.
3. Goto NEWPROP.
c. Else (property is an own data property):
1. If ``desc.[[Writable]]`` is ``false``, goto REJECT.
2. Goto UPDATEPROP.
6. Else (property is an accessor):
a. If ``desc.[[Set]]`` is ``undefined``, goto REJECT.
b. Call the ``[[Call]]`` internal method of ``desc.[[Set]]`` providing
``O`` as the ``this`` value and providing ``V`` as the sole argument.
(Note: the difference to a basic ``[[Put]]`` is that the setter ``this``
binding is the original, uncoerced object.)
c. Return.
7. **UPDATEPROP:**
(Inlined ``[[DefineOwnProperty]]`` call for existing property.)
If ``O`` is an ``Array`` object, and ``P`` is ``"length"``, then:
a. Let ``newLen`` be ``ToUint32(V)``.
b. If ``newLen`` is not equal to ``ToNumber(V)``, goto REJECTRANGE.
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.
8. Set the ``[[Value]]`` attribute of the property named ``P`` of object
``O`` to ``V``. (Since it is side effect free to update the value
with the same value, no check for that case is needed.)
9. 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``,
``V``, and ``Throw`` as the arguments. (This updates the bound
variable value.)
10. Return.
11. **NEWPROP:**
(Inlined ``[[DefineOwnProperty]]`` call for new property.)
Let ``pendingLength`` be 0 (zero).
12. 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. Let ``pendingLength`` be ``index + 1`` (always non-zero).
13. Create an own data property named ``P`` of object ``O`` whose attributes
are:
* ``[[Value]]: V``
* ``[[Writable]]: true``
* ``[[Enumerable]]: true``
* ``[[Configurable]]: true``
14. If ``pendingLength`` > ``0``:
a. Update the ``"length"`` property of ``O`` to the value ``pendingLength``.
This always succeeds.
(Note: this can only happen for an ``Array`` object, and the ``length``
property must exist and has already been checked to be writable.)
15. Return.
16. **REJECT**:
If ``Throw`` is ``true``, then throw a ``TypeError`` exception,
otherwise return.
Fast path for array indices
===========================
There is currently no fast path for array indices in the implementation.
This is primarily because to implement ``[[Put]`` properly, the prototype
chain needs to be walked when creating new properties, as an ancestor
property may prevent or capture the write. The current implementation cannot
walk the prototype chain without coercing the key to a string first.
A fast path could be easily added for writing to existing array entries,
though, but it's probably better to solve the problem a bit more comprehensively.
Implementation notes
====================
* Property writes may fail for out of memory or other internal reasons.
In such cases the algorithm should just throw an error and avoid making
any updates to the object state. This is easy for normal properties,
but there are some subtle issues when dealing with exotic behaviors
which link multiple properties together and should be updated either
atomically or in some consistent manner. In particular:
+ For NEWPROP, if the property written is an array index which updates
array ``length``, the property write should be performed first. If
the property write succeeds ``length`` should be updated (and should
never fail):
Final version
=============
(See above.)