Skip to content

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

  1. If O doesn't have an own property with name P, return undefined.
  2. Let D be a newly created Property Descriptor with no fields.
  3. Let X be O's own property named P.
  4. If X is a data property, then a. Set D.[[Value]] to the value of X's [[Value]] attribute. b. Set D.[[Writable]] to the value of X's [[Writable]] attribute.
  5. Else X is an accessor property, so a. Set D.[[Get]] to the value of X's [[Get]] attribute. b. Set D.[[Set]] to the value of X's [[Set]] attribute.
  6. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
  7. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
  8. Return D.

Adding String object 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:

  1. If O doesn't have an own property with name P: a. If O is not a String instance, return undefined. b. (String object exotic behavior.) Let str be the String value of the [[PrimitiveValue]] internal property of O and len be the number of characters in str. c. If P is "length", return a Property Descriptor with the values: - [[Value]]: len (a number) - [[Enumerable]]: false - [[Writable]]: false - [[Configurable]]: false d. If P is not an array index (E5 Section 15.4), return undefined. e. Let index be ToUint32(P). f. If len <= index, return undefined. g. Let resultStr be a string of length 1, containing one character from str, specifically the character at position index, where the first (leftmost) character in str is considered to be at position 0, the next one at position 1, and so on. h. Return a Property Descriptor with the values: - [[Value]]: resultStr - [[Enumerable]]: true - [[Writable]]: false - [[Configurable]]: false
  2. Let D be a newly created Property Descriptor with no fields.
  3. Let X be O's own property named P.
  4. If X is a data property, then a. Set D.[[Value]] to the value of X's [[Value]] attribute. b. Set D.[[Writable]] to the value of X's [[Writable]] attribute.
  5. Else X is an accessor property, so a. Set D.[[Get]] to the value of X's [[Get]] attribute. b. Set D.[[Set]] to the value of X's [[Set]] attribute.
  6. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
  7. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
  8. Return D.

Adding arguments object 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:

  1. If O doesn't have an own property with name P: a. If O is not a String instance, return undefined. b. (String object exotic behavior.) Let str be the String value of the [[PrimitiveValue]] internal property of O and len be the number of characters in str. c. If P is "length", return a Property Descriptor with the values: - [[Value]]: len (a number) - [[Enumerable]]: false - [[Writable]]: false - [[Configurable]]: false d. If P is not an array index (E5 Section 15.4), return undefined. e. Else let index be ToUint32(P). f. If len <= index, return undefined. g. Let resultStr be a string of length 1, containing one character from str, specifically the character at position index, where the first (leftmost) character in str is considered to be at position 0, the next one at position 1, and so on. h. Return a Property Descriptor with the values: - [[Value]]: resultStr - [[Enumerable]]: true - [[Writable]]: false - [[Configurable]]: false
  2. Let D be a newly created Property Descriptor with no fields.
  3. Let X be O's own property named P.
  4. If X is a data property, then a. Set D.[[Value]] to the value of X's [[Value]] attribute. b. Set D.[[Writable]] to the value of X's [[Writable]] attribute.
  5. Else X is an accessor property, so a. Set D.[[Get]] to the value of X's [[Get]] attribute. b. Set D.[[Set]] to the value of X's [[Set]] attribute.
  6. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute.
  7. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
  8. If O is an arguments object which contains a [[ParameterMap]] internal property: a. (Arguments object exotic behavior.) Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. Let isMapped be the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument. c. If the value of isMapped is not undefined, then: 1. Set D.[[Value]] to the result of calling the [[Get]] internal method of map passing P as the argument.
  9. Return D.

Notes:

  • Step 1.b: if the object is a String object, there is no need for the arguments object exotic behavior check in step 8: an object can never be a String object and an arguments object simultaenously.
  • Step 8: arguments objects for strict mode functions don't have the 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:

  1. Let X be O's own property named P. If O doesn't have an own property with name P: a. If O is not a String instance, return undefined. b. (String object exotic behavior.) Let str be the String value of the [[PrimitiveValue]] internal property of O and len be the number of characters in str. c. If P is "length": 1. Return a Property Descriptor with the values: - [[Value]]: len (a primitive number) - [[Enumerable]]: false - [[Writable]]: false - [[Configurable]]: false d. If P is an array index (E5 Section 15.4): 1. Let index be ToUint32(P). 2. If index < len, return a Property Descriptor with the values: - [[Value]]: a primitive string of length 1, containing one character from str at position index (zero based index) - [[Enumerable]]: true - [[Writable]]: false - [[Configurable]]: false e. Return undefined.
  2. Let D be a newly created Property Descriptor filled as follows: a. If X is a data property: 1. Set D.[[Value]] to the value of X's [[Value]] attribute. 2. Set D.[[Writable]] to the value of X's [[Writable]] attribute. b. Else X is an accessor property: 1. Set D.[[Get]] to the value of X's [[Get]] attribute. 2. Set D.[[Set]] to the value of X's [[Set]] attribute. c. For either type of property: 1. Set D.[[Enumerable]] to the value of X's [[Enumerable]] attribute. 2. Set D.[[Configurable]] to the value of X's [[Configurable]] attribute.
  3. If O is an arguments object which contains a [[ParameterMap]] internal property: a. (Arguments object exotic behavior.) Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. If the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument is not undefined, then: 1. Set D.[[Value]] to the result of calling the [[Get]] internal method of map passing P as the argument.
  4. Return D.

