diff --git a/.github/scripts/windows/build_task.bat b/.github/scripts/windows/build_task.bat index 3c8dc04d59b40..fd9a956bd38fb 100644 --- a/.github/scripts/windows/build_task.bat +++ b/.github/scripts/windows/build_task.bat @@ -30,6 +30,7 @@ if %errorlevel% neq 0 exit /b 3 if "%THREAD_SAFE%" equ "0" set ADD_CONF=%ADD_CONF% --disable-zts if "%INTRINSICS%" neq "" set ADD_CONF=%ADD_CONF% --enable-native-intrinsics=%INTRINSICS% +if "%ASAN%" equ "1" set ADD_CONF=%ADD_CONF% --enable-sanitizer --enable-debug-pack set CFLAGS=/W1 /WX /w14013 diff --git a/.github/scripts/windows/test_task.bat b/.github/scripts/windows/test_task.bat index 0d6117324d63d..ee8a5f01b2c0c 100644 --- a/.github/scripts/windows/test_task.bat +++ b/.github/scripts/windows/test_task.bat @@ -133,9 +133,11 @@ for %%i in (ldap) do ( set TEST_PHPDBG_EXECUTABLE=%PHP_BUILD_DIR%\phpdbg.exe +if "%ASAN%" equ "1" set ASAN_OPTS=--asan + mkdir c:\tests_tmp -nmake test TESTS="%OPCACHE_OPTS% -g FAIL,BORK,LEAK,XLEAK --no-progress -q --offline --show-diff --show-slow 1000 --set-timeout 120 --temp-source c:\tests_tmp --temp-target c:\tests_tmp %PARALLEL%" +nmake test TESTS="%OPCACHE_OPTS% -g FAIL,BORK,LEAK,XLEAK %ASAN_OPTS% --no-progress -q --offline --show-diff --show-slow 1000 --set-timeout 120 --temp-source c:\tests_tmp --temp-target c:\tests_tmp %PARALLEL%" set EXIT_CODE=%errorlevel% diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index a0900f1f3d2c5..4041271f4d041 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -963,10 +963,18 @@ jobs: - x64: true zts: true opcache: true + asan: false - x64: false zts: false opcache: false - name: "WINDOWS_${{ matrix.x64 && 'X64' || 'X86' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}" + asan: false + - x64: true + zts: true + opcache: true + asan: true + branch: 'master' + timeout: 120 + name: "WINDOWS_${{ matrix.x64 && 'X64' || 'X86' }}_${{ matrix.zts && 'ZTS' || 'NTS' }}${{ matrix.asan && '_ASAN' || ''}}" runs-on: windows-${{ inputs.windows_version }} env: PHP_BUILD_CACHE_BASE_DIR: C:\build-cache @@ -979,6 +987,7 @@ jobs: INTRINSICS: "${{ matrix.zts && 'AVX2' || '' }}" PARALLEL: -j2 OPCACHE: "${{ matrix.opcache && '1' || '0' }}" + ASAN: "${{ matrix.asan && '1' || '0' }}" steps: - name: git config run: git config --global core.autocrlf false && git config --global core.eol lf diff --git a/NEWS b/NEWS index 171a437f53c06..b723e8a11662c 100644 --- a/NEWS +++ b/NEWS @@ -81,6 +81,7 @@ PHP NEWS - Windows: . Fixed bug GH-10992 (Improper long path support for relative paths). (cmb, nielsdos) + . Fixed bug GH-16843 (Windows phpize builds ignore source subfolders). (cmb) - XMLWriter: . Improved performance and reduce memory consumption. (nielsdos) diff --git a/UPGRADING b/UPGRADING index 37538739d6af6..6446beec2d868 100644 --- a/UPGRADING +++ b/UPGRADING @@ -28,6 +28,12 @@ PHP 8.5 UPGRADE NOTES - Core: . It is no longer possible to use "array" and "callable" as class alias names in class_alias(). + . Loosely comparing uncomparable objects (e.g. enums, \CurlHandle and other + internal classes) to booleans was previously inconsistent. If compared to a + boolean literal $object == true, it would behave the same way as (bool) + $object. If compared to a statically unknown value $object == $true, it + would always return false. This behavior was consolidated to always follow + the behavior of (bool) $object. - Intl: . The extension now requires at least ICU 57.1. @@ -166,6 +172,10 @@ PHP 8.5 UPGRADE NOTES PHP_RELEASE_VERSION are now always numbers. Previously, they have been strings for buildconf builds. +* phpize builds now reflect the source tree in the build dir (like that already + worked for in-tree builds); some extension builds (especially when using + Makefile.frag.w32) may need adjustments. + * --enable-sanitzer is now supported for MSVC builds. This enables ASan and debug assertions, and is supported as of MSVC 16.10 and Windows 10. diff --git a/Zend/tests/enum/comparison.phpt b/Zend/tests/enum/comparison.phpt index 5df2f282ec064..778e9a48ef5e6 100644 --- a/Zend/tests/enum/comparison.phpt +++ b/Zend/tests/enum/comparison.phpt @@ -53,5 +53,5 @@ bool(false) bool(false) bool(false) bool(false) -bool(false) -bool(false) +bool(true) +bool(true) diff --git a/Zend/tests/gh16954.phpt b/Zend/tests/gh16954.phpt new file mode 100644 index 0000000000000..18a650dd73dcc --- /dev/null +++ b/Zend/tests/gh16954.phpt @@ -0,0 +1,50 @@ +--TEST-- +GH-16954: Enum to bool comparison is inconsistent +--FILE-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(false) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/Zend/tests/property_hooks/oss-fuzz-382922236.phpt b/Zend/tests/property_hooks/oss-fuzz-382922236.phpt new file mode 100644 index 0000000000000..f87b70ea5a7a7 --- /dev/null +++ b/Zend/tests/property_hooks/oss-fuzz-382922236.phpt @@ -0,0 +1,20 @@ +--TEST-- +OSS-Fuzz #382922236: Duplicate dynamic properties in hooked object iterator properties table +--FILE-- + 42; + } +} + +$obj = new C(); +$b = &$obj->b; +unset($b); +echo json_encode($obj); + +?> +--EXPECT-- +{"a":42,"b":null} diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 88e61bba449b4..9533ef8dfaa44 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8598,10 +8598,13 @@ static void zend_compile_property_hooks( ce->num_hooked_props++; + /* See zend_link_hooked_object_iter(). */ +#ifndef ZEND_OPCACHE_SHM_REATTACHMENT if (!ce->get_iterator) { /* Will be removed again, in case of Iterator or IteratorAggregate. */ ce->get_iterator = zend_hooked_object_get_iterator; } +#endif if (!prop_info->ce->parent_name) { zend_verify_hooked_property(ce, prop_info, prop_name); @@ -9127,6 +9130,10 @@ static void zend_compile_class_decl(znode *result, zend_ast *ast, bool toplevel) /* We currently don't early-bind classes that implement interfaces or use traits */ if (!ce->num_interfaces && !ce->num_traits && !ce->num_hooked_prop_variance_checks +#ifdef ZEND_OPCACHE_SHM_REATTACHMENT + /* See zend_link_hooked_object_iter(). */ + && !ce->num_hooked_props +#endif && !(CG(compiler_options) & ZEND_COMPILE_WITHOUT_EXECUTION)) { if (toplevel) { if (extends_ast) { diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index d85bfeb6ae2ab..3557b14bb9740 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -1746,6 +1746,27 @@ ZEND_API inheritance_status zend_verify_property_hook_variance(const zend_proper return zend_perform_covariant_type_check(ce, prop_info->type, ce, value_arg_info->type); } +#ifdef ZEND_OPCACHE_SHM_REATTACHMENT +/* Hooked properties set get_iterator, which causes issues on for shm + * reattachment. Avoid early-binding on Windows and set get_iterator during + * inheritance. The linked class may not use inheritance cache. */ +static void zend_link_hooked_object_iter(zend_class_entry *ce) { + if (!ce->get_iterator && ce->num_hooked_props) { + ce->get_iterator = zend_hooked_object_get_iterator; + ce->ce_flags &= ~ZEND_ACC_CACHEABLE; + if (CG(current_linking_class) == ce) { +# if ZEND_DEBUG + /* This check is executed before inheriting any elements that can + * track dependencies. */ + HashTable *ht = (HashTable*)ce->inheritance_cache; + ZEND_ASSERT(!ht); +# endif + CG(current_linking_class) = NULL; + } + } +} +#endif + ZEND_API void zend_do_inheritance_ex(zend_class_entry *ce, zend_class_entry *parent_ce, bool checked) /* {{{ */ { zend_property_info *property_info; @@ -3422,7 +3443,7 @@ static zend_class_entry *zend_lazy_class_load(zend_class_entry *pce) return ce; } -#ifndef ZEND_WIN32 +#ifndef ZEND_OPCACHE_SHM_REATTACHMENT # define UPDATE_IS_CACHEABLE(ce) do { \ if ((ce)->type == ZEND_USER_CLASS) { \ is_cacheable &= (ce)->ce_flags; \ @@ -3567,6 +3588,10 @@ ZEND_API zend_class_entry *zend_do_link_class(zend_class_entry *ce, zend_string zend_enum_register_funcs(ce); } +#ifdef ZEND_OPCACHE_SHM_REATTACHMENT + zend_link_hooked_object_iter(ce); +#endif + if (parent) { if (!(parent->ce_flags & ZEND_ACC_LINKED)) { add_dependency_obligation(ce, parent); @@ -3855,6 +3880,10 @@ ZEND_API zend_class_entry *zend_try_early_bind(zend_class_entry *ce, zend_class_ zend_begin_record_errors(); } +#ifdef ZEND_OPCACHE_SHM_REATTACHMENT + zend_link_hooked_object_iter(ce); +#endif + zend_do_inheritance_ex(ce, parent_ce, status == INHERITANCE_SUCCESS); if (parent_ce && parent_ce->num_interfaces) { zend_do_inherit_interfaces(ce, parent_ce); diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index f08685092745a..0709d05580dc8 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -2063,8 +2063,9 @@ ZEND_API int zend_std_compare_objects(zval *o1, zval *o2) /* {{{ */ object_lhs = false; } ZEND_ASSERT(Z_TYPE_P(value) != IS_OBJECT); - uint8_t target_type = (Z_TYPE_P(value) == IS_FALSE || Z_TYPE_P(value) == IS_TRUE) - ? _IS_BOOL : Z_TYPE_P(value); + uint8_t target_type = Z_TYPE_P(value); + /* Should be handled in zend_compare(). */ + ZEND_ASSERT(target_type != IS_FALSE && target_type != IS_TRUE); if (Z_OBJ_HT_P(object)->cast_object(Z_OBJ_P(object), &casted, target_type) == FAILURE) { // TODO: Less crazy. if (target_type == IS_LONG || target_type == IS_DOUBLE) { diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index c2fbc0ee110c9..879141d1a139e 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -2333,13 +2333,29 @@ ZEND_API int ZEND_FASTCALL zend_compare(zval *op1, zval *op2) /* {{{ */ } if (Z_TYPE_P(op1) == IS_OBJECT - && Z_TYPE_P(op2) == IS_OBJECT - && Z_OBJ_P(op1) == Z_OBJ_P(op2)) { - return 0; - } else if (Z_TYPE_P(op1) == IS_OBJECT) { - return Z_OBJ_HANDLER_P(op1, compare)(op1, op2); - } else if (Z_TYPE_P(op2) == IS_OBJECT) { - return Z_OBJ_HANDLER_P(op2, compare)(op1, op2); + || Z_TYPE_P(op2) == IS_OBJECT) { + zval *object, *other; + if (Z_TYPE_P(op1) == IS_OBJECT) { + object = op1; + other = op2; + } else { + object = op2; + other = op1; + } + if (EXPECTED(Z_TYPE_P(other) == IS_OBJECT)) { + if (Z_OBJ_P(object) == Z_OBJ_P(other)) { + return 0; + } + } else if (Z_TYPE_P(other) == IS_TRUE || Z_TYPE_P(other) == IS_FALSE) { + zval casted; + if (Z_OBJ_HANDLER_P(object, cast_object)(Z_OBJ_P(object), &casted, _IS_BOOL) == FAILURE) { + return object == op1 ? 1 : -1; + } + int ret = object == op1 ? zend_compare(&casted, other) : zend_compare(other, &casted); + ZEND_ASSERT(!Z_REFCOUNTED_P(&casted)); + return ret; + } + return Z_OBJ_HANDLER_P(object, compare)(op1, op2); } if (!converted) { diff --git a/Zend/zend_portability.h b/Zend/zend_portability.h index 9ab46f9b32cfe..7588f665bc0b3 100644 --- a/Zend/zend_portability.h +++ b/Zend/zend_portability.h @@ -865,4 +865,11 @@ static zend_always_inline uint64_t ZEND_BYTES_SWAP64(uint64_t u) } #endif +#ifdef ZEND_WIN32 +/* Whether it's allowed to reattach to a shm segment from different processes on + * this platform. This prevents pointing to internal structures from shm due to + * ASLR. Currently only possible on Windows. */ +# define ZEND_OPCACHE_SHM_REATTACHMENT 1 +#endif + #endif /* ZEND_PORTABILITY_H */ diff --git a/Zend/zend_property_hooks.c b/Zend/zend_property_hooks.c index 82ddf2f8835a0..d1db597d3a32d 100644 --- a/Zend/zend_property_hooks.c +++ b/Zend/zend_property_hooks.c @@ -44,7 +44,9 @@ static uint32_t zho_num_backed_props(zend_object *zobj) static zend_array *zho_build_properties_ex(zend_object *zobj, bool check_access, bool force_ptr, bool include_dynamic_props) { zend_class_entry *ce = zobj->ce; - zend_array *properties = zend_new_array(ce->default_properties_count); + zend_array *properties = zend_new_array(include_dynamic_props && zobj->properties + ? zend_hash_num_elements(zobj->properties) + : ce->default_properties_count); zend_hash_real_init_mixed(properties); /* Build list of parents */ @@ -105,7 +107,8 @@ static zend_array *zho_build_properties_ex(zend_object *zobj, bool check_access, zend_string *prop_name; zval *prop_value; ZEND_HASH_FOREACH_STR_KEY_VAL_FROM(zobj->properties, prop_name, prop_value, zho_num_backed_props(zobj)) { - Z_TRY_ADDREF_P(_zend_hash_append(properties, prop_name, prop_value)); + zval *tmp = _zend_hash_append(properties, prop_name, prop_value); + Z_TRY_ADDREF_P(tmp); } ZEND_HASH_FOREACH_END(); } diff --git a/ext/opcache/tests/dump_property_hooks.phpt b/ext/opcache/tests/dump_property_hooks.phpt index d8727e873b85f..5083ad385f31f 100644 --- a/ext/opcache/tests/dump_property_hooks.phpt +++ b/ext/opcache/tests/dump_property_hooks.phpt @@ -6,6 +6,12 @@ opcache.enable_cli=1 opcache.opt_debug_level=0x20000 --EXTENSIONS-- opcache +--SKIPIF-- + --FILE-- +--FILE-- + +--EXPECTF-- +float(0.0078125) +bool(true) diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index 1b62b757b87d2..e6642c76e1a93 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -848,7 +848,7 @@ ZEND_FUNCTION(opcache_get_configuration) add_assoc_long(&directives, "opcache.jit_max_recursive_returns", JIT_G(max_recursive_returns)); add_assoc_long(&directives, "opcache.jit_max_root_traces", JIT_G(max_root_traces)); add_assoc_long(&directives, "opcache.jit_max_side_traces", JIT_G(max_side_traces)); - add_assoc_long(&directives, "opcache.jit_prof_threshold", JIT_G(prof_threshold)); + add_assoc_double(&directives, "opcache.jit_prof_threshold", JIT_G(prof_threshold)); add_assoc_long(&directives, "opcache.jit_max_trace_length", JIT_G(max_trace_length)); #endif diff --git a/ext/sockets/sockets.c b/ext/sockets/sockets.c index 08b8d8406daa7..61ab3dc10b7c9 100644 --- a/ext/sockets/sockets.c +++ b/ext/sockets/sockets.c @@ -370,7 +370,11 @@ char *sockets_strerror(int error) /* {{{ */ #ifndef PHP_WIN32 if (error < -10000) { - error = -error - 10000; + if (error == INT_MIN) { + error = 2147473648; + } else { + error = -error - 10000; + } #ifdef HAVE_HSTRERROR buf = hstrerror(error); diff --git a/ext/sockets/tests/gh16267.phpt b/ext/sockets/tests/gh16267.phpt index d2462b3164530..de3e1b657fbc5 100644 --- a/ext/sockets/tests/gh16267.phpt +++ b/ext/sockets/tests/gh16267.phpt @@ -3,20 +3,16 @@ GH-16267 - overflow on socket_strerror argument --EXTENSIONS-- sockets --SKIPIF-- - + --FILE-- getMessage() . PHP_EOL; -} -try { - socket_strerror(PHP_INT_MAX); + socket_strerror(2147483648); } catch (\ValueError $e) { echo $e->getMessage() . PHP_EOL; } ?> --EXPECTF-- -socket_strerror(): Argument #1 ($error_code) must be between %s and %s -socket_strerror(): Argument #1 ($error_code) must be between %s and %s +string(%d) "%S" +socket_strerror(): Argument #1 ($error_code) must be between %i and %d diff --git a/win32/build/confutils.js b/win32/build/confutils.js index 438eb1d239c3f..cf1010266f2c4 100644 --- a/win32/build/confutils.js +++ b/win32/build/confutils.js @@ -1616,8 +1616,15 @@ function ADD_SOURCES(dir, file_list, target, obj_dir) if (obj_dir == null) { if (MODE_PHPIZE) { /* In the phpize mode, the subdirs are always relative to BUID_DIR. - No need to differentiate by extension, only one gets built. */ - var build_dir = (dirname ? dirname : "").replace(new RegExp("^..\\\\"), ""); + No need to differentiate by extension, only one gets built. + We still need to cater to subfolders, though. */ + if (dir.charAt(configure_module_dirname.length) === "\\" && + dir.substr(0, configure_module_dirname.length) === configure_module_dirname) { + var reldir = dir.substr(configure_module_dirname.length + 1); + var build_dir = (dirname ? (reldir + "\\" + dirname) : reldir).replace(new RegExp("^..\\\\"), ""); + } else { + var build_dir = (dirname ? dirname : "").replace(new RegExp("^..\\\\"), ""); + } } else { var build_dir = (dirname ? (dir + "\\" + dirname) : dir).replace(new RegExp("^..\\\\"), ""); }