4242
4343import com .oracle .truffle .api .dsl .Bind ;
4444import com .oracle .truffle .api .dsl .Cached ;
45- import com .oracle .truffle .api .dsl .Cached .Shared ;
4645import com .oracle .truffle .api .dsl .Fallback ;
4746import com .oracle .truffle .api .dsl .GenerateCached ;
4847import com .oracle .truffle .api .dsl .GenerateInline ;
48+ import com .oracle .truffle .api .dsl .ImportStatic ;
4949import com .oracle .truffle .api .dsl .Specialization ;
50+ import com .oracle .truffle .api .interop .InteropLibrary ;
51+ import com .oracle .truffle .api .interop .UnsupportedMessageException ;
52+ import com .oracle .truffle .api .library .CachedLibrary ;
5053import com .oracle .truffle .api .nodes .Node ;
5154import com .oracle .truffle .api .profiles .InlinedBranchProfile ;
55+ import com .oracle .truffle .api .profiles .InlinedConditionProfile ;
5256import com .oracle .truffle .api .profiles .InlinedIntValueProfile ;
5357import com .oracle .truffle .js .builtins .ArrayIteratorPrototypeBuiltinsFactory .ArrayIteratorNextNodeGen ;
58+ import com .oracle .truffle .js .nodes .JavaScriptBaseNode ;
5459import com .oracle .truffle .js .nodes .access .CreateIterResultObjectNode ;
5560import com .oracle .truffle .js .nodes .access .ReadElementNode ;
5661import com .oracle .truffle .js .nodes .array .JSGetLengthNode ;
5964import com .oracle .truffle .js .nodes .function .JSBuiltin ;
6065import com .oracle .truffle .js .nodes .function .JSBuiltinNode ;
6166import com .oracle .truffle .js .runtime .Errors ;
67+ import com .oracle .truffle .js .runtime .JSConfig ;
6268import com .oracle .truffle .js .runtime .JSContext ;
6369import com .oracle .truffle .js .runtime .JSRuntime ;
6470import com .oracle .truffle .js .runtime .builtins .BuiltinEnum ;
@@ -108,27 +114,39 @@ protected Object createNode(JSContext context, JSBuiltin builtin, boolean constr
108114
109115 public abstract static class ArrayIteratorNextNode extends JSBuiltinNode {
110116
117+ @ Child private CreateIterResultObjectNode createIterResultObjectNode ;
118+
111119 protected ArrayIteratorNextNode (JSContext context , JSBuiltin builtin ) {
112120 super (context , builtin );
121+ this .createIterResultObjectNode = CreateIterResultObjectNode .create (context );
122+ }
123+
124+ protected JSObject createIteratorResultObject (Object value , boolean done ) {
125+ return createIterResultObjectNode .execute (value , done );
113126 }
114127
115128 @ Specialization (guards = {"!isUndefined(array)" })
116129 JSObject doArrayIterator (JSArrayIteratorObject iterator ,
117130 @ Bind ("iterator.getIteratedObject()" ) Object array ,
118- @ Shared @ Cached ("create(getContext())" ) CreateIterResultObjectNode createIterResultObjectNode ,
119131 @ Cached ("create(getContext())" ) ReadElementNode readElementNode ,
120132 @ Cached ArrayIteratorGetLengthNode getLengthNode ,
121133 @ Cached (inline = true ) LongToIntOrDoubleNode toJSIndex ,
122- @ Cached InlinedIntValueProfile iterationKindProfile ) {
134+ @ Cached InlinedIntValueProfile iterationKindProfile ,
135+ @ Cached InlinedConditionProfile skipLengthCheckBranch ) {
123136 long index = iterator .getNextIndex ();
124- int itemKind = iterationKindProfile .profile (this , iterator .getIterationKind ());
125- long length = getLengthNode .execute (this , array , getJSContext ());
126-
127- if (index >= length ) {
128- iterator .setIteratedObject (Undefined .instance );
129- return createIterResultObjectNode .execute (Undefined .instance , true );
137+ assert index >= 0 ;
138+ if (skipLengthCheckBranch .profile (this , iterator .isSkipGetLength ())) {
139+ // Already checked in the caller, no need to get the length again.
140+ iterator .setSkipGetLength (false );
141+ } else {
142+ long length = getLengthNode .execute (this , array , getContext ());
143+ if (index >= length ) {
144+ iterator .setIteratedObject (Undefined .instance );
145+ return createIteratorResultObject (Undefined .instance , true );
146+ }
130147 }
131148
149+ int itemKind = iterationKindProfile .profile (this , iterator .getIterationKind ());
132150 iterator .setNextIndex (index + 1 );
133151 Object indexNumber = null ;
134152 if ((itemKind & JSRuntime .ITERATION_KIND_KEY ) != 0 ) {
@@ -146,13 +164,12 @@ JSObject doArrayIterator(JSArrayIteratorObject iterator,
146164 result = JSArray .createConstantObjectArray (getContext (), getRealm (), new Object []{indexNumber , elementValue });
147165 }
148166 }
149- return createIterResultObjectNode . execute (result , false );
167+ return createIteratorResultObject (result , false );
150168 }
151169
152170 @ Specialization (guards = {"isUndefined(iterator.getIteratedObject())" })
153- static JSObject doArrayIteratorDetached (@ SuppressWarnings ("unused" ) JSArrayIteratorObject iterator ,
154- @ Cached ("create(getContext())" ) @ Shared CreateIterResultObjectNode createIterResultObjectNode ) {
155- return createIterResultObjectNode .execute (Undefined .instance , true );
171+ final JSObject doArrayIteratorDetached (@ SuppressWarnings ("unused" ) JSArrayIteratorObject iterator ) {
172+ return createIteratorResultObject (Undefined .instance , true );
156173 }
157174
158175 @ SuppressWarnings ("unused" )
@@ -161,33 +178,87 @@ static JSObject doIncompatibleReceiver(Object iterator) {
161178 throw Errors .createTypeError ("not an Array Iterator" );
162179 }
163180 }
164- }
165181
166- @ GenerateInline
167- @ GenerateCached (false )
168- abstract class ArrayIteratorGetLengthNode extends Node {
182+ @ GenerateInline
183+ @ GenerateCached (false )
184+ public abstract static class ArrayIteratorGetLengthNode extends JavaScriptBaseNode {
169185
170- abstract long execute (Node node , Object array , JSContext context );
186+ public abstract long execute (Node node , Object array , JSContext context );
171187
172- @ Specialization
173- static long doArray (JSArrayObject array , @ SuppressWarnings ("unused" ) JSContext context ) {
174- return JSArray .arrayGetLength (array );
175- }
188+ @ Specialization
189+ static long doArray (JSArrayObject array , @ SuppressWarnings ("unused" ) JSContext context ) {
190+ return JSArray .arrayGetLength (array );
191+ }
176192
177- @ Specialization
178- static long doTypedArray (Node node , JSTypedArrayObject typedArray , JSContext context ,
179- @ Cached TypedArrayLengthNode typedArrayLengthNode ,
180- @ Cached InlinedBranchProfile errorBranch ) {
181- if (JSArrayBufferView .isOutOfBounds (typedArray , context )) {
182- errorBranch .enter (node );
183- throw Errors .createTypeError ("Cannot perform Array Iterator.prototype.next on a detached ArrayBuffer" );
193+ @ Specialization
194+ static long doTypedArray (Node node , JSTypedArrayObject typedArray , JSContext context ,
195+ @ Cached TypedArrayLengthNode typedArrayLengthNode ,
196+ @ Cached InlinedBranchProfile errorBranch ) {
197+ if (JSArrayBufferView .isOutOfBounds (typedArray , context )) {
198+ errorBranch .enter (node );
199+ throw Errors .createTypeError ("Cannot perform Array Iterator.prototype.next on a detached ArrayBuffer" );
200+ }
201+ return typedArrayLengthNode .execute (node , typedArray , context );
202+ }
203+
204+ @ Fallback
205+ static long doArrayLike (Object array , @ SuppressWarnings ("unused" ) JSContext context ,
206+ @ Cached (value = "create(context)" , inline = false ) JSGetLengthNode getLengthNode ) {
207+ return getLengthNode .executeLong (array );
184208 }
185- return typedArrayLengthNode .execute (node , typedArray , context );
186209 }
187210
188- @ Fallback
189- static long doArrayLike (Object array , @ SuppressWarnings ("unused" ) JSContext context ,
190- @ Cached (value = "create(context)" , inline = false ) JSGetLengthNode getLengthNode ) {
191- return getLengthNode .executeLong (array );
211+ /**
212+ * Like {@link ArrayIteratorGetLengthNode}, but designed to avoid observable side effects. Only
213+ * handles JS arrays, typed arrays, and foreign arrays (assumes getArraySize to well-behaved).
214+ * Returns {@value JSRuntime#INVALID_INTEGER_INDEX} for other object types or if getting the
215+ * length would throw an error.
216+ */
217+ @ ImportStatic (JSConfig .class )
218+ @ GenerateInline
219+ @ GenerateCached (false )
220+ public abstract static class ArrayIteratorGetLengthSafeNode extends JavaScriptBaseNode {
221+
222+ public abstract long execute (Node node , Object array );
223+
224+ @ Specialization
225+ static long doArray (JSArrayObject array ) {
226+ return JSArray .arrayGetLength (array );
227+ }
228+
229+ @ Specialization
230+ static long doTypedArray (Node node , JSTypedArrayObject typedArray ,
231+ @ Cached TypedArrayLengthNode typedArrayLengthNode ,
232+ @ Cached InlinedBranchProfile errorBranch ) {
233+ JSContext context = JSContext .get (node );
234+ if (JSArrayBufferView .isOutOfBounds (typedArray , context )) {
235+ errorBranch .enter (node );
236+ // Must call into the next method to ensure correct stack trace for the error.
237+ return JSRuntime .INVALID_INTEGER_INDEX ;
238+ }
239+ return typedArrayLengthNode .execute (node , typedArray , context );
240+ }
241+
242+ @ Specialization (guards = "isUndefined(array)" )
243+ static long doUndefined (@ SuppressWarnings ("unused" ) Object array ) {
244+ // Iterator is already done and detached.
245+ return 0L ;
246+ }
247+
248+ @ Specialization (guards = {"interop.hasArrayElements(array)" , "isForeignObject(array)" }, limit = "InteropLibraryLimit" )
249+ static long doForeignArray (Object array ,
250+ @ CachedLibrary ("array" ) InteropLibrary interop ) {
251+ try {
252+ return interop .getArraySize (array );
253+ } catch (UnsupportedMessageException e ) {
254+ return JSRuntime .INVALID_INTEGER_INDEX ;
255+ }
256+ }
257+
258+ @ Fallback
259+ static long doArrayLike (@ SuppressWarnings ("unused" ) Object array ) {
260+ // Must call into the next method to ensure correct stack trace for length getters.
261+ return JSRuntime .INVALID_INTEGER_INDEX ;
262+ }
192263 }
193264}
0 commit comments