Skip to content

Commit

Permalink
[JSC] Close Iterator Helpers underlying iterator when arguments are i…
Browse files Browse the repository at this point in the history
…nvalid

https://bugs.webkit.org/show_bug.cgi?id=284709

Reviewed by NOBODY (OOPS!).

The normative change[1] that requires closing underlying iterators when
argument validation fails for iterator helpers reached consensus at the
TC39 meeting in December 2024[2].

This patch implements it.

[1]: tc39/ecma262#3467
[2]: https://github.com/tc39/agendas/blob/main/2024/12.md

* JSTests/stress/iterator-helpers-close-for-invalid-argument.js: Added.
(shouldThrow):
(shouldBe):
(throw.new.Error.let.closable.get next):
(throw.new.Error):
(shouldBe.let.closable.get next):
(shouldBe.OurError):
(let.closable.get next):
(shouldBe.get shouldBe):
(OurError):
(get shouldBe):
* Source/JavaScriptCore/builtins/JSIteratorPrototype.js:
(some.wrapper.iterator):
(some):
(every.wrapper.iterator):
(every):
(find.wrapper.iterator):
(find):
(reduce):
  • Loading branch information
sosukesuzuki committed Dec 17, 2024
1 parent 8d75fca commit 9e974e1
Show file tree
Hide file tree
Showing 3 changed files with 319 additions and 20 deletions.
271 changes: 271 additions & 0 deletions JSTests/stress/iterator-helpers-close-for-invalid-argument.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,271 @@
function shouldThrow(errorType, func) {
let error;
try {
func();
} catch (e) {
error = e;
}
if (!(error instanceof errorType)) {
print(error.message);
throw new Error(`Expected ${errorType.name}! got ${error.name}`);
}
}

function shouldBe(a, b) {
if (a !== b)
throw new Error(`Expected ${b} but got ${a}`);
}

{
// Iterator.prototype.map
let closed = false;
let closable = {
__proto__: Iterator.prototype,
get next() {
throw new Error('next should not be read');
},
return() {
closed = true;
return {};
},
};
shouldThrow(TypeError, function() {
closable.map();
});
shouldBe(closed, true);

closed = false;
shouldThrow(TypeError, function() {
closable.map({});
});
shouldBe(closed, true);
}

{
// Iterator.prototype.filter
let closed = false;
let closable = {
__proto__: Iterator.prototype,
get next() {
throw new Error('next should not be read');
},
return() {
closed = true;
return {};
},
};
shouldThrow(TypeError, function() {
closable.filter();
});
shouldBe(closed, true);

closed = false;
shouldThrow(TypeError, function() {
closable.filter({});
});
shouldBe(closed, true);
}

{
// Iterator.prototype.take
let closed = false;
let closable = {
__proto__: Iterator.prototype,
get next() {
throw new Error('next should not be read');
},
return() {
closed = true;
return {};
},
};

shouldThrow(RangeError, function() {
closable.take();
});
shouldBe(closed, true);

closed = false;
shouldThrow(RangeError, function() {
closable.take(NaN);
});
shouldBe(closed, true);

closed = false;
shouldThrow(RangeError, function() {
closable.take(-1);
});
shouldBe(closed, true);

closed = false;
function OurError() {}
shouldThrow(OurError, function() {
closable.take({ get valueOf() { throw new OurError(); }});
});
shouldBe(closed, true);
}

{
// Iterator.prototype.drop
let closed = false;
let closable = {
__proto__: Iterator.prototype,
get next() {
throw new Error('next should not be read');
},
return() {
closed = true;
return {};
},
};

shouldThrow(RangeError, function() {
closable.drop();
});
shouldBe(closed, true);

closed = false;
shouldThrow(RangeError, function() {
closable.drop(NaN);
});
shouldBe(closed, true);

closed = false;
shouldThrow(RangeError, function() {
closable.drop(-1);
});
shouldBe(closed, true);

closed = false;
function OurError() {}
shouldThrow(OurError, function() {
closable.drop({ get valueOf() { throw new OurError(); }});
});
shouldBe(closed, true);
}

{
// Iterator.prototype.flatMap
let closed = false;
let closable = {
__proto__: Iterator.prototype,
get next() {
throw new Error('next should not be read');
},
return() {
closed = true;
return {};
},
};
shouldThrow(TypeError, function() {
closable.flatMap();
});
shouldBe(closed, true);

closed = false;
shouldThrow(TypeError, function() {
closable.flatMap({});
});
shouldBe(closed, true);
}

{
// Iterator.prototype.some
let closed = false;
let closable = {
__proto__: Iterator.prototype,
get next() {
throw new Error('next should not be read');
},
return() {
closed = true;
return {};
},
};
shouldThrow(TypeError, function() {
closable.some();
});
shouldBe(closed, true);

closed = false;
shouldThrow(TypeError, function() {
closable.some({});
});
shouldBe(closed, true);
}

{
// Iterator.prototype.every
let closed = false;
let closable = {
__proto__: Iterator.prototype,
get next() {
throw new Error('next should not be read');
},
return() {
closed = true;
return {};
},
};
shouldThrow(TypeError, function() {
closable.every();
});
shouldBe(closed, true);

closed = false;
shouldThrow(TypeError, function() {
closable.every({});
});
shouldBe(closed, true);
}

{
// Iterator.prototype.find
let closed = false;
let closable = {
__proto__: Iterator.prototype,
get next() {
throw new Error('next should not be read');
},
return() {
closed = true;
return {};
},
};
shouldThrow(TypeError, function() {
closable.find();
});
shouldBe(closed, true);

closed = false;
shouldThrow(TypeError, function() {
closable.find({});
});
shouldBe(closed, true);
}

{
// Iterator.prototype.reduce
let closed = false;
let closable = {
__proto__: Iterator.prototype,
get next() {
throw new Error('next should not be read');
},
return() {
closed = true;
return {};
},
};
shouldThrow(TypeError, function() {
closable.reduce();
});
shouldBe(closed, true);

closed = false;
shouldThrow(TypeError, function() {
closable.reduce({});
});
shouldBe(closed, true);
}

4 changes: 4 additions & 0 deletions JSTests/test262/config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,10 @@ skip:
- test/staging/JSON
- test/staging/Temporal
files:
# Temporaly failing due to https://github.com/tc39/ecma262/pull/3467
# context: https://github.com/tc39/test262/pull/4350#pullrequestreview-2504333076
- test/built-ins/Iterator/prototype/reduce/non-callable-reducer.js

# https://github.com/claudepache/es-legacy-function-reflection
- test/built-ins/ThrowTypeError/unique-per-realm-function-proto.js
- test/built-ins/Function/prototype/restricted-property-arguments.js
Expand Down
Loading

0 comments on commit 9e974e1

Please sign in to comment.