Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
158 changes: 158 additions & 0 deletions harness/builtin.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
// Copyright (C) 2025 André Bargull. All rights reserved.
// This code is governed by the BSD license found in the LICENSE file.

/*---
description: |
Functions to test built-in function and constructor objects.
defines:
- verifyBuiltinFunction
- verifyBuiltinConstructor
---*/

// Capture primordial functions that are used in verification but might be
// destroyed *by* that process itself.
var __Function = Function;
var __Proxy = Proxy;
var __Reflect_construct = Reflect.construct;
var __Reflect_getPrototypeOf = Reflect.getPrototypeOf;
var __Reflect_isExtensible = Reflect.isExtensible;
var __Reflect_ownKeys = Reflect.ownKeys;
Comment on lines +14 to +19
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we use an IIFE rather than this naming convention?


/**
* Verify `fun` is a normal built-in function object with the default
* properties as defined in
* <https://tc39.es/ecma262/#sec-ecmascript-standard-built-in-objects>.
*/
function verifyBuiltinFunction(fun, name, length) {
// Unless specified otherwise, a built-in object that is callable as a
// function is a built-in function object with the characteristics described
// in 10.3.
assert.sameValue(typeof fun, "function");

// Unless otherwise specified every built-in function and every built-in
// constructor has the Function prototype object, which is the initial value
// of the expression Function.prototype (20.2.3), as the value of its
// [[Prototype]] internal slot.
assert.sameValue(__Reflect_getPrototypeOf(fun), __Function.prototype);

// Unless specified otherwise, the [[Extensible]] internal slot of a built-in
// object initially has the value true.
assert.sameValue(__Reflect_isExtensible(fun), true);

// Built-in function objects that are not constructors do not have a
// "prototype" property unless otherwise specified in the description of a
// particular function.
//
// NOTE: |verifyBuiltinFunction| is more strict and allows only exactly two
// own properties: "name" and "length".
assert.sameValue(__Reflect_ownKeys(fun).length, 2);

// Unless otherwise specified, the "name" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "name", {
value: name,
writable: false,
enumerable: false,
configurable: true,
});

// Unless otherwise specified, the "length" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "length", {
value: length,
writable: false,
enumerable: false,
configurable: true,
});

Comment on lines +42 to +69
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Assertion ordering suggestion:

Suggested change
// Built-in function objects that are not constructors do not have a
// "prototype" property unless otherwise specified in the description of a
// particular function.
//
// NOTE: |verifyBuiltinFunction| is more strict and allows only exactly two
// own properties: "name" and "length".
assert.sameValue(__Reflect_ownKeys(fun).length, 2);
// Unless otherwise specified, the "name" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "name", {
value: name,
writable: false,
enumerable: false,
configurable: true,
});
// Unless otherwise specified, the "length" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "length", {
value: length,
writable: false,
enumerable: false,
configurable: true,
});
// Unless otherwise specified, the "name" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "name", {
value: name,
writable: false,
enumerable: false,
configurable: true,
});
// Unless otherwise specified, the "length" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "length", {
value: length,
writable: false,
enumerable: false,
configurable: true,
});
// Built-in function objects that are not constructors do not have a
// "prototype" property unless otherwise specified in the description of a
// particular function.
//
// NOTE: |verifyBuiltinFunction| is more strict and allows only exactly two
// own properties: "name" and "length".
assert.sameValue(__Reflect_ownKeys(fun).length, 2);

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We could also follow the __push(failures, msg); approach of harness/propertyHelper.js.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Reflect.ownKeys is intentionally called first, because verifyPrimordialProperty deletes the properties. And adding {restore: true} just to move Reflect.ownKeys after verifyPrimordialProperty didn't seem useful to me.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, noted.

Copy link
Member

@gibson042 gibson042 Jul 16, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So alternatively:

   // NOTE: |verifyBuiltinFunction| is more strict and allows only exactly two
   // own properties: "name" and "length".
-  assert.sameValue(__Reflect_ownKeys(fun).length, 2);
+  assert.compareArray(__Reflect_ownKeys(fun).sort(), ["length", "name"],
+    "own property keys");
 
   // Unless otherwise specified, the "name" property of a built-in function
