LibJS: Ensure iterator parameter validation closes underlying iterator

This is a normative change in the ECMA-262 spec. See:
9552f29
f2bad00
This commit is contained in:
Timothy Flynn 2025-04-28 19:03:05 -04:00 committed by Tim Flynn
commit 4d70f6ce1c
Notes: github-actions[bot] 2025-04-29 11:34:08 +00:00
11 changed files with 408 additions and 144 deletions

View file

@ -84,38 +84,53 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::drop)
// 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm));
// 3. Let numLimit be ? ToNumber(limit).
auto numeric_limit = TRY(limit.to_number(vm));
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. If numLimit is NaN, throw a RangeError exception.
if (numeric_limit.is_nan())
return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "limit"sv);
// 4. Let numLimit be Completion(ToNumber(limit)).
// 5. IfAbruptCloseIterator(numLimit, iterated).
auto numeric_limit = TRY_OR_CLOSE_ITERATOR(vm, iterated, limit.to_number(vm));
// 5. Let integerLimit be ! ToIntegerOrInfinity(numLimit).
// 6. If numLimit is NaN, then
if (numeric_limit.is_nan()) {
// a. Let error be ThrowCompletion(a newly created RangeError object).
auto error = vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "limit"sv);
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 7. Let integerLimit be ! ToIntegerOrInfinity(numLimit).
auto integer_limit = MUST(numeric_limit.to_integer_or_infinity(vm));
// 6. If integerLimit < 0, throw a RangeError exception.
if (integer_limit < 0)
return vm.throw_completion<RangeError>(ErrorType::NumberIsNegative, "limit"sv);
// 8. If integerLimit < 0, then
if (integer_limit < 0) {
// a. Let error be ThrowCompletion(a newly created RangeError object).
auto error = vm.throw_completion<RangeError>(ErrorType::NumberIsNegative, "limit"sv);
// 7. Let iterated be ? GetIteratorDirect(O).
auto iterated = TRY(get_iterator_direct(vm, object));
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 8. Let closure be a new Abstract Closure with no parameters that captures iterated and integerLimit and performs the following steps when called:
// 9. Set iterated to ? GetIteratorDirect(O).
iterated = TRY(get_iterator_direct(vm, object));
// 10. Let closure be a new Abstract Closure with no parameters that captures iterated and integerLimit and performs
// the following steps when called:
auto closure = GC::create_function(realm.heap(), [integer_limit](VM& vm, IteratorHelper& iterator) -> ThrowCompletionOr<Value> {
auto& iterated = iterator.underlying_iterator();
// a. Let remaining be integerLimit.
// b. Repeat, while remaining > 0,
while (iterator.counter() < integer_limit) {
// i. If remaining is not +∞, then
// i. If remaining +∞, then
// 1. Set remaining to remaining - 1.
iterator.increment_counter();
// ii. Let next be ? IteratorStep(iterated).
auto next = TRY(iterator_step(vm, iterated));
// iii. If next is false, return undefined.
// iii. If next is DONE, return ReturnCompletion(undefined).
if (!next)
return iterator.result(js_undefined());
}
@ -125,7 +140,7 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::drop)
// i. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// ii. If value is done, return undefined.
// ii. If value is DONE, return ReturnCompletion(undefined).
if (!value.has_value())
return iterator.result(js_undefined());
@ -134,8 +149,8 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::drop)
return iterator.result(*value);
});
// 9. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 10. Set result.[[UnderlyingIterator]] to iterated.
// 11. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 12. Set result.[[UnderlyingIterator]] to iterated.
auto result = TRY(IteratorHelper::create(realm, iterated, closure));
// 11. Return result.
@ -145,44 +160,48 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::drop)
// 27.1.4.3 Iterator.prototype.every ( predicate ), https://tc39.es/ecma262/#sec-iterator.prototype.every
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::every)
{
auto& realm = *vm.current_realm();
auto predicate = vm.argument(0);
// 1. Let O be the this value.
// 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm));
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
if (!predicate.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. Let iterated be ? GetIteratorDirect(O).
auto iterated = TRY(get_iterator_direct(vm, object));
// 4. If IsCallable(predicate) is false, then
if (!predicate.is_function()) {
// a. Let error be ThrowCompletion(a newly created TypeError object).
auto error = vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 5. Let counter be 0.
size_t counter = 0;
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 6. Repeat,
while (true) {
// 5. Set iterated to ? GetIteratorDirect(O).
iterated = TRY(get_iterator_direct(vm, object));
// 6. Let counter be 0.
// 7. Repeat,
for (size_t counter = 0;; ++counter) {
// a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// b. If value is done, return undefined.
// b. If value is DONE, return true.
if (!value.has_value())
return Value { true };
// c. Let result be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)).
auto result = call(vm, predicate.as_function(), js_undefined(), *value, Value { counter });
// d. IfAbruptCloseIterator(result, iterated).
if (result.is_error())
return TRY(iterator_close(vm, iterated, result.release_error()));
auto result = TRY_OR_CLOSE_ITERATOR(vm, iterated, call(vm, predicate.as_function(), js_undefined(), *value, Value { counter }));
// e. If ToBoolean(result) is false, return ? IteratorClose(iterated, NormalCompletion(false)).
if (!result.value().to_boolean())
if (!result.to_boolean())
return TRY(iterator_close(vm, iterated, normal_completion(Value { false })));
// f. Set counter to counter + 1.
++counter;
}
}
@ -197,25 +216,33 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::filter)
// 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm));
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
if (!predicate.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. Let iterated be ? GetIteratorDirect(O).
auto iterated = TRY(get_iterator_direct(vm, object));
// 4. If IsCallable(predicate) is false, then
if (!predicate.is_function()) {
// a. Let error be ThrowCompletion(a newly created TypeError object).
auto error = vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 5. Let closure be a new Abstract Closure with no parameters that captures iterated and predicate and performs the following steps when called:
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 5. Set iterated to ? GetIteratorDirect(O).
iterated = TRY(get_iterator_direct(vm, object));
// 6. Let closure be a new Abstract Closure with no parameters that captures iterated and predicate and performs the
// following steps when called:
auto closure = GC::create_function(realm.heap(), [predicate = GC::Ref { predicate.as_function() }](VM& vm, IteratorHelper& iterator) -> ThrowCompletionOr<Value> {
auto& iterated = iterator.underlying_iterator();
// a. Let counter be 0.
// b. Repeat,
while (true) {
// i. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// ii. If value is done, return undefined.
// ii. If value is DONE, return ReturnCompletion(undefined).
if (!value.has_value())
return iterator.result(js_undefined());
@ -239,55 +266,59 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::filter)
}
});
// 6. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 7. Set result.[[UnderlyingIterator]] to iterated.
// 7. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 8. Set result.[[UnderlyingIterator]] to iterated.
auto result = TRY(IteratorHelper::create(realm, iterated, closure));
// 8. Return result.
// 9. Return result.
return result;
}
// 27.1.4.5 Iterator.prototype.find ( predicate ), https://tc39.es/ecma262/#sec-iterator.prototype.find
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::find)
{
auto& realm = *vm.current_realm();
auto predicate = vm.argument(0);
// 1. Let O be the this value.
// 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm));
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
if (!predicate.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. Let iterated be ? GetIteratorDirect(O).
auto iterated = TRY(get_iterator_direct(vm, object));
// 4. If IsCallable(predicate) is false, then
if (!predicate.is_function()) {
// a. Let error be ThrowCompletion(a newly created TypeError object).
auto error = vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 5. Let counter be 0.
size_t counter = 0;
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 6. Repeat,
while (true) {
// 5. Set iterated to ? GetIteratorDirect(O).
iterated = TRY(get_iterator_direct(vm, object));
// 6. Let counter be 0.
// 7. Repeat,
for (size_t counter = 0;; ++counter) {
// a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// b. If value is done, return undefined.
// b. If value is DONE, return undefined.
if (!value.has_value())
return js_undefined();
// c. Let result be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)).
auto result = call(vm, predicate.as_function(), js_undefined(), *value, Value { counter });
// d. IfAbruptCloseIterator(result, iterated).
if (result.is_error())
return TRY(iterator_close(vm, iterated, result.release_error()));
auto result = TRY_OR_CLOSE_ITERATOR(vm, iterated, call(vm, predicate.as_function(), js_undefined(), *value, Value { counter }));
// e. If ToBoolean(result) is true, return ? IteratorClose(iterated, NormalCompletion(value)).
if (result.value().to_boolean())
if (result.to_boolean())
return TRY(iterator_close(vm, iterated, normal_completion(*value)));
// f. Set counter to counter + 1.
++counter;
}
}
@ -303,7 +334,7 @@ public:
return next_outer_iterator(vm, iterated, iterator, mapper);
}
// NOTE: This implements step 5.b.vii.4.b of Iterator.prototype.flatMap.
// NOTE: This implements step 6.b.vii.4.b of Iterator.prototype.flatMap.
ThrowCompletionOr<Value> on_abrupt_completion(VM& vm, IteratorHelper& iterator, Completion const& completion)
{
VERIFY(m_inner_iterator);
@ -335,7 +366,7 @@ private:
// i. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// ii. If value is done, return undefined.
// ii. If value is DONE, return undefined.
if (!value.has_value())
return iterator.result(js_undefined());
@ -375,7 +406,7 @@ private:
if (inner_value.is_error())
return iterator.close_result(vm, inner_value.release_error());
// 3. If innerValue is done, then
// 3. If innerValue is DONE, then
if (!inner_value.value().has_value()) {
// a. Set innerAlive to false.
m_inner_iterator = nullptr;
@ -406,16 +437,25 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::flat_map)
// 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm));
// 3. If IsCallable(mapper) is false, throw a TypeError exception.
if (!mapper.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "mapper"sv);
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. Let iterated be ? GetIteratorDirect(O).
auto iterated = TRY(get_iterator_direct(vm, object));
// 4. If IsCallable(mapper) is false, then
if (!mapper.is_function()) {
// a. Let error be ThrowCompletion(a newly created TypeError object).
auto error = vm.throw_completion<TypeError>(ErrorType::NotAFunction, "mapper"sv);
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 5. Set iterated to ? GetIteratorDirect(O).
iterated = TRY(get_iterator_direct(vm, object));
auto flat_map_iterator = realm.create<FlatMapIterator>();
// 5. Let closure be a new Abstract Closure with no parameters that captures iterated and mapper and performs the following steps when called:
// 7. Let closure be a new Abstract Closure with no parameters that captures iterated and mapper and performs the
// following steps when called:
auto closure = GC::create_function(realm.heap(), [flat_map_iterator, mapper = GC::Ref { mapper.as_function() }](VM& vm, IteratorHelper& iterator) mutable -> ThrowCompletionOr<Value> {
auto& iterated = iterator.underlying_iterator();
return flat_map_iterator->next(vm, iterated, iterator, *mapper);
@ -425,51 +465,55 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::flat_map)
return flat_map_iterator->on_abrupt_completion(vm, iterator, completion);
});
// 6. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 7. Set result.[[UnderlyingIterator]] to iterated.
// 8. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 9. Set result.[[UnderlyingIterator]] to iterated.
auto result = TRY(IteratorHelper::create(realm, iterated, closure, move(abrupt_closure)));
// 8. Return result.
// 9. Return result.
return result;
}
// 27.1.4.7 Iterator.prototype.forEach ( procedure ), https://tc39.es/ecma262/#sec-iterator.prototype.foreach
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::for_each)
{
auto function = vm.argument(0);
auto& realm = *vm.current_realm();
auto procedure = vm.argument(0);
// 1. Let O be the this value.
// 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm));
// 3. If IsCallable(fn) is false, throw a TypeError exception.
if (!function.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "fn"sv);
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. Let iterated be ? GetIteratorDirect(O).
auto iterated = TRY(get_iterator_direct(vm, object));
// 4. If IsCallable(procedure) is false, then
if (!procedure.is_function()) {
// a. Let error be ThrowCompletion(a newly created TypeError object).
auto error = vm.throw_completion<TypeError>(ErrorType::NotAFunction, "procedure"sv);
// 5. Let counter be 0.
size_t counter = 0;
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 6. Repeat,
while (true) {
// 5. Set iterated to ? GetIteratorDirect(O).
iterated = TRY(get_iterator_direct(vm, object));
// 6. Let counter be 0.
// 7. Repeat,
for (size_t counter = 0;; ++counter) {
// a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// b. If value is done, return undefined.
// b. If value is DONE, return undefined.
if (!value.has_value())
return js_undefined();
// c. Let result be Completion(Call(fn, undefined, « value, 𝔽(counter) »)).
auto result = call(vm, function.as_function(), js_undefined(), *value, Value { counter });
// c. Let result be Completion(Call(procedure, undefined, « value, 𝔽(counter) »)).
// d. IfAbruptCloseIterator(result, iterated).
if (result.is_error())
return TRY(iterator_close(vm, iterated, result.release_error()));
TRY_OR_CLOSE_ITERATOR(vm, iterated, call(vm, procedure.as_function(), js_undefined(), *value, Value { counter }));
// e. Set counter to counter + 1.
++counter;
}
}
@ -484,14 +528,23 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::map)
// 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm));
// 3. If IsCallable(mapper) is false, throw a TypeError exception.
if (!mapper.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "mapper"sv);
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. Let iterated be ? GetIteratorDirect(O).
auto iterated = TRY(get_iterator_direct(vm, object));
// 4. If IsCallable(mapper) is false, then
if (!mapper.is_function()) {
// a. Let error be ThrowCompletion(a newly created TypeError object).
auto error = vm.throw_completion<TypeError>(ErrorType::NotAFunction, "mapper"sv);
// 5. Let closure be a new Abstract Closure with no parameters that captures iterated and mapper and performs the following steps when called:
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 5. Set iterated to ? GetIteratorDirect(O).
iterated = TRY(get_iterator_direct(vm, object));
// 6. Let closure be a new Abstract Closure with no parameters that captures iterated and mapper and performs the
// following steps when called:
auto closure = GC::create_function(realm.heap(), [mapper = GC::Ref { mapper.as_function() }](VM& vm, IteratorHelper& iterator) -> ThrowCompletionOr<Value> {
auto& iterated = iterator.underlying_iterator();
@ -501,7 +554,7 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::map)
// i. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// ii. If value is done, return undefined.
// ii. If value is DONE, return undefined.
if (!value.has_value())
return iterator.result(js_undefined());
@ -521,48 +574,58 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::map)
return iterator.result(mapped.release_value());
});
// 6. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 7. Set result.[[UnderlyingIterator]] to iterated.
// 7. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 8. Set result.[[UnderlyingIterator]] to iterated.
auto result = TRY(IteratorHelper::create(realm, iterated, closure));
// 8. Return result.
// 9. Return result.
return result;
}
// 27.1.4.9 Iterator.prototype.reduce ( reducer [ , initialValue ] ), https://tc39.es/ecma262/#sec-iterator.prototype.reduce
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::reduce)
{
auto& realm = *vm.current_realm();
auto reducer = vm.argument(0);
// 1. Let O be the this value.
// 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm));
// 3. If IsCallable(reducer) is false, throw a TypeError exception.
if (!reducer.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "reducer"sv);
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. Let iterated be ? GetIteratorDirect(O).
auto iterated = TRY(get_iterator_direct(vm, object));
// 4. If IsCallable(reducer) is false, then
if (!reducer.is_function()) {
// a. Let error be ThrowCompletion(a newly created TypeError object).
auto error = vm.throw_completion<TypeError>(ErrorType::NotAFunction, "reducer"sv);
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 5. Set iterated to ? GetIteratorDirect(O).
iterated = TRY(get_iterator_direct(vm, object));
Value accumulator;
size_t counter = 0;
// 5. If initialValue is not present, then
// 6. If initialValue is not present, then
if (vm.argument_count() < 2) {
// a. Let accumulator be ? IteratorStepValue(iterated).
auto maybe_accumulator = TRY(iterator_step_value(vm, iterated));
// b. If accumulator is done, throw a TypeError exception.
// b. If accumulator is DONE, throw a TypeError exception.
if (!maybe_accumulator.has_value())
return vm.throw_completion<TypeError>(ErrorType::ReduceNoInitial);
// d. Let counter be 1.
// c. Let counter be 1.
counter = 1;
accumulator = maybe_accumulator.release_value();
}
// 6. Else,
// 7. Else,
else {
// a. Let accumulator be initialValue.
accumulator = vm.argument(1);
@ -571,12 +634,12 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::reduce)
counter = 0;
}
// 7. Repeat,
while (true) {
// 8. Repeat,
for (;; ++counter) {
// a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// b. If value is done, return accumulator.
// b. If value is DONE, return accumulator.
if (!value.has_value())
return accumulator;
@ -591,35 +654,42 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::reduce)
accumulator = result.release_value();
// f. Set counter to counter + 1.
++counter;
}
}
// 27.1.4.10 Iterator.prototype.some ( predicate ), https://tc39.es/ecma262/#sec-iterator.prototype.some
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::some)
{
auto& realm = *vm.current_realm();
auto predicate = vm.argument(0);
// 1. Let O be the this value.
// 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm));
// 3. If IsCallable(predicate) is false, throw a TypeError exception.
if (!predicate.is_function())
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. Let iterated be ? GetIteratorDirect(O).
auto iterated = TRY(get_iterator_direct(vm, object));
// 4. If IsCallable(predicate) is false, then
if (!predicate.is_function()) {
// a. Let error be ThrowCompletion(a newly created TypeError object).
auto error = vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 5. Let counter be 0.
size_t counter = 0;
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 6. Repeat,
while (true) {
// 5. Set iterated to ? GetIteratorDirect(O).
iterated = TRY(get_iterator_direct(vm, object));
// 6. Let counter be 0.
// 7. Repeat,
for (size_t counter = 0;; ++counter) {
// a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// b. If value is done, return undefined.
// b. If value is DONE, return false.
if (!value.has_value())
return Value { false };
@ -635,7 +705,6 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::some)
return TRY(iterator_close(vm, iterated, normal_completion(Value { true })));
// f. Set counter to counter + 1.
++counter;
}
}
@ -650,44 +719,59 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::take)
// 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm));
// 3. Let numLimit be ? ToNumber(limit).
auto numeric_limit = TRY(limit.to_number(vm));
// 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. If numLimit is NaN, throw a RangeError exception.
if (numeric_limit.is_nan())
return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "limit"sv);
// 4. Let numLimit be Completion(ToNumber(limit)).
// 5. IfAbruptCloseIterator(numLimit, iterated).
auto numeric_limit = TRY_OR_CLOSE_ITERATOR(vm, iterated, limit.to_number(vm));
// 5. Let integerLimit be ! ToIntegerOrInfinity(numLimit).
// 6. If numLimit is NaN, then
if (numeric_limit.is_nan()) {
// a. Let error be ThrowCompletion(a newly created RangeError object).
auto error = vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "limit"sv);
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 7. Let integerLimit be ! ToIntegerOrInfinity(numLimit).
auto integer_limit = MUST(numeric_limit.to_integer_or_infinity(vm));
// 6. If integerLimit < 0, throw a RangeError exception.
if (integer_limit < 0)
return vm.throw_completion<RangeError>(ErrorType::NumberIsNegative, "limit"sv);
// 8. If integerLimit < 0, then
if (integer_limit < 0) {
// a. Let error be ThrowCompletion(a newly created RangeError object).
auto error = vm.throw_completion<RangeError>(ErrorType::NumberIsNegative, "limit"sv);
// 7. Let iterated be ? GetIteratorDirect(O).
auto iterated = TRY(get_iterator_direct(vm, object));
// b. Return ? IteratorClose(iterated, error).
return iterator_close(vm, iterated, error);
}
// 8. Let closure be a new Abstract Closure with no parameters that captures iterated and integerLimit and performs the following steps when called:
// 9. Set iterated to ? GetIteratorDirect(O).
iterated = TRY(get_iterator_direct(vm, object));
// 10. Let closure be a new Abstract Closure with no parameters that captures iterated and integerLimit and performs
// the following steps when called:
auto closure = GC::create_function(realm.heap(), [integer_limit](VM& vm, IteratorHelper& iterator) -> ThrowCompletionOr<Value> {
auto& iterated = iterator.underlying_iterator();
// a. Let remaining be integerLimit.
// b. Repeat,
// i. If remaining is 0, then
// i. If remaining = 0, then
if (iterator.counter() >= integer_limit) {
// 1. Return ? IteratorClose(iterated, NormalCompletion(undefined)).
return iterator.close_result(vm, normal_completion(js_undefined()));
}
// ii. If remaining is not +∞, then
// ii. If remaining +∞, then
// 1. Set remaining to remaining - 1.
iterator.increment_counter();
// iii. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// iv. If value is done, return undefined.
// iv. If value is DONE, return ReturnCompletion(undefined)..
if (!value.has_value())
return iterator.result(js_undefined());
@ -696,11 +780,11 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::take)
return iterator.result(*value);
});
// 9. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 10. Set result.[[UnderlyingIterator]] to iterated.
// 11. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 12. Set result.[[UnderlyingIterator]] to iterated.
auto result = TRY(IteratorHelper::create(realm, iterated, closure));
// 11. Return result.
// 13. Return result.
return result;
}
@ -724,7 +808,7 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::to_array)
// a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, iterated));
// b. If value is done, return CreateArrayFromList(items).
// b. If value is DONE, return CreateArrayFromList(items).
if (!value.has_value())
return Array::create_from(realm, items);

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
});
test("argument validation closes underlying iterator", () => {
let closed = false;
let iterator = {
__proto__: Iterator.prototype,
return() {
closed = true;
return {};
},
};
expect(() => {
iterator.drop(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
expect(closed).toBeTrue();
});
test("called with invalid numbers", () => {
expect(() => {
Iterator.prototype.drop(NaN);

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "predicate is not a function");
});
test("argument validation closes underlying iterator", () => {
let closed = false;
let iterator = {
__proto__: Iterator.prototype,
return() {
closed = true;
return {};
},
};
expect(() => {
iterator.every(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "predicate is not a function");
expect(closed).toBeTrue();
});
test("iterator's next method throws", () => {
function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "predicate is not a function");
});
test("argument validation closes underlying iterator", () => {
let closed = false;
let iterator = {
__proto__: Iterator.prototype,
return() {
closed = true;
return {};
},
};
expect(() => {
iterator.filter(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "predicate is not a function");
expect(closed).toBeTrue();
});
test("iterator's next method throws", () => {
function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "predicate is not a function");
});
test("argument validation closes underlying iterator", () => {
let closed = false;
let iterator = {
__proto__: Iterator.prototype,
return() {
closed = true;
return {};
},
};
expect(() => {
iterator.find(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "predicate is not a function");
expect(closed).toBeTrue();
});
test("iterator's next method throws", () => {
function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "mapper is not a function");
});
test("argument validation closes underlying iterator", () => {
let closed = false;
let iterator = {
__proto__: Iterator.prototype,
return() {
closed = true;
return {};
},
};
expect(() => {
iterator.flatMap(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "mapper is not a function");
expect(closed).toBeTrue();
});
test("iterator's next method throws", () => {
function TestError() {}

View file

@ -2,7 +2,25 @@ describe("errors", () => {
test("called with non-callable object", () => {
expect(() => {
Iterator.prototype.forEach(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "fn is not a function");
}).toThrowWithMessage(TypeError, "procedure is not a function");
});
test("argument validation closes underlying iterator", () => {
let closed = false;
let iterator = {
__proto__: Iterator.prototype,
return() {
closed = true;
return {};
},
};
expect(() => {
iterator.forEach(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "procedure is not a function");
expect(closed).toBeTrue();
});
test("iterator's next method throws", () => {

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "mapper is not a function");
});
test("argument validation closes underlying iterator", () => {
let closed = false;
let iterator = {
__proto__: Iterator.prototype,
return() {
closed = true;
return {};
},
};
expect(() => {
iterator.map(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "mapper is not a function");
expect(closed).toBeTrue();
});
test("iterator's next method throws", () => {
function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "reducer is not a function");
});
test("argument validation closes underlying iterator", () => {
let closed = false;
let iterator = {
__proto__: Iterator.prototype,
return() {
closed = true;
return {};
},
};
expect(() => {
iterator.reduce(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "reducer is not a function");
expect(closed).toBeTrue();
});
test("iterator's next method throws", () => {
function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "predicate is not a function");
});
test("argument validation closes underlying iterator", () => {
let closed = false;
let iterator = {
__proto__: Iterator.prototype,
return() {
closed = true;
return {};
},
};
expect(() => {
iterator.some(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "predicate is not a function");
expect(closed).toBeTrue();
});
test("iterator's next method throws", () => {
function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
});
test("argument validation closes underlying iterator", () => {
let closed = false;
let iterator = {
__proto__: Iterator.prototype,
return() {
closed = true;
return {};
},
};
expect(() => {
iterator.take(Symbol.hasInstance);
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number");
expect(closed).toBeTrue();
});
test("called with invalid numbers", () => {
expect(() => {
Iterator.prototype.take(NaN);