@@ -1298,27 +1298,27 @@ unsafe extern "C" fn ast_get_gc(
12981298 std:: ptr:: null_mut ( )
12991299}
13001300
1301- /// Patch `WP_MySQL_Native_Ast`'s class entry to use a `get_gc` handler
1302- /// that walks the Rust-side `node_cache`. Called once during MINIT
1303- /// after ext-php-rs has registered the class.
1304- fn install_ast_gc_handler ( ) -> PhpResult < ( ) > {
1305- let class = ClassEntry :: try_find ( "WP_MySQL_Native_Ast" )
1306- . ok_or_else ( || php_error ( "WP_MySQL_Native_Ast class not registered" ) ) ?;
1307- unsafe {
1308- let class_mut = ( class as * const zend_class_entry ) as * mut zend_class_entry ;
1309- let default = ( * class_mut) . default_object_handlers ;
1310- if default. is_null ( ) {
1311- return Err ( php_error (
1312- "WP_MySQL_Native_Ast has no default object handlers" ,
1313- ) ) ;
1301+ /// Patch a freshly-constructed `WP_MySQL_Native_Ast` instance so its
1302+ /// `zend_object.handlers->get_gc` walks the Rust-side cache.
1303+ ///
1304+ /// PHP 8.3 introduced `zend_class_entry.default_object_handlers` which
1305+ /// would let us patch once per class on MINIT, but PHP 8.2 has no such
1306+ /// field; the only reliable place to install a custom `get_gc` across
1307+ /// both is per-instance, right after the object is allocated. The
1308+ /// patched handlers struct itself is computed lazily on first use and
1309+ /// shared across all subsequent ASTs.
1310+ unsafe fn install_gc_handler_for ( obj : * mut zend_object ) {
1311+ if WP_MYSQL_NATIVE_AST_HANDLERS . is_none ( ) {
1312+ let original = ( * obj) . handlers ;
1313+ if original. is_null ( ) {
1314+ return ;
13141315 }
1315- let mut patched = std:: ptr:: read ( default ) ;
1316+ let mut patched = std:: ptr:: read ( original ) ;
13161317 patched. get_gc = Some ( ast_get_gc) ;
13171318 WP_MYSQL_NATIVE_AST_HANDLERS = Some ( patched) ;
1318- let stored = WP_MYSQL_NATIVE_AST_HANDLERS . as_ref ( ) . unwrap ( ) ;
1319- ( * class_mut) . default_object_handlers = stored as * const _ ;
13201319 }
1321- Ok ( ( ) )
1320+ let stored = WP_MYSQL_NATIVE_AST_HANDLERS . as_ref ( ) . unwrap ( ) ;
1321+ ( * obj) . handlers = stored as * const _ ;
13221322}
13231323
13241324/// Build a Zval that references an existing PHP object with refcount bumped.
@@ -1935,6 +1935,14 @@ impl WpMySqlNativeParser {
19351935 }
19361936 . into_zval ( false )
19371937 . map_err ( php_error) ?;
1938+ // Install our custom `get_gc` so PHP's cycle collector can
1939+ // see the cached wrappers held by the Rust-side HashMap.
1940+ unsafe {
1941+ let obj_ref = native_ast_zval
1942+ . object ( )
1943+ . ok_or_else ( || php_error ( "Native AST zval is not an object" ) ) ?;
1944+ install_gc_handler_for ( ( obj_ref as * const ZendObject ) as * mut zend_object ) ;
1945+ }
19381946 let native_ast = native_ast ( & native_ast_zval) ?;
19391947 native_ast. arena . create_php_ast ( & native_ast_zval)
19401948 } )
@@ -2219,13 +2227,5 @@ pub fn get_module(module: ModuleBuilder) -> ModuleBuilder {
22192227 ) )
22202228 . function ( wrap_function ! ( wp_sqlite_mysql_native_ast_get_start) )
22212229 . function ( wrap_function ! ( wp_sqlite_mysql_native_ast_get_length) )
2222- . startup_function ( module_startup)
22232230 . info_function ( php_module_info)
22242231}
2225-
2226- extern "C" fn module_startup ( _type : i32 , _module_number : i32 ) -> i32 {
2227- if install_ast_gc_handler ( ) . is_err ( ) {
2228- return 0 ;
2229- }
2230- 1
2231- }
0 commit comments