GitHub PR suggestion
Suggested change
// Built-in function objects that are not constructors do not have a
// "prototype" property unless otherwise specified in the description of a
// particular function.
//
// NOTE: |verifyBuiltinFunction| is more strict and allows only exactly two
// own properties: "name" and "length".
assert.sameValue(__Reflect_ownKeys(fun).length, 2);
// Unless otherwise specified, the "name" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "name", {
value: name,
writable: false,
enumerable: false,
configurable: true,
});
// Unless otherwise specified, the "length" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "length", {
value: length,
writable: false,
enumerable: false,
configurable: true,
});
// Built-in function objects that are not constructors do not have a
// "prototype" property unless otherwise specified in the description of a
// particular function.
//
// NOTE: |verifyBuiltinFunction| is more strict and allows only exactly two
// own properties: "name" and "length".
assert.compareArray(__Reflect_ownKeys(fun).sort(), ["length", "name"],
"own property keys");
// Unless otherwise specified, the "name" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "name", {
value: name,
writable: false,
enumerable: false,
configurable: true,
});
// Unless otherwise specified, the "length" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "length", {
value: length,
writable: false,
enumerable: false,
configurable: true,
});

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Calling assert.compareArray requires including "harness/compareArray.js" in each test file including "harness/builtin.js". This kind of dependency is a bit annoying, so I think I'd prefer this alternative:

var ownKeys = __Reflect_ownKeys(fun);
assert.sameValue(ownKeys.length, 2);
assert(
  (ownKeys[0] === "length" && ownKeys[1] === "name") ||
  (ownKeys[0] === "name" && ownKeys[1] === "length")
);

Strictly speaking (ownKeys[0] === "name" && ownKeys[1] === "length") isn't allowed, because all built-in functions are created through CreateBuiltinFunction, which first defines "length" and then "name". But not all implementations get this right:

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That makes sense to me, but I'm specifically trying to ensure that assertion-failure output is useful. So:

  // NOTE: |verifyBuiltinFunction| is more strict and allows only exactly two
  // own properties: "name" and "length".
  var ownKeys = __Reflect_ownKeys(fun);
  if (
    ownKeys.length !== 2 ||
    (ownKeys[0] !== "name" && ownKeys[1] !== "name") ||
    (ownKeys[0] !== "length" && ownKeys[1] !== "length")
  ) {
    var actualKeysStr = "[" + ownKeys.map(String).join(", ") + "]";
    var msg = "Actual own keys " + actualKeysStr + " should have been [length, name]";
    assert(false, msg);
  }

  // Unless otherwise specified, the "name" property of a built-in function

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree with the goal, but the resulting code is pretty dense. Are we worried about the impact of doing the formatting if the test passes? Otherwise it could be something like

  var actualKeysStr = "[" + ownKeys.map(String).join(", ") + "]";
  var msg = "Actual own keys " + actualKeysStr + " should have been [length, name]";
  var ownKeys = __Reflect_ownKeys(fun);
  assert.sameValue(ownKeys.length, 2, msg);
  assert(ownKeys[0] === "name" || ownKeys[1] === "name", msg);
  assert(ownKeys[0] === "length" || ownKeys[1] === "length", msg);

but note that the formatting code is not robust against changes in the environment either (.map() in particular)

