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.
903 lines
29 KiB
903 lines
29 KiB
10 years ago
|
=======================================
|
||
|
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.)
|