From 93f4d41d718658268cbb7cf0e1382111ea78c3af Mon Sep 17 00:00:00 2001 From: Javier Garcia <93437997+IamYipi@users.noreply.github.com> Date: Wed, 30 Sep 2026 05:18:16 +0200 Subject: [PATCH] fix(engine): reject Iterator take/drop limits above 2^53 - 1 ECMA-262 now has Iterator.prototype.take and Iterator.prototype.drop throw a RangeError, after closing the underlying iterator, when the limit is finite and greater than 2^53 - 1 (tc39/ecma262#3776). Add that step to both methods and renumber the step comments that follow it. --- .../builtins/iterable/iterator_prototype.rs | 54 +++++++++---- core/engine/src/builtins/iterable/tests.rs | 75 +++++++++++++++++++ 2 files changed, 116 insertions(+), 13 deletions(-) diff --git a/core/engine/src/builtins/iterable/iterator_prototype.rs b/core/engine/src/builtins/iterable/iterator_prototype.rs index 21558d93012..2ce9ac2546a 100644 --- a/core/engine/src/builtins/iterable/iterator_prototype.rs +++ b/core/engine/src/builtins/iterable/iterator_prototype.rs @@ -1,7 +1,7 @@ use crate::{ Context, JsArgs, JsObject, JsResult, JsSymbol, JsValue, builtins::{ - IntrinsicObject, + IntrinsicObject, Number, array::Array, builder::BuiltInBuilder, iterable::{ @@ -304,7 +304,9 @@ impl Iterator { let limit = args.get_or_undefined(0); let num_limit = if_abrupt_close_iterator!(limit.to_number(context), iterated, context); - // 6. If numLimit is NaN, throw a RangeError exception. + // 6. If numLimit is NaN, then + // a. Let error be ThrowCompletion(a newly created RangeError object). + // b. Return ? IteratorClose(iterated, error). if num_limit.is_nan() { return iterated.close( Err(js_error!( @@ -314,10 +316,22 @@ impl Iterator { ); } - // 7. Let integerLimit be ! ToIntegerOrInfinity(numLimit). + // 7. If numLimit is finite and numLimit > 𝔽(2**53 - 1), then + // a. Let error be ThrowCompletion(a newly created RangeError object). + // b. Return ? IteratorClose(iterated, error). + if num_limit.is_finite() && num_limit > Number::MAX_SAFE_INTEGER { + return iterated.close( + Err(js_error!( + RangeError: "Iterator.prototype.take: limit cannot be greater than 2^53 - 1" + )), + context, + ); + } + + // 8. Let integerLimit be ! ToIntegerOrInfinity(numLimit). let integer_limit = IntegerOrInfinity::from(num_limit); - // 8. If integerLimit < 0, then + // 9. If integerLimit < 0, then let integer_limit = match integer_limit { IntegerOrInfinity::Integer(n) if n >= 0 => Some(n as u64), IntegerOrInfinity::PositiveInfinity => None, @@ -333,14 +347,14 @@ impl Iterator { } }; - // 9. Set iterated to ? GetIteratorDirect(O). + // 10. Set iterated to ? GetIteratorDirect(O). let iterated = get_iterator_direct(iterated.iterator(), context)?; - // 10-12 are deferred to `IteratorHelper::create` and `Take::new`. + // 11-13 are deferred to `IteratorHelper::create` and `Take::new`. let result = IteratorHelper::create(iterator_helper::Take::new(iterated, integer_limit), context); - // 13. Return result. + // 14. Return result. Ok(result.into()) } @@ -365,7 +379,9 @@ impl Iterator { let limit = args.get_or_undefined(0); let num_limit = if_abrupt_close_iterator!(limit.to_number(context), iterated, context); - // 6. If numLimit is NaN, throw a RangeError exception. + // 6. If numLimit is NaN, then + // a. Let error be ThrowCompletion(a newly created RangeError object). + // b. Return ? IteratorClose(iterated, error). if num_limit.is_nan() { return iterated.close( Err(js_error!( @@ -375,10 +391,22 @@ impl Iterator { ); } - // 7. Let integerLimit be ! ToIntegerOrInfinity(numLimit). + // 7. If numLimit is finite and numLimit > 𝔽(2**53 - 1), then + // a. Let error be ThrowCompletion(a newly created RangeError object). + // b. Return ? IteratorClose(iterated, error). + if num_limit.is_finite() && num_limit > Number::MAX_SAFE_INTEGER { + return iterated.close( + Err(js_error!( + RangeError: "Iterator.prototype.drop: limit cannot be greater than 2^53 - 1" + )), + context, + ); + } + + // 8. Let integerLimit be ! ToIntegerOrInfinity(numLimit). let integer_limit = IntegerOrInfinity::from(num_limit); - // 8. If integerLimit < 0, then + // 9. If integerLimit < 0, then let integer_limit = match integer_limit { IntegerOrInfinity::Integer(n) if n >= 0 => Some(n as u64), IntegerOrInfinity::PositiveInfinity => None, @@ -393,14 +421,14 @@ impl Iterator { ); } }; - // 9. Set iterated to ? GetIteratorDirect(O). + // 10. Set iterated to ? GetIteratorDirect(O). let iterated = get_iterator_direct(iterated.iterator(), context)?; - // 10-12 are deferred to `IteratorHelper::create` and `Drop::new`. + // 11-13 are deferred to `IteratorHelper::create` and `Drop::new`. let result = IteratorHelper::create(iterator_helper::Drop::new(iterated, integer_limit), context); - // 13. Return result. + // 14. Return result. Ok(result.into()) } diff --git a/core/engine/src/builtins/iterable/tests.rs b/core/engine/src/builtins/iterable/tests.rs index 07f75053b85..ce8b3c86352 100644 --- a/core/engine/src/builtins/iterable/tests.rs +++ b/core/engine/src/builtins/iterable/tests.rs @@ -148,6 +148,45 @@ fn iterator_take_nan_throws() { )]); } +#[test] +fn iterator_take_limit_above_max_safe_integer_throws() { + run_test_actions([TestAction::assert_native_error( + "Iterator.from([1]).take(Number.MAX_SAFE_INTEGER + 1)", + JsNativeErrorKind::Range, + "Iterator.prototype.take: limit cannot be greater than 2^53 - 1", + )]); +} + +#[test] +fn iterator_take_limit_max_safe_integer_or_infinity() { + run_test_actions([ + TestAction::assert_eq( + "Iterator.from([1,2,3]).take(Number.MAX_SAFE_INTEGER).toArray().join(',')", + js_str!("1,2,3"), + ), + TestAction::assert_eq( + "Iterator.from([1,2,3]).take(Infinity).toArray().join(',')", + js_str!("1,2,3"), + ), + ]); +} + +#[test] +fn iterator_take_limit_above_max_safe_integer_closes_underlying() { + run_test_actions([ + TestAction::run( + "let closed = false; + let it = { + __proto__: Iterator.prototype, + get next() { throw new Error('next should not be read'); }, + return() { closed = true; return {}; } + }; + try { it.take(Number.MAX_SAFE_INTEGER + 1); } catch (e) { if (!(e instanceof RangeError)) throw e; }", + ), + TestAction::assert("closed"), + ]); +} + #[test] fn iterator_take_more_than_length() { run_test_actions([TestAction::assert_eq( @@ -174,6 +213,42 @@ fn iterator_drop_more_than_length() { )]); } +#[test] +fn iterator_drop_limit_above_max_safe_integer_throws() { + run_test_actions([TestAction::assert_native_error( + "Iterator.from([1]).drop(Number.MAX_SAFE_INTEGER + 1)", + JsNativeErrorKind::Range, + "Iterator.prototype.drop: limit cannot be greater than 2^53 - 1", + )]); +} + +#[test] +fn iterator_drop_limit_max_safe_integer_or_infinity() { + run_test_actions([ + TestAction::assert_eq( + "Iterator.from([1,2,3]).drop(Number.MAX_SAFE_INTEGER).toArray().length", + 0, + ), + TestAction::assert_eq("Iterator.from([1,2,3]).drop(Infinity).toArray().length", 0), + ]); +} + +#[test] +fn iterator_drop_limit_above_max_safe_integer_closes_underlying() { + run_test_actions([ + TestAction::run( + "let closed = false; + let it = { + __proto__: Iterator.prototype, + get next() { throw new Error('next should not be read'); }, + return() { closed = true; return {}; } + }; + try { it.drop(Number.MAX_SAFE_INTEGER + 1); } catch (e) { if (!(e instanceof RangeError)) throw e; }", + ), + TestAction::assert("closed"), + ]); +} + #[test] fn iterator_drop_zero() { run_test_actions([TestAction::assert_eq(