// Built-in function objects that are not identified as constructors do not
// implement the [[Construct]] internal method unless otherwise specified in
// the description of a particular function.
assert.throws(TypeError, function() {
// Reflect.construct throws a TypeError if `fun` is not a constructor. Use
// the Proxy constructor because it ignores `NewTarget`.
//
// Two alternatives which also ensure no additional operations are called:
//
// 1. Create a Proxy for `fun` with a "construct" trap. Then call `new` on
// the newly created Proxy object.
// ```
// var p = new Proxy(fun, {construct(){ return {}; }});
// assert.throws(TypeError, function() { new p; });
// ```
//
// 2. Use a derived class object.
// ```
// class C extends null { constructor() { return {}; } };
// assert.throws(TypeError, function() {
// __Reflect_construct(C, [], fun);
// });
// ```
__Reflect_construct(__Proxy, [{}, {}], fun);
});
assert.throws(TypeError, function() {
// Test with `new` expression in addition to Reflect.construct.
Comment on lines +95 to +96
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
assert.throws(TypeError, function() {
// Test with `new` expression in addition to Reflect.construct.
// Test with `new` expression in addition to Reflect.construct.
assert.throws(TypeError, function() {

new fun();
});
}

/**
* Verify `fun` is a normal built-in constructor function object with the
* default properties as defined in
* <https://tc39.es/ecma262/#sec-ecmascript-standard-built-in-objects>.
*/
function verifyBuiltinConstructor(fun, name, length, prototype) {
// Unless specified otherwise, a built-in object that is callable as a
// function is a built-in function object with the characteristics described
// in 10.3.
assert.sameValue(typeof fun, "function");
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wonder if the common assertions between verifyBuiltinFunction and verifyBuiltinConstructor should be extracted.

/**
 * Verify `fun` is a normal built-in function object or constructor
 * function object with the default properties as defined in
 * <https://tc39.es/ecma262/#sec-ecmascript-standard-built-in-objects>.
 */
function verifyBuiltinCallable(fun, name, length, prototype) {
  // Unless specified otherwise, a built-in object that is callable as a
  // function is a built-in function object with the characteristics described
  // in 10.3.
  assert.sameValue(typeof fun, "function");

  // Unless otherwise specified every built-in function and every built-in
  // constructor has the Function prototype object, which is the initial value
  // of the expression Function.prototype (20.2.3), as the value of its
  // [[Prototype]] internal slot.
  if (prototype === undefined) {
    prototype = __Function.prototype;
  }
  assert.sameValue(__Reflect_getPrototypeOf(fun), prototype);

  // Unless specified otherwise, the [[Extensible]] internal slot of a built-in
  // object initially has the value true.
  assert.sameValue(__Reflect_isExtensible(fun), true);

  // Unless otherwise specified, the "name" property of a built-in function
  // object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
  // [[Configurable]]: true }.
  verifyPrimordialProperty(fun, "name", {
    value: name,
    writable: false,
    enumerable: false,
    configurable: true,
  });

  // Unless otherwise specified, the "length" property of a built-in function
  // object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
  // [[Configurable]]: true }.
  verifyPrimordialProperty(fun, "length", {
    value: length,
    writable: false,
    enumerable: false,
    configurable: true,
  });
}

/**
 * Verify `fun` is a normal built-in function object with the default
 * properties as defined in
 * <https://tc39.es/ecma262/#sec-ecmascript-standard-built-in-objects>.
 */
function verifyBuiltinFunction(fun, name, length) {
  verifyBuiltinCallable(fun, name, length);

  // Built-in function objects that are not constructors do not have a
  // "prototype" property unless otherwise specified in the description of a
  // particular function.
  // |verifyBuiltinFunction| is more strict, allowing only  "name" and "length".
  assert.sameValue(__Reflect_ownKeys(fun).length, 2);

  // Built-in function objects that are not identified as constructors do not
  // implement the [[Construct]] internal method unless otherwise specified in
  // the description of a particular function.
  assert.throws(TypeError, function() {
    // Reflect.construct throws a TypeError if `fun` is not a constructor. Use
    // the Proxy constructor because it ignores `NewTarget`.
    //
    // Two alternatives which also ensure no additional operations are called:
    //
    // 1. Create a Proxy for `fun` with a "construct" trap. Then call `new` on
    // the newly created Proxy object.
    // ```
    // var p = new Proxy(fun, {construct(){ return {}; }});
    // assert.throws(TypeError, function() { new p; });
    // ```
    //
    // 2. Use a derived class object.
    // ```
    // class C extends null { constructor() { return {}; } };
    // assert.throws(TypeError, function() {
    //   __Reflect_construct(C, [], fun);
    // });
    // ```
    __Reflect_construct(__Proxy, [{}, {}], fun);
  });

  // Test with `new` expression in addition to Reflect.construct.
  assert.throws(TypeError, function() {
    new fun();
  });
}

/**
 * Verify `fun` is a normal built-in constructor function object with the
 * default properties as defined in
 * <https://tc39.es/ecma262/#sec-ecmascript-standard-built-in-objects>.
 */
function verifyBuiltinConstructor(fun, name, length, prototype) {
  verifyBuiltinCallable(fun, name, length, prototype);

  // Built-in function objects that are not identified as constructors do not
  // implement the [[Construct]] internal method unless otherwise specified in
  // the description of a particular function.

  // Reflect.construct throws a TypeError if `fun` is not a constructor.
  //
  // See verifyBuiltinFunction for why Proxy is used here.
  assert.throws(Test262Error, function() {
    __Reflect_construct(__Proxy, [{}, {}], fun);

    // Throw the expected error.
    throw new Test262Error();
  });
}


// Unless otherwise specified every built-in function and every built-in
// constructor has the Function prototype object, which is the initial value
// of the expression Function.prototype (20.2.3), as the value of its
// [[Prototype]] internal slot.
if (prototype === undefined) {
prototype = __Function.prototype;
}
assert.sameValue(__Reflect_getPrototypeOf(fun), prototype);

// Unless specified otherwise, the [[Extensible]] internal slot of a built-in
// object initially has the value true.
assert.sameValue(__Reflect_isExtensible(fun), true);

// Unless otherwise specified, the "name" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "name", {
value: name,
writable: false,
enumerable: false,
configurable: true,
});

// Unless otherwise specified, the "length" property of a built-in function
// object has the attributes { [[Writable]]: false, [[Enumerable]]: false,
// [[Configurable]]: true }.
verifyPrimordialProperty(fun, "length", {
value: length,
writable: false,
enumerable: false,
configurable: true,
});

// Built-in function objects that are not identified as constructors do not
// implement the [[Construct]] internal method unless otherwise specified in
// the description of a particular function.

// Reflect.construct throws a TypeError if `fun` is not a constructor.
//
// See verifyBuiltinFunction for why Proxy is used here.
assert.throws(Test262Error, function() {
__Reflect_construct(__Proxy, [{}, {}], fun);

// Throw the expected error.
throw new Test262Error();
});
}
1 change: 1 addition & 0 deletions harness/features.yml
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
atomicsHelper: [Atomics]
builtin.js: [Proxy, Reflect, Reflect.construct]
typeCoercion.js: [Symbol.toPrimitive, BigInt]
testAtomics.js: [ArrayBuffer, Atomics, DataView, SharedArrayBuffer, Symbol, TypedArray]
testBigIntTypedArray.js: [BigInt, TypedArray]
Expand Down
6 changes: 3 additions & 3 deletions harness/propertyHelper.js
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,7 @@ function verifyProperty(obj, name, desc, options) {
}
}