Notes:

  • Step 3 can be skipped for accessors.

Get

Related E5 sections:

  • E5 Section 8.12.3: default algorithm
  • E5 Section 10.5: arguments object
  • E5 Section 15.3.5.4: Function

Default algorithm

(Note that E5 Section 8.12.3 has broken numbering; fixed below.)

  1. Let desc be the result of calling the [[GetProperty]] internal method of O with property name P.
  2. If desc is undefined, return undefined.
  3. If IsDataDescriptor(desc) is true, return desc.[[Value]].
  4. Otherwise, IsAccessorDescriptor(desc) must be true so, let getter be desc.[[Get]].
  5. If getter is undefined, return undefined.
  6. Return the result calling the [[Call]] internal method of getter providing O as the this value and providing no arguments.

Adding Function object 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:

  1. Let desc be the result of calling the [[GetProperty]] internal method of O with property name P.
  2. If desc is undefined, return undefined.
  3. If IsDataDescriptor(desc) is true: a. Let res be desc.[[Value]].
  4. Otherwise, IsAccessorDescriptor(desc) must be true: a. Let getter be desc.[[Get]]. b. If getter is undefined, return undefined. c. Else let res be the result of calling the [[Call]] internal method of getter providing O as the this value and providing no arguments.
  5. If O is a Function object, P is "caller", and res is a strict mode Function object, throw a TypeError exception.
  6. Return res.

Adding arguments object 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:

  1. If the property name P is magically bound to an identifier (through the [[ParameterMap]]) the default [[Get]] is bypassed entirely and the property value is read. (Note that the property Pmust be a data property in this case, so no side effects are lost by this behavior.)
  2. If the property name P is not bound to an identifier, the "caller" property has exotic behavior essentially identical to that of Function.

These can be incorporated as follows:

  1. If O is an arguments object which contains a [[ParameterMap]] internal property: a. (Arguments object exotic behavior.) Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. Let isMapped be the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument. c. If the value of isMapped is not undefined, then: 1. Return the result of calling the [[Get]] internal method of map passing P as the argument.
  2. Let desc be the result of calling the [[GetProperty]] internal method of O with property name P.
  3. If desc is undefined, return undefined.
  4. If IsDataDescriptor(desc) is true: a. Let res be desc.[[Value]].
  5. Otherwise, IsAccessorDescriptor(desc) must be true: a. Let getter be desc.[[Get]]. b. If getter is undefined, return undefined. c. Else let res be the result of calling the [[Call]] internal method of getter providing O as the this value and providing no arguments.
  6. If O is a Function object or an arguments object which contains a [[ParameterMap]] internal property: a. (Arguments or Function object exotic behavior.) If P is "caller" and res is a strict mode Function object, throw a TypeError exception.
  7. Return res.

Note:

  • Step 1 can match only when P is a "numeric" property name, and the property value is an own data property. Magically bound properties are initially own data properties, and if they're changed to accessors (or deleted), the binding is removed. Because of this, the arguments exotic behavior could just as well be moved to the end of the algorithm.

Final version

Final version with some cleanup and simplification:

  1. If O is an arguments object which contains a [[ParameterMap]] internal property: a. (Arguments object exotic behavior.) Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. If the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument is not undefined: 1. Return the result of calling the [[Get]] internal method of map passing P as the argument.
  2. Let desc be the result of calling the [[GetProperty]] internal method of O with property name P.
  3. If desc is undefined, return undefined.
  4. If IsDataDescriptor(desc) is true: a. Let res be desc.[[Value]].
  5. Otherwise, IsAccessorDescriptor(desc) must be true: a. Let getter be desc.[[Get]]. b. If getter is undefined, return undefined. c. Else let res be the result of calling the [[Call]] internal method of getter providing O as the this value and providing no arguments.
  6. If O is a Function object or an arguments object which contains a [[ParameterMap]] internal property: a. (Arguments or Function object exotic behavior.) If P is "caller" and res is a strict mode Function object, throw a TypeError exception.
  7. Return res.

DefineOwnProperty

Related E5 sections:

  • E5 Section 8.12.9: default algorithm
  • E5 Section 15.4.5: Array
  • E5 Section 10.5: arguments object

