@@ -137,41 +137,50 @@ void shouldHandleNestedArrayExpressionFromIssue40() throws Exception {
137137 }
138138
139139 // Regression tests for https://github.com/dashjoin/jsonata-java/pull/107
140- // Frame.lookup() was recursive — each JSONata recursive call adds one frame, so lookup()
141- // recurses once per frame. On small stacks this causes StackOverflowError before JSONata's
142- // own maxDepth guard can fire.
143-
144- @ Test
145- void shouldNotCrashWithDeepRecursionOnWindowsStack () throws InterruptedException {
146- // Windows JVM default thread stack ~256 KB; crashes at depth=999 before the fix.
147- RunContext runContext = runContextFactory .of ();
148- TransformValue task = TransformValue .builder ()
149- .from (Property .ofValue ("{}" ))
150- .expression (Property .ofValue (
151- "($f := function($n) { $n > 0 ? $f($n - 1) : 0 }; $f(999))"
152- ))
153- .maxDepth (Property .ofValue (1000 ))
154- .build ();
155-
140+ //
141+ // Frame.lookup() was recursive — each JSONata recursive call adds a scope frame, and lookup()
142+ // recurses once per frame when resolving a variable. With maxDepth=1000 (old default), a
143+ // 999-deep recursive expression causes lookup() to recurse 999 levels on top of JSONata's own
144+ // eval stack, overflowing the thread stack before maxDepth fires.
145+ //
146+ // Windows JVM default thread stack is ~256 KB; Linux is ~512 KB. Both are reproduced here
147+ // via Thread(stackSize) without requiring -Xss JVM flags in build config.
148+ private static final long SMALL_STACK_BYTES = 256 * 1024L ;
149+
150+ private void runOnSmallStack (TransformValue task , RunContext runContext ) throws InterruptedException {
156151 AtomicReference <Throwable > thrown = new AtomicReference <>();
157152 Thread t = new Thread (null , () -> {
158153 try {
159154 task .run (runContext );
160155 } catch (Throwable e ) {
161156 thrown .set (e );
162157 }
163- }, "windows -stack-sim" , 256 * 1024 );
158+ }, "small -stack-sim" , SMALL_STACK_BYTES );
164159 t .start ();
165160 t .join ();
166-
167161 assertThat (thrown .get ())
168- .as ("StackOverflowError on 256 KB stack (Windows default) — requires iterative Frame.lookup()" )
162+ .as ("StackOverflowError on %d KB stack — requires iterative Frame.lookup() fix" , SMALL_STACK_BYTES / 1024 )
169163 .isNull ();
170164 }
171165
172166 @ Test
173- void shouldNotCrashWithDeepRecursionOnLinuxStack () throws InterruptedException {
174- // Linux JVM default thread stack ~512 KB; needs higher depth to overflow than Windows.
167+ void shouldNotCrashWithDeepRecursionOnWindowsStack () throws Exception {
168+ // Simulates Windows JVM default (~256 KB): crashes at depth=999 with recursive lookup().
169+ RunContext runContext = runContextFactory .of ();
170+ TransformValue task = TransformValue .builder ()
171+ .from (Property .ofValue ("{}" ))
172+ .expression (Property .ofValue (
173+ "($f := function($n) { $n > 0 ? $f($n - 1) : 0 }; $f(999))"
174+ ))
175+ .maxDepth (Property .ofValue (1000 ))
176+ .build ();
177+
178+ runOnSmallStack (task , runContext );
179+ }
180+
181+ @ Test
182+ void shouldNotCrashWithDeepRecursionOnLinuxStack () throws Exception {
183+ // Simulates a Linux worker explicitly launched with -Xss256k (e.g. constrained container).
175184 RunContext runContext = runContextFactory .of ();
176185 TransformValue task = TransformValue .builder ()
177186 .from (Property .ofValue ("{}" ))
@@ -181,19 +190,6 @@ void shouldNotCrashWithDeepRecursionOnLinuxStack() throws InterruptedException {
181190 .maxDepth (Property .ofValue (2000 ))
182191 .build ();
183192
184- AtomicReference <Throwable > thrown = new AtomicReference <>();
185- Thread t = new Thread (null , () -> {
186- try {
187- task .run (runContext );
188- } catch (Throwable e ) {
189- thrown .set (e );
190- }
191- }, "linux-stack-sim" , 512 * 1024 );
192- t .start ();
193- t .join ();
194-
195- assertThat (thrown .get ())
196- .as ("StackOverflowError on 512 KB stack (Linux default) — requires iterative Frame.lookup()" )
197- .isNull ();
193+ runOnSmallStack (task , runContext );
198194 }
199195}
0 commit comments