if (__hasOwnProperty(desc, 'enumerable') && desc.enumerable !== undefined) {
if (__hasOwnProperty(desc, 'enumerable')) {
if (desc.enumerable !== originalDesc.enumerable ||
desc.enumerable !== isEnumerable(obj, name)) {
__push(failures, "obj['" + nameStr + "'] descriptor should " + (desc.enumerable ? '' : 'not ') + "be enumerable");
Expand All @@ -110,14 +110,14 @@ function verifyProperty(obj, name, desc, options) {

// Operations past this point are potentially destructive!

if (__hasOwnProperty(desc, 'writable') && desc.writable !== undefined) {
if (__hasOwnProperty(desc, 'writable')) {
if (desc.writable !== originalDesc.writable ||
desc.writable !== isWritable(obj, name)) {
__push(failures, "obj['" + nameStr + "'] descriptor should " + (desc.writable ? '' : 'not ') + "be writable");
}
}

if (__hasOwnProperty(desc, 'configurable') && desc.configurable !== undefined) {
if (__hasOwnProperty(desc, 'configurable')) {
if (desc.configurable !== originalDesc.configurable ||
desc.configurable !== isConfigurable(obj, name)) {
__push(failures, "obj['" + nameStr + "'] descriptor should " + (desc.configurable ? '' : 'not ') + "be configurable");
Expand Down
9 changes: 0 additions & 9 deletions test/built-ins/Number/15.7.3-1.js

This file was deleted.

13 changes: 0 additions & 13 deletions test/built-ins/Number/15.7.3-2.js

This file was deleted.

7 changes: 0 additions & 7 deletions test/built-ins/Number/EPSILON.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ info: |

This property has the attributes { [[Writable]]: false, [[Enumerable]]: false,
[[Configurable]]: false }.
includes: [propertyHelper.js]
---*/

assert(
Expand All @@ -25,9 +24,3 @@ assert(
Number.EPSILON < 0.000001,
"value is smaller than 0.000001"
);

verifyProperty(Number, "EPSILON", {
writable: false,
enumerable: false,
configurable: false,
});
29 changes: 29 additions & 0 deletions test/built-ins/Number/EPSILON/prop-desc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
// Test generated by: property-test-generator

/*---
esid: sec-number.epsilon
description: Property test for Number.EPSILON
info: |
Number.EPSILON
- The value of `Number.EPSILON` is the Number value for the magnitude of the
difference between 1 and the smallest value greater than 1 that is
representable as a Number value, which is approximately
2.2204460492503130808472633361816 × 10-16.
- This property has the attributes { [[Writable]]: false, [[Enumerable]]:
false, [[Configurable]]: false }.

ECMAScript Standard Built-in Objects

Every other data property described in clauses 19 through 28 and in Annex B.2
has the attributes { [[Writable]]: true, [[Enumerable]]: false,
[[Configurable]]: true } unless otherwise specified.
flags: [generated]
includes: [propertyHelper.js]
---*/

verifyPrimordialProperty(Number, "EPSILON", {
value: 2.2204460492503130808472633361816e-16,
writable: false,
enumerable: false,
configurable: false,
});
22 changes: 0 additions & 22 deletions test/built-ins/Number/MAX_SAFE_INTEGER.js

This file was deleted.

26 changes: 26 additions & 0 deletions test/built-ins/Number/MAX_SAFE_INTEGER/prop-desc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
// Test generated by: property-test-generator

/*---
esid: sec-number.max_safe_integer
description: Property test for Number.MAX_SAFE_INTEGER
info: |
Number.MAX_SAFE_INTEGER
- The value of `Number.MAX_SAFE_INTEGER` is 9007199254740991𝔽 (𝔽(253 - 1)).
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hm, that's quite a confusing way to render 2**53.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, fixing this will require rewriting <sup> tags to some other representation.

This the source code from spec.html:

<p>The value of `Number.MAX_SAFE_INTEGER` is *9007199254740991*<sub>𝔽</sub> (𝔽(2<sup>53</sup> - 1)).</p>

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
- The value of `Number.MAX_SAFE_INTEGER` is 9007199254740991𝔽 (𝔽(253 - 1)).
- The value of `Number.MAX_SAFE_INTEGER` is 9007199254740991𝔽 (𝔽(2⁵³ - 1)).

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hmm, fixing this will require rewriting <sup> tags to some other representation.

We're already doing this as of tc39/ecmarkup#517 . For example, the text at https://tc39.es/ecma262/multipage/numbers-and-dates.html#sec-number.max_safe_integer now copies as

Suggested change
- The value of `Number.MAX_SAFE_INTEGER` is 9007199254740991𝔽 (𝔽(253 - 1)).
- The value of Number.MAX_SAFE_INTEGER is 9007199254740991𝔽 (𝔽(2**53 - 1)).

- This property has the attributes { [[Writable]]: false, [[Enumerable]]:
false, [[Configurable]]: false }.
ECMAScript Standard Built-in Objects
Every other data property described in clauses 19 through 28 and in Annex B.2
has the attributes { [[Writable]]: true, [[Enumerable]]: false,
[[Configurable]]: true } unless otherwise specified.
flags: [generated]
includes: [propertyHelper.js]
---*/

verifyPrimordialProperty(Number, "MAX_SAFE_INTEGER", {
value: 9007199254740991,
writable: false,
enumerable: false,
configurable: false,
});
16 changes: 0 additions & 16 deletions test/built-ins/Number/MAX_VALUE/S15.7.3.2_A2.js

This file was deleted.

23 changes: 0 additions & 23 deletions test/built-ins/Number/MAX_VALUE/S15.7.3.2_A3.js

This file was deleted.

19 changes: 0 additions & 19 deletions test/built-ins/Number/MAX_VALUE/S15.7.3.2_A4.js

This file was deleted.

Loading