Skip to content

Commit d095fbf

Browse files
committed
[JSC] Close Iterator Helpers underlying iterator when arguments are invalid
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):
1 parent 94ac0af commit d095fbf

File tree

2 files changed

+315
-20
lines changed

2 files changed

+315
-20
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
function shouldThrow(errorType, func) {
2+
let error;
3+
try {
4+
func();
5+
} catch (e) {
6+
error = e;
7+
}
8+
if (!(error instanceof errorType)) {
9+
print(error.message);
10+
throw new Error(`Expected ${errorType.name}! got ${error.name}`);
11+
}
12+
}
13+
14+
function shouldBe(a, b) {
15+
if (a !== b)
16+
throw new Error(`Expected ${b} but got ${a}`);
17+
}
18+
19+
{
20+
// Iterator.prototype.map
21+
let closed = false;
22+
let closable = {
23+
__proto__: Iterator.prototype,
24+
get next() {
25+
throw new Error('next should not be read');
26+
},
27+
return() {
28+
closed = true;
29+
return {};
30+
},
31+
};
32+
shouldThrow(TypeError, function() {
33+
closable.map();
34+
});
35+
shouldBe(closed, true);
36+
37+
closed = false;
38+
shouldThrow(TypeError, function() {
39+
closable.map({});
40+
});
41+
shouldBe(closed, true);
42+
}
43+
44+
{
45+
// Iterator.prototype.filter
46+
let closed = false;
47+
let closable = {
48+
__proto__: Iterator.prototype,
49+
get next() {
50+
throw new Error('next should not be read');
51+
},
52+
return() {
53+
closed = true;
54+
return {};
55+
},
56+
};
57+
shouldThrow(TypeError, function() {
58+
closable.filter();
59+
});
60+
shouldBe(closed, true);
61+
62+
closed = false;
63+
shouldThrow(TypeError, function() {
64+
closable.filter({});
65+
});
66+
shouldBe(closed, true);
67+
}
68+
69+
{
70+
// Iterator.prototype.take
71+
let closed = false;
72+
let closable = {
73+
__proto__: Iterator.prototype,
74+
get next() {
75+
throw new Error('next should not be read');
76+
},
77+
return() {
78+
closed = true;
79+
return {};
80+
},
81+
};
82+
83+
shouldThrow(RangeError, function() {
84+
closable.take();
85+
});
86+
shouldBe(closed, true);
87+
88+
closed = false;
89+
shouldThrow(RangeError, function() {
90+
closable.take(NaN);
91+
});
92+
shouldBe(closed, true);
93+
94+
closed = false;
95+
shouldThrow(RangeError, function() {
96+
closable.take(-1);
97+
});
98+
shouldBe(closed, true);
99+
100+
closed = false;
101+
function OurError() {}
102+
shouldThrow(OurError, function() {
103+
closable.take({ get valueOf() { throw new OurError(); }});
104+
});
105+
shouldBe(closed, true);
106+
}
107+
108+
{
109+
// Iterator.prototype.drop
110+
let closed = false;
111+
let closable = {
112+
__proto__: Iterator.prototype,
113+
get next() {
114+
throw new Error('next should not be read');
115+
},
116+
return() {
117+
closed = true;
118+
return {};
119+
},
120+
};
121+
122+
shouldThrow(RangeError, function() {
123+
closable.drop();
124+
});
125+
shouldBe(closed, true);
126+
127+
closed = false;
128+
shouldThrow(RangeError, function() {
129+
closable.drop(NaN);
130+
});
131+
shouldBe(closed, true);
132+
133+
closed = false;
134+
shouldThrow(RangeError, function() {
135+
closable.drop(-1);
136+
});
137+
shouldBe(closed, true);
138+
139+
closed = false;
140+
function OurError() {}
141+
shouldThrow(OurError, function() {
142+
closable.drop({ get valueOf() { throw new OurError(); }});
143+
});
144+
shouldBe(closed, true);
145+
}
146+
147+
{
148+
// Iterator.prototype.flatMap
149+
let closed = false;
150+
let closable = {
151+
__proto__: Iterator.prototype,
152+
get next() {
153+
throw new Error('next should not be read');
154+
},
155+
return() {
156+
closed = true;
157+
return {};
158+
},
159+
};
160+
shouldThrow(TypeError, function() {
161+
closable.flatMap();
162+
});
163+
shouldBe(closed, true);
164+
165+
closed = false;
166+
shouldThrow(TypeError, function() {
167+
closable.flatMap({});
168+
});
169+
shouldBe(closed, true);
170+
}
171+
172+
{
173+
// Iterator.prototype.some
174+
let closed = false;
175+
let closable = {
176+
__proto__: Iterator.prototype,
177+
get next() {
178+
throw new Error('next should not be read');
179+
},
180+
return() {
181+
closed = true;
182+
return {};
183+
},
184+
};
185+
shouldThrow(TypeError, function() {
186+
closable.some();
187+
});
188+
shouldBe(closed, true);
189+
190+
closed = false;
191+
shouldThrow(TypeError, function() {
192+
closable.some({});
193+
});
194+
shouldBe(closed, true);
195+
}
196+
197+
{
198+
// Iterator.prototype.every
199+
let closed = false;
200+
let closable = {
201+
__proto__: Iterator.prototype,
202+
get next() {
203+
throw new Error('next should not be read');
204+
},
205+
return() {
206+
closed = true;
207+
return {};
208+
},
209+
};
210+
shouldThrow(TypeError, function() {
211+
closable.every();
212+
});
213+
shouldBe(closed, true);
214+
215+
closed = false;
216+
shouldThrow(TypeError, function() {
217+
closable.every({});
218+
});
219+
shouldBe(closed, true);
220+
}
221+
222+
{
223+
// Iterator.prototype.find
224+
let closed = false;
225+
let closable = {
226+
__proto__: Iterator.prototype,
227+
get next() {
228+
throw new Error('next should not be read');
229+
},
230+
return() {
231+
closed = true;
232+
return {};
233+
},
234+
};
235+
shouldThrow(TypeError, function() {
236+
closable.find();
237+
});
238+
shouldBe(closed, true);
239+
240+
closed = false;
241+
shouldThrow(TypeError, function() {
242+
closable.find({});
243+
});
244+
shouldBe(closed, true);
245+
}
246+
247+
{
248+
// Iterator.prototype.reduce
249+
let closed = false;
250+
let closable = {
251+
__proto__: Iterator.prototype,
252+
get next() {
253+
throw new Error('next should not be read');
254+
},
255+
return() {
256+
closed = true;
257+
return {};
258+
},
259+
};
260+
shouldThrow(TypeError, function() {
261+
closable.reduce();
262+
});
263+
shouldBe(closed, true);
264+
265+
closed = false;
266+
shouldThrow(TypeError, function() {
267+
closable.reduce({});
268+
});
269+
shouldBe(closed, true);
270+
}
271+

0 commit comments

Comments
 (0)