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.

1001 lines
37 KiB

=======================================
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 exotic ``[[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 exotic behavior.) Let ``map`` be the value of
the ``[[ParameterMap]]`` internal property of the arguments object.
b. If the result of calling the ``[[GetOwnProperty]]`` internal method
of ``map`` passing ``P`` as the argument is not ``undefined``:
1. Return the result of calling the ``[[Get]]`` internal method of
``map`` passing ``P`` as the argument.
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 exotic 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 exotic 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 exotic behavior.) Let ``map`` be the value of
the ``[[ParameterMap]]`` internal property of the arguments object.
b. If the result of calling the ``[[GetOwnProperty]]`` internal method
of ``map`` passing ``P`` as the argument is not ``undefined``:
1. Return the result of calling the ``[[Get]]`` internal method of
``map`` passing ``P`` as the argument.
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 exotic 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 exotic behavior.) Let ``map`` be the value of
the ``[[ParameterMap]]`` internal property of the arguments object.
b. If the result of calling the ``[[GetOwnProperty]]`` internal method
of ``map`` passing ``P`` as the argument is not ``undefined``:
1. Return the result of calling the ``[[Get]]`` internal method of
``map`` passing ``P`` as the argument.
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 exotic 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 exotic behavior.) Let ``map`` be the value of
the ``[[ParameterMap]]`` internal property of the arguments object.
b. If the result of calling the ``[[GetOwnProperty]]`` internal method
of ``map`` passing ``P`` as the argument is not ``undefined``:
1. Return the result of calling the ``[[Get]]`` internal method of
``map`` passing ``P`` as the argument.
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 exotic 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 "exotic 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 exotic behaviors are currently: ``String`` object exotic
behavior, and arguments object exotic behavior. Array exotic 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 exotic behaviors (cannot have
``String`` or arguments exotic 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 exotic behavior.) Let ``map`` be the value of
the ``[[ParameterMap]]`` internal property of the arguments object.
b. If the result of calling the ``[[GetOwnProperty]]`` internal method
of ``map`` passing ``P`` as the argument is not ``undefined``:
1. Return the result of calling the ``[[Get]]`` internal method of
``map`` passing ``P`` as the argument.
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 exotic 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 exotic behaviors (cannot have
``String`` or arguments exotic 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 exotic behavior.) Let ``map`` be the value of
the ``[[ParameterMap]]`` internal property of the arguments object.
b. If the result of calling the ``[[GetOwnProperty]]`` internal method
of ``map`` passing ``P`` as the argument is not ``undefined``:
1. Return the result of calling the ``[[Get]]`` internal method of
``map`` passing ``P`` as the argument.
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 exotic 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 exotic 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 exotic behaviors (cannot have
``String`` or arguments exotic 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 exotic 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 exotic 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 exotic behaviors (cannot have
``String`` or arguments exotic 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 exotic 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 exotic behavior.)
If ``P`` is ``"caller"`` and ``res`` is a strict mode ``Function``
object, throw a ``TypeError`` exception.
9. Return ``res``.
Final version
=============
(See above.)