Note that String 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

  1. Let current be the result of calling the [[GetOwnProperty]] internal method of O with property name P.
  2. Let extensible be the value of the [[Extensible]] internal property of O.
  3. If current is undefined and extensible is false, then Reject.
  4. If current is undefined and extensible is true, then a. If IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true, then 1. Create an own data property named P of object O whose [[Value]], [[Writable]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value. b. Else, Desc must be an accessor Property Descriptor so, 1. Create an own accessor property named P of object O whose [[Get]], [[Set]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value. c. Return true.
  5. Return true if every field in Desc is absent.
  6. Return true, if every field in Desc also occurs in current and the value of every field in Desc is the same value as the corresponding field in current when compared using the SameValue algorithm (E5 Section 9.12).
  7. If the [[Configurable]] field of current is false then a. Reject, if the [[Configurable]] field of Desc is true. b. Reject, if the [[Enumerable]] field of Desc is present and the [[Enumerable]] fields of current and Desc are the Boolean negation of each other.
  8. If IsGenericDescriptor(Desc) is true, then no further validation is required.
  9. Else, if IsDataDescriptor(current) and IsDataDescriptor(Desc) have different results, then a. Reject, if the [[Configurable]] field of current is false. b. If IsDataDescriptor(current) is true, then 1. Convert the property named P of object O from a data property to an accessor property. Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to their default values. c. Else, 1. Convert the property named P of object O from an accessor property to a data property. Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to their default values.
  10. Else, if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then a. If the [[Configurable]] field of current is false, then 1. Reject, if the [[Writable]] field of current is false and the [[Writable]] field of Desc is true. 2. If the [[Writable]] field of current is false, then a. Reject, if the [[Value]] field of Desc is present and SameValue(Desc.[[Value]], current.[[Value]]) is false. b. else, the [[Configurable]] field of current is true, so any change is acceptable.
  11. Else, IsAccessorDescriptor(current) and IsAccessorDescriptor(Desc) are both true so, a. If the [[Configurable]] field of current is false, then 1. Reject, if the [[Set]] field of Desc is present and SameValue(Desc.[[Set]], current.[[Set]]) is false. 2. Reject, if the [[Get]] field of Desc is present and SameValue(Desc.[[Get]], current.[[Get]]) is false.
  12. For each attribute field of Desc that is present, set the correspondingly named attribute of the property named P of object O to the value of the field.
  13. Return true.

Notes:

  • The default attributes are not the same as when [[Put]] creates a new property. The defaults here are "false" (and NULL for getter/setter), see E5 Section 8.6.1, Table 7).
  • Step 10.a.1 allows a non-configurable property to change from writable to non-writable, but not vice versa.
  • Step 10.b is not necessary (it is more of an assertion), and there is no corresponding step 11.b mentioning the same thing. This step can be removed from the description.
  • There are multiple exit points for both Reject (throw or return false) and true. For incorporating inline exotic behaviors, these are turned to "gotos" below.

Default algorithm reformulated

Let's first do a little bit of reformulation (see above):

  1. Let current be the result of calling the [[GetOwnProperty]] internal method of O with property name P.
  2. Let extensible be the value of the [[Extensible]] internal property of O.
  3. If current is undefined: a. If extensible is false, then goto REJECT. b. If IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true, then 1. Create an own data property named P of object O whose [[Value]], [[Writable]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value. c. Else, Desc must be an accessor Property Descriptor so, 1. Create an own accessor property named P of object O whose [[Get]], [[Set]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS.
  4. Goto SUCCESS, if every field in Desc also occurs in current and the value of every field in Desc is the same value as the corresponding field in current when compared using the SameValue algorithm (E5 Section 9.12). (This also covers the case where every field in Desc is absent.)
  5. If the [[Configurable]] field of current is false then a. Goto REJECT, if the [[Configurable]] field of Desc is true. b. Goto REJECT, if the [[Enumerable]] field of Desc is present and the [[Enumerable]] fields of current and Desc are the Boolean negation of each other.
  6. If IsGenericDescriptor(Desc) is true, then goto VALIDATED.
  7. Else, if IsDataDescriptor(current) and IsDataDescriptor(Desc) have different results, then a. Goto REJECT, if the [[Configurable]] field of current is false. b. If IsDataDescriptor(current) is true, then 1. Convert the property named P of object O from a data property to an accessor property. Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to their default values. c. Else, 1. Convert the property named P of object O from an accessor property to a data property. Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to their default values. d. Goto VALIDATED.
  8. Else, if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then a. If the [[Configurable]] field of current is false, then 1. Goto REJECT, if the [[Writable]] field of current is false and the [[Writable]] field of Desc is true. 2. Goto REJECT, If the [[Writable]] field of current is false, and the [[Value]] field of Desc is present, and SameValue(Desc.[[Value]], current.[[Value]]) is false. b. Goto VALIDATED.
  9. Else, IsAccessorDescriptor(current) and IsAccessorDescriptor(Desc) are both true so, a. If the [[Configurable]] field of current is false, then 1. Goto REJECT, if the [[Set]] field of Desc is present and SameValue(Desc.[[Set]], current.[[Set]]) is false. 2. Goto REJECT, if the [[Get]] field of Desc is present and SameValue(Desc.[[Get]], current.[[Get]]) is false. b. Goto VALIDATED.
  10. VALIDATED: For each attribute field of Desc that is present, set the correspondingly named attribute of the property named P of object O to the value of the field.
  11. SUCCESS: Return true.
  12. REJECT: If Throw is true, then throw a TypeError exception, otherwise return false.

Analysis of Array object [[DefineOwnProperty]]

The Array variant for [[DefineOwnProperty]] is described in E5 Section 15.4.5.1. The variant seems to be essentially a pre-check for length and array index properties before the default algorithm runs (see steps 1-4 of the variant).

However, it's much more complex than that, because the variant algorithm makes multiple calls to the default algorithm.

Let's look at the variant algorithm first (here we assume O is an Array with exotic behavior, so no check is made for exotic behavior):

  1. Let oldLenDesc be the result of calling the [[GetOwnProperty]] internal method of O passing "length" as the argument. The result will never be undefined or an accessor descriptor because Array objects are created with a length data property that cannot be deleted or reconfigured.
  2. Let oldLen be oldLenDesc.[[Value]]. (Note that oldLen is guaranteed to be a unsigned 32-bit integer.)
  3. If P is "length", then a. If the [[Value]] field of Desc is absent, then 1. Return the result of calling the default [[DefineOwnProperty]] internal method (E5 Section 8.12.9) on O passing "length", Desc, and Throw as arguments. b. Let newLenDesc be a copy of Desc. c. Let newLen be ToUint32(Desc.[[Value]]). d. If newLen is not equal to ToNumber(Desc.[[Value]]), throw a RangeError exception. e. Set newLenDesc.[[Value]] to newLen. f. If newLen >= oldLen, then 1. Return the result of calling the default [[DefineOwnProperty]] internal method (E5 Section 8.12.9) on O passing "length", newLenDesc, and Throw as arguments. g. Reject if oldLenDesc.[[Writable]] is false. h. If newLenDesc.[[Writable]] is absent or has the value true, let newWritable be true. i. Else, 1. Need to defer setting the [[Writable]] attribute to false in case any elements cannot be deleted. 2. Let newWritable be false. 3. Set newLenDesc.[[Writable]] to true. j. Let succeeded be the result of calling the default [[DefineOwnProperty]] internal method (E5 Section 8.12.9) on O passing "length", newLenDesc, and Throw as arguments. k. If succeeded is false, return false. l. While newLen < oldLen repeat, 1. Set oldLen to oldLen - 1. 2. Let canDelete be the result of calling the [[Delete]] internal method of O passing ToString(oldLen) and false as arguments. 3. If canDelete is false, then: a. Set newLenDesc.[[Value] to oldLen+1. b. If newWritable is false, set newLenDesc.[[Writable] to false. c. Call the default [[DefineOwnProperty]] internal method (E5 Section 8.12.9) on O passing "length", newLenDesc, and false as arguments. d. Reject. m. If newWritable is false, then 1. Call the default [[DefineOwnProperty]] internal method (E5 Section 8.12.9) on O passing "length", Property Descriptor {[[Writable]]: false}, and false as arguments. This call will always return true. n. Return true.
  4. Else if P is an array index (E5 Section 15.4), then: a. Let index be ToUint32(P). b. Reject if index >= oldLen and oldLenDesc.[[Writable]] is false. c. Let succeeded be the result of calling the default [[DefineOwnProperty]] internal method (E5 Section 8.12.9) on O passing P, Desc, and false as arguments. d. Reject if succeeded is false. e. If index >= oldLen: 1. Set oldLenDesc.[[Value]] to index + 1. 2. Call the default [[DefineOwnProperty]] internal method (E5 Section 8.12.9) on O passing "length", oldLenDesc, and false as arguments. This call will always return true. f. Return true.
  5. Return the result of calling the default [[DefineOwnProperty]] internal method (E5 Section 8.12.9) on O passing P, Desc, and Throw as arguments.

Notes:

  • In E5 Section 15.4.5.1 step 3.l.ii - 3.l.iii the temporary variable cannotDelete seems to be misused; it should probably be canDelete and the check in step iii should read "if canDelete is false ...".
  • Step 5 is the default behavior, assuming nothing "captured" the call before.
  • Unfortunately steps 3 and 4 call the default [[DefineOwnProperty]] internally (multiple times). We'd like to avoid this, to get a non-recursive implementation. This requires some major restatements.

Let's look at the calls to the default [[DefineOwnProperty]] (other than step 5) to see what could be done about them.

First, for P == length:

  • Step 3.a.1: If Desc.[[Value]] is absent, call the default algorithm.

    This is equivalent to:

    • Jumping to step 5.
  • Step 3.f.1: If newLen validation succeeds and new length is not shorter than previous, call the default algorithm with a modified property descriptor, newLenDesc. The new property descriptor is a copy of the original, with [[Value]] changed to the normalized and numeric (32-bit unsigned integer) length value.

    This is equivalent to:

    • Doing length validation and coercion
    • Checking that the new length is not shorter than previous; and if so, forcing Desc.[[Value]] to newLen, and then jumping to step 5.
    • Note: the caller's view of Desc must not change, so Desc cannot be a "pass by reference" value.
  • Step 3.f.j: Here newLen validation has succeeded, and the new length is shorter than previous. Also, Desc.[[Writable]] may have been fudged. The changes so far are "committed" to "length" property using the default call.

    Note that this call also has the important effect of checking that the default algorithm is expected to succeed before we touch any of the array elements.

    This is equivalent to:

    • Doing the newWritable fudging to Desc, and keeping newWritable for later.
    • Jumping to step 5.
    • Adding a post-step to the default algorithm for steps 3.k - 3.m.
  • Step 3.l.3.c: Here we've started to "shorten" the array but run into a non-deletable element. The "length" property is updated with the actual final length, and Desc.[[Writable]] is fudged back to its original, requested value.

    This is equivalent to:

    • Fudging both [[Value]] and [[Writable]] of Desc.
    • Jumping to step 5.
  • Step 3.m: Here a pending write protection is finally implemented by calling the default [[DefineOwnProperty]] with a property descriptor requesting only that the property be changed to non-writable.

    This is equivalent to:

    • Adding a "pending write protect" flag and jumping to 5.
    • Modifying the standard algorithm to recognize a "pending write protect" after standard property modifications and checks are complete.

Then, for the case when P is a valid array index:

  • Step 4.c: The index has been coerced and validated; the algorithm rejects if the array index would require that the array length be increased but length is write protected.

    This is equivalent to:

    • Doing the pre-checks for index vs. length.
    • Jumping to step 5.
    • Adding a post-step to the standard algorithm to handle steps 4.d - 4.f.
  • Step 4.e.2: This is a step which happens after the default algorithm has finished without errors. If so, and the array index extended the array length, the array length is updated to reflect this. This is expected to always succeed.

    This is equivalent to:

    • Adding a post-step to the standard algorithm.

A draft of modifications to the standard algorithm to avoid recursive calls could be something like:

  • Add a pre-step with:
    • Check for P == length, and:
      • If Desc.[[Value]] missing, use default algorithm
      • newLen validation and updating of Desc.[[Value]]
      • If new length is not shorter than old length, default algorithm with the modified Desc can be used
      • Possible fudging of Desc.[[Writable]] and check for setting pendingWriteProtect (set if newWritable is false)
      • If new length is shorter than old length, run the default algorithm successfully first before touching array elements
    • Check for P being a valid array index, and:
      • Pre-checks for index vs. length
  • Modify the standard algorithm:
    • Continuing with the post-step if the standard algorithm succeeds.
  • Add a post-step with:
    • Check whether we have a pending array "shortening", i.e. P was "length", and the new length is shorter than old.
      • A complex algorithm for shortening the array needs to run. This algorithm may either indicate success or failure, and returns the actual final length of the array which may differ from the requested one if a non-configurable element prevents deletion.
    • Check for pendingWriteProtect; if so, write protect the target property (this is for step 3.m).
    • Check whether P was an array index which should increase the length of the array.
      • If so, we've already checked in the pre-step that the length can be updated. So, update the pending new length value.

The algorithm for shortening the array is not inlined (it is a separate helper in the implementation too) as it's relatively tricky. It is instead isolated into ShortenArray() internal helper with inputs:

  • old length
  • new length

and outputs:

  • success flag (false if some element couldn't be deleted)
  • final array length to be updated into "length" property

Adding Array object exotic behavior

Incorporating the approach for adding a pre- and post-processing phase we get something like:

  1. Set pendingWriteProtect to false.
  2. If O is not an Array object, goto SKIPARRAY.
  3. Let oldLenDesc be the result of calling the [[GetOwnProperty]] internal method of O passing "length" as the argument. The result will never be undefined or an accessor descriptor because Array objects are created with a length data property that cannot be deleted or reconfigured.
  4. Let oldLen be oldLenDesc.[[Value]]. (Note that oldLen is guaranteed to be a unsigned 32-bit integer.)
  5. If P is "length", then a. If the [[Value]] field of Desc is absent, then goto SKIPARRAY. b. Let newLen be ToUint32(Desc.[[Value]]). c. If newLen is not equal to ToNumber(Desc.[[Value]]), goto REJECTRANGE. d. Set Desc.[[Value]] to newLen. e. If newLen >= oldLen, then goto SKIPARRAY. f. Goto REJECT if oldLenDesc.[[Writable]] is false. g. If Desc.[[Writable]] has the value false: 1. Need to defer setting the [[Writable]] attribute to false in case any elements cannot be deleted. 2. Set pendingWriteProtect to true. 3. Set Desc.[[Writable]] to true. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.)
  6. Else if P is an array index (E5 Section 15.4), then: a. Let index be ToUint32(P). b. Goto REJECT if index >= oldLen and oldLenDesc.[[Writable]] is false. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.)
  7. SKIPARRAY: Let current be the result of calling the [[GetOwnProperty]] internal method of O with property name P.
  8. Let extensible be the value of the [[Extensible]] internal property of O.
  9. If current is undefined: a. If extensible is false, then goto REJECT. b. If IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true, then 1. Create an own data property named P of object O whose [[Value]], [[Writable]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value. c. Else, Desc must be an accessor Property Descriptor so, 1. Create an own accessor property named P of object O whose [[Get]], [[Set]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS.
  10. Goto SUCCESS, if every field in Desc also occurs in current and the value of every field in Desc is the same value as the corresponding field in current when compared using the SameValue algorithm (E5 Section 9.12). (This also covers the case where every field in Desc is absent.)
  11. If the [[Configurable]] field of current is false then a. Goto REJECT, if the [[Configurable]] field of Desc is true. b. Goto REJECT, if the [[Enumerable]] field of Desc is present and the [[Enumerable]] fields of current and Desc are the Boolean negation of each other.
  12. If IsGenericDescriptor(Desc) is true, then goto VALIDATED.
  13. Else, if IsDataDescriptor(current) and IsDataDescriptor(Desc) have different results, then a. Goto REJECT, if the [[Configurable]] field of current is false. b. If IsDataDescriptor(current) is true, then 1. Convert the property named P of object O from a data property to an accessor property. Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to their default values. c. Else, 1. Convert the property named P of object O from an accessor property to a data property. Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to their default values. d. Goto VALIDATED.
  14. Else, if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then a. If the [[Configurable]] field of current is false, then 1. Goto REJECT, if the [[Writable]] field of current is false and the [[Writable]] field of Desc is true. 2. Goto REJECT, If the [[Writable]] field of current is false, and the [[Value]] field of Desc is present, and SameValue(Desc.[[Value]], current.[[Value]]) is false. b. Goto VALIDATED.
  15. Else, IsAccessorDescriptor(current) and IsAccessorDescriptor(Desc) are both true so, a. If the [[Configurable]] field of current is false, then 1. Goto REJECT, if the [[Set]] field of Desc is present and SameValue(Desc.[[Set]], current.[[Set]]) is false. 2. Goto REJECT, if the [[Get]] field of Desc is present and SameValue(Desc.[[Get]], current.[[Get]]) is false. b. Goto VALIDATED.
  16. VALIDATED: For each attribute field of Desc that is present, set the correspondingly named attribute of the property named P of object O to the value of the field.
  17. SUCCESS: If O is an Array object: a. If P is "length", and newLen < oldLen, then: 1. Let shortenSucceeded, finalLen be the result of calling the internal helper ShortenArray() with oldLen and newLen. 2. Update the property ("length") value to finalLen. 3. If pendingWriteProtect is true, update the property ("length") to have [[Writable]] = false. 4. Goto REJECT, if shortenSucceeded is false. b. If P is an array index and index >= oldLen: 1. Update the "length" property of O to the value index + 1. This always succeeds, because we've checked in the pre-step that the "length" is writable, and since P is an array index property, the length must still be writable here.
  18. Return true.
  19. REJECT: If Throw is true, then throw a TypeError exception, otherwise return false.
  20. REJECTRANGE: Throw a RangeError exception. Note that this is unconditional (thrown even if Throw is false).

Adding arguments object 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:

  1. Set pendingWriteProtect to false.
  2. If O is not an Array object, goto SKIPARRAY.
  3. Let oldLenDesc be the result of calling the [[GetOwnProperty]] internal method of O passing "length" as the argument. The result will never be undefined or an accessor descriptor because Array objects are created with a length data property that cannot be deleted or reconfigured.
  4. Let oldLen be oldLenDesc.[[Value]]. (Note that oldLen is guaranteed to be a unsigned 32-bit integer.)
  5. If P is "length", then a. If the [[Value]] field of Desc is absent, then goto SKIPARRAY. b. Let newLen be ToUint32(Desc.[[Value]]). c. If newLen is not equal to ToNumber(Desc.[[Value]]), goto REJECTRANGE. d. Set Desc.[[Value]] to newLen. e. If newLen >= oldLen, then goto SKIPARRAY. f. Goto REJECT if oldLenDesc.[[Writable]] is false. g. If Desc.[[Writable]] has the value false: 1. Need to defer setting the [[Writable]] attribute to false in case any elements cannot be deleted. 2. Set pendingWriteProtect to true. 3. Set Desc.[[Writable]] to true. h. Goto SKIPARRAY. (Rest of the processing happens in the post-step.)
  6. Else if P is an array index (E5 Section 15.4), then: a. Let index be ToUint32(P). b. Goto REJECT if index >= oldLen and oldLenDesc.[[Writable]] is false. c. Goto SKIPARRAY. (Rest of the processing happens in the post-step.)
  7. SKIPARRAY: Let current be the result of calling the [[GetOwnProperty]] internal method of O with property name P.
  8. Let extensible be the value of the [[Extensible]] internal property of O.
  9. If current is undefined: a. If extensible is false, then goto REJECT. b. If IsGenericDescriptor(Desc) or IsDataDescriptor(Desc) is true, then 1. Create an own data property named P of object O whose [[Value]], [[Writable]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value. c. Else, Desc must be an accessor Property Descriptor so, 1. Create an own accessor property named P of object O whose [[Get]], [[Set]], [[Enumerable]] and [[Configurable]] attribute values are described by Desc. If the value of an attribute field of Desc is absent, the attribute of the newly created property is set to its default value. d. Goto SUCCESS.
  10. Goto SUCCESS, if every field in Desc also occurs in current and the value of every field in Desc is the same value as the corresponding field in current when compared using the SameValue algorithm (E5 Section 9.12). (This also covers the case where every field in Desc is absent.)
  11. If the [[Configurable]] field of current is false then a. Goto REJECT, if the [[Configurable]] field of Desc is true. b. Goto REJECT, if the [[Enumerable]] field of Desc is present and the [[Enumerable]] fields of current and Desc are the Boolean negation of each other.
  12. If IsGenericDescriptor(Desc) is true, then goto VALIDATED.
  13. Else, if IsDataDescriptor(current) and IsDataDescriptor(Desc) have different results, then a. Goto REJECT, if the [[Configurable]] field of current is false. b. If IsDataDescriptor(current) is true, then 1. Convert the property named P of object O from a data property to an accessor property. Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to their default values. c. Else, 1. Convert the property named P of object O from an accessor property to a data property. Preserve the existing values of the converted property's [[Configurable]] and [[Enumerable]] attributes and set the rest of the property's attributes to their default values. d. Goto VALIDATED.
  14. Else, if IsDataDescriptor(current) and IsDataDescriptor(Desc) are both true, then a. If the [[Configurable]] field of current is false, then 1. Goto REJECT, if the [[Writable]] field of current is false and the [[Writable]] field of Desc is true. 2. Goto REJECT, If the [[Writable]] field of current is false, and the [[Value]] field of Desc is present, and SameValue(Desc.[[Value]], current.[[Value]]) is false. b. Goto VALIDATED.
  15. Else, IsAccessorDescriptor(current) and IsAccessorDescriptor(Desc) are both true so, a. If the [[Configurable]] field of current is false, then 1. Goto REJECT, if the [[Set]] field of Desc is present and SameValue(Desc.[[Set]], current.[[Set]]) is false. 2. Goto REJECT, if the [[Get]] field of Desc is present and SameValue(Desc.[[Get]], current.[[Get]]) is false. b. Goto VALIDATED.
  16. VALIDATED: For each attribute field of Desc that is present, set the correspondingly named attribute of the property named P of object O to the value of the field.
  17. SUCCESS: If O is an Array object: a. If P is "length", and newLen < oldLen, then: 1. Let shortenSucceeded, finalLen be the result of calling the internal helper ShortenArray() with oldLen and newLen. 2. Update the property ("length") value to finalLen. 3. If pendingWriteProtect is true, update the property ("length") to have [[Writable]] = false. 4. Goto REJECT, if shortenSucceeded is false. b. If P is an array index and index >= oldLen: 1. Update the "length" property of O to the value index + 1. This always succeeds, because we've checked in the pre-step that the "length" is writable, and since P is an array index property, the length must still be writable here.
  18. If O is an arguments object which has a [[ParameterMap]] internal property: a. Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. If the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument is not undefined, then: 1. If IsAccessorDescriptor(Desc) is true, then: a. Call the [[Delete]] internal method of map passing P, and false as the arguments. (This removes the magic binding for P.) 2. Else (Desc may be generic or data descriptor): a. If Desc.[[Value]] is present, then: 1. Call the [[Put]] internal method of map passing P, Desc.[[Value]], and Throw as the arguments. (This updates the bound variable value.) b. If Desc.[[Writable]] is present and its value is false, then: 1. Call the [[Delete]] internal method of map passing P and false as arguments. (This removes the magic binding for P, and must happen after a possible update of the variable value.)
  19. Return true.
  20. REJECT: If Throw is true, then throw a TypeError exception, otherwise return false.
  21. REJECTRANGE: Throw a RangeError exception. Note that this is unconditional (thrown even if Throw is false).

Final version

(See above, currently no additional cleanup.)

Delete

Related E5 sections:

  • E5 Section 8.12.7: default algorithm
  • E5 Section 10.5: arguments object

Default algorithm

  1. Let desc be the result of calling the [[GetOwnProperty]] internal method of O with property name P.
  2. If desc is undefined, then return true.
  3. If desc.[[Configurable]] is true, then a. Remove the own property with name P from O. b. Return true.
  4. Else if Throw is true, then throw a TypeError exception.
  5. Return false.

Adding arguments object 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:

  1. Let desc be the result of calling the [[GetOwnProperty]] internal method of O with property name P.
  2. If desc is undefined, then goto SUCCESS.
  3. If desc.[[Configurable]] is true, then a. Remove the own property with name P from O. b. Goto SUCCESS.
  4. Else if Throw is true, then throw a TypeError exception.
  5. Return false.
  6. SUCCESS: If O is an arguments object which has a [[ParameterMap]] internal property: a. Let map be the value of the [[ParameterMap]] internal property of the arguments object. b. If the result of calling the [[GetOwnProperty]] internal method of map passing P as the argument is not undefined, then: 1. Call the [[Delete]] internal method of map passing P, and false as the arguments. (This removes the magic binding for P.)
  7. Return true.

Notes:

  • In steps 2, if desc is undefined, it seems unnecessary to go to step 6 to check the arguments parameter map. Can a magically bound property exist in the parameter map with the underlying property having been deleted somehow?

Final version

(See above, currently no additional cleanup.)

HasInstance

Background

The [[HasInstance]] internal method is referred to in the following parts of the E5 specification:

  • Section 8.6.2: [[HasInstance]] is introduced as a SpecOp(any) -> Boolean internal method. Only Function objects have a [[HasInstance]] method.
  • Section 11.8.6: the instanceof operator, which is the only "caller" for [[HasInstance]] in the E5 specification.
  • Section 13.2: when Function objects are created, [[HasInstance]] is set to the algorithm in Section 15.3.5.3.
  • Section 15.3.4.5: when bound functions are created using Function.prototype.bind(), [[HasInstance]] is set to the algorithm in Section 15.3.4.5.3.
  • Section 15.3.4.5.3: [[HasInstance]] for bound functions.
  • Section 15.3.5.3: [[HasInstance]] for ordinary (non-bound) functions.

The [[HasInstance]] for ordinary functions is (F is the function object and V is the argument value, "V instanceof F"):

  1. If Type(V) is not an Object, return false.
  2. Let O be the result of calling the [[Get]] internal method of F with property name "prototype". (Note: this is the external prototype, not the internal one.)
  3. If Type(O) is not Object, throw a TypeError exception.
  4. Repeat a. Let V be the value of the [[Prototype]] internal property of V. b. If V is null, return false. c. If O and V refer to the same object, return true.

Notes:

  • In step 2, we're fetching the external prototype, which may have any values. It might also have been changed after the instance was created.
  • Step 4.a steps the internal prototype chain once before the first check.

The [[HasInstance]] for bound functions is:

  1. Let target be the value of F's [[TargetFunction]] internal property.
  2. If target has no [[HasInstance]] internal method, a TypeError exception is thrown.
  3. Return the result of calling the [[HasInstance]] internal method of target providing V as the argument.

Notes:

  • In step 3, the target may be another bound function, so we may need to follow an arbitrary number of bound functions before ending up with an actual function object.

Combined algorithm

The two [[HasInstance]] methods (for bound and non-bound functions) can be combined to yield:

  1. While F is a bound function: a. Set F to the value of F's [[TargetFunction]] internal property. b. If F has no [[HasInstance]] internal method, throw a TypeError exception. (Note: F can be another bound function, so we loop until we find the non-bound actual function.)
  2. If Type(V) is not an Object, return false.
  3. Let O be the result of calling the [[Get]] internal method of F with property name "prototype". (Note: this is the external prototype, not the internal one.)
  4. If Type(O) is not Object, throw a TypeError exception.
  5. Repeat a. Let V be the value of the [[Prototype]] internal property of V. b. If V is null, return false. c. If O and V refer to the same object, return true.

Final version

(See above, currently no additional cleanup.)