@@ -749,12 +749,22 @@ private static Object js_from(Context cx, Scriptable scope, Scriptable thisObj,
749749 return result ;
750750 }
751751
752+ /**
753+ * ES2024 Array.fromAsync implementation.
754+ * Context-safe: Uses BaseFunction instead of LambdaFunction to avoid Context capture.
755+ *
756+ * @param cx Current context (never stored)
757+ * @param scope Current scope
758+ * @param thisObj Constructor this (Array constructor)
759+ * @param args Arguments [items, mapfn, thisArg]
760+ * @return Promise that resolves to the created array
761+ */
752762 private static Object js_fromAsync (
753763 Context cx , Scriptable scope , Scriptable thisObj , Object [] args ) {
754764
755- Object items = args .length >= 1 ? args [0 ] : Undefined .instance ;
756- Object mapfn = args .length >= 2 ? args [1 ] : Undefined .instance ;
757- Object thisArg = args .length >= 3 ? args [2 ] : Undefined .instance ;
765+ final Object items = args .length >= 1 ? args [0 ] : Undefined .instance ;
766+ final Object mapfn = args .length >= 2 ? args [1 ] : Undefined .instance ;
767+ final Object thisArg = args .length >= 3 ? args [2 ] : Undefined .instance ;
758768
759769 // Get Promise constructor
760770 Object promiseCtor = ScriptableObject .getProperty (scope , "Promise" );
@@ -763,62 +773,62 @@ private static Object js_fromAsync(
763773 }
764774 Function promiseFunc = (Function ) promiseCtor ;
765775
766- // Create promise executor
767- Callable executor =
768- new LambdaFunction (
769- scope ,
770- 2 ,
771- (Context lcx ,
772- Scriptable lscope ,
773- Scriptable lthisObj ,
774- Object [] executorArgs ) -> {
775- if (executorArgs .length < 2 ) {
776- return Undefined .instance ;
777- }
776+ // Create promise executor using BaseFunction to avoid Context capture
777+ Function executor = new BaseFunction () {
778+ @ Override
779+ public Object call (Context lcx , Scriptable lscope , Scriptable lthisObj , Object [] executorArgs ) {
780+ // lcx is FRESH Context from Promise - never stored
781+ if (executorArgs .length < 2 ) {
782+ return Undefined .instance ;
783+ }
778784
779- Function resolveFunc = (Function ) executorArgs [0 ];
780- Function rejectFunc = (Function ) executorArgs [1 ];
785+ Function resolveFunc = (Function ) executorArgs [0 ];
786+ Function rejectFunc = (Function ) executorArgs [1 ];
781787
782- try {
783- // Validate mapfn if provided
784- Function mapFunction = null ;
785- Scriptable mapThis = null ;
788+ try {
789+ // Validate mapfn if provided
790+ Function mapFunction = null ;
791+ Scriptable mapThis = null ;
786792
787- if (!Undefined .isUndefined (mapfn )) {
788- if (!(mapfn instanceof Function )) {
789- throw ScriptRuntime .typeErrorById ("msg.map.function.not" );
790- }
791- mapFunction = (Function ) mapfn ;
792- mapThis =
793- ScriptRuntime .getApplyOrCallThis (
794- lcx , lscope , thisArg , 1 , mapFunction );
795- }
793+ if (!Undefined .isUndefined (mapfn )) {
794+ if (!(mapfn instanceof Function )) {
795+ throw ScriptRuntime .typeErrorById ("msg.map.function.not" );
796+ }
797+ mapFunction = (Function ) mapfn ;
798+ mapThis = ScriptRuntime .getApplyOrCallThis (
799+ lcx , lscope , thisArg , 1 , mapFunction );
800+ }
796801
797- // Process items
798- processAsyncItems (
799- lcx ,
800- lscope ,
801- thisObj ,
802- items ,
803- mapFunction ,
804- mapThis ,
805- resolveFunc ,
806- rejectFunc );
807-
808- } catch (RhinoException re ) {
809- rejectFunc .call (
810- lcx ,
811- lscope ,
812- null ,
813- new Object [] {getErrorObject (lcx , lscope , re )});
814- }
802+ // Process items with fresh Context
803+ processAsyncItems (
804+ lcx ,
805+ lscope ,
806+ thisObj ,
807+ items ,
808+ mapFunction ,
809+ mapThis ,
810+ resolveFunc ,
811+ rejectFunc );
812+
813+ } catch (RhinoException re ) {
814+ rejectFunc .call (
815+ lcx ,
816+ lscope ,
817+ null ,
818+ new Object [] {getErrorObject (lcx , lscope , re )});
819+ }
815820
816- return Undefined .instance ;
817- });
821+ return Undefined .instance ;
822+ }
823+ };
818824
819825 return promiseFunc .construct (cx , scope , new Object [] {executor });
820826 }
821827
828+ /**
829+ * Process items for Array.fromAsync - Context-safe implementation.
830+ * Simplified version that only handles array-like objects (no iterator support yet).
831+ */
822832 private static void processAsyncItems (
823833 Context cx ,
824834 Scriptable scope ,
@@ -830,33 +840,23 @@ private static void processAsyncItems(
830840 Function reject ) {
831841
832842 try {
843+ // Convert to object
833844 Scriptable itemsObj = ScriptRuntime .toObject (scope , items );
834845
835- // Check for iterator
836- Object iteratorProp = ScriptableObject .getProperty (itemsObj , SymbolKey .ITERATOR );
837-
838- if (iteratorProp != Scriptable .NOT_FOUND && !Undefined .isUndefined (iteratorProp )) {
839- // Iterable path
840- Object iterator = ScriptRuntime .callIterator (itemsObj , cx , scope );
841- if (!Undefined .isUndefined (iterator )) {
842- Scriptable A = callConstructorOrCreateArray (cx , scope , thisObj , 0 , false );
843- IteratorLikeIterable iter = new IteratorLikeIterable (cx , scope , iterator );
844-
845- processAsyncIterable (cx , scope , iter , A , 0 , mapFn , mapThis , resolve , reject );
846- return ;
847- }
848- }
849-
850- // Array-like path
846+ // Get length and create result array
851847 long len = getLengthProperty (cx , itemsObj );
852- Scriptable A = callConstructorOrCreateArray (cx , scope , thisObj , len , true );
848+ Scriptable result = callConstructorOrCreateArray (cx , scope , thisObj , len , true );
853849
854850 if (len == 0 ) {
855- resolve .call (cx , scope , null , new Object [] {A });
851+ // Empty array - resolve immediately
852+ resolve .call (cx , scope , null , new Object [] {result });
856853 return ;
857854 }
858855
859- processAsyncArrayLike (cx , scope , itemsObj , A , 0 , len , mapFn , mapThis , resolve , reject );
856+ // Process array-like object with Context-safe async processor
857+ AsyncArrayLikeProcessor processor = new AsyncArrayLikeProcessor (
858+ result , itemsObj , len , mapFn , mapThis , resolve , reject );
859+ processor .processNext (cx , scope , 0 );
860860
861861 } catch (RhinoException re ) {
862862 reject .call (cx , scope , null , new Object [] {getErrorObject (cx , scope , re )});
@@ -1063,6 +1063,102 @@ private static Object getErrorObject(Context cx, Scriptable scope, RhinoExceptio
10631063 return re ;
10641064 }
10651065
1066+ /**
1067+ * Context-safe async array-like processor.
1068+ * Never stores Context - receives fresh Context on each call.
1069+ */
1070+ private static class AsyncArrayLikeProcessor {
1071+ private final Scriptable result ;
1072+ private final Scriptable items ;
1073+ private final long length ;
1074+ private final Function mapFn ;
1075+ private final Scriptable mapThis ;
1076+ private final Function resolve ;
1077+ private final Function reject ;
1078+
1079+ AsyncArrayLikeProcessor (
1080+ Scriptable result ,
1081+ Scriptable items ,
1082+ long length ,
1083+ Function mapFn ,
1084+ Scriptable mapThis ,
1085+ Function resolve ,
1086+ Function reject ) {
1087+ this .result = result ;
1088+ this .items = items ;
1089+ this .length = length ;
1090+ this .mapFn = mapFn ;
1091+ this .mapThis = mapThis ;
1092+ this .resolve = resolve ;
1093+ this .reject = reject ;
1094+ }
1095+
1096+ void processNext (Context cx , Scriptable scope , final long index ) {
1097+ if (index >= length ) {
1098+ setLengthProperty (cx , result , length );
1099+ resolve .call (cx , scope , null , new Object [] {result });
1100+ return ;
1101+ }
1102+
1103+ try {
1104+ Object value = getElem (cx , items , index );
1105+
1106+ // Apply mapping function if provided
1107+ if (mapFn != null ) {
1108+ value = mapFn .call (cx , scope , mapThis , new Object [] {value , Long .valueOf (index )});
1109+ }
1110+
1111+ // Check if value is a promise/thenable
1112+ if (isThenable (value )) {
1113+ // Handle promise resolution with Context-safe callbacks
1114+ Function onFulfilled = new BaseFunction () {
1115+ @ Override
1116+ public Object call (Context lcx , Scriptable lscope , Scriptable thisObj , Object [] args ) {
1117+ // lcx is FRESH Context when promise resolves
1118+ Object resolvedValue = args .length > 0 ? args [0 ] : Undefined .instance ;
1119+ ArrayLikeAbstractOperations .defineElem (lcx , result , index , resolvedValue );
1120+ processNext (lcx , lscope , index + 1 );
1121+ return Undefined .instance ;
1122+ }
1123+ };
1124+
1125+ Function onRejected = new BaseFunction () {
1126+ @ Override
1127+ public Object call (Context lcx , Scriptable lscope , Scriptable thisObj , Object [] args ) {
1128+ // lcx is FRESH Context when promise rejects
1129+ Object error = args .length > 0 ? args [0 ] : Undefined .instance ;
1130+ reject .call (lcx , lscope , null , new Object [] {error });
1131+ return Undefined .instance ;
1132+ }
1133+ };
1134+
1135+ // Attach callbacks to promise
1136+ Object thenMethod = ScriptableObject .getProperty ((Scriptable ) value , "then" );
1137+ if (thenMethod instanceof Function ) {
1138+ ((Function ) thenMethod ).call (cx , scope , (Scriptable ) value ,
1139+ new Object [] {onFulfilled , onRejected });
1140+ }
1141+ } else {
1142+ // Synchronous value - store and continue
1143+ ArrayLikeAbstractOperations .defineElem (cx , result , index , value );
1144+ processNext (cx , scope , index + 1 );
1145+ }
1146+
1147+ } catch (Exception e ) {
1148+ reject .call (cx , scope , null , new Object [] {e });
1149+ }
1150+ }
1151+ }
1152+
1153+ // Helper method to check if value is thenable
1154+ private static boolean isThenable (Object value ) {
1155+ if (!(value instanceof Scriptable )) {
1156+ return false ;
1157+ }
1158+ Object then = ScriptableObject .getProperty ((Scriptable ) value , "then" );
1159+ return then instanceof Function ;
1160+ }
1161+
10661162 private static Object js_of (Context cx , Scriptable scope , Scriptable thisObj , Object [] args ) {
10671163 final Scriptable result =
10681164 callConstructorOrCreateArray (cx , scope , thisObj , args .length , true );
0 commit comments