Skip to content

Commit 72009da

Browse files
php-genericsclaude
andcommitted
Fix CI failures: opcache SHM persistence crashes, optimizer memory leak, compile warning
- Fix segfault with opcache.protect_memory=1: add zend_accel_in_shm() checks before calling zend_shared_memdup_put_free() on generic type refs, class refs, wildcard bounds, generic args, params info, and bound generic args hash tables that may already be in read-only SHM when inheriting from previously-persisted interfaces. - Fix 28-byte memory leak in static generic calls with opcache optimizer: when pass 4 (func_calls) + pass 16 (inline) combine to inline a Box<int>::hello() call, the INIT_STATIC_METHOD_CALL opcode is NOPed but the generic args literal at op1.constant+2 was never released. Release it before zend_delete_call_instructions NOPs the opcode. - Fix -Werror compile failure: remove unused 'key' variable in zend_verify_generic_variance() by switching to ZEND_HASH_MAP_FOREACH_PTR. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
1 parent 15247ea commit 72009da

File tree

4 files changed

+110
-29
lines changed

4 files changed

+110
-29
lines changed

Zend/Optimizer/optimize_func_calls.c

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include "zend_constants.h"
2828
#include "zend_execute.h"
2929
#include "zend_vm.h"
30+
#include "zend_generics.h"
3031

3132
typedef struct _optimizer_call_info {
3233
zend_function *func;
@@ -131,6 +132,17 @@ static void zend_try_inline_call(zend_op_array *op_array, const zend_op *fcall,
131132
MAKE_NOP(opline);
132133
}
133134

135+
/* Release generic args literal before the INIT opcode is NOPed */
136+
if (fcall->opcode == ZEND_INIT_STATIC_METHOD_CALL
137+
&& fcall->op1_type == IS_CONST
138+
&& (fcall->result.num & 0x80000000)) {
139+
zval *generic_args_zv = &op_array->literals[fcall->op1.constant + 2];
140+
if (Z_TYPE_P(generic_args_zv) == IS_PTR && Z_PTR_P(generic_args_zv) != NULL) {
141+
zend_generic_args_release((zend_generic_args *) Z_PTR_P(generic_args_zv));
142+
ZVAL_NULL(generic_args_zv);
143+
}
144+
}
145+
134146
zend_delete_call_instructions(op_array, opline-1);
135147
}
136148
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
--TEST--
2+
Generic static method call inlined by optimizer (no typed params, returns constant)
3+
--INI--
4+
opcache.enable=1
5+
opcache.enable_cli=1
6+
opcache.optimization_level=0x7FFEBFFF
7+
--EXTENSIONS--
8+
opcache
9+
--FILE--
10+
<?php
11+
12+
class Box<T> {
13+
public static function hello(): string {
14+
return "Hello from Box!";
15+
}
16+
17+
public static function getClass(): string {
18+
return self::class;
19+
}
20+
}
21+
22+
// These calls are eligible for optimizer inlining (pass 4+16):
23+
// no typed params + body is just "return constant"
24+
echo Box<int>::hello() . "\n";
25+
echo Box<string>::hello() . "\n";
26+
echo Box<int>::getClass() . "\n";
27+
28+
echo "OK\n";
29+
?>
30+
--EXPECT--
31+
Hello from Box!
32+
Hello from Box!
33+
Box
34+
OK

Zend/zend_compile.c

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9905,9 +9905,8 @@ static void zend_verify_generic_variance(zend_class_entry *ce) /* {{{ */
99059905
}
99069906

