Commit 5e6b90e2cc0 for php.net

commit 5e6b90e2cc018e032ed83dc23dbf4b1534401bba
Author: Ilia Alshanetsky <ilia@ilia.ws>
Date:   Sun Apr 12 12:43:19 2026 -0400

    Fix GH-21730: Mt19937::__debugInfo() leaks state HashTable when the serialize callback fails (#21733)

    Mt19937::__debugInfo() allocates a temporary HashTable with
    array_init(&t), calls the engine's serialize callback, and then
    inserts t into the return value. If the callback returns false, the
    method throws and hits RETURN_THROWS() before inserting t, so the
    HashTable leaks. PcgOneseq128XslRr64 and Xoshiro256StarStar alias
    the same method and share the leak.

    Niels Dossche fixed the same pattern in __serialize() via GH-20383
    (720e0069829). That cleanup didn't touch __debugInfo(). Apply the
    same reordering here: insert t into return_value first, then let
    the callback populate it. RETURN_THROWS() then unwinds the return
    value cleanly.

    The path is latent in stock PHP because the three built-in serialize
    callbacks (mt19937, pcg, xoshiro) all return true, so no user code
    reaches the leak today. I'm fixing it for symmetry with GH-20383 and
    to keep the pattern from regressing if a future engine grows a
    failing serialize path.

    Closes GH-21730

diff --git a/ext/random/engine_mt19937.c b/ext/random/engine_mt19937.c
index 64009990910..b76e89ac1d6 100644
--- a/ext/random/engine_mt19937.c
+++ b/ext/random/engine_mt19937.c
@@ -392,11 +392,11 @@ PHP_METHOD(Random_Engine_Mt19937, __debugInfo)

 	if (engine->engine.algo->serialize) {
 		array_init(&t);
+		zend_hash_str_add(Z_ARR_P(return_value), "__states", strlen("__states"), &t);
 		if (!engine->engine.algo->serialize(engine->engine.state, Z_ARRVAL(t))) {
 			zend_throw_exception(NULL, "Engine serialize failed", 0);
 			RETURN_THROWS();
 		}
-		zend_hash_str_add(Z_ARR_P(return_value), "__states", strlen("__states"), &t);
 	}
 }
 /* }}} */