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. // 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm)); auto object = TRY(this_object(vm));
// 3. Let numLimit be ? ToNumber(limit). // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto numeric_limit = TRY(limit.to_number(vm)); auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. If numLimit is NaN, throw a RangeError exception. // 4. Let numLimit be Completion(ToNumber(limit)).
if (numeric_limit.is_nan()) // 5. IfAbruptCloseIterator(numLimit, iterated).
return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "limit"sv); 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)); auto integer_limit = MUST(numeric_limit.to_integer_or_infinity(vm));
// 6. If integerLimit < 0, throw a RangeError exception. // 8. If integerLimit < 0, then
if (integer_limit < 0) if (integer_limit < 0) {
return vm.throw_completion<RangeError>(ErrorType::NumberIsNegative, "limit"sv); // 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). // b. Return ? IteratorClose(iterated, error).
auto iterated = TRY(get_iterator_direct(vm, object)); 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 closure = GC::create_function(realm.heap(), [integer_limit](VM& vm, IteratorHelper& iterator) -> ThrowCompletionOr<Value> {
auto& iterated = iterator.underlying_iterator(); auto& iterated = iterator.underlying_iterator();
// a. Let remaining be integerLimit. // a. Let remaining be integerLimit.
// b. Repeat, while remaining > 0, // b. Repeat, while remaining > 0,
while (iterator.counter() < integer_limit) { while (iterator.counter() < integer_limit) {
// i. If remaining is not +∞, then // i. If remaining +∞, then
// 1. Set remaining to remaining - 1. // 1. Set remaining to remaining - 1.
iterator.increment_counter(); iterator.increment_counter();
// ii. Let next be ? IteratorStep(iterated). // ii. Let next be ? IteratorStep(iterated).
auto next = TRY(iterator_step(vm, 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) if (!next)
return iterator.result(js_undefined()); return iterator.result(js_undefined());
} }
@ -125,7 +140,7 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::drop)
// i. Let value be ? IteratorStepValue(iterated). // i. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return iterator.result(js_undefined()); return iterator.result(js_undefined());
@ -134,8 +149,8 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::drop)
return iterator.result(*value); return iterator.result(*value);
}); });
// 9. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »). // 11. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 10. Set result.[[UnderlyingIterator]] to iterated. // 12. Set result.[[UnderlyingIterator]] to iterated.
auto result = TRY(IteratorHelper::create(realm, iterated, closure)); auto result = TRY(IteratorHelper::create(realm, iterated, closure));
// 11. Return result. // 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 // 27.1.4.3 Iterator.prototype.every ( predicate ), https://tc39.es/ecma262/#sec-iterator.prototype.every
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::every) JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::every)
{ {
auto& realm = *vm.current_realm();
auto predicate = vm.argument(0); auto predicate = vm.argument(0);
// 1. Let O be the this value. // 1. Let O be the this value.
// 2. If O is not an Object, throw a TypeError exception. // 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm)); auto object = TRY(this_object(vm));
// 3. If IsCallable(predicate) is false, throw a TypeError exception. // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
if (!predicate.is_function()) auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 4. Let iterated be ? GetIteratorDirect(O). // 4. If IsCallable(predicate) is false, then
auto iterated = TRY(get_iterator_direct(vm, object)); 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. // b. Return ? IteratorClose(iterated, error).
size_t counter = 0; return iterator_close(vm, iterated, error);
}
// 6. Repeat, // 5. Set iterated to ? GetIteratorDirect(O).
while (true) { 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). // a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return Value { true }; return Value { true };
// c. Let result be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)). // 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). // d. IfAbruptCloseIterator(result, iterated).
if (result.is_error()) auto result = TRY_OR_CLOSE_ITERATOR(vm, iterated, call(vm, predicate.as_function(), js_undefined(), *value, Value { counter }));
return TRY(iterator_close(vm, iterated, result.release_error()));
// e. If ToBoolean(result) is false, return ? IteratorClose(iterated, NormalCompletion(false)). // 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 }))); return TRY(iterator_close(vm, iterated, normal_completion(Value { false })));
// f. Set counter to counter + 1. // 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. // 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm)); auto object = TRY(this_object(vm));
// 3. If IsCallable(predicate) is false, throw a TypeError exception. // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
if (!predicate.is_function()) auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 4. Let iterated be ? GetIteratorDirect(O). // 4. If IsCallable(predicate) is false, then
auto iterated = TRY(get_iterator_direct(vm, object)); 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 closure = GC::create_function(realm.heap(), [predicate = GC::Ref { predicate.as_function() }](VM& vm, IteratorHelper& iterator) -> ThrowCompletionOr<Value> {
auto& iterated = iterator.underlying_iterator(); auto& iterated = iterator.underlying_iterator();
// a. Let counter be 0. // a. Let counter be 0.
// b. Repeat, // b. Repeat,
while (true) { while (true) {
// i. Let value be ? IteratorStepValue(iterated). // i. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return iterator.result(js_undefined()); 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. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 7. Set result.[[UnderlyingIterator]] to iterated. // 8. Set result.[[UnderlyingIterator]] to iterated.
auto result = TRY(IteratorHelper::create(realm, iterated, closure)); auto result = TRY(IteratorHelper::create(realm, iterated, closure));
// 8. Return result. // 9. Return result.
return result; return result;
} }
// 27.1.4.5 Iterator.prototype.find ( predicate ), https://tc39.es/ecma262/#sec-iterator.prototype.find // 27.1.4.5 Iterator.prototype.find ( predicate ), https://tc39.es/ecma262/#sec-iterator.prototype.find
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::find) JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::find)
{ {
auto& realm = *vm.current_realm();
auto predicate = vm.argument(0); auto predicate = vm.argument(0);
// 1. Let O be the this value. // 1. Let O be the this value.
// 2. If O is not an Object, throw a TypeError exception. // 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm)); auto object = TRY(this_object(vm));
// 3. If IsCallable(predicate) is false, throw a TypeError exception. // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
if (!predicate.is_function()) auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 4. Let iterated be ? GetIteratorDirect(O). // 4. If IsCallable(predicate) is false, then
auto iterated = TRY(get_iterator_direct(vm, object)); 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. // b. Return ? IteratorClose(iterated, error).
size_t counter = 0; return iterator_close(vm, iterated, error);
}
// 6. Repeat, // 5. Set iterated to ? GetIteratorDirect(O).
while (true) { 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). // a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return js_undefined(); return js_undefined();
// c. Let result be Completion(Call(predicate, undefined, « value, 𝔽(counter) »)). // 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). // d. IfAbruptCloseIterator(result, iterated).
if (result.is_error()) auto result = TRY_OR_CLOSE_ITERATOR(vm, iterated, call(vm, predicate.as_function(), js_undefined(), *value, Value { counter }));
return TRY(iterator_close(vm, iterated, result.release_error()));
// e. If ToBoolean(result) is true, return ? IteratorClose(iterated, NormalCompletion(value)). // 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))); return TRY(iterator_close(vm, iterated, normal_completion(*value)));
// f. Set counter to counter + 1. // f. Set counter to counter + 1.
++counter;
} }
} }
@ -303,7 +334,7 @@ public:
return next_outer_iterator(vm, iterated, iterator, mapper); 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) ThrowCompletionOr<Value> on_abrupt_completion(VM& vm, IteratorHelper& iterator, Completion const& completion)
{ {
VERIFY(m_inner_iterator); VERIFY(m_inner_iterator);
@ -335,7 +366,7 @@ private:
// i. Let value be ? IteratorStepValue(iterated). // i. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return iterator.result(js_undefined()); return iterator.result(js_undefined());
@ -375,7 +406,7 @@ private:
if (inner_value.is_error()) if (inner_value.is_error())
return iterator.close_result(vm, inner_value.release_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()) { if (!inner_value.value().has_value()) {
// a. Set innerAlive to false. // a. Set innerAlive to false.
m_inner_iterator = nullptr; 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. // 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm)); auto object = TRY(this_object(vm));
// 3. If IsCallable(mapper) is false, throw a TypeError exception. // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
if (!mapper.is_function()) auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "mapper"sv);
// 4. Let iterated be ? GetIteratorDirect(O). // 4. If IsCallable(mapper) is false, then
auto iterated = TRY(get_iterator_direct(vm, object)); 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>(); 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 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(); auto& iterated = iterator.underlying_iterator();
return flat_map_iterator->next(vm, iterated, iterator, *mapper); 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); return flat_map_iterator->on_abrupt_completion(vm, iterator, completion);
}); });
// 6. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »). // 8. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 7. Set result.[[UnderlyingIterator]] to iterated. // 9. Set result.[[UnderlyingIterator]] to iterated.
auto result = TRY(IteratorHelper::create(realm, iterated, closure, move(abrupt_closure))); auto result = TRY(IteratorHelper::create(realm, iterated, closure, move(abrupt_closure)));
// 8. Return result. // 9. Return result.
return result; return result;
} }
// 27.1.4.7 Iterator.prototype.forEach ( procedure ), https://tc39.es/ecma262/#sec-iterator.prototype.foreach // 27.1.4.7 Iterator.prototype.forEach ( procedure ), https://tc39.es/ecma262/#sec-iterator.prototype.foreach
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::for_each) 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. // 1. Let O be the this value.
// 2. If O is not an Object, throw a TypeError exception. // 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm)); auto object = TRY(this_object(vm));
// 3. If IsCallable(fn) is false, throw a TypeError exception. // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
if (!function.is_function()) auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "fn"sv);
// 4. Let iterated be ? GetIteratorDirect(O). // 4. If IsCallable(procedure) is false, then
auto iterated = TRY(get_iterator_direct(vm, object)); 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. // b. Return ? IteratorClose(iterated, error).
size_t counter = 0; return iterator_close(vm, iterated, error);
}
// 6. Repeat, // 5. Set iterated to ? GetIteratorDirect(O).
while (true) { 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). // a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return js_undefined(); return js_undefined();
// c. Let result be Completion(Call(fn, undefined, « value, 𝔽(counter) »)). // c. Let result be Completion(Call(procedure, undefined, « value, 𝔽(counter) »)).
auto result = call(vm, function.as_function(), js_undefined(), *value, Value { counter });
// d. IfAbruptCloseIterator(result, iterated). // d. IfAbruptCloseIterator(result, iterated).
if (result.is_error()) TRY_OR_CLOSE_ITERATOR(vm, iterated, call(vm, procedure.as_function(), js_undefined(), *value, Value { counter }));
return TRY(iterator_close(vm, iterated, result.release_error()));
// e. Set counter to counter + 1. // 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. // 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm)); auto object = TRY(this_object(vm));
// 3. If IsCallable(mapper) is false, throw a TypeError exception. // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
if (!mapper.is_function()) auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "mapper"sv);
// 4. Let iterated be ? GetIteratorDirect(O). // 4. If IsCallable(mapper) is false, then
auto iterated = TRY(get_iterator_direct(vm, object)); 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 closure = GC::create_function(realm.heap(), [mapper = GC::Ref { mapper.as_function() }](VM& vm, IteratorHelper& iterator) -> ThrowCompletionOr<Value> {
auto& iterated = iterator.underlying_iterator(); auto& iterated = iterator.underlying_iterator();
@ -501,7 +554,7 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::map)
// i. Let value be ? IteratorStepValue(iterated). // i. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return iterator.result(js_undefined()); return iterator.result(js_undefined());
@ -521,48 +574,58 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::map)
return iterator.result(mapped.release_value()); return iterator.result(mapped.release_value());
}); });
// 6. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »). // 7. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 7. Set result.[[UnderlyingIterator]] to iterated. // 8. Set result.[[UnderlyingIterator]] to iterated.
auto result = TRY(IteratorHelper::create(realm, iterated, closure)); auto result = TRY(IteratorHelper::create(realm, iterated, closure));
// 8. Return result. // 9. Return result.
return result; return result;
} }
// 27.1.4.9 Iterator.prototype.reduce ( reducer [ , initialValue ] ), https://tc39.es/ecma262/#sec-iterator.prototype.reduce // 27.1.4.9 Iterator.prototype.reduce ( reducer [ , initialValue ] ), https://tc39.es/ecma262/#sec-iterator.prototype.reduce
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::reduce) JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::reduce)
{ {
auto& realm = *vm.current_realm();
auto reducer = vm.argument(0); auto reducer = vm.argument(0);
// 1. Let O be the this value. // 1. Let O be the this value.
// 2. If O is not an Object, throw a TypeError exception. // 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm)); auto object = TRY(this_object(vm));
// 3. If IsCallable(reducer) is false, throw a TypeError exception. // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
if (!reducer.is_function()) auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "reducer"sv);
// 4. Let iterated be ? GetIteratorDirect(O). // 4. If IsCallable(reducer) is false, then
auto iterated = TRY(get_iterator_direct(vm, object)); 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; Value accumulator;
size_t counter = 0; size_t counter = 0;
// 5. If initialValue is not present, then // 6. If initialValue is not present, then
if (vm.argument_count() < 2) { if (vm.argument_count() < 2) {
// a. Let accumulator be ? IteratorStepValue(iterated). // a. Let accumulator be ? IteratorStepValue(iterated).
auto maybe_accumulator = TRY(iterator_step_value(vm, 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()) if (!maybe_accumulator.has_value())
return vm.throw_completion<TypeError>(ErrorType::ReduceNoInitial); return vm.throw_completion<TypeError>(ErrorType::ReduceNoInitial);
// d. Let counter be 1. // c. Let counter be 1.
counter = 1; counter = 1;
accumulator = maybe_accumulator.release_value(); accumulator = maybe_accumulator.release_value();
} }
// 6. Else, // 7. Else,
else { else {
// a. Let accumulator be initialValue. // a. Let accumulator be initialValue.
accumulator = vm.argument(1); accumulator = vm.argument(1);
@ -571,12 +634,12 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::reduce)
counter = 0; counter = 0;
} }
// 7. Repeat, // 8. Repeat,
while (true) { for (;; ++counter) {
// a. Let value be ? IteratorStepValue(iterated). // a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return accumulator; return accumulator;
@ -591,35 +654,42 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::reduce)
accumulator = result.release_value(); accumulator = result.release_value();
// f. Set counter to counter + 1. // f. Set counter to counter + 1.
++counter;
} }
} }
// 27.1.4.10 Iterator.prototype.some ( predicate ), https://tc39.es/ecma262/#sec-iterator.prototype.some // 27.1.4.10 Iterator.prototype.some ( predicate ), https://tc39.es/ecma262/#sec-iterator.prototype.some
JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::some) JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::some)
{ {
auto& realm = *vm.current_realm();
auto predicate = vm.argument(0); auto predicate = vm.argument(0);
// 1. Let O be the this value. // 1. Let O be the this value.
// 2. If O is not an Object, throw a TypeError exception. // 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm)); auto object = TRY(this_object(vm));
// 3. If IsCallable(predicate) is false, throw a TypeError exception. // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
if (!predicate.is_function()) auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
return vm.throw_completion<TypeError>(ErrorType::NotAFunction, "predicate"sv);
// 4. Let iterated be ? GetIteratorDirect(O). // 4. If IsCallable(predicate) is false, then
auto iterated = TRY(get_iterator_direct(vm, object)); 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. // b. Return ? IteratorClose(iterated, error).
size_t counter = 0; return iterator_close(vm, iterated, error);
}
// 6. Repeat, // 5. Set iterated to ? GetIteratorDirect(O).
while (true) { 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). // a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return Value { false }; return Value { false };
@ -635,7 +705,6 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::some)
return TRY(iterator_close(vm, iterated, normal_completion(Value { true }))); return TRY(iterator_close(vm, iterated, normal_completion(Value { true })));
// f. Set counter to counter + 1. // 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. // 2. If O is not an Object, throw a TypeError exception.
auto object = TRY(this_object(vm)); auto object = TRY(this_object(vm));
// 3. Let numLimit be ? ToNumber(limit). // 3. Let iterated be the Iterator Record { [[Iterator]]: O, [[NextMethod]]: undefined, [[Done]]: false }.
auto numeric_limit = TRY(limit.to_number(vm)); auto iterated = realm.create<IteratorRecord>(object, js_undefined(), false);
// 4. If numLimit is NaN, throw a RangeError exception. // 4. Let numLimit be Completion(ToNumber(limit)).
if (numeric_limit.is_nan()) // 5. IfAbruptCloseIterator(numLimit, iterated).
return vm.throw_completion<RangeError>(ErrorType::NumberIsNaN, "limit"sv); 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)); auto integer_limit = MUST(numeric_limit.to_integer_or_infinity(vm));
// 6. If integerLimit < 0, throw a RangeError exception. // 8. If integerLimit < 0, then
if (integer_limit < 0) if (integer_limit < 0) {
return vm.throw_completion<RangeError>(ErrorType::NumberIsNegative, "limit"sv); // 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). // b. Return ? IteratorClose(iterated, error).
auto iterated = TRY(get_iterator_direct(vm, object)); 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 closure = GC::create_function(realm.heap(), [integer_limit](VM& vm, IteratorHelper& iterator) -> ThrowCompletionOr<Value> {
auto& iterated = iterator.underlying_iterator(); auto& iterated = iterator.underlying_iterator();
// a. Let remaining be integerLimit. // a. Let remaining be integerLimit.
// b. Repeat, // b. Repeat,
// i. If remaining is 0, then // i. If remaining = 0, then
if (iterator.counter() >= integer_limit) { if (iterator.counter() >= integer_limit) {
// 1. Return ? IteratorClose(iterated, NormalCompletion(undefined)). // 1. Return ? IteratorClose(iterated, NormalCompletion(undefined)).
return iterator.close_result(vm, normal_completion(js_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. // 1. Set remaining to remaining - 1.
iterator.increment_counter(); iterator.increment_counter();
// iii. Let value be ? IteratorStepValue(iterated). // iii. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return iterator.result(js_undefined()); return iterator.result(js_undefined());
@ -696,11 +780,11 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::take)
return iterator.result(*value); return iterator.result(*value);
}); });
// 9. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »). // 11. Let result be CreateIteratorFromClosure(closure, "Iterator Helper", %IteratorHelperPrototype%, « [[UnderlyingIterator]] »).
// 10. Set result.[[UnderlyingIterator]] to iterated. // 12. Set result.[[UnderlyingIterator]] to iterated.
auto result = TRY(IteratorHelper::create(realm, iterated, closure)); auto result = TRY(IteratorHelper::create(realm, iterated, closure));
// 11. Return result. // 13. Return result.
return result; return result;
} }
@ -724,7 +808,7 @@ JS_DEFINE_NATIVE_FUNCTION(IteratorPrototype::to_array)
// a. Let value be ? IteratorStepValue(iterated). // a. Let value be ? IteratorStepValue(iterated).
auto value = TRY(iterator_step_value(vm, 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()) if (!value.has_value())
return Array::create_from(realm, items); return Array::create_from(realm, items);

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number"); }).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", () => { test("called with invalid numbers", () => {
expect(() => { expect(() => {
Iterator.prototype.drop(NaN); Iterator.prototype.drop(NaN);

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "predicate is not a function"); }).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", () => { test("iterator's next method throws", () => {
function TestError() {} function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "predicate is not a function"); }).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", () => { test("iterator's next method throws", () => {
function TestError() {} function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "predicate is not a function"); }).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", () => { test("iterator's next method throws", () => {
function TestError() {} function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "mapper is not a function"); }).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", () => { test("iterator's next method throws", () => {
function TestError() {} function TestError() {}

View file

@ -2,7 +2,25 @@ describe("errors", () => {
test("called with non-callable object", () => { test("called with non-callable object", () => {
expect(() => { expect(() => {
Iterator.prototype.forEach(Symbol.hasInstance); 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", () => { test("iterator's next method throws", () => {

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "mapper is not a function"); }).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", () => { test("iterator's next method throws", () => {
function TestError() {} function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "reducer is not a function"); }).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", () => { test("iterator's next method throws", () => {
function TestError() {} function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "predicate is not a function"); }).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", () => { test("iterator's next method throws", () => {
function TestError() {} function TestError() {}

View file

@ -5,6 +5,24 @@ describe("errors", () => {
}).toThrowWithMessage(TypeError, "Cannot convert symbol to number"); }).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", () => { test("called with invalid numbers", () => {
expect(() => { expect(() => {
Iterator.prototype.take(NaN); Iterator.prototype.take(NaN);