99079907
/* Check all methods */
9908-
zend_string *key;
99099908
zend_function *fn;
9910-
ZEND_HASH_MAP_FOREACH_STR_KEY_PTR(&ce->function_table, key, fn) {
9909+
ZEND_HASH_MAP_FOREACH_PTR(&ce->function_table, fn) {
99119910
if (fn->common.scope != ce) {
99129911
continue; /* Skip inherited methods */
99139912
}

ext/opcache/zend_persist.c

Lines changed: 63 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -364,17 +364,30 @@ uint32_t zend_accel_get_class_name_map_ptr(zend_string *type_name)
364364
static void zend_persist_generic_args(zend_generic_args **args_ptr);
365365

366366
static void zend_persist_type(zend_type *type) {
367-
/* Handle generic type references before the list/name iteration */
367+
/* Handle generic type references before the list/name iteration.
368+
* When inheriting from an already-persisted class/interface, the ref
369+
* may already live in SHM — use memdup_put (no free) to avoid efree
370+
* on read-only SHM memory. */
368371
if (ZEND_TYPE_IS_GENERIC_PARAM(*type)) {
369372
zend_generic_type_ref *ref = ZEND_TYPE_GENERIC_PARAM_REF(*type);
370-
zend_generic_type_ref *new_ref = zend_shared_memdup_put_free(ref, sizeof(zend_generic_type_ref));
373+
zend_generic_type_ref *new_ref;
374+
if (zend_accel_in_shm(ref)) {
375+
new_ref = zend_shared_memdup_put(ref, sizeof(zend_generic_type_ref));
376+
} else {
377+
new_ref = zend_shared_memdup_put_free(ref, sizeof(zend_generic_type_ref));
378+
}
371379
zend_accel_store_interned_string(new_ref->name);
372380
ZEND_TYPE_SET_PTR(*type, new_ref);
373381
return;
374382
}
375383
if (ZEND_TYPE_IS_GENERIC_CLASS(*type)) {
376384
zend_generic_class_ref *ref = ZEND_TYPE_GENERIC_CLASS_REF(*type);
377-
zend_generic_class_ref *new_ref = zend_shared_memdup_put_free(ref, sizeof(zend_generic_class_ref));
385+
zend_generic_class_ref *new_ref;
386+
if (zend_accel_in_shm(ref)) {
387+
new_ref = zend_shared_memdup_put(ref, sizeof(zend_generic_class_ref));
388+
} else {
389+
new_ref = zend_shared_memdup_put_free(ref, sizeof(zend_generic_class_ref));
390+
}
378391
zend_accel_store_interned_string(new_ref->class_name);
379392
if (!ZCG(current_persistent_script)->corrupted) {
380393
zend_accel_get_class_name_map_ptr(new_ref->class_name);
@@ -383,8 +396,13 @@ static void zend_persist_type(zend_type *type) {
383396
zend_persist_generic_args(&new_ref->type_args);
384397
}
385398
if (new_ref->wildcard_bounds && new_ref->type_args) {
386-
new_ref->wildcard_bounds = zend_shared_memdup_put_free(
387-
new_ref->wildcard_bounds, new_ref->type_args->num_args * sizeof(zend_generic_bound));
399+
if (zend_accel_in_shm(new_ref->wildcard_bounds)) {
400+
new_ref->wildcard_bounds = zend_shared_memdup_put(
401+
new_ref->wildcard_bounds, new_ref->type_args->num_args * sizeof(zend_generic_bound));
402+
} else {
403+
new_ref->wildcard_bounds = zend_shared_memdup_put_free(
404+
new_ref->wildcard_bounds, new_ref->type_args->num_args * sizeof(zend_generic_bound));
405+
}
388406
}
389407
ZEND_TYPE_SET_PTR(*type, new_ref);
390408
return;
@@ -422,7 +440,11 @@ static void zend_persist_generic_args(zend_generic_args **args_ptr)
422440
{
423441
zend_generic_args *args = *args_ptr;
424442
size_t size = ZEND_GENERIC_ARGS_SIZE(args->num_args);
425-
args = zend_shared_memdup_put_free(args, size);
443+
if (zend_accel_in_shm(args)) {
444+
args = zend_shared_memdup_put(args, size);
445+
} else {
446+
args = zend_shared_memdup_put_free(args, size);
447+
}
426448
args->refcount = 0; /* SHM: unmanaged, never freed via release */
427449
for (uint32_t i = 0; i < args->num_args; i++) {
428450
zend_persist_type(&args->args[i]);
@@ -434,7 +456,11 @@ static void zend_persist_generic_params_info(zend_generic_params_info **info_ptr
434456
{
435457
zend_generic_params_info *info = *info_ptr;
436458
size_t size = ZEND_GENERIC_PARAMS_INFO_SIZE(info->num_params);
437-
info = zend_shared_memdup_put_free(info, size);
459+
if (zend_accel_in_shm(info)) {
460+
info = zend_shared_memdup_put(info, size);
461+
} else {
462+
info = zend_shared_memdup_put_free(info, size);
463+
}
438464
for (uint32_t i = 0; i < info->num_params; i++) {
439465
zend_accel_store_interned_string(info->params[i].name);
440466
zend_persist_type(&info->params[i].constraint);
@@ -1215,28 +1241,38 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce)
12151241
zend_persist_generic_args(&ce->bound_generic_args);
12161242
}
12171243
if (ce->interface_bound_generic_args) {
1218-
zend_hash_persist(ce->interface_bound_generic_args);
1219-
ZEND_HASH_MAP_FOREACH_BUCKET(ce->interface_bound_generic_args, p) {
1220-
ZEND_ASSERT(p->key != NULL);
1221-
zend_accel_store_interned_string(p->key);
1222-
zend_generic_args *args = Z_PTR(p->val);
1223-
zend_persist_generic_args(&args);
1224-
Z_PTR(p->val) = args;
1225-
} ZEND_HASH_FOREACH_END();
1226-
ce->interface_bound_generic_args = zend_shared_memdup_put_free(
1227-
ce->interface_bound_generic_args, sizeof(HashTable));
1244+
if (zend_accel_in_shm(ce->interface_bound_generic_args)) {
1245+
ce->interface_bound_generic_args = zend_shared_memdup_put(
1246+
ce->interface_bound_generic_args, sizeof(HashTable));
1247+
} else {
1248+
zend_hash_persist(ce->interface_bound_generic_args);
1249+
ZEND_HASH_MAP_FOREACH_BUCKET(ce->interface_bound_generic_args, p) {
1250+
ZEND_ASSERT(p->key != NULL);
1251+
zend_accel_store_interned_string(p->key);
1252+
zend_generic_args *args = Z_PTR(p->val);
1253+
zend_persist_generic_args(&args);
1254+
Z_PTR(p->val) = args;
1255+
} ZEND_HASH_FOREACH_END();
1256+
ce->interface_bound_generic_args = zend_shared_memdup_put_free(
1257+
ce->interface_bound_generic_args, sizeof(HashTable));
1258+
}
12281259
}
12291260
if (ce->trait_bound_generic_args) {
1230-
zend_hash_persist(ce->trait_bound_generic_args);
1231-
ZEND_HASH_MAP_FOREACH_BUCKET(ce->trait_bound_generic_args, p) {
1232-
ZEND_ASSERT(p->key != NULL);
1233-
zend_accel_store_interned_string(p->key);
1234-
zend_generic_args *args = Z_PTR(p->val);
1235-
zend_persist_generic_args(&args);
1236-
Z_PTR(p->val) = args;
1237-
} ZEND_HASH_FOREACH_END();
1238-
ce->trait_bound_generic_args = zend_shared_memdup_put_free(
1239-
ce->trait_bound_generic_args, sizeof(HashTable));
1261+
if (zend_accel_in_shm(ce->trait_bound_generic_args)) {
1262+
ce->trait_bound_generic_args = zend_shared_memdup_put(
1263+
ce->trait_bound_generic_args, sizeof(HashTable));
1264+
} else {
1265+
zend_hash_persist(ce->trait_bound_generic_args);
1266+
ZEND_HASH_MAP_FOREACH_BUCKET(ce->trait_bound_generic_args, p) {
1267+
ZEND_ASSERT(p->key != NULL);
1268+
zend_accel_store_interned_string(p->key);
1269+
zend_generic_args *args = Z_PTR(p->val);
1270+
zend_persist_generic_args(&args);
1271+
Z_PTR(p->val) = args;
1272+
} ZEND_HASH_FOREACH_END();
1273+
ce->trait_bound_generic_args = zend_shared_memdup_put_free(
1274+
ce->trait_bound_generic_args, sizeof(HashTable));
1275+
}
12401276
}
12411277
}
12421278

0 commit comments

Comments
 (0)