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
Odoesn't have an own property with nameP, returnundefined. - Let
Dbe a newly created Property Descriptor with no fields. - Let
XbeO's own property named P. - If
Xis 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
Xis 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
Odoesn't have an own property with nameP: a. IfOis not aStringinstance, returnundefined. b. (Stringobject exotic behavior.) Letstrbe the String value of the[[PrimitiveValue]]internal property ofOandlenbe the number of characters instr. c. IfPis"length", return a Property Descriptor with the values: -[[Value]]: len(a number) -[[Enumerable]]: false-[[Writable]]: false-[[Configurable]]: falsed. IfPis not an array index (E5 Section 15.4), returnundefined. e. LetindexbeToUint32(P). f. Iflen<=index, returnundefined. g. LetresultStrbe a string of length 1, containing one character fromstr, specifically the character at positionindex, where the first (leftmost) character instris 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
Dbe a newly created Property Descriptor with no fields. - Let
XbeO's own property namedP. - If
Xis 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
Xis 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
Odoesn't have an own property with nameP: a. IfOis not aStringinstance, returnundefined. b. (Stringobject exotic behavior.) Letstrbe the String value of the[[PrimitiveValue]]internal property ofOandlenbe the number of characters instr. c. IfPis"length", return a Property Descriptor with the values: -[[Value]]: len(a number) -[[Enumerable]]: false-[[Writable]]: false-[[Configurable]]: falsed. IfPis not an array index (E5 Section 15.4), returnundefined. e. Else letindexbeToUint32(P). f. Iflen<=index, returnundefined. g. LetresultStrbe a string of length 1, containing one character fromstr, specifically the character at positionindex, where the first (leftmost) character instris 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
Dbe a newly created Property Descriptor with no fields. - Let
XbeO's own property namedP. - If
Xis 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
Xis 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
Ois anargumentsobject which contains a[[ParameterMap]]internal property: a. (Arguments object exotic behavior.) Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. LetisMappedbe the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument. c. If the value ofisMappedis notundefined, then: 1. SetD.[[Value]]to the result of calling the[[Get]]internal method ofmappassingPas the argument. - Return
D.
Notes:
- Step 1.b: if the object is a
Stringobject, there is no need for the arguments object exotic behavior check in step 8: an object can never be aStringobject 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
XbeO's own property namedP. IfOdoesn't have an own property with nameP: a. IfOis not aStringinstance, returnundefined. b. (Stringobject exotic behavior.) Letstrbe the String value of the[[PrimitiveValue]]internal property ofOandlenbe the number of characters instr. c. IfPis"length": 1. Return a Property Descriptor with the values: -[[Value]]: len(a primitive number) -[[Enumerable]]: false-[[Writable]]: false-[[Configurable]]: falsed. IfPis an array index (E5 Section 15.4): 1. LetindexbeToUint32(P). 2. Ifindex<len, return a Property Descriptor with the values: -[[Value]]:a primitive string of length 1, containing one character fromstrat positionindex(zero based index) -[[Enumerable]]: true-[[Writable]]: false-[[Configurable]]: falsee. Returnundefined. - Let
Dbe a newly created Property Descriptor filled as follows: a. IfXis a data property: 1. SetD.[[Value]]to the value ofX's[[Value]]attribute. 2. SetD.[[Writable]]to the value ofX's[[Writable]]attribute. b. ElseXis 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
Ois anargumentsobject which contains a[[ParameterMap]]internal property: a. (Arguments object exotic behavior.) Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined, then: 1. SetD.[[Value]]to the result of calling the[[Get]]internal method ofmappassingPas 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
descbe the result of calling the[[GetProperty]]internal method ofOwith property nameP. - If
descisundefined, returnundefined. - If
IsDataDescriptor(desc)istrue, returndesc.[[Value]]. - Otherwise,
IsAccessorDescriptor(desc)must betrueso, letgetterbedesc.[[Get]]. - If
getterisundefined, returnundefined. - Return the result calling the
[[Call]]internal method ofgetterprovidingOas thethisvalue 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
descbe the result of calling the[[GetProperty]]internal method ofOwith property nameP. - If
descisundefined, returnundefined. - If
IsDataDescriptor(desc)istrue: a. Letresbedesc.[[Value]]. - Otherwise,
IsAccessorDescriptor(desc)must betrue: a. Letgetterbedesc.[[Get]]. b. Ifgetterisundefined, returnundefined. c. Else letresbe the result of calling the[[Call]]internal method ofgetterprovidingOas thethisvalue and providing no arguments. - If
Ois aFunctionobject,Pis"caller", andresis a strict modeFunctionobject, throw aTypeErrorexception. - 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
Pis magically bound to an identifier (through the[[ParameterMap]]) the default[[Get]]is bypassed entirely and the property value is read. (Note that the propertyPmust be a data property in this case, so no side effects are lost by this behavior.) - If the property name
Pis not bound to an identifier, the"caller"property has exotic behavior essentially identical to that ofFunction.
These can be incorporated as follows:
- If
Ois anargumentsobject which contains a[[ParameterMap]]internal property: a. (Arguments object exotic behavior.) Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. LetisMappedbe the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument. c. If the value ofisMappedis notundefined, then: 1. Return the result of calling the[[Get]]internal method ofmappassingPas the argument. - Let
descbe the result of calling the[[GetProperty]]internal method ofOwith property nameP. - If
descisundefined, returnundefined. - If
IsDataDescriptor(desc)istrue: a. Letresbedesc.[[Value]]. - Otherwise,
IsAccessorDescriptor(desc)must betrue: a. Letgetterbedesc.[[Get]]. b. Ifgetterisundefined, returnundefined. c. Else letresbe the result of calling the[[Call]]internal method ofgetterprovidingOas thethisvalue and providing no arguments. - If
Ois aFunctionobject or anargumentsobject which contains a[[ParameterMap]]internal property: a. (Arguments or Function object exotic behavior.) IfPis"caller"andresis a strict modeFunctionobject, throw aTypeErrorexception. - Return
res.
Note:
- Step 1 can match only when
Pis 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
Ois anargumentsobject which contains a[[ParameterMap]]internal property: a. (Arguments object exotic behavior.) Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined: 1. Return the result of calling the[[Get]]internal method ofmappassingPas the argument. - Let
descbe the result of calling the[[GetProperty]]internal method ofOwith property nameP. - If
descisundefined, returnundefined. - If
IsDataDescriptor(desc)istrue: a. Letresbedesc.[[Value]]. - Otherwise,
IsAccessorDescriptor(desc)must betrue: a. Letgetterbedesc.[[Get]]. b. Ifgetterisundefined, returnundefined. c. Else letresbe the result of calling the[[Call]]internal method ofgetterprovidingOas thethisvalue and providing no arguments. - If
Ois aFunctionobject or anargumentsobject which contains a[[ParameterMap]]internal property: a. (Arguments or Function object exotic behavior.) IfPis"caller"andresis a strict modeFunctionobject, throw aTypeErrorexception. - 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
currentbe the result of calling the[[GetOwnProperty]]internal method ofOwith property nameP. - Let
extensiblebe the value of the[[Extensible]]internal property ofO. - If
currentisundefinedandextensibleisfalse, then Reject. - If
currentisundefinedandextensibleistrue, then a. IfIsGenericDescriptor(Desc)orIsDataDescriptor(Desc)istrue, then 1. Create an own data property namedPof objectOwhose[[Value]],[[Writable]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. b. Else,Descmust be an accessor Property Descriptor so, 1. Create an own accessor property namedPof objectOwhose[[Get]],[[Set]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. c. Returntrue. - Return
trueif every field inDescis absent. - Return
true, if every field inDescalso occurs incurrentand the value of every field inDescis the same value as the corresponding field incurrentwhen compared using theSameValuealgorithm (E5 Section 9.12). - If the
[[Configurable]]field ofcurrentisfalsethen a. Reject, if the[[Configurable]]field ofDescis true. b. Reject, if the[[Enumerable]]field ofDescis present and the[[Enumerable]]fields ofcurrentandDescare 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 ofcurrentisfalse. b. IfIsDataDescriptor(current)is true, then 1. Convert the property namedPof objectOfrom 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 namedPof objectOfrom 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 ofcurrentisfalse, then 1. Reject, if the[[Writable]]field ofcurrentisfalseand the[[Writable]]field ofDescistrue. 2. If the[[Writable]]field ofcurrentisfalse, then a. Reject, if the[[Value]]field ofDescis present andSameValue(Desc.[[Value]], current.[[Value]])isfalse. b. else, the[[Configurable]]field ofcurrentistrue, so any change is acceptable. - Else,
IsAccessorDescriptor(current)andIsAccessorDescriptor(Desc)are bothtrueso, a. If the[[Configurable]]field ofcurrentisfalse, then 1. Reject, if the[[Set]]field ofDescis present andSameValue(Desc.[[Set]], current.[[Set]])isfalse. 2. Reject, if the[[Get]]field ofDescis present andSameValue(Desc.[[Get]], current.[[Get]])isfalse. - For each attribute field of
Descthat is present, set the correspondingly named attribute of the property namedPof objectOto 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
currentbe the result of calling the[[GetOwnProperty]]internal method ofOwith property nameP. - Let
extensiblebe the value of the[[Extensible]]internal property ofO. - If
currentisundefined: a. Ifextensibleisfalse, then goto REJECT. b. IfIsGenericDescriptor(Desc)orIsDataDescriptor(Desc)istrue, then 1. Create an own data property namedPof objectOwhose[[Value]],[[Writable]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. c. Else,Descmust be an accessor Property Descriptor so, 1. Create an own accessor property namedPof objectOwhose[[Get]],[[Set]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. - Goto SUCCESS, if every field in
Descalso occurs incurrentand the value of every field inDescis the same value as the corresponding field incurrentwhen compared using theSameValuealgorithm (E5 Section 9.12). (This also covers the case where every field inDescis absent.) - If the
[[Configurable]]field ofcurrentisfalsethen a. Goto REJECT, if the[[Configurable]]field ofDescis true. b. Goto REJECT, if the[[Enumerable]]field ofDescis present and the[[Enumerable]]fields ofcurrentandDescare 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 ofcurrentisfalse. b. IfIsDataDescriptor(current)is true, then 1. Convert the property namedPof objectOfrom 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 namedPof objectOfrom 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 ofcurrentisfalse, then 1. Goto REJECT, if the[[Writable]]field ofcurrentisfalseand the[[Writable]]field ofDescistrue. 2. Goto REJECT, If the[[Writable]]field ofcurrentisfalse, and the[[Value]]field ofDescis present, andSameValue(Desc.[[Value]], current.[[Value]])isfalse. b. Goto VALIDATED. - Else,
IsAccessorDescriptor(current)andIsAccessorDescriptor(Desc)are bothtrueso, a. If the[[Configurable]]field ofcurrentisfalse, then 1. Goto REJECT, if the[[Set]]field ofDescis present andSameValue(Desc.[[Set]], current.[[Set]])isfalse. 2. Goto REJECT, if the[[Get]]field ofDescis present andSameValue(Desc.[[Get]], current.[[Get]])isfalse. b. Goto VALIDATED. - VALIDATED: For each attribute field of
Descthat is present, set the correspondingly named attribute of the property namedPof objectOto the value of the field. - SUCCESS: Return
true. - REJECT: If
Throwistrue, then throw aTypeErrorexception, 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
oldLenDescbe the result of calling the[[GetOwnProperty]]internal method ofOpassing"length"as the argument. The result will never beundefinedor an accessor descriptor becauseArrayobjects are created with a length data property that cannot be deleted or reconfigured. - Let
oldLenbeoldLenDesc.[[Value]]. (Note thatoldLenis guaranteed to be a unsigned 32-bit integer.) - If
Pis"length", then a. If the[[Value]]field ofDescis absent, then 1. Return the result of calling the default[[DefineOwnProperty]]internal method (E5 Section 8.12.9) onOpassing"length",Desc, andThrowas arguments. b. LetnewLenDescbe a copy ofDesc. c. LetnewLenbeToUint32(Desc.[[Value]]). d. IfnewLenis not equal toToNumber(Desc.[[Value]]), throw aRangeErrorexception. e. SetnewLenDesc.[[Value]]tonewLen. f. IfnewLen>=oldLen, then 1. Return the result of calling the default[[DefineOwnProperty]]internal method (E5 Section 8.12.9) onOpassing"length",newLenDesc, andThrowas arguments. g. Reject ifoldLenDesc.[[Writable]]isfalse. h. IfnewLenDesc.[[Writable]]is absent or has the valuetrue, letnewWritablebetrue. i. Else, 1. Need to defer setting the[[Writable]]attribute tofalsein case any elements cannot be deleted. 2. LetnewWritablebefalse. 3. SetnewLenDesc.[[Writable]]totrue. j. Letsucceededbe the result of calling the default[[DefineOwnProperty]]internal method (E5 Section 8.12.9) onOpassing"length",newLenDesc, andThrowas arguments. k. Ifsucceededisfalse, returnfalse. l. WhilenewLen<oldLenrepeat, 1. SetoldLentooldLen - 1. 2. LetcanDeletebe the result of calling the[[Delete]]internal method ofOpassingToString(oldLen)andfalseas arguments. 3. IfcanDeleteisfalse, then: a. SetnewLenDesc.[[Value]tooldLen+1. b. IfnewWritableisfalse, setnewLenDesc.[[Writable]tofalse. c. Call the default[[DefineOwnProperty]]internal method (E5 Section 8.12.9) onOpassing"length",newLenDesc, andfalseas arguments. d. Reject. m. IfnewWritableisfalse, then 1. Call the default[[DefineOwnProperty]]internal method (E5 Section 8.12.9) onOpassing"length", Property Descriptor{[[Writable]]: false}, andfalseas arguments. This call will always returntrue. n. Returntrue. - Else if
Pis an array index (E5 Section 15.4), then: a. LetindexbeToUint32(P). b. Reject ifindex>=oldLenandoldLenDesc.[[Writable]]isfalse. c. Letsucceededbe the result of calling the default[[DefineOwnProperty]]internal method (E5 Section 8.12.9) onOpassingP,Desc, andfalseas arguments. d. Reject ifsucceededisfalse. e. Ifindex>=oldLen: 1. SetoldLenDesc.[[Value]]toindex + 1. 2. Call the default[[DefineOwnProperty]]internal method (E5 Section 8.12.9) onOpassing"length",oldLenDesc, andfalseas arguments. This call will always returntrue. f. Returntrue. - Return the result of calling the default
[[DefineOwnProperty]]internal method (E5 Section 8.12.9) onOpassingP,Desc, andThrowas arguments.
Notes:
- In E5 Section 15.4.5.1 step 3.l.ii - 3.l.iii the temporary variable
cannotDeleteseems to be misused; it should probably becanDeleteand the check in step iii should read "ifcanDeleteisfalse...". - 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
newLenvalidation 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
Descmust not change, soDesccannot be a "pass by reference" value.
Step 3.f.j: Here
newLenvalidation 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
newWritablefudging toDesc, and keepingnewWritablefor 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
lengthbe increased butlengthis 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 arraylengthis 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 newLenvalidation and updating ofDesc.[[Value]]- If new length is not shorter than old length, default algorithm with the modified
Desccan be used - Possible fudging of
Desc.[[Writable]]and check for settingpendingWriteProtect(set ifnewWritableisfalse) - If new length is shorter than old length, run the default algorithm successfully first before touching array elements
- If
- Check for
Pbeing 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.
Pwas"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
Pwas 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 (
falseif 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
pendingWriteProtecttofalse. - If
Ois not anArrayobject, goto SKIPARRAY. - Let
oldLenDescbe the result of calling the[[GetOwnProperty]]internal method ofOpassing"length"as the argument. The result will never beundefinedor an accessor descriptor becauseArrayobjects are created with a length data property that cannot be deleted or reconfigured. - Let
oldLenbeoldLenDesc.[[Value]]. (Note thatoldLenis guaranteed to be a unsigned 32-bit integer.) - If
Pis"length", then a. If the[[Value]]field ofDescis absent, then goto SKIPARRAY. b. LetnewLenbeToUint32(Desc.[[Value]]). c. IfnewLenis 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 tofalsein case any elements cannot be deleted. 2. SetpendingWriteProtecttotrue. 3. SetDesc.[[Writable]]totrue. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - Else if
Pis an array index (E5 Section 15.4), then: a. LetindexbeToUint32(P). b. Goto REJECT ifindex>=oldLenandoldLenDesc.[[Writable]]isfalse. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - SKIPARRAY: Let
currentbe the result of calling the[[GetOwnProperty]]internal method ofOwith property nameP. - Let
extensiblebe the value of the[[Extensible]]internal property ofO. - If
currentisundefined: a. Ifextensibleisfalse, then goto REJECT. b. IfIsGenericDescriptor(Desc)orIsDataDescriptor(Desc)istrue, then 1. Create an own data property namedPof objectOwhose[[Value]],[[Writable]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. c. Else,Descmust be an accessor Property Descriptor so, 1. Create an own accessor property namedPof objectOwhose[[Get]],[[Set]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. - Goto SUCCESS, if every field in
Descalso occurs incurrentand the value of every field inDescis the same value as the corresponding field incurrentwhen compared using theSameValuealgorithm (E5 Section 9.12). (This also covers the case where every field inDescis absent.) - If the
[[Configurable]]field ofcurrentisfalsethen a. Goto REJECT, if the[[Configurable]]field ofDescis true. b. Goto REJECT, if the[[Enumerable]]field ofDescis present and the[[Enumerable]]fields ofcurrentandDescare 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 ofcurrentisfalse. b. IfIsDataDescriptor(current)is true, then 1. Convert the property namedPof objectOfrom 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 namedPof objectOfrom 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 ofcurrentisfalse, then 1. Goto REJECT, if the[[Writable]]field ofcurrentisfalseand the[[Writable]]field ofDescistrue. 2. Goto REJECT, If the[[Writable]]field ofcurrentisfalse, and the[[Value]]field ofDescis present, andSameValue(Desc.[[Value]], current.[[Value]])isfalse. b. Goto VALIDATED. - Else,
IsAccessorDescriptor(current)andIsAccessorDescriptor(Desc)are bothtrueso, a. If the[[Configurable]]field ofcurrentisfalse, then 1. Goto REJECT, if the[[Set]]field ofDescis present andSameValue(Desc.[[Set]], current.[[Set]])isfalse. 2. Goto REJECT, if the[[Get]]field ofDescis present andSameValue(Desc.[[Get]], current.[[Get]])isfalse. b. Goto VALIDATED. - VALIDATED: For each attribute field of
Descthat is present, set the correspondingly named attribute of the property namedPof objectOto the value of the field. - SUCCESS: If
Ois anArrayobject: a. IfPis"length", andnewLen<oldLen, then: 1. LetshortenSucceeded,finalLenbe the result of calling the internal helperShortenArray()witholdLenandnewLen. 2. Update the property ("length") value tofinalLen. 3. IfpendingWriteProtectistrue, update the property ("length") to have[[Writable]] = false. 4. Goto REJECT, ifshortenSucceededisfalse. b. IfPis an array index andindex>=oldLen: 1. Update the"length"property ofOto the valueindex + 1. This always succeeds, because we've checked in the pre-step that the"length"is writable, and sincePis an array index property, the length must still be writable here. - Return
true. - REJECT: If
Throwistrue, then throw aTypeErrorexception, otherwise returnfalse. - REJECTRANGE: Throw a
RangeErrorexception. Note that this is unconditional (thrown even ifThrowisfalse).
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
pendingWriteProtecttofalse. - If
Ois not anArrayobject, goto SKIPARRAY. - Let
oldLenDescbe the result of calling the[[GetOwnProperty]]internal method ofOpassing"length"as the argument. The result will never beundefinedor an accessor descriptor becauseArrayobjects are created with a length data property that cannot be deleted or reconfigured. - Let
oldLenbeoldLenDesc.[[Value]]. (Note thatoldLenis guaranteed to be a unsigned 32-bit integer.) - If
Pis"length", then a. If the[[Value]]field ofDescis absent, then goto SKIPARRAY. b. LetnewLenbeToUint32(Desc.[[Value]]). c. IfnewLenis 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 tofalsein case any elements cannot be deleted. 2. SetpendingWriteProtecttotrue. 3. SetDesc.[[Writable]]totrue. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - Else if
Pis an array index (E5 Section 15.4), then: a. LetindexbeToUint32(P). b. Goto REJECT ifindex>=oldLenandoldLenDesc.[[Writable]]isfalse. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.) - SKIPARRAY: Let
currentbe the result of calling the[[GetOwnProperty]]internal method ofOwith property nameP. - Let
extensiblebe the value of the[[Extensible]]internal property ofO. - If
currentisundefined: a. Ifextensibleisfalse, then goto REJECT. b. IfIsGenericDescriptor(Desc)orIsDataDescriptor(Desc)istrue, then 1. Create an own data property namedPof objectOwhose[[Value]],[[Writable]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. c. Else,Descmust be an accessor Property Descriptor so, 1. Create an own accessor property namedPof objectOwhose[[Get]],[[Set]],[[Enumerable]]and[[Configurable]]attribute values are described byDesc. If the value of an attribute field ofDescis absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS. - Goto SUCCESS, if every field in
Descalso occurs incurrentand the value of every field inDescis the same value as the corresponding field incurrentwhen compared using theSameValuealgorithm (E5 Section 9.12). (This also covers the case where every field inDescis absent.) - If the
[[Configurable]]field ofcurrentisfalsethen a. Goto REJECT, if the[[Configurable]]field ofDescis true. b. Goto REJECT, if the[[Enumerable]]field ofDescis present and the[[Enumerable]]fields ofcurrentandDescare 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 ofcurrentisfalse. b. IfIsDataDescriptor(current)is true, then 1. Convert the property namedPof objectOfrom 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 namedPof objectOfrom 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 ofcurrentisfalse, then 1. Goto REJECT, if the[[Writable]]field ofcurrentisfalseand the[[Writable]]field ofDescistrue. 2. Goto REJECT, If the[[Writable]]field ofcurrentisfalse, and the[[Value]]field ofDescis present, andSameValue(Desc.[[Value]], current.[[Value]])isfalse. b. Goto VALIDATED. - Else,
IsAccessorDescriptor(current)andIsAccessorDescriptor(Desc)are bothtrueso, a. If the[[Configurable]]field ofcurrentisfalse, then 1. Goto REJECT, if the[[Set]]field ofDescis present andSameValue(Desc.[[Set]], current.[[Set]])isfalse. 2. Goto REJECT, if the[[Get]]field ofDescis present andSameValue(Desc.[[Get]], current.[[Get]])isfalse. b. Goto VALIDATED. - VALIDATED: For each attribute field of
Descthat is present, set the correspondingly named attribute of the property namedPof objectOto the value of the field. - SUCCESS: If
Ois anArrayobject: a. IfPis"length", andnewLen<oldLen, then: 1. LetshortenSucceeded,finalLenbe the result of calling the internal helperShortenArray()witholdLenandnewLen. 2. Update the property ("length") value tofinalLen. 3. IfpendingWriteProtectistrue, update the property ("length") to have[[Writable]] = false. 4. Goto REJECT, ifshortenSucceededisfalse. b. IfPis an array index andindex>=oldLen: 1. Update the"length"property ofOto the valueindex + 1. This always succeeds, because we've checked in the pre-step that the"length"is writable, and sincePis an array index property, the length must still be writable here. - If
Ois an arguments object which has a[[ParameterMap]]internal property: a. Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined, then: 1. IfIsAccessorDescriptor(Desc)istrue, then: a. Call the[[Delete]]internal method ofmappassingP, andfalseas the arguments. (This removes the magic binding forP.) 2. Else (Descmay be generic or data descriptor): a. IfDesc.[[Value]]is present, then: 1. Call the[[Put]]internal method ofmappassingP,Desc.[[Value]], andThrowas the arguments. (This updates the bound variable value.) b. IfDesc.[[Writable]]is present and its value isfalse, then: 1. Call the[[Delete]]internal method ofmappassingPandfalseas arguments. (This removes the magic binding forP, and must happen after a possible update of the variable value.) - Return
true. - REJECT: If
Throwistrue, then throw aTypeErrorexception, otherwise returnfalse. - REJECTRANGE: Throw a
RangeErrorexception. Note that this is unconditional (thrown even ifThrowisfalse).
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
descbe the result of calling the[[GetOwnProperty]]internal method ofOwith property nameP. - If
descisundefined, then returntrue. - If
desc.[[Configurable]]istrue, then a. Remove the own property with namePfromO. b. Returntrue. - Else if
Throwis true, then throw aTypeErrorexception. - 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
descbe the result of calling the[[GetOwnProperty]]internal method ofOwith property nameP. - If
descisundefined, then goto SUCCESS. - If
desc.[[Configurable]]istrue, then a. Remove the own property with namePfromO. b. Goto SUCCESS. - Else if
Throwis true, then throw aTypeErrorexception. - Return
false. - SUCCESS: If
Ois an arguments object which has a[[ParameterMap]]internal property: a. Letmapbe the value of the[[ParameterMap]]internal property of the arguments object. b. If the result of calling the[[GetOwnProperty]]internal method ofmappassingPas the argument is notundefined, then: 1. Call the[[Delete]]internal method ofmappassingP, andfalseas the arguments. (This removes the magic binding forP.) - Return
true.
Notes:
- In steps 2, if
descisundefined, 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)->Booleaninternal method. OnlyFunctionobjects have a[[HasInstance]]method. - Section 11.8.6: the
instanceofoperator, which is the only "caller" for[[HasInstance]]in the E5 specification. - Section 13.2: when
Functionobjects 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
Obe the result of calling the[[Get]]internal method ofFwith property name"prototype". (Note: this is the external prototype, not the internal one.) - If
Type(O)is notObject, throw aTypeErrorexception. - Repeat a. Let
Vbe the value of the[[Prototype]]internal property ofV. b. IfVisnull, returnfalse. c. IfOandVrefer 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
targetbe the value ofF's[[TargetFunction]]internal property. - If
targethas no[[HasInstance]]internal method, aTypeErrorexception is thrown. - Return the result of calling the
[[HasInstance]]internal method oftargetprovidingVas the argument.
Notes:
- In step 3, the
targetmay 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
Fis a bound function: a. SetFto the value ofF's[[TargetFunction]]internal property. b. IfFhas no[[HasInstance]]internal method, throw aTypeErrorexception. (Note:Fcan be another bound function, so we loop until we find the non-bound actual function.) - If
Type(V)is not anObject, returnfalse. - Let
Obe the result of calling the[[Get]]internal method ofFwith property name"prototype". (Note: this is the external prototype, not the internal one.) - If
Type(O)is notObject, throw aTypeErrorexception. - Repeat a. Let
Vbe the value of the[[Prototype]]internal property ofV. b. IfVisnull, returnfalse. c. IfOandVrefer to the same object, returntrue.
Final version
(See above, currently no additional cleanup.)