Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
6084765
Add ZendMM observer
realFlowControl Jul 5, 2023
c9cd716
remove `php.h`
realFlowControl Jul 24, 2023
5c4e6a1
Add missing `ZEND_API`
realFlowControl Jul 24, 2023
f5d5701
make observer less complex
realFlowControl Jul 25, 2023
f0e3900
shutdown ZendMM observers during request shutdown
realFlowControl Jul 25, 2023
252fd60
add tests for ZendMM Observer
realFlowControl Jul 26, 2023
dd25c81
rename zendmm -> zend_mm
realFlowControl Jul 28, 2023
51dd7a4
removed unused functions
realFlowControl Jul 28, 2023
9c6e74a
add missing author
realFlowControl Jul 28, 2023
e3bb4df
fix naming and bool parsing
realFlowControl Jul 28, 2023
d6d9712
make functions static to avoid name clashes
realFlowControl Jul 28, 2023
95711bf
simplyfy test
realFlowControl Jul 28, 2023
cce50ce
deduplicate observer calling
realFlowControl Jul 28, 2023
f8cc8e2
Add parameter names
realFlowControl Jul 31, 2023
3f498b1
fix test
realFlowControl Jul 31, 2023
2c70e10
fix tests on windows
realFlowControl Aug 1, 2023
60819d6
fix asan tests
realFlowControl Aug 2, 2023
5578359
remove `is_zend_mm()` check
realFlowControl Aug 3, 2023
b6e481c
Add missing author
realFlowControl Aug 3, 2023
9b44978
observe ZendMM garbage collection
realFlowControl Oct 27, 2023
19434e3
Add shutdown observer and fix debug builds
realFlowControl Oct 27, 2023
be9b879
Fix remaining conflict markers from rebase
realFlowControl Nov 10, 2025
f8ab266
fixup after rebase
realFlowControl Nov 10, 2025
ed36539
fix tests
realFlowControl Nov 12, 2025
3efcffc
fix gc with custom gc
realFlowControl Nov 12, 2025
9f9462f
fixup
realFlowControl Nov 12, 2025
d8ea60e
cleanup
realFlowControl Apr 27, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
351 changes: 315 additions & 36 deletions Zend/zend_alloc.c

Large diffs are not rendered by default.

20 changes: 17 additions & 3 deletions Zend/zend_alloc.h
Original file line number Diff line number Diff line change
Expand Up @@ -268,9 +268,10 @@ ZEND_API zend_mm_heap *zend_mm_get_heap(void);

ZEND_API size_t zend_mm_gc(zend_mm_heap *heap);

#define ZEND_MM_CUSTOM_HEAP_NONE 0
#define ZEND_MM_CUSTOM_HEAP_STD 1
#define ZEND_MM_CUSTOM_HEAP_DEBUG 2
#define ZEND_MM_CUSTOM_HEAP_NONE 0
#define ZEND_MM_CUSTOM_HEAP_STD 1
#define ZEND_MM_CUSTOM_HEAP_DEBUG 2
#define ZEND_MM_CUSTOM_HEAP_OBSERVED 4

ZEND_API bool zend_mm_is_custom_heap(zend_mm_heap *new_heap);
ZEND_API void zend_mm_set_custom_handlers(zend_mm_heap *heap,
Expand All @@ -294,6 +295,19 @@ ZEND_API void zend_mm_get_custom_handlers_ex(zend_mm_heap *heap,
size_t (**_gc)(void),
void (**_shutdown)(bool, bool));

typedef struct _zend_mm_observer zend_mm_observer;

ZEND_API bool zend_mm_is_observed(zend_mm_heap *heap);
ZEND_API zend_mm_observer* zend_mm_observer_register(
zend_mm_heap *heap,
void (*malloc)(size_t len, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void (*free)(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void (*realloc)(void *old_ptr, size_t len, void *new_ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC),
void (*gc)(size_t len),
void (*shutdown)(bool full, bool silent)
);
ZEND_API bool zend_mm_observer_unregister(zend_mm_heap *heap, zend_mm_observer *observer);

typedef struct _zend_mm_storage zend_mm_storage;

typedef void* (*zend_mm_chunk_alloc_t)(zend_mm_storage *storage, size_t size, size_t alignment);
Expand Down
1 change: 1 addition & 0 deletions ext/zend_test/config.m4
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ if test "$PHP_ZEND_TEST" != "no"; then
observer.c
test.c
zend_mm_custom_handlers.c
zend_mm_observer.c
]),
[$ext_shared],,
[-DZEND_ENABLE_STATIC_TSRMLS_CACHE=1])
Expand Down
3 changes: 2 additions & 1 deletion ext/zend_test/config.w32
Original file line number Diff line number Diff line change
Expand Up @@ -3,5 +3,6 @@
ARG_ENABLE("zend-test", "enable zend_test extension", "no");

