array.[[ThrowablePut]] bugs in Kona draft

# David-Sarah Hopwood (17 years ago)

Section 15.4.5.1 ([[ThrowablePut]] on an array) has several bugs:

  • the algorithm has not been changed to take account of property descriptors. For example, it attempts to directly get and set values of properties, rather than getting and setting [[Value]] attributes or calling setter functions.

  • it creates new properties with empty attributes, rather than with attributes [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true, as the algorithm of 8.6.2.10 step 7 does.

  • it may change the value of the 'length' property even if that property has been made non-[[Writable]] using "Object.defineProperty(array, 'length', {writable: false});" (which is possible even though 'length' is non-[[Configurable]]).

  • it may delete array index properties that are non-[[Configurable]] when the 'length' property is set.

Suggested fix:

==== 15.4.5.1 [[ThrowablePut]] ( P, V, Throw )

Array objects use a variation of the [[ThrowablePut]] method used for other native ECMAScript objects (8.6.2.10).

Assume A is an Array object, P is a string, and Throw is a boolean flag. In the following algorithm, the term "Reject" means "If Throw is true, then throw a TypeError exception, otherwise return."

When the [[ThrowablePut]] method of A is called with property P, value V, and Boolean flag Throw, the following steps are taken:

  1. Call the [[CanPut]] method of A with argument P.
  2. If Result(1) is false, then Reject.
  3. Let OldLength be the [[Value]] attribute of A's own "length" property (which necessarily exists and is a data property holding an integer value).
  4. If P is "length", then go to step 13.
  5. If P is an array index, ToUint32(P) is not less than OldLength, and the [[Writable]] attribute of A's own "length" property is false, then Reject.
  6. Call the [[GetOwnProperty]] method of A with argument P.
  7. If IsDataDescriptor(Result(6)) is true, then a. Set the [[Value]] attribute of property P of A to V. b. Go to step 11.
  8. Call the [[GetProperty]] method of A with argument P.
  9. If IsAccessorDescriptor(Result(8)) is true, then a. Get Result(8).[[Setter]] which cannot be undefined. b. Call the [[Call]] method of Result(9a) providing A as the this value and providing V as the sole argument. c. Go to step 11.
  10. Create a data property named P on object A whose attributes are [[Value]]: V, [[Writable]]: true, [[Enumerable]]: true, [[Configurable]]: true.
  11. If P is an array index and ToUint32(P) is not less than OldLength, then a. Set the [[Value]] attribute of A's own "length" property to ToUint32(P)+1.
  12. Return.
  13. Let NewLength be ToUint32(V).
  14. If NewLength is not equal to ToNumber(V), throw a RangeError exception.
  15. For every integer k that is less than OldLength but not less than NewLength: a. If an own property of A named ToString(k) exists and its [[Configurable]] attribute is false, then Reject.
  16. For every integer k that is less than OldLength but not less than NewLength: a. If an own property of A named ToString(k) exists, then remove it.
  17. Set the [[Value]] attribute of A's own "length" property to NewLength.
  18. Return. ====

The overhead of step 8, and of steps 5-8 of the [[CanPut]] algorithm, could be eliminated if Array.prototype and Object.prototype were not allowed to have setters (assuming that the [[Prototype]] property of an array cannot be changed after creation).

An implementation could eliminate the overhead of step 15 in the common case by maintaining a flag in the array header that keeps track of whether the array has any non-[[Configurable]] array index properties.

Also note that [[CanPut]] is only used by the two versions of [[ThrowablePut]], and it would arguably be clearer to fold [[CanPut]] into the [[ThrowablePut]] algorithms, since they are doing the same prototype chain search and checking the same descriptors. Steps 3 and 4 of [[CanPut]] are redundant.