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.
6874 lines
239 KiB
6874 lines
239 KiB
======================
|
|
duk_hobject algorithms
|
|
======================
|
|
|
|
Overview
|
|
========
|
|
|
|
Purpose
|
|
-------
|
|
|
|
This document discusses, in detail, the internal algorithms for dealing
|
|
with objects, in particular for object property access. These algorithms
|
|
are based on the algorithm descriptions in the E5 specification, which
|
|
have been refined towards the practical implementation needs e.g. by
|
|
combining multiple algorithms, inlining calls, and inlining "special
|
|
behaviors".
|
|
|
|
The intent is to describe versions of the conceptual algorithms most suited
|
|
for implementation, without actually going into implementation level details.
|
|
Only complicated core algorithms and built-in methods are covered.
|
|
|
|
One important question is to identify the *exposed interface* of operations
|
|
invoked from concrete object-related expressions of Ecmascript code. These
|
|
primitives are also in almost 1:1 relationship with the internal bytecode
|
|
operations.
|
|
|
|
Call and constructor related algorithms (``[[Call]]`` and ``[[Construct]]``)
|
|
are covered in ``execution.txt``.
|
|
|
|
Related sections of E5 specification
|
|
------------------------------------
|
|
|
|
For raw property algorithms:
|
|
|
|
* E5 Section 8.12: the default algorithms
|
|
* E5 Section 8.6.2: one paragraph lists special behaviors (page 33, PDF page 43)
|
|
* E5 Section 10.6: arguments object
|
|
* E5 Section 15.5.5.2: String object
|
|
* E5 Section 15.3.4.5.3, 15.3.5.3, 15.3.5.4: Function object
|
|
* E5 Section 15.4.5.1: Array object
|
|
|
|
For other algorithms:
|
|
|
|
* E5 Section 8.10.4: FromPropertyDescriptor
|
|
* E5 Section 8.10.5: ToPropertyDescriptor
|
|
* E5 Section 8.7.1: GetValue
|
|
* E5 Section 8.7.2: PutValue
|
|
* E5 Section 11.2.1: property accessor expression
|
|
* E5 Section 11.13: assignment operators (note that several other places
|
|
also "evaluate" property accessor expressions)
|
|
* E5 Section 11.4.1: ``delete`` operator
|
|
* E5 Section 11.8.6: ``instanceof`` operator
|
|
* E5 Section 11.8.7: ``in`` operator
|
|
* E5 Section 15.2.3.3: ``Object.getOwnPropertyDescriptor()``
|
|
* E5 Section 15.2.3.6: ``Object.defineProperty()``
|
|
* E5 Section 15.2.3.7: ``Object.defineProperties()``
|
|
|
|
Algorithm overview
|
|
------------------
|
|
|
|
Ecmascript object property access behavior is described by internal property
|
|
handling algorithms. The default algorithms are described in E5 Section 8.12.
|
|
There are some objects with special behaviors; these have variants of the
|
|
default property handling algorithms. See ``hobject-design.txt`` for a
|
|
detailed discussion of special behaviors.
|
|
|
|
The "raw" property algorithms are:
|
|
|
|
* The default ``[[GetOwnProperty]](P)`` algorithm (E5 Section 8.12.1)
|
|
|
|
* Modified behavior for: String object (E5 Section 15.5.5.2)
|
|
|
|
* Modified behavior for: Arguments object created for non-strict functions (E5 Section 10.6)
|
|
|
|
* The default ``[[GetProperty]](P)`` algorithm (E5 Section 8.12.1)
|
|
|
|
* The default ``[[Get]](P)`` algorithm (E5 Section 8.12.1)
|
|
|
|
* Modified behavior for: Arguments object created for non-strict functions (E5 Section 10.6)
|
|
|
|
* Modified behavior for: Function object (E5 Section 15.3.5.4)
|
|
|
|
* Note that special behaviors of ``[[Get]]`` are '''not''' visible through property
|
|
descriptors at all.
|
|
|
|
* The default ``[[CanPut]](P)`` algorithm (E5 Section 8.12.1)
|
|
|
|
* The default ``[[Put]](P,V,Throw)`` algorithm (E5 Section 8.12.1)
|
|
|
|
* The default ``[[HasProperty]](P)`` algorithm (E5 Section 8.12.1)
|
|
|
|
* Modified behavior for: Function object (E5 Section 15.3.5.3)
|
|
|
|
* Modified behavior for: bound Function objects (E5 Section 15.3.4.5.3)
|
|
|
|
* The default ``[[Delete]](P,Throw)`` algorithm (E5 Section 8.12.1)
|
|
|
|
* Modified behavior for: Arguments object created for non-strict functions (E5 Section 10.6)
|
|
|
|
* The default ``[[DefineOwnProperty]](P,Desc,Throw)`` algorithm (E5 Section 8.12.1)
|
|
|
|
* Modified behavior for: Array object (E5 Section 15.4.5.1)
|
|
|
|
* Modified behavior for: Arguments object created for non-strict functions (E5 Section 10.6)
|
|
|
|
The following figure illustrates the caller-callee relationship between the
|
|
default property algorithms (the figure would be somewhat different for
|
|
special behavior variants)::
|
|
|
|
[[Put]]
|
|
|
|
|
|
|
|
.----+---------+
|
|
| | |
|
|
| v |
|
|
[[HasProperty]] [[Get]] | [[CanPut]] |
|
|
| | | | | |
|
|
`--------. | | | | |
|
|
| | | | | .---'
|
|
| | | | | |
|
|
v v v v | |
|
|
[[Delete]] [[GetProperty]] | | [[DefineOwnProperty]]
|
|
| | | | |
|
|
| | .-' | |
|
|
| | | .--' |
|
|
`---------. | | | .-----------'
|
|
| | | | |
|
|
v v v v v
|
|
[[GetOwnProperty]] [[DefaultValue]]
|
|
|
|
However, these algorithms are only the "raw" property handling algorithms.
|
|
Actual property access operations in Ecmascript code are "wrapped" by e.g.:
|
|
|
|
* The evaluation rules for the specific expression (e.g. property read).
|
|
These usually contain type checks, some coercions, etc.
|
|
|
|
* ``GetValue()`` and ``PutValue()`` (E5 Section 8.7) are used for property
|
|
property read/write operations in Ecmascript code. The algorithms are
|
|
wrappers for ``[[Get]]`` and ``[[Put]]`` which allow the base reference
|
|
to be a non-object, coercing it to a (temporary) object first. This allows
|
|
expressions like::
|
|
|
|
print("foo".length);
|
|
|
|
Important questions
|
|
-------------------
|
|
|
|
From an implementation perspective there are many questions which don't have
|
|
an easy answer in the E5 specification, e.g.:
|
|
|
|
* How are these internal algorithms visible to user code? This exposed
|
|
interface places hard requirements on viable implementation approaches,
|
|
whereas internal behavior can be implemented in several ways.
|
|
|
|
* What do the internal algorithms look like after you "inline" the calls
|
|
used in the specification (which often obscure the true semantics)?
|
|
|
|
* What do the internal algorithms look like if special behaviors are
|
|
"inlined" into one algorithm which supports both the default and all the
|
|
special behaviors?
|
|
|
|
* What do the internal algorithms look like once we add "fast paths" for
|
|
array index access (where the fast path avoids string interning if
|
|
possible)?
|
|
|
|
* What do the internal algorithms look like once we consider the internal
|
|
``duk_hobject`` representation (e.g. separation between entry and array
|
|
parts)?
|
|
|
|
The purpose of this document is to provide answers to these questions, and
|
|
act as a basis for implementing the rather tricky required behavior
|
|
accurately. The internal algorithms are discussed, inlined, and reformulated
|
|
to a more useful form. The sections are organized on the basis of practical
|
|
implementations needs, i.e. the context where internal algorithms are actually
|
|
needed.
|
|
|
|
What's not covered?
|
|
-------------------
|
|
|
|
This document does not go into full implementation detail in the algorithms.
|
|
Algorithms remain at a conceptual level.
|
|
In particular, the following are not covered:
|
|
|
|
* Relatively simple algorithms or built-in methods are not covered.
|
|
For instance, ``Object.defineProperty()`` is covered but
|
|
``Object.seal()`` is not, because ``Object.seal()`` is simple enough
|
|
to be implemented (and later verified) directly.
|
|
|
|
* Reference counts and reachability for garbage collection. These are
|
|
critical and sometimes difficult to implement correctly.
|
|
|
|
* Internal errors such as out-of-memory which may happen at any point
|
|
but are not mentioned in the algorithms. Where appropriate, the steps
|
|
in abstract algorithms *are* adjusted to minimize inconsistencies if
|
|
an internal error occurs.
|
|
|
|
* ``duk_hobject`` entry and array part separation, which affects all
|
|
operations dealing with properties.
|
|
|
|
* Error ``message`` strings for particular kinds of error. The E5
|
|
specification only mandates error type (its class) but never mandates
|
|
any texts.
|
|
|
|
* Concrete code structure or ordering; actual implementation may have a
|
|
slightly different structure.
|
|
|
|
Exposed interface
|
|
=================
|
|
|
|
What is an exposed interface?
|
|
-----------------------------
|
|
|
|
The relevant *exposed interface* is the set of object related operations
|
|
which can be invoked from Ecmascript code, e.g.::
|
|
|
|
// property write
|
|
o.foo = "bar";
|
|
|
|
// property read
|
|
print(o.foo);
|
|
|
|
// property deletion
|
|
delete o.foo;
|
|
|
|
// property existence check
|
|
print('foo' in o);
|
|
|
|
// object class membership test
|
|
print(x instanceof Array);
|
|
|
|
It also covers intricate built-in methods, such as::
|
|
|
|
var t = Object.getOwnPropertyDescriptor(o, 'foo');
|
|
|
|
Object.defineOwnProperty(o, 'foo', { value: 'bar' });
|
|
|
|
Object.defineProperties(o, {
|
|
foo: { value: 'bar' },
|
|
bar: { value: 'quux' }
|
|
});
|
|
|
|
These exposed primitives are discussed in this section.
|
|
|
|
Contexts and property algorithms used
|
|
-------------------------------------
|
|
|
|
The following table lists all contexts where property algorithms are
|
|
invoked from user code. (All ``Object`` built-in methods are listed
|
|
for completeness although not all of them invoke property algorithms.)
|
|
|
|
+---------------------------------------+---------------------------------+
|
|
| Context | Related algorithms / notes |
|
|
+=======================================+=================================+
|
|
| Property read | Property accessor reference, |
|
|
| | ``GetValue()`` for the |
|
|
| | reference, ``[[Get]]``. If |
|
|
| | base reference is not an object,|
|
|
| | ``[[GetProperty]]``, |
|
|
| | ``[[Call]]``. |
|
|
+---------------------------------------+---------------------------------+
|
|
| Property write | Property accessor reference, |
|
|
| | ``PutValue()`` for the |
|
|
| | reference, ``[[Put]]``. If |
|
|
| | base reference is not an object,|
|
|
| | ``[[CanPut]]``, |
|
|
| | ``[[GetOwnProperty]]``, |
|
|
| | ``[[GetProperty]]``, |
|
|
| | ``[[Call]]``. |
|
|
+---------------------------------------+---------------------------------+
|
|
| Property ``delete`` | Property accessor reference, |
|
|
| | ``[[Delete]]``. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``in`` | ``[[HasProperty]]``. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``instanceof`` | ``[[HasInstance]]``. |
|
|
+---------------------------------------+---------------------------------+
|
|
| "for-in" enumeration | Enumeration order guarantees, |
|
|
| | see ``hobject-design.txt``. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.getPrototypeOf()`` | Just returns internal prototype.|
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.getOwnPropertyDescriptor()`` | ``[[GetOwnProperty]]``, |
|
|
| | ``FromPropertyDescriptor()`` |
|
|
| | for a fully populated property |
|
|
| | descriptor. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.getOwnPropertyNames()`` | Creates a result array, uses |
|
|
| | ``[[DefineOwnProperty]]`` |
|
|
| | internally. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.create()`` | No direct use of property |
|
|
| | algorithms but conceptually |
|
|
| | calls |
|
|
| | ``Object.defineProperties()`` |
|
|
| | internally. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.defineProperty()`` | ``ToPropertyDescriptor()``, |
|
|
| | ``[[DefineOwnProperty]]`` with |
|
|
| | an arbitrary descriptor. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.defineProperties()`` | ``ToPropertyDescriptor()``, |
|
|
| | ``[[DefineOwnProperty[]`` with |
|
|
| | an arbitrary descriptor. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.seal()`` | ``[[GetOwnProperty]]``, |
|
|
| | ``[[DefineOwnProperty]]``; |
|
|
| | sets ``[[Extensible]]`` to |
|
|
| | false. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.freeze()`` | ``[[GetOwnProperty]]``, |
|
|
| | ``[[DefineOwnProperty]]``, |
|
|
| | sets ``[[Extensible]]`` to |
|
|
| | false. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.preventExtensions()`` | Sets ``[[Extensible]]`` to |
|
|
| | false. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.isSealed()`` | ``[[GetOwnProperty]]``, reads |
|
|
| | ``[[Extensible]]``. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.isFrozen()`` | ``[[GetOwnProperty]]``, reads |
|
|
| | ``[[Extensible]]``. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.isExtensible()`` | Reads ``[[Extensible]]``. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.keys()`` | Key order must match "for-in" |
|
|
| | enumeration order. |
|
|
+---------------------------------------+---------------------------------+
|
|
| ``Object.prototype.hasOwnProperty()`` | ``[[GetOwnProperty]]`` |
|
|
| | (does *not* use |
|
|
| | ``[[HasProperty]]``) |
|
|
+---------------------------------------+---------------------------------+
|
|
|
|
Central exposed primitives
|
|
--------------------------
|
|
|
|
The central exposed primitives are as follows. Some have been given an
|
|
internal name which corresponds to the bytecode instruction:
|
|
|
|
* GETPROP: property read expression: coercion wrapping, ``GetValue()``,
|
|
``[[Get]]``, and a special ``[[Get]]`` variant if base is primitive
|
|
|
|
* PUTPROP: property write expression: coercion wrapping, ``PutValue()``,
|
|
``[[Put]]``, and a special ``[[Put]]`` variant if base is primitive
|
|
|
|
* DELPROP: ``delete`` operator: coercion wrapping, ``[[Delete]]``
|
|
|
|
* HASPROP: ``in`` operator: type check wrapping, ``[[HasProperty]]``
|
|
|
|
* INSTOF: ``instanceof`` operator: type check wrapping, ``[[HasInstance]]``
|
|
|
|
+ Not a property related primitive directly, but tied to the
|
|
prototype chain
|
|
|
|
* ``Object.getOwnPropertyDescriptor()``
|
|
|
|
+ But not ``[[GetOwnProperty]]`` or ``[[GetProperty]]`` directly
|
|
+ Not "fast path" so implementation should be compact
|
|
+ Only throwing variant (``Throw`` is ``true``)
|
|
|
|
* ``Object.defineProperty()`` and ``Object.defineProperties()``
|
|
|
|
+ But not ``[[DefineOwnProperty]]`` directly
|
|
+ Not "fast path" so implementation should be compact
|
|
+ Only throwing variant (``Throw`` is ``true``)
|
|
|
|
These are used to implement the basic property related run-time operations
|
|
and some difficult built-in functions. They are also used to implement
|
|
the C API and are also basic bytecode operations.
|
|
|
|
The remaining primitives (like ``Object.seal()`` etc) are trivial in
|
|
comparison, and are not analyzed in this document.
|
|
|
|
Notes
|
|
-----
|
|
|
|
* ``Object.getOwnPropertyDescriptor()``, ``Object.defineProperty()``,
|
|
and ``Object.defineProperties()`` are the only exposed interfaces where
|
|
property descriptors are explicitly exposed to user code, and also the
|
|
only places where property descriptors are converted between internal and
|
|
external forms. All other exposed interfaces deal with property
|
|
descriptors and attributes internally only. These methods always set the
|
|
``Throw`` flag to ``true``, so the exposed implementation only needs to
|
|
have a "throwing" variant.
|
|
|
|
* Property read and write handle a non-object base object with a specific
|
|
variant for the basic ``[[Get]]`` and ``[[Put]]``, defined in E5 Sections
|
|
8.7.1 and 8.7.2 (``GetValue()`` and ``PutValue()``). Property delete uses
|
|
a normal ``ToObject()`` coercion and then calls ``[[Delete]]`` normally.
|
|
Property existence check (``in``) does a type check and throws an error
|
|
if an argument is not already an object. So, coercion behavior is a bit
|
|
different in each context.
|
|
|
|
* Property References are established when parsing property access
|
|
expressions, E5 Section 11.2.1.
|
|
|
|
* Property References are used as right-hand-side values and read using
|
|
``GetValue()`` from various places:
|
|
|
|
+ Array initializer
|
|
+ Object initializer
|
|
+ Grouping operator (parentheses)
|
|
+ Property accessor (e.g. ``x['foo']['bar']``)
|
|
+ ``new`` operator
|
|
+ Function calls
|
|
+ Postfix and prefix increment/decrement
|
|
+ ``void`` operator
|
|
+ Unary operators (plus, minus, bitwise and logical NOT)
|
|
+ Binary operators (additive and multiplicative expressions, bitwise,
|
|
logical, and comparison operations)
|
|
+ ``instanceof`` operator
|
|
+ ``in`` operator
|
|
+ Conditional operator (``?:``)
|
|
+ Simple and compound assignment (right hand side)
|
|
+ Comma operator (``,``)
|
|
+ Variable declaration initializer
|
|
+ ``if``, ``do-while``, ``while``, ``for``, ``for-in``, ``with`` statements
|
|
+ ``throw`` statement
|
|
|
|
* Property references are used as left-hand-side values and written using
|
|
``PutValue()`` from various places:
|
|
|
|
+ Postfix and prefix increment/decrement
|
|
+ Simple and compound assignment
|
|
+ Variable declaration initializer
|
|
+ ``for`` and ``for-in`` statements
|
|
|
|
Special behaviors
|
|
=================
|
|
|
|
This section covers the standard algorithms with special behaviors inlined.
|
|
For each algorithm, a single algorithm with all special behaviors inlined
|
|
is presented. Calls to other internal algorithms are not inlined; the
|
|
purpose is to clarify how the special behaviors can be implemented
|
|
reasonably.
|
|
|
|
Note: the ``String`` object has no special behaviors as such, but the
|
|
``length`` and array index properties are implemented as virtual properties,
|
|
so they are inlined into the algorithms below.
|
|
|
|
GetOwnProperty
|
|
--------------
|
|
|
|
Related E5 sections:
|
|
|
|
* E5 Section 8.12.1: default algorithm
|
|
* E5 Section 15.5.5: ``String``
|
|
* E5 Section 10.5: arguments object
|
|
|
|
Default algorithm
|
|
:::::::::::::::::
|
|
|
|
1. If ``O`` doesn’t have an own property with name ``P``, return ``undefined``.
|
|
|
|
2. Let ``D`` be a newly created Property Descriptor with no fields.
|
|
|
|
3. Let ``X`` be ``O``\ ’s own property named P.
|
|
|
|
4. If ``X`` is a data property, then
|
|
|
|
a. Set ``D.[[Value]]`` to the value of ``X``\ ’s ``[[Value]]`` attribute.
|
|
|
|
b. Set ``D.[[Writable]]`` to the value of ``X``\ ’s ``[[Writable]]`` attribute.
|
|
|
|
5. Else ``X`` is an accessor property, so
|
|
|
|
a. Set ``D.[[Get]]`` to the value of ``X``\ ’s ``[[Get]]`` attribute.
|
|
|
|
b. Set ``D.[[Set]]`` to the value of ``X``\ ’s ``[[Set]]`` attribute.
|
|
|
|
6. Set ``D.[[Enumerable]]`` to the value of ``X``\ ’s ``[[Enumerable]]`` attribute.
|
|
|
|
7. Set ``D.[[Configurable]]`` to the value of ``X``\ ’s ``[[Configurable]]`` attribute.
|
|
|
|
8. Return ``D``.
|
|
|
|
Adding String object special behavior
|
|
:::::::::::::::::::::::::::::::::::::
|
|
|
|
Now consider the ``String`` variant in E5 Section 15.5.5.2. Step 2 states that if
|
|
the default algorithm returns a descriptor (not undefined), the special behavior
|
|
does not execute at all. That, is the special algorithm is skipped if ``O`` has
|
|
an "own property" for key ``P``.
|
|
|
|
If the default algorithm fails to find an own property, the variant kicks in
|
|
checking for a valid array index key which is inside the string length. If so,
|
|
it returns a single character data property descriptor. The descriptor has
|
|
``[[Writable]]`` and ``[[Configurable]]`` set to ``false`` which means that
|
|
the property cannot be written or deleted -- the property is thus perfect for
|
|
implementation as a virtual property backed to an immutable internal string
|
|
value.
|
|
|
|
.. note:: Ecmascript 5.1 no longer requires the numbered index to be a valid
|
|
array index, any number-like value will do. This allows strings
|
|
longer than 4G. The algorithms here don't reflect this correctly.
|
|
|
|
The ``String`` object ``length`` property is an ordinary (non-special)
|
|
property, see E5 Section 15.5.5.1. However, it is non-writable and
|
|
non-configurable (and even non-enumerable), so it too is nice and easy
|
|
to implement as a special property. We'll thus incorporate the ``length``
|
|
property into the algorithm.
|
|
|
|
Finally note that from an implementation perspective it might be easier
|
|
to check for the special (virtual) properties before looking at the actual
|
|
ones (i.e. reverse the order of checking). This seems perfectly OK to do,
|
|
because *if* the property name matches a virtual property, the object cannot
|
|
have a "normal" property of the same name: the initial ``String`` object
|
|
does not have such properties, and since the virtual properties cannot be
|
|
deleted, they prevent the insertion of normal "own properties" of the same
|
|
name. Hence, if the virtual properties are checked for first and the check
|
|
matches, the object is guaranteed not to have a normal property of the same
|
|
name. (Whether this is useful in an implementation is another issue.)
|
|
|
|
The combined algorithm, assuming the the virtual properties are checked
|
|
after the normal property check is as follows:
|
|
|
|
1. If ``O`` doesn’t have an own property with name ``P``:
|
|
|
|
a. If ``O`` is not a ``String`` instance, return ``undefined``.
|
|
|
|
b. (``String`` object special 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"``, return a Property Descriptor with the values:
|
|
|
|
* ``[[Value]]: len`` (a number)
|
|
* ``[[Enumerable]]: false``
|
|
* ``[[Writable]]: false``
|
|
* ``[[Configurable]]: false``
|
|
|
|
d. If ``P`` is not an array index (E5 Section 15.4), return ``undefined``.
|
|
|
|
e. Let ``index`` be ``ToUint32(P)``.
|
|
|
|
f. If ``len`` <= ``index``, return ``undefined``.
|
|
|
|
g. Let ``resultStr`` be a string of length 1, containing one character
|
|
from ``str``, specifically the character at position ``index``, where
|
|
the first (leftmost) character in ``str`` is considered to be at
|
|
position 0, the next one at position 1, and so on.
|
|
|
|
h. Return a Property Descriptor with the values:
|
|
|
|
* ``[[Value]]: resultStr``
|
|
* ``[[Enumerable]]: true``
|
|
* ``[[Writable]]: false``
|
|
* ``[[Configurable]]: false``
|
|
|
|
2. Let ``D`` be a newly created Property Descriptor with no fields.
|
|
|
|
3. Let ``X`` be ``O``\ ’s own property named ``P``.
|
|
|
|
4. If ``X`` is a data property, then
|
|
|
|
a. Set ``D.[[Value]]`` to the value of ``X``\ ’s ``[[Value]]`` attribute.
|
|
|
|
b. Set ``D.[[Writable]]`` to the value of ``X``\ ’s ``[[Writable]]`` attribute.
|
|
|
|
5. Else ``X`` is an accessor property, so
|
|
|
|
a. Set ``D.[[Get]]`` to the value of ``X``\ ’s ``[[Get]]`` attribute.
|
|
|
|
b. Set ``D.[[Set]]`` to the value of ``X``\ ’s ``[[Set]]`` attribute.
|
|
|
|
6. Set ``D.[[Enumerable]]`` to the value of ``X``\ ’s ``[[Enumerable]]`` attribute.
|
|
|
|
7. Set ``D.[[Configurable]]`` to the value of ``X``\ ’s ``[[Configurable]]`` attribute.
|
|
|
|
8. Return ``D``.
|
|
|
|
Adding arguments object special behavior
|
|
::::::::::::::::::::::::::::::::::::::::
|
|
|
|
Next, consider the special ``[[GetOwnProperty]]`` behavior for a non-strict
|
|
arguments object described in E5 Section 10.6. The special behavior only
|
|
applies if the object *did* contain the own property ``P``, and possibly
|
|
modifies the looked up value if the key ``P`` matches a numeric index
|
|
magically "bound" to a formal.
|
|
|
|
Note that the property descriptors for such variables are initially data
|
|
property descriptors, so the default algorithm will find a data property
|
|
descriptor (and not an accessor property descriptor). If the property is
|
|
later converted to an accessor, the magical variable binding is also
|
|
dropped. So, if the special behavior activates, the property is always
|
|
a data property.
|
|
|
|
The special behavior can be appended to the above algorithm as follows:
|
|
|
|
1. If ``O`` doesn’t have an own property with name ``P``:
|
|
|
|
a. If ``O`` is not a ``String`` instance, return ``undefined``.
|
|
|
|
b. (``String`` object special 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"``, return a Property Descriptor with the values:
|
|
|
|
* ``[[Value]]: len`` (a number)
|
|
* ``[[Enumerable]]: false``
|
|
* ``[[Writable]]: false``
|
|
* ``[[Configurable]]: false``
|
|
|
|
d. If ``P`` is not an array index (E5 Section 15.4), return ``undefined``.
|
|
|
|
e. Else let ``index`` be ``ToUint32(P)``.
|
|
|
|
f. If ``len`` <= ``index``, return ``undefined``.
|
|
|
|
g. Let ``resultStr`` be a string of length 1, containing one character
|
|
from ``str``, specifically the character at position ``index``, where
|
|
the first (leftmost) character in ``str`` is considered to be at
|
|
position 0, the next one at position 1, and so on.
|
|
|
|
h. Return a Property Descriptor with the values:
|
|
|
|
* ``[[Value]]: resultStr``
|
|
* ``[[Enumerable]]: true``
|
|
* ``[[Writable]]: false``
|
|
* ``[[Configurable]]: false``
|
|
|
|
2. Let ``D`` be a newly created Property Descriptor with no fields.
|
|
|
|
3. Let ``X`` be ``O``\ ’s own property named ``P``.
|
|
|
|
4. If ``X`` is a data property, then
|
|
|
|
a. Set ``D.[[Value]]`` to the value of ``X``\ ’s ``[[Value]]`` attribute.
|
|
|
|
b. Set ``D.[[Writable]]`` to the value of ``X``\ ’s ``[[Writable]]`` attribute.
|
|
|
|
5. Else ``X`` is an accessor property, so
|
|
|
|
a. Set ``D.[[Get]]`` to the value of ``X``\ ’s ``[[Get]]`` attribute.
|
|
|
|
b. Set ``D.[[Set]]`` to the value of ``X``\ ’s ``[[Set]]`` attribute.
|
|
|
|
6. Set ``D.[[Enumerable]]`` to the value of ``X``\ ’s ``[[Enumerable]]`` attribute.
|
|
|
|
7. Set ``D.[[Configurable]]`` to the value of ``X``\ ’s ``[[Configurable]]`` attribute.
|
|
|
|
8. If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special behavior.) Let ``map`` be the value of
|
|
the ``[[ParameterMap]]`` internal property of the arguments object.
|
|
|
|
b. Let ``isMapped`` be the result of calling the ``[[GetOwnProperty]]``
|
|
internal method of ``map`` passing ``P`` as the argument.
|
|
|
|
c. If the value of ``isMapped`` is not ``undefined``, then:
|
|
|
|
1. Set ``D.[[Value]]`` to the result of calling the ``[[Get]]``
|
|
internal method of ``map`` passing ``P`` as the argument.
|
|
|
|
9. Return ``D``.
|
|
|
|
Notes:
|
|
|
|
* Step 1.b: if the object is a ``String`` object, there is no need for the
|
|
arguments object special behavior check in step 8: an object can never be
|
|
a ``String`` object and an arguments object simultaenously.
|
|
|
|
* Step 8: arguments objects for strict mode functions don't have the special
|
|
behavior (or a ``[[ParameterMap]]``). Arguments objects for non-strict
|
|
functions don't always have special behavior either: they only do, if there
|
|
is at least one mapped variable. If so, ``[[ParameterMap]]`` is added, and
|
|
special behavior is enabled. See the main algorithm in E5 Section 10.6,
|
|
step 12.
|
|
|
|
* Step 8.c.1: this step invokes an internal getter function which looks up
|
|
the magically bound variable. See E5 Section 10.6, 11.c.ii, and the
|
|
*MakeArgGetter* concept. A practical implementation may not create such
|
|
internal functions (we don't).
|
|
|
|
* Step 8.c.1: the rules of maintaining the ``[[ParameterMap]]`` ensures that
|
|
at this point the property is always a data property, so setting the
|
|
``[[Value]]`` is correct. If a magically bound value is converted into an
|
|
accessor, the property is deleted from the ``[[ParameterMap]]`` so it no
|
|
longer has special behavior.
|
|
|
|
Final version
|
|
:::::::::::::
|
|
|
|
Final version with some cleanup and simplification:
|
|
|
|
1. Let ``X`` be ``O``\ ’s own property named ``P``.
|
|
If ``O`` doesn’t have an own property with name ``P``:
|
|
|
|
a. If ``O`` is not a ``String`` instance, return ``undefined``.
|
|
|
|
b. (``String`` object special 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. Return ``undefined``.
|
|
|
|
2. 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.
|
|
|
|
3. If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special 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.
|
|
|
|
4. Return ``D``.
|
|
|
|
Notes:
|
|
|
|
* Step 3 can be skipped for accessors.
|
|
|
|
Get
|
|
---
|
|
|
|
Related E5 sections:
|
|
|
|
* E5 Section 8.12.3: default algorithm
|
|
* E5 Section 10.5: arguments object
|
|
* E5 Section 15.3.5.4: ``Function``
|
|
|
|
Default algorithm
|
|
:::::::::::::::::
|
|
|
|
(Note that E5 Section 8.12.3 has broken numbering; fixed below.)
|
|
|
|
1. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
2. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
3. If ``IsDataDescriptor(desc)`` is ``true``, return ``desc.[[Value]]``.
|
|
|
|
4. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true`` so, let
|
|
``getter`` be ``desc.[[Get]]``.
|
|
|
|
5. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
6. Return the result calling the ``[[Call]]`` internal method of ``getter``
|
|
providing ``O`` as the ``this`` value and providing no arguments.
|
|
|
|
Adding Function object special behavior
|
|
:::::::::::::::::::::::::::::::::::::::
|
|
|
|
Consider the ``Function`` variant in E5 Section 15.3.5.4. The behavior only
|
|
applies if ``P`` is ``caller`` and the resulting return *value* of the default
|
|
function is a strict mode function.
|
|
|
|
The special behavior does not need to be checked in steps 2 or 5 of the
|
|
default algorithm, because ``undefined`` is never a strict mode function
|
|
value.
|
|
|
|
So, we can reformulate into:
|
|
|
|
1. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
2. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
3. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
4. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
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.
|
|
|
|
5. If ``O`` is a ``Function`` object, ``P`` is ``"caller"``, and ``res``
|
|
is a strict mode ``Function`` object, throw a ``TypeError`` exception.
|
|
|
|
6. Return ``res``.
|
|
|
|
Adding arguments object special behavior
|
|
::::::::::::::::::::::::::::::::::::::::
|
|
|
|
Next, consider the special ``[[Get]]`` behavior for a non-strict arguments
|
|
object described in E5 Section 10.6. To be exact, the special behaviors
|
|
are only enabled for objects with a non-empty initial ``[[ParameterMap]]``
|
|
(see E5 Section 10.6, main algorithm, step 12).
|
|
|
|
There are two special behaviors:
|
|
|
|
1. If the property name ``P`` is magically bound to an identifier
|
|
(through the ``[[ParameterMap]]``) the default ``[[Get]]`` is
|
|
bypassed entirely and the property value is read.
|
|
(Note that the property ``P`` *must* be a data property in this
|
|
case, so no side effects are lost by this behavior.)
|
|
|
|
2. If the property name ``P`` is *not bound* to an identifier,
|
|
the ``"caller"`` property has special behavior essentially
|
|
identical to that of ``Function``.
|
|
|
|
These can be incorporated as follows:
|
|
|
|
1. If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special behavior.) Let ``map`` be the value of
|
|
the ``[[ParameterMap]]`` internal property of the arguments object.
|
|
|
|
b. Let ``isMapped`` be the result of calling the ``[[GetOwnProperty]]``
|
|
internal method of ``map`` passing ``P`` as the argument.
|
|
|
|
c. If the value of ``isMapped`` is not ``undefined``, then:
|
|
|
|
1. Return the result of calling the ``[[Get]]`` internal method of
|
|
``map`` passing ``P`` as the argument.
|
|
|
|
2. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
3. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
4. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
5. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
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.
|
|
|
|
6. If ``O`` is a ``Function`` object or an ``arguments`` object which
|
|
contains a ``[[ParameterMap]]`` internal property:
|
|
|
|
a. (Arguments or Function object special behavior.)
|
|
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
|
|
object, throw a ``TypeError`` exception.
|
|
|
|
7. Return ``res``.
|
|
|
|
Note:
|
|
|
|
* Step 1 can match only when ``P`` is a "numeric" property name, and
|
|
the property value is an own data property. Magically bound properties
|
|
are initially own data properties, and if they're changed to accessors
|
|
(or deleted), the binding is removed. Because of this, the arguments
|
|
special behavior could just as well be moved to the end of the algorithm.
|
|
|
|
Final version
|
|
:::::::::::::
|
|
|
|
Final version with some cleanup and simplification:
|
|
|
|
1. If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special 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. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
3. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
4. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
5. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
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.
|
|
|
|
6. If ``O`` is a ``Function`` object or an ``arguments`` object which
|
|
contains a ``[[ParameterMap]]`` internal property:
|
|
|
|
a. (Arguments or Function object special behavior.)
|
|
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
|
|
object, throw a ``TypeError`` exception.
|
|
|
|
7. Return ``res``.
|
|
|
|
DefineOwnProperty
|
|
-----------------
|
|
|
|
Related E5 sections:
|
|
|
|
* E5 Section 8.12.9: default algorithm
|
|
* E5 Section 15.4.5: ``Array``
|
|
* E5 Section 10.5: arguments object
|
|
|
|
Note that ``String`` special properties are taken into account by
|
|
``[[DefineOwnProperty]]`` through ``[[GetOwnProperty]]`` which
|
|
returns a property descriptor prohibiting any property value or
|
|
attribute changes. However, no explicit checks are needed for
|
|
these (virtual) properties.
|
|
|
|
This is by the far the most complex property algorithm, especially
|
|
with special behaviors incorporated. The algorithm itself is
|
|
complex, but the ``Array`` variant actually makes multiple calls to
|
|
the default variant which is even trickier for "inlining".
|
|
|
|
Default algorithm
|
|
:::::::::::::::::
|
|
|
|
1. Let ``current`` be the result of calling the ``[[GetOwnProperty]]``
|
|
internal method of ``O`` with property name ``P``.
|
|
|
|
2. Let ``extensible`` be the value of the ``[[Extensible]]`` internal
|
|
property of ``O``.
|
|
|
|
3. If ``current`` is ``undefined`` and ``extensible`` is ``false``,
|
|
then Reject.
|
|
|
|
4. If ``current`` is ``undefined`` and ``extensible`` is ``true``, then
|
|
|
|
a. 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.
|
|
|
|
b. 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.
|
|
|
|
c. Return ``true``.
|
|
|
|
5. Return ``true`` if every field in ``Desc`` is absent.
|
|
|
|
6. Return ``true``, 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).
|
|
|
|
7. If the ``[[Configurable]]`` field of ``current`` is ``false`` then
|
|
|
|
a. Reject, if the ``[[Configurable]]`` field of ``Desc`` is true.
|
|
|
|
b. Reject, if the ``[[Enumerable]]`` field of ``Desc`` is present and
|
|
the ``[[Enumerable]]`` fields of ``current`` and ``Desc`` are the
|
|
Boolean negation of each other.
|
|
|
|
8. If ``IsGenericDescriptor(Desc)`` is ``true``, then no further validation
|
|
is required.
|
|
|
|
9. Else, if ``IsDataDescriptor(current)`` and ``IsDataDescriptor(Desc)``
|
|
have different results, then
|
|
|
|
a. 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.
|
|
|
|
10. Else, if ``IsDataDescriptor(current)`` and ``IsDataDescriptor(Desc)``
|
|
are both true, then
|
|
|
|
a. If the ``[[Configurable]]`` field of ``current`` is ``false``, then
|
|
|
|
1. Reject, if the ``[[Writable]]`` field of ``current`` is ``false``
|
|
and the ``[[Writable]]`` field of ``Desc`` is ``true``.
|
|
|
|
2. If the ``[[Writable]]`` field of ``current`` is ``false``, then
|
|
|
|
a. Reject, if the ``[[Value]]`` field of ``Desc`` is present and
|
|
``SameValue(Desc.[[Value]], current.[[Value]])`` is ``false``.
|
|
|
|
b. else, the ``[[Configurable]]`` field of ``current`` is ``true``, so
|
|
any change is acceptable.
|
|
|
|
11. Else, ``IsAccessorDescriptor(current)`` and ``IsAccessorDescriptor(Desc)``
|
|
are both ``true`` so,
|
|
|
|
a. If the ``[[Configurable]]`` field of ``current`` is ``false``, then
|
|
|
|
1. Reject, if the ``[[Set]]`` field of ``Desc`` is present and
|
|
``SameValue(Desc.[[Set]], current.[[Set]])`` is ``false``.
|
|
|
|
2. Reject, if the ``[[Get]]`` field of ``Desc`` is present and
|
|
``SameValue(Desc.[[Get]], current.[[Get]])`` is ``false``.
|
|
|
|
12. 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.
|
|
|
|
13. Return ``true``.
|
|
|
|
Notes:
|
|
|
|
* The default attributes are *not* the same as when ``[[Put]]`` creates a
|
|
new property. The defaults here are "false" (and NULL for getter/setter),
|
|
see E5 Section 8.6.1, Table 7).
|
|
|
|
* Step 10.a.1 allows a non-configurable property to change from writable to
|
|
non-writable, but not vice versa.
|
|
|
|
* Step 10.b is not necessary (it is more of an assertion), and there is no
|
|
corresponding step 11.b mentioning the same thing. This step can be removed
|
|
from the description.
|
|
|
|
* There are multiple exit points for both Reject (throw or return false) and
|
|
true. For incorporating inline special behaviors, these are turned to
|
|
"gotos" below.
|
|
|
|
Default algorithm reformulated
|
|
::::::::::::::::::::::::::::::
|
|
|
|
Let's first do a little bit of reformulation (see above):
|
|
|
|
1. Let ``current`` be the result of calling the ``[[GetOwnProperty]]``
|
|
internal method of ``O`` with property name ``P``.
|
|
|
|
2. Let ``extensible`` be the value of the ``[[Extensible]]`` internal
|
|
property of ``O``.
|
|
|
|
3. 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.
|
|
|
|
4. 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.)
|
|
|
|
5. 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.
|
|
|
|
6. If ``IsGenericDescriptor(Desc)`` is ``true``, then goto VALIDATED.
|
|
|
|
7. 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.
|
|
|
|
8. 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.
|
|
|
|
9. 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.
|
|
|
|
10. **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.
|
|
|
|
11. **SUCCESS:** Return ``true``.
|
|
|
|
12. **REJECT**: If ``Throw`` is ``true``, then throw a ``TypeError``
|
|
exception, otherwise return ``false``.
|
|
|
|
Analysis of Array object [[DefineOwnProperty]]
|
|
::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
The ``Array`` variant for ``[[DefineOwnProperty]]`` is described in
|
|
E5 Section 15.4.5.1. The variant *seems* to be essentially a pre-check
|
|
for ``length`` and array index properties before the default algorithm
|
|
runs (see steps 1-4 of the variant).
|
|
|
|
However, it's much more complex than that, because the variant algorithm
|
|
makes multiple calls to the default algorithm.
|
|
|
|
Let's look at the variant algorithm first (here we assume ``O`` is an
|
|
``Array`` with special behavior, so no check is made for special behavior):
|
|
|
|
1. 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.
|
|
|
|
2. Let ``oldLen`` be ``oldLenDesc.[[Value]]``.
|
|
(Note that ``oldLen`` is guaranteed to be a unsigned 32-bit integer.)
|
|
|
|
3. If ``P`` is ``"length"``, then
|
|
|
|
a. If the ``[[Value]]`` field of ``Desc`` is absent, then
|
|
|
|
1. Return the result of calling the default ``[[DefineOwnProperty]]``
|
|
internal method (E5 Section 8.12.9) on ``O`` passing ``"length"``,
|
|
``Desc``, and ``Throw`` as arguments.
|
|
|
|
b. Let ``newLenDesc`` be a copy of ``Desc``.
|
|
|
|
c. Let ``newLen`` be ``ToUint32(Desc.[[Value]])``.
|
|
|
|
d. If ``newLen`` is not equal to ``ToNumber(Desc.[[Value]])``, throw a
|
|
``RangeError`` exception.
|
|
|
|
e. Set ``newLenDesc.[[Value]]`` to ``newLen``.
|
|
|
|
f. If ``newLen`` >= ``oldLen``, then
|
|
|
|
1. Return the result of calling the default ``[[DefineOwnProperty]]``
|
|
internal method (E5 Section 8.12.9) on ``O`` passing ``"length"``,
|
|
``newLenDesc``, and ``Throw`` as arguments.
|
|
|
|
g. Reject if ``oldLenDesc.[[Writable]]`` is ``false``.
|
|
|
|
h. If ``newLenDesc.[[Writable]]`` is absent or has the value ``true``,
|
|
let ``newWritable`` be ``true``.
|
|
|
|
i. Else,
|
|
|
|
1. Need to defer setting the ``[[Writable]]`` attribute to ``false`` in
|
|
case any elements cannot be deleted.
|
|
|
|
2. Let ``newWritable`` be ``false``.
|
|
|
|
3. Set ``newLenDesc.[[Writable]]`` to ``true``.
|
|
|
|
j. Let ``succeeded`` be the result of calling the default
|
|
``[[DefineOwnProperty]]`` internal method (E5 Section 8.12.9) on ``O``
|
|
passing ``"length"``, ``newLenDesc``, and ``Throw`` as arguments.
|
|
|
|
k. If ``succeeded`` is ``false``, return ``false``.
|
|
|
|
l. While ``newLen`` < ``oldLen`` repeat,
|
|
|
|
1. Set ``oldLen`` to ``oldLen – 1``.
|
|
|
|
2. Let ``canDelete`` be the result of calling the ``[[Delete]]``
|
|
internal method of ``O`` passing ``ToString(oldLen)`` and ``false``
|
|
as arguments.
|
|
|
|
3. If ``canDelete`` is ``false``, then:
|
|
|
|
a. Set ``newLenDesc.[[Value]`` to ``oldLen+1``.
|
|
|
|
b. If ``newWritable`` is ``false``, set ``newLenDesc.[[Writable]`` to
|
|
``false``.
|
|
|
|
c. Call the default ``[[DefineOwnProperty]]`` internal method (E5
|
|
Section 8.12.9) on ``O`` passing ``"length"``, ``newLenDesc``, and
|
|
``false`` as arguments.
|
|
|
|
d. Reject.
|
|
|
|
m. If ``newWritable`` is ``false``, then
|
|
|
|
1. Call the default ``[[DefineOwnProperty]]`` internal method (E5 Section
|
|
8.12.9) on ``O`` passing ``"length"``, Property Descriptor
|
|
``{[[Writable]]: false}``, and ``false`` as arguments. This call will
|
|
always return ``true``.
|
|
|
|
n. Return ``true``.
|
|
|
|
4. Else if ``P`` is an array index (E5 Section 15.4), then:
|
|
|
|
a. Let ``index`` be ``ToUint32(P)``.
|
|
|
|
b. Reject if ``index`` >= ``oldLen`` and ``oldLenDesc.[[Writable]]`` is
|
|
``false``.
|
|
|
|
c. Let ``succeeded`` be the result of calling the default
|
|
``[[DefineOwnProperty]]`` internal method (E5 Section 8.12.9) on ``O``
|
|
passing ``P``, ``Desc``, and ``false`` as arguments.
|
|
|
|
d. Reject if ``succeeded`` is ``false``.
|
|
|
|
e. If ``index`` >= ``oldLen``:
|
|
|
|
1. Set ``oldLenDesc.[[Value]]`` to ``index + 1``.
|
|
|
|
2. Call the default ``[[DefineOwnProperty]]`` internal method (E5 Section
|
|
8.12.9) on ``O`` passing ``"length"``, ``oldLenDesc``, and ``false``
|
|
as arguments. This call will always return ``true``.
|
|
|
|
f. Return ``true``.
|
|
|
|
5. Return the result of calling the default ``[[DefineOwnProperty]]``
|
|
internal method (E5 Section 8.12.9) on ``O`` passing ``P``, ``Desc``,
|
|
and ``Throw`` as arguments.
|
|
|
|
Notes:
|
|
|
|
* In E5 Section 15.4.5.1 step 3.l.ii - 3.l.iii the temporary variable
|
|
``cannotDelete`` seems to be misused; it should probably be ``canDelete``
|
|
and the check in step iii should read "if ``canDelete`` is ``false`` ...".
|
|
|
|
* Step 5 is the default behavior, assuming nothing "captured" the call
|
|
before.
|
|
|
|
* Unfortunately steps 3 and 4 call the default ``[[DefineOwnProperty]]``
|
|
internally (multiple times). We'd like to avoid this, to get a
|
|
non-recursive implementation. This requires some major restatements.
|
|
|
|
Let's look at the calls to the default ``[[DefineOwnProperty]]`` (other
|
|
than step 5) to see what could be done about them.
|
|
|
|
First, for ``P`` == ``length``:
|
|
|
|
* Step 3.a.1:
|
|
If ``Desc.[[Value]]`` is absent, call the default algorithm.
|
|
|
|
This is equivalent to:
|
|
|
|
- Jumping to step 5.
|
|
|
|
* Step 3.f.1:
|
|
If ``newLen`` validation succeeds and new length is not shorter
|
|
than previous, call the default algorithm with a modified
|
|
property descriptor, ``newLenDesc``. The new property descriptor
|
|
is a copy of the original, with ``[[Value]]`` changed to the
|
|
normalized and numeric (32-bit unsigned integer) length value.
|
|
|
|
This is equivalent to:
|
|
|
|
+ Doing length validation and coercion
|
|
|
|
+ Checking that the new length is not shorter than previous;
|
|
and if so, forcing ``Desc.[[Value]]`` to ``newLen``, and
|
|
then jumping to step 5.
|
|
|
|
+ Note: the caller's view of ``Desc`` must not change, so ``Desc``
|
|
cannot be a "pass by reference" value.
|
|
|
|
* Step 3.f.j:
|
|
Here ``newLen`` validation has succeeded, and the new length is shorter
|
|
than previous. Also, ``Desc.[[Writable]]`` may have been fudged.
|
|
The changes so far are "committed" to ``"length"`` property using the
|
|
default call.
|
|
|
|
Note that this call also has the important effect of checking that
|
|
the default algorithm is expected to succeed before we touch any of
|
|
the array elements.
|
|
|
|
This is equivalent to:
|
|
|
|
+ Doing the ``newWritable`` fudging to ``Desc``, and keeping
|
|
``newWritable`` for later.
|
|
|
|
+ Jumping to step 5.
|
|
|
|
+ Adding a post-step to the default algorithm for steps 3.k - 3.m.
|
|
|
|
* Step 3.l.3.c:
|
|
Here we've started to "shorten" the array but run into a non-deletable
|
|
element. The ``"length"`` property is updated with the actual final
|
|
length, and ``Desc.[[Writable]]`` is fudged back to its original,
|
|
requested value.
|
|
|
|
This is equivalent to:
|
|
|
|
+ Fudging both ``[[Value]]`` and ``[[Writable]]`` of ``Desc``.
|
|
|
|
+ Jumping to step 5.
|
|
|
|
* Step 3.m:
|
|
Here a pending write protection is finally implemented by calling
|
|
the default ``[[DefineOwnProperty]]`` with a property descriptor
|
|
requesting only that the property be changed to non-writable.
|
|
|
|
This is equivalent to:
|
|
|
|
+ Adding a "pending write protect" flag and jumping to 5.
|
|
|
|
+ Modifying the standard algorithm to recognize a "pending
|
|
write protect" after standard property modifications and
|
|
checks are complete.
|
|
|
|
Then, for the case when ``P`` is a valid array index:
|
|
|
|
* Step 4.c:
|
|
The index has been coerced and validated; the algorithm rejects if the
|
|
array index would require that the array ``length`` be increased but
|
|
``length`` is write protected.
|
|
|
|
This is equivalent to:
|
|
|
|
+ Doing the pre-checks for index vs. ``length``.
|
|
|
|
+ Jumping to step 5.
|
|
|
|
+ Adding a post-step to the standard algorithm to handle steps 4.d - 4.f.
|
|
|
|
* Step 4.e.2:
|
|
This is a step which happens after the default algorithm has finished
|
|
without errors. If so, and the array index extended the array ``length``,
|
|
the array ``length`` is updated to reflect this. This is expected to
|
|
always succeed.
|
|
|
|
This is equivalent to:
|
|
|
|
+ Adding a post-step to the standard algorithm.
|
|
|
|
A draft of modifications to the standard algorithm to avoid recursive
|
|
calls could be something like:
|
|
|
|
* Add a pre-step with:
|
|
|
|
+ Check for ``P`` == ``length``, and:
|
|
|
|
- If ``Desc.[[Value]]`` missing, use default algorithm
|
|
|
|
- ``newLen`` validation and updating of ``Desc.[[Value]]``
|
|
|
|
- If new length is not shorter than old length, default algorithm
|
|
with the modified ``Desc`` can be used
|
|
|
|
- Possible fudging of ``Desc.[[Writable]]`` and check for
|
|
setting ``pendingWriteProtect`` (set if ``newWritable``
|
|
is ``false``)
|
|
|
|
- If new length is shorter than old length, run the default
|
|
algorithm successfully first before touching array elements
|
|
|
|
+ Check for ``P`` being a valid array index, and:
|
|
|
|
- Pre-checks for index vs. ``length``
|
|
|
|
* Modify the standard algorithm:
|
|
|
|
+ Continuing with the post-step if the standard algorithm succeeds.
|
|
|
|
* Add a post-step with:
|
|
|
|
+ Check whether we have a pending array "shortening", i.e.
|
|
``P`` was ``"length"``, and the new length is shorter than
|
|
old.
|
|
|
|
- A complex algorithm for shortening the array needs to run.
|
|
This algorithm may either indicate success or failure, and
|
|
returns the actual final length of the array which may
|
|
differ from the requested one if a non-configurable element
|
|
prevents deletion.
|
|
|
|
+ Check for ``pendingWriteProtect``; if so, write protect the
|
|
target property (this is for step 3.m).
|
|
|
|
+ Check whether ``P`` was an array index which should increase
|
|
the length of the array.
|
|
|
|
- If so, we've already checked in the pre-step that the length
|
|
can be updated. So, update the pending new length value.
|
|
|
|
The algorithm for shortening the array is not inlined (it is a separate
|
|
helper in the implementation too) as it's relatively tricky. It is
|
|
instead isolated into ``ShortenArray()`` internal helper with inputs:
|
|
|
|
* old length
|
|
* new length
|
|
|
|
and outputs:
|
|
|
|
* success flag (``false`` if some element couldn't be deleted)
|
|
* final array length to be updated into ``"length"`` property
|
|
|
|
Adding ``Array`` object special behavior
|
|
::::::::::::::::::::::::::::::::::::::::
|
|
|
|
Incorporating the approach for adding a pre- and post-processing phase
|
|
we get something like:
|
|
|
|
1. Set ``pendingWriteProtect`` to ``false``.
|
|
|
|
2. If ``O`` is not an ``Array`` object, goto SKIPARRAY.
|
|
|
|
3. 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.
|
|
|
|
4. Let ``oldLen`` be ``oldLenDesc.[[Value]]``.
|
|
(Note that ``oldLen`` is guaranteed to be a unsigned 32-bit integer.)
|
|
|
|
5. 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.)
|
|
|
|
6. 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.)
|
|
|
|
7. **SKIPARRAY**:
|
|
Let ``current`` be the result of calling the ``[[GetOwnProperty]]``
|
|
internal method of ``O`` with property name ``P``.
|
|
|
|
8. Let ``extensible`` be the value of the ``[[Extensible]]`` internal
|
|
property of ``O``.
|
|
|
|
9. 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.
|
|
|
|
10. 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.)
|
|
|
|
11. 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.
|
|
|
|
12. If ``IsGenericDescriptor(Desc)`` is ``true``, then goto VALIDATED.
|
|
|
|
13. 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.
|
|
|
|
14. 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.
|
|
|
|
15. 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.
|
|
|
|
16. **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.
|
|
|
|
17. **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.
|
|
|
|
18. Return ``true``.
|
|
|
|
19. **REJECT**:
|
|
If ``Throw`` is ``true``, then throw a ``TypeError`` exception,
|
|
otherwise return ``false``.
|
|
|
|
20. **REJECTRANGE**:
|
|
Throw a ``RangeError`` exception. Note that this is unconditional
|
|
(thrown even if ``Throw`` is ``false``).
|
|
|
|
Adding arguments object special behavior
|
|
::::::::::::::::::::::::::::::::::::::::
|
|
|
|
The special ``[[DefineOwnProperty]]`` behavior for an arguments object
|
|
containing a ``[[ParameterMap]]`` is described in E5 Section 10.6.
|
|
|
|
The variant algorithm essentially first runs the default algorithm.
|
|
If the default algorithm finishes successfully, the variant will then
|
|
maintain the parameter map and possibly perform a setter call.
|
|
|
|
This is easy to incorporate and results in:
|
|
|
|
1. Set ``pendingWriteProtect`` to ``false``.
|
|
|
|
2. If ``O`` is not an ``Array`` object, goto SKIPARRAY.
|
|
|
|
3. 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.
|
|
|
|
4. Let ``oldLen`` be ``oldLenDesc.[[Value]]``.
|
|
(Note that ``oldLen`` is guaranteed to be a unsigned 32-bit integer.)
|
|
|
|
5. 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.)
|
|
|
|
6. 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.)
|
|
|
|
7. **SKIPARRAY**:
|
|
Let ``current`` be the result of calling the ``[[GetOwnProperty]]``
|
|
internal method of ``O`` with property name ``P``.
|
|
|
|
8. Let ``extensible`` be the value of the ``[[Extensible]]`` internal
|
|
property of ``O``.
|
|
|
|
9. 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.
|
|
|
|
10. 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.)
|
|
|
|
11. 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.
|
|
|
|
12. If ``IsGenericDescriptor(Desc)`` is ``true``, then goto VALIDATED.
|
|
|
|
13. 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.
|
|
|
|
14. 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.
|
|
|
|
15. 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.
|
|
|
|
16. **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.
|
|
|
|
17. **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.
|
|
|
|
18. 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.)
|
|
|
|
19. Return ``true``.
|
|
|
|
20. **REJECT**:
|
|
If ``Throw`` is ``true``, then throw a ``TypeError`` exception,
|
|
otherwise return ``false``.
|
|
|
|
21. **REJECTRANGE**:
|
|
Throw a ``RangeError`` exception. Note that this is unconditional
|
|
(thrown even if ``Throw`` is ``false``).
|
|
|
|
Final version
|
|
:::::::::::::
|
|
|
|
(See above, currently no additional cleanup.)
|
|
|
|
Delete
|
|
------
|
|
|
|
Related E5 sections:
|
|
|
|
* E5 Section 8.12.7: default algorithm
|
|
* E5 Section 10.5: arguments object
|
|
|
|
Default algorithm
|
|
:::::::::::::::::
|
|
|
|
1. Let ``desc`` be the result of calling the ``[[GetOwnProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
2. If ``desc`` is ``undefined``, then return ``true``.
|
|
|
|
3. If ``desc.[[Configurable]]`` is ``true``, then
|
|
|
|
a. Remove the own property with name ``P`` from ``O``.
|
|
|
|
b. Return ``true``.
|
|
|
|
4. Else if ``Throw`` is true, then throw a ``TypeError`` exception.
|
|
|
|
5. Return ``false``.
|
|
|
|
Adding arguments object special behavior
|
|
::::::::::::::::::::::::::::::::::::::::
|
|
|
|
The special ``[[Delete]]`` behavior for an arguments object containing a
|
|
``[[ParameterMap]]`` is described in E5 Section 10.6.
|
|
|
|
The variant algorithm essentially first runs the default algorithm.
|
|
If the default algorithm finishes successfully, the variant will then
|
|
possibly delete a magic variable binding.
|
|
|
|
This is easy to incorporate and results in:
|
|
|
|
1. Let ``desc`` be the result of calling the ``[[GetOwnProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
2. If ``desc`` is ``undefined``, then goto SUCCESS.
|
|
|
|
3. If ``desc.[[Configurable]]`` is ``true``, then
|
|
|
|
a. Remove the own property with name ``P`` from ``O``.
|
|
|
|
b. Goto SUCCESS.
|
|
|
|
4. Else if ``Throw`` is true, then throw a ``TypeError`` exception.
|
|
|
|
5. Return ``false``.
|
|
|
|
6. **SUCCESS:**
|
|
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:
|
|
|
|
a. Call the ``[[Delete]]`` internal method of ``map`` passing ``P``,
|
|
and ``false`` as the arguments. (This removes the magic binding
|
|
for ``P``.)
|
|
|
|
7. Return ``true``.
|
|
|
|
Notes:
|
|
|
|
* In steps 2, if ``desc`` is ``undefined``, it seems unnecessary to go to
|
|
step 6 to check the arguments parameter map. Can a magically bound
|
|
property exist in the parameter map with the underlying property having
|
|
been deleted somehow?
|
|
|
|
Final version
|
|
:::::::::::::
|
|
|
|
(See above, currently no additional cleanup.)
|
|
|
|
HasInstance
|
|
-----------
|
|
|
|
Background
|
|
::::::::::
|
|
|
|
The ``[[HasInstance]]`` internal method is referred to in the following
|
|
parts of the E5 specification:
|
|
|
|
* Section 8.6.2: ``[[HasInstance]]`` is introduced as a ``SpecOp(any)``
|
|
-> ``Boolean`` internal method. Only ``Function`` objects have a
|
|
``[[HasInstance]]`` method.
|
|
|
|
* Section 11.8.6: the ``instanceof`` operator, which is the only "caller"
|
|
for ``[[HasInstance]]`` in the E5 specification.
|
|
|
|
* Section 13.2: when ``Function`` objects are created, ``[[HasInstance]]``
|
|
is set to the algorithm in Section 15.3.5.3.
|
|
|
|
* Section 15.3.4.5: when bound functions are created using
|
|
``Function.prototype.bind()``, ``[[HasInstance]]`` is set to the
|
|
algorithm in Section 15.3.4.5.3.
|
|
|
|
* Section 15.3.4.5.3: ``[[HasInstance]]`` for bound functions.
|
|
|
|
* Section 15.3.5.3: ``[[HasInstance]]`` for ordinary (non-bound)
|
|
functions.
|
|
|
|
The ``[[HasInstance]]`` for ordinary functions is (``F`` is the function
|
|
object and ``V`` is the argument value, "V instanceof F"):
|
|
|
|
1. If ``Type(V)`` is not an ``Object``, return ``false``.
|
|
|
|
2. Let ``O`` be the result of calling the ``[[Get]]`` internal method of
|
|
``F`` with property name ``"prototype"``.
|
|
(Note: this is the external prototype, not the internal one.)
|
|
|
|
3. If ``Type(O)`` is not ``Object``, throw a ``TypeError`` exception.
|
|
|
|
4. Repeat
|
|
|
|
a. Let ``V`` be the value of the ``[[Prototype]]`` internal property of
|
|
``V``.
|
|
|
|
b. If ``V`` is ``null``, return ``false``.
|
|
|
|
c. If ``O`` and ``V`` refer to the same object, return ``true``.
|
|
|
|
Notes:
|
|
|
|
* In step 2, we're fetching the *external prototype*, which may have any
|
|
values. It might also have been changed after the instance was created.
|
|
|
|
* Step 4.a steps the internal prototype chain once before the first check.
|
|
|
|
The ``[[HasInstance]]`` for bound functions is:
|
|
|
|
1. Let ``target`` be the value of ``F``\ ’s ``[[TargetFunction]]`` internal
|
|
property.
|
|
|
|
2. If ``target`` has no ``[[HasInstance]]`` internal method, a ``TypeError``
|
|
exception is thrown.
|
|
|
|
3. Return the result of calling the ``[[HasInstance]]`` internal method of
|
|
``target`` providing ``V`` as the argument.
|
|
|
|
Notes:
|
|
|
|
* In step 3, the ``target`` may be another bound function, so we may need
|
|
to follow an arbitrary number of bound functions before ending up with an
|
|
actual function object.
|
|
|
|
Combined algorithm
|
|
::::::::::::::::::
|
|
|
|
The two ``[[HasInstance]]`` methods (for bound and non-bound functions)
|
|
can be combined to yield:
|
|
|
|
1. While ``F`` is a bound function:
|
|
|
|
a. Set ``F`` to the value of ``F``\ 's ``[[TargetFunction]]`` internal
|
|
property.
|
|
|
|
b. If ``F`` has no ``[[HasInstance]]`` internal method, throw a
|
|
``TypeError`` exception.
|
|
(Note: ``F`` can be another bound function, so we loop until we find
|
|
the non-bound actual function.)
|
|
|
|
2. If ``Type(V)`` is not an ``Object``, return ``false``.
|
|
|
|
3. Let ``O`` be the result of calling the ``[[Get]]`` internal method of
|
|
``F`` with property name ``"prototype"``.
|
|
(Note: this is the external prototype, not the internal one.)
|
|
|
|
4. If ``Type(O)`` is not ``Object``, throw a ``TypeError`` exception.
|
|
|
|
5. Repeat
|
|
|
|
a. Let ``V`` be the value of the ``[[Prototype]]`` internal property of
|
|
``V``.
|
|
|
|
b. If ``V`` is ``null``, return ``false``.
|
|
|
|
c. If ``O`` and ``V`` refer to the same object, return ``true``.
|
|
|
|
Final version
|
|
:::::::::::::
|
|
|
|
(See above, currently no additional cleanup.)
|
|
|
|
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 special behavior for ``[[GetProperty]]``, the special 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 special 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 special behaviors are missing.
|
|
|
|
GetProperty with complete GetOwnProperty inlined
|
|
::::::::::::::::::::::::::::::::::::::::::::::::
|
|
|
|
The following inlines ``[[GetOwnProperty]]`` with all special 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 special 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 special 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 special 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
|
|
special behaviors:
|
|
|
|
1. If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special 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 special 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 special
|
|
behavior or ``"caller"`` property special 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 special behavior or
|
|
``"caller"`` property special 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 special behavior for mapped variables cannot
|
|
apply: if the property is an accessor, it can never be in the arguments
|
|
object ``[[ParameterMap]]``. Also, the ``"caller"`` special 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 special behavior for mapped variables cannot
|
|
apply: if the property is an accessor, it can never be in the arguments
|
|
object ``[[ParameterMap]]``. However, the ``"caller"`` special behavior
|
|
might apply, at FOUND2.)
|
|
|
|
6. **FOUND1**:
|
|
If ``curr`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special 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 special 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 special 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 special ``[[DefineOwnProperty]]`` relies on the default one, E5
|
|
Section 15.4.5.1
|
|
|
|
* Argument object's special ``[[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 special 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`` special 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 special 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 special
|
|
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 special 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 special
|
|
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.
|
|
|
|
GETPROP: exposed property get algorithm
|
|
=======================================
|
|
|
|
Background
|
|
----------
|
|
|
|
Consider the following expression::
|
|
|
|
x = y[z]
|
|
|
|
The following happens compile time:
|
|
|
|
* ``z`` is parsed as an identifier reference
|
|
|
|
* ``y`` is parsed as an identifier reference
|
|
|
|
* ``y[z]`` is parsed as a property accessor (E5 Section 11.2.1)
|
|
|
|
* When the simple assignment is parsed, the ``y[z]`` compiler knows that
|
|
the property accessor is used as a right-hand-side value, so it emits
|
|
whatever internal bytecode is required to read the property value
|
|
during execution
|
|
|
|
The following happens run time:
|
|
|
|
* The compiled code contains the sequence described in E5 Section 11.2.1:
|
|
|
|
+ ``baseValue = GetValue(y)``, where ``y`` is the identifier reference
|
|
|
|
+ ``propertyNameValue = GetValue(z)``, where ``z`` is the identifier reference
|
|
|
|
+ ``CheckObjectCoercible(baseValue)``, which throws a ``TypeError`` if the
|
|
``baseValue`` is ``null`` or ``undefined``
|
|
|
|
+ Create a property reference with ``baseValue`` as the base reference and
|
|
``ToString(propertyNameValue)`` as the property name (and strict flag
|
|
based on current code strictness)
|
|
|
|
* Call ``GetValue()`` for the property reference. This results in the
|
|
following sub-steps of E5 Section 8.7.1 to be executed:
|
|
|
|
+ ``base`` is the result of ``GetValue(y)`` (identifier lookup result
|
|
directly)
|
|
|
|
+ The referenced name is ``ToString(GetValue(z))`` (identifier lookup
|
|
result with coercion)
|
|
|
|
+ If ``base`` is not a primitive: use ``[[Get]]`` directly for
|
|
``base`` and the referenced name
|
|
|
|
+ Else use a variant for ``[[Get]]``
|
|
|
|
The ``[[Get]]`` variant for a primitive base is specified explicitly in
|
|
E5 Section 8.7.1. This seems a bit odd, as it seems equivalent to:
|
|
|
|
* Let ``O`` be ``ToObject(base)``
|
|
|
|
* Call ``[[Get]]`` for ``O`` and referenced name
|
|
|
|
However, *this is not the case*. There is a subtle difference in the case
|
|
that the property is an accessor. Normally the ``this`` binding for the
|
|
getter is the object given to ``[[Get]]``. Here the ``this`` binding is
|
|
the *uncoerced primitive value*.
|
|
|
|
This leads to externally visible behavior, illustrated in the following::
|
|
|
|
// add test getter
|
|
Object.defineProperty(String.prototype, 'test', {
|
|
get: function() { print(typeof this); },
|
|
set: function(x) { print(typeof this); },
|
|
});
|
|
|
|
"foo".test; // prints 'string'
|
|
|
|
var s = new String("foo");
|
|
s.test; // prints 'object'
|
|
|
|
Behavior in Ecmascript implementations seems to vary:
|
|
|
|
* NodeJS / V8: prints 'string' and 'object' as expected
|
|
* Rhino: prints 'object' and 'object'
|
|
* Smjs: prints 'object' and 'object'
|
|
|
|
``GetValue()`` allows the caller to skip creation of the coerced object
|
|
(which is one of: a ``Boolean``, a ``Number``, or a ``String``; see E5
|
|
Section 9.9, ``ToObject()``).
|
|
|
|
Note: the replacement ``[[Get]]`` overrides whatever ``[[Get]]`` function
|
|
would normally be used for the target object. For instance, if there were
|
|
some primitive-to-object coercion which created an arguments object, the
|
|
arguments object special ``[[Get]]`` behavior would be skipped. However,
|
|
since the arguments and ``Function`` objects are the only objects with
|
|
non-default ``[[Get]]``, this is not an issue in practice.
|
|
|
|
First draft
|
|
-----------
|
|
|
|
When the property accessor is created, the base reference and property
|
|
name are "coerced" to a value using ``GetValue()``. In the example
|
|
above, this causes ``x``\ 's and ``foo``\ 's values to be looked up.
|
|
These correspond to steps 1-4 of the property accessor expression in
|
|
E5 Section 11.2.1. When compiling, these are converted into whatever
|
|
code is necessary to fetch the two values into VM registers.
|
|
|
|
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 ``GetValue()``, and ultimately ``[[Get]]`` 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 getter call.)
|
|
|
|
2. Call ``CheckObjectCoercible`` with ``O`` as argument. In practice: if
|
|
``O`` is ``null`` or ``undefined``, throw a ``TypeError``.
|
|
|
|
3. Let ``P`` be ``ToString(P)``.
|
|
(This may have side effects if ``P`` is an object.)
|
|
|
|
4. Let ``O`` be ``ToObject(O)``.
|
|
(This is side effect free.)
|
|
|
|
5. If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special 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.
|
|
|
|
6. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
7. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
8. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
9. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
c. Else let ``res`` be the result of calling the ``[[Call]]`` internal
|
|
method of ``getter`` providing ``orig`` as the ``this`` value and
|
|
providing no arguments.
|
|
(Note: the difference to a basic ``[[Get]]`` is that the getter ``this``
|
|
binding is the original, uncoerced object.)
|
|
|
|
10. If ``orig`` is a ``Function`` object or an ``arguments`` object which
|
|
contains a ``[[ParameterMap]]`` internal property:
|
|
|
|
a. (Arguments or Function object special behavior.)
|
|
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
|
|
object, throw a ``TypeError`` exception.
|
|
|
|
11. Return ``res``.
|
|
|
|
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 can be reversed.
|
|
|
|
* Step 4 comes from ``GetValue()``.
|
|
|
|
* Steps 5 and forward come from ``[[Get]]``; here with special behaviors
|
|
inlined, but ``[[GetProperty]]`` not inlined.
|
|
|
|
We could inline the ``[[GetProperty]]`` call to the algorithm. However,
|
|
because the current implementation doesn't do so, that has been omitted
|
|
for now.
|
|
|
|
Improving type checking of base value
|
|
-------------------------------------
|
|
|
|
A variant where steps 3 and 4 are reversed and expanded is as follows:
|
|
|
|
1. Let ``orig`` be ``O``.
|
|
(Remember the uncoerced original for a possible getter call.)
|
|
|
|
2. Check and/or coerce ``O`` as follows:
|
|
|
|
a. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``.
|
|
(This is the ``CheckObjectCoercible`` part.)
|
|
|
|
b. Else if ``O`` is a boolean, a number, or a string, set ``O`` to
|
|
``ToObject(O)``.
|
|
|
|
c. Else if ``O`` is an object, do nothing.
|
|
|
|
d. Throw a ``TypeError``.
|
|
(Note that this case should not happen, as steps a-c are exhaustive.
|
|
However, this step is useful as a fallback, and for handling any
|
|
internal types.)
|
|
|
|
3. Let ``P`` be ``ToString(P)``.
|
|
(This may have side effects if ``P`` is an object.)
|
|
|
|
4. If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special 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.
|
|
|
|
5. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
6. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
7. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
8. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
c. Else let ``res`` be the result of calling the ``[[Call]]`` internal
|
|
method of ``getter`` providing ``orig`` as the ``this`` value and
|
|
providing no arguments.
|
|
(Note: the difference to a basic ``[[Get]]`` is that the getter ``this``
|
|
binding is the original, uncoerced object.)
|
|
|
|
9. If ``orig`` is a ``Function`` object or an ``arguments`` object which
|
|
contains a ``[[ParameterMap]]`` internal property:
|
|
|
|
a. (Arguments or Function object special behavior.)
|
|
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
|
|
object, throw a ``TypeError`` exception.
|
|
|
|
10. Return ``res``.
|
|
|
|
Avoiding temporary objects
|
|
--------------------------
|
|
|
|
If the base value is not an object, step 4 in the above algorithm creates
|
|
a temporary object given to ``[[GetProperty]]`` for a property descriptor
|
|
lookup. The first object in the prototype chain is the temporary object,
|
|
while the rest are already established non-temporary objects.
|
|
|
|
If we knew that the property ``P`` could never be an *own property* of the
|
|
temporary object, we could skip creation of the temporary object altogether.
|
|
Instead, we could simply start ``[[GetProperty]]`` from the internal
|
|
prototype that the coerced object would get without actually creating the
|
|
object.
|
|
|
|
Since the coerced object is created by ``ToObject`` from a primitive value,
|
|
we know that it is a ``Boolean`` instance, a ``Number`` instance, or a ``String``
|
|
instance (see E5 Section 9.9). The "own properties" of these are:
|
|
|
|
* ``Boolean``: none
|
|
* ``Number``: none
|
|
* ``String``: ``"length"`` and index properties for string characters
|
|
|
|
So, the coercion can be skipped safely for everything except ``String``\ s.
|
|
This is unfortunate, because it is conceivably the string primitive value
|
|
which is most likely to be accessed through a coercion, e.g. as in::
|
|
|
|
var t = "my string";
|
|
print(t.length);
|
|
|
|
In any case, avoiding temporary creation for everything but ``Strings``
|
|
can be worked into the algorithm e.g. as follows:
|
|
|
|
1. Let ``orig`` be ``O``.
|
|
(Remember the uncoerced original fora possible getter call.)
|
|
|
|
2. Check and/or coerce ``O`` as follows:
|
|
|
|
a. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``.
|
|
(This is the ``CheckObjectCoercible`` part.)
|
|
|
|
b. If ``O`` is a boolean: set ``O`` to the built-in ``Boolean``
|
|
prototype object (skip creation of temporary)
|
|
|
|
c. Else if ``O`` is a number: set ``O`` to the built-in ``Number``
|
|
prototype object (skip creation of temporary)
|
|
|
|
d. Else if ``O`` is a string, set ``O`` to ``ToObject(O)``.
|
|
|
|
e. Else if ``O`` is an object, do nothing.
|
|
|
|
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.)
|
|
|
|
3. Let ``P`` be ``ToString(P)``.
|
|
(This may have side effects if ``P`` is an object.)
|
|
|
|
4. If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special 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.
|
|
|
|
5. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
6. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
7. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
8. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
c. Else let ``res`` be the result of calling the ``[[Call]]`` internal
|
|
method of ``getter`` providing ``orig`` as the ``this`` value and
|
|
providing no arguments.
|
|
(Note: the difference to a basic ``[[Get]]`` is that the getter ``this``
|
|
binding is the original, uncoerced object.)
|
|
|
|
9. If ``orig`` is a ``Function`` object or an ``arguments`` object which
|
|
contains a ``[[ParameterMap]]`` internal property:
|
|
|
|
a. (Arguments or Function object special behavior.)
|
|
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
|
|
object, throw a ``TypeError`` exception.
|
|
|
|
10. Return ``res``.
|
|
|
|
If we change step 2.d to get the related string value (length or character
|
|
of the string) directly, no temporaries need to be created due to coercion.
|
|
However, if the property name ``P`` is checked, it needs to be string coerced
|
|
which happens only later in step 3. If we add a separate coercion to step 2.d,
|
|
``P`` will be coerced twice unless step 3 is then explicitly skipped; this is
|
|
not an issue as the latter coercion is a NOP and can in any case be easily
|
|
skipped.
|
|
|
|
This variant is as follows:
|
|
|
|
1. Let ``orig`` be ``O``.
|
|
(Remember the uncoerced original for a possible getter call.)
|
|
|
|
2. Check and/or coerce ``O`` as follows:
|
|
|
|
a. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``.
|
|
(This is the ``CheckObjectCoercible`` part.)
|
|
|
|
b. If ``O`` is a boolean: set ``O`` to the built-in ``Boolean``
|
|
prototype object (skip creation of temporary)
|
|
|
|
c. Else if ``O`` is a number: set ``O`` 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``, return the length of the primitive string
|
|
value as a number.
|
|
|
|
3. If ``P`` is a valid array index within the string length, return
|
|
a one-character substring of the primitive string value at the
|
|
specified index.
|
|
|
|
4. Else, set ``O`` to the built-in ``String`` prototype object
|
|
(skip creation of temporary)
|
|
|
|
5. Goto LOOKUP. (Avoid double coercion of ``P``.)
|
|
|
|
e. Else if ``O`` is an object, do nothing.
|
|
|
|
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.)
|
|
|
|
3. Let ``P`` be ``ToString(P)``.
|
|
(This may have side effects if ``P`` is an object.)
|
|
|
|
4. **LOOKUP:**
|
|
If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special 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.
|
|
|
|
5. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
6. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
7. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
8. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
c. Else let ``res`` be the result of calling the ``[[Call]]`` internal
|
|
method of ``getter`` providing ``orig`` as the ``this`` value and
|
|
providing no arguments.
|
|
(Note: the difference to a basic ``[[Get]]`` is that the getter ``this``
|
|
binding is the original, uncoerced object.)
|
|
|
|
9. If ``orig`` is a ``Function`` object or an ``arguments`` object which
|
|
contains a ``[[ParameterMap]]`` internal property:
|
|
|
|
a. (Arguments or Function object special behavior.)
|
|
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
|
|
object, throw a ``TypeError`` exception.
|
|
|
|
10. Return ``res``.
|
|
|
|
Fast path for array indices
|
|
---------------------------
|
|
|
|
When the property name is a number and a *valid array index*, we'd prefer
|
|
to be able to lookup the property without coercing the number to a string.
|
|
This "fast path" needs to work for the common cases; rare cases can go
|
|
through the ordinary algorithm which requires a ``ToString()`` coercion.
|
|
|
|
There are many ways to do a (compliant) fast path. The simple case we're
|
|
considering here is the case when the target object has an "own property"
|
|
matching the property name (a number).
|
|
|
|
A simple "shallow fast path" could be:
|
|
|
|
* If ``P`` is a whole number in the range [0,2**32-2] (a valid array index)
|
|
AND ``O`` has an array part
|
|
AND ``O`` has no conflicting "special behaviors", then:
|
|
|
|
+ Let ``idx`` be the array index represented by ``P``
|
|
|
|
+ If the array part of ``O`` contains ``idx`` and the key exists,
|
|
read and return the value. Note that the value can be ``undefined``
|
|
|
|
* Else use normal algorithm.
|
|
|
|
Some notes:
|
|
|
|
* The behavior of the fast path must match the behavior of the normal
|
|
algorithm exactly (including side effects). This should be the case
|
|
here, but can be verified by simulating the normal algorithm with the
|
|
assumption of a number as a property name, with the target property
|
|
present as an "own data property" of the target object.
|
|
|
|
* The conflicting special behaviors are currently: ``String`` object special
|
|
behavior, and arguments object special behavior. Array special behaviors
|
|
are not conflicting for read operations.
|
|
|
|
* A certain key in the array can be defined even if the value is ``undefined``.
|
|
The check is whether the key has been defined, i.e. ``[[HasProperty]]``
|
|
would be true. Internally, the value "undefined unused" is used to denote
|
|
unused entries with unused keys, while the value "undefined actual"
|
|
represents an undefined value with a defined key. For instance, the
|
|
following defines an array key::
|
|
|
|
var a = [];
|
|
a[10] = undefined; // "10" will now enumerate
|
|
|
|
* The fast path avoids the ``ToString()`` coercion which *may*, in general,
|
|
have side effects (at least for objects). However, the fast path only
|
|
applies if ``P`` is a number, and the ``ToString()`` coercion of a number
|
|
is side effect free.
|
|
|
|
* If the array part does *not* contain the key, the normal algorithm is
|
|
always used, regardless of whether the ancestors contain the key or not.
|
|
This means that if a non-existent key is accessed from the array (even
|
|
if the index is within the current array length), string interning will
|
|
be required with this fast path. For instance::
|
|
|
|
var a = [];
|
|
a[0] = 'foo';
|
|
a[2] = 'bar';
|
|
|
|
// fast path ok, no string interning
|
|
print(a[0]);
|
|
|
|
// fast path fails, string interned but still not found
|
|
print(a[1]);
|
|
|
|
Inlining the above shallow fast path with the variant which avoids temporaries
|
|
altogether produces:
|
|
|
|
1. Let ``orig`` be ``O``.
|
|
(Remember the uncoerced original for a possible getter call.)
|
|
|
|
2. Check and/or coerce ``O`` as follows:
|
|
|
|
a. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``.
|
|
(This is the ``CheckObjectCoercible`` part.)
|
|
|
|
b. If ``O`` is a boolean: set ``O`` to the built-in ``Boolean``
|
|
prototype object (skip creation of temporary)
|
|
|
|
c. Else if ``O`` is a number: set ``O`` 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``, return the length of the primitive string
|
|
value as a number.
|
|
|
|
3. If ``P`` is a valid array index within the string length, return
|
|
a one-character substring of the primitive string value at the
|
|
specified index.
|
|
|
|
4. Else, set ``O`` to the built-in ``String`` prototype object
|
|
(skip creation of temporary)
|
|
|
|
5. Goto LOOKUP. (Avoid double coercion of ``P``.)
|
|
|
|
e. Else if ``O`` is an object:
|
|
|
|
1. Array fast path: If ``O`` is an object (always true here)
|
|
AND ``P`` is a number and a valid array index (whole number in [0,2**32-2])
|
|
AND ``O`` internal representation has an array part
|
|
AND ``O`` does not have conflicting special behaviors (cannot have
|
|
``String`` or arguments special behaviors, may have ``Array``
|
|
behavior), then:
|
|
|
|
a. Let ``idx`` be the array index represented by ``P``
|
|
|
|
b. If the array part of ``O`` contains ``idx`` and the key exists,
|
|
read and return that value.
|
|
(Note: ``ToString(P)`` is skipped, but it would have no side
|
|
effects as ``P`` is a number. The ``"caller"`` check for ``P``
|
|
is also skipped, but it would never match because ``P`` is a
|
|
number.)
|
|
|
|
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.)
|
|
|
|
3. Let ``P`` be ``ToString(P)``.
|
|
(This may have side effects if ``P`` is an object.)
|
|
|
|
4. **LOOKUP:**
|
|
If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special 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.
|
|
|
|
5. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
6. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
7. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
8. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
c. Else let ``res`` be the result of calling the ``[[Call]]`` internal
|
|
method of ``getter`` providing ``orig`` as the ``this`` value and
|
|
providing no arguments.
|
|
(Note: the difference to a basic ``[[Get]]`` is that the getter ``this``
|
|
binding is the original, uncoerced object.)
|
|
|
|
9. If ``orig`` is a ``Function`` object or an ``arguments`` object which
|
|
contains a ``[[ParameterMap]]`` internal property:
|
|
|
|
a. (Arguments or Function object special behavior.)
|
|
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
|
|
object, throw a ``TypeError`` exception.
|
|
|
|
10. Return ``res``.
|
|
|
|
We can further improve this by adding a fast path for the case where ``O``
|
|
is a primitive string (in step 2.d):
|
|
|
|
1. Let ``orig`` be ``O``.
|
|
(Remember the uncoerced original fora possible getter call.)
|
|
|
|
2. 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 ``O`` to the built-in ``Boolean``
|
|
prototype object (skip creation of temporary)
|
|
|
|
c. Else if ``O`` is a number: set ``O`` to the built-in ``Number``
|
|
prototype object (skip creation of temporary)
|
|
|
|
d. Else if ``O`` is a string:
|
|
|
|
1. If ``P`` is a number, is a whole number, a valid array index, and
|
|
within the string length, return a one-character substring of the
|
|
primitive string value at the specified index.
|
|
(Note: ``ToString(P)`` is skipped, but it would have no side
|
|
effects as ``P`` is a number. The ``"caller"`` check for ``P``
|
|
is also skipped, but it would never match because ``P`` is a
|
|
number.)
|
|
|
|
2. Set ``P`` to ``ToString(P)``.
|
|
(This may have side effects if ``P`` is an object.)
|
|
|
|
3. If ``P`` is ``length``, return the length of the primitive string
|
|
value as a number.
|
|
(Note: The ``"caller"`` check for ``P`` is skipped, but would
|
|
never match.)
|
|
|
|
4. If ``P`` is a valid array index within the string length, return
|
|
a one-character substring of the primitive string value at the
|
|
specified index.
|
|
(Note: The ``"caller"`` check for ``P`` is skipped, but would
|
|
never match.)
|
|
|
|
5. Else, set ``O`` to the built-in ``String`` prototype object
|
|
(skip creation of temporary)
|
|
|
|
6. Goto LOOKUP. (Avoid double coercion of ``P``.)
|
|
|
|
e. Else if ``O`` is an object:
|
|
|
|
1. Array fast path: If ``O`` is an object (always true here)
|
|
AND ``P`` is a number and a valid array index (whole number in [0,2**32-2])
|
|
AND ``O`` internal representation has an array part
|
|
AND ``O`` does not have conflicting special behaviors (cannot have
|
|
``String`` or arguments special behaviors, may have ``Array``
|
|
behavior), then:
|
|
|
|
a. Let ``idx`` be the array index represented by ``P``
|
|
|
|
b. If the array part of ``O`` contains ``idx`` and the key exists,
|
|
read and return that value.
|
|
(Note: ``ToString(P)`` is skipped, but it would have no side
|
|
effects as ``P`` is a number. The ``"caller"`` check for ``P``
|
|
is also skipped, but it would never match because ``P`` is a
|
|
number.)
|
|
|
|
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.)
|
|
|
|
3. Let ``P`` be ``ToString(P)``.
|
|
(This may have side effects if ``P`` is an object.)
|
|
|
|
4. **LOOKUP:**
|
|
If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. (Arguments object special 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.
|
|
|
|
5. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
6. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
7. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
8. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
c. Else let ``res`` be the result of calling the ``[[Call]]`` internal
|
|
method of ``getter`` providing ``orig`` as the ``this`` value and
|
|
providing no arguments.
|
|
(Note: the difference to a basic ``[[Get]]`` is that the getter ``this``
|
|
binding is the original, uncoerced object.)
|
|
|
|
9. If ``orig`` is a ``Function`` object or an ``arguments`` object which
|
|
contains a ``[[ParameterMap]]`` internal property:
|
|
|
|
a. (Arguments or Function object special behavior.)
|
|
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
|
|
object, throw a ``TypeError`` exception.
|
|
|
|
10. Return ``res``.
|
|
|
|
We can also move step 4 (arguments special behavior) to step 2.e. This has
|
|
the problem that step 4 assumes ``P`` has been string coerced already. So,
|
|
a duplicate coercion is needed (like for strings):
|
|
|
|
1. Let ``orig`` be ``O``.
|
|
(Remember the uncoerced original for a possible getter call.)
|
|
|
|
2. 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 ``O`` to the built-in ``Boolean``
|
|
prototype object (skip creation of temporary)
|
|
|
|
c. Else if ``O`` is a number: set ``O`` to the built-in ``Number``
|
|
prototype object (skip creation of temporary)
|
|
|
|
d. Else if ``O`` is a string:
|
|
|
|
1. If ``P`` is a number, is a whole number, a valid array index, and
|
|
within the string length, return a one-character substring of the
|
|
primitive string value at the specified index.
|
|
(Note: ``ToString(P)`` is skipped, but it would have no side
|
|
effects as ``P`` is a number. The ``"caller"`` check for ``P``
|
|
is also skipped, but it would never match because ``P`` is a
|
|
number.)
|
|
|
|
2. Set ``P`` to ``ToString(P)``.
|
|
(This may have side effects if ``P`` is an object.)
|
|
|
|
3. If ``P`` is ``length``, return the length of the primitive string
|
|
value as a number.
|
|
(Note: The ``"caller"`` check for ``P`` is skipped, but would
|
|
never match.)
|
|
|
|
4. If ``P`` is a valid array index within the string length, return
|
|
a one-character substring of the primitive string value at the
|
|
specified index.
|
|
(Note: The ``"caller"`` check for ``P`` is skipped, but would
|
|
never match.)
|
|
|
|
5. Set ``O`` to the built-in ``String`` prototype object
|
|
(skip creation of temporary)
|
|
|
|
6. Goto LOOKUP. (Avoid double coercion of ``P``.)
|
|
|
|
e. Else if ``O`` is an object:
|
|
|
|
1. Array fast path: If ``O`` is an object (always true here)
|
|
AND ``P`` is a number and a valid array index (whole number in [0,2**32-2])
|
|
AND ``O`` internal representation has an array part
|
|
AND ``O`` does not have conflicting special behaviors (cannot have
|
|
``String`` or arguments special behaviors, may have ``Array``
|
|
behavior), then:
|
|
|
|
a. Let ``idx`` be the array index represented by ``P``.
|
|
|
|
b. If the array part of ``O`` contains ``idx`` and the key exists,
|
|
read and return that value.
|
|
(Note: ``ToString(P)`` is skipped, but it would have no side
|
|
effects as ``P`` is a number. The ``"caller"`` check for ``P``
|
|
is also skipped, but it would never match because ``P`` is a
|
|
number.)
|
|
|
|
2. If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. Set ``P`` to ``ToString(P)``.
|
|
|
|
b. (Arguments object special behavior.) Let ``map`` be the value of
|
|
the ``[[ParameterMap]]`` internal property of the arguments object.
|
|
|
|
c. 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.
|
|
|
|
d. Else, goto LOOKUP. (Avoid double coercion of ``P``.)
|
|
|
|
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.)
|
|
|
|
3. Let ``P`` be ``ToString(P)``.
|
|
(This may have side effects if ``P`` is an object.)
|
|
|
|
4. **LOOKUP:**
|
|
Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
5. If ``desc`` is ``undefined``, return ``undefined``.
|
|
|
|
6. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
7. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
c. Else let ``res`` be the result of calling the ``[[Call]]`` internal
|
|
method of ``getter`` providing ``orig`` as the ``this`` value and
|
|
providing no arguments.
|
|
(Note: the difference to a basic ``[[Get]]`` is that the getter ``this``
|
|
binding is the original, uncoerced object.)
|
|
|
|
8. If ``orig`` is a ``Function`` object or an ``arguments`` object which
|
|
contains a ``[[ParameterMap]]`` internal property:
|
|
|
|
a. (Arguments or Function object special behavior.)
|
|
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
|
|
object, throw a ``TypeError`` exception.
|
|
|
|
9. Return ``res``.
|
|
|
|
.. note:: The above is the current "shallow fast path" approach, which has a
|
|
couple of annoying limitations. For instance, if the array index
|
|
is not used, the key will be coerced to string (regardless of whether
|
|
ancestors have the key or not). Many improvements are possible;
|
|
these are future work.
|
|
|
|
Inlining GetProperty
|
|
--------------------
|
|
|
|
Inlining ``[[GetProperty]]`` (but not ``[[GetOwnProperty]]``),
|
|
maintaining the original input value in ``O`` instead of ``orig``,
|
|
and using ``curr`` instead of ``O`` otherwise, we get:
|
|
|
|
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. If ``P`` is a number, is a whole number, a valid array index, and
|
|
within the string length, return a one-character substring of the
|
|
primitive string value at the specified index.
|
|
(Note: ``ToString(P)`` is skipped, but it would have no side
|
|
effects as ``P`` is a number. The ``"caller"`` check for ``P``
|
|
is also skipped, but it would never match because ``P`` is a
|
|
number.)
|
|
|
|
2. Set ``P`` to ``ToString(P)``.
|
|
(This may have side effects if ``P`` is an object.)
|
|
|
|
3. If ``P`` is ``length``, return the length of the primitive string
|
|
value as a number.
|
|
(Note: The ``"caller"`` check for ``P`` is skipped, but would
|
|
never match.)
|
|
|
|
4. If ``P`` is a valid array index within the string length, return
|
|
a one-character substring of the primitive string value at the
|
|
specified index.
|
|
(Note: The ``"caller"`` check for ``P`` is skipped, but would
|
|
never match.)
|
|
|
|
5. Set ``curr`` to the built-in ``String`` prototype object
|
|
(skip creation of temporary)
|
|
|
|
6. Goto NEXT. (Avoid double coercion of ``P``.)
|
|
|
|
e. Else if ``O`` is an object:
|
|
|
|
1. Set ``curr`` to ``O``.
|
|
|
|
2. Array fast path: If ``O`` is an object (always true here)
|
|
AND ``P`` is a number and a valid array index (whole number in [0,2**32-2])
|
|
AND ``O`` internal representation has an array part
|
|
AND ``O`` does not have conflicting special behaviors (cannot have
|
|
``String`` or arguments special behaviors, may have ``Array``
|
|
behavior), then:
|
|
|
|
a. Let ``idx`` be the array index represented by ``P``.
|
|
|
|
b. If the array part of ``O`` contains ``idx`` and the key exists,
|
|
read and return that value.
|
|
(Note: ``ToString(P)`` is skipped, but it would have no side
|
|
effects as ``P`` is a number. The ``"caller"`` check for ``P``
|
|
is also skipped, but it would never match because ``P`` is a
|
|
number.)
|
|
|
|
3. If ``O`` is an ``arguments`` object which contains a ``[[ParameterMap]]``
|
|
internal property:
|
|
|
|
a. Set ``P`` to ``ToString(P)``.
|
|
|
|
b. (Arguments object special behavior.) Let ``map`` be the value of
|
|
the ``[[ParameterMap]]`` internal property of the arguments object.
|
|
|
|
c. 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.
|
|
|
|
d. Else, goto NEXT. (Avoid double coercion of ``P``.)
|
|
|
|
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.)
|
|
|
|
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. Return ``undefined``.
|
|
|
|
6. If ``IsDataDescriptor(desc)`` is ``true``:
|
|
|
|
a. Let ``res`` be ``desc.[[Value]]``.
|
|
|
|
7. Otherwise, ``IsAccessorDescriptor(desc)`` must be ``true``:
|
|
|
|
a. Let ``getter`` be ``desc.[[Get]]``.
|
|
|
|
b. If ``getter`` is ``undefined``, return ``undefined``.
|
|
|
|
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.
|
|
(Note: the difference to a basic ``[[Get]]`` is that the getter ``this``
|
|
binding is the original, uncoerced object.)
|
|
|
|
8. If ``O`` is a ``Function`` object or an ``arguments`` object which
|
|
contains a ``[[ParameterMap]]`` internal property:
|
|
|
|
a. (Arguments or Function object special behavior.)
|
|
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
|
|
object, throw a ``TypeError`` exception.
|
|
|
|
9. Return ``res``.
|
|
|
|
Final version
|
|
-------------
|
|
|
|
(See above.)
|
|
|
|
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
|
|
special 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 special 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]]`` special 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 special behavior, as
|
|
only number-like indices have magic bindings (not ``"length"``).
|
|
|
|
* In steps 12-14, we don't need to check for arguments special 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`` special 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 special 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 special 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.)
|
|
|
|
DELPROP: exposed property deletion algorithm ("delete" operator)
|
|
================================================================
|
|
|
|
Background
|
|
----------
|
|
|
|
Properties are deleted in Ecmascript code with the ``delete`` operator, e.g.::
|
|
|
|
delete foo.bar;
|
|
|
|
This involves:
|
|
|
|
* A property accessor reference (E5 Section 11.2.1)
|
|
|
|
* ``delete`` semantics (E5 Section 11.4.1)
|
|
|
|
* A call to ``[[Delete]]``
|
|
|
|
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 ``delete`` expression will then:
|
|
|
|
* Coerce the base value to an object
|
|
|
|
* Call the ``[[Delete]]`` algorithm
|
|
|
|
Note that if the base value is not an object, a temporary object will be
|
|
created by coercion. Since a deletion always operates on the "own
|
|
properties" of an object, the deletion can only have side effects (error
|
|
throwing) side effects. Any other effects will be lost with the temporary
|
|
object. This is discussed in more detail below, for the deletion algorithm.
|
|
|
|
Notes:
|
|
|
|
* ``[[Delete]]`` only checks for the property ``P`` in the original object
|
|
``O``, and does not follow the prototype chain
|
|
|
|
* In particular, an inherited property ``P`` which would prevent a ``[[Put]]``
|
|
does not affect the outcome of ``[[Delete]]``
|
|
|
|
First draft
|
|
-----------
|
|
|
|
Starting from the property accessor, then applying ``delete`` (and skipping any
|
|
unused steps):
|
|
|
|
1. Call ``CheckObjectCoercible`` for the base value. In practice, throw a
|
|
``TypeError`` if the base value is ``null`` or ``undefined``.
|
|
|
|
2. Coerce property name to string using ``ToString()``.
|
|
|
|
3. Coerce base value to object using ``ToObject()`` and call ``[[Delete]]``
|
|
with the coerced object, the coerced key, and a "Throw" flag set if
|
|
the property reference is contained in strict mode code.
|
|
|
|
More formally, suppose ``O`` is the base value, ``P`` is the property name
|
|
value, and ``currStrict`` is ``true`` if the property deletion expression
|
|
occurred in strict code:
|
|
|
|
1. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``
|
|
|
|
2. ``P`` = ``ToString(P)``
|
|
|
|
3. ``O`` = ``ToObject(O)``
|
|
|
|
4. Call ``O.[[Delete]](P, currStrict)``, and return its result
|
|
|
|
Avoiding object coercion
|
|
------------------------
|
|
|
|
We want to avoid the object coercion; let's first make it more explicit:
|
|
|
|
1. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``
|
|
|
|
2. ``P`` = ``ToString(P)``
|
|
|
|
3. If ``O`` is an object, call ``[[Delete]](O, P, currStrict)``, and
|
|
return its result
|
|
|
|
4. Else ``O`` is primitive:
|
|
|
|
a. ``O`` = ``ToObject(O)`` (create temporary object)
|
|
|
|
b. Call ``O.[[Delete]](P, currStrict)``, and return its result
|
|
|
|
Avoiding temporary objects
|
|
--------------------------
|
|
|
|
Note that a ``[[Delete]]`` only operates on the "own properties" of the
|
|
target object. When the base value is not an object, the deletion operates
|
|
only on the temporary object. Since the temporary object is immediately
|
|
discarded, there are only two possible user visible effects:
|
|
|
|
* The return value of ``[[Delete]]``, which is:
|
|
|
|
+ ``true``, if the property does not exist
|
|
+ ``true``, if the property exists and could be deleted
|
|
+ ``false``, if the property exists, cannot be deleted, and
|
|
``Throw`` is ``false`` (if ``Throw`` is ``true``, an error is
|
|
thrown instead)
|
|
|
|
* Errors thrown by ``[[Delete]]``, which happens if:
|
|
|
|
+ The (own) property exists, the property is non-configurable, and the
|
|
Throw flag is set, i.e. we're evaluating ``delete`` in strict code
|
|
|
|
The coerced temporary object can be:
|
|
|
|
* a ``Boolean`` instance: no own properties
|
|
* a ``Number`` instance: no own properties
|
|
* a ``String`` instance: has ``length`` and array indices (inside string
|
|
length) as own properties, all non-configurable
|
|
|
|
Given these, the algorithm can be changed to avoid creation of temporaries
|
|
entirely:
|
|
|
|
1. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``
|
|
|
|
2. ``P`` = ``ToString(P)``
|
|
|
|
3. If ``O`` is an object, call ``[[Delete]](O, P, currStrict)`` and
|
|
return its result
|
|
|
|
4. Else ``O`` is primitive:
|
|
|
|
a. If ``O`` is a boolean, return ``true``
|
|
|
|
b. If ``O`` is a number, return ``true``
|
|
|
|
c. If ``O`` is a string:
|
|
|
|
1. If ``P`` is length or an array index inside the ``O`` string length:
|
|
|
|
a. If ``currStrict`` is ``true``, throw a ``TypeError``
|
|
b. Else, return ``false``
|
|
|
|
2. Else, return ``true``
|
|
|
|
d. Return ``true``
|
|
(This step should never be reached, as the checks above are
|
|
comprehensive.)
|
|
|
|
Step 4 can be simplified a bit:
|
|
|
|
1. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``
|
|
|
|
2. ``P`` = ``ToString(P)``
|
|
|
|
3. If ``O`` is an object, call ``[[Delete]](O, P, currStrict)`` and
|
|
return its result
|
|
|
|
4. If ``O`` is a string:
|
|
|
|
a. If ``P`` is length or an array index inside the ``O`` string length:
|
|
|
|
1. If ``currStrict`` is ``true``, throw a ``TypeError``
|
|
2. Else, return ``false``
|
|
|
|
5. Return ``true``
|
|
|
|
Fast path for array indices
|
|
---------------------------
|
|
|
|
It would be straightforward to add a fast path for array indices, but there
|
|
is no fast path in the current implementation for array index deletion.
|
|
The index is always string coerced and interned.
|
|
|
|
HASPROP: Exposed property existence check ("in" operator)
|
|
=========================================================
|
|
|
|
Background
|
|
----------
|
|
|
|
Property existence check is done using the ``in`` operator in Ecmascript
|
|
code, e.g.::
|
|
|
|
print('foo' in bar); // check whether foo.bar exists
|
|
|
|
This involves:
|
|
|
|
* An expression for the left-hand-side
|
|
|
|
* An expression for the right-hand-side
|
|
|
|
* ``in`` semantics (E5 Section 11.8.7)
|
|
|
|
* A call to ``[[HasProperty]]``
|
|
|
|
First draft
|
|
-----------
|
|
|
|
Starting from the property accessor, then applying ``in`` (and skipping any
|
|
unused steps):
|
|
|
|
1. Call ``CheckObjectCoercible`` for the base value. In practice, throw a
|
|
``TypeError`` if the base value is ``null`` or ``undefined``.
|
|
|
|
2. If the base value is not an object, throw a ``TypeError``.
|
|
|
|
3. Coerce property name to string using ``ToString()``.
|
|
|
|
4. Call ``[[HasProperty]]`` with the base object and the coerced property
|
|
name.
|
|
|
|
Note that the error throwing is unconditional and happens for non-strict
|
|
code too::
|
|
|
|
// throws TypeError
|
|
"foo" in "bar";
|
|
|
|
More formally, suppose ``O`` is the base value, ``P`` is the property name
|
|
value:
|
|
|
|
1. If ``O`` is ``null`` or ``undefined``, throw a ``TypeError``
|
|
|
|
2. If ``O`` is not an object, throw a ``TypeError``
|
|
|
|
3. ``P`` = ``ToString(P)``
|
|
|
|
4. Call ``O.[[HasProperty]](P)``, and return its result
|
|
|
|
The step 1 is unnecessary (step 2 suffices):
|
|
|
|
1. If ``O`` is not an object, throw a ``TypeError``
|
|
|
|
2. ``P`` = ``ToString(P)``
|
|
|
|
3. Call ``O.[[HasProperty]](P)``, and return its result
|
|
|
|
Inlining HasProperty
|
|
--------------------
|
|
|
|
Inlining ``[[HasProperty]]`` from E5 Section 8.12.6:
|
|
|
|
1. If ``O`` is not an object, throw a ``TypeError``
|
|
|
|
2. ``P`` = ``ToString(P)``
|
|
|
|
3. Let ``desc`` be the result of calling the ``[[GetProperty]]`` internal
|
|
method of ``O`` with property name ``P``.
|
|
|
|
4. If ``desc`` is ``undefined``, then return ``false``.
|
|
|
|
5. Else return ``true``.
|
|
|
|
INSTOF: exposed object class membership check ("instanceof" operator)
|
|
=====================================================================
|
|
|
|
Background
|
|
----------
|
|
|
|
Object class membership check is done using the ``instanceof`` operator
|
|
in Ecmascript code, e.g.::
|
|
|
|
print(x instanceof Array);
|
|
|
|
The language semantics of "class membership" are not as clear cut in
|
|
Ecmascript as in some other languages. But essentially, the ``instanceof``
|
|
expression above checks whether ``Array.prototype`` occurs in the internal
|
|
prototype chain of ``x``).
|
|
|
|
This involves:
|
|
|
|
* An expression for the left-hand-side
|
|
|
|
* An expression for the right-hand-side
|
|
|
|
* ``instanceof`` semantics (E5 Section 11.8.6)
|
|
|
|
* A call to ``[[HasInstance]]``
|
|
|
|
First draft
|
|
-----------
|
|
|
|
The ``instanceof`` operator is the only "caller" for ``[[HasInstance]]`` and
|
|
has the following steps (for evaluating RelationalExpression **instanceof**
|
|
ShiftExpression):
|
|
|
|
1. Let ``lref`` be the result of evaluating RelationalExpression.
|
|
|
|
2. Let ``lval`` be ``GetValue(lref)``.
|
|
|
|
3. Let ``rref`` be the result of evaluating ShiftExpression.
|
|
|
|
4. Let ``rval`` be ``GetValue(rref)``.
|
|
|
|
5. If ``Type(rval)`` is not ``Object``, throw a ``TypeError`` exception.
|
|
|
|
6. If ``rval`` does not have a ``[[HasInstance]]`` internal method, throw a
|
|
``TypeError`` exception.
|
|
|
|
7. Return the result of calling the ``[[HasInstance]]`` internal method of
|
|
``rval`` with argument ``lval``.
|
|
|
|
For implementing ``instanceof``, steps 1-4 can be assumed to be handled by
|
|
the compiler and map to a certain bytecode sequence. Steps 5-7 are the
|
|
relevant part.
|
|
|
|
The following algorithm integrates steps 5-7 from above with the combined
|
|
``[[HasInstance]]`` algorithm (``lval`` is renamed to ``V`` and ``rval``
|
|
to ``F``):
|
|
|
|
1. If ``Type(F)`` is not ``Object``, throw a ``TypeError`` exception.
|
|
|
|
2. If ``F`` does not have a ``[[HasInstance]]`` internal method, throw a
|
|
``TypeError`` exception.
|
|
|
|
3. While ``F`` is a bound function:
|
|
|
|
a. Set ``F`` to the value of ``F``\ 's ``[[TargetFunction]]`` internal
|
|
property.
|
|
|
|
b. If ``F`` has no ``[[HasInstance]]`` internal method, throw a
|
|
``TypeError`` exception.
|
|
(Note: ``F`` can be another bound function, so we loop until we find
|
|
the non-bound actual function.)
|
|
|
|
4. If ``V`` is not an object, return ``false``.
|
|
|
|
5. Let ``O`` be the result of calling the ``[[Get]]`` internal method of
|
|
``F`` with property name ``"prototype"``.
|
|
(Note: this is the external prototype, not the internal one.)
|
|
|
|
6. If ``Type(O)`` is not ``Object``, throw a ``TypeError`` exception.
|
|
|
|
7. Repeat
|
|
|
|
a. Let ``V`` be the value of the ``[[Prototype]]`` internal property of
|
|
``V``.
|
|
|
|
b. If ``V`` is ``null``, return ``false``.
|
|
|
|
c. If ``O`` and ``V`` refer to the same object, return ``true``.
|
|
|
|
Notes:
|
|
|
|
* The initial ``rval`` may be something other than a callable function,
|
|
so it needs an explicit check, whereas the ``[[TargetFunction]]``
|
|
internal property can only exist with a valid callable object value
|
|
(E5 Section 15.3.4.5, step 2 checks for this).
|
|
|
|
* Step 3.b seems to be unnecessary: ``Function.prototype.bind()`` will
|
|
not create a bound function whose target function is not callable, so
|
|
they should always have a ``[[HasInstance]]`` internal method. If this
|
|
is just to add some internal robustness, why not also check that the
|
|
target function is an object?
|
|
|
|
* In step 7 we assume that the internal prototype is always an object or
|
|
``null``. If the internal implementation does not constrain this fully,
|
|
it makes sense to check this explicitly. The current implementation uses
|
|
an ``duk_hobject`` pointer for the internal prototype, so the prototype is
|
|
effectively constrained to be either object or ``null``.
|
|
|
|
* The loop in step 7 assumes that there are no prototype loops. An explicit
|
|
sanity check should be inserted.
|
|
|
|
Cleanup
|
|
-------
|
|
|
|
Steps 1-3 can be combined to a simpler loop with a bit more paranoid checks:
|
|
|
|
1. Repeat
|
|
|
|
a. If ``Type(F)`` is not ``Object``, throw a ``TypeError`` exception.
|
|
|
|
b. If ``F`` does not have a ``[[HasInstance]]`` internal method, throw a
|
|
``TypeError`` exception.
|
|
|
|
c. If ``F`` is a normal (non-bound) function, break repeat loop.
|
|
|
|
d. If ``F`` is *not* a bound function, throw a ``TypeError`` exception.
|
|
(Note: this should never happen, but is nice to check.)
|
|
|
|
e. Set ``F`` to the value of ``F``\ 's ``[[TargetFunction]]`` internal
|
|
property and repeat from a).
|
|
(Note: ``F`` may be another bound function when exiting this step,
|
|
so we must repeat until the final, non-bound function is found.)
|
|
|
|
2. If ``V`` is not an object, return ``false``.
|
|
|
|
3. Let ``O`` be the result of calling the ``[[Get]]`` internal method of
|
|
``F`` with property name ``"prototype"``.
|
|
(Note: this is the external prototype, not the internal one.)
|
|
|
|
4. If ``Type(O)`` is not ``Object``, throw a ``TypeError`` exception.
|
|
|
|
5. Repeat
|
|
|
|
a. Let ``V`` be the value of the ``[[Prototype]]`` internal property of
|
|
``V``.
|
|
|
|
b. If ``V`` is ``null``, return ``false``.
|
|
|
|
c. If ``O`` and ``V`` refer to the same object, return ``true``.
|
|
|
|
Exposed Object.getOwnPropertyDescriptor()
|
|
=========================================
|
|
|
|
Original algorithm
|
|
------------------
|
|
|
|
The algorithm is specified in E5 Section 15.2.3.3:
|
|
|
|
1. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception.
|
|
|
|
2. Let ``name`` be ``ToString(P)``.
|
|
(Note: this may have a side effect.)
|
|
|
|
3. Let ``desc`` be the result of calling the ``[[GetOwnProperty]]`` internal
|
|
method of ``O`` with argument ``name``.
|
|
|
|
4. Return the result of calling ``FromPropertyDescriptor(desc)``
|
|
(E5 Section 8.10.4).
|
|
|
|
FromPropertyDescriptor
|
|
----------------------
|
|
|
|
The ``FromPropertyDescriptor()`` algorithm in E5 Section 8.10.4 is
|
|
as follows:
|
|
|
|
1. If ``Desc`` is ``undefined``, then return ``undefined``.
|
|
|
|
2. Let ``obj`` be the result of creating a new object as if by the expression
|
|
``new Object()`` where ``Object`` is the standard built-in constructor with
|
|
that name.
|
|
|
|
3. If ``IsDataDescriptor(Desc)`` is ``true``, then
|
|
|
|
a. Call the ``[[DefineOwnProperty]]`` internal method of ``obj`` with
|
|
arguments ``"value"``, Property Descriptor {[[Value]]: Desc.[[Value]],
|
|
[[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and
|
|
``false``.
|
|
|
|
b. Call the ``[[DefineOwnProperty]]`` internal method of ``obj`` with
|
|
arguments ``"writable"``, Property Descriptor {[[Value]]:
|
|
Desc.[[Writable]], [[Writable]]: true, [[Enumerable]]: true,
|
|
[[Configurable]]: true}, and ``false``.
|
|
|
|
4. Else, ``IsAccessorDescriptor(Desc)`` must be ``true``, so
|
|
|
|
a. Call the ``[[DefineOwnProperty]]`` internal method of ``obj`` with
|
|
arguments ``"get"``, Property Descriptor {[[Value]]: Desc.[[Get]],
|
|
[[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and
|
|
``false``.
|
|
|
|
b. Call the ``[[DefineOwnProperty]]`` internal method of ``obj`` with
|
|
arguments ``"set"``, Property Descriptor {[[Value]]: Desc.[[Set]],
|
|
[[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true}, and
|
|
``false``.
|
|
|
|
5. Call the ``[[DefineOwnProperty]]`` internal method of ``obj`` with
|
|
arguments ``"enumerable"``, Property Descriptor {[[Value]]:
|
|
Desc.[[Enumerable]], [[Writable]]: true, [[Enumerable]]: true,
|
|
[[Configurable]]: true}, and ``false``.
|
|
|
|
6. Call the ``[[DefineOwnProperty]]`` internal method of ``obj`` with
|
|
arguments ``"configurable"``, Property Descriptor {[[Value]]:
|
|
Desc.[[Configurable]], [[Writable]]: true, [[Enumerable]]: true,
|
|
[[Configurable]]: true}, and ``false``.
|
|
|
|
7. Return ``obj``.
|
|
|
|
Notes:
|
|
|
|
* Since all the ``[[DefineOwnProperty]]`` calls create new property values,
|
|
and the property attributes match the defaults for ``[[Put]]``, we can
|
|
simply use ``[[Put]]`` instead. The ``Throw`` flag does not matter as
|
|
the ``[[Put]]`` operations cannot fail (except for some internal reason,
|
|
which is thrown unconditionally without regard for ``Throw`` anyway).
|
|
|
|
* The order of settings properties to ``obj`` matters since it will affect
|
|
the enumeration order of ``obj``.
|
|
|
|
Changing ``[[DefineOwnProperty]]`` to ``[[Put]]`` and renaming ``Desc``
|
|
to ``desc`` (for compatibility with ``Object.getOwnPropertyDescriptor()``
|
|
algorithm):
|
|
|
|
1. If ``desc`` is ``undefined``, then return ``undefined``.
|
|
|
|
2. Let ``obj`` be the result of creating a new object as if by the expression
|
|
``new Object()`` where ``Object`` is the standard built-in constructor with
|
|
that name.
|
|
|
|
3. If ``IsDataDescriptor(desc)`` is ``true``, then
|
|
|
|
a. Call ``obj.[[Put]]`` with arguments
|
|
``"value"``, ``desc.[[Value]]``, and ``false``.
|
|
|
|
b. Call ``obj.[[Put]]`` with arguments
|
|
``"writable"``, ``desc.[[Writable]]``, and ``false``.
|
|
|
|
4. Else, ``IsAccessorDescriptor(Desc)`` must be ``true``, so
|
|
|
|
a. Call ``obj.[[Put]]`` with arguments
|
|
``"get"``, ``desc.[[Get]]``, and ``false``.
|
|
(Note: ``desc.[[Get]]`` may be ``undefined``.)
|
|
|
|
b. Call ``obj.[[Put]]`` with arguments
|
|
``"set"``, ``desc.[[Set]]``, and ``false``.
|
|
(Note: ``desc.[[Set]]`` may be ``undefined``.)
|
|
|
|
5. Call ``obj.[[Put]]`` with arguments
|
|
``"enumerable"``, ``desc.[[Enumerable]]``, and ``false``.
|
|
|
|
6. Call ``obj.[[Put]]`` with arguments
|
|
``"configurable"``, ``desc.[[Configurable]]``, and ``false``.
|
|
|
|
7. Return ``obj``.
|
|
|
|
Inlining FromPropertyDescriptor
|
|
-------------------------------
|
|
|
|
1. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception.
|
|
|
|
2. Let ``name`` be ``ToString(P)``.
|
|
(Note: this may have a side effect.)
|
|
|
|
3. Let ``desc`` be the result of calling the ``[[GetOwnProperty]]`` internal
|
|
method of ``O`` with argument ``name``.
|
|
|
|
4. If ``desc`` is ``undefined``, then return ``undefined``.
|
|
|
|
5. Let ``obj`` be the result of creating a new object as if by the expression
|
|
``new Object()`` where ``Object`` is the standard built-in constructor with
|
|
that name.
|
|
|
|
6. If ``IsDataDescriptor(desc)`` is ``true``, then
|
|
|
|
a. Call ``obj.[[Put]]`` with arguments
|
|
``"value"``, ``desc.[[Value]]``, and ``false``.
|
|
|
|
b. Call ``obj.[[Put]]`` with arguments
|
|
``"writable"``, ``desc.[[Writable]]``, and ``false``.
|
|
|
|
7. Else, ``IsAccessorDescriptor(Desc)`` must be ``true``, so
|
|
|
|
a. Call ``obj.[[Put]]`` with arguments
|
|
``"get"``, ``desc.[[Get]]``, and ``false``.
|
|
(Note: ``desc.[[Get]]`` may be ``undefined``.)
|
|
|
|
b. Call ``obj.[[Put]]`` with arguments
|
|
``"set"``, ``desc.[[Set]]``, and ``false``.
|
|
(Note: ``desc.[[Set]]`` may be ``undefined``.)
|
|
|
|
8. Call ``obj.[[Put]]`` with arguments
|
|
``"enumerable"``, ``desc.[[Enumerable]]``, and ``false``.
|
|
|
|
9. Call ``obj.[[Put]]`` with arguments
|
|
``"configurable"``, ``desc.[[Configurable]]``, and ``false``.
|
|
|
|
10. Return ``obj``.
|
|
|
|
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 special 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.
|
|
|
|
Exposed Object.defineProperties()
|
|
=================================
|
|
|
|
Implementation approach discussion
|
|
----------------------------------
|
|
|
|
Since ``Object.defineProperty()`` and ``Object.defineProperties()`` are
|
|
such expensive functions (from a code footprint point of view), we'd
|
|
really like to have only one implementation with some wrappers. For
|
|
instance, we could have an actual implementation of
|
|
``Object.defineProperty()`` and then have ``Object.defineProperties()``
|
|
call it as a helper (or vice versa).
|
|
|
|
Considering the case where ``Object.defineProperties()`` would use
|
|
``Object.defineProperty()`` as a helper, the ``Object.defineProperties()``
|
|
algorithm is unfortunate: it coerces all property descriptors with
|
|
``ToPropertyDescriptor()`` and puts them on an internal list
|
|
(``descriptors``) *before* doing any operations on the target object.
|
|
The coercion includes property descriptor validation, and implies some
|
|
way of storing the internal descriptors (other than local variables).
|
|
|
|
Note that the ``ToPropertyDescriptor()`` coercion may also have arbitrary
|
|
user visible side effects because it calls ``[[Get]]`` on the relevant
|
|
properties. The ``[[Get]]`` may invoke a getter call, which may in
|
|
pathological cases even *modify* the other descriptors -- creating both an
|
|
ordering and a call count dependency. Consider the pathological case::
|
|
|
|
var desc2 = { value: 0 };
|
|
var desc1 = {
|
|
get value() {
|
|
print("desc1 value getter");
|
|
desc2.value++; // increment for every call
|
|
return "test";
|
|
}
|
|
};
|
|
|
|
var descs = { foo: desc1, bar: desc2 };
|
|
|
|
var o = {};
|
|
Object.defineProperties(o, descs);
|
|
print(o.foo); // should print test
|
|
print(o.bar); // should print 1, as getter is called exactly once
|
|
|
|
If the implementation were to, for instance, call ``ToPropertyDescriptor()``
|
|
twice (once to validate, discarding any results, and a second time when
|
|
calling ``Object.defineProperty()`` internally as a helper), it would
|
|
fail the above test.
|
|
|
|
On the other hand, if the implementation simply called
|
|
``Object.defineProperty()`` for each descriptor in turn, it would not
|
|
be compliant if there is an invalid descriptor in the ``Properties``
|
|
argument list of ``Object.defineProperties()``. No changes to the target
|
|
object can be made if there is an invalid descriptor in the list.
|
|
|
|
There are other pathological cases too, e.g. a getter removing elements
|
|
from the ``Properties`` argument of ``Object.defineProperties()``.
|
|
|
|
Another implementation approach is to make ``Object.defineProperties()`` the
|
|
main algorithm and have ``Object.defineProperty()`` be a wrapper around it.
|
|
This works but still has issues:
|
|
|
|
* ``Object.defineProperties()`` still needs to have a list of *coerced*
|
|
descriptors internally, which implies some storage (other than local
|
|
variables) for coerced (internal) descriptors.
|
|
|
|
* ``Object.defineProperty()`` would need to create a temporary object
|
|
for containing the one property descriptor it gets as an input, e.g.::
|
|
|
|
Object.defineProperty(o, 'foo', { value: 'bar' });
|
|
|
|
needs to become::
|
|
|
|
Object.defineProperties(o, { foo: { value: 'bar' }});
|
|
|
|
A way around creating an internal representation for partially populated
|
|
descriptors is to use an internal Ecmascript object representing a
|
|
validated and normalized descriptor with all property values already
|
|
coerced and checked; any getter calls would be done during coercion
|
|
and the final value would be a plain one. In the pathological example
|
|
above, the internal descriptors could be::
|
|
|
|
{
|
|
foo: {
|
|
value: "test"
|
|
},
|
|
bar: {
|
|
value: 1
|
|
}
|
|
}
|
|
|
|
The coercions could then be executed first, and the coerced descriptors
|
|
then given one at a time (as Ecmascript objects) to
|
|
``Object.defineProperty()``.
|
|
|
|
This would eliminate any side effects of the coercion and would allow
|
|
validation of the descriptors before any object changes. The downside
|
|
is the need for an additional helper, and creating temporary objects
|
|
for each ``Object.defineProperties()`` (but not ``Object.defineProperty()``)
|
|
call.
|
|
|
|
This is the current implementation approach. The coercion helper is defined
|
|
as ``NormalizePropertyDescriptor`` in the restatements section and will be
|
|
inlined below. Note that this helper is not part of the E5 specification.
|
|
|
|
Original algorithm
|
|
------------------
|
|
|
|
1. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception.
|
|
|
|
2. Let ``props`` be ``ToObject(Properties)``.
|
|
|
|
3. Let ``names`` be an internal list containing the names of each enumerable
|
|
own property of ``props``.
|
|
|
|
4. Let ``descriptors`` be an empty internal List.
|
|
|
|
5. For each element ``P`` of ``names`` in list order,
|
|
|
|
a. Let ``descObj`` be the result of calling the ``[[Get]]`` internal method
|
|
of ``props`` with ``P`` as the argument.
|
|
|
|
b. Let ``desc`` be the result of calling ``ToPropertyDescriptor`` with
|
|
``descObj`` as the argument.
|
|
(Note: this step may fail due for invalid property descriptors, and may
|
|
have user visible side effects due to potential getter calls.)
|
|
|
|
c. Append ``desc`` to the end of ``descriptors``.
|
|
|
|
6. For each element ``desc`` of ``descriptors`` in list order,
|
|
|
|
a. Call the ``[[DefineOwnProperty]]`` internal method of ``O`` with
|
|
arguments ``P``, ``desc``, and ``true``.
|
|
|
|
7. Return ``O``.
|
|
|
|
Notes:
|
|
|
|
* In Step 6.a ``P`` should refer to the name related to the descriptor being
|
|
processed, but there is no assignment for ``P`` after step 5. This seems
|
|
like a small typo in the specification.
|
|
|
|
Using NormalizePropertyDescriptor
|
|
---------------------------------
|
|
|
|
Below, the standard algorithm has been changed to use
|
|
``NormalizePropertyDescriptor()`` and to call ``Object.defineProperty()``
|
|
instead of ``[[DefineOwnProperty]]``:
|
|
|
|
1. If ``Type(O)`` is not ``Object`` throw a ``TypeError`` exception.
|
|
|
|
2. Let ``props`` be ``ToObject(Properties)``.
|
|
|
|
3. Let ``descriptors`` be an empty internal Object.
|
|
(Note: we assume that the object has enumeration order matching property
|
|
insertion order.)
|
|
|
|
4. For each enumerable property ``P`` of ``props`` (in normal enumeration
|
|
order),
|
|
|
|
a. Let ``descObj`` be the result of calling the ``[[Get]]`` internal method
|
|
of ``props`` with ``P`` as the argument.
|
|
|
|
b. Let ``desc`` be the result of calling ``NormalizePropertyDescriptor``
|
|
with ``descObj`` as the argument.
|
|
(Note: this step may fail due for invalid property descriptors, and may
|
|
have user visible side effects due to potential getter calls.)
|
|
|
|
c. Call the ``[[Put]]`` internal method of ``descriptors`` with
|
|
``P``, ``desc`` and ``true`` as arguments.
|
|
|
|
5. For each enumerable property ``P`` of ``descriptors`` (in insertion
|
|
order),
|
|
|
|
a. Let ``desc`` be the result of calling the ``[[Get]]`` internal method
|
|
of ``descriptors`` with ``P`` as the argument.
|
|
(Note: this is guaranteed to succeed and yield a valid descriptor
|
|
object.)
|
|
|
|
b. Call the ``Object.defineProperty()`` built-in method with the arguments
|
|
``O``, ``P`` and ``desc``, ignoring its result value.
|
|
(Note: this call may fail due to an exception.)
|
|
|
|
6. Return ``O``.
|
|
|
|
Changing ``[[DefineOwnProperty]]`` to ``Object.defineProperty()`` should be
|
|
semantically correct. Consider the steps of ``Object.defineProperty()``
|
|
in E5 Section 15.2.3.6:
|
|
|
|
* Step 1: already covered by step 1 above.
|
|
|
|
* Step 2: a no-op because all property names (``P``) above are naturally
|
|
strings.
|
|
|
|
* Step 3: guaranteed to succeed and be side-effect free, and to produce
|
|
the same result as it normally would.
|
|
|
|
* Step 4: makes a call to ``[[DefineOwnProperty]]``
|
|
|
|
* Step 5: return value is ignored.
|
|
|
|
Future work
|
|
===========
|
|
|
|
* Add ES6 Proxy object or a Lua metatable-like mechanism and integrate it
|
|
into the Ecmascript algorithms in a natural way (``[[Get]]``, ``[[GetOwnProperty]]``,
|
|
``[[HasProperty]]``, and ``[[DefineOwnProperty]]`` most likely).
|
|
|
|
* Integrate other ES6 features into the basic object representation, with
|
|
possible some impact on these algorithms.
|
|
|
|
* Array fast path improvements for both reading of non-existent elements
|
|
and writing elements in general.
|
|
|
|
* Review the algorithms for robustness against internal errors such as
|
|
out of memory. The order of operations should be chosen to minimize
|
|
any inconsistency in state if an internal error occurs.
|
|
|
|
|