if (PHP_ZEND_TEST != "no") {
EXTENSION("zend_test", "test.c observer.c fiber.c iterators.c object_handlers.c zend_mm_custom_handlers.c", PHP_ZEND_TEST_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
EXTENSION("zend_test", "test.c observer.c fiber.c iterators.c object_handlers.c zend_mm_custom_handlers.c zend_mm_observer.c", PHP_ZEND_TEST_SHARED, "/DZEND_ENABLE_STATIC_TSRMLS_CACHE=1");
ADD_FLAG("CFLAGS_ZEND_TEST", "/D PHP_ZEND_TEST_EXPORTS ");
}
3 changes: 3 additions & 0 deletions ext/zend_test/php_test.h
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
#define PHP_TEST_H

#include "fiber.h"
#include "Zend/zend_alloc.h"

extern zend_module_entry zend_test_module_entry;
#define phpext_zend_test_ptr &zend_test_module_entry
Expand Down Expand Up @@ -71,6 +72,8 @@ ZEND_BEGIN_MODULE_GLOBALS(zend_test)
// this is our heap that we install our custom handlers on and inject into
// ZendMM
zend_mm_heap* observed_heap;
int zend_mm_observer_enabled;
zend_mm_observer *observer;
ZEND_END_MODULE_GLOBALS(zend_test)

extern ZEND_DECLARE_MODULE_GLOBALS(zend_test)
Expand Down
4 changes: 4 additions & 0 deletions ext/zend_test/test.c
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include "ext/standard/info.h"
#include "php_test.h"
#include "observer.h"
#include "zend_mm_observer.h"
#include "fiber.h"
#include "iterators.h"
#include "object_handlers.h"
Expand Down Expand Up @@ -1602,6 +1603,7 @@ PHP_MINIT_FUNCTION(zend_test)

zend_test_observer_init(INIT_FUNC_ARGS_PASSTHRU);
zend_test_mm_custom_handlers_minit(INIT_FUNC_ARGS_PASSTHRU);
zend_test_mm_observer_minit(INIT_FUNC_ARGS_PASSTHRU);
zend_test_fiber_init();
zend_test_iterators_init();
zend_test_object_handlers_init();
Expand Down Expand Up @@ -1632,6 +1634,7 @@ PHP_RINIT_FUNCTION(zend_test)
zend_hash_init(ZT_G(global_weakmap), 8, NULL, ZVAL_PTR_DTOR, 0);
ZT_G(observer_nesting_depth) = 0;
zend_test_mm_custom_handlers_rinit();
zend_test_mm_observer_rinit();
return SUCCESS;
}

Expand All @@ -1647,6 +1650,7 @@ PHP_RSHUTDOWN_FUNCTION(zend_test)
}

zend_test_mm_custom_handlers_rshutdown();
zend_test_mm_observer_rshutdown();
return SUCCESS;
}

