@@ -10,7 +10,7 @@ use ext_php_rs::boxed::ZBox;
1010
1111use ext_php_rs:: convert:: { FromZval , IntoZval , IntoZvalDyn } ;
1212use ext_php_rs:: exception:: { PhpException , PhpResult } ;
13- use ext_php_rs:: ffi:: { zend_class_entry, zend_object, zend_object_handlers , zval, HashTable } ;
13+ use ext_php_rs:: ffi:: { zend_class_entry, zend_object, zval} ;
1414use ext_php_rs:: flags:: DataType ;
1515use ext_php_rs:: prelude:: * ;
1616use ext_php_rs:: types:: { ArrayKey , ZendCallable , ZendHashTable , ZendObject , Zval } ;
@@ -39,16 +39,6 @@ extern "C" {
3939
4040}
4141
42- /// `Z_TYPE_INFO` for an OBJECT zval that carries a refcount.
43- ///
44- /// PHP packs `(type | flags << 8)` into a single 32-bit word. For
45- /// `IS_OBJECT` (8) plus `IS_TYPE_REFCOUNTED` (1) and `IS_TYPE_COLLECTABLE`
46- /// (2) shifted into the flags byte, the value is `0x10 | 0x208 == 0x208`
47- /// — but the canonical value PHP uses is `0x0a08`. We only ever read this
48- /// from PHP's own headers indirectly, so the constant lives here so the
49- /// gc-trace path doesn't depend on ext-php-rs exposing it.
50- const PHP_IS_OBJECT_EX : u32 = 8 | ( ( ( 1 << 0 ) | ( 1 << 1 ) ) << 8 ) ;
51-
5242#[ derive( Clone ) ]
5343struct BinaryString ( Vec < u8 > ) ;
5444
@@ -1068,13 +1058,6 @@ pub struct WpMySqlNativeAst {
10681058 /// bumped, skipping the allocation and four `zend_update_property`
10691059 /// calls of the construction path.
10701060 node_cache : RefCell < HashMap < usize , ZBox < ZendObject > > > ,
1071- /// Scratch buffer the custom `get_gc` handler refills on each
1072- /// invocation with zvals pointing at every cached wrapper. We can't
1073- /// use PHP's `zend_get_gc_buffer_*` API because PHP 8.2 builds on
1074- /// Ubuntu's `shivammathur/setup-php` don't export those symbols, so
1075- /// we own the buffer ourselves and hand its raw pointer to PHP via
1076- /// the `(table, n)` out-params of the gc handler.
1077- gc_trace : RefCell < Vec < zval > > ,
10781061}
10791062
10801063impl NativeAstArena {
@@ -1240,95 +1223,6 @@ fn native_ast(native_ast: &Zval) -> PhpResult<&WpMySqlNativeAst> {
12401223 . ok_or_else ( || php_error ( "Missing native AST handle" ) )
12411224}
12421225
1243- /// Tracks whether the GC handler has been installed on `WP_MySQL_Native_Ast`'s
1244- /// shared handlers struct. We patch in place rather than swapping the
1245- /// handlers pointer because ext-php-rs's `FromZval` for registered
1246- /// classes verifies identity via the handlers pointer; replacing it
1247- /// would make every native_ast lookup fail.
1248- static mut WP_MYSQL_NATIVE_AST_HANDLERS_PATCHED : bool = false ;
1249-
1250- /// Custom `get_gc` handler that exposes `WpMySqlNativeAst.node_cache` to
1251- /// PHP's cycle collector.
1252- ///
1253- /// The cache forms `AST -> wrappers -> $native_ast property -> AST`,
1254- /// which PHP can't traverse on its own because the `node_cache` lives in
1255- /// a Rust `HashMap<usize, ZBox<ZendObject>>` opaque to the engine. By
1256- /// reporting each cached wrapper here, PHP's mark-and-sweep can complete
1257- /// the cycle and reclaim both the AST and its wrappers.
1258- ///
1259- /// # Safety
1260- ///
1261- /// Called by PHP's GC with a non-null `object` pointer guaranteed to
1262- /// belong to the registered class. `table` and `n` are non-null
1263- /// out-params owned by the caller. The zvals we push into the gc buffer
1264- /// are read-only handles for traversal — they don't transfer ownership
1265- /// or change refcounts, so we deliberately do *not* call `set_object`
1266- /// (which addrefs); we set the union directly.
1267- unsafe extern "C" fn ast_get_gc (
1268- object : * mut zend_object ,
1269- table : * mut * mut zval ,
1270- n : * mut std:: os:: raw:: c_int ,
1271- ) -> * mut HashTable {
1272- * table = std:: ptr:: null_mut ( ) ;
1273- * n = 0 ;
1274-
1275- let Some ( ast) = ext_php_rs:: types:: ZendClassObject :: < WpMySqlNativeAst > :: from_zend_obj ( & * object)
1276- . and_then ( |z| z. obj . as_ref ( ) )
1277- else {
1278- return std:: ptr:: null_mut ( ) ;
1279- } ;
1280-
1281- let Ok ( cache) = ast. node_cache . try_borrow ( ) else {
1282- return std:: ptr:: null_mut ( ) ;
1283- } ;
1284- let Ok ( mut trace) = ast. gc_trace . try_borrow_mut ( ) else {
1285- return std:: ptr:: null_mut ( ) ;
1286- } ;
1287-
1288- trace. clear ( ) ;
1289- trace. reserve ( cache. len ( ) ) ;
1290- for boxed in cache. values ( ) {
1291- // Build a zval pointing at the cached wrapper without bumping
1292- // refcount — the GC scan just enumerates outgoing references;
1293- // mutating refcounts here would un-balance the collector's
1294- // accounting. Using `Zval::new` + manual field writes (rather
1295- // than `Zval::set_object` which would addref) keeps the trace
1296- // entries refcount-neutral.
1297- let mut zv: zval = std:: mem:: zeroed ( ) ;
1298- zv. value . obj = ( & * * boxed) as * const ZendObject as * mut _ ;
1299- zv. u1 . type_info = PHP_IS_OBJECT_EX ;
1300- trace. push ( zv) ;
1301- }
1302-
1303- * table = trace. as_mut_ptr ( ) ;
1304- * n = trace. len ( ) as std:: os:: raw:: c_int ;
1305- std:: ptr:: null_mut ( )
1306- }
1307-
1308- /// Patch a freshly-constructed `WP_MySQL_Native_Ast` instance so its
1309- /// shared `zend_object_handlers->get_gc` walks the Rust-side cache.
1310- ///
1311- /// PHP 8.3 introduced `zend_class_entry.default_object_handlers` which
1312- /// would let us configure handlers once per class on MINIT, but PHP 8.2
1313- /// has no such field. Within ext-php-rs the per-class handlers struct
1314- /// is heap-allocated (via Box::leak), shared across all instances of
1315- /// the class, and identified by its pointer. Swapping the pointer to a
1316- /// copy would break ext-php-rs's `FromZval` identity check, so we
1317- /// instead set `get_gc` in place on the original struct. We only need
1318- /// to do it once per process; subsequent calls are cheap.
1319- unsafe fn install_gc_handler_for ( obj : * mut zend_object ) {
1320- if WP_MYSQL_NATIVE_AST_HANDLERS_PATCHED {
1321- return ;
1322- }
1323- let handlers = ( * obj) . handlers ;
1324- if handlers. is_null ( ) {
1325- return ;
1326- }
1327- let handlers_mut = handlers as * mut zend_object_handlers ;
1328- ( * handlers_mut) . get_gc = Some ( ast_get_gc) ;
1329- WP_MYSQL_NATIVE_AST_HANDLERS_PATCHED = true ;
1330- }
1331-
13321226/// Build a Zval that references an existing PHP object with refcount bumped.
13331227///
13341228/// Used on cache hits to hand a stored `ZBox<ZendObject>` back to PHP without
@@ -1940,19 +1834,10 @@ impl WpMySqlNativeParser {
19401834 let native_ast_zval = WpMySqlNativeAst {
19411835 arena,
19421836 node_cache : RefCell :: new ( HashMap :: new ( ) ) ,
1943- gc_trace : RefCell :: new ( Vec :: new ( ) ) ,
19441837 }
19451838 . into_zval ( false )
19461839 . map_err ( php_error) ?;
19471840 let native_ast = native_ast ( & native_ast_zval) ?;
1948- // Install our custom `get_gc` so PHP's cycle collector can
1949- // see the cached wrappers held by the Rust-side HashMap.
1950- unsafe {
1951- let obj_ref = native_ast_zval
1952- . object ( )
1953- . ok_or_else ( || php_error ( "Native AST zval is not an object" ) ) ?;
1954- install_gc_handler_for ( ( obj_ref as * const ZendObject ) as * mut zend_object ) ;
1955- }
19561841 native_ast. arena . create_php_ast ( & native_ast_zval)
19571842 } )
19581843 }
0 commit comments