From 81d0d0fbc2efe08eabce39575200073fd54f9981 Mon Sep 17 00:00:00 2001 From: Marko Lahma Date: Tue, 7 Jul 2026 00:00:15 +0300 Subject: [PATCH] Extend prepared-script engine-retention test to newer env cache shapes Analysis of #2587 confirmed that everything a prepared script retains after engine disposal is engine-independent prepare-time metadata by design (constant-folded primitives, FDI State, block declaration caches, binding names) and that no Engine/Realm/Environment can be reached from the shared AST. The regression test guarding that invariant predates two environment cache shapes added since: the for-of/for-in per-iteration environment cache on the JintForInForOfStatement handler (#2586) and the Function-constructor definition-level environment parked on the realm-cached dynamic State (#2579). Cover both so a future refactor moving either onto shared AST state fails the test. Co-Authored-By: Claude Fable 5 --- Jint.Tests/Runtime/GarbageCollectionTests.cs | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/Jint.Tests/Runtime/GarbageCollectionTests.cs b/Jint.Tests/Runtime/GarbageCollectionTests.cs index 573dfce63..126b6498a 100644 --- a/Jint.Tests/Runtime/GarbageCollectionTests.cs +++ b/Jint.Tests/Runtime/GarbageCollectionTests.cs @@ -123,15 +123,22 @@ public void SharedPreparedScriptDoesNotRetainEngines() // per engine) rather than on state shared through the AST, so once an engine is dropped its cached // environments — and the engine/realm they reference — become collectable even while the prepared // script stays cached. The script covers every cache shape: an ordinary function (single-slot env - // cache), a direct-recursive one (bounded RecursiveEnvPool), a let/const block (block env cache) - // and a for-let loop (loop env cache). + // cache), a direct-recursive one (bounded RecursiveEnvPool), a let/const block (block env cache), + // a for-let loop (loop env cache), for-of/for-in with let head (per-iteration env cache on the + // JintForInForOfStatement handler) and a Function-constructor function (definition-level env parked + // on the realm-cached dynamic State, which must die with the realm). var prepared = Engine.PrepareScript(""" function f(x) { var y = x + 1; return y; } function fib(n) { return n < 2 ? n : fib(n - 1) + fib(n - 2); } function b(x) { { let y = x + 1; const z = y * 2; f(y + z); } } function l(x) { var sum = 0; for (let i = 0; i < 3; i++) { sum += i; } return sum; } + function fo(arr) { var sum = 0; for (let v of arr) { sum += v; } return sum; } + function fi(obj) { var keys = ''; for (let k in obj) { keys += k; } return keys; } + var dyn = new Function('a', 'return a + 1'); f(1); f(2); fib(8); b(1); b(2); l(1); l(2); + fo([1, 2, 3]); fo([4, 5]); fi({ a: 1, b: 2 }); fi({ c: 3 }); + dyn(1); dyn(2); """); const int count = 20;