Expand Down
16 changes: 16 additions & 0 deletions ext/zend_test/tests/zend_mm_observer_free_01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
--TEST--
ZendMM Observer: Observe free
--EXTENSIONS--
zend_test
--INI--
zend_test.zend_mm_observer.enabled=0
opcache.enable=0
--FILE--
<?php
$string = str_repeat("ZendMM Observer", 100);
ini_set('zend_test.zend_mm_observer.enabled', 'true');
unset($string);
ini_set('zend_test.zend_mm_observer.enabled', 'false');
?>
--EXPECTREGEX--
.*freed \S+ of size \d+.*
15 changes: 15 additions & 0 deletions ext/zend_test/tests/zend_mm_observer_gc_01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
ZendMM Observer: Observe garbage collection
--EXTENSIONS--
zend_test
--INI--
zend_test.zend_mm_observer.enabled=0
opcache.enable=0
--FILE--
<?php
ini_set('zend_test.zend_mm_observer.enabled', 'true');
gc_mem_caches();
ini_set('zend_test.zend_mm_observer.enabled', 'false');
?>
--EXPECTREGEX--
.*garbage collection ended with \d+ bytes collected.*
13 changes: 13 additions & 0 deletions ext/zend_test/tests/zend_mm_observer_lifecycle_01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
ZendMM Observer: Observe lifecycle (RINIT -> RSHUTDOWN)
--EXTENSIONS--
zend_test
--INI--
zend_test.zend_mm_observer.enabled=1
opcache.enable=0
--FILE--
<?php
echo "done";
?>
--EXPECTREGEX--
.*ZendMM Observer enabled.*
15 changes: 15 additions & 0 deletions ext/zend_test/tests/zend_mm_observer_malloc_01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
ZendMM Observer: Observe malloc
--EXTENSIONS--
zend_test
--INI--
zend_test.zend_mm_observer.enabled=0
opcache.enable=0
--FILE--
<?php
ini_set('zend_test.zend_mm_observer.enabled', 'true');
$string = str_repeat("ZendMM Observer", 100);
ini_set('zend_test.zend_mm_observer.enabled', 'false');
?>
--EXPECTREGEX--
.*malloc \S+ of size \d+ \(block: \d+\).*
26 changes: 26 additions & 0 deletions ext/zend_test/tests/zend_mm_observer_realloc_01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
--TEST--
ZendMM Observer: Observe realloc
--EXTENSIONS--
zend_test
--INI--
zend_test.zend_mm_observer.enabled=0
opcache.enable=0
--FILE--
<?php
$a = [];
$a[] = 'ZendMM Observer';
$a[] = 'ZendMM Observer';
$a[] = 'ZendMM Observer';
$a[] = 'ZendMM Observer';
$a[] = 'ZendMM Observer';
$a[] = 'ZendMM Observer';
$a[] = 'ZendMM Observer';
$a[] = 'ZendMM Observer';
ini_set('zend_test.zend_mm_observer.enabled', 'true');
$a[] = 'ZendMM Observer';
ini_set('zend_test.zend_mm_observer.enabled', 'false');
?>
--EXPECTREGEX--
.*ZendMM Observer enabled.*
.*realloc \S+ of size \d+ \(block: \d+, former \S+\).*
.*ZendMM Observer disabled.*
14 changes: 14 additions & 0 deletions ext/zend_test/tests/zend_mm_observer_shutdown_01.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
ZendMM Observer: Observe shutdown
--EXTENSIONS--
zend_test
--ENV--
ZEND_TEST_MM_OBSERVER_SHUTDOWN_TEST=1
--INI--
zend_test.zend_mm_observer.enabled=1
--FILE--
<?php
echo "done.";
?>
--EXPECTREGEX--
.*shutdown in progress: full\(\d\), silent\(\d\).*
143 changes: 143 additions & 0 deletions ext/zend_test/zend_mm_observer.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Florian Engelhardt <florian@engelhardt.tc> |
+----------------------------------------------------------------------+
*/

#include "php.h"
#include "php_test.h"
#include "Zend/zend_alloc.h"
#include "ext/standard/info.h"
#include "ext/standard/php_var.h"
#include "zend_mm_observer.h"

static void zend_mm_test_observer_malloc(size_t len, void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
size_t block_len = 0;
if (is_zend_ptr(ptr)) {
block_len = zend_mm_block_size(zend_mm_get_heap(), ptr);
}
printf("malloc %p of size %zu (block: %zu)\n", ptr, len, block_len);
fflush(stdout);
}

static void zend_mm_test_observer_free(void *ptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
size_t block_len = 0;
if (is_zend_ptr(ptr)) {
block_len = zend_mm_block_size(zend_mm_get_heap(), ptr);
}
printf("freed %p of size %zu\n", ptr, block_len);
fflush(stdout);
}

static void zend_mm_test_observer_realloc(void *ptr, size_t len, void *newptr ZEND_FILE_LINE_DC ZEND_FILE_LINE_ORIG_DC)
{
size_t block_len = 0;
if (is_zend_ptr(ptr)) {
block_len = zend_mm_block_size(zend_mm_get_heap(), ptr);
}
printf("realloc %p of size %zu (block: %zu, former %p)\n", newptr, len, block_len, ptr);
fflush(stdout);
}

