Skip to content

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

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't working

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions