diff --git a/Zend/zend.c b/Zend/zend.c index 78bde673d69..971560f2df7 100644 --- a/Zend/zend.c +++ b/Zend/zend.c @@ -1281,6 +1281,34 @@ ZEND_API void zend_deactivate(void) /* {{{ */ zend_destroy_rsrc_list(&EG(regular_list)); + /* See GH-8646: https://github.com/php/php-src/issues/8646 + * + * Interned strings that hold class entries can get a corresponding slot in map_ptr for the CE cache. + * map_ptr works like a bump allocator: there is a counter which increases to allocate the next slot in the map. + * + * For class name strings in non-opcache we have: + * - on startup: permanent + interned + * - on request: interned + * For class name strings in opcache we have: + * - on startup: permanent + interned + * - on request: either not interned at all, which we can ignore because they won't get a CE cache entry + * or they were already permanent + interned + * or we get a new permanent + interned string in the opcache persistence code + * + * Notice that the map_ptr layout always has the permanent strings first, and the request strings after. + * In non-opcache, a request string may get a slot in map_ptr, and that interned request string + * gets destroyed at the end of the request. The corresponding map_ptr slot can thereafter never be used again. + * This causes map_ptr to keep reallocating to larger and larger sizes. + * + * We solve it as follows: + * We can check whether we had any interned request strings, which only happens in non-opcache. + * If we have any, we reset map_ptr to the last permanent string. + * We can't lose any permanent strings because of map_ptr's layout. + */ + if (zend_hash_num_elements(&CG(interned_strings)) > 0) { + zend_map_ptr_reset(); + } + #if GC_BENCH fprintf(stderr, "GC Statistics\n"); fprintf(stderr, "-------------\n"); diff --git a/ext/gd/gd.stub.php b/ext/gd/gd.stub.php index 9f7783e9717..0c8a714068a 100644 --- a/ext/gd/gd.stub.php +++ b/ext/gd/gd.stub.php @@ -541,6 +541,7 @@ function imagesetbrush(GdImage $image, GdImage $brush): bool {} /** @refcount 1 */ function imagecreate(int $width, int $height): GdImage|false {} +/** @compile-time-eval */ function imagetypes(): int {} /** @refcount 1 */ diff --git a/ext/gd/gd_arginfo.h b/ext/gd/gd_arginfo.h index 9804ca7f0ec..cf6bcefa7b3 100644 --- a/ext/gd/gd_arginfo.h +++ b/ext/gd/gd_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 81ba6bf7b07027f6930db1c48a602f27724958af */ + * Stub hash: 810838932a482065c48ab715857062c071db31fd */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_gd_info, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -747,7 +747,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(imagesettile, arginfo_imagesettile) ZEND_FE(imagesetbrush, arginfo_imagesetbrush) ZEND_FE(imagecreate, arginfo_imagecreate) - ZEND_FE(imagetypes, arginfo_imagetypes) + ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(imagetypes, arginfo_imagetypes) ZEND_FE(imagecreatefromstring, arginfo_imagecreatefromstring) #if defined(HAVE_GD_AVIF) ZEND_FE(imagecreatefromavif, arginfo_imagecreatefromavif) diff --git a/ext/opcache/tests/gh8065.phpt b/ext/opcache/tests/gh8065.phpt new file mode 100644 index 00000000000..db951a7b091 --- /dev/null +++ b/ext/opcache/tests/gh8065.phpt @@ -0,0 +1,26 @@ +--TEST-- +GH-8065: opcache.consistency_checks > 0 causes segfaults in PHP >= 8.1.5 in fpm context +--EXTENSIONS-- +opcache +--INI-- +opcache.enable_cli=1 +opcache.consistency_checks=1 +opcache.log_verbosity_level=2 +--FILE-- + +--EXPECTF-- +%sWarning opcache.consistency_checks is reset back to 0 because it does not work properly (see GH-8065, GH-10624). + +string(1) "0" +%sWarning opcache.consistency_checks is reset back to 0 because it does not work properly (see GH-8065, GH-10624). + +bool(false) +%sWarning opcache.consistency_checks is reset back to 0 because it does not work properly (see GH-8065, GH-10624). + +bool(false) +string(1) "0" diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index f78e0651426..a50110b1757 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -129,6 +129,19 @@ static ZEND_INI_MH(OnUpdateMaxWastedPercentage) return SUCCESS; } +static ZEND_INI_MH(OnUpdateConsistencyChecks) +{ + zend_long *p = (zend_long *) ZEND_INI_GET_ADDR(); + zend_long consistency_checks = atoi(ZSTR_VAL(new_value)); + + if (consistency_checks != 0) { + zend_accel_error(ACCEL_LOG_WARNING, "opcache.consistency_checks is reset back to 0 because it does not work properly (see GH-8065, GH-10624).\n"); + return FAILURE; + } + *p = 0; + return SUCCESS; +} + static ZEND_INI_MH(OnEnable) { if (stage == ZEND_INI_STAGE_STARTUP || @@ -263,7 +276,7 @@ ZEND_INI_BEGIN() STD_PHP_INI_ENTRY("opcache.interned_strings_buffer", "8" , PHP_INI_SYSTEM, OnUpdateInternedStringsBuffer, accel_directives.interned_strings_buffer, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.max_accelerated_files" , "10000", PHP_INI_SYSTEM, OnUpdateMaxAcceleratedFiles, accel_directives.max_accelerated_files, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.max_wasted_percentage" , "5" , PHP_INI_SYSTEM, OnUpdateMaxWastedPercentage, accel_directives.max_wasted_percentage, zend_accel_globals, accel_globals) - STD_PHP_INI_ENTRY("opcache.consistency_checks" , "0" , PHP_INI_ALL , OnUpdateLong, accel_directives.consistency_checks, zend_accel_globals, accel_globals) + STD_PHP_INI_ENTRY("opcache.consistency_checks" , "0" , PHP_INI_ALL , OnUpdateConsistencyChecks, accel_directives.consistency_checks, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.force_restart_timeout" , "180" , PHP_INI_SYSTEM, OnUpdateLong, accel_directives.force_restart_timeout, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.revalidate_freq" , "2" , PHP_INI_ALL , OnUpdateLong, accel_directives.revalidate_freq, zend_accel_globals, accel_globals) STD_PHP_INI_ENTRY("opcache.file_update_protection", "2" , PHP_INI_ALL , OnUpdateLong, accel_directives.file_update_protection, zend_accel_globals, accel_globals) diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index ab56a8c0e8f..68d0e69516f 100755 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -1846,7 +1846,10 @@ function array_filter(array $array, ?callable $callback = null, int $mode = 0): function array_map(?callable $callback, array $array, array ...$arrays): array {} -/** @param string|int $key */ +/** + * @param string|int $key + * @compile-time-eval + */ function array_key_exists($key, array $array): bool {} /** @@ -1859,6 +1862,7 @@ function array_chunk(array $array, int $length, bool $preserve_keys = false): ar function array_combine(array $keys, array $values): array {} +/** @compile-time-eval */ function array_is_list(array $array): bool {} /* base64.c */ @@ -2314,7 +2318,10 @@ function pathinfo(string $path, int $flags = PATHINFO_ALL): array|string {} /** @refcount 1 */ function stristr(string $haystack, string $needle, bool $before_needle = false): string|false {} -/** @refcount 1 */ +/** + * @compile-time-eval + * @refcount 1 + */ function strstr(string $haystack, string $needle, bool $before_needle = false): string|false {} /** @alias strstr */ diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 077f9876df7..45daf2d4f76 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 39d455982dfdea9d0b9b646bc207b05f7108d1b2 */ + * Stub hash: b0b1fb366a3ba6acfcb460e5b1ac8212c436bf2f */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -2920,11 +2920,11 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(array_reduce, arginfo_array_reduce) ZEND_FE(array_filter, arginfo_array_filter) ZEND_FE(array_map, arginfo_array_map) - ZEND_FE(array_key_exists, arginfo_array_key_exists) + ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(array_key_exists, arginfo_array_key_exists) ZEND_FALIAS(key_exists, array_key_exists, arginfo_key_exists) ZEND_FE(array_chunk, arginfo_array_chunk) ZEND_FE(array_combine, arginfo_array_combine) - ZEND_FE(array_is_list, arginfo_array_is_list) + ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(array_is_list, arginfo_array_is_list) ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(base64_encode, arginfo_base64_encode) ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(base64_decode, arginfo_base64_decode) ZEND_FE(constant, arginfo_constant) @@ -3090,7 +3090,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(dirname, arginfo_dirname) ZEND_FE(pathinfo, arginfo_pathinfo) ZEND_FE(stristr, arginfo_stristr) - ZEND_FE(strstr, arginfo_strstr) + ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strstr, arginfo_strstr) ZEND_FALIAS(strchr, strstr, arginfo_strchr) ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(strpos, arginfo_strpos) ZEND_SUPPORTS_COMPILE_TIME_EVAL_FE(stripos, arginfo_stripos) diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index 66d9bffe57c..78b5ee00f6f 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -527,6 +527,12 @@ static ZEND_FUNCTION(zend_test_zend_call_stack_use_all) } #endif /* ZEND_CHECK_STACK_LIMIT */ +static ZEND_FUNCTION(zend_get_map_ptr_last) +{ + ZEND_PARSE_PARAMETERS_NONE(); + RETURN_LONG(CG(map_ptr_last)); +} + static zend_object *zend_test_class_new(zend_class_entry *class_type) { zend_object *obj = zend_objects_new(class_type); diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 83b1e4de334..138af49e02f 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -188,6 +188,8 @@ namespace { #endif function zend_test_is_string_marked_as_valid_utf8(string $string): bool {} + + function zend_get_map_ptr_last(): int {} } namespace ZendTestNS { diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index 800db7ee978..fc346a86c1e 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 947056bfe52d586b8a4969e3022f84e83b677e0e */ + * Stub hash: 904f34b4921ebde937b8f08834750aa78b32b852 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -111,6 +111,9 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_is_string_marked_as_va ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_get_map_ptr_last, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_ZendTestNS2_namespaced_func, 0, 0, _IS_BOOL, 0) ZEND_END_ARG_INFO() @@ -128,8 +131,7 @@ ZEND_END_ARG_INFO() #define arginfo_ZendTestNS2_ZendSubNS_namespaced_deprecated_aliased_func arginfo_zend_test_void_return -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_class__ZendTestClass_is_object, 0, 0, IS_LONG, 0) -ZEND_END_ARG_INFO() +#define arginfo_class__ZendTestClass_is_object arginfo_zend_get_map_ptr_last #define arginfo_class__ZendTestClass___toString arginfo_zend_get_current_func_name @@ -210,6 +212,7 @@ static ZEND_FUNCTION(zend_test_zend_call_stack_get); static ZEND_FUNCTION(zend_test_zend_call_stack_use_all); #endif static ZEND_FUNCTION(zend_test_is_string_marked_as_valid_utf8); +static ZEND_FUNCTION(zend_get_map_ptr_last); static ZEND_FUNCTION(ZendTestNS2_namespaced_func); static ZEND_FUNCTION(ZendTestNS2_namespaced_deprecated_func); static ZEND_FUNCTION(ZendTestNS2_ZendSubNS_namespaced_func); @@ -268,6 +271,7 @@ static const zend_function_entry ext_functions[] = { ZEND_FE(zend_test_zend_call_stack_use_all, arginfo_zend_test_zend_call_stack_use_all) #endif ZEND_FE(zend_test_is_string_marked_as_valid_utf8, arginfo_zend_test_is_string_marked_as_valid_utf8) + ZEND_FE(zend_get_map_ptr_last, arginfo_zend_get_map_ptr_last) ZEND_NS_FALIAS("ZendTestNS2", namespaced_func, ZendTestNS2_namespaced_func, arginfo_ZendTestNS2_namespaced_func) ZEND_NS_DEP_FALIAS("ZendTestNS2", namespaced_deprecated_func, ZendTestNS2_namespaced_deprecated_func, arginfo_ZendTestNS2_namespaced_deprecated_func) ZEND_NS_FALIAS("ZendTestNS2", namespaced_aliased_func, zend_test_void_return, arginfo_ZendTestNS2_namespaced_aliased_func) diff --git a/sapi/fpm/tests/gh8646.phpt b/sapi/fpm/tests/gh8646.phpt new file mode 100644 index 00000000000..c35b57ec916 --- /dev/null +++ b/sapi/fpm/tests/gh8646.phpt @@ -0,0 +1,52 @@ +--TEST-- +GH-8646 (Memory leak PHP FPM 8.1) +--EXTENSIONS-- +zend_test +--SKIPIF-- + +--FILE-- +start(); +$tester->expectLogStartNotices(); +$map_ptr_last_values = []; +for ($i = 0; $i < 10; $i++) { + $map_ptr_last_values[] = (int) $tester->request()->getBody(); +} +// Ensure that map_ptr_last did not increase +var_dump(count(array_unique($map_ptr_last_values, SORT_REGULAR)) === 1); +$tester->terminate(); +$tester->expectLogTerminatingNotices(); +$tester->close(); + +?> +Done +--EXPECT-- +bool(true) +Done +--CLEAN-- +