static void zend_mm_test_observer_gc(size_t len)
{
printf("garbage collection ended with %zu bytes collected\n", len);
fflush(stdout);
}

static void zend_mm_test_observer_shutdown(bool full, bool silent)
{
printf("shutdown in progress: full(%d), silent(%d)\n", full, silent);
fflush(stdout);
}

static PHP_INI_MH(OnUpdateZendTestMMObserverEnabled)
{
if (new_value == NULL) {
return FAILURE;
}

int int_value = zend_ini_parse_bool(new_value);

/* Only toggle observer during runtime (ini_set during active request).
* RINIT/RSHUTDOWN handle initialization and cleanup. */
if (stage == PHP_INI_STAGE_RUNTIME) {
if (int_value == 1) {
if (ZT_G(observer) == NULL) {
ZT_G(observer) = zend_mm_observer_register(
zend_mm_get_heap(),
zend_mm_test_observer_malloc,
zend_mm_test_observer_free,
zend_mm_test_observer_realloc,
zend_mm_test_observer_gc,
zend_mm_test_observer_shutdown
);
printf("ZendMM Observer enabled\n");
fflush(stdout);
}
} else {
if (ZT_G(observer) != NULL) {
zend_mm_observer_unregister(zend_mm_get_heap(), ZT_G(observer));
ZT_G(observer) = NULL;
printf("ZendMM Observer disabled\n");
fflush(stdout);
}
}
}
return OnUpdateBool(entry, new_value, mh_arg1, mh_arg2, mh_arg3, stage);
}

PHP_INI_BEGIN()
STD_PHP_INI_BOOLEAN("zend_test.zend_mm_observer.enabled", "0", PHP_INI_ALL, OnUpdateZendTestMMObserverEnabled, zend_mm_observer_enabled, zend_zend_test_globals, zend_test_globals)
PHP_INI_END()

void zend_test_mm_observer_minit(INIT_FUNC_ARGS)
{
if (type != MODULE_TEMPORARY) {
REGISTER_INI_ENTRIES();
} else {
(void)ini_entries;
}
}

void zend_test_mm_observer_rinit(void)
{
if (ZT_G(zend_mm_observer_enabled)) {
ZT_G(observer) = zend_mm_observer_register(
zend_mm_get_heap(),
zend_mm_test_observer_malloc,
zend_mm_test_observer_free,
zend_mm_test_observer_realloc,
zend_mm_test_observer_gc,
zend_mm_test_observer_shutdown
);
printf("ZendMM Observer enabled\n");
fflush(stdout);
}
}

void zend_test_mm_observer_rshutdown(void)
{
char *env = getenv("ZEND_TEST_MM_OBSERVER_SHUTDOWN_TEST");
if (env != NULL) {
return;
}
if (ZT_G(observer)) {
zend_mm_observer_unregister(zend_mm_get_heap(), ZT_G(observer));
printf("ZendMM Observer disabled\n");
fflush(stdout);
}
ZT_G(observer) = NULL;
}
24 changes: 24 additions & 0 deletions ext/zend_test/zend_mm_observer.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
/*
+----------------------------------------------------------------------+
| Copyright (c) The PHP Group |
+----------------------------------------------------------------------+
| This source file is subject to version 3.01 of the PHP license, |
| that is bundled with this package in the file LICENSE, and is |
| available through the world-wide-web at the following url: |
| https://www.php.net/license/3_01.txt |
| If you did not receive a copy of the PHP license and are unable to |
| obtain it through the world-wide-web, please send a note to |
| license@php.net so we can mail you a copy immediately. |
+----------------------------------------------------------------------+
| Author: Florian Engelhardt <florian@engelhardt.tc> |
+----------------------------------------------------------------------+
*/

#ifndef ZEND_TEST_MM_OBSERVER_H
#define ZEND_TEST_MM_OBSERVER_H

void zend_test_mm_observer_minit(INIT_FUNC_ARGS);
void zend_test_mm_observer_rinit(void);
void zend_test_mm_observer_rshutdown(void);

#endif
Loading