Skip to content

Fix #[php(prop)] String getter leak on Exception::getMessage path #738

@ptondereau

Description

@ptondereau

Description

Follow-up to #737, which ships only the regression test (ignored) and docs.

Any #[php(prop)] field holding an owned refcounted type leaks one zend_string per call when read through Exception::getMessage (or any C method using zval_get_string + RETURN_STR). The generated getter writes a refcount=1 string into the rv slot, getMessage reads it and addrefs to 2, transfers the pointer to return_value, then returns without zval_ptr_dtor(&rv). One refcount orphaned per call. Direct property access via FETCH_OBJ_R is not affected.

Two viable fixes:

  1. Mirror the shadow field into the parent's real property slot via zend_update_property_stringl at write time, so reads go through zend_std_read_property and return a direct pointer. Self-contained in ext-php-rs.
  2. Upstream PHP patch so Exception::getMessage and siblings call zval_ptr_dtor(&rv) when retval == &rv. (not sure)

Steps to Reproduce

  1. cargo test -p tests -- --ignored prop_string_field_does_not_leak_on_repeated_get_message
  2. Without the fix the test fails: roughly 75KB heap growth on release, hashtable refcount assertion on debug.

Example

Extension Code:

#[php_class]
#[php(extends(ce = ce::exception, stub = "\\Exception"))]
#[derive(Default)]
pub struct MyException {
    #[php(prop)]
    pub message: String,
}

PHP Code:

<?php
try { throw_my_exception(); } catch (MyException $e) {
    for ($i = 0; $i < 500; $i++) $e->getMessage();
}

Actual Behavior

One zend_string orphaned per getMessage() call. Linear memory growth.

Expected Behavior

Stable memory across repeated reads.

PHP Version

PHP 8.5.2-dev (cli) (NTS DEBUG)

ext-php-rs Version

master + PR #737

Operating System

Linux

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions