Skip to content

Commit 89a4619

Browse files
committed
Patch GC handler per-instance for PHP 8.2 compat
PHP 8.3 added zend_class_entry.default_object_handlers letting us patch get_gc once per class on MINIT, but PHP 8.2 has no such field — the class entry only stores create_object. The portable path is to override each WpMySqlNativeAst's zend_object.handlers right after the object is allocated. We seed the patched handlers struct lazily on the first AST and reuse it for the rest.
1 parent 7b1ad32 commit 89a4619

1 file changed

Lines changed: 25 additions & 25 deletions

File tree

  • packages/php-ext-wp-mysql-parser/src

packages/php-ext-wp-mysql-parser/src/lib.rs

Lines changed: 25 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -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

Comments
 (0)