Exotic behaviors
This section covers the standard algorithms with exotic behaviors inlined. For each algorithm, a single algorithm with all exotic behaviors inlined is presented. Calls to other internal algorithms are not inlined; the purpose is to clarify how the exotic behaviors can be implemented reasonably.
Note: the String
object has no exotic 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
- If
O
doesn't have an own property with nameP
, returnundefined
. - Let
D
be a newly created Property Descriptor with no fields. - Let
X
beO
's own property named P. - If
X
is a data property, then a. SetD.[[Value]]
to the value ofX
's[[Value]]
attribute. b. SetD.[[Writable]]
to the value ofX
's[[Writable]]
attribute. - Else
X
is an accessor property, so a. SetD.[[Get]]
to the value ofX
's[[Get]]
attribute. b. SetD.[[Set]]
to the value ofX
's[[Set]]
attribute. - Set
D.[[Enumerable]]
to the value ofX
's[[Enumerable]]
attribute. - Set
D.[[Configurable]]
to the value ofX
's[[Configurable]]
attribute. - Return
D
.
Adding String object exotic 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 exotic behavior does not execute at all. That, is the exotic 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 ::: title 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-exotic) 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 exotic 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 exotic (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:
- If
O
doesn't have an own property with nameP
: a. IfO
is not aString
instance, returnundefined
. b. (String
object exotic behavior.) Letstr
be the String value of the[[PrimitiveValue]]
internal property ofO
andlen
be the number of characters instr
. c. IfP
is"length"
, return a Property Descriptor with the values: -[[Value]]: len
(a number) -[[Enumerable]]: false
-[[Writable]]: false
-[[Configurable]]: false
d. IfP
is not an array index (E5 Section 15.4), returnundefined
. e. Letindex
beToUint32(P)
. f. Iflen
<=index
, returnundefined
. g. LetresultStr
be a string of length 1, containing one character fromstr
, specifically the character at positionindex
, where the first (leftmost) character instr
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
- Let
D
be a newly created Property Descriptor with no fields. - Let
X
beO
's own property namedP
. - If
X
is a data property, then a. SetD.[[Value]]
to the value ofX
's[[Value]]
attribute. b. SetD.[[Writable]]
to the value ofX
's[[Writable]]
attribute. - Else
X
is an accessor property, so a. SetD.[[Get]]
to the value ofX
's[[Get]]
attribute. b. SetD.[[Set]]
to the value ofX
's[[Set]]
attribute. - Set
D.[[Enumerable]]
to the value ofX
's[[Enumerable]]
attribute. - Set
D.[[Configurable]]
to the value ofX
's[[Configurable]]
attribute. - Return
D
.
Adding arguments object exotic behavior
Next, consider the exotic [[GetOwnProperty]]
behavior for a non-strict arguments object described in E5 Section 10.6. The exotic 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 exotic behavior activates, the property is always a data property.
The exotic behavior can be appended to the above algorithm as follows:
- If
O
doesn't have an own property with nameP
: a. IfO
is not aString
instance, returnundefined
. b. (String
object exotic behavior.) Letstr
be the String value of the[[PrimitiveValue]]
internal property ofO
andlen
be the number of characters instr
. c. IfP
is"length"
, return a Property Descriptor with the values: -[[Value]]: len
(a number) -[[Enumerable]]: false
-[[Writable]]: false
-[[Configurable]]: false
d. IfP
is not an array index (E5 Section 15.4), returnundefined
. e. Else letindex
beToUint32(P)
. f. Iflen
<=index
, returnundefined
. g. LetresultStr
be a string of length 1, containing one character fromstr
, specifically the character at positionindex
, where the first (leftmost) character instr
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
- Let
D
be a newly created Property Descriptor with no fields. - Let
X
beO
's own property namedP
. - If
X
is a data property, then a. SetD.[[Value]]
to the value ofX
's[[Value]]
attribute. b. SetD.[[Writable]]
to the value ofX
's[[Writable]]
attribute. - Else
X
is an accessor property, so a. SetD.[[Get]]
to the value ofX
's[[Get]]
attribute. b. SetD.[[Set]]
to the value ofX
's[[Set]]
attribute. - Set
D.[[Enumerable]]
to the value ofX
's[[Enumerable]]
attribute. - Set
D.[[Configurable]]
to the value ofX
's[[Configurable]]
attribute. - If
O
is anarguments
object which contains a[[ParameterMap]]
internal property: a. (Arguments object exotic behavior.) Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. LetisMapped
be the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument. c. If the value ofisMapped
is notundefined
, then: 1. SetD.[[Value]]
to the result of calling the[[Get]]
internal method ofmap
passingP
as the argument. - Return
D
.
Notes:
- Step 1.b: if the object is a
String
object, there is no need for the arguments object exotic behavior check in step 8: an object can never be aString
object and an arguments object simultaenously. - Step 8: arguments objects for strict mode functions don't have the exotic behavior (or a
[[ParameterMap]]
). Arguments objects for non-strict functions don't always have exotic behavior either: they only do, if there is at least one mapped variable. If so,[[ParameterMap]]
is added, and exotic 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 exotic behavior.
Final version
Final version with some cleanup and simplification:
- Let
X
beO
's own property namedP
. IfO
doesn't have an own property with nameP
: a. IfO
is not aString
instance, returnundefined
. b. (String
object exotic behavior.) Letstr
be the String value of the[[PrimitiveValue]]
internal property ofO
andlen
be the number of characters instr
. c. IfP
is"length"
: 1. Return a Property Descriptor with the values: -[[Value]]: len
(a primitive number) -[[Enumerable]]: false
-[[Writable]]: false
-[[Configurable]]: false
d. IfP
is an array index (E5 Section 15.4): 1. Letindex
beToUint32(P)
. 2. Ifindex
<len
, return a Property Descriptor with the values: -[[Value]]:
a primitive string of length 1, containing one character fromstr
at positionindex
(zero based index) -[[Enumerable]]: true
-[[Writable]]: false
-[[Configurable]]: false
e. Returnundefined
. - Let
D
be a newly created Property Descriptor filled as follows: a. IfX
is a data property: 1. SetD.[[Value]]
to the value ofX
's[[Value]]
attribute. 2. SetD.[[Writable]]
to the value ofX
's[[Writable]]
attribute. b. ElseX
is an accessor property: 1. SetD.[[Get]]
to the value ofX
's[[Get]]
attribute. 2. SetD.[[Set]]
to the value ofX
's[[Set]]
attribute. c. For either type of property: 1. SetD.[[Enumerable]]
to the value ofX
's[[Enumerable]]
attribute. 2. SetD.[[Configurable]]
to the value ofX
's[[Configurable]]
attribute. - If
O
is anarguments
object which contains a[[ParameterMap]]
internal property: a. (Arguments object exotic behavior.) Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then: 1. SetD.[[Value]]
to the result of calling the[[Get]]
internal method ofmap
passingP
as the argument. - 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.)
- Let
desc
be the result of calling the[[GetProperty]]
internal method ofO
with property nameP
. - If
desc
isundefined
, returnundefined
. - If
IsDataDescriptor(desc)
istrue
, returndesc.[[Value]]
. - Otherwise,
IsAccessorDescriptor(desc)
must betrue
so, letgetter
bedesc.[[Get]]
. - If
getter
isundefined
, returnundefined
. - Return the result calling the
[[Call]]
internal method ofgetter
providingO
as thethis
value and providing no arguments.
Adding Function object exotic 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 exotic 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:
- Let
desc
be the result of calling the[[GetProperty]]
internal method ofO
with property nameP
. - If
desc
isundefined
, returnundefined
. - If
IsDataDescriptor(desc)
istrue
: a. Letres
bedesc.[[Value]]
. - Otherwise,
IsAccessorDescriptor(desc)
must betrue
: a. Letgetter
bedesc.[[Get]]
. b. Ifgetter
isundefined
, returnundefined
. c. Else letres
be the result of calling the[[Call]]
internal method ofgetter
providingO
as thethis
value and providing no arguments. - If
O
is aFunction
object,P
is"caller"
, andres
is a strict modeFunction
object, throw aTypeError
exception. - Return
res
.
Adding arguments object exotic behavior
Next, consider the exotic [[Get]]
behavior for a non-strict arguments object described in E5 Section 10.6. To be exact, the exotic behaviors are only enabled for objects with a non-empty initial [[ParameterMap]]
(see E5 Section 10.6, main algorithm, step 12).
There are two exotic behaviors:
- 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 propertyP
must be a data property in this case, so no side effects are lost by this behavior.) - If the property name
P
is not bound to an identifier, the"caller"
property has exotic behavior essentially identical to that ofFunction
.
These can be incorporated as follows:
- If
O
is anarguments
object which contains a[[ParameterMap]]
internal property: a. (Arguments object exotic behavior.) Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. LetisMapped
be the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument. c. If the value ofisMapped
is notundefined
, then: 1. Return the result of calling the[[Get]]
internal method ofmap
passingP
as the argument. - Let
desc
be the result of calling the[[GetProperty]]
internal method ofO
with property nameP
. - If
desc
isundefined
, returnundefined
. - If
IsDataDescriptor(desc)
istrue
: a. Letres
bedesc.[[Value]]
. - Otherwise,
IsAccessorDescriptor(desc)
must betrue
: a. Letgetter
bedesc.[[Get]]
. b. Ifgetter
isundefined
, returnundefined
. c. Else letres
be the result of calling the[[Call]]
internal method ofgetter
providingO
as thethis
value and providing no arguments. - If
O
is aFunction
object or anarguments
object which contains a[[ParameterMap]]
internal property: a. (Arguments or Function object exotic behavior.) IfP
is"caller"
andres
is a strict modeFunction
object, throw aTypeError
exception. - 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 exotic behavior could just as well be moved to the end of the algorithm.
Final version
Final version with some cleanup and simplification:
- If
O
is anarguments
object which contains a[[ParameterMap]]
internal property: a. (Arguments object exotic behavior.) Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
: 1. Return the result of calling the[[Get]]
internal method ofmap
passingP
as the argument. - Let
desc
be the result of calling the[[GetProperty]]
internal method ofO
with property nameP
. - If
desc
isundefined
, returnundefined
. - If
IsDataDescriptor(desc)
istrue
: a. Letres
bedesc.[[Value]]
. - Otherwise,
IsAccessorDescriptor(desc)
must betrue
: a. Letgetter
bedesc.[[Get]]
. b. Ifgetter
isundefined
, returnundefined
. c. Else letres
be the result of calling the[[Call]]
internal method ofgetter
providingO
as thethis
value and providing no arguments. - If
O
is aFunction
object or anarguments
object which contains a[[ParameterMap]]
internal property: a. (Arguments or Function object exotic behavior.) IfP
is"caller"
andres
is a strict modeFunction
object, throw aTypeError
exception. - 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
exotic 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 exotic 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
- Let
current
be the result of calling the[[GetOwnProperty]]
internal method ofO
with property nameP
. - Let
extensible
be the value of the[[Extensible]]
internal property ofO
. - If
current
isundefined
andextensible
isfalse
, then Reject. - If
current
isundefined
andextensible
istrue
, then a. IfIsGenericDescriptor(Desc)
orIsDataDescriptor(Desc)
istrue
, then 1. Create an own data property namedP
of objectO
whose[[Value]]
,[[Writable]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
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 namedP
of objectO
whose[[Get]]
,[[Set]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
is absent, the attribute of the newly created property is set to its default value. c. Returntrue
. - Return
true
if every field inDesc
is absent. - Return
true
, if every field inDesc
also occurs incurrent
and the value of every field inDesc
is the same value as the corresponding field incurrent
when compared using theSameValue
algorithm (E5 Section 9.12). - If the
[[Configurable]]
field ofcurrent
isfalse
then a. Reject, if the[[Configurable]]
field ofDesc
is true. b. Reject, if the[[Enumerable]]
field ofDesc
is present and the[[Enumerable]]
fields ofcurrent
andDesc
are the Boolean negation of each other. - If
IsGenericDescriptor(Desc)
istrue
, then no further validation is required. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
have different results, then a. Reject, if the[[Configurable]]
field ofcurrent
isfalse
. b. IfIsDataDescriptor(current)
is true, then 1. Convert the property namedP
of objectO
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 namedP
of objectO
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. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
are both true, then a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Reject, if the[[Writable]]
field ofcurrent
isfalse
and the[[Writable]]
field ofDesc
istrue
. 2. If the[[Writable]]
field ofcurrent
isfalse
, then a. Reject, if the[[Value]]
field ofDesc
is present andSameValue(Desc.[[Value]], current.[[Value]])
isfalse
. b. else, the[[Configurable]]
field ofcurrent
istrue
, so any change is acceptable. - Else,
IsAccessorDescriptor(current)
andIsAccessorDescriptor(Desc)
are bothtrue
so, a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Reject, if the[[Set]]
field ofDesc
is present andSameValue(Desc.[[Set]], current.[[Set]])
isfalse
. 2. Reject, if the[[Get]]
field ofDesc
is present andSameValue(Desc.[[Get]], current.[[Get]])
isfalse
. - For each attribute field of
Desc
that is present, set the correspondingly named attribute of the property namedP
of objectO
to the value of the field. - 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 exotic behaviors, these are turned to "gotos" below.
Default algorithm reformulated
Let's first do a little bit of reformulation (see above):
- Let
current
be the result of calling the[[GetOwnProperty]]
internal method ofO
with property nameP
. - Let
extensible
be the value of the[[Extensible]]
internal property ofO
. - If
current
isundefined
: a. Ifextensible
isfalse
, then goto REJECT. b. IfIsGenericDescriptor(Desc)
orIsDataDescriptor(Desc)
istrue
, then 1. Create an own data property namedP
of objectO
whose[[Value]]
,[[Writable]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
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 namedP
of objectO
whose[[Get]]
,[[Set]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
is absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. - Goto SUCCESS, if every field in
Desc
also occurs incurrent
and the value of every field inDesc
is the same value as the corresponding field incurrent
when compared using theSameValue
algorithm (E5 Section 9.12). (This also covers the case where every field inDesc
is absent.) - If the
[[Configurable]]
field ofcurrent
isfalse
then a. Goto REJECT, if the[[Configurable]]
field ofDesc
is true. b. Goto REJECT, if the[[Enumerable]]
field ofDesc
is present and the[[Enumerable]]
fields ofcurrent
andDesc
are the Boolean negation of each other. - If
IsGenericDescriptor(Desc)
istrue
, then goto VALIDATED. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
have different results, then a. Goto REJECT, if the[[Configurable]]
field ofcurrent
isfalse
. b. IfIsDataDescriptor(current)
is true, then 1. Convert the property namedP
of objectO
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 namedP
of objectO
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. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
are both true, then a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Goto REJECT, if the[[Writable]]
field ofcurrent
isfalse
and the[[Writable]]
field ofDesc
istrue
. 2. Goto REJECT, If the[[Writable]]
field ofcurrent
isfalse
, and the[[Value]]
field ofDesc
is present, andSameValue(Desc.[[Value]], current.[[Value]])
isfalse
. b. Goto VALIDATED. - Else,
IsAccessorDescriptor(current)
andIsAccessorDescriptor(Desc)
are bothtrue
so, a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Goto REJECT, if the[[Set]]
field ofDesc
is present andSameValue(Desc.[[Set]], current.[[Set]])
isfalse
. 2. Goto REJECT, if the[[Get]]
field ofDesc
is present andSameValue(Desc.[[Get]], current.[[Get]])
isfalse
. b. Goto VALIDATED. - VALIDATED: For each attribute field of
Desc
that is present, set the correspondingly named attribute of the property namedP
of objectO
to the value of the field. - SUCCESS: Return
true
. - REJECT: If
Throw
istrue
, then throw aTypeError
exception, otherwise returnfalse
.
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 exotic behavior, so no check is made for exotic behavior):
- Let
oldLenDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
passing"length"
as the argument. The result will never beundefined
or an accessor descriptor becauseArray
objects are created with a length data property that cannot be deleted or reconfigured. - Let
oldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) - If
P
is"length"
, then a. If the[[Value]]
field ofDesc
is absent, then 1. Return the result of calling the default[[DefineOwnProperty]]
internal method (E5 Section 8.12.9) onO
passing"length"
,Desc
, andThrow
as arguments. b. LetnewLenDesc
be a copy ofDesc
. c. LetnewLen
beToUint32(Desc.[[Value]])
. d. IfnewLen
is not equal toToNumber(Desc.[[Value]])
, throw aRangeError
exception. e. SetnewLenDesc.[[Value]]
tonewLen
. f. IfnewLen
>=oldLen
, then 1. Return the result of calling the default[[DefineOwnProperty]]
internal method (E5 Section 8.12.9) onO
passing"length"
,newLenDesc
, andThrow
as arguments. g. Reject ifoldLenDesc.[[Writable]]
isfalse
. h. IfnewLenDesc.[[Writable]]
is absent or has the valuetrue
, letnewWritable
betrue
. i. Else, 1. Need to defer setting the[[Writable]]
attribute tofalse
in case any elements cannot be deleted. 2. LetnewWritable
befalse
. 3. SetnewLenDesc.[[Writable]]
totrue
. j. Letsucceeded
be the result of calling the default[[DefineOwnProperty]]
internal method (E5 Section 8.12.9) onO
passing"length"
,newLenDesc
, andThrow
as arguments. k. Ifsucceeded
isfalse
, returnfalse
. l. WhilenewLen
<oldLen
repeat, 1. SetoldLen
tooldLen - 1
. 2. LetcanDelete
be the result of calling the[[Delete]]
internal method ofO
passingToString(oldLen)
andfalse
as arguments. 3. IfcanDelete
isfalse
, then: a. SetnewLenDesc.[[Value]
tooldLen+1
. b. IfnewWritable
isfalse
, setnewLenDesc.[[Writable]
tofalse
. c. Call the default[[DefineOwnProperty]]
internal method (E5 Section 8.12.9) onO
passing"length"
,newLenDesc
, andfalse
as arguments. d. Reject. m. IfnewWritable
isfalse
, then 1. Call the default[[DefineOwnProperty]]
internal method (E5 Section 8.12.9) onO
passing"length"
, Property Descriptor{[[Writable]]: false}
, andfalse
as arguments. This call will always returntrue
. n. Returntrue
. - Else if
P
is an array index (E5 Section 15.4), then: a. Letindex
beToUint32(P)
. b. Reject ifindex
>=oldLen
andoldLenDesc.[[Writable]]
isfalse
. c. Letsucceeded
be the result of calling the default[[DefineOwnProperty]]
internal method (E5 Section 8.12.9) onO
passingP
,Desc
, andfalse
as arguments. d. Reject ifsucceeded
isfalse
. e. Ifindex
>=oldLen
: 1. SetoldLenDesc.[[Value]]
toindex + 1
. 2. Call the default[[DefineOwnProperty]]
internal method (E5 Section 8.12.9) onO
passing"length"
,oldLenDesc
, andfalse
as arguments. This call will always returntrue
. f. Returntrue
. - Return the result of calling the default
[[DefineOwnProperty]]
internal method (E5 Section 8.12.9) onO
passingP
,Desc
, andThrow
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 becanDelete
and the check in step iii should read "ifcanDelete
isfalse
...". - 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]]
tonewLen
, and then jumping to step 5. - Note: the caller's view of
Desc
must not change, soDesc
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 toDesc
, and keepingnewWritable
for later. - Jumping to step 5.
- Adding a post-step to the default algorithm for steps 3.k - 3.m.
- Doing the
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, andDesc.[[Writable]]
is fudged back to its original, requested value.This is equivalent to:
- Fudging both
[[Value]]
and[[Writable]]
ofDesc
. - Jumping to step 5.
- Fudging both
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 butlength
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.
- Doing the pre-checks for index vs.
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 arraylength
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 ofDesc.[[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 settingpendingWriteProtect
(set ifnewWritable
isfalse
) - If new length is shorter than old length, run the default algorithm successfully first before touching array elements
- If
- Check for
P
being a valid array index, and:- Pre-checks for index vs.
length
- Pre-checks for index vs.
- Check for
- 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.
- Check whether we have a pending array "shortening", i.e.
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 exotic behavior
Incorporating the approach for adding a pre- and post-processing phase we get something like:
- Set
pendingWriteProtect
tofalse
. - If
O
is not anArray
object, goto SKIPARRAY. - Let
oldLenDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
passing"length"
as the argument. The result will never beundefined
or an accessor descriptor becauseArray
objects are created with a length data property that cannot be deleted or reconfigured. - Let
oldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) - If
P
is"length"
, then a. If the[[Value]]
field ofDesc
is absent, then goto SKIPARRAY. b. LetnewLen
beToUint32(Desc.[[Value]])
. c. IfnewLen
is not equal toToNumber(Desc.[[Value]])
, goto REJECTRANGE. d. SetDesc.[[Value]]
tonewLen
. e. IfnewLen
>=oldLen
, then goto SKIPARRAY. f. Goto REJECT ifoldLenDesc.[[Writable]]
isfalse
. g. IfDesc.[[Writable]]
has the valuefalse
: 1. Need to defer setting the[[Writable]]
attribute tofalse
in case any elements cannot be deleted. 2. SetpendingWriteProtect
totrue
. 3. SetDesc.[[Writable]]
totrue
. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - Else if
P
is an array index (E5 Section 15.4), then: a. Letindex
beToUint32(P)
. b. Goto REJECT ifindex
>=oldLen
andoldLenDesc.[[Writable]]
isfalse
. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - SKIPARRAY: Let
current
be the result of calling the[[GetOwnProperty]]
internal method ofO
with property nameP
. - Let
extensible
be the value of the[[Extensible]]
internal property ofO
. - If
current
isundefined
: a. Ifextensible
isfalse
, then goto REJECT. b. IfIsGenericDescriptor(Desc)
orIsDataDescriptor(Desc)
istrue
, then 1. Create an own data property namedP
of objectO
whose[[Value]]
,[[Writable]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
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 namedP
of objectO
whose[[Get]]
,[[Set]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
is absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. - Goto SUCCESS, if every field in
Desc
also occurs incurrent
and the value of every field inDesc
is the same value as the corresponding field incurrent
when compared using theSameValue
algorithm (E5 Section 9.12). (This also covers the case where every field inDesc
is absent.) - If the
[[Configurable]]
field ofcurrent
isfalse
then a. Goto REJECT, if the[[Configurable]]
field ofDesc
is true. b. Goto REJECT, if the[[Enumerable]]
field ofDesc
is present and the[[Enumerable]]
fields ofcurrent
andDesc
are the Boolean negation of each other. - If
IsGenericDescriptor(Desc)
istrue
, then goto VALIDATED. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
have different results, then a. Goto REJECT, if the[[Configurable]]
field ofcurrent
isfalse
. b. IfIsDataDescriptor(current)
is true, then 1. Convert the property namedP
of objectO
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 namedP
of objectO
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. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
are both true, then a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Goto REJECT, if the[[Writable]]
field ofcurrent
isfalse
and the[[Writable]]
field ofDesc
istrue
. 2. Goto REJECT, If the[[Writable]]
field ofcurrent
isfalse
, and the[[Value]]
field ofDesc
is present, andSameValue(Desc.[[Value]], current.[[Value]])
isfalse
. b. Goto VALIDATED. - Else,
IsAccessorDescriptor(current)
andIsAccessorDescriptor(Desc)
are bothtrue
so, a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Goto REJECT, if the[[Set]]
field ofDesc
is present andSameValue(Desc.[[Set]], current.[[Set]])
isfalse
. 2. Goto REJECT, if the[[Get]]
field ofDesc
is present andSameValue(Desc.[[Get]], current.[[Get]])
isfalse
. b. Goto VALIDATED. - VALIDATED: For each attribute field of
Desc
that is present, set the correspondingly named attribute of the property namedP
of objectO
to the value of the field. - SUCCESS: If
O
is anArray
object: a. IfP
is"length"
, andnewLen
<oldLen
, then: 1. LetshortenSucceeded
,finalLen
be the result of calling the internal helperShortenArray()
witholdLen
andnewLen
. 2. Update the property ("length"
) value tofinalLen
. 3. IfpendingWriteProtect
istrue
, update the property ("length"
) to have[[Writable]] = false
. 4. Goto REJECT, ifshortenSucceeded
isfalse
. b. IfP
is an array index andindex
>=oldLen
: 1. Update the"length"
property ofO
to the valueindex + 1
. This always succeeds, because we've checked in the pre-step that the"length"
is writable, and sinceP
is an array index property, the length must still be writable here. - Return
true
. - REJECT: If
Throw
istrue
, then throw aTypeError
exception, otherwise returnfalse
. - REJECTRANGE: Throw a
RangeError
exception. Note that this is unconditional (thrown even ifThrow
isfalse
).
Adding arguments object exotic behavior
The exotic [[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:
- Set
pendingWriteProtect
tofalse
. - If
O
is not anArray
object, goto SKIPARRAY. - Let
oldLenDesc
be the result of calling the[[GetOwnProperty]]
internal method ofO
passing"length"
as the argument. The result will never beundefined
or an accessor descriptor becauseArray
objects are created with a length data property that cannot be deleted or reconfigured. - Let
oldLen
beoldLenDesc.[[Value]]
. (Note thatoldLen
is guaranteed to be a unsigned 32-bit integer.) - If
P
is"length"
, then a. If the[[Value]]
field ofDesc
is absent, then goto SKIPARRAY. b. LetnewLen
beToUint32(Desc.[[Value]])
. c. IfnewLen
is not equal toToNumber(Desc.[[Value]])
, goto REJECTRANGE. d. SetDesc.[[Value]]
tonewLen
. e. IfnewLen
>=oldLen
, then goto SKIPARRAY. f. Goto REJECT ifoldLenDesc.[[Writable]]
isfalse
. g. IfDesc.[[Writable]]
has the valuefalse
: 1. Need to defer setting the[[Writable]]
attribute tofalse
in case any elements cannot be deleted. 2. SetpendingWriteProtect
totrue
. 3. SetDesc.[[Writable]]
totrue
. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - Else if
P
is an array index (E5 Section 15.4), then: a. Letindex
beToUint32(P)
. b. Goto REJECT ifindex
>=oldLen
andoldLenDesc.[[Writable]]
isfalse
. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - SKIPARRAY: Let
current
be the result of calling the[[GetOwnProperty]]
internal method ofO
with property nameP
. - Let
extensible
be the value of the[[Extensible]]
internal property ofO
. - If
current
isundefined
: a. Ifextensible
isfalse
, then goto REJECT. b. IfIsGenericDescriptor(Desc)
orIsDataDescriptor(Desc)
istrue
, then 1. Create an own data property namedP
of objectO
whose[[Value]]
,[[Writable]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
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 namedP
of objectO
whose[[Get]]
,[[Set]]
,[[Enumerable]]
and[[Configurable]]
attribute values are described byDesc
. If the value of an attribute field ofDesc
is absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. - Goto SUCCESS, if every field in
Desc
also occurs incurrent
and the value of every field inDesc
is the same value as the corresponding field incurrent
when compared using theSameValue
algorithm (E5 Section 9.12). (This also covers the case where every field inDesc
is absent.) - If the
[[Configurable]]
field ofcurrent
isfalse
then a. Goto REJECT, if the[[Configurable]]
field ofDesc
is true. b. Goto REJECT, if the[[Enumerable]]
field ofDesc
is present and the[[Enumerable]]
fields ofcurrent
andDesc
are the Boolean negation of each other. - If
IsGenericDescriptor(Desc)
istrue
, then goto VALIDATED. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
have different results, then a. Goto REJECT, if the[[Configurable]]
field ofcurrent
isfalse
. b. IfIsDataDescriptor(current)
is true, then 1. Convert the property namedP
of objectO
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 namedP
of objectO
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. - Else, if
IsDataDescriptor(current)
andIsDataDescriptor(Desc)
are both true, then a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Goto REJECT, if the[[Writable]]
field ofcurrent
isfalse
and the[[Writable]]
field ofDesc
istrue
. 2. Goto REJECT, If the[[Writable]]
field ofcurrent
isfalse
, and the[[Value]]
field ofDesc
is present, andSameValue(Desc.[[Value]], current.[[Value]])
isfalse
. b. Goto VALIDATED. - Else,
IsAccessorDescriptor(current)
andIsAccessorDescriptor(Desc)
are bothtrue
so, a. If the[[Configurable]]
field ofcurrent
isfalse
, then 1. Goto REJECT, if the[[Set]]
field ofDesc
is present andSameValue(Desc.[[Set]], current.[[Set]])
isfalse
. 2. Goto REJECT, if the[[Get]]
field ofDesc
is present andSameValue(Desc.[[Get]], current.[[Get]])
isfalse
. b. Goto VALIDATED. - VALIDATED: For each attribute field of
Desc
that is present, set the correspondingly named attribute of the property namedP
of objectO
to the value of the field. - SUCCESS: If
O
is anArray
object: a. IfP
is"length"
, andnewLen
<oldLen
, then: 1. LetshortenSucceeded
,finalLen
be the result of calling the internal helperShortenArray()
witholdLen
andnewLen
. 2. Update the property ("length"
) value tofinalLen
. 3. IfpendingWriteProtect
istrue
, update the property ("length"
) to have[[Writable]] = false
. 4. Goto REJECT, ifshortenSucceeded
isfalse
. b. IfP
is an array index andindex
>=oldLen
: 1. Update the"length"
property ofO
to the valueindex + 1
. This always succeeds, because we've checked in the pre-step that the"length"
is writable, and sinceP
is an array index property, the length must still be writable here. - If
O
is an arguments object which has a[[ParameterMap]]
internal property: a. Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then: 1. IfIsAccessorDescriptor(Desc)
istrue
, then: a. Call the[[Delete]]
internal method ofmap
passingP
, andfalse
as the arguments. (This removes the magic binding forP
.) 2. Else (Desc
may be generic or data descriptor): a. IfDesc.[[Value]]
is present, then: 1. Call the[[Put]]
internal method ofmap
passingP
,Desc.[[Value]]
, andThrow
as the arguments. (This updates the bound variable value.) b. IfDesc.[[Writable]]
is present and its value isfalse
, then: 1. Call the[[Delete]]
internal method ofmap
passingP
andfalse
as arguments. (This removes the magic binding forP
, and must happen after a possible update of the variable value.) - Return
true
. - REJECT: If
Throw
istrue
, then throw aTypeError
exception, otherwise returnfalse
. - REJECTRANGE: Throw a
RangeError
exception. Note that this is unconditional (thrown even ifThrow
isfalse
).
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
- Let
desc
be the result of calling the[[GetOwnProperty]]
internal method ofO
with property nameP
. - If
desc
isundefined
, then returntrue
. - If
desc.[[Configurable]]
istrue
, then a. Remove the own property with nameP
fromO
. b. Returntrue
. - Else if
Throw
is true, then throw aTypeError
exception. - Return
false
.
Adding arguments object exotic behavior
The exotic [[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:
- Let
desc
be the result of calling the[[GetOwnProperty]]
internal method ofO
with property nameP
. - If
desc
isundefined
, then goto SUCCESS. - If
desc.[[Configurable]]
istrue
, then a. Remove the own property with nameP
fromO
. b. Goto SUCCESS. - Else if
Throw
is true, then throw aTypeError
exception. - Return
false
. - SUCCESS: If
O
is an arguments object which has a[[ParameterMap]]
internal property: a. Letmap
be the value of the[[ParameterMap]]
internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]
internal method ofmap
passingP
as the argument is notundefined
, then: 1. Call the[[Delete]]
internal method ofmap
passingP
, andfalse
as the arguments. (This removes the magic binding forP
.) - Return
true
.
Notes:
- In steps 2, if
desc
isundefined
, 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 aSpecOp(any)
->Boolean
internal method. OnlyFunction
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"):
- If
Type(V)
is not anObject
, returnfalse
. - Let
O
be the result of calling the[[Get]]
internal method ofF
with property name"prototype"
. (Note: this is the external prototype, not the internal one.) - If
Type(O)
is notObject
, throw aTypeError
exception. - Repeat a. Let
V
be the value of the[[Prototype]]
internal property ofV
. b. IfV
isnull
, returnfalse
. c. IfO
andV
refer to the same object, returntrue
.
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:
- Let
target
be the value ofF
's[[TargetFunction]]
internal property. - If
target
has no[[HasInstance]]
internal method, aTypeError
exception is thrown. - Return the result of calling the
[[HasInstance]]
internal method oftarget
providingV
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:
- While
F
is a bound function: a. SetF
to the value ofF
's[[TargetFunction]]
internal property. b. IfF
has no[[HasInstance]]
internal method, throw aTypeError
exception. (Note:F
can be another bound function, so we loop until we find the non-bound actual function.) - If
Type(V)
is not anObject
, returnfalse
. - Let
O
be the result of calling the[[Get]]
internal method ofF
with property name"prototype"
. (Note: this is the external prototype, not the internal one.) - If
Type(O)
is notObject
, throw aTypeError
exception. - Repeat a. Let
V
be the value of the[[Prototype]]
internal property ofV
. b. IfV
isnull
, returnfalse
. c. IfO
andV
refer to the same object, returntrue
.
Final version
(See above, currently no additional cleanup.)