diff --git a/.github/scripts/windows/build_task.bat b/.github/scripts/windows/build_task.bat index e8d84b8c0bfd6..071c0c28f5ac5 100644 --- a/.github/scripts/windows/build_task.bat +++ b/.github/scripts/windows/build_task.bat @@ -5,11 +5,6 @@ if /i "%GITHUB_ACTIONS%" neq "True" ( exit /b 3 ) -del /f /q C:\Windows\System32\libcrypto-1_1-x64.dll >NUL 2>NUL -if %errorlevel% neq 0 exit /b 3 -del /f /q C:\Windows\System32\libssl-1_1-x64.dll >NUL 2>NUL -if %errorlevel% neq 0 exit /b 3 - call %~dp0find-target-branch.bat set STABILITY=staging set DEPS_DIR=%PHP_BUILD_CACHE_BASE_DIR%\deps-%BRANCH%-%PHP_SDK_VS%-%PHP_SDK_ARCH% diff --git a/.github/scripts/windows/test_task.bat b/.github/scripts/windows/test_task.bat index 79517b5338b84..124b9b019ca4b 100644 --- a/.github/scripts/windows/test_task.bat +++ b/.github/scripts/windows/test_task.bat @@ -136,6 +136,8 @@ for %%i in (ldap) do ( set TEST_PHPDBG_EXECUTABLE=%PHP_BUILD_DIR%\phpdbg.exe +copy /-y %DEPS_DIR%\bin\*.dll %PHP_BUILD_DIR%\* + 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 --bless %PARALLEL%" diff --git a/.github/workflows/nightly.yml b/.github/workflows/nightly.yml index 4c8ec23b158a4..2377286ca830c 100644 --- a/.github/workflows/nightly.yml +++ b/.github/workflows/nightly.yml @@ -520,7 +520,7 @@ jobs: - name: Test Laravel if: ${{ !cancelled() }} run: | - git clone https://github.com/laravel/framework.git --branch=master --depth=1 + git clone https://github.com/laravel/framework.git --depth=1 cd framework git rev-parse HEAD php /usr/bin/composer install --no-progress --ignore-platform-reqs diff --git a/.github/workflows/windows-builds.yml b/.github/workflows/windows-builds.yml new file mode 100644 index 0000000000000..6bb4cd897164d --- /dev/null +++ b/.github/workflows/windows-builds.yml @@ -0,0 +1,23 @@ +name: Windows builds +run-name: Windows builds for ${{ inputs.tag || github.ref_name }} +on: + push: + tags: + - 'php-*' + workflow_dispatch: + inputs: + tag: + description: 'Tag version' + required: true + +jobs: + publish: + runs-on: ubuntu-latest + name: Build + steps: + - name: Build + env: + GITHUB_TOKEN: ${{ secrets.WINDOWS_BUILDS_TOKEN }} + run: | + TAG="${{ inputs.tag || github.ref_name }}" + gh workflow run php.yml -R php/php-windows-builder -f php-version="${TAG#php-}" diff --git a/.gitignore b/.gitignore index 52dde98ed43b6..d8cb25a1a109c 100644 --- a/.gitignore +++ b/.gitignore @@ -290,6 +290,11 @@ tmp-php.ini /junit.out.xml /.ccache/ +# ------------------------------------------------------------------------------ +# Additional test build files +# ------------------------------------------------------------------------------ +/ext/standard/tests/helpers/bad_cmd.exe + # ------------------------------------------------------------------------------ # Special cases to invert previous ignore patterns # ------------------------------------------------------------------------------ diff --git a/NEWS b/NEWS index eb6021800efbd..4e1359700a103 100644 --- a/NEWS +++ b/NEWS @@ -1,6 +1,123 @@ PHP NEWS ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||| -?? ??? ????, PHP 8.4.4 +13 Mar 2025, PHP 8.4.5 + +- BCMath: + . Fixed bug GH-17398 (bcmul memory leak). (SakiTakamachi) + +- Core: + . Fixed bug GH-17623 (Broken stack overflow detection for variable + compilation). (ilutov) + . Fixed bug GH-17618 (UnhandledMatchError does not take + zend.exception_ignore_args=1 into account). (timwolla) + . Fix fallback paths in fast_long_{add,sub}_function. (nielsdos) + . Fixed bug OSS-Fuzz #391975641 (Crash when accessing property backing value + by reference). (ilutov) + . Fixed bug GH-17718 (Calling static methods on an interface that has + `__callStatic` is allowed). (timwolla) + . Fixed bug GH-17713 (ReflectionProperty::getRawValue() and related methods + may call hooks of overridden properties). (Arnaud) + . Fixed bug GH-17916 (Final abstract properties should error). + (DanielEScherzer) + . Fixed bug GH-17866 (zend_mm_heap corrupted error after upgrading from + 8.4.3 to 8.4.4). (nielsdos) + . Fixed GHSA-rwp7-7vc6-8477 (Reference counting in php_request_shutdown + causes Use-After-Free). (CVE-2024-11235) (ilutov) + +- DOM: + . Fixed bug GH-17609 (Typo in error message: Dom\NO_DEFAULT_NS instead of + Dom\HTML_NO_DEFAULT_NS). (nielsdos) + . Fixed bug GH-17802 (\Dom\HTMLDocument querySelector attribute name is case + sensitive in HTML). (nielsdos) + . Fixed bug GH-17847 (xinclude destroys live node). (nielsdos) + . Fix using Dom\Node with Dom\XPath callbacks. (nielsdos) + +- GD: + . Fixed bug GH-17703 (imagescale with both width and height negative values + triggers only an Exception on width). (David Carlier) + +- FFI: + . Fix FFI Parsing of Pointer Declaration Lists. (davnotdev) + +- FPM: + . Fixed bug GH-17643 (FPM with httpd ProxyPass encoded PATH_INFO env). + (Jakub Zelenka) + +- GD: + . Fixed bug GH-17772 (imagepalettetotruecolor crash with memory_limit=2M). + (David Carlier) + +- LDAP: + . Fixed bug GH-17704 (ldap_search fails when $attributes contains a + non-packed array with numerical keys). (nielsdos, 7u83) + +- LibXML: + . Fixed GHSA-wg4p-4hqh-c3g9 (Reocurrence of #72714). (nielsdos) + . Fixed GHSA-p3x9-6h7p-cgfc (libxml streams use wrong `content-type` header + when requesting a redirected resource). (CVE-2025-1219) (timwolla) + +- MBString: + . Fixed bug GH-17503 (Undefined float conversion in mb_convert_variables). + (cmb) + +- Opcache: + . Fixed bug GH-17654 (Multiple classes using same trait causes function + JIT crash). (nielsdos) + . Fixed bug GH-17577 (JIT packed type guard crash). (nielsdos, Dmitry) + . Fixed bug GH-17747 (Exception on reading property in register-based + FETCH_OBJ_R breaks JIT). (Dmitry, nielsdos) + . Fixed bug GH-17715 (Null pointer deref in observer API when calling + cases() method on preloaded enum). (Bob) + . Fixed bug GH-17868 (Cannot allocate memory with tracing JIT on 8.4.4). + (nielsdos) + +- PDO_SQLite: + . Fixed GH-17837 ()::getColumnMeta() on unexecuted statement segfaults). + (cmb) + . Fix cycle leak in sqlite3 setAuthorizer(). (nielsdos) + . Fix memory leaks in pdo_sqlite callback registration. (nielsdos) + +- Phar: + . Fixed bug GH-17808: PharFileInfo refcount bug. (nielsdos) + +- PHPDBG: + . Partially fixed bug GH-17387 (Trivial crash in phpdbg lexer). (nielsdos) + . Fix memory leak in phpdbg calling registered function. (nielsdos) + +- Reflection: + . Fixed bug GH-15902 (Core dumped in ext/reflection/php_reflection.c). + (DanielEScherzer) + . Fixed missing final and abstract flags when dumping properties. + (DanielEScherzer) + +- Standard: + . Fixed bug #72666 (stat cache clearing inconsistent between file:// paths + and plain paths). (Jakub Zelenka) + +- Streams: + . Fixed bug GH-17650 (realloc with size 0 in user_filters.c). (nielsdos) + . Fix memory leak on overflow in _php_stream_scandir(). (nielsdos) + . Fixed GHSA-hgf54-96fm-v528 (Stream HTTP wrapper header check might omit + basic auth header). (CVE-2025-1736) (Jakub Zelenka) + . Fixed GHSA-52jp-hrpf-2jff (Stream HTTP wrapper truncate redirect location + to 1024 bytes). (CVE-2025-1861) (Jakub Zelenka) + . Fixed GHSA-pcmh-g36c-qc44 (Streams HTTP wrapper does not fail for headers + without colon). (CVE-2025-1734) (Jakub Zelenka) + . Fixed GHSA-v8xr-gpvj-cx9g (Header parser of `http` stream wrapper does not + handle folded headers). (CVE-2025-1217) (Jakub Zelenka) + +- Windows: + . Fixed phpize for Windows 11 (24H2). (Bob) + . Fixed GH-17855 (CURL_STATICLIB flag set even if linked with shared lib). + (cmb) + +- Zlib: + . Fixed bug GH-17745 (zlib extension incorrectly handles object arguments). + (nielsdos) + . Fix memory leak when encoding check fails. (nielsdos) + . Fix zlib support for large files. (nielsdos) + +30 Jan 2025, PHP 8.4.4 - Core: . Fixed bug GH-17234 (Numeric parent hook call fails with assertion). @@ -57,7 +174,7 @@ PHP NEWS - Intl: . Fixed bug GH-11874 (intl causing segfault in docker images). (nielsdos) - + - Opcache: . Fixed bug GH-15981 (Segfault with frameless jumps and minimal JIT). (nielsdos) diff --git a/UPGRADING b/UPGRADING index 3bab45da53e74..a67fc1b1d88fd 100644 --- a/UPGRADING +++ b/UPGRADING @@ -627,6 +627,9 @@ PHP 8.4 UPGRADE NOTES . DOMDocument::registerNodeClass() now has a tentative return type of true. Previously, the return type was bool but only true could be returned in practice. +- GD: + . imagescale now throws a ValueError when both width and height arguments are negative. + - Hash: . Changed the return type of hash_update() to true. It was already the case that only true could be returned, but the stub was not updated yet. diff --git a/Zend/tests/attributes/deprecated/functions/gh17866.phpt b/Zend/tests/attributes/deprecated/functions/gh17866.phpt new file mode 100644 index 0000000000000..8786b49c602f2 --- /dev/null +++ b/Zend/tests/attributes/deprecated/functions/gh17866.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-17866 (zend_mm_heap corrupted error after upgrading from 8.4.3 to 8.4.4) +--FILE-- +__invoke(...); + +$rc = new ReflectionMethod($test, '__invoke'); +var_dump($rc->getAttributes()); +var_dump($rc->isDeprecated()); + +$test(); + +?> +--EXPECTF-- +array(1) { + [0]=> + object(ReflectionAttribute)#%d (1) { + ["name"]=> + string(10) "Deprecated" + } +} +bool(true) + +Deprecated: Method Foo::__invoke() is deprecated, xyzzy in %s on line %d +In __invoke diff --git a/Zend/tests/gh_17718_001.phpt b/Zend/tests/gh_17718_001.phpt new file mode 100644 index 0000000000000..1f5bee961eb71 --- /dev/null +++ b/Zend/tests/gh_17718_001.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-17718: Disallow calling abstract `__callStatic()` trampoline on an interface +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot call abstract method Foo::bar() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/gh_17718_002.phpt b/Zend/tests/gh_17718_002.phpt new file mode 100644 index 0000000000000..f3f75fca40f72 --- /dev/null +++ b/Zend/tests/gh_17718_002.phpt @@ -0,0 +1,17 @@ +--TEST-- +GH-17718: Disallow calling abstract `__callStatic()` trampoline on an abstract class +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Cannot call abstract method Foo::bar() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/gh_17728.phpt b/Zend/tests/gh_17728.phpt new file mode 100644 index 0000000000000..ef458a8e8d5e1 --- /dev/null +++ b/Zend/tests/gh_17728.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-17728: Assertion failure when calling static method of trait with `__callStatic()` with throwing error handler +--FILE-- +getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Calling static trait method Foo::bar is deprecated, it should only be called on a class using the trait diff --git a/Zend/tests/ghsa-rwp7-7vc6-8477_001.phpt b/Zend/tests/ghsa-rwp7-7vc6-8477_001.phpt new file mode 100644 index 0000000000000..d0e9ddcba410b --- /dev/null +++ b/Zend/tests/ghsa-rwp7-7vc6-8477_001.phpt @@ -0,0 +1,26 @@ +--TEST-- +GHSA-rwp7-7vc6-8477: Use-after-free for ??= due to incorrect live-range calculation +--FILE-- +foo()->baz ??= 1; +} catch (Exception $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +Hello diff --git a/Zend/tests/ghsa-rwp7-7vc6-8477_002.phpt b/Zend/tests/ghsa-rwp7-7vc6-8477_002.phpt new file mode 100644 index 0000000000000..4115d9eae26fd --- /dev/null +++ b/Zend/tests/ghsa-rwp7-7vc6-8477_002.phpt @@ -0,0 +1,24 @@ +--TEST-- +GHSA-rwp7-7vc6-8477: Use-after-free for ??= due to incorrect live-range calculation +--FILE-- +foo()->prop ??= 'foo'; +} catch (Error $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +Cannot assign string to property Foo::$prop of type int diff --git a/Zend/tests/ghsa-rwp7-7vc6-8477_003.phpt b/Zend/tests/ghsa-rwp7-7vc6-8477_003.phpt new file mode 100644 index 0000000000000..f2808afbf84de --- /dev/null +++ b/Zend/tests/ghsa-rwp7-7vc6-8477_003.phpt @@ -0,0 +1,22 @@ +--TEST-- +GHSA-rwp7-7vc6-8477: Use-after-free for ??= due to incorrect live-range calculation +--FILE-- +prop ??= 'foo'; +} catch (Error $e) { + echo $e->getMessage(); +} + +?> +--EXPECT-- +Cannot assign string to property Foo::$prop of type int diff --git a/Zend/tests/lazy_objects/skipLazyInitialization.phpt b/Zend/tests/lazy_objects/skipLazyInitialization.phpt index faff902d0e418..d4d564d9c6532 100644 --- a/Zend/tests/lazy_objects/skipLazyInitialization.phpt +++ b/Zend/tests/lazy_objects/skipLazyInitialization.phpt @@ -198,10 +198,10 @@ getValue(): string(5) "value" ## Property [ public static $static = 'static' ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on static property B::$static +ReflectionException: Can not use skipLazyInitialization on static property A::$static setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property B::$static +ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property A::$static ## Property [ public $noDefault = NULL ] @@ -238,10 +238,10 @@ getValue(): string(5) "value" ## Property [ public $virtual ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on virtual property B::$virtual +ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property B::$virtual +ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property A::$virtual ## Property [ $dynamicProp ] @@ -295,10 +295,10 @@ getValue(): string(5) "value" ## Property [ public static $static = 'static' ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on static property B::$static +ReflectionException: Can not use skipLazyInitialization on static property A::$static setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property B::$static +ReflectionException: Can not use setRawValueWithoutLazyInitialization on static property A::$static ## Property [ public $noDefault = NULL ] @@ -335,10 +335,10 @@ getValue(): string(5) "value" ## Property [ public $virtual ] skipInitializerForProperty(): -ReflectionException: Can not use skipLazyInitialization on virtual property B::$virtual +ReflectionException: Can not use skipLazyInitialization on virtual property A::$virtual setRawValueWithoutLazyInitialization(): -ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property B::$virtual +ReflectionException: Can not use setRawValueWithoutLazyInitialization on virtual property A::$virtual ## Property [ $dynamicProp ] diff --git a/Zend/tests/match/049.phpt b/Zend/tests/match/049.phpt new file mode 100644 index 0000000000000..2a2385f704ff4 --- /dev/null +++ b/Zend/tests/match/049.phpt @@ -0,0 +1,42 @@ +--TEST-- +Match expression error messages (zend.exception_ignore_args=1) +--INI-- +zend.exception_ignore_args=1 +--FILE-- +getMessage() . PHP_EOL; + } +} + +test(null); +test(1); +test(5.5); +test(5.0); +test("foo"); +test(true); +test(false); +test([1, 2, 3]); +test(new Beep()); +// Testing long strings. +test(str_repeat('e', 100)); +test(str_repeat("e\n", 100)); +?> +--EXPECT-- +Unhandled match case of type null +Unhandled match case of type int +Unhandled match case of type float +Unhandled match case of type float +Unhandled match case of type string +Unhandled match case of type bool +Unhandled match case of type bool +Unhandled match case of type array +Unhandled match case of type Beep +Unhandled match case of type string +Unhandled match case of type string diff --git a/Zend/tests/oss-fuzz-391975641.phpt b/Zend/tests/oss-fuzz-391975641.phpt new file mode 100644 index 0000000000000..586457aeac591 --- /dev/null +++ b/Zend/tests/oss-fuzz-391975641.phpt @@ -0,0 +1,22 @@ +--TEST-- +OSS-Fuzz #391975641: Segfault when creating reference from backing value +--FILE-- + $this->prop; + set { + $this->prop = &$value; + $value = &$this->prop; + } + } +} + +$c = new C; +$c->prop = 1; +var_dump($c->prop); + +?> +--EXPECT-- +int(1) diff --git a/Zend/tests/property_hooks/abstract_prop_final.phpt b/Zend/tests/property_hooks/abstract_prop_final.phpt new file mode 100644 index 0000000000000..17f2282507f1f --- /dev/null +++ b/Zend/tests/property_hooks/abstract_prop_final.phpt @@ -0,0 +1,14 @@ +--TEST-- +GH-17916: Abstract property cannot be marked as final +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use the final modifier on an abstract property in %s on line %d diff --git a/Zend/tests/stack_limit/stack_limit_015.phpt b/Zend/tests/stack_limit/stack_limit_015.phpt new file mode 100644 index 0000000000000..b725523b7840a --- /dev/null +++ b/Zend/tests/stack_limit/stack_limit_015.phpt @@ -0,0 +1,71 @@ +--TEST-- +Stack limit 015 - Internal stack limit check in zend_compile_var() +--CREDITS-- +abdullahasif88 +--SKIPIF-- + +--EXTENSIONS-- +zend_test +--INI-- +zend.max_allowed_stack_size=128K +--FILE-- +p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p + ->p->p->p->p->p->p->p->p->p->p +; + +?> +--EXPECTF-- +Fatal error: Maximum call stack size of %d bytes (zend.max_allowed_stack_size - zend.reserved_stack_size) reached during compilation. Try splitting expression in %s on line %d diff --git a/Zend/zend.h b/Zend/zend.h index 03f59be376e27..bbca84072a9e1 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -20,7 +20,7 @@ #ifndef ZEND_H #define ZEND_H -#define ZEND_VERSION "4.4.4-dev" +#define ZEND_VERSION "4.4.5" #define ZEND_ENGINE_3 diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 629aa7d51a840..049c026845b1c 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -464,7 +464,7 @@ ZEND_API zend_function *zend_get_closure_invoke_method(zend_object *object) /* { zend_closure *closure = (zend_closure *)object; zend_function *invoke = (zend_function*)emalloc(sizeof(zend_function)); const uint32_t keep_flags = - ZEND_ACC_RETURN_REFERENCE | ZEND_ACC_VARIADIC | ZEND_ACC_HAS_RETURN_TYPE; + ZEND_ACC_RETURN_REFERENCE | ZEND_ACC_VARIADIC | ZEND_ACC_HAS_RETURN_TYPE | ZEND_ACC_DEPRECATED; invoke->common = closure->func.common; /* We return ZEND_INTERNAL_FUNCTION, but arg_info representation is the diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 6c5d6d0d3c456..ef75b45ad0528 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -1036,10 +1036,17 @@ uint32_t zend_add_member_modifier(uint32_t flags, uint32_t new_flag, zend_modifi "Multiple readonly modifiers are not allowed", 0); return 0; } - if (target == ZEND_MODIFIER_TARGET_METHOD && (new_flags & ZEND_ACC_ABSTRACT) && (new_flags & ZEND_ACC_FINAL)) { - zend_throw_exception(zend_ce_compile_error, - "Cannot use the final modifier on an abstract method", 0); - return 0; + if ((new_flags & ZEND_ACC_ABSTRACT) && (new_flags & ZEND_ACC_FINAL)) { + if (target == ZEND_MODIFIER_TARGET_METHOD) { + zend_throw_exception(zend_ce_compile_error, + "Cannot use the final modifier on an abstract method", 0); + return 0; + } + if (target == ZEND_MODIFIER_TARGET_PROPERTY) { + zend_throw_exception(zend_ce_compile_error, + "Cannot use the final modifier on an abstract property", 0); + return 0; + } } if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) { if ((flags & ZEND_ACC_PPP_SET_MASK) && (new_flag & ZEND_ACC_PPP_SET_MASK)) { @@ -11633,6 +11640,8 @@ static zend_op *zend_compile_var_inner(znode *result, zend_ast *ast, uint32_t ty static zend_op *zend_compile_var(znode *result, zend_ast *ast, uint32_t type, bool by_ref) /* {{{ */ { + zend_check_stack_limit(); + uint32_t checkpoint = zend_short_circuiting_checkpoint(); zend_op *opcode = zend_compile_var_inner(result, ast, type, by_ref); zend_short_circuiting_commit(checkpoint, result, ast); @@ -11641,6 +11650,8 @@ static zend_op *zend_compile_var(znode *result, zend_ast *ast, uint32_t type, bo static zend_op *zend_delayed_compile_var(znode *result, zend_ast *ast, uint32_t type, bool by_ref) /* {{{ */ { + zend_check_stack_limit(); + switch (ast->kind) { case ZEND_AST_VAR: return zend_compile_simple_var(result, ast, type, 1); diff --git a/Zend/zend_enum.c b/Zend/zend_enum.c index 8259441fd4228..ccafca48fe9b8 100644 --- a/Zend/zend_enum.c +++ b/Zend/zend_enum.c @@ -418,7 +418,10 @@ static void zend_enum_register_func(zend_class_entry *ce, zend_known_string_id n zif->module = EG(current_module); zif->scope = ce; zif->T = ZEND_OBSERVER_ENABLED; - if (EG(active)) { // at run-time + if (EG(active)) { // at run-time + if (CG(compiler_options) & ZEND_COMPILE_PRELOAD) { + zif->fn_flags |= ZEND_ACC_PRELOADED; + } ZEND_MAP_PTR_INIT(zif->run_time_cache, zend_arena_calloc(&CG(arena), 1, zend_internal_run_time_cache_reserved_size())); } else { #ifdef ZTS diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index e425e6625e4c5..0f8b062e3f046 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -875,7 +875,10 @@ ZEND_COLD zend_never_inline void zend_magic_get_property_type_inconsistency_erro ZEND_COLD void zend_match_unhandled_error(const zval *value) { smart_str msg = {0}; - if (smart_str_append_zval(&msg, value, EG(exception_string_param_max_len)) != SUCCESS) { + if ( + EG(exception_ignore_args) + || smart_str_append_zval(&msg, value, EG(exception_string_param_max_len)) != SUCCESS + ) { smart_str_appendl(&msg, "of type ", sizeof("of type ")-1); smart_str_appends(&msg, zend_zval_type_name(value)); } @@ -3487,7 +3490,7 @@ static zend_always_inline void zend_assign_to_property_reference(zval *container variable_ptr = zend_wrong_assign_to_variable_reference( variable_ptr, value_ptr, &garbage OPLINE_CC EXECUTE_DATA_CC); - } else if (prop_info) { + } else if (prop_info && ZEND_TYPE_IS_SET(prop_info->type)) { variable_ptr = zend_assign_to_typed_property_reference(prop_info, variable_ptr, value_ptr, &garbage EXECUTE_DATA_CC); } else { zend_assign_to_variable_reference(variable_ptr, value_ptr, &garbage); diff --git a/Zend/zend_map_ptr.h b/Zend/zend_map_ptr.h index ebcda89411d0e..4dfa0e5043ef4 100644 --- a/Zend/zend_map_ptr.h +++ b/Zend/zend_map_ptr.h @@ -70,6 +70,9 @@ typedef struct _zend_string zend_string; } while (0) # define ZEND_MAP_PTR_BIASED_BASE(real_base) \ ((void*)(((uintptr_t)(real_base)) + zend_map_ptr_static_size * sizeof(void *) - 1)) +/* Note: chunked like: [8192..12287][4096..8191][0..4095] */ +#define ZEND_MAP_PTR_STATIC_NUM_TO_PTR(num) \ + ((void **)CG(map_ptr_real_base) + zend_map_ptr_static_size - ZEND_MM_ALIGNED_SIZE_EX((num) + 1, 4096) + ((num) & 4095)) #else # error "Unknown ZEND_MAP_PTR_KIND" #endif diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 68d21d25253a4..5a4e4b3ea3a1c 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1617,13 +1617,9 @@ ZEND_API zend_function *zend_get_call_trampoline_func(const zend_class_entry *ce func->fn_flags = ZEND_ACC_CALL_VIA_TRAMPOLINE | ZEND_ACC_PUBLIC | ZEND_ACC_VARIADIC - | (fbc->common.fn_flags & (ZEND_ACC_RETURN_REFERENCE|ZEND_ACC_DEPRECATED)); - if (fbc->common.attributes) { - func->attributes = fbc->common.attributes; - GC_TRY_ADDREF(func->attributes); - } else { - func->attributes = NULL; - } + | (fbc->common.fn_flags & (ZEND_ACC_RETURN_REFERENCE|ZEND_ACC_ABSTRACT|ZEND_ACC_DEPRECATED)); + /* Attributes outlive the trampoline because they are created by the compiler. */ + func->attributes = fbc->common.attributes; if (is_static) { func->fn_flags |= ZEND_ACC_STATIC; } @@ -1898,19 +1894,27 @@ ZEND_API zend_function *zend_std_get_static_method(zend_class_entry *ce, zend_st if (EXPECTED(fbc)) { if (UNEXPECTED(fbc->common.fn_flags & ZEND_ACC_ABSTRACT)) { zend_abstract_method_call(fbc); - fbc = NULL; + goto fail; } else if (UNEXPECTED(fbc->common.scope->ce_flags & ZEND_ACC_TRAIT)) { zend_error(E_DEPRECATED, "Calling static trait method %s::%s is deprecated, " "it should only be called on a class using the trait", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name)); if (EG(exception)) { - return NULL; + goto fail; } } } return fbc; + + fail: + if (UNEXPECTED(fbc->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)) { + zend_string_release_ex(fbc->common.function_name, 0); + zend_free_trampoline(fbc); + } + + return NULL; } /* }}} */ diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index 2fc59d5020c25..c7348a7424558 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -339,10 +339,6 @@ ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_p } while (0) #define zend_free_trampoline(func) do { \ - HashTable *attributes = (func)->common.attributes; \ - if (attributes && !(GC_FLAGS(attributes) & GC_IMMUTABLE) && !GC_DELREF(attributes)) { \ - zend_array_destroy(attributes); \ - } \ if ((func) == &EG(trampoline)) { \ EG(trampoline).common.attributes = NULL; \ EG(trampoline).common.function_name = NULL; \ diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 12c2759656b2d..f32ae13e06793 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -940,6 +940,14 @@ static void zend_calc_live_ranges( opnum--; opline--; + /* SEPARATE always redeclares its op1. For the purposes of live-ranges, + * its declaration is irrelevant. Don't terminate the current live-range + * to avoid breaking special handling of COPY_TMP. */ + if (opline->opcode == ZEND_SEPARATE) { + ZEND_ASSERT(opline->op1.var == opline->result.var); + continue; + } + if ((opline->result_type & (IS_TMP_VAR|IS_VAR)) && !is_fake_def(opline)) { uint32_t var_num = EX_VAR_TO_NUM(opline->result.var) - var_offset; /* Defs without uses can occur for two reasons: Either because the result is diff --git a/Zend/zend_operators.h b/Zend/zend_operators.h index f8c901b149806..6a3d54a9dbf2e 100644 --- a/Zend/zend_operators.h +++ b/Zend/zend_operators.h @@ -731,11 +731,13 @@ overflow: ZEND_ATTRIBUTE_COLD_LABEL * have read the values of op1 and op2. */ + zend_long sum = (zend_long) ((zend_ulong) Z_LVAL_P(op1) + (zend_ulong) Z_LVAL_P(op2)); + if (UNEXPECTED((Z_LVAL_P(op1) & LONG_SIGN_MASK) == (Z_LVAL_P(op2) & LONG_SIGN_MASK) - && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != ((Z_LVAL_P(op1) + Z_LVAL_P(op2)) & LONG_SIGN_MASK))) { + && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != (sum & LONG_SIGN_MASK))) { ZVAL_DOUBLE(result, (double) Z_LVAL_P(op1) + (double) Z_LVAL_P(op2)); } else { - ZVAL_LONG(result, Z_LVAL_P(op1) + Z_LVAL_P(op2)); + ZVAL_LONG(result, sum); } #endif } @@ -813,11 +815,19 @@ overflow: ZEND_ATTRIBUTE_COLD_LABEL ZVAL_LONG(result, llresult); } #else - ZVAL_LONG(result, Z_LVAL_P(op1) - Z_LVAL_P(op2)); + /* + * 'result' may alias with op1 or op2, so we need to + * ensure that 'result' is not updated until after we + * have read the values of op1 and op2. + */ + + zend_long sub = (zend_long) ((zend_ulong) Z_LVAL_P(op1) - (zend_ulong) Z_LVAL_P(op2)); if (UNEXPECTED((Z_LVAL_P(op1) & LONG_SIGN_MASK) != (Z_LVAL_P(op2) & LONG_SIGN_MASK) - && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != (Z_LVAL_P(result) & LONG_SIGN_MASK))) { + && (Z_LVAL_P(op1) & LONG_SIGN_MASK) != (sub & LONG_SIGN_MASK))) { ZVAL_DOUBLE(result, (double) Z_LVAL_P(op1) - (double) Z_LVAL_P(op2)); + } else { + ZVAL_LONG(result, sub); } #endif } diff --git a/configure.ac b/configure.ac index dd68b78047fc1..e3d226e32c624 100644 --- a/configure.ac +++ b/configure.ac @@ -17,7 +17,7 @@ dnl Basic autoconf initialization, generation of config.nice. dnl ---------------------------------------------------------------------------- AC_PREREQ([2.68]) -AC_INIT([PHP],[8.4.4-dev],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) +AC_INIT([PHP],[8.4.5],[https://github.com/php/php-src/issues],[php],[https://www.php.net]) AC_CONFIG_SRCDIR([main/php_version.h]) AC_CONFIG_AUX_DIR([build]) AC_PRESERVE_HELP_ORDER diff --git a/ext/bcmath/bcmath.c b/ext/bcmath/bcmath.c index 832d2758fe968..233045bd7cd7e 100644 --- a/ext/bcmath/bcmath.c +++ b/ext/bcmath/bcmath.c @@ -109,9 +109,9 @@ static PHP_GINIT_FUNCTION(bcmath) /* {{{ PHP_GSHUTDOWN_FUNCTION */ static PHP_GSHUTDOWN_FUNCTION(bcmath) { - _bc_free_num_ex(&bcmath_globals->_zero_, 1); - _bc_free_num_ex(&bcmath_globals->_one_, 1); - _bc_free_num_ex(&bcmath_globals->_two_, 1); + bc_force_free_number(&bcmath_globals->_zero_); + bc_force_free_number(&bcmath_globals->_one_); + bc_force_free_number(&bcmath_globals->_two_); bcmath_globals->arena = NULL; bcmath_globals->arena_offset = 0; } diff --git a/ext/bcmath/libbcmath/src/bcmath.h b/ext/bcmath/libbcmath/src/bcmath.h index 9a4d1c9b4c355..214d616d0acb3 100644 --- a/ext/bcmath/libbcmath/src/bcmath.h +++ b/ext/bcmath/libbcmath/src/bcmath.h @@ -83,6 +83,8 @@ typedef struct bc_struct { void bc_init_numbers(void); +void bc_force_free_number(bc_num *num); + bc_num _bc_new_num_ex(size_t length, size_t scale, bool persistent); bc_num _bc_new_num_nonzeroed_ex(size_t length, size_t scale, bool persistent); diff --git a/ext/bcmath/libbcmath/src/init.c b/ext/bcmath/libbcmath/src/init.c index 18efc1c39a2b2..beaa14ea8c4d2 100644 --- a/ext/bcmath/libbcmath/src/init.c +++ b/ext/bcmath/libbcmath/src/init.c @@ -97,6 +97,12 @@ void bc_init_numbers(void) BCG(_two_)->n_value[0] = 2; } +void bc_force_free_number(bc_num *num) +{ + pefree(*num, 1); + *num = NULL; +} + /* Initialize a number NUM by making it a copy of zero. */ void bc_init_num(bc_num *num) diff --git a/ext/bcmath/tests/gh17398.phpt b/ext/bcmath/tests/gh17398.phpt new file mode 100644 index 0000000000000..6a0cda09ec76f --- /dev/null +++ b/ext/bcmath/tests/gh17398.phpt @@ -0,0 +1,10 @@ +--TEST-- +GH-17398 (bcmul memory leak) +--EXTENSIONS-- +bcmath +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d diff --git a/ext/curl/config.w32 b/ext/curl/config.w32 index 82be5212e56b3..407659cf6517b 100644 --- a/ext/curl/config.w32 +++ b/ext/curl/config.w32 @@ -13,7 +13,8 @@ if (PHP_CURL != "no") { } } - if (CHECK_LIB("libcurl_a.lib;libcurl.lib", "curl", PHP_CURL) && + var curl_location; + if ((curl_location = CHECK_LIB("libcurl_a.lib;libcurl.lib", "curl", PHP_CURL)) && CHECK_HEADER_ADD_INCLUDE("curl/easy.h", "CFLAGS_CURL") && SETUP_OPENSSL("curl", PHP_CURL) >= 2 && CHECK_LIB("winmm.lib", "curl", PHP_CURL) && @@ -27,7 +28,10 @@ if (PHP_CURL != "no") { ) { EXTENSION("curl", "interface.c multi.c share.c curl_file.c"); AC_DEFINE('HAVE_CURL', 1, "Define to 1 if the PHP extension 'curl' is available."); - ADD_FLAG("CFLAGS_CURL", "/D CURL_STATICLIB /D PHP_CURL_EXPORTS=1"); + ADD_FLAG("CFLAGS_CURL", "/D PHP_CURL_EXPORTS=1"); + if (curl_location.match(/libcurl_a\.lib$/)) { + ADD_FLAG("CFLAGS_CURL", "/D CURL_STATICLIB"); + } PHP_INSTALL_HEADERS("ext/curl", "php_curl.h"); } else { WARNING("curl not enabled; libraries and headers not found"); diff --git a/ext/curl/tests/check_win_config.phpt b/ext/curl/tests/check_win_config.phpt index b3beb044a7cfc..fc29e37281970 100644 --- a/ext/curl/tests/check_win_config.phpt +++ b/ext/curl/tests/check_win_config.phpt @@ -54,7 +54,7 @@ UNICODE => No ZSTD => No HSTS => Yes GSASL => No -Protocols => dict, file, ftp, ftps, gopher, %r(gophers, )?%rhttp, https, imap, imaps, ldap, ldaps, %r(mqtt, )?%rpop3, pop3s, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp +Protocols => dict, file, ftp, ftps, gopher, %r(gophers, )?%rhttp, https, imap, imaps, ldap, ldaps, %r(mqtt, )?%rpop3, pop3s, rtsp, scp, sftp, smb, smbs, smtp, smtps, telnet, tftp%r(, ws)?(, wss)?%r Host => %s-pc-win32 SSL Version => OpenSSL/%s ZLib Version => %s diff --git a/ext/curl/tests/curl_basic_022.phpt b/ext/curl/tests/curl_basic_022.phpt index e905dfd885d16..4a2177e06bd47 100644 --- a/ext/curl/tests/curl_basic_022.phpt +++ b/ext/curl/tests/curl_basic_022.phpt @@ -11,10 +11,10 @@ curl_setopt($ch, CURLOPT_COOKIELIST, 'Set-Cookie: C2=v2; expires=Thu, 31-Dec-203 var_dump(curl_getinfo($ch, CURLINFO_COOKIELIST)); ?> ---EXPECT-- +--EXPECTF-- array(2) { [0]=> - string(38) ".php.net TRUE / FALSE 2145916799 C1 v1" + string(38) ".php.net TRUE / FALSE %d C1 v1" [1]=> - string(38) ".php.net TRUE / FALSE 2145916799 C2 v2" + string(38) ".php.net TRUE / FALSE %d C2 v2" } diff --git a/ext/dom/document.c b/ext/dom/document.c index 3193d2478f42b..aed65630d2563 100644 --- a/ext/dom/document.c +++ b/ext/dom/document.c @@ -1669,47 +1669,6 @@ PHP_METHOD(Dom_XMLDocument, saveXml) } /* }}} end dom_document_savexml */ -static xmlNodePtr php_dom_free_xinclude_node(xmlNodePtr cur) /* {{{ */ -{ - xmlNodePtr xincnode; - - xincnode = cur; - cur = cur->next; - xmlUnlinkNode(xincnode); - php_libxml_node_free_resource(xincnode); - - return cur; -} -/* }}} */ - -static void php_dom_remove_xinclude_nodes(xmlNodePtr cur) /* {{{ */ -{ - while(cur) { - if (cur->type == XML_XINCLUDE_START) { - cur = php_dom_free_xinclude_node(cur); - - /* XML_XINCLUDE_END node will be a sibling of XML_XINCLUDE_START */ - while(cur && cur->type != XML_XINCLUDE_END) { - /* remove xinclude processing nodes from recursive xincludes */ - if (cur->type == XML_ELEMENT_NODE) { - php_dom_remove_xinclude_nodes(cur->children); - } - cur = cur->next; - } - - if (cur && cur->type == XML_XINCLUDE_END) { - cur = php_dom_free_xinclude_node(cur); - } - } else { - if (cur->type == XML_ELEMENT_NODE) { - php_dom_remove_xinclude_nodes(cur->children); - } - cur = cur->next; - } - } -} -/* }}} */ - static void dom_xinclude_strip_references(xmlNodePtr basep) { php_libxml_node_free_resource(basep); @@ -1722,17 +1681,19 @@ static void dom_xinclude_strip_references(xmlNodePtr basep) } } -/* See GH-14702. - * We have to remove userland references to xinclude fallback nodes because libxml2 will make clones of these +/* See GH-14702 and GH-17847. + * We have to remove userland references to xinclude nodes because libxml2 will make clones of these * and remove the original nodes. If the originals are removed while there are still userland references * this will cause memory corruption. */ static void dom_xinclude_strip_fallback_references(const xmlNode *basep) { xmlNodePtr current = basep->children; + /* TODO: try to improve loop search performance */ while (current) { - if (current->type == XML_ELEMENT_NODE && current->ns != NULL && current->_private != NULL - && xmlStrEqual(current->name, XINCLUDE_FALLBACK) + if (current->type == XML_ELEMENT_NODE + && current->ns != NULL + && xmlStrEqual(current->name, XINCLUDE_NODE) && (xmlStrEqual(current->ns->href, XINCLUDE_NS) || xmlStrEqual(current->ns->href, XINCLUDE_OLD_NS))) { dom_xinclude_strip_references(current); } @@ -1745,22 +1706,11 @@ static int dom_perform_xinclude(xmlDocPtr docp, dom_object *intern, zend_long fl { dom_xinclude_strip_fallback_references((const xmlNode *) docp); + flags |= XML_PARSE_NOXINCNODE; PHP_LIBXML_SANITIZE_GLOBALS(xinclude); int err = xmlXIncludeProcessFlags(docp, (int)flags); PHP_LIBXML_RESTORE_GLOBALS(xinclude); - /* XML_XINCLUDE_START and XML_XINCLUDE_END nodes need to be removed as these - are added via xmlXIncludeProcess to mark beginning and ending of xincluded document - but are not wanted in resulting document - must be done even if err as it could fail after - having processed some xincludes */ - xmlNodePtr root = docp->children; - while (root && root->type != XML_ELEMENT_NODE && root->type != XML_XINCLUDE_START) { - root = root->next; - } - if (root) { - php_dom_remove_xinclude_nodes(root); - } - php_libxml_invalidate_node_list_cache(intern->document); return err; diff --git a/ext/dom/html_document.c b/ext/dom/html_document.c index 254c18deb1b8d..1ba39870d39b9 100644 --- a/ext/dom/html_document.c +++ b/ext/dom/html_document.c @@ -546,7 +546,7 @@ static bool dom_decode_encode_fast_path( parser, buf_ptr - buf, buf, - buf_ptr - buf, + buf_ref - *buf_ref_ref, tokenizer_error_offset, tree_error_offset )) { @@ -758,7 +758,7 @@ static bool check_options_validity(uint32_t arg_num, zend_long options) "LIBXML_NOERROR, " "LIBXML_COMPACT, " "LIBXML_HTML_NOIMPLIED, " - "Dom\\NO_DEFAULT_NS)"); + "Dom\\HTML_NO_DEFAULT_NS)"); return false; } return true; diff --git a/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c b/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c index 8bd996a3910fe..3a40318628f10 100644 --- a/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c +++ b/ext/dom/lexbor/lexbor/selectors-adapted/selectors.c @@ -65,7 +65,21 @@ static zend_always_inline bool lxb_selectors_adapted_cmp_local_name_id(const xml static zend_always_inline const xmlAttr *lxb_selectors_adapted_attr(const xmlNode *node, const lxb_char_t *name) { - const xmlAttr *attr = xmlHasProp(node, (const xmlChar *) name); + const xmlAttr *attr = NULL; + ZEND_ASSERT(node->doc != NULL); + if (php_dom_ns_is_html_and_document_is_html(node)) { + /* No need to handle DTD entities as we're in HTML. */ + size_t name_bound = strlen((const char *) name) + 1; + for (const xmlAttr *cur = node->properties; cur != NULL; cur = cur->next) { + if (lexbor_str_data_nlocmp_right(cur->name, name, name_bound)) { + attr = cur; + break; + } + } + } else { + attr = xmlHasProp(node, (const xmlChar *) name); + } + if (attr != NULL && attr->ns != NULL) { return NULL; } @@ -85,8 +99,67 @@ static zend_always_inline dom_lxb_str_wrapper lxb_selectors_adapted_attr_value(c return ret; } +static bool lxb_selectors_attrib_name_cmp(const lxb_css_selector_t *selector, const char *name, size_t len) +{ + return selector->name.length == len && lexbor_str_data_nlocmp_right((const lxb_char_t *) name, selector->name.data, len); +} + +/* From https://html.spec.whatwg.org/#case-sensitivity-of-selectors + * "Attribute selectors on an HTML element in an HTML document must treat the values of attributes with the following names as ASCII case-insensitive:" */ +static bool lxb_selectors_is_lowercased_html_attrib_name(const lxb_css_selector_t *selector) +{ + return lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("accept")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("accept-charset")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("align")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("alink")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("axis")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("bgcolor")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("charset")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("checked")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("clear")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("codetype")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("color")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("compact")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("declare")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("defer")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("dir")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("direction")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("disabled")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("enctype")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("face")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("frame")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("hreflang")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("http-equiv")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("lang")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("language")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("link")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("media")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("method")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("multiple")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("nohref")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("noresize")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("noshade")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("nowrap")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("readonly")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("rel")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("rev")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("rules")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("scope")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("scrolling")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("selected")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("shape")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("target")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("text")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("type")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("valign")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("valuetype")) + || lxb_selectors_attrib_name_cmp(selector, ZEND_STRL("vlink")); +} + static void lxb_selectors_adapted_set_entry_id_ex(lxb_selectors_entry_t *entry, const lxb_css_selector_t *selector, const xmlNode *node) { + entry->id.attr_case_insensitive = lxb_selectors_is_lowercased_html_attrib_name(selector); + if (node->doc != NULL && node->doc->dict != NULL) { const xmlChar *interned = xmlDictExists(node->doc->dict, selector->name.data, selector->name.length); if (interned != NULL) { @@ -1290,10 +1363,10 @@ lxb_selectors_match_class(const lexbor_str_t *target, const lexbor_str_t *src, } static bool -lxb_selectors_match_attribute_value(const lxb_css_selector_attribute_t *attr, const lexbor_str_t *trg, const lexbor_str_t *src) +lxb_selectors_match_attribute_value(const lxb_css_selector_attribute_t *attr, bool force_modifier_i, const lexbor_str_t *trg, const lexbor_str_t *src) { bool res; - bool ins = attr->modifier == LXB_CSS_SELECTOR_MODIFIER_I; + bool ins = attr->modifier == LXB_CSS_SELECTOR_MODIFIER_I || force_modifier_i; switch (attr->match) { case LXB_CSS_SELECTOR_MATCH_EQUAL: /* = */ @@ -1405,7 +1478,13 @@ lxb_selectors_match_attribute(const lxb_css_selector_t *selector, } dom_lxb_str_wrapper trg = lxb_selectors_adapted_attr_value(dom_attr); - bool res = lxb_selectors_match_attribute_value(attr, &trg.str, src); + ZEND_ASSERT(node->doc != NULL); + bool res = lxb_selectors_match_attribute_value( + attr, + entry->id.attr_case_insensitive && php_dom_ns_is_html_and_document_is_html(node), + &trg.str, + src + ); dom_lxb_str_wrapper_release(&trg); return res; } diff --git a/ext/dom/lexbor/lexbor/selectors-adapted/selectors.h b/ext/dom/lexbor/lexbor/selectors-adapted/selectors.h index 441976b1e3d41..9057fae684189 100644 --- a/ext/dom/lexbor/lexbor/selectors-adapted/selectors.h +++ b/ext/dom/lexbor/lexbor/selectors-adapted/selectors.h @@ -78,6 +78,7 @@ typedef lxb_selectors_entry_t * typedef struct { const xmlChar *name; bool interned; + bool attr_case_insensitive; } lxb_selectors_adapted_id; struct lxb_selectors_entry { diff --git a/ext/dom/tests/gh17847.phpt b/ext/dom/tests/gh17847.phpt new file mode 100644 index 0000000000000..c32263bfe240f --- /dev/null +++ b/ext/dom/tests/gh17847.phpt @@ -0,0 +1,41 @@ +--TEST-- +GH-17847 (xinclude destroys live node) +--EXTENSIONS-- +dom +--FILE-- +loadXML(<< + + fallback

garbage

+

garbage

+
+ + +

garbage

+
+
+ +XML); + +$xpath = new DOMXPath($doc); + +$garbage = []; +foreach ($xpath->query('//p') as $entry) + $garbage[] = $entry; + +@$doc->xinclude(); + +foreach ($garbage as $node) { + try { + var_dump($node->localName); + } catch (DOMException $e) { + echo $e->getMessage(), "\n"; + } +} +?> +--EXPECT-- +Invalid State Error +Invalid State Error +Invalid State Error diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt new file mode 100644 index 0000000000000..47212cb34100a --- /dev/null +++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_001.phpt @@ -0,0 +1,60 @@ +--TEST-- +GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Basic) +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- + + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + + EOT; + // Intentionally using non-standard casing for content-type to verify it is matched not case sensitively. + yield "data://text/plain,HTTP/1.1 200 OK\r\nconteNt-tyPe: text/html; charset=utf-8\r\n\r\n{$xml}"; +} + +['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output); +$document = new \DOMDocument(); +$document->loadHTMLFile($uri); + +$h1 = $document->getElementsByTagName('h1'); +var_dump($h1->length); +var_dump($document->saveHTML()); +http_server_kill($pid); +?> +--EXPECT-- +int(1) +string(266) " + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + +" diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt new file mode 100644 index 0000000000000..a7eff3b9a8b76 --- /dev/null +++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_002.phpt @@ -0,0 +1,60 @@ +--TEST-- +GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Missing content-type) +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- + + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + + EOT; + // Missing content-type in actual response. + yield "data://text/plain,HTTP/1.1 200 OK\r\n\r\n{$xml}"; +} + +['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output); +$document = new \DOMDocument(); +$document->loadHTMLFile($uri); + +$h1 = $document->getElementsByTagName('h1'); +var_dump($h1->length); +var_dump($document->saveHTML()); +http_server_kill($pid); +?> +--EXPECT-- +int(1) +string(266) " + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + +" diff --git a/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt new file mode 100644 index 0000000000000..178b35f3525a5 --- /dev/null +++ b/ext/dom/tests/ghsa-p3x9-6h7p-cgfc_003.phpt @@ -0,0 +1,60 @@ +--TEST-- +GHSA-p3x9-6h7p-cgfc: libxml streams use wrong `content-type` header when requesting a redirected resource (Reason with colon) +--EXTENSIONS-- +dom +--SKIPIF-- + +--FILE-- + + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + + EOT; + // Missing content-type in actual response. + yield "data://text/plain,HTTP/1.1 200 OK: This is fine\r\n\r\n{$xml}"; +} + +['pid' => $pid, 'uri' => $uri] = http_server('genResponses', $output); +$document = new \DOMDocument(); +$document->loadHTMLFile($uri); + +$h1 = $document->getElementsByTagName('h1'); +var_dump($h1->length); +var_dump($document->saveHTML()); +http_server_kill($pid); +?> +--EXPECT-- +int(1) +string(266) " + + + GHSA-p3x9-6h7p-cgfc + + + + + + +

GHSA-p3x9-6h7p-cgfc

+ + +" diff --git a/ext/dom/tests/modern/css_selectors/gh17802.phpt b/ext/dom/tests/modern/css_selectors/gh17802.phpt new file mode 100644 index 0000000000000..2af1dab622836 --- /dev/null +++ b/ext/dom/tests/modern/css_selectors/gh17802.phpt @@ -0,0 +1,63 @@ +--TEST-- +GH-17802 (\Dom\HTMLDocument querySelector attribute name is case sensitive in HTML) +--EXTENSIONS-- +dom +--FILE-- + + + + + + + +TEXT; + +$dom = \Dom\HTMLDocument::createFromString($text, options: LIBXML_NOERROR); +$meta2 = $dom->head->appendChild($dom->createElementNS('urn:x', 'meta')); +$meta2->setAttribute('charset', 'x'); +echo $dom->saveHtml(), "\n"; + +echo "--- charseT ---\n"; + +foreach ($dom->querySelectorAll('meta[charseT]') as $entry) { + var_dump($dom->saveHtml($entry)); +} + +echo "--- charset ---\n"; + +foreach ($dom->querySelectorAll('meta[charset]') as $entry) { + var_dump($dom->saveHtml($entry)); +} + +echo "--- charseT and lowercase value ---\n"; + +foreach ($dom->querySelectorAll('meta[charseT="windows-1252"]') as $entry) { + var_dump($dom->saveHtml($entry)); +} + +echo "--- charset and lowercase value ---\n"; + +foreach ($dom->querySelectorAll('meta[charset="windows-1252"]') as $entry) { + var_dump($dom->saveHtml($entry)); +} + +?> +--EXPECT-- + + + + + + +--- charseT --- +string(29) "" +--- charset --- +string(29) "" +string(25) "" +--- charseT and lowercase value --- +string(29) "" +--- charset and lowercase value --- +string(29) "" diff --git a/ext/dom/tests/modern/html/encoding/HTMLDocument_encoding_edge_case_08.phpt b/ext/dom/tests/modern/html/encoding/HTMLDocument_encoding_edge_case_08.phpt index 68f0708bab780..305b78f4b6123 100644 --- a/ext/dom/tests/modern/html/encoding/HTMLDocument_encoding_edge_case_08.phpt +++ b/ext/dom/tests/modern/html/encoding/HTMLDocument_encoding_edge_case_08.phpt @@ -9,9 +9,9 @@ function test(string $str) { $dom = Dom\HTMLDocument::createFromString($str); var_dump($dom->body->textContent); - file_put_contents(__DIR__ . '/HTMLDocument_encoding_edge_case_07.tmp', $str); + file_put_contents(__DIR__ . '/HTMLDocument_encoding_edge_case_08.tmp', $str); - $dom = Dom\HTMLDocument::createFromFile(__DIR__ . '/HTMLDocument_encoding_edge_case_07.tmp'); + $dom = Dom\HTMLDocument::createFromFile(__DIR__ . '/HTMLDocument_encoding_edge_case_08.tmp'); var_dump($dom->body->textContent); } @@ -36,7 +36,7 @@ test($str); ?> --CLEAN-- --EXPECT-- string(4198) "AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA�BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB" diff --git a/ext/dom/tests/modern/html/parser/HTMLDocument_parse_options.phpt b/ext/dom/tests/modern/html/parser/HTMLDocument_parse_options.phpt index 0d3ad1a474ac8..f61ef2dec8676 100644 --- a/ext/dom/tests/modern/html/parser/HTMLDocument_parse_options.phpt +++ b/ext/dom/tests/modern/html/parser/HTMLDocument_parse_options.phpt @@ -41,67 +41,67 @@ foreach (["createFromString", "createFromFile"] as $method) { --EXPECTF-- --- Method createFromString --- int(%d) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(4194304) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(524288) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(8) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(4) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(16) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(4) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(256) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(16384) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(4) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(2) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(1024) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(1) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(2048) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(64) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(128) -Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromString(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) --- Method createFromFile --- int(%d) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(4194304) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(524288) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(8) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(4) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(16) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(4) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(256) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(16384) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(4) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(2) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(1024) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(1) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(2048) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(64) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) int(128) -Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\NO_DEFAULT_NS) +Dom\HTMLDocument::createFromFile(): Argument #2 ($options) contains invalid flags (allowed flags: LIBXML_NOERROR, LIBXML_COMPACT, LIBXML_HTML_NOIMPLIED, Dom\HTML_NO_DEFAULT_NS) diff --git a/ext/dom/tests/modern/xml/return_dom_node_from_xpath.phpt b/ext/dom/tests/modern/xml/return_dom_node_from_xpath.phpt new file mode 100644 index 0000000000000..4388139c43cbb --- /dev/null +++ b/ext/dom/tests/modern/xml/return_dom_node_from_xpath.phpt @@ -0,0 +1,17 @@ +--TEST-- +Returning a Dom\Node from Dom\XPath callback +--EXTENSIONS-- +dom +--FILE-- +'); +$xpath = new Dom\XPath($dom); +$xpath->registerPhpFunctionNs('urn:x', 'test', fn() => $dom->createElement('foo')); +$xpath->registerNamespace('x', 'urn:x'); +$test = $xpath->query('x:test()'); +var_dump($test[0]->nodeName); + +?> +--EXPECT-- +string(3) "foo" diff --git a/ext/dom/xpath_callbacks.c b/ext/dom/xpath_callbacks.c index 00b5ccb5911cd..283ccd8f7cc62 100644 --- a/ext/dom/xpath_callbacks.c +++ b/ext/dom/xpath_callbacks.c @@ -24,6 +24,7 @@ #if defined(HAVE_LIBXML) && defined(HAVE_DOM) #include "php_dom.h" +#include "internal_helpers.h" #include static void xpath_callbacks_entry_dtor(zval *zv) @@ -425,7 +426,8 @@ static zend_result php_dom_xpath_callback_dispatch(php_dom_xpath_callbacks *xpat } if (Z_TYPE(callback_retval) != IS_UNDEF) { - if (Z_TYPE(callback_retval) == IS_OBJECT && instanceof_function(Z_OBJCE(callback_retval), dom_node_class_entry)) { + if (Z_TYPE(callback_retval) == IS_OBJECT + && (instanceof_function(Z_OBJCE(callback_retval), dom_get_node_ce(php_dom_follow_spec_node((const xmlNode *) ctxt->context->doc))))) { xmlNode *nodep; dom_object *obj; if (xpath_callbacks->node_list == NULL) { @@ -439,7 +441,7 @@ static zend_result php_dom_xpath_callback_dispatch(php_dom_xpath_callbacks *xpat } else if (Z_TYPE(callback_retval) == IS_FALSE || Z_TYPE(callback_retval) == IS_TRUE) { valuePush(ctxt, xmlXPathNewBoolean(Z_TYPE(callback_retval) == IS_TRUE)); } else if (Z_TYPE(callback_retval) == IS_OBJECT) { - zend_type_error("Only objects that are instances of DOMNode can be converted to an XPath expression"); + zend_type_error("Only objects that are instances of DOM nodes can be converted to an XPath expression"); zval_ptr_dtor(&callback_retval); return FAILURE; } else { diff --git a/ext/ffi/ffi.g b/ext/ffi/ffi.g index d70075267e54f..3648372428900 100644 --- a/ext/ffi/ffi.g +++ b/ext/ffi/ffi.g @@ -264,12 +264,14 @@ struct_contents(zend_ffi_dcl *dcl): struct_declaration(zend_ffi_dcl *struct_dcl): {zend_ffi_dcl common_field_dcl = ZEND_FFI_ATTR_INIT;} + {zend_ffi_dcl base_field_dcl = ZEND_FFI_ATTR_INIT;} specifier_qualifier_list(&common_field_dcl) + {base_field_dcl = common_field_dcl;} ( /* empty */ {zend_ffi_add_anonymous_field(struct_dcl, &common_field_dcl);} | struct_declarator(struct_dcl, &common_field_dcl) ( "," - {zend_ffi_dcl field_dcl = common_field_dcl;} + {zend_ffi_dcl field_dcl = base_field_dcl;} attributes(&field_dcl)? struct_declarator(struct_dcl, &field_dcl) )* diff --git a/ext/ffi/ffi_parser.c b/ext/ffi/ffi_parser.c index 2589ae81e0259..26d623a40290e 100644 --- a/ext/ffi/ffi_parser.c +++ b/ext/ffi/ffi_parser.c @@ -2472,14 +2472,16 @@ static int parse_struct_contents(int sym, zend_ffi_dcl *dcl) { static int parse_struct_declaration(int sym, zend_ffi_dcl *struct_dcl) { zend_ffi_dcl common_field_dcl = ZEND_FFI_ATTR_INIT; + zend_ffi_dcl base_field_dcl = ZEND_FFI_ATTR_INIT; sym = parse_specifier_qualifier_list(sym, &common_field_dcl); + base_field_dcl = common_field_dcl; if (sym == YY__SEMICOLON || sym == YY__RBRACE) { zend_ffi_add_anonymous_field(struct_dcl, &common_field_dcl); } else if (sym == YY__STAR || sym == YY_ID || sym == YY__LPAREN || sym == YY__COLON) { sym = parse_struct_declarator(sym, struct_dcl, &common_field_dcl); while (sym == YY__COMMA) { sym = get_sym(); - zend_ffi_dcl field_dcl = common_field_dcl; + zend_ffi_dcl field_dcl = base_field_dcl; if (YY_IN_SET(sym, (YY___ATTRIBUTE,YY___ATTRIBUTE__,YY___DECLSPEC,YY___CDECL,YY___STDCALL,YY___FASTCALL,YY___THISCALL,YY___VECTORCALL), "\000\000\000\000\000\000\360\017\000\000\000\000\000")) { sym = parse_attributes(sym, &field_dcl); } diff --git a/ext/ffi/tests/ptr_declaration_list.phpt b/ext/ffi/tests/ptr_declaration_list.phpt new file mode 100644 index 0000000000000..cb8ecafd935ba --- /dev/null +++ b/ext/ffi/tests/ptr_declaration_list.phpt @@ -0,0 +1,33 @@ +--TEST-- +Declaration Lists with Pointers +--EXTENSIONS-- +ffi +--SKIPIF-- +--FILE-- +new('struct MyStruct'); +$one = $ffi->new("uint8_t"); +$oneptr = $ffi->new("uint8_t*"); +$oneptrptr = $ffi->new("uint8_t**"); +$one->cdata = 1; +$oneptr = FFI::addr($one); +$oneptrptr = FFI::addr($oneptr); + +$test_struct->a = $oneptrptr; +$test_struct->b = $oneptr; +$test_struct->c = $one; + +var_dump($test_struct->a[0][0]); +var_dump($test_struct->b[0]); +var_dump($test_struct->c); +?> +--EXPECT-- +int(1) +int(1) +int(1) diff --git a/ext/gd/gd.c b/ext/gd/gd.c index c81861ab80193..962041232c618 100644 --- a/ext/gd/gd.c +++ b/ext/gd/gd.c @@ -3981,6 +3981,11 @@ PHP_FUNCTION(imagescale) im = php_gd_libgdimageptr_from_zval_p(IM); + if (tmp_h < 0 && tmp_w < 0) { + zend_value_error("Argument #2 ($width) and argument #3 ($height) cannot be both negative"); + RETURN_THROWS(); + } + if (tmp_h < 0 || tmp_w < 0) { /* preserve ratio */ long src_x, src_y; diff --git a/ext/gd/libgd/gd.c b/ext/gd/libgd/gd.c index 922e39031c2ad..baa8887089efd 100644 --- a/ext/gd/libgd/gd.c +++ b/ext/gd/libgd/gd.c @@ -3162,7 +3162,11 @@ int gdImagePaletteToTrueColor(gdImagePtr src) const unsigned int sy = gdImageSY(src); const unsigned int sx = gdImageSX(src); - src->tpixels = (int **) gdMalloc(sizeof(int *) * sy); + // Note: do not revert back to gdMalloc() below ; reason here, + // due to a bug with a certain memory_limit INI value treshold, + // imagepalettetotruecolor crashes with even unrelated ZendMM allocations. + // See GH-17772 for an use case. + src->tpixels = (int **) gdCalloc(sizeof(int *), sy); if (src->tpixels == NULL) { return 0; } diff --git a/ext/gd/tests/gh17373.phpt b/ext/gd/tests/gh17373.phpt index 354cdd07362b2..f62c74fbd3e05 100644 --- a/ext/gd/tests/gh17373.phpt +++ b/ext/gd/tests/gh17373.phpt @@ -2,6 +2,10 @@ Bug GH-17373 (imagefttext() ignores clipping rect for palette images) --EXTENSIONS-- gd +--SKIPIF-- + --FILE-- getMessage(); +} +?> +--EXPECT-- +Argument #2 ($width) and argument #3 ($height) cannot be both negative diff --git a/ext/gd/tests/gh17772.phpt b/ext/gd/tests/gh17772.phpt new file mode 100644 index 0000000000000..6252a13341f41 --- /dev/null +++ b/ext/gd/tests/gh17772.phpt @@ -0,0 +1,28 @@ +--TEST-- +GH-17772 (imagepalettetotruecolor segfault on image deallocation) +--EXTENSIONS-- +gd +--INI-- +memory_limit=2M +--CREDITS-- +YuanchengJiang +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted%s(tried to allocate %d bytes) in %s on line %d diff --git a/ext/intl/tests/timezone_IDforWindowsID_basic2.phpt b/ext/intl/tests/timezone_IDforWindowsID_basic2.phpt index b6728352fc86f..600e21fa70c80 100644 --- a/ext/intl/tests/timezone_IDforWindowsID_basic2.phpt +++ b/ext/intl/tests/timezone_IDforWindowsID_basic2.phpt @@ -25,7 +25,7 @@ foreach ($tzs as $tz => $regions) { } } ?> ---EXPECT-- +--EXPECTF-- ** Gnomeregan bool(false) Error: unknown windows timezone: U_ILLEGAL_ARGUMENT_ERROR @@ -36,7 +36,7 @@ string(19) "America/Los_Angeles" string(17) "America/Vancouver" string(19) "America/Los_Angeles" string(19) "America/Los_Angeles" -string(7) "PST8PDT" +string(%d) "%r(PST8PDT|America\/Los_Angeles)%r" ** Romance Standard Time string(12) "Europe/Paris" string(15) "Europe/Brussels" diff --git a/ext/ldap/ldap.c b/ext/ldap/ldap.c index ad112a968f20b..67b01c0ec4472 100644 --- a/ext/ldap/ldap.c +++ b/ext/ldap/ldap.c @@ -247,7 +247,7 @@ static bool php_ldap_is_numerically_indexed_array(zend_array *arr) } } ZEND_HASH_FOREACH_END(); - return false; + return true; } /* {{{ Parse controls from and to arrays */ diff --git a/ext/ldap/tests/gh17704.phpt b/ext/ldap/tests/gh17704.phpt new file mode 100644 index 0000000000000..2403a63860e4a --- /dev/null +++ b/ext/ldap/tests/gh17704.phpt @@ -0,0 +1,24 @@ +--TEST-- +GH-17704 (ldap_search fails when $attributes contains a non-packed array with numerical keys) +--EXTENSIONS-- +ldap +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +object(LDAP\Result)#%d (0) { +} diff --git a/ext/libxml/mime_sniff.c b/ext/libxml/mime_sniff.c index 79ce1dc46b5e0..0ca032f9b795e 100644 --- a/ext/libxml/mime_sniff.c +++ b/ext/libxml/mime_sniff.c @@ -308,11 +308,21 @@ PHP_LIBXML_API zend_string *php_libxml_sniff_charset_from_stream(const php_strea if (Z_TYPE(s->wrapperdata) == IS_ARRAY) { zval *header; - ZEND_HASH_FOREACH_VAL_IND(Z_ARRVAL(s->wrapperdata), header) { - const char buf[] = "Content-Type:"; - if (Z_TYPE_P(header) == IS_STRING && - !zend_binary_strncasecmp(Z_STRVAL_P(header), Z_STRLEN_P(header), buf, sizeof(buf)-1, sizeof(buf)-1)) { - return php_libxml_sniff_charset_from_string(Z_STRVAL_P(header) + sizeof(buf) - 1, Z_STRVAL_P(header) + Z_STRLEN_P(header)); + /* Scan backwards: The header array might contain the headers for multiple responses, if + * a redirect was followed. + */ + ZEND_HASH_REVERSE_FOREACH_VAL_IND(Z_ARRVAL(s->wrapperdata), header) { + if (Z_TYPE_P(header) == IS_STRING) { + /* If no colon is found in the header, we assume it's the HTTP status line and bail out. */ + char *colon = memchr(Z_STRVAL_P(header), ':', Z_STRLEN_P(header)); + char *space = memchr(Z_STRVAL_P(header), ' ', Z_STRLEN_P(header)); + if (colon == NULL || space < colon) { + return NULL; + } + + if (zend_string_starts_with_literal_ci(Z_STR_P(header), "content-type:")) { + return php_libxml_sniff_charset_from_string(Z_STRVAL_P(header) + strlen("content-type:"), Z_STRVAL_P(header) + Z_STRLEN_P(header)); + } } } ZEND_HASH_FOREACH_END(); } diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index 9c2ecc44550bd..cd8a77d5c773a 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -3348,7 +3348,8 @@ try_next_encoding:; } for (size_t i = 0; i < length; i++) { - array[i].demerits *= array[i].multiplier; + double demerits = array[i].demerits * (double) array[i].multiplier; + array[i].demerits = demerits < (double) UINT64_MAX ? (uint64_t) demerits : UINT64_MAX; } return length; diff --git a/ext/mbstring/tests/gh17503.phpt b/ext/mbstring/tests/gh17503.phpt new file mode 100644 index 0000000000000..92a2cf39cb10f --- /dev/null +++ b/ext/mbstring/tests/gh17503.phpt @@ -0,0 +1,11 @@ +--TEST-- +GH-17503 (Undefined float conversion in mb_convert_variables) +--EXTENSIONS-- +mbstring +--FILE-- +"); +var_dump(mb_convert_variables("ASCII", ["UTF-8", "UTF-16"], $a)); +?> +--EXPECT-- +string(5) "UTF-8" diff --git a/ext/opcache/ZendAccelerator.c b/ext/opcache/ZendAccelerator.c index d1421fdf8653b..6e53b987d6761 100644 --- a/ext/opcache/ZendAccelerator.c +++ b/ext/opcache/ZendAccelerator.c @@ -1338,7 +1338,12 @@ zend_string *accel_make_persistent_key(zend_string *str) EXPECTED((parent_script = zend_get_executed_filename_ex()) != NULL)) { parent_script_len = ZSTR_LEN(parent_script); - while ((--parent_script_len > 0) && !IS_SLASH(ZSTR_VAL(parent_script)[parent_script_len])); + while (parent_script_len > 0) { + --parent_script_len; + if (IS_SLASH(ZSTR_VAL(parent_script)[parent_script_len])) { + break; + } + } if (UNEXPECTED((size_t)(key_length + parent_script_len + 1) >= ZCG_KEY_LEN)) { return NULL; @@ -2609,6 +2614,7 @@ static void zend_reset_cache_vars(void) ZCSG(restart_pending) = false; ZCSG(force_restart_time) = 0; ZCSG(map_ptr_last) = CG(map_ptr_last); + ZCSG(map_ptr_static_last) = zend_map_ptr_static_last; } static void accel_reset_pcre_cache(void) @@ -2624,7 +2630,7 @@ static void accel_reset_pcre_cache(void) } ZEND_HASH_FOREACH_END(); } -zend_result accel_activate(INIT_FUNC_ARGS) +ZEND_RINIT_FUNCTION(zend_accelerator) { if (!ZCG(enabled) || !accel_startup_ok) { ZCG(accelerator_enabled) = false; @@ -2956,12 +2962,15 @@ static void accel_globals_ctor(zend_accel_globals *accel_globals) GC_MAKE_PERSISTENT_LOCAL(accel_globals->key); } -#ifdef ZTS static void accel_globals_dtor(zend_accel_globals *accel_globals) { +#ifdef ZTS zend_string_free(accel_globals->key); -} #endif + if (accel_globals->preloaded_internal_run_time_cache) { + pefree(accel_globals->preloaded_internal_run_time_cache, 1); + } +} #ifdef HAVE_HUGE_CODE_PAGES # ifndef _WIN32 @@ -3402,6 +3411,8 @@ void accel_shutdown(void) if (!ZCG(enabled) || !accel_startup_ok) { #ifdef ZTS ts_free_id(accel_globals_id); +#else + accel_globals_dtor(&accel_globals); #endif return; } @@ -3416,6 +3427,8 @@ void accel_shutdown(void) #ifdef ZTS ts_free_id(accel_globals_id); +#else + accel_globals_dtor(&accel_globals); #endif if (!_file_cache_only) { @@ -4313,7 +4326,7 @@ static zend_persistent_script* preload_script_in_shared_memory(zend_persistent_s return new_persistent_script; } -static void preload_load(void) +static void preload_load(size_t orig_map_ptr_static_last) { /* Load into process tables */ zend_script *script = &ZCSG(preload_script)->script; @@ -4348,14 +4361,42 @@ static void preload_load(void) if (EG(class_table)) { EG(persistent_classes_count) = EG(class_table)->nNumUsed; } - if (CG(map_ptr_last) != ZCSG(map_ptr_last)) { - size_t old_map_ptr_last = CG(map_ptr_last); + + size_t old_map_ptr_last = CG(map_ptr_last); + if (zend_map_ptr_static_last != ZCSG(map_ptr_static_last) || old_map_ptr_last != ZCSG(map_ptr_last)) { CG(map_ptr_last) = ZCSG(map_ptr_last); - CG(map_ptr_size) = ZEND_MM_ALIGNED_SIZE_EX(CG(map_ptr_last) + 1, 4096); - CG(map_ptr_real_base) = perealloc(CG(map_ptr_real_base), CG(map_ptr_size) * sizeof(void*), 1); + CG(map_ptr_size) = ZEND_MM_ALIGNED_SIZE_EX(ZCSG(map_ptr_last) + 1, 4096); + zend_map_ptr_static_last = ZCSG(map_ptr_static_last); + + /* Grow map_ptr table as needed, but allocate once for static + regular map_ptrs */ + size_t new_static_size = ZEND_MM_ALIGNED_SIZE_EX(zend_map_ptr_static_last, 4096); + if (zend_map_ptr_static_size != new_static_size) { + void *new_base = pemalloc((new_static_size + CG(map_ptr_size)) * sizeof(void *), 1); + if (CG(map_ptr_real_base)) { + memcpy((void **) new_base + new_static_size - zend_map_ptr_static_size, CG(map_ptr_real_base), (old_map_ptr_last + zend_map_ptr_static_size) * sizeof(void *)); + pefree(CG(map_ptr_real_base), 1); + } + CG(map_ptr_real_base) = new_base; + zend_map_ptr_static_size = new_static_size; + } else { + CG(map_ptr_real_base) = perealloc(CG(map_ptr_real_base), (zend_map_ptr_static_size + CG(map_ptr_size)) * sizeof(void *), 1); + } + + memset((void **) CG(map_ptr_real_base) + zend_map_ptr_static_size + old_map_ptr_last, 0, (CG(map_ptr_last) - old_map_ptr_last) * sizeof(void *)); CG(map_ptr_base) = ZEND_MAP_PTR_BIASED_BASE(CG(map_ptr_real_base)); - memset((void **) CG(map_ptr_real_base) + old_map_ptr_last, 0, - (CG(map_ptr_last) - old_map_ptr_last) * sizeof(void *)); + } + + if (orig_map_ptr_static_last != zend_map_ptr_static_last) { + /* preloaded static entries currently are all runtime cache pointers, just assign them as such */ + size_t runtime_cache_size = zend_internal_run_time_cache_reserved_size(); + ZCG(preloaded_internal_run_time_cache_size) = (zend_map_ptr_static_last - orig_map_ptr_static_last) * runtime_cache_size; + char *cache = pemalloc(ZCG(preloaded_internal_run_time_cache_size), 1); + ZCG(preloaded_internal_run_time_cache) = cache; + + for (size_t cur_static_map_ptr = orig_map_ptr_static_last; cur_static_map_ptr < zend_map_ptr_static_last; ++cur_static_map_ptr) { + *ZEND_MAP_PTR_STATIC_NUM_TO_PTR(cur_static_map_ptr) = cache; + cache += runtime_cache_size; + } } } @@ -4364,7 +4405,7 @@ static zend_result accel_preload(const char *config, bool in_child) zend_file_handle file_handle; zend_result ret; char *orig_open_basedir; - size_t orig_map_ptr_last; + size_t orig_map_ptr_last, orig_map_ptr_static_last; uint32_t orig_compiler_options; ZCG(enabled) = false; @@ -4375,6 +4416,7 @@ static zend_result accel_preload(const char *config, bool in_child) accelerator_orig_compile_file = preload_compile_file; orig_map_ptr_last = CG(map_ptr_last); + orig_map_ptr_static_last = zend_map_ptr_static_last; /* Compile and execute preloading script */ zend_stream_init_filename(&file_handle, (char *) config); @@ -4554,7 +4596,7 @@ static zend_result accel_preload(const char *config, bool in_child) SHM_PROTECT(); HANDLE_UNBLOCK_INTERRUPTIONS(); - preload_load(); + preload_load(orig_map_ptr_static_last); /* Store individual scripts with unlinked classes */ HANDLE_BLOCK_INTERRUPTIONS(); @@ -4806,7 +4848,7 @@ static zend_result accel_finish_startup(void) if (ZCSG(preload_script)) { /* Preloading was done in another process */ - preload_load(); + preload_load(zend_map_ptr_static_last); zend_shared_alloc_unlock(); return SUCCESS; } @@ -4834,7 +4876,7 @@ static zend_result accel_finish_startup(void) } if (ZCSG(preload_script)) { - preload_load(); + preload_load(zend_map_ptr_static_last); } zend_shared_alloc_unlock(); @@ -4848,6 +4890,12 @@ static zend_result accel_finish_startup(void) #endif /* ZEND_WIN32 */ } +static void accel_activate(void) { + if (ZCG(preloaded_internal_run_time_cache)) { + memset(ZCG(preloaded_internal_run_time_cache), 0, ZCG(preloaded_internal_run_time_cache_size)); + } +} + ZEND_EXT_API zend_extension zend_extension_entry = { ACCELERATOR_PRODUCT_NAME, /* name */ PHP_VERSION, /* version */ @@ -4856,7 +4904,7 @@ ZEND_EXT_API zend_extension zend_extension_entry = { "Copyright (c)", /* copyright */ accel_startup, /* startup */ NULL, /* shutdown */ - NULL, /* per-script activation */ + accel_activate, /* per-script activation */ #ifdef HAVE_JIT accel_deactivate, /* per-script deactivation */ #else diff --git a/ext/opcache/ZendAccelerator.h b/ext/opcache/ZendAccelerator.h index 54d55e10e4f5c..486074ef0012b 100644 --- a/ext/opcache/ZendAccelerator.h +++ b/ext/opcache/ZendAccelerator.h @@ -50,6 +50,7 @@ #include "zend_extensions.h" #include "zend_compile.h" +#include "zend_API.h" #include "Optimizer/zend_optimizer.h" #include "zend_accelerator_hash.h" @@ -216,6 +217,8 @@ typedef struct _zend_accel_globals { #ifndef ZEND_WIN32 zend_ulong root_hash; #endif + void *preloaded_internal_run_time_cache; + size_t preloaded_internal_run_time_cache_size; /* preallocated shared-memory block to save current script */ void *mem; zend_persistent_script *current_persistent_script; @@ -251,6 +254,7 @@ typedef struct _zend_accel_shared_globals { zend_accel_hash hash; /* hash table for cached scripts */ size_t map_ptr_last; + size_t map_ptr_static_last; /* Directives & Maintenance */ time_t start_time; @@ -310,7 +314,7 @@ extern const char *zps_api_failure_reason; BEGIN_EXTERN_C() void accel_shutdown(void); -zend_result accel_activate(INIT_FUNC_ARGS); +ZEND_RINIT_FUNCTION(zend_accelerator); zend_result accel_post_deactivate(void); void zend_accel_schedule_restart(zend_accel_restart_reason reason); void zend_accel_schedule_restart_if_necessary(zend_accel_restart_reason reason); diff --git a/ext/opcache/jit/ir/ir.c b/ext/opcache/jit/ir/ir.c index 1aa887f6a949e..a6bb7c993c51b 100644 --- a/ext/opcache/jit/ir/ir.c +++ b/ext/opcache/jit/ir/ir.c @@ -974,7 +974,7 @@ ir_ref ir_folding(ir_ctx *ctx, uint32_t opt, ir_ref op1, ir_ref op2, ir_ref op3, } while (1); ir_fold_restart: - if (!(ctx->flags2 & IR_OPT_IN_SCCP)) { + if (!ctx->use_lists) { op1_insn = ctx->ir_base + op1; op2_insn = ctx->ir_base + op2; op3_insn = ctx->ir_base + op3; @@ -987,7 +987,7 @@ ir_ref ir_folding(ir_ctx *ctx, uint32_t opt, ir_ref op1, ir_ref op2, ir_ref op3, return IR_FOLD_DO_RESTART; } ir_fold_cse: - if (!(ctx->flags2 & IR_OPT_IN_SCCP)) { + if (!ctx->use_lists) { /* Local CSE */ ref = _ir_fold_cse(ctx, opt, op1, op2, op3); if (ref) { @@ -1009,9 +1009,15 @@ ir_ref ir_folding(ir_ctx *ctx, uint32_t opt, ir_ref op1, ir_ref op2, ir_ref op3, ctx->prev_insn_chain[op] = ref; return ref; + } else { + ctx->fold_insn.optx = opt; + ctx->fold_insn.op1 = op1; + ctx->fold_insn.op2 = op2; + ctx->fold_insn.op3 = op3; + return IR_FOLD_DO_CSE; } ir_fold_emit: - if (!(ctx->flags2 & IR_OPT_IN_SCCP)) { + if (!ctx->use_lists) { return ir_emit(ctx, opt, op1, op2, op3); } else { ctx->fold_insn.optx = opt; @@ -1021,17 +1027,17 @@ ir_ref ir_folding(ir_ctx *ctx, uint32_t opt, ir_ref op1, ir_ref op2, ir_ref op3, return IR_FOLD_DO_EMIT; } ir_fold_copy: - if (!(ctx->flags2 & IR_OPT_IN_SCCP)) { + if (!ctx->use_lists) { return ref; } else { ctx->fold_insn.op1 = ref; return IR_FOLD_DO_COPY; } ir_fold_const: - if (!(ctx->flags2 & IR_OPT_IN_SCCP)) { + if (!ctx->use_lists) { return ir_const(ctx, val, IR_OPT_TYPE(opt)); } else { - ctx->fold_insn.type = IR_OPT_TYPE(opt); + ctx->fold_insn.opt = IR_OPT(IR_OPT_TYPE(opt), IR_OPT_TYPE(opt)); ctx->fold_insn.val.u64 = val.u64; return IR_FOLD_DO_CONST; } @@ -1436,7 +1442,7 @@ void ir_replace(ir_ctx *ctx, ir_ref ref, ir_ref new_ref) n = use_list->count; p = ctx->use_edges + use_list->refs; - if (new_ref < 0) { + if (new_ref <= 0) { /* constant or IR_UNUSED */ for (; n; p++, n--) { use = *p; @@ -1915,7 +1921,7 @@ static ir_alias ir_check_aliasing(ir_ctx *ctx, ir_ref addr1, ir_ref addr2) } #endif -static ir_alias ir_check_partial_aliasing(const ir_ctx *ctx, ir_ref addr1, ir_ref addr2, ir_type type1, ir_type type2) +ir_alias ir_check_partial_aliasing(const ir_ctx *ctx, ir_ref addr1, ir_ref addr2, ir_type type1, ir_type type2) { ir_insn *insn1, *insn2; ir_ref base1, base2, off1, off2; @@ -2009,9 +2015,8 @@ static ir_alias ir_check_partial_aliasing(const ir_ctx *ctx, ir_ref addr1, ir_re return IR_MAY_ALIAS; } -static ir_ref ir_find_aliasing_load(ir_ctx *ctx, ir_ref ref, ir_type type, ir_ref addr) +IR_ALWAYS_INLINE ir_ref ir_find_aliasing_load_i(ir_ctx *ctx, ir_ref ref, ir_type type, ir_ref addr, ir_ref limit) { - ir_ref limit = (addr > 0) ? addr : 1; ir_insn *insn; uint32_t modified_regset = 0; @@ -2022,10 +2027,10 @@ static ir_ref ir_find_aliasing_load(ir_ctx *ctx, ir_ref ref, ir_type type, ir_re if (insn->type == type) { return ref; /* load forwarding (L2L) */ } else if (ir_type_size[insn->type] == ir_type_size[type]) { - return ir_fold1(ctx, IR_OPT(IR_BITCAST, type), ref); /* load forwarding with bitcast (L2L) */ + return ref; /* load forwarding with bitcast (L2L) */ } else if (ir_type_size[insn->type] > ir_type_size[type] && IR_IS_TYPE_INT(type) && IR_IS_TYPE_INT(insn->type)) { - return ir_fold1(ctx, IR_OPT(IR_TRUNC, type), ref); /* partial load forwarding (L2L) */ + return ref; /* partial load forwarding (L2L) */ } } } else if (insn->op == IR_STORE) { @@ -2039,10 +2044,10 @@ static ir_ref ir_find_aliasing_load(ir_ctx *ctx, ir_ref ref, ir_type type, ir_re } else if (type2 == type) { return insn->op3; /* store forwarding (S2L) */ } else if (ir_type_size[type2] == ir_type_size[type]) { - return ir_fold1(ctx, IR_OPT(IR_BITCAST, type), insn->op3); /* store forwarding with bitcast (S2L) */ + return insn->op3; /* store forwarding with bitcast (S2L) */ } else if (ir_type_size[type2] > ir_type_size[type] && IR_IS_TYPE_INT(type) && IR_IS_TYPE_INT(type2)) { - return ir_fold1(ctx, IR_OPT(IR_TRUNC, type), insn->op3); /* partial store forwarding (S2L) */ + return insn->op3; /* partial store forwarding (S2L) */ } else { return IR_UNUSED; } @@ -2056,9 +2061,235 @@ static ir_ref ir_find_aliasing_load(ir_ctx *ctx, ir_ref ref, ir_type type, ir_re } ref = insn->op1; } + + return IR_UNUSED; +} + +ir_ref ir_find_aliasing_load(ir_ctx *ctx, ir_ref ref, ir_type type, ir_ref addr) +{ + return ir_find_aliasing_load_i(ctx, ref, type, addr, (addr > 0 && addr < ref) ? addr : 1); +} + +IR_ALWAYS_INLINE ir_ref ir_find_aliasing_vload_i(ir_ctx *ctx, ir_ref ref, ir_type type, ir_ref var) +{ + ir_insn *insn; + + while (ref > var) { + insn = &ctx->ir_base[ref]; + if (insn->op == IR_VLOAD) { + if (insn->op2 == var) { + if (insn->type == type) { + return ref; /* load forwarding (L2L) */ + } else if (ir_type_size[insn->type] == ir_type_size[type]) { + return ir_fold1(ctx, IR_OPT(IR_BITCAST, type), ref); /* load forwarding with bitcast (L2L) */ + } else if (ir_type_size[insn->type] > ir_type_size[type] + && IR_IS_TYPE_INT(type) && IR_IS_TYPE_INT(insn->type)) { + return ir_fold1(ctx, IR_OPT(IR_TRUNC, type), ref); /* partial load forwarding (L2L) */ + } + } + } else if (insn->op == IR_VSTORE) { + ir_type type2 = ctx->ir_base[insn->op3].type; + + if (insn->op2 == var) { + if (type2 == type) { + return insn->op3; /* store forwarding (S2L) */ + } else if (ir_type_size[type2] == ir_type_size[type]) { + return ir_fold1(ctx, IR_OPT(IR_BITCAST, type), insn->op3); /* store forwarding with bitcast (S2L) */ + } else if (ir_type_size[type2] > ir_type_size[type] + && IR_IS_TYPE_INT(type) && IR_IS_TYPE_INT(type2)) { + return ir_fold1(ctx, IR_OPT(IR_TRUNC, type), insn->op3); /* partial store forwarding (S2L) */ + } else { + break; + } + } + } else if (insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN || insn->op == IR_CALL || insn->op == IR_STORE) { + break; + } + ref = insn->op1; + } + + return IR_UNUSED; +} + +ir_ref ir_find_aliasing_vload(ir_ctx *ctx, ir_ref ref, ir_type type, ir_ref var) +{ + return ir_find_aliasing_vload_i(ctx, ref, type, var); +} + +IR_ALWAYS_INLINE ir_ref ir_find_aliasing_store_i(ir_ctx *ctx, ir_ref ref, ir_ref addr, ir_ref val, ir_ref limit) +{ + ir_ref next = IR_UNUSED; + ir_insn *insn; + ir_type type = ctx->ir_base[val].type; + ir_type type2; + bool guarded = 0; + +// if (!IR_IS_CONST_REF(val)) { +// insn = &ctx->ir_base[val]; +// if (insn->op == IR_BITCAST +// && !IR_IS_CONST_REF(insn->op1) +// && ir_type_size[insn->type] == ir_type_size[ctx->ir_base[insn->op1].type]) { +// /* skip BITCAST */ +// val = insn->op1; +// } +// } + + while (ref > limit) { + insn = &ctx->ir_base[ref]; + if (insn->op == IR_STORE) { + if (insn->op2 == addr) { + if (ctx->ir_base[insn->op3].type == type) { + if (insn->op3 == val) { + /* dead STORE (store the same value once again) */ + return ref; + } else { + if (!guarded) { + /* the previous STORE is dead (there are no LOADs) */ + if (!ctx->use_lists) { + if (next) { + ctx->ir_base[next].op1 = insn->op1; + } else { + ctx->control = insn->op1; + } + } else { + ir_ref prev = insn->op1; + + if (!next) { + IR_ASSERT(ctx->use_lists[ref].count == 1); + next = ctx->use_edges[ctx->use_lists[ref].refs]; + } + ctx->ir_base[next].op1 = prev; + ir_use_list_remove_one(ctx, ref, next); + ir_use_list_replace_one(ctx, prev, ref, next); + if (!IR_IS_CONST_REF(insn->op2)) { + ir_use_list_remove_one(ctx, insn->op2, ref); + } + if (!IR_IS_CONST_REF(insn->op3)) { + ir_use_list_remove_one(ctx, insn->op3, ref); + } + insn->op1 = IR_UNUSED; + } + MAKE_NOP(insn); + } + break; + } + } else { + break; + } + } else { + type2 = ctx->ir_base[insn->op3].type; + goto check_aliasing; + } + } else if (insn->op == IR_LOAD) { + if (insn->op2 == addr) { + if (ref == val) { + /* dead STORE (store the value that was loaded before) */ + return ref; + } + break; + } + type2 = insn->type; +check_aliasing: + if (ir_check_partial_aliasing(ctx, addr, insn->op2, type, type2) != IR_NO_ALIAS) { + break; + } + } else if (insn->op == IR_GUARD || insn->op == IR_GUARD_NOT) { + guarded = 1; + } else if (insn->op >= IR_START || insn->op == IR_CALL) { + break; + } + next = ref; + ref = insn->op1; + } + return IR_UNUSED; } +ir_ref ir_find_aliasing_store(ir_ctx *ctx, ir_ref ref, ir_ref addr, ir_ref val) +{ + return ir_find_aliasing_store_i(ctx, ref, addr, val, (addr > 0 && addr < ref) ? addr : 1); +} + +IR_ALWAYS_INLINE ir_ref ir_find_aliasing_vstore_i(ir_ctx *ctx, ir_ref ref, ir_ref var, ir_ref val) +{ + ir_ref limit = var; + ir_ref next = IR_UNUSED; + ir_insn *insn; + bool guarded = 0; + +// if (!IR_IS_CONST_REF(val)) { +// insn = &ctx->ir_base[val]; +// if (insn->op == IR_BITCAST +// && !IR_IS_CONST_REF(insn->op1) +// && ir_type_size[insn->type] == ir_type_size[ctx->ir_base[insn->op1].type]) { +// /* skip BITCAST */ +// val = insn->op1; +// } +// } + + while (ref > limit) { + insn = &ctx->ir_base[ref]; + if (insn->op == IR_VSTORE) { + if (insn->op2 == var) { + if (insn->op3 == val) { + /* dead VSTORE */ + return ref; + } else { + if (!guarded) { + /* the previous VSTORE is dead (there are no VLOADs) */ + if (!ctx->use_lists) { + if (next) { + ctx->ir_base[next].op1 = insn->op1; + } else { + ctx->control = insn->op1; + } + } else { + ir_ref prev = insn->op1; + + if (!next) { + IR_ASSERT(ctx->use_lists[ref].count == 1); + next = ctx->use_edges[ctx->use_lists[ref].refs]; + } + ctx->ir_base[next].op1 = prev; + ir_use_list_remove_one(ctx, ref, next); + ir_use_list_replace_one(ctx, prev, ref, next); + if (!IR_IS_CONST_REF(insn->op2)) { + ir_use_list_remove_one(ctx, insn->op2, ref); + } + if (!IR_IS_CONST_REF(insn->op3)) { + ir_use_list_remove_one(ctx, insn->op3, ref); + } + insn->op1 = IR_UNUSED; + } + MAKE_NOP(insn); + } + break; + } + } + } else if (insn->op == IR_VLOAD) { + if (insn->op2 == var) { + if (ref == val) { + /* dead VSTORE */ + return ref; + } + break; + } + } else if (insn->op == IR_GUARD || insn->op == IR_GUARD_NOT) { + guarded = 1; + } else if (insn->op >= IR_START || insn->op == IR_CALL || insn->op == IR_LOAD || insn->op == IR_STORE) { + break; + } + next = ref; + ref = insn->op1; + } + return IR_UNUSED; +} + +ir_ref ir_find_aliasing_vstore(ir_ctx *ctx, ir_ref ref, ir_ref var, ir_ref val) +{ + return ir_find_aliasing_vstore_i(ctx, ref, var, val); +} + /* IR Construction API */ ir_ref _ir_PARAM(ir_ctx *ctx, ir_type type, const char* name, ir_ref num) @@ -2176,7 +2407,7 @@ void _ir_BEGIN(ir_ctx *ctx, ir_ref src) } } -ir_ref _ir_fold_condition(ir_ctx *ctx, ir_ref ref) +static ir_ref _ir_fold_condition(ir_ctx *ctx, ir_ref ref) { ir_insn *insn = &ctx->ir_base[ref]; @@ -2184,12 +2415,59 @@ ir_ref _ir_fold_condition(ir_ctx *ctx, ir_ref ref) ir_insn *op2_insn = &ctx->ir_base[insn->op2]; if (IR_IS_TYPE_INT(op2_insn->type) && op2_insn->val.u64 == 0) { - return insn->op1; + ref = insn->op1; + insn = &ctx->ir_base[ref]; } + } else if (insn->op == IR_EQ && insn->op2 == IR_TRUE) { + ref = insn->op1; + insn = &ctx->ir_base[ref]; } +// while (insn->op == IR_SEXT || insn->op == IR_ZEXT || insn->op == IR_BITCAST) { +// ref = insn->op1; +// insn = &ctx->ir_base[ref]; +// } return ref; } +IR_ALWAYS_INLINE ir_ref ir_check_dominating_predicates_i(ir_ctx *ctx, ir_ref ref, ir_ref condition, ir_ref limit) +{ + ir_insn *prev = NULL; + ir_insn *insn; + + while (ref > limit) { + insn = &ctx->ir_base[ref]; + if (insn->op == IR_GUARD_NOT) { + if (insn->op2 == condition) { + return IR_FALSE; + } + } else if (insn->op == IR_GUARD) { + if (insn->op2 == condition) { + return IR_TRUE; + } + } else if (insn->op == IR_IF) { + if (insn->op2 == condition) { + if (prev->op == IR_IF_TRUE) { + return IR_TRUE; + } else if (prev->op == IR_IF_FALSE) { + return IR_FALSE; + } + } + } else if (insn->op == IR_START || insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN) { + break; + } + prev = insn; + ref = insn->op1; + } + + return condition; +} + +ir_ref ir_check_dominating_predicates(ir_ctx *ctx, ir_ref ref, ir_ref condition) +{ + IR_ASSERT(!IR_IS_CONST_REF(condition)); + return ir_check_dominating_predicates_i(ctx, ref, condition, (condition < ref) ? condition : 1); +} + ir_ref _ir_IF(ir_ctx *ctx, ir_ref condition) { ir_ref if_ref; @@ -2205,38 +2483,7 @@ ir_ref _ir_IF(ir_ctx *ctx, ir_ref condition) if (IR_IS_CONST_REF(condition)) { condition = ir_ref_is_true(ctx, condition) ? IR_TRUE : IR_FALSE; } else { - ir_insn *prev = NULL; - ir_ref ref = ctx->control; - ir_insn *insn; - - while (ref > condition) { - insn = &ctx->ir_base[ref]; - if (insn->op == IR_GUARD_NOT) { - if (insn->op2 == condition) { - condition = IR_FALSE; - break; - } - } else if (insn->op == IR_GUARD) { - if (insn->op2 == condition) { - condition = IR_TRUE; - break; - } - } else if (insn->op == IR_IF) { - if (insn->op2 == condition) { - if (prev->op == IR_IF_TRUE) { - condition = IR_TRUE; - break; - } else if (prev->op == IR_IF_FALSE) { - condition = IR_FALSE; - break; - } - } - } else if (insn->op == IR_START || insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN) { - break; - } - prev = insn; - ref = insn->op1; - } + condition = ir_check_dominating_predicates_i(ctx, ctx->control, condition, condition); } if_ref = ir_emit2(ctx, IR_IF, ctx->control, condition); ctx->control = IR_UNUSED; @@ -2755,35 +3002,9 @@ void _ir_GUARD(ir_ctx *ctx, ir_ref condition, ir_ref addr) } condition = IR_FALSE; } else if (EXPECTED(ctx->flags & IR_OPT_FOLDING)) { - ir_insn *prev = NULL; - ir_ref ref = ctx->control; - ir_insn *insn; - - while (ref > condition) { - insn = &ctx->ir_base[ref]; - if (insn->op == IR_GUARD) { - if (insn->op2 == condition) { - return; - } - } else if (insn->op == IR_GUARD_NOT) { - if (insn->op2 == condition) { - condition = IR_FALSE; - break; - } - } else if (insn->op == IR_IF) { - if (insn->op2 == condition) { - if (prev->op == IR_IF_TRUE) { - return; - } else if (prev->op == IR_IF_FALSE) { - condition = IR_FALSE; - break; - } - } - } else if (insn->op == IR_START || insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN) { - break; - } - prev = insn; - ref = insn->op1; + condition = ir_check_dominating_predicates_i(ctx, ctx->control, condition, condition); + if (condition == IR_TRUE) { + return; } } if (ctx->snapshot_create) { @@ -2801,35 +3022,9 @@ void _ir_GUARD_NOT(ir_ctx *ctx, ir_ref condition, ir_ref addr) } condition = IR_TRUE; } else if (EXPECTED(ctx->flags & IR_OPT_FOLDING)) { - ir_insn *prev = NULL; - ir_ref ref = ctx->control; - ir_insn *insn; - - while (ref > condition) { - insn = &ctx->ir_base[ref]; - if (insn->op == IR_GUARD_NOT) { - if (insn->op2 == condition) { - return; - } - } else if (insn->op == IR_GUARD) { - if (insn->op2 == condition) { - condition = IR_TRUE; - break; - } - } else if (insn->op == IR_IF) { - if (insn->op2 == condition) { - if (prev->op == IR_IF_TRUE) { - condition = IR_TRUE; - break; - } else if (prev->op == IR_IF_FALSE) { - return; - } - } - } else if (insn->op == IR_START || insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN) { - break; - } - prev = insn; - ref = insn->op1; + condition = ir_check_dominating_predicates_i(ctx, ctx->control, condition, condition); + if (condition == IR_FALSE) { + return; } } if (ctx->snapshot_create) { @@ -2881,109 +3076,33 @@ void _ir_AFREE(ir_ctx *ctx, ir_ref size) ir_ref _ir_VLOAD(ir_ctx *ctx, ir_type type, ir_ref var) { - ir_ref ref = ctx->control; - ir_insn *insn; - - if (UNEXPECTED(!(ctx->flags & IR_OPT_FOLDING))) { - IR_ASSERT(ctx->control); - return ctx->control = ir_emit2(ctx, IR_OPT(IR_VLOAD, type), ctx->control, var); - } - while (ref > var) { - insn = &ctx->ir_base[ref]; - if (insn->op == IR_VLOAD) { - if (insn->op2 == var) { - if (insn->type == type) { - return ref; /* load forwarding (L2L) */ - } else if (ir_type_size[insn->type] == ir_type_size[type]) { - return ir_fold1(ctx, IR_OPT(IR_BITCAST, type), ref); /* load forwarding with bitcast (L2L) */ - } else if (ir_type_size[insn->type] > ir_type_size[type] - && IR_IS_TYPE_INT(type) && IR_IS_TYPE_INT(insn->type)) { - return ir_fold1(ctx, IR_OPT(IR_TRUNC, type), ref); /* partial load forwarding (L2L) */ - } - } - } else if (insn->op == IR_VSTORE) { - ir_type type2 = ctx->ir_base[insn->op3].type; + ir_ref ref; - if (insn->op2 == var) { - if (type2 == type) { - return insn->op3; /* store forwarding (S2L) */ - } else if (ir_type_size[type2] == ir_type_size[type]) { - return ir_fold1(ctx, IR_OPT(IR_BITCAST, type), insn->op3); /* store forwarding with bitcast (S2L) */ - } else if (ir_type_size[type2] > ir_type_size[type] - && IR_IS_TYPE_INT(type) && IR_IS_TYPE_INT(type2)) { - return ir_fold1(ctx, IR_OPT(IR_TRUNC, type), insn->op3); /* partial store forwarding (S2L) */ - } else { - break; - } + IR_ASSERT(ctx->control); + if (EXPECTED(ctx->flags & IR_OPT_FOLDING)) { + ref = ir_find_aliasing_vload_i(ctx, ctx->control, type, var); + if (ref) { + ir_insn *insn = &ctx->ir_base[ref]; + if (insn->type == type) { + return ref; + } else if (ir_type_size[insn->type] == ir_type_size[type]) { + return ir_fold1(ctx, IR_OPT(IR_BITCAST, type), ref); /* load forwarding with bitcast (L2L) */ + } else { + return ir_fold1(ctx, IR_OPT(IR_TRUNC, type), ref); /* partial load forwarding (L2L) */ } - } else if (insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN || insn->op == IR_CALL || insn->op == IR_STORE) { - break; } - ref = insn->op1; } - - IR_ASSERT(ctx->control); return ctx->control = ir_emit2(ctx, IR_OPT(IR_VLOAD, type), ctx->control, var); } void _ir_VSTORE(ir_ctx *ctx, ir_ref var, ir_ref val) { - ir_ref limit = var; - ir_ref ref = ctx->control; - ir_ref prev = IR_UNUSED; - ir_insn *insn; - bool guarded = 0; - - if (UNEXPECTED(!(ctx->flags & IR_OPT_FOLDING))) { - IR_ASSERT(ctx->control); - ctx->control = ir_emit3(ctx, IR_VSTORE, ctx->control, var, val); - return; - } - - if (!IR_IS_CONST_REF(val)) { - insn = &ctx->ir_base[val]; - if (insn->op == IR_BITCAST - && !IR_IS_CONST_REF(insn->op1) - && ir_type_size[insn->type] == ir_type_size[ctx->ir_base[insn->op1].type]) { - /* skip BITCAST */ - val = insn->op1; - } - } - IR_ASSERT(ctx->control); - while (ref > limit) { - insn = &ctx->ir_base[ref]; - if (insn->op == IR_VSTORE) { - if (insn->op2 == var) { - if (insn->op3 == val) { - return; - } else { - if (!guarded) { - if (prev) { - ctx->ir_base[prev].op1 = insn->op1; - } else { - ctx->control = insn->op1; - } - MAKE_NOP(insn); - } - break; - } - } - } else if (insn->op == IR_VLOAD) { - if (insn->op2 == var) { - if (ref == val) { - /* dead STORE */ - return; - } - break; - } - } else if (insn->op == IR_GUARD || insn->op == IR_GUARD_NOT) { - guarded = 1; - } else if (insn->op >= IR_START || insn->op == IR_CALL || insn->op == IR_LOAD || insn->op == IR_STORE) { - break; + if (EXPECTED(ctx->flags & IR_OPT_FOLDING)) { + if (ir_find_aliasing_vstore_i(ctx, ctx->control, var, val)) { + /* dead STORE */ + return; } - prev = ref; - ref = insn->op1; } ctx->control = ir_emit3(ctx, IR_VSTORE, ctx->control, var, val); } @@ -3008,89 +3127,40 @@ void _ir_RSTORE(ir_ctx *ctx, ir_ref reg, ir_ref val) ir_ref _ir_LOAD(ir_ctx *ctx, ir_type type, ir_ref addr) { - ir_ref ref = IR_UNUSED; + ir_ref ref; IR_ASSERT(ctx->control); if (EXPECTED(ctx->flags & IR_OPT_FOLDING)) { - ref = ir_find_aliasing_load(ctx, ctx->control, type, addr); - } - if (!ref) { - ctx->control = ref = ir_emit2(ctx, IR_OPT(IR_LOAD, type), ctx->control, addr); + if (ctx->ir_base[addr].op == IR_VADDR) { + return _ir_VLOAD(ctx, type, ctx->ir_base[addr].op1); + } + ref = ir_find_aliasing_load_i(ctx, ctx->control, type, addr, (addr > 0) ? addr : 1); + if (ref) { + ir_insn *insn = &ctx->ir_base[ref]; + if (insn->type == type) { + return ref; + } else if (ir_type_size[insn->type] == ir_type_size[type]) { + return ir_fold1(ctx, IR_OPT(IR_BITCAST, type), ref); /* load forwarding with bitcast (L2L) */ + } else { + return ir_fold1(ctx, IR_OPT(IR_TRUNC, type), ref); /* partial load forwarding (L2L) */ + } + } } - return ref; + return ctx->control = ir_emit2(ctx, IR_OPT(IR_LOAD, type), ctx->control, addr); } void _ir_STORE(ir_ctx *ctx, ir_ref addr, ir_ref val) { - ir_ref limit = (addr > 0) ? addr : 1; - ir_ref ref = ctx->control; - ir_ref prev = IR_UNUSED; - ir_insn *insn; - ir_type type = ctx->ir_base[val].type; - ir_type type2; - bool guarded = 0; - IR_ASSERT(ctx->control); - if (UNEXPECTED(!(ctx->flags & IR_OPT_FOLDING))) { - ctx->control = ir_emit3(ctx, IR_STORE, ctx->control, addr, val); - return; - } - - if (!IR_IS_CONST_REF(val)) { - insn = &ctx->ir_base[val]; - if (insn->op == IR_BITCAST - && !IR_IS_CONST_REF(insn->op1) - && ir_type_size[insn->type] == ir_type_size[ctx->ir_base[insn->op1].type]) { - /* skip BITCAST */ - val = insn->op1; + if (EXPECTED(ctx->flags & IR_OPT_FOLDING)) { + if (ctx->ir_base[addr].op == IR_VADDR) { + _ir_VSTORE(ctx, ctx->ir_base[addr].op1, val); + return; } - } - - while (ref > limit) { - insn = &ctx->ir_base[ref]; - if (insn->op == IR_STORE) { - if (insn->op2 == addr) { - if (ctx->ir_base[insn->op3].type == type) { - if (insn->op3 == val) { - return; - } else { - if (!guarded) { - if (prev) { - ctx->ir_base[prev].op1 = insn->op1; - } else { - ctx->control = insn->op1; - } - MAKE_NOP(insn); - } - break; - } - } else { - break; - } - } else { - type2 = ctx->ir_base[insn->op3].type; - goto check_aliasing; - } - } else if (insn->op == IR_LOAD) { - if (insn->op2 == addr) { - if (ref == val) { - /* dead STORE */ - return; - } - break; - } - type2 = insn->type; -check_aliasing: - if (ir_check_partial_aliasing(ctx, addr, insn->op2, type, type2) != IR_NO_ALIAS) { - break; - } - } else if (insn->op == IR_GUARD || insn->op == IR_GUARD_NOT) { - guarded = 1; - } else if (insn->op >= IR_START || insn->op == IR_CALL) { - break; + if (ir_find_aliasing_store_i(ctx, ctx->control, addr, val, (addr > 0) ? addr : 1)) { + /* dead STORE */ + return; } - prev = ref; - ref = insn->op1; } ctx->control = ir_emit3(ctx, IR_STORE, ctx->control, addr, val); } diff --git a/ext/opcache/jit/ir/ir.h b/ext/opcache/jit/ir/ir.h index 2670fdfa96d4f..60c501d0bd470 100644 --- a/ext/opcache/jit/ir/ir.h +++ b/ext/opcache/jit/ir/ir.h @@ -297,10 +297,11 @@ typedef enum _ir_type { _(COND, d3, def, def, def) /* op1 ? op2 : op3 */ \ \ /* data-flow and miscellaneous ops */ \ + _(VADDR, d1, var, ___, ___) /* load address of local var */ \ + _(FRAME_ADDR, d0, ___, ___, ___) /* function frame address */ \ _(PHI, pN, reg, def, def) /* SSA Phi function */ \ _(COPY, d1X1, def, opt, ___) /* COPY (last foldable op) */ \ _(PI, p2, reg, def, ___) /* e-SSA Pi constraint ??? */ \ - _(FRAME_ADDR, d0, ___, ___, ___) /* function frame address */ \ /* (USE, RENAME) */ \ \ /* data ops */ \ @@ -320,7 +321,6 @@ typedef enum _ir_type { _(AFREE, a2, src, def, ___) /* revert alloca(def) */ \ _(BLOCK_BEGIN, a1, src, ___, ___) /* stacksave */ \ _(BLOCK_END, a2, src, def, ___) /* stackrestore */ \ - _(VADDR, d1, var, ___, ___) /* load address of local var */ \ _(VLOAD, l2, src, var, ___) /* load value of local var */ \ _(VSTORE, s3, src, var, def) /* store value to local var */ \ _(RLOAD, l1X2, src, num, opt) /* load value from register */ \ @@ -960,9 +960,7 @@ IR_ALWAYS_INLINE void *ir_jit_compile(ir_ctx *ctx, int opt_level, size_t *size) || !ir_mem2ssa(ctx)) { return NULL; } - if (opt_level > 1) { - ir_reset_cfg(ctx); - } + ir_reset_cfg(ctx); } if (opt_level > 1) { diff --git a/ext/opcache/jit/ir/ir_aarch64.dasc b/ext/opcache/jit/ir/ir_aarch64.dasc index b96d47461196c..772eea7a5d78a 100644 --- a/ext/opcache/jit/ir/ir_aarch64.dasc +++ b/ext/opcache/jit/ir/ir_aarch64.dasc @@ -1383,9 +1383,16 @@ static void ir_load_local_addr(ir_ctx *ctx, ir_reg reg, ir_ref src) ir_backend_data *data = ctx->data; dasm_State **Dst = &data->dasm_state; ir_reg base = (ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FRAME_POINTER : IR_REG_STACK_POINTER; - int32_t offset = IR_SPILL_POS_TO_OFFSET(ctx->ir_base[src].op3); + ir_insn *var_insn; + int32_t offset; IR_ASSERT(ir_rule(ctx, src) == IR_STATIC_ALLOCA); + var_insn = &ctx->ir_base[src]; + if (var_insn->op == IR_VADDR) { + var_insn = &ctx->ir_base[var_insn->op1]; + } + IR_ASSERT(var_insn->op == IR_VAR || var_insn->op == IR_ALLOCA); + offset = IR_SPILL_POS_TO_OFFSET(var_insn->op3); if (aarch64_may_encode_imm12(offset)) { | add Rx(reg), Rx(base), #offset } else { @@ -5680,10 +5687,15 @@ static void ir_allocate_unique_spill_slots(ir_ctx *ctx) ir_reg reg = ir_get_free_reg(constraints.tmp_regs[n].type, available); ir_ref *ops = insn->ops; IR_REGSET_EXCL(available, reg); - if (constraints.tmp_regs[n].num > 0 - && IR_IS_CONST_REF(ops[constraints.tmp_regs[n].num])) { - /* rematerialization */ - reg |= IR_REG_SPILL_LOAD; + if (constraints.tmp_regs[n].num > 0) { + if (IR_IS_CONST_REF(ops[constraints.tmp_regs[n].num])) { + /* rematerialization */ + reg |= IR_REG_SPILL_LOAD; + } else if (ctx->ir_base[ops[constraints.tmp_regs[n].num]].op == IR_ALLOCA || + ctx->ir_base[ops[constraints.tmp_regs[n].num]].op == IR_VADDR) { + /* local address rematerialization */ + reg |= IR_REG_SPILL_LOAD; + } } ctx->regs[i][constraints.tmp_regs[n].num] = reg; } else if (constraints.tmp_regs[n].reg == IR_REG_SCRATCH) { diff --git a/ext/opcache/jit/ir/ir_builder.h b/ext/opcache/jit/ir/ir_builder.h index 208c1ae4c8167..4e4ea53683afb 100644 --- a/ext/opcache/jit/ir/ir_builder.h +++ b/ext/opcache/jit/ir/ir_builder.h @@ -528,7 +528,7 @@ extern "C" { #define ir_ALLOCA(_size) _ir_ALLOCA(_ir_CTX, (_size)) #define ir_AFREE(_size) _ir_AFREE(_ir_CTX, (_size)) -#define ir_VADDR(_var) ir_emit1(_ir_CTX, IR_OPT(IR_VADDR, IR_ADDR), (_var)) +#define ir_VADDR(_var) ir_fold1(_ir_CTX, IR_OPT(IR_VADDR, IR_ADDR), (_var)) #define ir_VLOAD(_type, _var) _ir_VLOAD(_ir_CTX, (_type), (_var)) #define ir_VLOAD_B(_var) _ir_VLOAD(_ir_CTX, IR_BOOL, (_var)) #define ir_VLOAD_U8(_var) _ir_VLOAD(_ir_CTX, IR_U8, (_var)) diff --git a/ext/opcache/jit/ir/ir_check.c b/ext/opcache/jit/ir/ir_check.c index 0e0bf3ca4c535..40a475ae22f99 100644 --- a/ext/opcache/jit/ir/ir_check.c +++ b/ext/opcache/jit/ir/ir_check.c @@ -106,7 +106,10 @@ bool ir_check(const ir_ctx *ctx) use = *p; if (use != IR_UNUSED) { if (IR_IS_CONST_REF(use)) { - if (use >= ctx->consts_count) { + if (IR_OPND_KIND(flags, j) != IR_OPND_DATA) { + fprintf(stderr, "ir_base[%d].ops[%d] reference (%d) must not be constant\n", i, j, use); + ok = 0; + } else if (use >= ctx->consts_count) { fprintf(stderr, "ir_base[%d].ops[%d] constant reference (%d) is out of range\n", i, j, use); ok = 0; } diff --git a/ext/opcache/jit/ir/ir_fold.h b/ext/opcache/jit/ir/ir_fold.h index f768116976f3b..6f0bfea47820d 100644 --- a/ext/opcache/jit/ir/ir_fold.h +++ b/ext/opcache/jit/ir/ir_fold.h @@ -1469,6 +1469,27 @@ IR_FOLD(EQ(SEXT, C_ADDR)) op1 = op1_insn->op1; op2 = IR_UNUSED; IR_FOLD_RESTART; + } else { + ir_type type = ctx->ir_base[op1_insn->op1].type; + + if (IR_IS_TYPE_SIGNED(type)) { + switch (ir_type_size[type]) { + case 1: val.i64 = op2_insn->val.i8; break; + case 2: val.i64 = op2_insn->val.i16; break; + case 4: val.i64 = op2_insn->val.i32; break; + default: val.u64 = op2_insn->val.u64; break; + } + } else { + switch (ir_type_size[type]) { + case 1: val.u64 = op2_insn->val.u8; break; + case 2: val.u64 = op2_insn->val.u16; break; + case 4: val.u64 = op2_insn->val.u32; break; + default: val.u64 = op2_insn->val.u64; break; + } + } + op1 = op1_insn->op1; + op2 = ir_const(ctx, val, type); + IR_FOLD_RESTART; } IR_FOLD_NEXT; } @@ -1490,6 +1511,27 @@ IR_FOLD(NE(SEXT, C_ADDR)) { if (op2_insn->val.u64 == 0 && ctx->ir_base[op1_insn->op1].type == IR_BOOL) { IR_FOLD_COPY(op1_insn->op1); + } else { + ir_type type = ctx->ir_base[op1_insn->op1].type; + + if (IR_IS_TYPE_SIGNED(type)) { + switch (ir_type_size[type]) { + case 1: val.i64 = op2_insn->val.i8; break; + case 2: val.i64 = op2_insn->val.i16; break; + case 4: val.i64 = op2_insn->val.i32; break; + default: val.u64 = op2_insn->val.u64; break; + } + } else { + switch (ir_type_size[type]) { + case 1: val.u64 = op2_insn->val.u8; break; + case 2: val.u64 = op2_insn->val.u16; break; + case 4: val.u64 = op2_insn->val.u32; break; + default: val.u64 = op2_insn->val.u64; break; + } + } + op1 = op1_insn->op1; + op2 = ir_const(ctx, val, type); + IR_FOLD_RESTART; } IR_FOLD_NEXT; } @@ -1586,6 +1628,24 @@ IR_FOLD(SUB_OV(_, C_ADDR)) IR_FOLD_NEXT; } +/* This rule is useful for ADD(0, SYM) => SYM */ +IR_FOLD(ADD(C_U8, _)) +IR_FOLD(ADD(C_U16, _)) +IR_FOLD(ADD(C_U32, _)) +IR_FOLD(ADD(C_U64, _)) +IR_FOLD(ADD(C_I8, _)) +IR_FOLD(ADD(C_I16, _)) +IR_FOLD(ADD(C_I32, _)) +IR_FOLD(ADD(C_I64, _)) +IR_FOLD(ADD(C_ADDR, _)) +{ + if (op1_insn->val.u64 == 0) { + /* 0 + a => a */ + IR_FOLD_COPY(op2); + } + IR_FOLD_NEXT; +} + IR_FOLD(SUB(C_I8, _)) IR_FOLD(SUB(C_I16, _)) IR_FOLD(SUB(C_I32, _)) @@ -3013,6 +3073,7 @@ IR_FOLD(UGT(_, _)) } else if (op1 < op2) { /* move lower ref to op2 */ SWAP_REFS(op1, op2); opt ^= 3; /* [U]LT <-> [U]GT, [U]LE <-> [U]GE */ + IR_FOLD_RESTART; } IR_FOLD_NEXT; } diff --git a/ext/opcache/jit/ir/ir_gcm.c b/ext/opcache/jit/ir/ir_gcm.c index 12103a174d07c..be8744ef198fd 100644 --- a/ext/opcache/jit/ir/ir_gcm.c +++ b/ext/opcache/jit/ir/ir_gcm.c @@ -1059,7 +1059,7 @@ int ir_schedule(ir_ctx *ctx) if (ctx->flags & IR_DEBUG_SCHEDULE) { fprintf(stderr, "After Schedule\n"); for (i = 1; i != 0; i = _next[i]) { - fprintf(stderr, "%d -> %d\n", i, _blocks[i]); + fprintf(stderr, "%d -> %d (%d)\n", i, _blocks[i], _xlat[i]); } } #endif @@ -1328,11 +1328,13 @@ int ir_schedule(ir_ctx *ctx) new_ctx.cfg_edges = ctx->cfg_edges; ctx->cfg_blocks = NULL; ctx->cfg_edges = NULL; + ir_code_buffer *saved_code_buffer = ctx->code_buffer; ir_free(ctx); IR_ASSERT(new_ctx.consts_count == new_ctx.consts_limit); IR_ASSERT(new_ctx.insns_count == new_ctx.insns_limit); memcpy(ctx, &new_ctx, sizeof(ir_ctx)); + ctx->code_buffer = saved_code_buffer; ctx->flags2 |= IR_LINEAR; ir_mem_free(_next); diff --git a/ext/opcache/jit/ir/ir_private.h b/ext/opcache/jit/ir/ir_private.h index f980b86b89320..9c69d6074defe 100644 --- a/ext/opcache/jit/ir/ir_private.h +++ b/ext/opcache/jit/ir/ir_private.h @@ -1013,8 +1013,10 @@ IR_ALWAYS_INLINE uint32_t ir_insn_len(const ir_insn *insn) #define IR_HAS_FP_RET_SLOT (1<<10) #define IR_16B_FRAME_ALIGNMENT (1<<11) +/* Temporary: MEM2SSA -> SCCP */ +#define IR_MEM2SSA_VARS (1<<25) + /* Temporary: SCCP -> CFG */ -#define IR_SCCP_DONE (1<<25) #define IR_CFG_REACHABLE (1<<26) /* Temporary: Dominators -> Loops */ @@ -1088,6 +1090,11 @@ IR_ALWAYS_INLINE ir_ref ir_next_control(const ir_ctx *ctx, ir_ref ref) void ir_replace(ir_ctx *ctx, ir_ref ref, ir_ref new_ref); void ir_update_op(ir_ctx *ctx, ir_ref ref, uint32_t idx, ir_ref new_val); +/*** Iterative Optimization ***/ +void ir_iter_replace(ir_ctx *ctx, ir_ref ref, ir_ref new_ref, ir_bitqueue *worklist); +void ir_iter_update_op(ir_ctx *ctx, ir_ref ref, uint32_t idx, ir_ref new_val, ir_bitqueue *worklist); +void ir_iter_opt(ir_ctx *ctx, ir_bitqueue *worklist); + /*** IR Basic Blocks info ***/ #define IR_IS_BB_START(op) \ ((ir_op_flags[op] & IR_OP_FLAG_BB_START) != 0) @@ -1169,6 +1176,15 @@ typedef enum _ir_fold_action { ir_ref ir_folding(ir_ctx *ctx, uint32_t opt, ir_ref op1, ir_ref op2, ir_ref op3, ir_insn *op1_insn, ir_insn *op2_insn, ir_insn *op3_insn); +/*** Alias Analyzes (see ir.c) ***/ +ir_ref ir_find_aliasing_load(ir_ctx *ctx, ir_ref ref, ir_type type, ir_ref addr); +ir_ref ir_find_aliasing_vload(ir_ctx *ctx, ir_ref ref, ir_type type, ir_ref var); +ir_ref ir_find_aliasing_store(ir_ctx *ctx, ir_ref ref, ir_ref addr, ir_ref val); +ir_ref ir_find_aliasing_vstore(ir_ctx *ctx, ir_ref ref, ir_ref addr, ir_ref val); + +/*** Predicates (see ir.c) ***/ +ir_ref ir_check_dominating_predicates(ir_ctx *ctx, ir_ref ref, ir_ref condition); + /*** IR Live Info ***/ typedef ir_ref ir_live_pos; typedef struct _ir_use_pos ir_use_pos; diff --git a/ext/opcache/jit/ir/ir_sccp.c b/ext/opcache/jit/ir/ir_sccp.c index 680e86c508657..18192d7f1792a 100644 --- a/ext/opcache/jit/ir/ir_sccp.c +++ b/ext/opcache/jit/ir/ir_sccp.c @@ -12,39 +12,246 @@ #include "ir.h" #include "ir_private.h" +#define IR_COMBO_COPY_PROPAGATION 1 + #define IR_TOP IR_UNUSED #define IR_BOTTOM IR_LAST_OP #define IR_MAKE_TOP(ref) do {IR_ASSERT(ref > 0); _values[ref].optx = IR_TOP;} while (0) #define IR_MAKE_BOTTOM(ref) do {IR_ASSERT(ref > 0); _values[ref].optx = IR_BOTTOM;} while (0) -#define IR_IS_TOP(ref) (ref >= 0 && _values[ref].optx == IR_TOP) -#define IR_IS_BOTTOM(ref) (ref >= 0 && _values[ref].optx == IR_BOTTOM) -#define IR_IS_FEASIBLE(ref) (ref >= 0 && _values[ref].optx != IR_TOP) +#define IR_IS_TOP(ref) (ref >= 0 && _values[ref].op == IR_TOP) +#define IR_IS_BOTTOM(ref) (ref >= 0 && _values[ref].op == IR_BOTTOM) +#define IR_IS_REACHABLE(ref) _ir_is_reachable_ctrl(ctx, _values, ref) +#define IR_IS_CONST(ref) (IR_IS_CONST_REF(ref) || IR_IS_CONST_OP(_values[ref].op)) -#define IR_COMBO_COPY_PROPAGATION 1 +IR_ALWAYS_INLINE bool _ir_is_reachable_ctrl(ir_ctx *ctx, ir_insn *_values, ir_ref ref) +{ + IR_ASSERT(!IR_IS_CONST_REF(ref)); + IR_ASSERT(ir_op_flags[ctx->ir_base[ref].op] & IR_OP_FLAG_CONTROL); + return _values[ref].op != IR_TOP; /* BOTTOM, IF or MERGE */ +} + +IR_ALWAYS_INLINE void ir_sccp_add_uses(ir_ctx *ctx, ir_insn *_values, ir_bitqueue *worklist, ir_ref ref) +{ + ir_use_list *use_list; + ir_ref n, *p, use; + + IR_ASSERT(!IR_IS_CONST_REF(ref)); + use_list = &ctx->use_lists[ref]; + n = use_list->count; + for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) { + use = *p; + if (_values[use].op != IR_BOTTOM) { + ir_bitqueue_add(worklist, use); + } + } +} + +IR_ALWAYS_INLINE void ir_sccp_add_input(ir_ctx *ctx, ir_insn *_values, ir_bitqueue *worklist, ir_ref ref) +{ + IR_ASSERT(!IR_IS_CONST_REF(ref)); + IR_ASSERT(_values[ref].op == IR_TOP); + /* do backward propagaton only once */ + if (!_values[ref].op1) { + _values[ref].op1 = 1; + ir_bitqueue_add(worklist, ref); + } +} #if IR_COMBO_COPY_PROPAGATION -IR_ALWAYS_INLINE ir_ref ir_sccp_identity(ir_insn *_values, ir_ref a) +IR_ALWAYS_INLINE ir_ref ir_sccp_identity(ir_ctx *ctx, ir_insn *_values, ir_ref a) { if (a > 0 && _values[a].op == IR_COPY) { - a = _values[a].op1; - IR_ASSERT(a < 0 || _values[a].op != IR_COPY); /* this may be a copy of symbolic constant */ + do { + a = _values[a].op1; + IR_ASSERT(a > 0); + } while (_values[a].op == IR_COPY); + IR_ASSERT(_values[a].op == IR_BOTTOM); } return a; } + +#if 0 +static void CHECK_LIST(ir_insn *_values, ir_ref ref) +{ + ir_ref member = _values[ref].op2; + while (member != ref) { + IR_ASSERT(_values[_values[member].op2].op3 == member); + member = _values[member].op2; + } + IR_ASSERT(_values[_values[ref].op2].op3 == ref); +} +#else +# define CHECK_LIST(_values, ref) #endif -static ir_ref ir_sccp_fold(ir_ctx *ctx, ir_insn *_values, ir_ref res, uint32_t opt, ir_ref op1, ir_ref op2, ir_ref op3) +static void ir_sccp_add_identity(ir_ctx *ctx, ir_insn *_values, ir_ref src, ir_ref dst) { - ir_insn *op1_insn, *op2_insn, *op3_insn, *insn; + IR_ASSERT(dst > 0 && _values[dst].op != IR_BOTTOM && _values[dst].op != IR_COPY); + IR_ASSERT((src > 0 && (_values[src].op == IR_BOTTOM || _values[src].op == IR_COPY))); + IR_ASSERT(ir_sccp_identity(ctx, _values, src) != dst); + + _values[dst].optx = IR_COPY; + _values[dst].op1 = src; + + if (_values[src].op == IR_BOTTOM) { + /* initialize empty double-linked list */ + if (_values[src].op1 != src) { + _values[src].op1 = src; + _values[src].op2 = src; + _values[src].op3 = src; + } + } else { + src = ir_sccp_identity(ctx, _values, src); + } + + /* insert into circular double-linked list */ + ir_ref prev = _values[src].op3; + _values[dst].op2 = src; + _values[dst].op3 = prev; + _values[src].op3 = dst; + _values[prev].op2 = dst; + CHECK_LIST(_values, dst); +} + +static void ir_sccp_split_partition(ir_ctx *ctx, ir_insn *_values, ir_bitqueue *worklist, ir_ref ref) +{ + ir_ref member, head, tail, next, prev; + + CHECK_LIST(_values, ref); + IR_MAKE_BOTTOM(ref); + _values[ref].op1 = ref; + + member = _values[ref].op2; + head = tail = IR_UNUSED; + while (member != ref) { + if (_values[member].op != IR_BOTTOM) { + ir_bitqueue_add(worklist, member); + } + ir_sccp_add_uses(ctx, _values, worklist, member); + + next = _values[member].op2; + if (ir_sccp_identity(ctx, _values, member) == ref) { + /* remove "member" from the old circular double-linked list */ + prev = _values[member].op3; + _values[prev].op2 = next; + _values[next].op3 = prev; + + /* insert "member" into the new double-linked list */ + if (!head) { + head = tail = member; + } else { + _values[tail].op2 = member; + _values[member].op3 = tail; + tail = member; + } + } + member = next; + } + + /* remove "ref" from the old circular double-linked list */ + next = _values[ref].op2; + prev = _values[ref].op3; + _values[prev].op2 = next; + _values[next].op3 = prev; + CHECK_LIST(_values, next); + + /* close the new circle */ + if (head) { + _values[ref].op2 = head; + _values[ref].op3 = tail; + _values[tail].op2 = ref; + _values[head].op3 = ref; + } else { + _values[ref].op2 = ref; + _values[ref].op3 = ref; + } + CHECK_LIST(_values, ref); +} + +IR_ALWAYS_INLINE void ir_sccp_make_bottom_ex(ir_ctx *ctx, ir_insn *_values, ir_bitqueue *worklist, ir_ref ref) +{ + if (_values[ref].op == IR_COPY) { + ir_sccp_split_partition(ctx, _values, worklist, ref); + } else { + IR_MAKE_BOTTOM(ref); + } +} + +# define IR_MAKE_BOTTOM_EX(ref) ir_sccp_make_bottom_ex(ctx, _values, worklist, ref) +#else +# define ir_sccp_identity(_ctx, _values, ref) (ref) +# define IR_MAKE_BOTTOM_EX(ref) IR_MAKE_BOTTOM(ref) +#endif + +IR_ALWAYS_INLINE bool ir_sccp_meet_const(ir_ctx *ctx, ir_insn *_values, ir_bitqueue *worklist, ir_ref ref, ir_insn *val_insn) +{ + IR_ASSERT(IR_IS_CONST_OP(val_insn->op) || IR_IS_SYM_CONST(val_insn->op)); + + if (_values[ref].op == IR_TOP) { + /* TOP meet NEW_CONST => NEW_CONST */ + _values[ref].optx = val_insn->opt; + _values[ref].val.u64 = val_insn->val.u64; + return 1; + } else if (_values[ref].opt == val_insn->opt) { + /* OLD_CONST meet NEW_CONST => (OLD_CONST == NEW_CONST) ? OLD_CONST : BOTTOM */ + if (_values[ref].val.u64 == val_insn->val.u64) { + return 0; + } + } + + IR_MAKE_BOTTOM_EX(ref); + return 1; +} +IR_ALWAYS_INLINE bool ir_sccp_meet(ir_ctx *ctx, ir_insn *_values, ir_bitqueue *worklist, ir_ref ref, ir_ref val) +{ + ir_ref val_identity = ir_sccp_identity(ctx, _values, val); + ir_insn *val_insn; + + if (IR_IS_CONST_REF(val_identity)) { + val_insn = &ctx->ir_base[val_identity]; + } else { + val_insn = &_values[val_identity]; + + if (!IR_IS_CONST_OP(val_insn->op) && !IR_IS_SYM_CONST(val_insn->op)) { #if IR_COMBO_COPY_PROPAGATION - op1 = ir_sccp_identity(_values, op1); - op2 = ir_sccp_identity(_values, op2); - op3 = ir_sccp_identity(_values, op3); + if (_values[ref].op == IR_COPY) { + /* COPY(OLD_VAL) meet COPY(NEW_VAL) => + * (IDENTITY(OLD_VAL) == IDENTITY(NEW_VAL) ? COPY(OLD_VAL) ? BOTTOM */ + if (ir_sccp_identity(ctx, _values, ref) == val_identity) { + return 0; /* not changed */ + } + ir_sccp_split_partition(ctx, _values, worklist, ref); + return 1; + } else { + IR_ASSERT(_values[ref].op != IR_BOTTOM); + /* TOP meet COPY(NEW_VAL) -> COPY(NEW_VAL) */ + /* OLD_CONST meet COPY(NEW_VAL) -> COPY(NEW_VAL) */ + ir_sccp_add_identity(ctx, _values, val, ref); + return 1; + } #endif + IR_MAKE_BOTTOM(ref); + return 1; + } + } + + return ir_sccp_meet_const(ctx, _values, worklist, ref, val_insn); +} + +static ir_ref ir_sccp_fold(ir_ctx *ctx, ir_insn *_values, ir_bitqueue *worklist, ir_ref ref, ir_insn *insn) +{ + ir_insn *op1_insn, *op2_insn, *op3_insn; + ir_ref op1, op2, op3, copy; + uint32_t opt = insn->opt; + + op1 = ir_sccp_identity(ctx, _values, insn->op1); + op2 = ir_sccp_identity(ctx, _values, insn->op2); + op3 = ir_sccp_identity(ctx, _values, insn->op3); + restart: op1_insn = (op1 > 0 && IR_IS_CONST_OP(_values[op1].op)) ? _values + op1 : ctx->ir_base + op1; op2_insn = (op2 > 0 && IR_IS_CONST_OP(_values[op2].op)) ? _values + op2 : ctx->ir_base + op2; @@ -57,69 +264,36 @@ static ir_ref ir_sccp_fold(ir_ctx *ctx, ir_insn *_values, ir_ref res, uint32_t o op2 = ctx->fold_insn.op2; op3 = ctx->fold_insn.op3; goto restart; + case IR_FOLD_DO_CSE: case IR_FOLD_DO_EMIT: - IR_MAKE_BOTTOM(res); + IR_MAKE_BOTTOM_EX(ref); return 1; case IR_FOLD_DO_COPY: - op1 = ctx->fold_insn.op1; -#if IR_COMBO_COPY_PROPAGATION - op1 = ir_sccp_identity(_values, op1); -#endif - insn = (op1 > 0 && IR_IS_CONST_OP(_values[op1].op)) ? _values + op1 : ctx->ir_base + op1; - if (IR_IS_CONST_OP(insn->op)) { - /* pass */ -#if IR_COMBO_COPY_PROPAGATION - } else if (_values[res].optx == IR_TOP) { - _values[res].optx = IR_OPT(IR_COPY, insn->type); - _values[res].op1 = op1; - return 1; - } else if (_values[res].op == IR_COPY && _values[res].op1 == op1) { - return 0; /* not changed */ - } else { - IR_ASSERT(_values[res].optx != IR_BOTTOM); - /* we don't check for widening */ - _values[res].optx = IR_OPT(IR_COPY, insn->type); - _values[res].op1 = op1; - return 1; -#else - } else { - IR_MAKE_BOTTOM(res); - return 1; -#endif - } - break; + copy = ctx->fold_insn.op1; + return ir_sccp_meet(ctx, _values, worklist, ref, copy); case IR_FOLD_DO_CONST: - insn = &ctx->fold_insn; - break; + return ir_sccp_meet_const(ctx, _values, worklist, ref, &ctx->fold_insn); default: IR_ASSERT(0); return 0; } - - if (IR_IS_TOP(res)) { - _values[res].optx = IR_OPT(insn->type, insn->type); - _values[res].val.u64 = insn->val.u64; - return 1; - } else if (_values[res].opt != IR_OPT(insn->type, insn->type) || _values[res].val.u64 != insn->val.u64) { - IR_MAKE_BOTTOM(res); - return 1; - } - return 0; /* not changed */ } -static bool ir_sccp_meet_phi(ir_ctx *ctx, ir_insn *_values, ir_ref i, ir_insn *insn, ir_bitqueue *worklist) +static bool ir_sccp_analyze_phi(ir_ctx *ctx, ir_insn *_values, ir_bitqueue *worklist, ir_ref i, ir_insn *insn) { ir_ref j, n, input, *merge_input, *p; ir_insn *v, *new_const = NULL; #if IR_COMBO_COPY_PROPAGATION - ir_ref new_copy; + ir_ref new_copy = IR_UNUSED; + ir_ref new_copy_identity = IR_UNUSED; + ir_ref phi_identity = ir_sccp_identity(ctx, _values, i); #endif - if (!IR_IS_FEASIBLE(insn->op1)) { + if (!IR_IS_REACHABLE(insn->op1)) { return 0; } n = insn->inputs_count; - if (n > 3 && _values[i].optx == IR_TOP) { + if (n > 3 && _values[i].op == IR_TOP) { for (j = 0; j < (n>>2); j++) { _values[i+j+1].optx = IR_BOTTOM; /* keep the tail of a long multislot instruction */ } @@ -129,7 +303,7 @@ static bool ir_sccp_meet_phi(ir_ctx *ctx, ir_insn *_values, ir_ref i, ir_insn *i merge_input = ctx->ir_base[insn->op1].ops + 1; for (; --n > 0; p++, merge_input++) { IR_ASSERT(*merge_input > 0); - if (_values[*merge_input].optx == IR_TOP) { + if (!IR_IS_REACHABLE(*merge_input)) { continue; } @@ -141,34 +315,35 @@ static bool ir_sccp_meet_phi(ir_ctx *ctx, ir_insn *_values, ir_ref i, ir_insn *i } else { v = &_values[input]; if (v->op == IR_TOP) { - /* do backward propagaton only once */ - if (!v->op1) { - v->op1 = 1; - ir_bitqueue_add(worklist, input); - } + ir_sccp_add_input(ctx, _values, worklist, input); continue; #if IR_COMBO_COPY_PROPAGATION } else if (v->op == IR_COPY) { input = v->op1; - IR_ASSERT(input < 0 || _values[input].op != IR_COPY); + new_copy_identity = ir_sccp_identity(ctx, _values, input); + if (new_copy_identity == phi_identity) { + new_copy_identity = IR_UNUSED; + continue; + } new_copy = input; goto next; +#endif } else if (v->op == IR_BOTTOM) { - new_copy = input; +#if IR_COMBO_COPY_PROPAGATION + if (input == phi_identity) { + continue; + } + new_copy = new_copy_identity = input; goto next; #else - } else if (v->op == IR_BOTTOM) { - IR_MAKE_BOTTOM(i); - return 1; + goto make_bottom; #endif } } - new_copy = IR_UNUSED; new_const = v; goto next; } - IR_ASSERT(_values[i].optx == IR_TOP); return 0; next: @@ -177,78 +352,59 @@ static bool ir_sccp_meet_phi(ir_ctx *ctx, ir_insn *_values, ir_ref i, ir_insn *i /* for all live merge inputs */ for (; --n > 0; p++, merge_input++) { IR_ASSERT(*merge_input > 0); - if (_values[*merge_input].optx == IR_TOP) { + if (!IR_IS_REACHABLE(*merge_input)) { continue; } input = *p; if (IR_IS_CONST_REF(input)) { +#if IR_COMBO_COPY_PROPAGATION + if (new_copy) { + goto make_bottom; + } +#endif v = &ctx->ir_base[input]; } else if (input == i) { continue; } else { v = &_values[input]; if (v->op == IR_TOP) { - /* do backward propagaton only once */ - if (!v->op1) { - v->op1 = 1; - ir_bitqueue_add(worklist, input); - } + ir_sccp_add_input(ctx, _values, worklist, input); continue; #if IR_COMBO_COPY_PROPAGATION } else if (v->op == IR_COPY) { - input = v->op1; - IR_ASSERT(input < 0 || _values[input].op != IR_COPY); - if (new_copy == input) { + ir_ref identity = ir_sccp_identity(ctx, _values, v->op1); + + if (identity == phi_identity || identity == new_copy_identity) { continue; - } else { - IR_MAKE_BOTTOM(i); - return 1; } + goto make_bottom; +#endif } else if (v->op == IR_BOTTOM) { - if (new_copy == input) { +#if IR_COMBO_COPY_PROPAGATION + if (input == phi_identity || input == new_copy_identity) { continue; - } else { - IR_MAKE_BOTTOM(i); - return 1; } -#else - } else if (v->op == IR_BOTTOM) { - IR_MAKE_BOTTOM(i); - return 1; #endif + goto make_bottom; } } if (!new_const || new_const->opt != v->opt || new_const->val.u64 != v->val.u64) { - IR_MAKE_BOTTOM(i); - return 1; + goto make_bottom; } } #if IR_COMBO_COPY_PROPAGATION if (new_copy) { - if (_values[i].op == IR_COPY && _values[i].op1 == new_copy) { - return 0; /* not changed */ - } else { - IR_ASSERT(_values[i].optx != IR_BOTTOM); - /* we don't check for widening */ - _values[i].optx = IR_OPT(IR_COPY, ctx->ir_base[new_copy].type); - _values[i].op1 = new_copy; - return 1; - } + return ir_sccp_meet(ctx, _values, worklist, i, new_copy); } #endif - if (_values[i].optx == IR_TOP) { - _values[i].optx = new_const->opt; - _values[i].val.u64 = new_const->val.u64; - return 1; - } else if (_values[i].opt == new_const->opt && _values[i].val.u64 == new_const->val.u64) { - return 0; - } else { - IR_MAKE_BOTTOM(i); - return 1; - } + return ir_sccp_meet_const(ctx, _values, worklist, i, new_const); + +make_bottom: + IR_MAKE_BOTTOM_EX(i); + return 1; } static bool ir_is_dead_load_ex(ir_ctx *ctx, ir_ref ref, uint32_t flags, ir_insn *insn) @@ -285,25 +441,6 @@ static bool ir_is_dead(ir_ctx *ctx, ir_ref ref) return 0; } -static ir_ref ir_find1(ir_ctx *ctx, uint32_t optx, ir_ref op1) -{ - IR_ASSERT(!IR_IS_CONST_REF(op1)); - - ir_use_list *use_list = &ctx->use_lists[op1]; - ir_ref *p, n = use_list->count; - - for (p = ctx->use_edges + use_list->refs; n > 0; p++, n--) { - ir_ref use = *p; - ir_insn *use_insn = &ctx->ir_base[use]; - - if (use_insn->optx == optx) { - IR_ASSERT(use_insn->op1 == op1); - return use; - } - } - return IR_UNUSED; -} - static bool ir_sccp_is_true(ir_ctx *ctx, ir_insn *_values, ir_ref a) { ir_insn *v = IR_IS_CONST_REF(a) ? &ctx->ir_base[a] : &_values[a]; @@ -321,97 +458,408 @@ static bool ir_sccp_is_equal(ir_ctx *ctx, ir_insn *_values, ir_ref a, ir_ref b) return v1->val.u64 == v2->val.u64; } -static void ir_sccp_make_nop(ir_ctx *ctx, ir_ref ref) +#ifdef IR_SCCP_TRACE +static void ir_sccp_trace_val(ir_ctx *ctx, ir_insn *_values, ir_ref i) { - ir_ref j, n, *p; - ir_insn *insn; - - CLEAR_USES(ref); - insn = &ctx->ir_base[ref]; - n = insn->inputs_count; - insn->opt = IR_NOP; /* keep "inputs_count" */ - for (j = 1, p = insn->ops + j; j <= n; j++, p++) { - *p = IR_UNUSED; + if (IR_IS_BOTTOM(i)) { + fprintf(stderr, "BOTTOM"); + } else if (IR_IS_CONST_OP(_values[i].op) || IR_IS_SYM_CONST(_values[i].op)) { + fprintf(stderr, "CONST("); + ir_print_const(ctx, &_values[i], stderr, true); + fprintf(stderr, ")"); +#if IR_COMBO_COPY_PROPAGATION + } else if (_values[i].op == IR_COPY) { + fprintf(stderr, "COPY(%d)", _values[i].op1); +#endif + } else if (IR_IS_TOP(i)) { + fprintf(stderr, "TOP"); + } else if (_values[i].op == IR_IF) { + fprintf(stderr, "IF(%d)", _values[i].op1); + } else if (_values[i].op == IR_MERGE) { + fprintf(stderr, "MERGE(%d)", _values[i].op1); + } else { + fprintf(stderr, "%d", _values[i].op); } } -static void ir_sccp_remove_insn(ir_ctx *ctx, ir_insn *_values, ir_ref ref, ir_bitqueue *worklist) +static void ir_sccp_trace_start(ir_ctx *ctx, ir_insn *_values, ir_ref i) { - ir_ref j, n, *p; - ir_insn *insn; - - CLEAR_USES(ref); - insn = &ctx->ir_base[ref]; - n = insn->inputs_count; - insn->opt = IR_NOP; /* keep "inputs_count" */ - for (j = 1, p = insn->ops + j; j <= n; j++, p++) { - ir_ref input = *p; - *p = IR_UNUSED; - /* we may skip nodes that are going to be removed by SCCP (TOP, CONST and COPY) */ - if (input > 0 && _values[input].op > IR_COPY) { - ir_use_list_remove_all(ctx, input, ref); - if (ir_is_dead(ctx, input)) { - /* schedule DCE */ - ir_bitqueue_add(worklist, input); - } - } - } + fprintf(stderr, "%d. ", i); + ir_sccp_trace_val(ctx, _values, i); } -static void ir_sccp_remove_insn2(ir_ctx *ctx, ir_ref ref, ir_bitqueue *worklist) +static void ir_sccp_trace_end(ir_ctx *ctx, ir_insn *_values, ir_ref i) { - ir_ref j, n, *p; - ir_insn *insn; - - CLEAR_USES(ref); - insn = &ctx->ir_base[ref]; - n = insn->inputs_count; - insn->opt = IR_NOP; /* keep "inputs_count" */ - for (j = 1, p = insn->ops + j; j <= n; j++, p++) { - ir_ref input = *p; - *p = IR_UNUSED; - if (input > 0) { - ir_use_list_remove_all(ctx, input, ref); - if (ir_is_dead(ctx, input)) { - /* schedule DCE */ - ir_bitqueue_add(worklist, input); - } else if (ctx->ir_base[input].op == IR_PHI && ctx->use_lists[input].count == 1) { - /* try to optimize PHI into ABS/MIN/MAX/COND */ - ir_bitqueue_add(worklist, ctx->ir_base[input].op1); - } - } - } + fprintf(stderr, " -> "); + ir_sccp_trace_val(ctx, _values, i); + fprintf(stderr, "\n"); } +#else +# define ir_sccp_trace_start(c, v, i) +# define ir_sccp_trace_end(c, v, i) +#endif -static void ir_sccp_replace_insn(ir_ctx *ctx, ir_insn *_values, ir_ref ref, ir_ref new_ref, ir_bitqueue *worklist) +static IR_NEVER_INLINE void ir_sccp_analyze(ir_ctx *ctx, ir_insn *_values, ir_bitqueue *worklist, ir_bitqueue *iter_worklist) { - ir_ref j, n, *p, use, i; - ir_insn *insn; + ir_ref i, j, n, *p, use; ir_use_list *use_list; + ir_insn *insn, *use_insn; + uint32_t flags; - IR_ASSERT(ref != new_ref); + /* A bit modified SCCP algorithm of M. N. Wegman and F. K. Zadeck */ + worklist->pos = 0; + ir_bitset_incl(worklist->set, 1); + for (; (i = ir_bitqueue_pop(worklist)) >= 0; ir_sccp_trace_end(ctx, _values, i)) { + IR_ASSERT(_values[i].op != IR_BOTTOM); + ir_sccp_trace_start(ctx, _values, i); + insn = &ctx->ir_base[i]; + flags = ir_op_flags[insn->op]; + if (flags & IR_OP_FLAG_DATA) { + if (ctx->use_lists[i].count == 0) { + /* dead code */ + continue; + } else if (insn->op == IR_PHI) { + if (!ir_sccp_analyze_phi(ctx, _values, worklist, i, insn)) { + continue; + } + } else if (EXPECTED(IR_IS_FOLDABLE_OP(insn->op))) { + bool may_benefit = 0; + bool has_top = 0; - insn = &ctx->ir_base[ref]; - n = insn->inputs_count; - insn->opt = IR_NOP; /* keep "inputs_count" */ - for (j = 1, p = insn->ops + 1; j <= n; j++, p++) { - ir_ref input = *p; - *p = IR_UNUSED; - /* we may skip nodes that are going to be removed by SCCP (TOP, CONST and COPY) */ - if (input > 0 && _values[input].op > IR_COPY) { - ir_use_list_remove_all(ctx, input, ref); - if (ir_is_dead(ctx, input)) { - /* schedule DCE */ - ir_bitqueue_add(worklist, input); - } - } - } + if (_values[i].op != IR_TOP) { + may_benefit = 1; + } - use_list = &ctx->use_lists[ref]; - n = use_list->count; - p = &ctx->use_edges[use_list->refs]; - if (new_ref <= 0) { - /* constant or IR_UNUSED */ + IR_ASSERT(!IR_OP_HAS_VAR_INPUTS(flags)); + n = IR_INPUT_EDGES_COUNT(flags); + for (p = insn->ops + 1; n > 0; p++, n--) { + ir_ref input = *p; + if (input > 0) { + if (_values[input].op == IR_TOP) { + has_top = 1; + ir_sccp_add_input(ctx, _values, worklist, input); + } else if (_values[input].op != IR_BOTTOM) { + /* Perform folding only if some of direct inputs + * is going to be replaced by a constant or copy. + * This approach may miss some folding optimizations + * dependent on indirect inputs. e.g. reassociation. + */ + may_benefit = 1; + } + } + } + if (has_top) { + continue; + } + if (!may_benefit) { + IR_MAKE_BOTTOM_EX(i); + if (insn->op == IR_FP2FP || insn->op == IR_FP2INT || insn->op == IR_TRUNC + || insn->op == IR_ZEXT || insn->op == IR_SEXT || insn->op == IR_EQ || insn->op == IR_NE) { + ir_bitqueue_add(iter_worklist, i); + } + } else if (!ir_sccp_fold(ctx, _values, worklist, i, insn)) { + /* not changed */ + continue; + } else if (_values[i].op == IR_BOTTOM) { + insn = &ctx->ir_base[i]; + if (insn->op == IR_FP2FP || insn->op == IR_FP2INT || insn->op == IR_TRUNC + || insn->op == IR_ZEXT || insn->op == IR_SEXT || insn->op == IR_EQ || insn->op == IR_NE) { + ir_bitqueue_add(iter_worklist, i); + } + } + } else { + IR_MAKE_BOTTOM_EX(i); + } + } else if (flags & IR_OP_FLAG_BB_START) { + if (insn->op == IR_MERGE || insn->op == IR_BEGIN) { + ir_bitqueue_add(iter_worklist, i); + } + if (insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN) { + ir_ref unfeasible_inputs = 0; + + n = insn->inputs_count; + if (n > 3 && _values[i].op == IR_TOP) { + for (j = 0; j < (n>>2); j++) { + _values[i+j+1].optx = IR_BOTTOM; /* keep the tail of a long multislot instruction */ + } + } + for (p = insn->ops + 1; n > 0; p++, n--) { + ir_ref input = *p; + IR_ASSERT(input > 0); + if (!IR_IS_REACHABLE(input)) { + unfeasible_inputs++; + } + } + if (unfeasible_inputs == 0) { + IR_MAKE_BOTTOM(i); + } else if (_values[i].op != IR_MERGE || _values[i].op1 != unfeasible_inputs) { + _values[i].optx = IR_MERGE; + _values[i].op1 = unfeasible_inputs; + } else { + continue; + } + if (ctx->flags2 & IR_MEM2SSA_VARS) { + /* MEM2SSA puts new PHI at the bottom, but we like to process them now */ + use_list = &ctx->use_lists[i]; + n = use_list->count; + for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) { + use = *p; + if (_values[use].op != IR_BOTTOM) { + if (ctx->ir_base[use].op == IR_PHI) { + ir_bitqueue_del(worklist, use); + if (ctx->use_lists[use].count != 0) { + if (ir_sccp_analyze_phi(ctx, _values, worklist, use, &ctx->ir_base[use])) { + ir_sccp_add_uses(ctx, _values, worklist, use); + } + } + } else { + ir_bitqueue_add(worklist, use); + } + } + } + continue; + } + } else { + IR_ASSERT(insn->op == IR_START || IR_IS_REACHABLE(insn->op1)); + IR_MAKE_BOTTOM(i); + } + } else { + IR_ASSERT(insn->op1 > 0); + if (!IR_IS_REACHABLE(insn->op1)) { + /* control inpt is not feasible */ + continue; + } + if (insn->op == IR_IF) { + if (IR_IS_TOP(insn->op2)) { + ir_sccp_add_input(ctx, _values, worklist, insn->op2); + continue; + } + if (IR_IS_CONST(insn->op2)) { + bool b = ir_sccp_is_true(ctx, _values, insn->op2); + use_list = &ctx->use_lists[i]; + IR_ASSERT(use_list->count == 2); + p = &ctx->use_edges[use_list->refs]; + use = *p; + use_insn = &ctx->ir_base[use]; + IR_ASSERT(use_insn->op == IR_IF_TRUE || use_insn->op == IR_IF_FALSE); + if ((use_insn->op == IR_IF_TRUE) != b) { + use = *(p+1); + IR_ASSERT(ctx->ir_base[use].op == IR_IF_TRUE || ctx->ir_base[use].op == IR_IF_FALSE); + } + if (_values[i].op == IR_TOP) { + _values[i].optx = IR_IF; + _values[i].op1 = use; + ir_bitqueue_add(worklist, use); + continue; + } else if (_values[i].op == IR_IF && _values[i].op1 == use) { + continue; + } + } + IR_MAKE_BOTTOM(i); + ir_bitqueue_add(iter_worklist, i); + } else if (insn->op == IR_SWITCH) { + if (IR_IS_TOP(insn->op2)) { + ir_sccp_add_input(ctx, _values, worklist, insn->op2); + continue; + } + if (IR_IS_CONST(insn->op2)) { + ir_ref use_case = IR_UNUSED; + + use_list = &ctx->use_lists[i]; + n = use_list->count; + for (j = 0, p = &ctx->use_edges[use_list->refs]; j < n; j++, p++) { + use = *p; + IR_ASSERT(use > 0); + use_insn = &ctx->ir_base[use]; + if (use_insn->op == IR_CASE_VAL) { + if (ir_sccp_is_equal(ctx, _values, insn->op2, use_insn->op2)) { + use_case = use; + break; + } + } else if (use_insn->op == IR_CASE_DEFAULT) { + use_case = use; + } + } + if (use_case) { + use_insn = &ctx->ir_base[use_case]; + if (_values[i].op == IR_TOP) { + _values[i].optx = IR_IF; + _values[i].op1 = use_case; + ir_bitqueue_add(worklist, use_case); + continue; + } else if (_values[i].op == IR_IF || _values[i].op1 == use_case) { + continue; + } + } + } + IR_MAKE_BOTTOM(i); + } else if (ir_is_dead_load_ex(ctx, i, flags, insn)) { + /* schedule dead load elimination */ + ir_bitqueue_add(iter_worklist, i); + IR_MAKE_BOTTOM(i); + } else { + if (_values[i].op == IR_TOP) { + bool has_top = 0; + + /* control, call, load and store instructions may have unprocessed inputs */ + n = IR_INPUT_EDGES_COUNT(flags); + if (IR_OP_HAS_VAR_INPUTS(flags) && (n = insn->inputs_count) > 3) { + for (j = 0; j < (n>>2); j++) { + _values[i+j+1].optx = IR_BOTTOM; /* keep the tail of a long multislot instruction */ + } + for (j = 2, p = insn->ops + j; j <= n; j++, p++) { + IR_ASSERT(IR_OPND_KIND(flags, j) == IR_OPND_DATA); + use = *p; + if (use > 0 && _values[use].op == IR_TOP) { + has_top = 1; + ir_sccp_add_input(ctx, _values, worklist, use); + } + } + } else if (n >= 2) { + IR_ASSERT(IR_OPND_KIND(flags, 2) == IR_OPND_DATA); + use = insn->op2; + if (use > 0 && _values[use].op == IR_TOP) { + has_top = 1; + ir_sccp_add_input(ctx, _values, worklist, use); + } + if (n > 2) { + IR_ASSERT(n == 3); + IR_ASSERT(IR_OPND_KIND(flags, 3) == IR_OPND_DATA); + use = insn->op3; + if (use > 0 && _values[use].op == IR_TOP) { + has_top = 1; + ir_sccp_add_input(ctx, _values, worklist, use); + } + } + } + + if (has_top && !(flags & IR_OP_FLAG_BB_END)) { + use = ir_next_control(ctx, i); + if (_values[use].op == IR_TOP) { + has_top = 1; + /* do forward control propagaton only once */ + if (!_values[use].op1) { + _values[use].op1 = 1; + ir_bitqueue_add(worklist, use); + } + } + continue; + } + } + IR_MAKE_BOTTOM(i); + } + } + ir_sccp_add_uses(ctx, _values, worklist, i); + } + +#ifdef IR_DEBUG + if (ctx->flags & IR_DEBUG_SCCP) { + for (i = 1; i < ctx->insns_count; i++) { + if (IR_IS_CONST_OP(_values[i].op) || IR_IS_SYM_CONST(_values[i].op)) { + fprintf(stderr, "%d. CONST(", i); + ir_print_const(ctx, &_values[i], stderr, true); + fprintf(stderr, ")\n"); +#if IR_COMBO_COPY_PROPAGATION + } else if (_values[i].op == IR_COPY) { + fprintf(stderr, "%d. COPY(%d)\n", i, _values[i].op1); +#endif + } else if (IR_IS_TOP(i)) { + fprintf(stderr, "%d. TOP\n", i); + } else if (_values[i].op == IR_IF) { + fprintf(stderr, "%d. IF(%d)\n", i, _values[i].op1); + } else if (_values[i].op == IR_MERGE) { + fprintf(stderr, "%d. MERGE(%d)\n", i, _values[i].op1); + } else if (!IR_IS_BOTTOM(i)) { + fprintf(stderr, "%d. %d\n", i, _values[i].op); + } + } + } +#endif +} + +/**********************/ +/* SCCP trasformation */ +/**********************/ + +static void ir_sccp_make_nop(ir_ctx *ctx, ir_ref ref) +{ + ir_ref j, n, *p; + ir_insn *insn; + + CLEAR_USES(ref); + insn = &ctx->ir_base[ref]; + n = insn->inputs_count; + insn->opt = IR_NOP; /* keep "inputs_count" */ + for (j = 1, p = insn->ops + j; j <= n; j++, p++) { + *p = IR_UNUSED; + } +} + +static void ir_sccp_remove_insn(ir_ctx *ctx, ir_insn *_values, ir_ref ref, ir_bitqueue *worklist) +{ + ir_ref j, n, *p; + ir_insn *insn; + + CLEAR_USES(ref); + insn = &ctx->ir_base[ref]; + n = insn->inputs_count; + insn->opt = IR_NOP; /* keep "inputs_count" */ + for (j = 1, p = insn->ops + j; j <= n; j++, p++) { + ir_ref input = *p; + *p = IR_UNUSED; + /* we may skip nodes that are going to be removed by SCCP (TOP, CONST and COPY) */ + if (input > 0 && _values[input].op > IR_COPY) { + ir_use_list_remove_all(ctx, input, ref); + if (ir_is_dead(ctx, input)) { + /* schedule DCE */ + ir_bitqueue_add(worklist, input); + } + } + } +} + +static void ir_sccp_replace_insn(ir_ctx *ctx, ir_insn *_values, ir_ref ref, ir_ref new_ref, ir_bitqueue *worklist) +{ + ir_ref j, n, *p, use, i; + ir_insn *insn; + ir_use_list *use_list; + + IR_ASSERT(ref != new_ref); + + insn = &ctx->ir_base[ref]; + +#if IR_COMBO_COPY_PROPAGATION + if ((ir_op_flags[insn->op] & IR_OP_FLAG_MEM) && IR_IS_REACHABLE(insn->op1)) { + /* remove from control list */ + ir_ref prev = insn->op1; + ir_ref next = ir_next_control(ctx, ref); + ctx->ir_base[next].op1 = prev; + ir_use_list_remove_one(ctx, ref, next); + ir_use_list_replace_one(ctx, prev, ref, next); + insn->op1 = IR_UNUSED; + } +#endif + + n = insn->inputs_count; + insn->opt = IR_NOP; /* keep "inputs_count" */ + for (j = 1, p = insn->ops + 1; j <= n; j++, p++) { + ir_ref input = *p; + *p = IR_UNUSED; + /* we may skip nodes that are going to be removed by SCCP (TOP, CONST and COPY) */ + if (input > 0 && _values[input].op > IR_COPY) { + ir_use_list_remove_all(ctx, input, ref); + if (ir_is_dead(ctx, input)) { + /* schedule DCE */ + ir_bitqueue_add(worklist, input); + } + } + } + + use_list = &ctx->use_lists[ref]; + n = use_list->count; + p = &ctx->use_edges[use_list->refs]; + if (new_ref <= 0) { + /* constant or IR_UNUSED */ for (; n; p++, n--) { use = *p; /* we may skip nodes that are going to be removed by SCCP (TOP, CONST and COPY) */ @@ -429,7 +877,7 @@ static void ir_sccp_replace_insn(ir_ctx *ctx, ir_insn *_values, ir_ref ref, ir_r for (j = 0; j < n; j++, p++) { use = *p; /* we may skip nodes that are going to be removed by SCCP (TOP, CONST and COPY) */ - if (_values[use].optx == IR_BOTTOM) { + if (_values[use].op == IR_BOTTOM) { insn = &ctx->ir_base[use]; i = ir_insn_find_op(insn, ref); IR_ASSERT(i > 0); @@ -448,18 +896,294 @@ static void ir_sccp_replace_insn(ir_ctx *ctx, ir_insn *_values, ir_ref ref, ir_r CLEAR_USES(ref); } -static void ir_sccp_replace_insn2(ir_ctx *ctx, ir_ref ref, ir_ref new_ref, ir_bitqueue *worklist) +static void ir_sccp_remove_if(ir_ctx *ctx, ir_insn *_values, ir_ref ref, ir_ref dst) { - ir_ref i, j, n, *p, use; - ir_insn *insn; + ir_ref next; + ir_insn *insn, *next_insn; + + insn = &ctx->ir_base[ref]; + if (ctx->use_lists[dst].count == 1) { + next = ctx->use_edges[ctx->use_lists[dst].refs]; + next_insn = &ctx->ir_base[next]; + /* remove IF and IF_TRUE/FALSE from double linked control list */ + next_insn->op1 = insn->op1; + ir_use_list_replace_one(ctx, insn->op1, ref, next); + /* remove IF and IF_TRUE/FALSE instructions */ + ir_sccp_make_nop(ctx, ref); + ir_sccp_make_nop(ctx, dst); + } else { + insn->op2 = IR_UNUSED; + insn->optx = IR_OPTX(IR_END, IR_VOID, 1); + next_insn = &ctx->ir_base[dst]; + next_insn->op = IR_BEGIN; + } +} + +static void ir_sccp_remove_unfeasible_merge_inputs(ir_ctx *ctx, ir_insn *_values, ir_ref ref, ir_ref unfeasible_inputs) +{ + ir_ref i, j, n, k, *p, use; + ir_insn *insn, *use_insn; ir_use_list *use_list; + ir_bitset life_inputs; - IR_ASSERT(ref != new_ref); + insn = &ctx->ir_base[ref]; + IR_ASSERT(insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN); + n = insn->inputs_count; + if (n - unfeasible_inputs == 1) { + /* remove MERGE completely */ + for (j = 1; j <= n; j++) { + ir_ref input = ir_insn_op(insn, j); + if (input && IR_IS_REACHABLE(input)) { + ir_insn *input_insn = &ctx->ir_base[input]; + + IR_ASSERT(input_insn->op == IR_END || input_insn->op == IR_LOOP_END|| + input_insn->op == IR_IJMP || input_insn->op == IR_UNREACHABLE); + if (input_insn->op == IR_END || input_insn->op == IR_LOOP_END) { + ir_ref prev, next = IR_UNUSED; + ir_insn *next_insn = NULL; + + prev = input_insn->op1; + use_list = &ctx->use_lists[ref]; + if (use_list->count == 1) { + next = ctx->use_edges[use_list->refs]; + next_insn = &ctx->ir_base[next]; + } else { + k = 0; + p = &ctx->use_edges[use_list->refs]; + while (k < use_list->count) { + use = *p; + use_insn = &ctx->ir_base[use]; +#if IR_COMBO_COPY_PROPAGATION + IR_ASSERT((use_insn->op != IR_PHI) && "PHI must be already removed"); +#else + if (use_insn->op == IR_PHI) { + /* Convert PHI into COPY */ + ir_ref i, n = use_insn->inputs_count; + + for (i = 2; i <= n; i++) { + if (i != j + 1) { + ir_ref from = ir_insn_op(use_insn, i); + if (from > 0) { + ir_use_list_remove_one(ctx, from, use); + } + ir_insn_set_op(use_insn, i, IR_UNUSED); + } + } + use_insn->optx = IR_OPTX(IR_COPY, use_insn->type, 1); + use_insn->op1 = ir_insn_op(use_insn, j + 1); + ir_insn_set_op(use_insn, j + 1, IR_UNUSED); + ir_use_list_remove_one(ctx, ref, use); + p = &ctx->use_edges[use_list->refs + k]; + continue; + } +#endif + if (ir_op_flags[use_insn->op] & IR_OP_FLAG_CONTROL) { + IR_ASSERT(!next); + next = use; + next_insn = use_insn; + } else if (use_insn->op != IR_NOP) { + IR_ASSERT(use_insn->op1 == ref); + IR_ASSERT(use_insn->op == IR_VAR); + ir_ref region = prev; + while (!IR_IS_BB_START(ctx->ir_base[region].op)) { + region = ctx->ir_base[region].op1; + } + use_insn->op1 = region; + ir_use_list_add(ctx, region, use); + p = &ctx->use_edges[use_list->refs + k]; + } + k++; + p++; + } + } + IR_ASSERT(prev && next); + if (prev < next) { + /* remove MERGE and input END from double linked control list */ + next_insn->op1 = prev; + ir_use_list_replace_one(ctx, prev, input, next); + /* remove MERGE and input END instructions */ + ir_sccp_make_nop(ctx, ref); + ir_sccp_make_nop(ctx, input); + } else { + for (i = 2; i <= n; i++) { + ir_insn_set_op(insn, i, IR_UNUSED); + } + insn->op = IR_BEGIN; + insn->op1 = input; + input_insn->op = IR_END; + } + break; + } else { + for (i = 2; i <= n; i++) { + ir_insn_set_op(insn, i, IR_UNUSED); + } + insn->op = IR_BEGIN; + insn->op1 = input; + } + } + } + } else { + n = insn->inputs_count; + i = 1; + life_inputs = ir_bitset_malloc(n + 1); + for (j = 1; j <= n; j++) { + ir_ref input = ir_insn_op(insn, j); + + if (input) { + if (i != j) { + ir_insn_set_op(insn, i, input); + } + ir_bitset_incl(life_inputs, j); + i++; + } + } + j = i; + while (j <= n) { + ir_insn_set_op(insn, j, IR_UNUSED); + j++; + } + i--; + insn->inputs_count = i; + + n++; + use_list = &ctx->use_lists[ref]; + if (use_list->count > 1) { + for (k = 0, p = &ctx->use_edges[use_list->refs]; k < use_list->count; k++, p++) { + use = *p; + use_insn = &ctx->ir_base[use]; + if (use_insn->op == IR_PHI) { + i = 2; + for (j = 2; j <= n; j++) { + ir_ref input = ir_insn_op(use_insn, j); + + if (ir_bitset_in(life_inputs, j - 1)) { + IR_ASSERT(input); + if (i != j) { + ir_insn_set_op(use_insn, i, input); + } + i++; + } else if (!IR_IS_CONST_REF(input)) { + ir_use_list_remove_one(ctx, input, use); + } + } + while (i <= n) { + ir_insn_set_op(use_insn, i, IR_UNUSED); + i++; + } + use_insn->inputs_count = insn->inputs_count + 1; + } + } + } + ir_mem_free(life_inputs); + } +} + +static IR_NEVER_INLINE void ir_sccp_transform(ir_ctx *ctx, ir_insn *_values, ir_bitqueue *worklist, ir_bitqueue *iter_worklist) +{ + ir_ref i, j; + ir_insn *value; + + for (i = 1, value = _values + i; i < ctx->insns_count; value++, i++) { + if (value->op == IR_BOTTOM) { + continue; + } else if (IR_IS_CONST_OP(value->op)) { + /* replace instruction by constant */ + j = ir_const(ctx, value->val, value->type); + ir_sccp_replace_insn(ctx, _values, i, j, iter_worklist); + } else if (IR_IS_SYM_CONST(value->op)) { + /* replace instruction by constant */ + j = ir_const_ex(ctx, value->val, value->type, value->optx); + ir_sccp_replace_insn(ctx, _values, i, j, iter_worklist); +#if IR_COMBO_COPY_PROPAGATION + } else if (value->op == IR_COPY) { + ir_sccp_replace_insn(ctx, _values, i, ir_sccp_identity(ctx, _values, value->op1), iter_worklist); +#endif + } else if (value->op == IR_TOP) { + /* remove unreachable instruction */ + ir_insn *insn = &ctx->ir_base[i]; + + if (insn->op == IR_NOP) { + /* already removed */ + } else if (ir_op_flags[insn->op] & (IR_OP_FLAG_DATA|IR_OP_FLAG_MEM)) { + if (insn->op != IR_PARAM && (insn->op != IR_VAR || _values[insn->op1].op == IR_TOP)) { + ir_sccp_remove_insn(ctx, _values, i, iter_worklist); + } + } else { + if (ir_op_flags[insn->op] & IR_OP_FLAG_TERMINATOR) { + /* remove from terminators list */ + ir_ref prev = ctx->ir_base[1].op1; + if (prev == i) { + ctx->ir_base[1].op1 = insn->op3; + } else { + while (prev) { + if (ctx->ir_base[prev].op3 == i) { + ctx->ir_base[prev].op3 = insn->op3; + break; + } + prev = ctx->ir_base[prev].op3; + } + } + } + ir_sccp_replace_insn(ctx, _values, i, IR_UNUSED, iter_worklist); + } + } else if (value->op == IR_IF) { + /* remove one way IF/SWITCH */ + ir_sccp_remove_if(ctx, _values, i, value->op1); + } else if (value->op == IR_MERGE) { + /* schedule merge to remove unfeasible MERGE inputs */ + ir_bitqueue_add(worklist, i); + } + } + + while ((i = ir_bitqueue_pop(worklist)) >= 0) { + IR_ASSERT(_values[i].op == IR_MERGE); + ir_sccp_remove_unfeasible_merge_inputs(ctx, _values, i, _values[i].op1); + } +} + +/***************************/ +/* Iterative Optimizations */ +/***************************/ + +/* Modification of some instruction may open new optimization oprtunities for other + * instructions that use this one. + * + * For example, let "a = ADD(x, y)" became "a = ADD(x, C1)". In case we also have + * "b = ADD(a, C2)" we may optimize it into "b = ADD(x, C1 + C2)" and then might + * also remove "a". + * + * This implementation supports only few optimization of combinations from ir_fold.h + * + * TODO: Think abput a more general solution ??? + */ +static void ir_iter_add_related_uses(ir_ctx *ctx, ir_ref ref, ir_bitqueue *worklist) +{ + ir_insn *insn = &ctx->ir_base[ref]; + + if (insn->op == IR_ADD || insn->op == IR_SUB) { + ir_use_list *use_list = &ctx->use_lists[ref]; + + if (use_list->count == 1) { + ir_ref use = ctx->use_edges[use_list->refs]; + ir_insn *use_insn = &ctx->ir_base[ref]; + + if (use_insn->op == IR_ADD || use_insn->op == IR_SUB) { + ir_bitqueue_add(worklist, use); + } + } + } +} + +static void ir_iter_remove_insn(ir_ctx *ctx, ir_ref ref, ir_bitqueue *worklist) +{ + ir_ref j, n, *p; + ir_insn *insn; + CLEAR_USES(ref); insn = &ctx->ir_base[ref]; n = insn->inputs_count; insn->opt = IR_NOP; /* keep "inputs_count" */ - for (j = 1, p = insn->ops + 1; j <= n; j++, p++) { + for (j = 1, p = insn->ops + j; j <= n; j++, p++) { ir_ref input = *p; *p = IR_UNUSED; if (input > 0) { @@ -469,10 +1193,19 @@ static void ir_sccp_replace_insn2(ir_ctx *ctx, ir_ref ref, ir_ref new_ref, ir_bi ir_bitqueue_add(worklist, input); } else if (ctx->ir_base[input].op == IR_PHI && ctx->use_lists[input].count == 1) { /* try to optimize PHI into ABS/MIN/MAX/COND */ - ir_bitqueue_add(worklist, input); + ir_bitqueue_add(worklist, ctx->ir_base[input].op1); } } } +} + +void ir_iter_replace(ir_ctx *ctx, ir_ref ref, ir_ref new_ref, ir_bitqueue *worklist) +{ + ir_ref i, j, n, *p, use; + ir_insn *insn; + ir_use_list *use_list; + + IR_ASSERT(ref != new_ref); use_list = &ctx->use_lists[ref]; n = use_list->count; @@ -488,6 +1221,7 @@ static void ir_sccp_replace_insn2(ir_ctx *ctx, ir_ref ref, ir_ref new_ref, ir_bi ir_insn_set_op(insn, i, new_ref); /* schedule folding */ ir_bitqueue_add(worklist, use); + ir_iter_add_related_uses(ctx, use, worklist); } } else { for (j = 0; j < n; j++, p++) { @@ -507,13 +1241,162 @@ static void ir_sccp_replace_insn2(ir_ctx *ctx, ir_ref ref, ir_ref new_ref, ir_bi ir_bitqueue_add(worklist, use); } } +} + +static void ir_iter_replace_insn(ir_ctx *ctx, ir_ref ref, ir_ref new_ref, ir_bitqueue *worklist) +{ + ir_ref j, n, *p; + ir_insn *insn; + + insn = &ctx->ir_base[ref]; + n = insn->inputs_count; + insn->opt = IR_NOP; /* keep "inputs_count" */ + for (j = 1, p = insn->ops + 1; j <= n; j++, p++) { + ir_ref input = *p; + *p = IR_UNUSED; + if (input > 0) { + ir_use_list_remove_all(ctx, input, ref); + if (ir_is_dead(ctx, input)) { + /* schedule DCE */ + ir_bitqueue_add(worklist, input); + } else if (ctx->ir_base[input].op == IR_PHI && ctx->use_lists[input].count == 1) { + /* try to optimize PHI into ABS/MIN/MAX/COND */ + ir_bitqueue_add(worklist, input); + } + } + } + + ir_iter_replace(ctx, ref, new_ref, worklist); + CLEAR_USES(ref); } -static void ir_sccp_fold2(ir_ctx *ctx, ir_ref ref, ir_bitqueue *worklist) +void ir_iter_update_op(ir_ctx *ctx, ir_ref ref, uint32_t idx, ir_ref new_val, ir_bitqueue *worklist) +{ + ir_insn *insn = &ctx->ir_base[ref]; + ir_ref old_val = ir_insn_op(insn, idx); + + IR_ASSERT(old_val != new_val); + if (!IR_IS_CONST_REF(new_val)) { + ir_use_list_add(ctx, new_val, ref); + } + ir_insn_set_op(insn, idx, new_val); + if (!IR_IS_CONST_REF(old_val)) { + ir_use_list_remove_one(ctx, old_val, ref); + if (ir_is_dead(ctx, old_val)) { + /* schedule DCE */ + ir_bitqueue_add(worklist, old_val); + } + } +} + +static ir_ref ir_iter_find_cse1(ir_ctx *ctx, uint32_t optx, ir_ref op1) +{ + IR_ASSERT(!IR_IS_CONST_REF(op1)); + + ir_use_list *use_list = &ctx->use_lists[op1]; + ir_ref *p, n = use_list->count; + + for (p = ctx->use_edges + use_list->refs; n > 0; p++, n--) { + ir_ref use = *p; + ir_insn *use_insn = &ctx->ir_base[use]; + + if (use_insn->optx == optx) { + IR_ASSERT(use_insn->op1 == op1); + return use; + } + } + return IR_UNUSED; +} + +static ir_ref ir_iter_find_cse(ir_ctx *ctx, ir_ref ref, uint32_t opt, ir_ref op1, ir_ref op2, ir_ref op3, ir_bitqueue *worklist) +{ + uint32_t n = IR_INPUT_EDGES_COUNT(ir_op_flags[opt & IR_OPT_OP_MASK]); + ir_use_list *use_list = NULL; + ir_ref *p, use; + ir_insn *use_insn; + + if (n == 2) { + if (!IR_IS_CONST_REF(op1)) { + use_list = &ctx->use_lists[op1]; + } + if (!IR_IS_CONST_REF(op2) && (!use_list || use_list->count > ctx->use_lists[op2].count)) { + use_list = &ctx->use_lists[op2]; + } + if (use_list) { + n = use_list->count; + for (p = ctx->use_edges + use_list->refs; n > 0; p++, n--) { + use = *p; + if (use != ref) { + use_insn = &ctx->ir_base[use]; + if (use_insn->opt == opt && use_insn->op1 == op1 && use_insn->op2 == op2) { + IR_ASSERT(use_insn->op3 == op3); + if (use < ref) { + return use; + } else { + ir_bitqueue_add(worklist, use); + } + } + } + } + } + } else if (n < 2) { + IR_ASSERT(n == 1); + if (!IR_IS_CONST_REF(op1)) { + use_list = &ctx->use_lists[op1]; + n = use_list->count; + for (p = ctx->use_edges + use_list->refs; n > 0; p++, n--) { + use = *p; + if (use != ref) { + use_insn = &ctx->ir_base[use]; + if (use_insn->opt == opt) { + IR_ASSERT(use_insn->op1 == op1); + IR_ASSERT(use_insn->op2 == op2); + IR_ASSERT(use_insn->op3 == op3); + if (use < ref) { + return use; + } else { + ir_bitqueue_add(worklist, use); + } + } + } + } + } + } else { + IR_ASSERT(n == 3); + if (!IR_IS_CONST_REF(op1)) { + use_list = &ctx->use_lists[op1]; + } + if (!IR_IS_CONST_REF(op2) && (!use_list || use_list->count > ctx->use_lists[op2].count)) { + use_list = &ctx->use_lists[op2]; + } + if (!IR_IS_CONST_REF(op3) && (!use_list || use_list->count > ctx->use_lists[op3].count)) { + use_list = &ctx->use_lists[op3]; + } + if (use_list) { + n = use_list->count; + for (p = ctx->use_edges + use_list->refs; n > 0; p++, n--) { + use = *p; + if (use != ref) { + use_insn = &ctx->ir_base[use]; + if (use_insn->opt == opt && use_insn->op1 == op1 && use_insn->op2 == op2 && use_insn->op3 == op3) { + if (use < ref) { + return use; + } else { + ir_bitqueue_add(worklist, use); + } + } + } + } + } + } + return IR_UNUSED; +} + +static void ir_iter_fold(ir_ctx *ctx, ir_ref ref, ir_bitqueue *worklist) { uint32_t opt; - ir_ref op1, op2, op3; + ir_ref op1, op2, op3, copy; ir_insn *op1_insn, *op2_insn, *op3_insn, *insn; insn = &ctx->ir_base[ref]; @@ -534,6 +1417,14 @@ static void ir_sccp_fold2(ir_ctx *ctx, ir_ref ref, ir_bitqueue *worklist) op2 = ctx->fold_insn.op2; op3 = ctx->fold_insn.op3; goto restart; + case IR_FOLD_DO_CSE: + copy = ir_iter_find_cse(ctx, ref, ctx->fold_insn.opt, + ctx->fold_insn.op1, ctx->fold_insn.op2, ctx->fold_insn.op3, worklist); + if (copy) { + ir_iter_replace_insn(ctx, ref, copy, worklist); + break; + } + IR_FALLTHROUGH; case IR_FOLD_DO_EMIT: insn = &ctx->ir_base[ref]; if (insn->opt != ctx->fold_insn.opt @@ -555,200 +1446,45 @@ static void ir_sccp_fold2(ir_ctx *ctx, ir_ref ref, ir_bitqueue *worklist) ir_use_list_add(ctx, ctx->fold_insn.op1, ref); } } - if (insn->op2 != ctx->fold_insn.op2) { - if (insn->op2 > 0) { - ir_use_list_remove_one(ctx, insn->op2, ref); - } - if (ctx->fold_insn.op2 > 0) { - ir_use_list_add(ctx, ctx->fold_insn.op2, ref); - } - } - if (insn->op3 != ctx->fold_insn.op3) { - if (insn->op3 > 0) { - ir_use_list_remove_one(ctx, insn->op3, ref); - } - if (ctx->fold_insn.op3 > 0) { - ir_use_list_add(ctx, ctx->fold_insn.op3, ref); - } - } - insn->op1 = ctx->fold_insn.op1; - insn->op2 = ctx->fold_insn.op2; - insn->op3 = ctx->fold_insn.op3; - - use_list = &ctx->use_lists[ref]; - n = use_list->count; - for (j = 0, p = &ctx->use_edges[use_list->refs]; j < n; j++, p++) { - use = *p; - ir_bitqueue_add(worklist, use); - } - } - break; - case IR_FOLD_DO_COPY: - op1 = ctx->fold_insn.op1; - ir_sccp_replace_insn2(ctx, ref, op1, worklist); - break; - case IR_FOLD_DO_CONST: - op1 = ir_const(ctx, ctx->fold_insn.val, ctx->fold_insn.type); - ir_sccp_replace_insn2(ctx, ref, op1, worklist); - break; - default: - IR_ASSERT(0); - break; - } -} - -static void ir_sccp_remove_if(ir_ctx *ctx, ir_insn *_values, ir_ref ref, ir_ref dst) -{ - ir_ref next; - ir_insn *insn, *next_insn; - - insn = &ctx->ir_base[ref]; - if (ctx->use_lists[dst].count == 1) { - next = ctx->use_edges[ctx->use_lists[dst].refs]; - next_insn = &ctx->ir_base[next]; - /* remove IF and IF_TRUE/FALSE from double linked control list */ - next_insn->op1 = insn->op1; - ir_use_list_replace_one(ctx, insn->op1, ref, next); - /* remove IF and IF_TRUE/FALSE instructions */ - ir_sccp_make_nop(ctx, ref); - ir_sccp_make_nop(ctx, dst); - } else { - insn->op2 = IR_UNUSED; - insn->optx = IR_OPTX(IR_END, IR_VOID, 1); - next_insn = &ctx->ir_base[dst]; - next_insn->op = IR_BEGIN; - } -} - -static void ir_sccp_remove_unfeasible_merge_inputs(ir_ctx *ctx, ir_insn *_values, ir_ref ref, ir_ref unfeasible_inputs) -{ - ir_ref i, j, n, k, *p, use; - ir_insn *insn, *use_insn; - ir_use_list *use_list; - ir_bitset life_inputs; - - insn = &ctx->ir_base[ref]; - IR_ASSERT(insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN); - n = insn->inputs_count; - if (n - unfeasible_inputs == 1) { - /* remove MERGE completely */ - for (j = 1; j <= n; j++) { - ir_ref input = ir_insn_op(insn, j); - if (input && IR_IS_FEASIBLE(input)) { - ir_insn *input_insn = &ctx->ir_base[input]; - - IR_ASSERT(input_insn->op == IR_END || input_insn->op == IR_LOOP_END|| - input_insn->op == IR_IJMP || input_insn->op == IR_UNREACHABLE); - if (input_insn->op == IR_END || input_insn->op == IR_LOOP_END) { - if (input < ref) { - ir_ref prev, next = IR_UNUSED; - ir_insn *next_insn = NULL; - - prev = input_insn->op1; - use_list = &ctx->use_lists[ref]; - if (use_list->count == 1) { - next = ctx->use_edges[use_list->refs]; - next_insn = &ctx->ir_base[next]; - } else { - for (k = 0, p = &ctx->use_edges[use_list->refs]; k < use_list->count; k++, p++) { - use = *p; - use_insn = &ctx->ir_base[use]; - IR_ASSERT((use_insn->op != IR_PHI) && "PHI must be already removed"); - if (ir_op_flags[use_insn->op] & IR_OP_FLAG_CONTROL) { - IR_ASSERT(!next); - next = use; - next_insn = use_insn; - } else if (use_insn->op != IR_NOP) { - IR_ASSERT(use_insn->op1 == ref); - IR_ASSERT(use_insn->op == IR_VAR); - ir_ref region = prev; - while (!IR_IS_BB_START(ctx->ir_base[region].op)) { - region = ctx->ir_base[region].op1; - } - use_insn->op1 = region; - ir_use_list_add(ctx, region, use); - p = &ctx->use_edges[use_list->refs + k]; - } - } - } - IR_ASSERT(prev && next); - /* remove MERGE and input END from double linked control list */ - next_insn->op1 = prev; - ir_use_list_replace_one(ctx, prev, input, next); - /* remove MERGE and input END instructions */ - ir_sccp_make_nop(ctx, ref); - ir_sccp_make_nop(ctx, input); - } else { - for (i = 2; i <= n; i++) { - ir_insn_set_op(insn, i, IR_UNUSED); - } - insn->op = IR_BEGIN; - insn->op1 = input; - input_insn->op = IR_END; - } - break; - } else { - for (i = 2; i <= n; i++) { - ir_insn_set_op(insn, i, IR_UNUSED); - } - insn->op = IR_BEGIN; - insn->op1 = input; - } - } - } - } else { - n = insn->inputs_count; - i = 1; - life_inputs = ir_bitset_malloc(n + 1); - for (j = 1; j <= n; j++) { - ir_ref input = ir_insn_op(insn, j); - - if (input) { - if (i != j) { - ir_insn_set_op(insn, i, input); - } - ir_bitset_incl(life_inputs, j); - i++; - } - } - j = i; - while (j <= n) { - ir_insn_set_op(insn, j, IR_UNUSED); - j++; - } - i--; - insn->inputs_count = i; - - n++; - use_list = &ctx->use_lists[ref]; - if (use_list->count > 1) { - for (k = 0, p = &ctx->use_edges[use_list->refs]; k < use_list->count; k++, p++) { - use = *p; - use_insn = &ctx->ir_base[use]; - if (use_insn->op == IR_PHI) { - i = 2; - for (j = 2; j <= n; j++) { - ir_ref input = ir_insn_op(use_insn, j); - - if (ir_bitset_in(life_inputs, j - 1)) { - IR_ASSERT(input); - if (i != j) { - ir_insn_set_op(use_insn, i, input); - } - i++; - } else if (!IR_IS_CONST_REF(input)) { - ir_use_list_remove_one(ctx, input, use); - } + if (insn->op2 != ctx->fold_insn.op2) { + if (insn->op2 > 0) { + ir_use_list_remove_one(ctx, insn->op2, ref); } - while (i <= n) { - ir_insn_set_op(use_insn, i, IR_UNUSED); - i++; + if (ctx->fold_insn.op2 > 0) { + ir_use_list_add(ctx, ctx->fold_insn.op2, ref); } - use_insn->inputs_count = insn->inputs_count + 1; + } + if (insn->op3 != ctx->fold_insn.op3) { + if (insn->op3 > 0) { + ir_use_list_remove_one(ctx, insn->op3, ref); + } + if (ctx->fold_insn.op3 > 0) { + ir_use_list_add(ctx, ctx->fold_insn.op3, ref); + } + } + insn->op1 = ctx->fold_insn.op1; + insn->op2 = ctx->fold_insn.op2; + insn->op3 = ctx->fold_insn.op3; + + use_list = &ctx->use_lists[ref]; + n = use_list->count; + for (j = 0, p = &ctx->use_edges[use_list->refs]; j < n; j++, p++) { + use = *p; + ir_bitqueue_add(worklist, use); } } - } - ir_mem_free(life_inputs); + break; + case IR_FOLD_DO_COPY: + op1 = ctx->fold_insn.op1; + ir_iter_replace_insn(ctx, ref, op1, worklist); + break; + case IR_FOLD_DO_CONST: + op1 = ir_const(ctx, ctx->fold_insn.val, ctx->fold_insn.type); + ir_iter_replace_insn(ctx, ref, op1, worklist); + break; + default: + IR_ASSERT(0); + break; } } @@ -916,7 +1652,7 @@ static ir_ref ir_promote_f2d(ir_ctx *ctx, ir_ref ref, ir_ref use) } return insn->op1; case IR_INT2FP: - old_ref = ir_find1(ctx, IR_OPTX(IR_INT2FP, IR_DOUBLE, 1), insn->op1); + old_ref = ir_iter_find_cse1(ctx, IR_OPTX(IR_INT2FP, IR_DOUBLE, 1), insn->op1); if (old_ref) { IR_ASSERT(ctx->use_lists[ref].count == 1); ir_use_list_remove_one(ctx, insn->op1, ref); @@ -1095,7 +1831,7 @@ static ir_ref ir_ext_ref(ir_ctx *ctx, ir_ref var_ref, ir_ref src_ref, ir_op op, ir_ref ref; if (!IR_IS_CONST_REF(src_ref)) { - ref = ir_find1(ctx, optx, src_ref); + ref = ir_iter_find_cse1(ctx, optx, src_ref); if (ref) { ir_use_list_add(ctx, ref, var_ref); if (!IR_IS_CONST_REF(src_ref)) { @@ -1107,9 +1843,6 @@ static ir_ref ir_ext_ref(ir_ctx *ctx, ir_ref var_ref, ir_ref src_ref, ir_op op, } ref = ir_emit1(ctx, optx, src_ref); - ctx->use_lists = ir_mem_realloc(ctx->use_lists, ctx->insns_count * sizeof(ir_use_list)); - ctx->use_lists[ref].count = 0; - ctx->use_lists[ref].refs = IR_UNUSED; ir_use_list_add(ctx, ref, var_ref); if (!IR_IS_CONST_REF(src_ref)) { ir_use_list_replace_one(ctx, src_ref, var_ref, ref); @@ -1189,6 +1922,7 @@ static bool ir_try_promote_ext(ir_ctx *ctx, ir_ref ext_ref, ir_insn *insn, ir_bi } else { ctx->ir_base[use].op1 = ir_ext_ref(ctx, use, use_insn->op1, op, type, worklist); } + ir_bitqueue_add(worklist, use); } if (use_insn->op2 != ref) { if (IR_IS_CONST_REF(use_insn->op2) @@ -1197,11 +1931,12 @@ static bool ir_try_promote_ext(ir_ctx *ctx, ir_ref ext_ref, ir_insn *insn, ir_bi } else { ctx->ir_base[use].op2 = ir_ext_ref(ctx, use, use_insn->op2, op, type, worklist); } + ir_bitqueue_add(worklist, use); } } } - ir_sccp_replace_insn2(ctx, ext_ref, ref, worklist); + ir_iter_replace_insn(ctx, ext_ref, ref, worklist); phi_insn = &ctx->ir_base[ref]; if (IR_IS_CONST_REF(phi_insn->op2) @@ -1447,7 +2182,7 @@ static bool ir_optimize_phi(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_re ir_ref root_ref = start1->op1; ir_insn *root = &ctx->ir_base[root_ref]; - if (root->op == IR_IF && ctx->use_lists[root->op2].count == 1) { + if (root->op == IR_IF && !IR_IS_CONST_REF(root->op2) && ctx->use_lists[root->op2].count == 1) { ir_ref cond_ref = root->op2; ir_insn *cond = &ctx->ir_base[cond_ref]; ir_type type = insn->type; @@ -2118,438 +2853,279 @@ static bool ir_try_split_if_cmp(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqu * IF => | END | * | \ | | | * | +------+ | | | - * | IF_TRUE | | IF_TRUE - * IF_FALSE | MERGE - * | | - */ - - ir_use_list_remove_all(ctx, merge_ref, phi_ref); - ir_use_list_remove_all(ctx, ref, if_true_ref); - if (!IR_IS_CONST_REF(phi->op3)) { - ir_use_list_replace_one(ctx, phi->op3, phi_ref, insn->op2); - } - ir_use_list_replace_one(ctx, end1_ref, merge_ref, if_false_ref); - ir_use_list_replace_one(ctx, cond_ref, ref, end2_ref); - ir_use_list_add(ctx, end2_ref, if_true_ref); - - end2->optx = IR_OPTX(IR_IF, IR_VOID, 2); - end2->op2 = insn->op2; - - merge->optx = IR_OPTX(op, IR_VOID, 1); - merge->op1 = end2_ref; - merge->op2 = IR_UNUSED; - - cond->op1 = phi->op3; - MAKE_NOP(phi); - CLEAR_USES(phi_ref); - - insn->optx = IR_OPTX(IR_END, IR_VOID, 1); - insn->op1 = merge_ref; - insn->op2 = IR_UNUSED; - - if_true->op1 = end2_ref; - - if_false->optx = IR_OPTX(IR_MERGE, IR_VOID, 2); - if_false->op1 = end1_ref; - if_false->op2 = ref; - - ir_bitqueue_add(worklist, if_false_ref); - if (ctx->ir_base[end2->op1].op == IR_BEGIN || ctx->ir_base[end2->op1].op == IR_MERGE) { - ir_bitqueue_add(worklist, end2->op1); - } - - return 1; - } - } - } - } - } - - return 0; -} - -static void ir_optimize_merge(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_bitqueue *worklist) -{ - ir_use_list *use_list = &ctx->use_lists[merge_ref]; - - if (use_list->count == 1) { - ir_try_remove_empty_diamond(ctx, merge_ref, merge, worklist); - } else if (use_list->count == 2) { - if (merge->inputs_count == 2) { - ir_ref phi_ref = ctx->use_edges[use_list->refs]; - ir_insn *phi = &ctx->ir_base[phi_ref]; - - ir_ref next_ref = ctx->use_edges[use_list->refs + 1]; - ir_insn *next = &ctx->ir_base[next_ref]; - - if (next->op == IR_PHI) { - SWAP_REFS(phi_ref, next_ref); - SWAP_INSNS(phi, next); - } - - if (phi->op == IR_PHI && next->op != IR_PHI) { - if (next->op == IR_IF && next->op1 == merge_ref && ctx->use_lists[phi_ref].count == 1) { - if (next->op2 == phi_ref) { - if (ir_try_split_if(ctx, next_ref, next, worklist)) { - return; - } - } else { - ir_insn *cmp = &ctx->ir_base[next->op2]; - - if (cmp->op >= IR_EQ && cmp->op <= IR_UGT - && cmp->op1 == phi_ref - && IR_IS_CONST_REF(cmp->op2) - && !IR_IS_SYM_CONST(ctx->ir_base[cmp->op2].op) - && ctx->use_lists[next->op2].count == 1) { - if (ir_try_split_if_cmp(ctx, next_ref, next, worklist)) { - return; - } - } - } - } - ir_optimize_phi(ctx, merge_ref, merge, phi_ref, phi, worklist); - } - } - } -} - -int ir_sccp(ir_ctx *ctx) -{ - ir_ref i, j, n, *p, use; - ir_use_list *use_list; - ir_insn *insn, *use_insn, *value; - uint32_t flags; - ir_bitqueue worklist, worklist2; - ir_insn *_values = ir_mem_calloc(ctx->insns_count, sizeof(ir_insn)); - - ctx->flags2 |= IR_OPT_IN_SCCP; - - /* A bit modified SCCP algorithm of M. N. Wegman and F. K. Zadeck */ - ir_bitqueue_init(&worklist2, ctx->insns_count); - ir_bitqueue_init(&worklist, ctx->insns_count); - worklist.pos = 0; - ir_bitset_incl(worklist.set, 1); - while ((i = ir_bitqueue_pop(&worklist)) >= 0) { - insn = &ctx->ir_base[i]; - flags = ir_op_flags[insn->op]; - if (flags & IR_OP_FLAG_DATA) { - if (ctx->use_lists[i].count == 0) { - /* dead code */ - continue; - } else if (insn->op == IR_PHI) { - if (!ir_sccp_meet_phi(ctx, _values, i, insn, &worklist)) { - continue; - } - } else if (EXPECTED(IR_IS_FOLDABLE_OP(insn->op))) { - bool may_benefit = 0; - bool has_top = 0; - - IR_ASSERT(!IR_OP_HAS_VAR_INPUTS(flags)); - n = IR_INPUT_EDGES_COUNT(flags); - for (p = insn->ops + 1; n > 0; p++, n--) { - ir_ref input = *p; - if (input > 0) { - if (_values[input].optx == IR_TOP) { - has_top = 1; - /* do backward propagaton only once */ - if (!_values[input].op1) { - _values[input].op1 = 1; - ir_bitqueue_add(&worklist, input); - } - } else if (_values[input].optx != IR_BOTTOM) { - /* Perform folding only if some of direct inputs - * is going to be replaced by a constant or copy. - * This approach may miss some folding optimizations - * dependent on indirect inputs. e.g. reassociation. - */ - may_benefit = 1; - } - } - } - if (has_top) { - continue; - } - if (!may_benefit) { - IR_MAKE_BOTTOM(i); - if (insn->op == IR_FP2FP || insn->op == IR_FP2INT || insn->op == IR_TRUNC - || insn->op == IR_ZEXT || insn->op == IR_SEXT || insn->op == IR_EQ || insn->op == IR_NE) { - ir_bitqueue_add(&worklist2, i); - } - } else if (!ir_sccp_fold(ctx, _values, i, insn->opt, insn->op1, insn->op2, insn->op3)) { - /* not changed */ - continue; - } else if (_values[i].optx == IR_BOTTOM) { - insn = &ctx->ir_base[i]; - if (insn->op == IR_FP2FP || insn->op == IR_FP2INT || insn->op == IR_TRUNC - || insn->op == IR_ZEXT || insn->op == IR_SEXT || insn->op == IR_EQ || insn->op == IR_NE) { - ir_bitqueue_add(&worklist2, i); - } - } - } else { - IR_MAKE_BOTTOM(i); - } - } else if (flags & IR_OP_FLAG_BB_START) { - if (insn->op == IR_MERGE || insn->op == IR_BEGIN) { - ir_bitqueue_add(&worklist2, i); - } - if (insn->op == IR_MERGE || insn->op == IR_LOOP_BEGIN) { - ir_ref unfeasible_inputs = 0; - - n = insn->inputs_count; - if (n > 3 && _values[i].optx == IR_TOP) { - for (j = 0; j < (n>>2); j++) { - _values[i+j+1].optx = IR_BOTTOM; /* keep the tail of a long multislot instruction */ - } - } - for (p = insn->ops + 1; n > 0; p++, n--) { - ir_ref input = *p; - IR_ASSERT(input > 0); - if (_values[input].optx == IR_TOP) { - unfeasible_inputs++; - } - } - if (unfeasible_inputs == 0) { - IR_MAKE_BOTTOM(i); - } else if (_values[i].op1 != unfeasible_inputs) { - _values[i].optx = IR_MERGE; - _values[i].op1 = unfeasible_inputs; - } else { - continue; - } - } else { - IR_ASSERT(insn->op == IR_START || IR_IS_FEASIBLE(insn->op1)); - IR_MAKE_BOTTOM(i); - } - } else { - IR_ASSERT(insn->op1 > 0); - if (_values[insn->op1].optx == IR_TOP) { - /* control inpt is not feasible */ - continue; - } - if (insn->op == IR_IF) { - if (IR_IS_TOP(insn->op2)) { - /* do backward propagaton only once */ - if (!_values[insn->op2].op1) { - _values[insn->op2].op1 = 1; - ir_bitqueue_add(&worklist, insn->op2); - } - continue; - } - if (!IR_IS_BOTTOM(insn->op2) -#if IR_COMBO_COPY_PROPAGATION - && (IR_IS_CONST_REF(insn->op2) || _values[insn->op2].op != IR_COPY) -#endif - ) { - bool b = ir_sccp_is_true(ctx, _values, insn->op2); - use_list = &ctx->use_lists[i]; - IR_ASSERT(use_list->count == 2); - p = &ctx->use_edges[use_list->refs]; - use = *p; - use_insn = &ctx->ir_base[use]; - IR_ASSERT(use_insn->op == IR_IF_TRUE || use_insn->op == IR_IF_FALSE); - if ((use_insn->op == IR_IF_TRUE) != b) { - use = *(p+1); - IR_ASSERT(ctx->ir_base[use].op == IR_IF_TRUE || ctx->ir_base[use].op == IR_IF_FALSE); - } - if (_values[i].optx == IR_TOP) { - _values[i].optx = IR_IF; - _values[i].op1 = use; - } else if (_values[i].optx != IR_IF || _values[i].op1 != use) { - IR_MAKE_BOTTOM(i); - } - if (!IR_IS_BOTTOM(use)) { - ir_bitqueue_add(&worklist, use); - } - continue; - } - IR_MAKE_BOTTOM(i); - } else if (insn->op == IR_SWITCH) { - if (IR_IS_TOP(insn->op2)) { - /* do backward propagaton only once */ - if (!_values[insn->op2].op1) { - _values[insn->op2].op1 = 1; - ir_bitqueue_add(&worklist, insn->op2); - } - continue; - } - if (!IR_IS_BOTTOM(insn->op2) -#if IR_COMBO_COPY_PROPAGATION - && (IR_IS_CONST_REF(insn->op2) || _values[insn->op2].op != IR_COPY) -#endif - ) { - ir_ref use_case = IR_UNUSED; + * | IF_TRUE | | IF_TRUE + * IF_FALSE | MERGE + * | | + */ - use_list = &ctx->use_lists[i]; - n = use_list->count; - for (j = 0, p = &ctx->use_edges[use_list->refs]; j < n; j++, p++) { - use = *p; - IR_ASSERT(use > 0); - use_insn = &ctx->ir_base[use]; - if (use_insn->op == IR_CASE_VAL) { - if (ir_sccp_is_equal(ctx, _values, insn->op2, use_insn->op2)) { - use_case = use; - break; - } - } else if (use_insn->op == IR_CASE_DEFAULT) { - use_case = use; - } - } - if (use_case) { - use_insn = &ctx->ir_base[use_case]; - if (_values[i].optx == IR_TOP) { - _values[i].optx = IR_IF; - _values[i].op1 = use_case; - } else if (_values[i].optx != IR_IF || _values[i].op1 != use_case) { - IR_MAKE_BOTTOM(i); + ir_use_list_remove_all(ctx, merge_ref, phi_ref); + ir_use_list_remove_all(ctx, ref, if_true_ref); + if (!IR_IS_CONST_REF(phi->op3)) { + ir_use_list_replace_one(ctx, phi->op3, phi_ref, insn->op2); } - if (!IR_IS_BOTTOM(use_case)) { - ir_bitqueue_add(&worklist, use_case); + ir_use_list_replace_one(ctx, end1_ref, merge_ref, if_false_ref); + ir_use_list_replace_one(ctx, cond_ref, ref, end2_ref); + ir_use_list_add(ctx, end2_ref, if_true_ref); + + end2->optx = IR_OPTX(IR_IF, IR_VOID, 2); + end2->op2 = insn->op2; + + merge->optx = IR_OPTX(op, IR_VOID, 1); + merge->op1 = end2_ref; + merge->op2 = IR_UNUSED; + + cond->op1 = phi->op3; + MAKE_NOP(phi); + CLEAR_USES(phi_ref); + + insn->optx = IR_OPTX(IR_END, IR_VOID, 1); + insn->op1 = merge_ref; + insn->op2 = IR_UNUSED; + + if_true->op1 = end2_ref; + + if_false->optx = IR_OPTX(IR_MERGE, IR_VOID, 2); + if_false->op1 = end1_ref; + if_false->op2 = ref; + + ir_bitqueue_add(worklist, if_false_ref); + if (ctx->ir_base[end2->op1].op == IR_BEGIN || ctx->ir_base[end2->op1].op == IR_MERGE) { + ir_bitqueue_add(worklist, end2->op1); } - } - if (!IR_IS_BOTTOM(i)) { - continue; + + return 1; } } - IR_MAKE_BOTTOM(i); - } else if (ir_is_dead_load_ex(ctx, i, flags, insn)) { - /* dead load */ - _values[i].optx = IR_LOAD; - } else { - IR_MAKE_BOTTOM(i); + } + } + } - /* control, call, load and store instructions may have unprocessed inputs */ - n = IR_INPUT_EDGES_COUNT(flags); - if (IR_OP_HAS_VAR_INPUTS(flags) && (n = insn->inputs_count) > 3) { - for (j = 0; j < (n>>2); j++) { - _values[i+j+1].optx = IR_BOTTOM; /* keep the tail of a long multislot instruction */ - } - for (j = 2, p = insn->ops + j; j <= n; j++, p++) { - IR_ASSERT(IR_OPND_KIND(flags, j) == IR_OPND_DATA); - use = *p; - if (use > 0 && UNEXPECTED(_values[use].optx == IR_TOP)) { - ir_bitqueue_add(&worklist, use); + return 0; +} + +static void ir_iter_optimize_merge(ir_ctx *ctx, ir_ref merge_ref, ir_insn *merge, ir_bitqueue *worklist) +{ + ir_use_list *use_list = &ctx->use_lists[merge_ref]; + + if (use_list->count == 1) { + ir_try_remove_empty_diamond(ctx, merge_ref, merge, worklist); + } else if (use_list->count == 2) { + if (merge->inputs_count == 2) { + ir_ref phi_ref = ctx->use_edges[use_list->refs]; + ir_insn *phi = &ctx->ir_base[phi_ref]; + + ir_ref next_ref = ctx->use_edges[use_list->refs + 1]; + ir_insn *next = &ctx->ir_base[next_ref]; + + if (next->op == IR_PHI) { + SWAP_REFS(phi_ref, next_ref); + SWAP_INSNS(phi, next); + } + + if (phi->op == IR_PHI && next->op != IR_PHI) { + if (next->op == IR_IF && next->op1 == merge_ref && ctx->use_lists[phi_ref].count == 1) { + if (next->op2 == phi_ref) { + if (ir_try_split_if(ctx, next_ref, next, worklist)) { + return; } - } - } else if (n >= 2) { - IR_ASSERT(IR_OPND_KIND(flags, 2) == IR_OPND_DATA); - use = insn->op2; - if (use > 0 && UNEXPECTED(_values[use].optx == IR_TOP)) { - ir_bitqueue_add(&worklist, use); - } - if (n > 2) { - IR_ASSERT(n == 3); - IR_ASSERT(IR_OPND_KIND(flags, 3) == IR_OPND_DATA); - use = insn->op3; - if (use > 0 && UNEXPECTED(_values[use].optx == IR_TOP)) { - ir_bitqueue_add(&worklist, use); + } else { + ir_insn *cmp = &ctx->ir_base[next->op2]; + + if (cmp->op >= IR_EQ && cmp->op <= IR_UGT + && cmp->op1 == phi_ref + && IR_IS_CONST_REF(cmp->op2) + && !IR_IS_SYM_CONST(ctx->ir_base[cmp->op2].op) + && ctx->use_lists[next->op2].count == 1) { + if (ir_try_split_if_cmp(ctx, next_ref, next, worklist)) { + return; + } } } } + ir_optimize_phi(ctx, merge_ref, merge, phi_ref, phi, worklist); } } - use_list = &ctx->use_lists[i]; - n = use_list->count; - for (p = &ctx->use_edges[use_list->refs]; n > 0; p++, n--) { - use = *p; - if (_values[use].optx != IR_BOTTOM) { - ir_bitqueue_add(&worklist, use); - } + } +} + +static ir_ref ir_iter_optimize_condition(ir_ctx *ctx, ir_ref control, ir_ref condition, bool *swap) +{ + ir_insn *condition_insn = &ctx->ir_base[condition]; + + if (condition_insn->opt == IR_OPT(IR_NOT, IR_BOOL)) { + *swap = 1; + condition = condition_insn->op1; + condition_insn = &ctx->ir_base[condition]; + } + + if (condition_insn->op == IR_NE && IR_IS_CONST_REF(condition_insn->op2)) { + ir_insn *val_insn = &ctx->ir_base[condition_insn->op2]; + + if (IR_IS_TYPE_INT(val_insn->type) && val_insn->val.u64 == 0) { + condition = condition_insn->op1; + condition_insn = &ctx->ir_base[condition]; + } + } else if (condition_insn->op == IR_EQ && IR_IS_CONST_REF(condition_insn->op2)) { + ir_insn *val_insn = &ctx->ir_base[condition_insn->op2]; + + if (condition_insn->op2 == IR_TRUE) { + condition = condition_insn->op1; + condition_insn = &ctx->ir_base[condition]; + } else if (IR_IS_TYPE_INT(val_insn->type) && val_insn->val.u64 == 0) { + condition = condition_insn->op1; + condition_insn = &ctx->ir_base[condition]; + *swap = !*swap; } } -#ifdef IR_DEBUG - if (ctx->flags & IR_DEBUG_SCCP) { - for (i = 1; i < ctx->insns_count; i++) { - if (IR_IS_CONST_OP(_values[i].op) || IR_IS_SYM_CONST(_values[i].op)) { - fprintf(stderr, "%d. CONST(", i); - ir_print_const(ctx, &_values[i], stderr, true); - fprintf(stderr, ")\n"); -#if IR_COMBO_COPY_PROPAGATION - } else if (_values[i].op == IR_COPY) { - fprintf(stderr, "%d. COPY(%d)\n", i, _values[i].op1); -#endif - } else if (IR_IS_TOP(i)) { - fprintf(stderr, "%d. TOP\n", i); - } else if (_values[i].op == IR_IF) { - fprintf(stderr, "%d. IF(%d)\n", i, _values[i].op1); - } else if (_values[i].op == IR_MERGE) { - fprintf(stderr, "%d. MERGE(%d)\n", i, _values[i].op1); - } else if (!IR_IS_BOTTOM(i)) { - fprintf(stderr, "%d. %d\n", i, _values[i].op); - } + while ((condition_insn->op == IR_BITCAST + || condition_insn->op == IR_ZEXT + || condition_insn->op == IR_SEXT) + && ctx->use_lists[condition].count == 1) { + condition = condition_insn->op1; + condition_insn = &ctx->ir_base[condition]; + } + + if (!IR_IS_CONST_REF(condition) && ctx->use_lists[condition].count > 1) { + condition = ir_check_dominating_predicates(ctx, control, condition); + } + + return condition; +} + +static void ir_iter_optimize_if(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqueue *worklist) +{ + bool swap = 0; + ir_ref condition = ir_iter_optimize_condition(ctx, insn->op1, insn->op2, &swap); + + if (swap) { + ir_use_list *use_list = &ctx->use_lists[ref]; + ir_ref *p, use; + + IR_ASSERT(use_list->count == 2); + p = ctx->use_edges + use_list->refs; + use = *p; + if (ctx->ir_base[use].op == IR_IF_TRUE) { + ctx->ir_base[use].op = IR_IF_FALSE; + use = *(p+1); + ctx->ir_base[use].op = IR_IF_TRUE; + } else { + ctx->ir_base[use].op = IR_IF_TRUE; + use = *(p+1); + ctx->ir_base[use].op = IR_IF_FALSE; } - } -#endif + } - for (i = 1, value = _values + i; i < ctx->insns_count; value++, i++) { - if (value->op == IR_BOTTOM) { - continue; - } else if (IR_IS_CONST_OP(value->op)) { - /* replace instruction by constant */ - j = ir_const(ctx, value->val, value->type); - ir_sccp_replace_insn(ctx, _values, i, j, &worklist2); - } else if (IR_IS_SYM_CONST(value->op)) { - /* replace instruction by constant */ - j = ir_const_ex(ctx, value->val, value->type, value->optx); - ir_sccp_replace_insn(ctx, _values, i, j, &worklist2); -#if IR_COMBO_COPY_PROPAGATION - } else if (value->op == IR_COPY) { - ir_sccp_replace_insn(ctx, _values, i, value->op1, &worklist2); -#endif - } else if (value->op == IR_TOP) { - /* remove unreachable instruction */ - insn = &ctx->ir_base[i]; - if (insn->op == IR_NOP) { - /* already removed */ - } else if (ir_op_flags[insn->op] & (IR_OP_FLAG_DATA|IR_OP_FLAG_MEM)) { - if (insn->op != IR_PARAM && (insn->op != IR_VAR || _values[insn->op1].op == IR_TOP)) { - ir_sccp_remove_insn(ctx, _values, i, &worklist2); - } - } else { - if (ir_op_flags[insn->op] & IR_OP_FLAG_TERMINATOR) { - /* remove from terminators list */ - ir_ref prev = ctx->ir_base[1].op1; - if (prev == i) { - ctx->ir_base[1].op1 = insn->op3; - } else { - while (prev) { - if (ctx->ir_base[prev].op3 == i) { - ctx->ir_base[prev].op3 = insn->op3; - break; - } - prev = ctx->ir_base[prev].op3; - } + if (IR_IS_CONST_REF(condition)) { + /* + * | | + * IF(TRUE) => END + * | \ | + * | +------+ | + * | IF_TRUE | BEGIN(unreachable) + * IF_FALSE | BEGIN + * | | + */ + ir_ref if_true_ref, if_false_ref; + ir_insn *if_true, *if_false; + + insn->optx = IR_OPTX(IR_END, IR_VOID, 1); + if (!IR_IS_CONST_REF(insn->op2)) { + ir_use_list_remove_one(ctx, insn->op2, ref); + } + insn->op2 = IR_UNUSED; + + ir_get_true_false_refs(ctx, ref, &if_true_ref, &if_false_ref); + if_true = &ctx->ir_base[if_true_ref]; + if_false = &ctx->ir_base[if_false_ref]; + if_true->op = IR_BEGIN; + if_false->op = IR_BEGIN; + if (ir_ref_is_true(ctx, condition)) { + if_false->op1 = IR_UNUSED; + ir_use_list_remove_one(ctx, ref, if_false_ref); + ir_bitqueue_add(worklist, if_true_ref); + } else { + if_true->op1 = IR_UNUSED; + ir_use_list_remove_one(ctx, ref, if_true_ref); + ir_bitqueue_add(worklist, if_false_ref); + } + ctx->flags2 &= ~IR_CFG_REACHABLE; + } else if (insn->op2 != condition) { + ir_iter_update_op(ctx, ref, 2, condition, worklist); + } +} + +static void ir_iter_optimize_guard(ir_ctx *ctx, ir_ref ref, ir_insn *insn, ir_bitqueue *worklist) +{ + bool swap; + ir_ref condition = ir_iter_optimize_condition(ctx, insn->op1, insn->op2, &swap); + + if (swap) { + if (insn->op == IR_GUARD) { + insn->op = IR_GUARD_NOT; + } else { + insn->op = IR_GUARD; + } + } + + if (IR_IS_CONST_REF(condition)) { + if (insn->op == IR_GUARD) { + if (ir_ref_is_true(ctx, condition)) { + ir_ref prev, next; + +remove_guard: + prev = insn->op1; + next = ir_next_control(ctx, ref); + ctx->ir_base[next].op1 = prev; + ir_use_list_remove_one(ctx, ref, next); + ir_use_list_replace_one(ctx, prev, ref, next); + insn->op1 = IR_UNUSED; + + if (!IR_IS_CONST_REF(insn->op2)) { + ir_use_list_remove_one(ctx, insn->op2, ref); + if (ir_is_dead(ctx, insn->op2)) { + /* schedule DCE */ + ir_bitqueue_add(worklist, insn->op2); } } - ir_sccp_replace_insn(ctx, _values, i, IR_UNUSED, &worklist2); + + if (insn->op3) { + /* SNAPSHOT */ + ir_iter_remove_insn(ctx, insn->op3, worklist); + } + + MAKE_NOP(insn); + return; + } else { + condition = IR_FALSE; + } + } else { + if (ir_ref_is_true(ctx, condition)) { + condition = IR_TRUE; + } else { + goto remove_guard; } - } else if (value->op == IR_IF) { - /* remove one way IF/SWITCH */ - ir_sccp_remove_if(ctx, _values, i, value->op1); - } else if (value->op == IR_MERGE) { - /* schedule merge to remove unfeasible MERGE inputs */ - ir_bitqueue_add(&worklist, i); - } else if (value->op == IR_LOAD) { - /* schedule dead load elimination */ - ir_bitqueue_add(&worklist2, i); } } - while ((i = ir_bitqueue_pop(&worklist)) >= 0) { - IR_ASSERT(_values[i].op == IR_MERGE); - ir_sccp_remove_unfeasible_merge_inputs(ctx, _values, i, _values[i].op1); + if (insn->op2 != condition) { + ir_iter_update_op(ctx, ref, 2, condition, worklist); } +} - ctx->flags2 |= IR_CFG_REACHABLE; +void ir_iter_opt(ir_ctx *ctx, ir_bitqueue *worklist) +{ + ir_ref i, val; + ir_insn *insn; - while ((i = ir_bitqueue_pop(&worklist2)) >= 0) { + while ((i = ir_bitqueue_pop(worklist)) >= 0) { insn = &ctx->ir_base[i]; if (IR_IS_FOLDABLE_OP(insn->op)) { if (ctx->use_lists[i].count == 0) { if (insn->op == IR_PHI) { - ir_bitqueue_add(&worklist2, insn->op1); + ir_bitqueue_add(worklist, insn->op1); } - ir_sccp_remove_insn2(ctx, i, &worklist2); + ir_iter_remove_insn(ctx, i, worklist); } else { insn = &ctx->ir_base[i]; switch (insn->op) { @@ -2558,14 +3134,14 @@ int ir_sccp(ir_ctx *ctx) if (ir_may_promote_d2f(ctx, insn->op1)) { ir_ref ref = ir_promote_d2f(ctx, insn->op1, i); insn->op1 = ref; - ir_sccp_replace_insn2(ctx, i, ref, &worklist2); + ir_iter_replace_insn(ctx, i, ref, worklist); break; } } else { if (ir_may_promote_f2d(ctx, insn->op1)) { ir_ref ref = ir_promote_f2d(ctx, insn->op1, i); insn->op1 = ref; - ir_sccp_replace_insn2(ctx, i, ref, &worklist2); + ir_iter_replace_insn(ctx, i, ref, worklist); break; } } @@ -2585,13 +3161,13 @@ int ir_sccp(ir_ctx *ctx) if (ir_may_promote_i2i(ctx, insn->type, insn->op1)) { ir_ref ref = ir_promote_i2i(ctx, insn->type, insn->op1, i); insn->op1 = ref; - ir_sccp_replace_insn2(ctx, i, ref, &worklist2); + ir_iter_replace_insn(ctx, i, ref, worklist); break; } goto folding; case IR_SEXT: case IR_ZEXT: - if (ir_try_promote_ext(ctx, i, insn, &worklist2)) { + if (ir_try_promote_ext(ctx, i, insn, worklist)) { break; } goto folding; @@ -2599,7 +3175,7 @@ int ir_sccp(ir_ctx *ctx) break; default: folding: - ir_sccp_fold2(ctx, i, &worklist2); + ir_iter_fold(ctx, i, worklist); break; } } @@ -2609,28 +3185,127 @@ int ir_sccp(ir_ctx *ctx) } else if (insn->op == IR_BEGIN) { if (ctx->ir_base[insn->op1].op == IR_END && ctx->use_lists[i].count == 1) { - ir_merge_blocks(ctx, insn->op1, i, &worklist2); + ir_merge_blocks(ctx, insn->op1, i, worklist); } } else if (insn->op == IR_MERGE) { - ir_optimize_merge(ctx, i, insn, &worklist2); + ir_iter_optimize_merge(ctx, i, insn, worklist); } } else if (ir_is_dead_load(ctx, i)) { - ir_ref next = ctx->use_edges[ctx->use_lists[i].refs]; + ir_ref next; /* remove LOAD from double linked control list */ +remove_mem_insn: + next = ctx->use_edges[ctx->use_lists[i].refs]; + IR_ASSERT(ctx->use_lists[i].count == 1); ctx->ir_base[next].op1 = insn->op1; ir_use_list_replace_one(ctx, insn->op1, i, next); insn->op1 = IR_UNUSED; - ir_sccp_remove_insn2(ctx, i, &worklist2); + ir_iter_remove_insn(ctx, i, worklist); + } else if (insn->op == IR_LOAD) { + val = ir_find_aliasing_load(ctx, insn->op1, insn->type, insn->op2); + if (val) { + ir_insn *val_insn; + ir_ref prev, next; + +remove_aliased_load: + prev = insn->op1; + next = ir_next_control(ctx, i); + ctx->ir_base[next].op1 = prev; + ir_use_list_remove_one(ctx, i, next); + ir_use_list_replace_one(ctx, prev, i, next); + insn->op1 = IR_UNUSED; + + val_insn = &ctx->ir_base[val]; + if (val_insn->type == insn->type) { + ir_iter_replace_insn(ctx, i, val, worklist); + } else { + IR_ASSERT(!IR_IS_CONST_REF(insn->op2)); + ir_use_list_remove_one(ctx, insn->op2, i); + if (ir_is_dead(ctx, insn->op2)) { + /* schedule DCE */ + ir_bitqueue_add(worklist, insn->op2); + } + if (!IR_IS_CONST_REF(val)) { + ir_use_list_add(ctx, val, i); + } + if (ir_type_size[val_insn->type] == ir_type_size[insn->type]) { + /* load forwarding with bitcast (L2L) */ + insn->optx = IR_OPTX(IR_BITCAST, insn->type, 1); + } else { + /* partial load forwarding (L2L) */ + insn->optx = IR_OPTX(IR_TRUNC, insn->type, 1); + } + insn->op1 = val; + insn->op2 = IR_UNUSED; + ir_bitqueue_add(worklist, i); + } + } + } else if (insn->op == IR_STORE) { + if (ir_find_aliasing_store(ctx, insn->op1, insn->op2, insn->op3)) { + goto remove_mem_insn; + } else { + ir_insn *val_insn; + +remove_bitcast: + val = insn->op3; + val_insn = &ctx->ir_base[val]; + if (val_insn->op == IR_BITCAST + && ir_type_size[val_insn->type] == ir_type_size[ctx->ir_base[val_insn->op1].type]) { + insn->op3 = val_insn->op1; + ir_use_list_remove_one(ctx, val, i); + if (ctx->use_lists[val].count == 0) { + if (!IR_IS_CONST_REF(val_insn->op1)) { + ir_use_list_replace_one(ctx, val_insn->op1, val, i); + } + ir_iter_remove_insn(ctx, val, worklist); + } else { + if (!IR_IS_CONST_REF(val_insn->op1)) { + ir_use_list_add(ctx, val_insn->op1, i); + } + } + } + } + } else if (insn->op == IR_VLOAD) { + val = ir_find_aliasing_vload(ctx, insn->op1, insn->type, insn->op2); + if (val) { + goto remove_aliased_load; + } + } else if (insn->op == IR_VSTORE) { + if (ir_find_aliasing_vstore(ctx, insn->op1, insn->op2, insn->op3)) { + goto remove_mem_insn; + } else { + goto remove_bitcast; + } + } else if (insn->op == IR_IF) { + ir_iter_optimize_if(ctx, i, insn, worklist); + } else if (insn->op == IR_GUARD || insn->op == IR_GUARD_NOT) { + ir_iter_optimize_guard(ctx, i, insn, worklist); } } +} - ir_mem_free(_values); - ir_bitqueue_free(&worklist); - ir_bitqueue_free(&worklist2); +int ir_sccp(ir_ctx *ctx) +{ + ir_bitqueue sccp_worklist, iter_worklist; + ir_insn *_values; + ir_bitqueue_init(&iter_worklist, ctx->insns_count); + ir_bitqueue_init(&sccp_worklist, ctx->insns_count); + _values = ir_mem_calloc(ctx->insns_count, sizeof(ir_insn)); + + ctx->flags2 |= IR_OPT_IN_SCCP; + ir_sccp_analyze(ctx, _values, &sccp_worklist, &iter_worklist); + ir_sccp_transform(ctx, _values, &sccp_worklist, &iter_worklist); ctx->flags2 &= ~IR_OPT_IN_SCCP; - ctx->flags2 |= IR_SCCP_DONE; + + ir_mem_free(_values); + ir_bitqueue_free(&sccp_worklist); + + ctx->flags2 |= IR_CFG_REACHABLE; + + ir_iter_opt(ctx, &iter_worklist); + + ir_bitqueue_free(&iter_worklist); return 1; } diff --git a/ext/opcache/jit/ir/ir_x86.dasc b/ext/opcache/jit/ir/ir_x86.dasc index 58c6ed40f7df2..c4f0eae01c004 100644 --- a/ext/opcache/jit/ir/ir_x86.dasc +++ b/ext/opcache/jit/ir/ir_x86.dasc @@ -3090,9 +3090,16 @@ static void ir_load_local_addr(ir_ctx *ctx, ir_reg reg, ir_ref src) ir_backend_data *data = ctx->data; dasm_State **Dst = &data->dasm_state; ir_reg base = (ctx->flags & IR_USE_FRAME_POINTER) ? IR_REG_FRAME_POINTER : IR_REG_STACK_POINTER; - int32_t offset = IR_SPILL_POS_TO_OFFSET(ctx->ir_base[src].op3); + ir_insn *var_insn; + int32_t offset; IR_ASSERT(ir_rule(ctx, src) == IR_STATIC_ALLOCA); + var_insn = &ctx->ir_base[src]; + if (var_insn->op == IR_VADDR) { + var_insn = &ctx->ir_base[var_insn->op1]; + } + IR_ASSERT(var_insn->op == IR_VAR || var_insn->op == IR_ALLOCA); + offset = IR_SPILL_POS_TO_OFFSET(var_insn->op3); if (offset == 0) { | mov Ra(reg), Ra(base) } else { @@ -6407,16 +6414,23 @@ static void ir_emit_cond_cmp_int(ir_ctx *ctx, ir_ref def, ir_insn *insn) ir_reg op3_reg = ctx->regs[def][3]; ir_op op; - if (op2_reg != IR_REG_NONE && IR_REG_SPILLED(op2_reg)) { + if (op2 != op3) { + if (op2_reg != IR_REG_NONE && IR_REG_SPILLED(op2_reg)) { + op2_reg = IR_REG_NUM(op2_reg); + ir_emit_load(ctx, type, op2_reg, op2); + } + if (op3_reg != IR_REG_NONE && IR_REG_SPILLED(op3_reg)) { + op3_reg = IR_REG_NUM(op3_reg); + ir_emit_load(ctx, type, op3_reg, op3); + } + } else if (op2_reg != IR_REG_NONE && IR_REG_SPILLED(op2_reg)) { op2_reg = IR_REG_NUM(op2_reg); ir_emit_load(ctx, type, op2_reg, op2); - if (op3 == op2) { - op3_reg = op2_reg; - } - } - if (op3_reg != IR_REG_NONE && op3 != op2 && IR_REG_SPILLED(op3_reg)) { + op3_reg = op2_reg; + } else if (op3_reg != IR_REG_NONE && IR_REG_SPILLED(op3_reg)) { op3_reg = IR_REG_NUM(op3_reg); ir_emit_load(ctx, type, op3_reg, op3); + op2_reg = op3_reg; } ir_emit_cmp_int_common2(ctx, def, insn->op1, &ctx->ir_base[insn->op1]); @@ -6571,16 +6585,23 @@ static void ir_emit_cond_cmp_fp(ir_ctx *ctx, ir_ref def, ir_insn *insn) ir_reg op3_reg = ctx->regs[def][3]; ir_op op; - if (op2_reg != IR_REG_NONE && IR_REG_SPILLED(op2_reg)) { + if (op2 != op3) { + if (op2_reg != IR_REG_NONE && IR_REG_SPILLED(op2_reg)) { + op2_reg = IR_REG_NUM(op2_reg); + ir_emit_load(ctx, type, op2_reg, op2); + } + if (op3_reg != IR_REG_NONE && IR_REG_SPILLED(op3_reg)) { + op3_reg = IR_REG_NUM(op3_reg); + ir_emit_load(ctx, type, op3_reg, op3); + } + } else if (op2_reg != IR_REG_NONE && IR_REG_SPILLED(op2_reg)) { op2_reg = IR_REG_NUM(op2_reg); ir_emit_load(ctx, type, op2_reg, op2); - if (op3 == op2) { - op3_reg = op2_reg; - } - } - if (op3_reg != IR_REG_NONE && op3 != op2 && IR_REG_SPILLED(op3_reg)) { + op3_reg = op2_reg; + } else if (op3_reg != IR_REG_NONE && IR_REG_SPILLED(op3_reg)) { op3_reg = IR_REG_NUM(op3_reg); ir_emit_load(ctx, type, op3_reg, op3); + op2_reg = op3_reg; } op = ir_emit_cmp_fp_common(ctx, def, insn->op1, &ctx->ir_base[insn->op1]); @@ -7569,7 +7590,11 @@ static void ir_emit_vaddr(ir_ctx *ctx, ir_ref def, ir_insn *insn) mem = ir_var_spill_slot(ctx, insn->op1); fp = IR_MEM_BASE(mem); offset = IR_MEM_OFFSET(mem); - | lea Ra(def_reg), aword [Ra(fp)+offset] + if (offset == 0) { + | mov Ra(def_reg), Ra(fp) + } else { + | lea Ra(def_reg), aword [Ra(fp)+offset] + } if (IR_REG_SPILLED(ctx->regs[def][0])) { ir_emit_store(ctx, type, def, def_reg); } @@ -10237,10 +10262,15 @@ static void ir_allocate_unique_spill_slots(ir_ctx *ctx) ir_reg reg = ir_get_free_reg(constraints.tmp_regs[n].type, available); ir_ref *ops = insn->ops; IR_REGSET_EXCL(available, reg); - if (constraints.tmp_regs[n].num > 0 - && IR_IS_CONST_REF(ops[constraints.tmp_regs[n].num])) { - /* rematerialization */ - reg |= IR_REG_SPILL_LOAD; + if (constraints.tmp_regs[n].num > 0) { + if (IR_IS_CONST_REF(ops[constraints.tmp_regs[n].num])) { + /* rematerialization */ + reg |= IR_REG_SPILL_LOAD; + } else if (ctx->ir_base[ops[constraints.tmp_regs[n].num]].op == IR_ALLOCA || + ctx->ir_base[ops[constraints.tmp_regs[n].num]].op == IR_VADDR) { + /* local address rematerialization */ + reg |= IR_REG_SPILL_LOAD; + } } ctx->regs[i][constraints.tmp_regs[n].num] = reg; } else if (constraints.tmp_regs[n].reg == IR_REG_SCRATCH) { diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 0aea4eab2bff7..b7841fd1701a8 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -8676,6 +8676,7 @@ static int zend_jit_push_call_frame(zend_jit_ctx *jit, const zend_op *opline, co ir_STORE(jit_CALL(rx, This), IR_NULL); } else { ir_ref object_or_called_scope, call_info, call_info2, object, if_cond; + ir_ref if_cond_user = IR_UNUSED; if (opline->op2_type == IS_CV) { // JIT: GC_ADDREF(closure); @@ -8713,15 +8714,22 @@ static int zend_jit_push_call_frame(zend_jit_ctx *jit, const zend_op *opline, co // JIT: Z_PTR(call->This) = object_or_called_scope; ir_STORE(jit_CALL(rx, This.value.ptr), object_or_called_scope); - // JIT: if (closure->func.op_array.run_time_cache__ptr) - if_cond = ir_IF(ir_LOAD_A(ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func.op_array.run_time_cache__ptr)))); - ir_IF_FALSE(if_cond); + if (!func) { + // JIT: if (closure->func.common.type & ZEND_USER_FUNCTION) + ir_ref type = ir_LOAD_U8(ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func.type))); + if_cond_user = ir_IF(ir_AND_U8(type, ir_CONST_U8(ZEND_USER_FUNCTION))); + ir_IF_TRUE(if_cond_user); + } - // JIT: zend_jit_init_func_run_time_cache_helper(closure->func); - ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_init_func_run_time_cache_helper), - ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func))); + if (!func || func->common.type == ZEND_USER_FUNCTION) { + // JIT: zend_jit_init_func_run_time_cache_helper(closure->func); + ir_CALL_1(IR_VOID, ir_CONST_FC_FUNC(zend_jit_init_func_run_time_cache_helper), + ir_ADD_OFFSET(func_ref, offsetof(zend_closure, func))); + } - ir_MERGE_WITH_EMPTY_TRUE(if_cond); + if (!func) { + ir_MERGE_WITH_EMPTY_FALSE(if_cond_user); + } } // JIT: ZEND_CALL_NUM_ARGS(call) = num_args; @@ -9871,9 +9879,9 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen func = call_info->callee_func; } if ((op_array->fn_flags & ZEND_ACC_TRAIT_CLONE) - && JIT_G(current_frame) - && JIT_G(current_frame)->call - && !JIT_G(current_frame)->call->func) { + && (!JIT_G(current_frame) + || !JIT_G(current_frame)->call + || !JIT_G(current_frame)->call->func)) { call_info = NULL; func = NULL; /* megamorphic call from trait */ } } @@ -14539,7 +14547,11 @@ static int zend_jit_fetch_obj(zend_jit_ctx *jit, } if (may_throw) { - zend_jit_check_exception(jit); + if (Z_MODE(res_addr) == IS_REG) { + zend_jit_check_exception_undef_result(jit, opline); + } else { + zend_jit_check_exception(jit); + } } return 1; diff --git a/ext/opcache/jit/zend_jit_trace.c b/ext/opcache/jit/zend_jit_trace.c index 8f20c85da05b1..4ee0a8a413c8e 100644 --- a/ext/opcache/jit/zend_jit_trace.c +++ b/ext/opcache/jit/zend_jit_trace.c @@ -1848,7 +1848,8 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin if (!(orig_op1_type & IS_TRACE_PACKED)) { zend_ssa_var_info *info = &tssa->var_info[tssa->ops[idx].op1_use]; - if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type)) { + if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type) + && (info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { info->type |= MAY_BE_PACKED_GUARD; info->type &= ~MAY_BE_ARRAY_PACKED; } @@ -1857,7 +1858,8 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin && val_type != IS_UNDEF) { zend_ssa_var_info *info = &tssa->var_info[tssa->ops[idx].op1_use]; - if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type)) { + if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type) + && (info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { info->type |= MAY_BE_PACKED_GUARD; info->type &= ~(MAY_BE_ARRAY_NUMERIC_HASH|MAY_BE_ARRAY_STRING_HASH); } @@ -1941,7 +1943,8 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin zend_ssa_var_info *info = &tssa->var_info[tssa->ops[idx].op1_use]; - if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type)) { + if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type) + && (info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { info->type |= MAY_BE_PACKED_GUARD; if (orig_op1_type & IS_TRACE_PACKED) { info->type &= ~(MAY_BE_ARRAY_NUMERIC_HASH|MAY_BE_ARRAY_STRING_HASH); @@ -2043,7 +2046,8 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin zend_ssa_var_info *info = &tssa->var_info[tssa->ops[idx].op1_use]; - if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type)) { + if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type) + && (info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { info->type |= MAY_BE_PACKED_GUARD; if (orig_op1_type & IS_TRACE_PACKED) { info->type &= ~(MAY_BE_ARRAY_NUMERIC_HASH|MAY_BE_ARRAY_STRING_HASH); @@ -2073,7 +2077,8 @@ static zend_ssa *zend_jit_trace_build_tssa(zend_jit_trace_rec *trace_buffer, uin zend_ssa_var_info *info = &tssa->var_info[tssa->ops[idx].op1_use]; - if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type)) { + if (MAY_BE_PACKED(info->type) && MAY_BE_HASH(info->type) + && (info->type & (MAY_BE_ANY|MAY_BE_UNDEF)) == MAY_BE_ARRAY) { info->type |= MAY_BE_PACKED_GUARD; info->type &= ~MAY_BE_ARRAY_PACKED; } @@ -4212,10 +4217,13 @@ static const void *zend_jit_trace(zend_jit_trace_rec *trace_buffer, uint32_t par if ((info & MAY_BE_PACKED_GUARD) != 0 && (trace_buffer->stop == ZEND_JIT_TRACE_STOP_LOOP || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_CALL - || trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET) + || (trace_buffer->stop == ZEND_JIT_TRACE_STOP_RECURSIVE_RET + && EX_VAR_TO_NUM((opline-1)->result.var) == i)) && (ssa->vars[i].use_chain != -1 || (ssa->vars[i].phi_use_chain && !(ssa->var_info[ssa->vars[i].phi_use_chain->ssa_var].type & MAY_BE_PACKED_GUARD)))) { + ZEND_ASSERT(STACK_TYPE(stack, i) == IS_ARRAY); + if (!zend_jit_packed_guard(&ctx, opline, EX_NUM_TO_VAR(i), info)) { goto jit_failure; } diff --git a/ext/opcache/tests/jit/gh17577.phpt b/ext/opcache/tests/jit/gh17577.phpt new file mode 100644 index 0000000000000..2eac2d05e432d --- /dev/null +++ b/ext/opcache/tests/jit/gh17577.phpt @@ -0,0 +1,27 @@ +--TEST-- +GH-17577 (JIT packed type guard crash) +--EXTENSIONS-- +opcache +--INI-- +opcache.jit_buffer_size=16M +opcache.jit_hot_func=1 +--FILE-- + +--EXPECTF-- +Warning: Trying to access array offset on int in %s on line %d + +Warning: Trying to access array offset on int in %s on line %d + +Warning: Trying to access array offset on int in %s on line %d diff --git a/ext/opcache/tests/jit/gh17654.phpt b/ext/opcache/tests/jit/gh17654.phpt new file mode 100644 index 0000000000000..59d9205b37f2a --- /dev/null +++ b/ext/opcache/tests/jit/gh17654.phpt @@ -0,0 +1,38 @@ +--TEST-- +GH-17654 (Multiple classes using same trait causes function JIT crash) +--EXTENSIONS-- +opcache +--INI-- +opcache.jit=1214 +opcache.jit_buffer_size=16M +--FILE-- +addUnit("test2"); + (new Test)->addUnit("test"); +} + +main(); +?> +--EXPECT-- +string(5) "test2" +string(4) "test" diff --git a/ext/opcache/tests/jit/gh17747.phpt b/ext/opcache/tests/jit/gh17747.phpt new file mode 100644 index 0000000000000..803d2201ec5b4 --- /dev/null +++ b/ext/opcache/tests/jit/gh17747.phpt @@ -0,0 +1,23 @@ +--TEST-- +GH-17747 (Exception on reading property in register-based FETCH_OBJ_R breaks JIT) +--EXTENSIONS-- +opcache +--INI-- +opcache.jit=function +--FILE-- +a); + } +} +$test = new C; +$test->test(); +?> +--EXPECTF-- +Fatal error: Uncaught Error: Typed property C::$a must not be accessed before initialization in %s:%d +Stack trace: +#0 %s(%d): C->test() +#1 {main} + thrown in %s on line %d diff --git a/ext/opcache/tests/preload_enum_observed.phpt b/ext/opcache/tests/preload_enum_observed.phpt new file mode 100644 index 0000000000000..7898fa084a5ff --- /dev/null +++ b/ext/opcache/tests/preload_enum_observed.phpt @@ -0,0 +1,55 @@ +--TEST-- +Enum preloading with observers +--EXTENSIONS-- +opcache +zend_test +--INI-- +opcache.enable=1 +opcache.enable_cli=1 +opcache.optimization_level=-1 +opcache.preload={PWD}/preload_enum.inc +zend_test.observer.enabled=1 +zend_test.observer.show_output=1 +zend_test.observer.observe_all=1 +--SKIPIF-- + +--FILE-- + +--EXPECTF-- + + + + +enum(MyEnum::Bar) + + + + + + + + + + + + +array(2) { + [0]=> + enum(MyEnum::Foo) + [1]=> + enum(MyEnum::Bar) +} + + diff --git a/ext/opcache/zend_accelerator_module.c b/ext/opcache/zend_accelerator_module.c index a59502eb0d5fc..ffa09aaf9e679 100644 --- a/ext/opcache/zend_accelerator_module.c +++ b/ext/opcache/zend_accelerator_module.c @@ -572,7 +572,7 @@ static zend_module_entry accel_module_entry = { ext_functions, ZEND_MINIT(zend_accelerator), ZEND_MSHUTDOWN(zend_accelerator), - accel_activate, + ZEND_RINIT(zend_accelerator), NULL, zend_accel_info, PHP_VERSION, diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index c5ddc040b22d8..1c21e031a1958 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -735,7 +735,11 @@ static zend_op_array *zend_persist_class_method(zend_op_array *op_array, zend_cl // Real dynamically created internal functions like enum methods must have their own run_time_cache pointer. They're always on the same scope as their defining class. // However, copies - as caused by inheritance of internal methods - must retain the original run_time_cache pointer, shared with the source function. if (!op_array->scope || (op_array->scope == ce && !(op_array->fn_flags & ZEND_ACC_TRAIT_CLONE))) { - ZEND_MAP_PTR_NEW(op_array->run_time_cache); + if (op_array->fn_flags & ZEND_ACC_PRELOADED) { + ZEND_MAP_PTR_NEW_STATIC(op_array->run_time_cache); + } else { + ZEND_MAP_PTR_NEW(op_array->run_time_cache); + } } } } @@ -1413,6 +1417,7 @@ zend_persistent_script *zend_accel_script_persist(zend_persistent_script *script if (for_shm) { ZCSG(map_ptr_last) = CG(map_ptr_last); + ZCSG(map_ptr_static_last) = zend_map_ptr_static_last; } #ifdef HAVE_JIT diff --git a/ext/pdo_pgsql/tests/pdopgsql_003.phpt b/ext/pdo_pgsql/tests/pdopgsql_003.phpt index 378a15c86da99..2902590f1f490 100644 --- a/ext/pdo_pgsql/tests/pdopgsql_003.phpt +++ b/ext/pdo_pgsql/tests/pdopgsql_003.phpt @@ -27,6 +27,6 @@ try { echo $e->getMessage() . "\n"; } ---EXPECT-- +--EXPECTF-- "This is a quote""" -SQLSTATE[HY000]: General error: 7 incomplete multibyte character +SQLSTATE[HY000]: General error: 7 %r(incomplete|invalid)%r multibyte character diff --git a/ext/pdo_sqlite/sqlite_driver.c b/ext/pdo_sqlite/sqlite_driver.c index 3c2b4efde16fb..e7eb710030c35 100644 --- a/ext/pdo_sqlite/sqlite_driver.c +++ b/ext/pdo_sqlite/sqlite_driver.c @@ -513,7 +513,7 @@ void pdo_sqlite_create_function_internal(INTERNAL_FUNCTION_PARAMETERS) ZEND_PARSE_PARAMETERS_END_EX(goto error;); dbh = Z_PDO_DBH_P(ZEND_THIS); - PDO_CONSTRUCT_CHECK; + PDO_CONSTRUCT_CHECK_WITH_CLEANUP(error); H = (pdo_sqlite_db_handle *)dbh->driver_data; @@ -571,7 +571,7 @@ void pdo_sqlite_create_aggregate_internal(INTERNAL_FUNCTION_PARAMETERS) ZEND_PARSE_PARAMETERS_END_EX(goto error;); dbh = Z_PDO_DBH_P(ZEND_THIS); - PDO_CONSTRUCT_CHECK; + PDO_CONSTRUCT_CHECK_WITH_CLEANUP(error); H = (pdo_sqlite_db_handle *)dbh->driver_data; @@ -643,7 +643,7 @@ void pdo_sqlite_create_collation_internal(INTERNAL_FUNCTION_PARAMETERS, pdo_sqli ZEND_PARSE_PARAMETERS_END(); dbh = Z_PDO_DBH_P(ZEND_THIS); - PDO_CONSTRUCT_CHECK; + PDO_CONSTRUCT_CHECK_WITH_CLEANUP(cleanup_fcc); H = (pdo_sqlite_db_handle *)dbh->driver_data; @@ -663,12 +663,12 @@ void pdo_sqlite_create_collation_internal(INTERNAL_FUNCTION_PARAMETERS, pdo_sqli zend_release_fcall_info_cache(&fcc); - if (UNEXPECTED(EG(exception))) { - RETURN_THROWS(); - } - efree(collation); RETURN_FALSE; + +cleanup_fcc: + zend_release_fcall_info_cache(&fcc); + RETURN_THROWS(); } /* {{{ bool SQLite::sqliteCreateCollation(string name, callable callback) diff --git a/ext/pdo_sqlite/sqlite_statement.c b/ext/pdo_sqlite/sqlite_statement.c index 29309ac5f1ff0..c0e327450232f 100644 --- a/ext/pdo_sqlite/sqlite_statement.c +++ b/ext/pdo_sqlite/sqlite_statement.c @@ -305,7 +305,7 @@ static int pdo_sqlite_stmt_col_meta(pdo_stmt_t *stmt, zend_long colno, zval *ret const char *str; zval flags; - if (!S->stmt) { + if (!S->stmt || !stmt->executed) { return FAILURE; } if(colno >= sqlite3_column_count(S->stmt)) { diff --git a/ext/pdo_sqlite/tests/gh17837.phpt b/ext/pdo_sqlite/tests/gh17837.phpt new file mode 100644 index 0000000000000..c8e1f6dab2829 --- /dev/null +++ b/ext/pdo_sqlite/tests/gh17837.phpt @@ -0,0 +1,14 @@ +--TEST-- +GH-17837 (::getColumnMeta() on unexecuted statement segfaults) +--EXTENSIONS-- +pdo_sqlite +--CREDITS-- +YuanchengJiang +--FILE-- +prepare('select :a, :b, ?'); +var_dump($stmt->getColumnMeta(0)); +?> +--EXPECT-- +bool(false) diff --git a/ext/phar/phar_object.c b/ext/phar/phar_object.c index 3b8ecddfac079..74da7db319fc2 100644 --- a/ext/phar/phar_object.c +++ b/ext/phar/phar_object.c @@ -4510,6 +4510,9 @@ PHP_METHOD(PharFileInfo, __construct) efree(entry); entry_obj->entry = entry_info; + if (!entry_info->is_persistent && !entry_info->is_temp_dir) { + ++entry_info->fp_refcount; + } ZVAL_STRINGL(&arg1, fname, fname_len); @@ -4539,15 +4542,23 @@ PHP_METHOD(PharFileInfo, __destruct) RETURN_THROWS(); } - if (entry_obj->entry && entry_obj->entry->is_temp_dir) { + if (!entry_obj->entry) { + return; + } + + if (entry_obj->entry->is_temp_dir) { if (entry_obj->entry->filename) { efree(entry_obj->entry->filename); entry_obj->entry->filename = NULL; } efree(entry_obj->entry); - entry_obj->entry = NULL; + } else if (!entry_obj->entry->is_persistent) { + --entry_obj->entry->fp_refcount; + /* It is necessarily still in the manifest, which will ultimately free this. */ } + + entry_obj->entry = NULL; } /* }}} */ diff --git a/ext/phar/tests/gh17808.phpt b/ext/phar/tests/gh17808.phpt new file mode 100644 index 0000000000000..03e54ff264bfa --- /dev/null +++ b/ext/phar/tests/gh17808.phpt @@ -0,0 +1,22 @@ +--TEST-- +GH-17808 (PharFileInfo refcount bug) +--EXTENSIONS-- +phar +zlib +--FILE-- +getContent())); +unlink("$file"); +var_dump($file->getATime()); +?> +--EXPECTF-- +string(%d) "phar://%spackage.xml" +int(6747) + +Warning: unlink(): phar error: "package.xml" in phar %s, has open file pointers, cannot unlink in %s on line %d +int(33188) diff --git a/ext/phar/tests/zip/033.phpt b/ext/phar/tests/zip/033.phpt index de4ba2b71f027..cf5ccd9a51050 100644 --- a/ext/phar/tests/zip/033.phpt +++ b/ext/phar/tests/zip/033.phpt @@ -5,6 +5,12 @@ phar --INI-- phar.readonly=0 phar.require_hash=0 +--SKIPIF-- + --FILE-- ce; + ZEND_ASSERT(!(class->ce_flags & ZEND_ACC_ENUM)); + smart_str_appends(str, "object("); + smart_str_append(str, class->name); + smart_str_appends(str, ")"); } else { ZEND_ASSERT(Z_TYPE_P(value) == IS_CONSTANT_AST); zend_string *ast_str = zend_ast_export("", Z_ASTVAL_P(value), ""); @@ -938,6 +948,12 @@ static void _property_string(smart_str *str, zend_property_info *prop, const cha if (!prop) { smart_str_append_printf(str, " public $%s", prop_name); } else { + if (prop->flags & ZEND_ACC_ABSTRACT) { + smart_str_appends(str, "abstract "); + } + if (prop->flags & ZEND_ACC_FINAL) { + smart_str_appends(str, "final "); + } /* These are mutually exclusive */ switch (prop->flags & ZEND_ACC_PPP_MASK) { case ZEND_ACC_PUBLIC: @@ -6096,6 +6112,19 @@ ZEND_METHOD(ReflectionProperty, setValue) } /* }}} */ +/* Return the property info being used when accessing 'ref->prop' from scope + * 'scope' on 'object'. The result may be different from 'ref->prop' when the + * property is overridden on 'object' and was not private in 'scope'. + * The effective prop may add hooks or change flags. */ +static zend_property_info *reflection_property_get_effective_prop( + property_reference *ref, zend_class_entry *scope, zend_object *object) { + zend_property_info *prop = ref->prop; + if (scope != object->ce && !(prop && (prop->flags & ZEND_ACC_PRIVATE))) { + prop = zend_hash_find_ptr(&object->ce->properties_info, ref->unmangled_name); + } + return prop; +} + ZEND_METHOD(ReflectionProperty, getRawValue) { reflection_object *intern; @@ -6108,17 +6137,20 @@ ZEND_METHOD(ReflectionProperty, getRawValue) GET_REFLECTION_OBJECT_PTR(ref); - if (prop_get_flags(ref) & ZEND_ACC_STATIC) { - _DO_THROW("May not use getRawValue on static properties"); + if (!instanceof_function(Z_OBJCE_P(object), intern->ce)) { + _DO_THROW("Given object is not an instance of the class this property was declared in"); RETURN_THROWS(); } - if (!instanceof_function(Z_OBJCE_P(object), intern->ce)) { - _DO_THROW("Given object is not an instance of the class this property was declared in"); + zend_property_info *prop = reflection_property_get_effective_prop(ref, + intern->ce, Z_OBJ_P(object)); + + if (UNEXPECTED(prop && (prop->flags & ZEND_ACC_STATIC))) { + _DO_THROW("May not use getRawValue on static properties"); RETURN_THROWS(); } - if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_GET]) { + if (!prop || !prop->hooks || !prop->hooks[ZEND_PROPERTY_HOOK_GET]) { zval rv; zval *member_p = zend_read_property_ex(intern->ce, Z_OBJ_P(object), ref->unmangled_name, 0, &rv); @@ -6131,17 +6163,19 @@ ZEND_METHOD(ReflectionProperty, getRawValue) RETURN_COPY_VALUE(member_p); } } else { - zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_GET, ref->unmangled_name); + zend_function *func = zend_get_property_hook_trampoline(prop, ZEND_PROPERTY_HOOK_GET, ref->unmangled_name); zend_call_known_instance_method_with_0_params(func, Z_OBJ_P(object), return_value); } } -static void reflection_property_set_raw_value(property_reference *ref, reflection_object *intern, zend_object *object, zval *value) +static void reflection_property_set_raw_value(zend_property_info *prop, + zend_string *unmangled_name, reflection_object *intern, + zend_object *object, zval *value) { - if (!ref->prop || !ref->prop->hooks || !ref->prop->hooks[ZEND_PROPERTY_HOOK_SET]) { - zend_update_property_ex(intern->ce, object, ref->unmangled_name, value); + if (!prop || !prop->hooks || !prop->hooks[ZEND_PROPERTY_HOOK_SET]) { + zend_update_property_ex(intern->ce, object, unmangled_name, value); } else { - zend_function *func = zend_get_property_hook_trampoline(ref->prop, ZEND_PROPERTY_HOOK_SET, ref->unmangled_name); + zend_function *func = zend_get_property_hook_trampoline(prop, ZEND_PROPERTY_HOOK_SET, unmangled_name); zend_call_known_instance_method_with_1_params(func, object, NULL, value); } } @@ -6155,42 +6189,46 @@ ZEND_METHOD(ReflectionProperty, setRawValue) GET_REFLECTION_OBJECT_PTR(ref); - if (prop_get_flags(ref) & ZEND_ACC_STATIC) { - _DO_THROW("May not use setRawValue on static properties"); + if (zend_parse_parameters(ZEND_NUM_ARGS(), "oz", &object, &value) == FAILURE) { RETURN_THROWS(); } - if (zend_parse_parameters(ZEND_NUM_ARGS(), "oz", &object, &value) == FAILURE) { + zend_property_info *prop = reflection_property_get_effective_prop(ref, + intern->ce, Z_OBJ_P(object)); + + if (UNEXPECTED(prop && (prop->flags & ZEND_ACC_STATIC))) { + _DO_THROW("May not use setRawValue on static properties"); RETURN_THROWS(); } - reflection_property_set_raw_value(ref, intern, Z_OBJ_P(object), value); + reflection_property_set_raw_value(prop, ref->unmangled_name, intern, Z_OBJ_P(object), value); } -static zend_result reflection_property_check_lazy_compatible(reflection_object *intern, - property_reference *ref, zend_object *object, const char *method) +static zend_result reflection_property_check_lazy_compatible( + zend_property_info *prop, zend_string *unmangled_name, + reflection_object *intern, zend_object *object, const char *method) { - if (!ref->prop) { + if (!prop) { zend_throw_exception_ex(reflection_exception_ptr, 0, "Can not use %s on dynamic property %s::$%s", method, ZSTR_VAL(intern->ce->name), - ZSTR_VAL(ref->unmangled_name)); + ZSTR_VAL(unmangled_name)); return FAILURE; } - if (ref->prop->flags & ZEND_ACC_STATIC) { + if (prop->flags & ZEND_ACC_STATIC) { zend_throw_exception_ex(reflection_exception_ptr, 0, "Can not use %s on static property %s::$%s", - method, ZSTR_VAL(intern->ce->name), - ZSTR_VAL(ref->unmangled_name)); + method, ZSTR_VAL(prop->ce->name), + ZSTR_VAL(unmangled_name)); return FAILURE; } - if (ref->prop->flags & ZEND_ACC_VIRTUAL) { + if (prop->flags & ZEND_ACC_VIRTUAL) { zend_throw_exception_ex(reflection_exception_ptr, 0, "Can not use %s on virtual property %s::$%s", - method, ZSTR_VAL(intern->ce->name), - ZSTR_VAL(ref->unmangled_name)); + method, ZSTR_VAL(prop->ce->name), + ZSTR_VAL(unmangled_name)); return FAILURE; } @@ -6198,12 +6236,12 @@ static zend_result reflection_property_check_lazy_compatible(reflection_object * if (!zend_class_can_be_lazy(object->ce)) { zend_throw_exception_ex(reflection_exception_ptr, 0, "Can not use %s on internal class %s", - method, ZSTR_VAL(intern->ce->name)); + method, ZSTR_VAL(object->ce->name)); return FAILURE; } } - ZEND_ASSERT(IS_VALID_PROPERTY_OFFSET(ref->prop->offset)); + ZEND_ASSERT(IS_VALID_PROPERTY_OFFSET(prop->offset)); return SUCCESS; } @@ -6223,23 +6261,27 @@ ZEND_METHOD(ReflectionProperty, setRawValueWithoutLazyInitialization) Z_PARAM_ZVAL(value) } ZEND_PARSE_PARAMETERS_END(); - if (reflection_property_check_lazy_compatible(intern, ref, object, - "setRawValueWithoutLazyInitialization") == FAILURE) { - RETURN_THROWS(); - } - while (zend_object_is_lazy_proxy(object) && zend_lazy_object_initialized(object)) { object = zend_lazy_object_get_instance(object); } - zval *var_ptr = OBJ_PROP(object, ref->prop->offset); + zend_property_info *prop = reflection_property_get_effective_prop(ref, + intern->ce, object); + + if (reflection_property_check_lazy_compatible(prop, ref->unmangled_name, + intern, object, "setRawValueWithoutLazyInitialization") == FAILURE) { + RETURN_THROWS(); + } + + zval *var_ptr = OBJ_PROP(object, prop->offset); bool prop_was_lazy = Z_PROP_FLAG_P(var_ptr) & IS_PROP_LAZY; /* Do not trigger initialization */ Z_PROP_FLAG_P(var_ptr) &= ~IS_PROP_LAZY; - reflection_property_set_raw_value(ref, intern, object, value); + reflection_property_set_raw_value(prop, ref->unmangled_name, intern, object, + value); /* Mark property as lazy again if an exception prevented update */ if (EG(exception) && prop_was_lazy && Z_TYPE_P(var_ptr) == IS_UNDEF @@ -6271,7 +6313,8 @@ ZEND_METHOD(ReflectionProperty, skipLazyInitialization) Z_PARAM_OBJ_OF_CLASS(object, intern->ce) } ZEND_PARSE_PARAMETERS_END(); - if (reflection_property_check_lazy_compatible(intern, ref, object, + if (reflection_property_check_lazy_compatible(ref->prop, + ref->unmangled_name, intern, object, "skipLazyInitialization") == FAILURE) { RETURN_THROWS(); } diff --git a/ext/reflection/tests/abstract_property_indicated.phpt b/ext/reflection/tests/abstract_property_indicated.phpt new file mode 100644 index 0000000000000..a70d88b7ece29 --- /dev/null +++ b/ext/reflection/tests/abstract_property_indicated.phpt @@ -0,0 +1,42 @@ +--TEST-- +Output of properties indicates if they are abstract +--FILE-- + +--EXPECTF-- +Class [ abstract class Demo ] { + @@ %s %d-%d + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [2] { + Property [ abstract public $a ] + Property [ public $b = NULL ] + } + + - Methods [0] { + } +} +Property [ abstract public $a ] +Property [ public $b = NULL ] diff --git a/ext/reflection/tests/asymmetric_visibility_flags.phpt b/ext/reflection/tests/asymmetric_visibility_flags.phpt index e6b171d3e765a..580404decda05 100644 --- a/ext/reflection/tests/asymmetric_visibility_flags.phpt +++ b/ext/reflection/tests/asymmetric_visibility_flags.phpt @@ -26,7 +26,7 @@ bool(true) bool(false) bool(true) bool(false) -Property [ public private(set) int $bar ] +Property [ final public private(set) int $bar ] bool(false) bool(true) bool(false) diff --git a/ext/reflection/tests/final_property_indicated.phpt b/ext/reflection/tests/final_property_indicated.phpt new file mode 100644 index 0000000000000..d4e159749a99a --- /dev/null +++ b/ext/reflection/tests/final_property_indicated.phpt @@ -0,0 +1,42 @@ +--TEST-- +Output of properties indicates if they are final +--FILE-- + +--EXPECTF-- +Class [ class Demo ] { + @@ %s %d-%d + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [2] { + Property [ final public $a = NULL ] + Property [ public $b = NULL ] + } + + - Methods [0] { + } +} +Property [ final public $a = NULL ] +Property [ public $b = NULL ] diff --git a/ext/reflection/tests/gh15902/ReflectionClass-callable.phpt b/ext/reflection/tests/gh15902/ReflectionClass-callable.phpt new file mode 100644 index 0000000000000..0ed02dd837c9a --- /dev/null +++ b/ext/reflection/tests/gh15902/ReflectionClass-callable.phpt @@ -0,0 +1,37 @@ +--TEST-- +ReflectionClass object default property - used to say "callable" +--INI-- +opcache.enable_cli=0 +--FILE-- + +--EXPECTF-- +Class [ class C ] { + @@ %sReflectionClass-callable.php %d-%d + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [1] { + Property [ public stdClass $a = object(stdClass) ] + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/gh15902/ReflectionClass-class.phpt b/ext/reflection/tests/gh15902/ReflectionClass-class.phpt new file mode 100644 index 0000000000000..cb03cdd383fec --- /dev/null +++ b/ext/reflection/tests/gh15902/ReflectionClass-class.phpt @@ -0,0 +1,38 @@ +--TEST-- +ReflectionClass object default property - used to say "__CLASS__" +--INI-- +opcache.enable_cli=0 +--FILE-- + +--EXPECTF-- +Class [ class C ] { + @@ %sReflectionClass-class.php %d-%d + + - Constants [0] { + } + + - Static properties [0] { + } + + - Static methods [0] { + } + + - Properties [1] { + Property [ public stdClass $a = object(stdClass) ] + } + + - Methods [0] { + } +} diff --git a/ext/reflection/tests/gh15902/ReflectionProperty-callable.phpt b/ext/reflection/tests/gh15902/ReflectionProperty-callable.phpt new file mode 100644 index 0000000000000..b88d52721fce0 --- /dev/null +++ b/ext/reflection/tests/gh15902/ReflectionProperty-callable.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionProperty object default - used to say "callable" +--INI-- +opcache.enable_cli=0 +--FILE-- + +--EXPECTF-- +Property [ public stdClass $a = object(stdClass) ] diff --git a/ext/reflection/tests/gh15902/ReflectionProperty-class.phpt b/ext/reflection/tests/gh15902/ReflectionProperty-class.phpt new file mode 100644 index 0000000000000..021df4fe87be2 --- /dev/null +++ b/ext/reflection/tests/gh15902/ReflectionProperty-class.phpt @@ -0,0 +1,20 @@ +--TEST-- +ReflectionProperty object default - used to say "__CLASS__" +--INI-- +opcache.enable_cli=0 +--FILE-- + +--EXPECTF-- +Property [ public stdClass $a = object(stdClass) ] diff --git a/ext/reflection/tests/property_hooks/gh17713.phpt b/ext/reflection/tests/property_hooks/gh17713.phpt new file mode 100644 index 0000000000000..c6d4d241bc501 --- /dev/null +++ b/ext/reflection/tests/property_hooks/gh17713.phpt @@ -0,0 +1,169 @@ +--TEST-- +GH-17713: getRawValue(), setRawValue(), setRawValueWithoutLazyInitialization() may call hooks +--FILE-- +hookedProp; + } + set { + echo __METHOD__, "\n"; + $this->hookedProp = $value; + } + } + public $virtualProp { + get { + echo __METHOD__, "\n"; + return __METHOD__; + } + set { + echo __METHOD__, "\n"; + } + } +} + +class Test extends Base { + public $publicProp { + get { + echo __FUNCTION__, "\n"; + return $this->publicProp; + } + set { + echo __FUNCTION__, "\n"; + $this->publicProp = $value; + } + } + private $privateProp = 'Test::$privateProp' { + get { + echo __FUNCTION__, "\n"; + return $this->privateProp; + } + set { + echo __FUNCTION__, "\n"; + $this->privateProp = $value; + } + } + public $dynamicProp { + get { + echo __FUNCTION__, "\n"; + return $this->dynamicProp; + } + set { + echo __FUNCTION__, "\n"; + $this->dynamicProp = $value; + } + } + public $hookedProp { + get { + echo __METHOD__, "\n"; + return $this->hookedProp; + } + set { + echo __METHOD__, "\n"; + $this->hookedProp = $value; + } + } + public $virtualProp { + get { + echo __METHOD__, "\n"; + return $this->virtualProp; + } + set { + echo __METHOD__, "\n"; + $this->virtualProp = $value; + } + } + public static $changedProp = 'Test::$changedProp'; +} + +function test($scope, $class, $prop) { + printf( + "# Accessing %s->%s from scope %s\n", + $class, + $prop, + is_object($scope) ? get_class($scope) : $scope, + ); + + $propertyReflection = new ReflectionProperty($scope, $prop); + $object = new $class(); + try { + $propertyReflection->setRawValue($object, 42); + } catch (Throwable $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump($propertyReflection->getRawValue($object)); + } catch (Throwable $e) { + echo $e->getMessage(), "\n"; + } + + try { + $propertyReflection->setRawValueWithoutLazyInitialization($object, 43); + } catch (Throwable $e) { + echo $e->getMessage(), "\n"; + } + try { + var_dump($propertyReflection->getRawValue($object)); + } catch (Throwable $e) { + echo $e->getMessage(), "\n"; + } + if ($prop === 'privateProp') { + printf( + "# Accessing %s->%s again from scope %s\n", + $class, + $prop, + $class, + ); + $propertyReflection = new ReflectionProperty($class, $prop); + var_dump($propertyReflection->getRawValue($object)); + } +} + +test(Base::class, Test::class, 'publicProp'); +test(Base::class, Test::class, 'privateProp'); +test(Base::class, Test::class, 'hookedProp'); +test(Base::class, Test::class, 'virtualProp'); +test(Base::class, Base::class, 'virtualProp'); + +$scope = new Base(); +$scope->dynamicProp = 'Base::$dynamicProp'; +$scope->changedProp = 'Base::$dynamicProp'; + +test($scope, Test::class, 'dynamicProp'); +test($scope, Test::class, 'changedProp'); + +?> +--EXPECT-- +# Accessing Test->publicProp from scope Base +int(42) +int(43) +# Accessing Test->privateProp from scope Base +int(42) +int(43) +# Accessing Test->privateProp again from scope Test +string(18) "Test::$privateProp" +# Accessing Test->hookedProp from scope Base +int(42) +int(43) +# Accessing Test->virtualProp from scope Base +int(42) +int(43) +# Accessing Base->virtualProp from scope Base +Must not write to virtual property Base::$virtualProp +Must not read from virtual property Base::$virtualProp +Can not use setRawValueWithoutLazyInitialization on virtual property Base::$virtualProp +Must not read from virtual property Base::$virtualProp +# Accessing Test->dynamicProp from scope Base +int(42) +int(43) +# Accessing Test->changedProp from scope Base +May not use setRawValue on static properties +May not use getRawValue on static properties +Can not use setRawValueWithoutLazyInitialization on static property Test::$changedProp +May not use getRawValue on static properties diff --git a/ext/sqlite3/sqlite3.c b/ext/sqlite3/sqlite3.c index 01b8af435b633..5b6ce6054f23e 100644 --- a/ext/sqlite3/sqlite3.c +++ b/ext/sqlite3/sqlite3.c @@ -2243,7 +2243,7 @@ static HashTable *php_sqlite3_get_gc(zend_object *object, zval **table, int *n) { php_sqlite3_db_object *intern = php_sqlite3_db_from_obj(object); - if (intern->funcs == NULL && intern->collations == NULL) { + if (intern->funcs == NULL && intern->collations == NULL && !ZEND_FCC_INITIALIZED(intern->authorizer_fcc)) { /* Fast path without allocations */ *table = NULL; *n = 0; @@ -2251,6 +2251,8 @@ static HashTable *php_sqlite3_get_gc(zend_object *object, zval **table, int *n) } else { zend_get_gc_buffer *gc_buffer = zend_get_gc_buffer_create(); + php_sqlite3_gc_buffer_add_fcc(gc_buffer, &intern->authorizer_fcc); + php_sqlite3_func *func = intern->funcs; while (func != NULL) { php_sqlite3_gc_buffer_add_fcc(gc_buffer, &func->func); diff --git a/ext/sqlite3/tests/setauthorizer_cycle_leak.phpt b/ext/sqlite3/tests/setauthorizer_cycle_leak.phpt new file mode 100644 index 0000000000000..b0ff384c72273 --- /dev/null +++ b/ext/sqlite3/tests/setauthorizer_cycle_leak.phpt @@ -0,0 +1,21 @@ +--TEST-- +setAuthorizer() cycle leak +--EXTENSIONS-- +sqlite3 +--FILE-- +setAuthorizer([$this, "foo"]); + } + + public function foo() {} +} + +$test = new Foo; + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/ext/standard/filestat.c b/ext/standard/filestat.c index 0825fa058641b..705370a59062a 100644 --- a/ext/standard/filestat.c +++ b/ext/standard/filestat.c @@ -388,6 +388,9 @@ static void php_do_chgrp(INTERNAL_FUNCTION_PARAMETERS, int do_lchgrp) /* {{{ */ php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); RETURN_FALSE; } + + php_clear_stat_cache(0, NULL, 0); + RETURN_TRUE; #endif } @@ -527,6 +530,9 @@ static void php_do_chown(INTERNAL_FUNCTION_PARAMETERS, int do_lchown) /* {{{ */ php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); RETURN_FALSE; } + + php_clear_stat_cache(0, NULL, 0); + RETURN_TRUE; #endif } @@ -591,6 +597,9 @@ PHP_FUNCTION(chmod) php_error_docref(NULL, E_WARNING, "%s", strerror(errno)); RETURN_FALSE; } + + php_clear_stat_cache(0, NULL, 0); + RETURN_TRUE; } /* }}} */ @@ -676,6 +685,9 @@ PHP_FUNCTION(touch) php_error_docref(NULL, E_WARNING, "Utime failed: %s", strerror(errno)); RETURN_FALSE; } + + php_clear_stat_cache(0, NULL, 0); + RETURN_TRUE; } /* }}} */ diff --git a/ext/standard/http_fopen_wrapper.c b/ext/standard/http_fopen_wrapper.c index fff6af5e2143c..ea06e9dff61ea 100644 --- a/ext/standard/http_fopen_wrapper.c +++ b/ext/standard/http_fopen_wrapper.c @@ -67,15 +67,16 @@ #include "php_fopen_wrappers.h" -#define HTTP_HEADER_BLOCK_SIZE 1024 -#define PHP_URL_REDIRECT_MAX 20 -#define HTTP_HEADER_USER_AGENT 1 -#define HTTP_HEADER_HOST 2 -#define HTTP_HEADER_AUTH 4 -#define HTTP_HEADER_FROM 8 -#define HTTP_HEADER_CONTENT_LENGTH 16 -#define HTTP_HEADER_TYPE 32 -#define HTTP_HEADER_CONNECTION 64 +#define HTTP_HEADER_BLOCK_SIZE 1024 +#define HTTP_HEADER_MAX_LOCATION_SIZE 8182 /* 8192 - 10 (size of "Location: ") */ +#define PHP_URL_REDIRECT_MAX 20 +#define HTTP_HEADER_USER_AGENT 1 +#define HTTP_HEADER_HOST 2 +#define HTTP_HEADER_AUTH 4 +#define HTTP_HEADER_FROM 8 +#define HTTP_HEADER_CONTENT_LENGTH 16 +#define HTTP_HEADER_TYPE 32 +#define HTTP_HEADER_CONNECTION 64 #define HTTP_WRAPPER_HEADER_INIT 1 #define HTTP_WRAPPER_REDIRECTED 2 @@ -107,7 +108,7 @@ static inline void strip_header(char *header_bag, char *lc_header_bag, static bool check_has_header(const char *headers, const char *header) { const char *s = headers; while ((s = strstr(s, header))) { - if (s == headers || *(s-1) == '\n') { + if (s == headers || (*(s-1) == '\n' && *(s-2) == '\r')) { return 1; } s++; @@ -143,6 +144,214 @@ static zend_result php_stream_handle_proxy_authorization_header(const char *s, s return FAILURE; } +typedef struct _php_stream_http_response_header_info { + php_stream_filter *transfer_encoding; + size_t file_size; + bool error; + bool follow_location; + char *location; + size_t location_len; +} php_stream_http_response_header_info; + +static void php_stream_http_response_header_info_init( + php_stream_http_response_header_info *header_info) +{ + memset(header_info, 0, sizeof(php_stream_http_response_header_info)); + header_info->follow_location = 1; +} + +/* Trim white spaces from response header line and update its length */ +static bool php_stream_http_response_header_trim(char *http_header_line, + size_t *http_header_line_length) +{ + char *http_header_line_end = http_header_line + *http_header_line_length - 1; + while (http_header_line_end >= http_header_line && + (*http_header_line_end == '\n' || *http_header_line_end == '\r')) { + http_header_line_end--; + } + + /* The primary definition of an HTTP header in RFC 7230 states: + * > Each header field consists of a case-insensitive field name followed + * > by a colon (":"), optional leading whitespace, the field value, and + * > optional trailing whitespace. */ + + /* Strip trailing whitespace */ + bool space_trim = (*http_header_line_end == ' ' || *http_header_line_end == '\t'); + if (space_trim) { + do { + http_header_line_end--; + } while (http_header_line_end >= http_header_line && + (*http_header_line_end == ' ' || *http_header_line_end == '\t')); + } + http_header_line_end++; + *http_header_line_end = '\0'; + *http_header_line_length = http_header_line_end - http_header_line; + + return space_trim; +} + +/* Process folding headers of the current line and if there are none, parse last full response + * header line. It returns NULL if the last header is finished, otherwise it returns updated + * last header line. */ +static zend_string *php_stream_http_response_headers_parse(php_stream_wrapper *wrapper, + php_stream *stream, php_stream_context *context, int options, + zend_string *last_header_line_str, char *header_line, size_t *header_line_length, + int response_code, zval *response_header, + php_stream_http_response_header_info *header_info) +{ + char *last_header_line = ZSTR_VAL(last_header_line_str); + size_t last_header_line_length = ZSTR_LEN(last_header_line_str); + char *last_header_line_end = ZSTR_VAL(last_header_line_str) + ZSTR_LEN(last_header_line_str) - 1; + + /* Process non empty header line. */ + if (header_line && (*header_line != '\n' && *header_line != '\r')) { + /* Removing trailing white spaces. */ + if (php_stream_http_response_header_trim(header_line, header_line_length) && + *header_line_length == 0) { + /* Only spaces so treat as an empty folding header. */ + return last_header_line_str; + } + + /* Process folding headers if starting with a space or a tab. */ + if (header_line && (*header_line == ' ' || *header_line == '\t')) { + char *http_folded_header_line = header_line; + size_t http_folded_header_line_length = *header_line_length; + /* Remove the leading white spaces. */ + while (*http_folded_header_line == ' ' || *http_folded_header_line == '\t') { + http_folded_header_line++; + http_folded_header_line_length--; + } + /* It has to have some characters because it would get returned after the call + * php_stream_http_response_header_trim above. */ + ZEND_ASSERT(http_folded_header_line_length > 0); + /* Concatenate last header line, space and current header line. */ + zend_string *extended_header_str = zend_string_concat3( + last_header_line, last_header_line_length, + " ", 1, + http_folded_header_line, http_folded_header_line_length); + zend_string_efree(last_header_line_str); + last_header_line_str = extended_header_str; + /* Return new header line. */ + return last_header_line_str; + } + } + + /* Find header separator position. */ + char *last_header_value = memchr(last_header_line, ':', last_header_line_length); + if (last_header_value) { + /* Verify there is no space in header name */ + char *last_header_name = last_header_line + 1; + while (last_header_name < last_header_value) { + if (*last_header_name == ' ' || *last_header_name == '\t') { + header_info->error = true; + php_stream_wrapper_log_error(wrapper, options, + "HTTP invalid response format (space in header name)!"); + zend_string_efree(last_header_line_str); + return NULL; + } + ++last_header_name; + } + + last_header_value++; /* Skip ':'. */ + + /* Strip leading whitespace. */ + while (last_header_value < last_header_line_end + && (*last_header_value == ' ' || *last_header_value == '\t')) { + last_header_value++; + } + } else { + /* There is no colon which means invalid response so error. */ + header_info->error = true; + php_stream_wrapper_log_error(wrapper, options, + "HTTP invalid response format (no colon in header line)!"); + zend_string_efree(last_header_line_str); + return NULL; + } + + bool store_header = true; + zval *tmpzval = NULL; + + if (!strncasecmp(last_header_line, "Location:", sizeof("Location:")-1)) { + /* Check if the location should be followed. */ + if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) { + header_info->follow_location = zval_is_true(tmpzval); + } else if (!((response_code >= 300 && response_code < 304) + || 307 == response_code || 308 == response_code)) { + /* The redirection should not be automatic if follow_location is not set and + * response_code not in (300, 301, 302, 303 and 307) + * see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1 + * RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */ + header_info->follow_location = 0; + } + size_t last_header_value_len = strlen(last_header_value); + if (last_header_value_len > HTTP_HEADER_MAX_LOCATION_SIZE) { + header_info->error = true; + php_stream_wrapper_log_error(wrapper, options, + "HTTP Location header size is over the limit of %d bytes", + HTTP_HEADER_MAX_LOCATION_SIZE); + zend_string_efree(last_header_line_str); + return NULL; + } + if (header_info->location_len == 0) { + header_info->location = emalloc(last_header_value_len + 1); + } else if (header_info->location_len <= last_header_value_len) { + header_info->location = erealloc(header_info->location, last_header_value_len + 1); + } + header_info->location_len = last_header_value_len; + memcpy(header_info->location, last_header_value, last_header_value_len + 1); + } else if (!strncasecmp(last_header_line, "Content-Type:", sizeof("Content-Type:")-1)) { + php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, last_header_value, 0); + } else if (!strncasecmp(last_header_line, "Content-Length:", sizeof("Content-Length:")-1)) { + /* https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length */ + const char *ptr = last_header_value; + /* must contain only digits, no + or - symbols */ + if (*ptr >= '0' && *ptr <= '9') { + char *endptr = NULL; + size_t parsed = ZEND_STRTOUL(ptr, &endptr, 10); + /* check whether there was no garbage in the header value and the conversion was successful */ + if (endptr && !*endptr) { + /* truncate for 32-bit such that no negative file sizes occur */ + header_info->file_size = MIN(parsed, ZEND_LONG_MAX); + php_stream_notify_file_size(context, header_info->file_size, last_header_line, 0); + } + } + } else if ( + !strncasecmp(last_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1) + && !strncasecmp(last_header_value, "Chunked", sizeof("Chunked")-1) + ) { + /* Create filter to decode response body. */ + if (!(options & STREAM_ONLY_GET_HEADERS)) { + bool decode = true; + + if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) { + decode = zend_is_true(tmpzval); + } + if (decode) { + if (header_info->transfer_encoding != NULL) { + /* Prevent a memory leak in case there are more transfer-encoding headers. */ + php_stream_filter_free(header_info->transfer_encoding); + } + header_info->transfer_encoding = php_stream_filter_create( + "dechunk", NULL, php_stream_is_persistent(stream)); + if (header_info->transfer_encoding != NULL) { + /* Do not store transfer-encoding header. */ + store_header = false; + } + } + } + } + + if (store_header) { + zval http_header; + ZVAL_NEW_STR(&http_header, last_header_line_str); + zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header); + } else { + zend_string_efree(last_header_line_str); + } + + return NULL; +} + static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, const char *path, const char *mode, int options, zend_string **opened_path, php_stream_context *context, int redirect_max, int flags, @@ -155,11 +364,12 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, zend_string *tmp = NULL; char *ua_str = NULL; zval *ua_zval = NULL, *tmpzval = NULL, ssl_proxy_peer_name; - char location[HTTP_HEADER_BLOCK_SIZE]; int reqok = 0; char *http_header_line = NULL; + zend_string *last_header_line_str = NULL; + php_stream_http_response_header_info header_info; char tmp_line[128]; - size_t chunk_size = 0, file_size = 0; + size_t chunk_size = 0; int eol_detect = 0; zend_string *transport_string; zend_string *errstr = NULL; @@ -170,8 +380,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, int header_init = ((flags & HTTP_WRAPPER_HEADER_INIT) != 0); int redirected = ((flags & HTTP_WRAPPER_REDIRECTED) != 0); int redirect_keep_method = ((flags & HTTP_WRAPPER_KEEP_METHOD) != 0); - bool follow_location = 1; - php_stream_filter *transfer_encoding = NULL; int response_code; smart_str req_buf = {0}; bool custom_request_method; @@ -360,6 +568,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } } + php_stream_http_response_header_info_init(&header_info); + if (stream == NULL) goto out; @@ -660,8 +870,6 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, /* send it */ php_stream_write(stream, ZSTR_VAL(req_buf.s), ZSTR_LEN(req_buf.s)); - location[0] = '\0'; - if (Z_ISUNDEF_P(response_header)) { array_init(response_header); } @@ -744,140 +952,103 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, } /* read past HTTP headers */ - while (!php_stream_eof(stream)) { size_t http_header_line_length; if (http_header_line != NULL) { efree(http_header_line); } - if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length)) && *http_header_line != '\n' && *http_header_line != '\r') { - char *e = http_header_line + http_header_line_length - 1; - char *http_header_value; - - while (e >= http_header_line && (*e == '\n' || *e == '\r')) { - e--; - } - - /* The primary definition of an HTTP header in RFC 7230 states: - * > Each header field consists of a case-insensitive field name followed - * > by a colon (":"), optional leading whitespace, the field value, and - * > optional trailing whitespace. */ - - /* Strip trailing whitespace */ - while (e >= http_header_line && (*e == ' ' || *e == '\t')) { - e--; - } - - /* Terminate header line */ - e++; - *e = '\0'; - http_header_line_length = e - http_header_line; - - http_header_value = memchr(http_header_line, ':', http_header_line_length); - if (http_header_value) { - http_header_value++; /* Skip ':' */ - - /* Strip leading whitespace */ - while (http_header_value < e - && (*http_header_value == ' ' || *http_header_value == '\t')) { - http_header_value++; + if ((http_header_line = php_stream_get_line(stream, NULL, 0, &http_header_line_length))) { + bool last_line; + if (*http_header_line == '\r') { + if (http_header_line[1] != '\n') { + php_stream_close(stream); + stream = NULL; + php_stream_wrapper_log_error(wrapper, options, + "HTTP invalid header name (cannot start with CR character)!"); + goto out; } + last_line = true; + } else if (*http_header_line == '\n') { + last_line = true; } else { - /* There is no colon. Set the value to the end of the header line, which is - * effectively an empty string. */ - http_header_value = e; + last_line = false; } - - if (!strncasecmp(http_header_line, "Location:", sizeof("Location:")-1)) { - if (context && (tmpzval = php_stream_context_get_option(context, "http", "follow_location")) != NULL) { - follow_location = zval_is_true(tmpzval); - } else if (!((response_code >= 300 && response_code < 304) - || 307 == response_code || 308 == response_code)) { - /* we shouldn't redirect automatically - if follow_location isn't set and response_code not in (300, 301, 302, 303 and 307) - see http://www.w3.org/Protocols/rfc2616/rfc2616-sec10.html#sec10.3.1 - RFC 7238 defines 308: http://tools.ietf.org/html/rfc7238 */ - follow_location = 0; - } - strlcpy(location, http_header_value, sizeof(location)); - } else if (!strncasecmp(http_header_line, "Content-Type:", sizeof("Content-Type:")-1)) { - php_stream_notify_info(context, PHP_STREAM_NOTIFY_MIME_TYPE_IS, http_header_value, 0); - } else if (!strncasecmp(http_header_line, "Content-Length:", sizeof("Content-Length:")-1)) { - /* https://www.rfc-editor.org/rfc/rfc9110.html#name-content-length */ - const char *ptr = http_header_value; - /* must contain only digits, no + or - symbols */ - if (*ptr >= '0' && *ptr <= '9') { - char *endptr = NULL; - size_t parsed = ZEND_STRTOUL(ptr, &endptr, 10); - /* check whether there was no garbage in the header value and the conversion was successful */ - if (endptr && !*endptr) { - /* truncate for 32-bit such that no negative file sizes occur */ - file_size = MIN(parsed, ZEND_LONG_MAX); - php_stream_notify_file_size(context, file_size, http_header_line, 0); + + if (last_header_line_str != NULL) { + /* Parse last header line. */ + last_header_line_str = php_stream_http_response_headers_parse(wrapper, stream, + context, options, last_header_line_str, http_header_line, + &http_header_line_length, response_code, response_header, &header_info); + if (EXPECTED(last_header_line_str == NULL)) { + if (UNEXPECTED(header_info.error)) { + php_stream_close(stream); + stream = NULL; + goto out; } + } else { + /* Folding header present so continue. */ + continue; } - } else if ( - !strncasecmp(http_header_line, "Transfer-Encoding:", sizeof("Transfer-Encoding:")-1) - && !strncasecmp(http_header_value, "Chunked", sizeof("Chunked")-1) - ) { - - /* create filter to decode response body */ - if (!(options & STREAM_ONLY_GET_HEADERS)) { - bool decode = true; - - if (context && (tmpzval = php_stream_context_get_option(context, "http", "auto_decode")) != NULL) { - decode = zend_is_true(tmpzval); - } - if (decode) { - transfer_encoding = php_stream_filter_create("dechunk", NULL, php_stream_is_persistent(stream)); - if (transfer_encoding) { - /* don't store transfer-encodeing header */ - continue; - } - } + } else if (!last_line) { + /* The first line cannot start with spaces. */ + if (*http_header_line == ' ' || *http_header_line == '\t') { + php_stream_close(stream); + stream = NULL; + php_stream_wrapper_log_error(wrapper, options, + "HTTP invalid response format (folding header at the start)!"); + goto out; } + /* Trim the first line if it is not the last line. */ + php_stream_http_response_header_trim(http_header_line, &http_header_line_length); } - - { - zval http_header; - ZVAL_STRINGL(&http_header, http_header_line, http_header_line_length); - zend_hash_next_index_insert(Z_ARRVAL_P(response_header), &http_header); + if (last_line) { + /* For the last line the last header line must be NULL. */ + ZEND_ASSERT(last_header_line_str == NULL); + break; } + /* Save current line as the last line so it gets parsed in the next round. */ + last_header_line_str = zend_string_init(http_header_line, http_header_line_length, 0); } else { break; } } - if (!reqok || (location[0] != '\0' && follow_location)) { - if (!follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) { + /* If the stream was closed early, we still want to process the last line to keep BC. */ + if (last_header_line_str != NULL) { + php_stream_http_response_headers_parse(wrapper, stream, context, options, + last_header_line_str, NULL, NULL, response_code, response_header, &header_info); + } + + if (!reqok || (header_info.location != NULL && header_info.follow_location)) { + if (!header_info.follow_location || (((options & STREAM_ONLY_GET_HEADERS) || ignore_errors) && redirect_max <= 1)) { goto out; } - if (location[0] != '\0') - php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, location, 0); + if (header_info.location != NULL) + php_stream_notify_info(context, PHP_STREAM_NOTIFY_REDIRECTED, header_info.location, 0); php_stream_close(stream); stream = NULL; - if (transfer_encoding) { - php_stream_filter_free(transfer_encoding); - transfer_encoding = NULL; + if (header_info.transfer_encoding) { + php_stream_filter_free(header_info.transfer_encoding); + header_info.transfer_encoding = NULL; } - if (location[0] != '\0') { + if (header_info.location != NULL) { - char new_path[HTTP_HEADER_BLOCK_SIZE]; - char loc_path[HTTP_HEADER_BLOCK_SIZE]; + char *new_path = NULL; - *new_path='\0'; - if (strlen(location)<8 || (strncasecmp(location, "http://", sizeof("http://")-1) && - strncasecmp(location, "https://", sizeof("https://")-1) && - strncasecmp(location, "ftp://", sizeof("ftp://")-1) && - strncasecmp(location, "ftps://", sizeof("ftps://")-1))) + if (strlen(header_info.location) < 8 || + (strncasecmp(header_info.location, "http://", sizeof("http://")-1) && + strncasecmp(header_info.location, "https://", sizeof("https://")-1) && + strncasecmp(header_info.location, "ftp://", sizeof("ftp://")-1) && + strncasecmp(header_info.location, "ftps://", sizeof("ftps://")-1))) { - if (*location != '/') { - if (*(location+1) != '\0' && resource->path) { + char *loc_path = NULL; + if (*header_info.location != '/') { + if (*(header_info.location+1) != '\0' && resource->path) { char *s = strrchr(ZSTR_VAL(resource->path), '/'); if (!s) { s = ZSTR_VAL(resource->path); @@ -893,29 +1064,35 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (resource->path && ZSTR_VAL(resource->path)[0] == '/' && ZSTR_VAL(resource->path)[1] == '\0') { - snprintf(loc_path, sizeof(loc_path) - 1, "%s%s", ZSTR_VAL(resource->path), location); + spprintf(&loc_path, 0, "%s%s", ZSTR_VAL(resource->path), header_info.location); } else { - snprintf(loc_path, sizeof(loc_path) - 1, "%s/%s", ZSTR_VAL(resource->path), location); + spprintf(&loc_path, 0, "%s/%s", ZSTR_VAL(resource->path), header_info.location); } } else { - snprintf(loc_path, sizeof(loc_path) - 1, "/%s", location); + spprintf(&loc_path, 0, "/%s", header_info.location); } } else { - strlcpy(loc_path, location, sizeof(loc_path)); + loc_path = header_info.location; + header_info.location = NULL; } if ((use_ssl && resource->port != 443) || (!use_ssl && resource->port != 80)) { - snprintf(new_path, sizeof(new_path) - 1, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), resource->port, loc_path); + spprintf(&new_path, 0, "%s://%s:%d%s", ZSTR_VAL(resource->scheme), + ZSTR_VAL(resource->host), resource->port, loc_path); } else { - snprintf(new_path, sizeof(new_path) - 1, "%s://%s%s", ZSTR_VAL(resource->scheme), ZSTR_VAL(resource->host), loc_path); + spprintf(&new_path, 0, "%s://%s%s", ZSTR_VAL(resource->scheme), + ZSTR_VAL(resource->host), loc_path); } + efree(loc_path); } else { - strlcpy(new_path, location, sizeof(new_path)); + new_path = header_info.location; + header_info.location = NULL; } php_url_free(resource); /* check for invalid redirection URLs */ if ((resource = php_url_parse(new_path)) == NULL) { php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); + efree(new_path); goto out; } @@ -927,6 +1104,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, while (s < e) { \ if (iscntrl(*s)) { \ php_stream_wrapper_log_error(wrapper, options, "Invalid redirect URL! %s", new_path); \ + efree(new_path); \ goto out; \ } \ s++; \ @@ -949,6 +1127,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, stream = php_stream_url_wrap_http_ex( wrapper, new_path, mode, options, opened_path, context, --redirect_max, new_flags, response_header STREAMS_CC); + efree(new_path); } else { php_stream_wrapper_log_error(wrapper, options, "HTTP request failed! %s", tmp_line); } @@ -961,6 +1140,10 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, efree(http_header_line); } + if (header_info.location != NULL) { + efree(header_info.location); + } + if (resource) { php_url_free(resource); } @@ -969,7 +1152,7 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, if (header_init) { ZVAL_COPY(&stream->wrapperdata, response_header); } - php_stream_notify_progress_init(context, 0, file_size); + php_stream_notify_progress_init(context, 0, header_info.file_size); /* Restore original chunk size now that we're done with headers */ if (options & STREAM_WILL_CAST) @@ -985,8 +1168,8 @@ static php_stream *php_stream_url_wrap_http_ex(php_stream_wrapper *wrapper, /* restore mode */ strlcpy(stream->mode, mode, sizeof(stream->mode)); - if (transfer_encoding) { - php_stream_filter_append(&stream->readfilters, transfer_encoding); + if (header_info.transfer_encoding) { + php_stream_filter_append(&stream->readfilters, header_info.transfer_encoding); } /* It's possible that the server already sent in more data than just the headers. diff --git a/ext/standard/tests/file/bug72666_variation1.phpt b/ext/standard/tests/file/bug72666_variation1.phpt new file mode 100644 index 0000000000000..6e59405d14ca8 --- /dev/null +++ b/ext/standard/tests/file/bug72666_variation1.phpt @@ -0,0 +1,19 @@ +--TEST-- +Bug #72666 (stat cache clearing inconsistent - touch) +--FILE-- + 2); +touch($filename, 1); +var_dump(filemtime($filename)); + +?> +--CLEAN-- + +--EXPECT-- +bool(true) +int(1) diff --git a/ext/standard/tests/file/bug72666_variation2.phpt b/ext/standard/tests/file/bug72666_variation2.phpt new file mode 100644 index 0000000000000..7621133c71b24 --- /dev/null +++ b/ext/standard/tests/file/bug72666_variation2.phpt @@ -0,0 +1,35 @@ +--TEST-- +Bug #72666 (stat cache clearing inconsistent - chgrp, chmod) +--SKIPIF-- + +--FILE-- + $ctime1); +var_dump($ctime3 > $ctime2); +?> +--CLEAN-- + +--EXPECT-- +bool(true) +bool(true) +bool(true) +bool(true) +bool(true) diff --git a/ext/standard/tests/file/bug72666_variation3.phpt b/ext/standard/tests/file/bug72666_variation3.phpt new file mode 100644 index 0000000000000..a491640c4f746 --- /dev/null +++ b/ext/standard/tests/file/bug72666_variation3.phpt @@ -0,0 +1,35 @@ +--TEST-- +Bug #72666 (stat cache clearing inconsistent - plain wrapper) +--FILE-- + $atime1); +} +var_dump($mtime2 > $mtime1); +?> +--CLEAN-- + +--EXPECT-- +string(4) "test" +int(4) +bool(true) +bool(true) diff --git a/ext/standard/tests/file/bug72666_variation4.phpt b/ext/standard/tests/file/bug72666_variation4.phpt new file mode 100644 index 0000000000000..09e32dafed9cf --- /dev/null +++ b/ext/standard/tests/file/bug72666_variation4.phpt @@ -0,0 +1,26 @@ +--TEST-- +Bug #72666 (stat cache clearing inconsistent - exec) +--FILE-- + 1); + + +touch($filename, 1); +var_dump(filemtime($filename)); +shell_exec("touch $filename"); +var_dump(filemtime($filename) > 1); +?> +--CLEAN-- + +--EXPECT-- +int(1) +bool(true) +int(1) +bool(true) diff --git a/ext/standard/tests/file/readfile_variation9.phpt b/ext/standard/tests/file/readfile_variation9.phpt index a09846587a6dd..382186fed10ba 100644 --- a/ext/standard/tests/file/readfile_variation9.phpt +++ b/ext/standard/tests/file/readfile_variation9.phpt @@ -5,8 +5,8 @@ Dave Kelsey --FILE-- int(%d) } -string(7) "Hello 6" +string(%d) "Hello%s" diff --git a/ext/standard/tests/http/bug47021.phpt b/ext/standard/tests/http/bug47021.phpt index 326eceb687a52..168721f4ec1b6 100644 --- a/ext/standard/tests/http/bug47021.phpt +++ b/ext/standard/tests/http/bug47021.phpt @@ -70,23 +70,27 @@ do_test(1, true); echo "\n"; ?> ---EXPECT-- +--EXPECTF-- + Type='text/plain' Hello -Size=5 -World + +Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s + Type='text/plain' Hello -Size=5 -World + +Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s + Type='text/plain' Hello -Size=5 -World + +Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s + Type='text/plain' Hello -Size=5 -World + +Warning: file_get_contents(http://%s:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s diff --git a/ext/standard/tests/http/bug75535.phpt b/ext/standard/tests/http/bug75535.phpt index 27249c3fa18ec..94348d1a027aa 100644 --- a/ext/standard/tests/http/bug75535.phpt +++ b/ext/standard/tests/http/bug75535.phpt @@ -14,27 +14,14 @@ $responses = array( ['pid' => $pid, 'uri' => $uri] = http_server($responses, $output); -var_dump(http_get_last_response_headers()); - var_dump(file_get_contents($uri)); var_dump($http_response_header); -var_dump(http_get_last_response_headers()); http_server_kill($pid); -?> --EXPECT-- -NULL string(0) "" -array(2) { - [0]=> - string(15) "HTTP/1.0 200 Ok" - [1]=> - string(14) "Content-Length" -} -array(2) { +array(1) { [0]=> string(15) "HTTP/1.0 200 Ok" - [1]=> - string(14) "Content-Length" } diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt new file mode 100644 index 0000000000000..744cff9cc72f2 --- /dev/null +++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-001.phpt @@ -0,0 +1,58 @@ +--TEST-- +GHSA-52jp-hrpf-2jff: HTTP stream wrapper truncate redirect location to 1024 bytes (success) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + $loc = str_repeat("y", 8000); + fwrite($conn, "HTTP/1.0 301 Ok\r\nContent-Type: text/html;\r\nLocation: $loc\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + case STREAM_NOTIFY_REDIRECTED: + echo "Redirected: "; + var_dump($message); + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html; +Redirected: string(8000) "%s" + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: %s +string(0) "" +array(3) { + [0]=> + string(15) "HTTP/1.0 301 Ok" + [1]=> + string(24) "Content-Type: text/html;" + [2]=> + string(8010) "Location: %s" +} diff --git a/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt new file mode 100644 index 0000000000000..bc71fd4e41167 --- /dev/null +++ b/ext/standard/tests/http/ghsa-52jp-hrpf-2jff-002.phpt @@ -0,0 +1,55 @@ +--TEST-- +GHSA-52jp-hrpf-2jff: HTTP stream wrapper truncate redirect location to 1024 bytes (over limit) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + $loc = str_repeat("y", 9000); + fwrite($conn, "HTTP/1.0 301 Ok\r\nContent-Type: text/html;\r\nLocation: $loc\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + case STREAM_NOTIFY_REDIRECTED: + echo "Redirected: "; + var_dump($message); + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html; + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP Location header size is over the limit of 8182 bytes in %s +string(0) "" +array(2) { + [0]=> + string(15) "HTTP/1.0 301 Ok" + [1]=> + string(24) "Content-Type: text/html;" +} diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt new file mode 100644 index 0000000000000..c40123560ef1e --- /dev/null +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-001.phpt @@ -0,0 +1,65 @@ +--TEST-- +GHSA-hgf5-96fm-v528: Stream HTTP wrapper header check might omit basic auth header (incorrect inside pos) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + $result = fread($conn, 1024); + $encoded_result = base64_encode($result); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n"); + +CODE; + +$clientCode = <<<'CODE' + $opts = [ + "http" => [ + "method" => "GET", + "header" => "Cookie: foo=bar\nauthorization:x\r\n" + ] + ]; + $ctx = stream_context_create($opts); + var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx)))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +array(7) { + [0]=> + string(14) "GET / HTTP/1.1" + [1]=> + string(33) "Authorization: Basic dXNlcjpwd2Q=" + [2]=> + string(21) "Host: 127.0.0.1:%d" + [3]=> + string(17) "Connection: close" + [4]=> + string(31) "Cookie: foo=bar +authorization:x" + [5]=> + string(0) "" + [6]=> + string(0) "" +} +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(38) "Content-Type: text/html; charset=utf-8" +} diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt new file mode 100644 index 0000000000000..37a47df060a1c --- /dev/null +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-002.phpt @@ -0,0 +1,62 @@ +--TEST-- +GHSA-hgf5-96fm-v528: Header parser of http stream wrapper does not handle folded headers (correct start pos) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + $result = fread($conn, 1024); + $encoded_result = base64_encode($result); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n"); + +CODE; + +$clientCode = <<<'CODE' + $opts = [ + "http" => [ + "method" => "GET", + "header" => "Authorization: Bearer x\r\n" + ] + ]; + $ctx = stream_context_create($opts); + var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx)))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +array(6) { + [0]=> + string(14) "GET / HTTP/1.1" + [1]=> + string(21) "Host: 127.0.0.1:%d" + [2]=> + string(17) "Connection: close" + [3]=> + string(23) "Authorization: Bearer x" + [4]=> + string(0) "" + [5]=> + string(0) "" +} +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(38) "Content-Type: text/html; charset=utf-8" +} diff --git a/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt new file mode 100644 index 0000000000000..6c84679ff63bd --- /dev/null +++ b/ext/standard/tests/http/ghsa-hgf5-96fm-v528-003.phpt @@ -0,0 +1,64 @@ +--TEST-- +GHSA-hgf5-96fm-v528: Header parser of http stream wrapper does not handle folded headers (correct middle pos) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + $result = fread($conn, 1024); + $encoded_result = base64_encode($result); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html; charset=utf-8\r\n\r\n$encoded_result\r\n"); + +CODE; + +$clientCode = <<<'CODE' + $opts = [ + "http" => [ + "method" => "GET", + "header" => "Cookie: x=y\r\nAuthorization: Bearer x\r\n" + ] + ]; + $ctx = stream_context_create($opts); + var_dump(explode("\r\n", base64_decode(file_get_contents("http://user:pwd@{{ ADDR }}", false, $ctx)))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +array(7) { + [0]=> + string(14) "GET / HTTP/1.1" + [1]=> + string(21) "Host: 127.0.0.1:%d" + [2]=> + string(17) "Connection: close" + [3]=> + string(11) "Cookie: x=y" + [4]=> + string(23) "Authorization: Bearer x" + [5]=> + string(0) "" + [6]=> + string(0) "" +} +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(38) "Content-Type: text/html; charset=utf-8" +} diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt new file mode 100644 index 0000000000000..bb7945ce62d0e --- /dev/null +++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-001.phpt @@ -0,0 +1,51 @@ +--TEST-- +GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (colon) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header\r\nGood-Header: test\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx)); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (no colon in header line)! in %s +bool(false) +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(23) "Content-Type: text/html" +} diff --git a/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt new file mode 100644 index 0000000000000..1d0e4fa70a2c9 --- /dev/null +++ b/ext/standard/tests/http/ghsa-pcmh-g36c-qc44-002.phpt @@ -0,0 +1,51 @@ +--TEST-- +GHSA-pcmh-g36c-qc44: Header parser of http stream wrapper does not verify header name and colon (name) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html\r\nWrong-Header : test\r\nGood-Header: test\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx)); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (space in header name)! in %s +bool(false) +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(23) "Content-Type: text/html" +} diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt new file mode 100644 index 0000000000000..f935b5a02ca94 --- /dev/null +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-001.phpt @@ -0,0 +1,49 @@ +--TEST-- +GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (single) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n charset=utf-8\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html; charset=utf-8 +string(4) "body" +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(38) "Content-Type: text/html; charset=utf-8" +} diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt new file mode 100644 index 0000000000000..078d605b6718f --- /dev/null +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-002.phpt @@ -0,0 +1,51 @@ +--TEST-- +GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (multiple) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\nCustom-Header: somevalue;\r\n param1=value1; \r\n param2=value2\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html; +string(4) "body" +array(3) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(24) "Content-Type: text/html;" + [2]=> + string(54) "Custom-Header: somevalue; param1=value1; param2=value2" +} diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt new file mode 100644 index 0000000000000..ad5ddc879cead --- /dev/null +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-003.phpt @@ -0,0 +1,49 @@ +--TEST-- +GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (empty) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\nContent-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(trim(file_get_contents("http://{{ ADDR }}", false, $ctx))); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- +Found the mime-type: text/html; charset=utf-8 +string(4) "body" +array(2) { + [0]=> + string(15) "HTTP/1.0 200 Ok" + [1]=> + string(38) "Content-Type: text/html; charset=utf-8" +} diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt new file mode 100644 index 0000000000000..d0396e819fbd3 --- /dev/null +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-004.phpt @@ -0,0 +1,48 @@ +--TEST-- +GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (first line) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\n Content-Type: text/html;\r\n \r\n charset=utf-8\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx)); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid response format (folding header at the start)! in %s +bool(false) +array(1) { + [0]=> + string(15) "HTTP/1.0 200 Ok" +} diff --git a/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt new file mode 100644 index 0000000000000..037d2002cc537 --- /dev/null +++ b/ext/standard/tests/http/ghsa-v8xr-gpvj-cx9g-005.phpt @@ -0,0 +1,48 @@ +--TEST-- +GHSA-v8xr-gpvj-cx9g: Header parser of http stream wrapper does not handle folded headers (CR before header name) +--FILE-- + [ + "tcp_nodelay" => true + ] + ]); + + $server = stream_socket_server( + "tcp://127.0.0.1:0", $errno, $errstr, STREAM_SERVER_BIND | STREAM_SERVER_LISTEN, $ctxt); + phpt_notify_server_start($server); + + $conn = stream_socket_accept($server); + + phpt_notify(message:"server-accepted"); + + fwrite($conn, "HTTP/1.0 200 Ok\r\n\rIgnored: ignored\r\n\r\nbody\r\n"); +CODE; + +$clientCode = <<<'CODE' + function stream_notification_callback($notification_code, $severity, $message, $message_code, $bytes_transferred, $bytes_max) { + switch($notification_code) { + case STREAM_NOTIFY_MIME_TYPE_IS: + echo "Found the mime-type: ", $message, PHP_EOL; + break; + } + } + + $ctx = stream_context_create(); + stream_context_set_params($ctx, array("notification" => "stream_notification_callback")); + var_dump(file_get_contents("http://{{ ADDR }}", false, $ctx)); + var_dump($http_response_header); +CODE; + +include sprintf("%s/../../../openssl/tests/ServerClientTestCase.inc", __DIR__); +ServerClientTestCase::getInstance()->run($clientCode, $serverCode); +?> +--EXPECTF-- + +Warning: file_get_contents(http://127.0.0.1:%d): Failed to open stream: HTTP invalid header name (cannot start with CR character)! in %s +bool(false) +array(1) { + [0]=> + string(15) "HTTP/1.0 200 Ok" +} diff --git a/ext/standard/tests/http/http_response_header_05.phpt b/ext/standard/tests/http/http_response_header_05.phpt deleted file mode 100644 index d2a7800f42850..0000000000000 --- a/ext/standard/tests/http/http_response_header_05.phpt +++ /dev/null @@ -1,41 +0,0 @@ ---TEST-- -$http_reponse_header (whitespace-only "header") ---SKIPIF-- - ---INI-- -allow_url_fopen=1 ---FILE-- - $pid, 'uri' => $uri] = http_server($responses, $output); - -var_dump(http_get_last_response_headers()); - -$f = file_get_contents($uri); -var_dump($f); -var_dump($http_response_header); -var_dump(http_get_last_response_headers()); - -http_server_kill($pid); - -?> ---EXPECT-- -NULL -string(4) "Body" -array(2) { - [0]=> - string(15) "HTTP/1.0 200 Ok" - [1]=> - string(0) "" -} -array(2) { - [0]=> - string(15) "HTTP/1.0 200 Ok" - [1]=> - string(0) "" -} diff --git a/ext/standard/tests/network/bug20134.phpt b/ext/standard/tests/network/bug20134.phpt index b5a7234f73c4f..6692e92b31052 100644 --- a/ext/standard/tests/network/bug20134.phpt +++ b/ext/standard/tests/network/bug20134.phpt @@ -2,6 +2,8 @@ Bug #20134 (UDP reads from invalid ports) --INI-- default_socket_timeout=1 +--CONFLICTS-- +all --FILE-- data = ''; + $consumed += strlen($bucket->data); + stream_bucket_append($out, $bucket); + } + return PSFS_PASS_ON; + } +} + +stream_filter_register('testfilter','testfilter'); + +$text = "Hello There!"; + +$fp = fopen('php://memory', 'w+'); +fwrite($fp, $text); + +rewind($fp); +stream_filter_append($fp, 'testfilter', STREAM_FILTER_READ, 'testuserfilter'); + +while ($x = fgets($fp)) { + var_dump($x); +} + +fclose($fp); + +echo "Done\n"; +?> +--EXPECT-- +Done diff --git a/ext/standard/user_filters.c b/ext/standard/user_filters.c index fcbc09c645882..ca5d25dc62568 100644 --- a/ext/standard/user_filters.c +++ b/ext/standard/user_filters.c @@ -402,7 +402,7 @@ static void php_stream_bucket_attach(int append, INTERNAL_FUNCTION_PARAMETERS) bucket = php_stream_bucket_make_writeable(bucket); } if (bucket->buflen != Z_STRLEN_P(pzdata)) { - bucket->buf = perealloc(bucket->buf, Z_STRLEN_P(pzdata), bucket->is_persistent); + bucket->buf = perealloc(bucket->buf, MAX(Z_STRLEN_P(pzdata), 1), bucket->is_persistent); bucket->buflen = Z_STRLEN_P(pzdata); } memcpy(bucket->buf, Z_STRVAL_P(pzdata), bucket->buflen); diff --git a/ext/xml/tests/toffset_bounds.phpt b/ext/xml/tests/toffset_bounds.phpt new file mode 100644 index 0000000000000..5a3fd22f86cd7 --- /dev/null +++ b/ext/xml/tests/toffset_bounds.phpt @@ -0,0 +1,42 @@ +--TEST-- +XML_OPTION_SKIP_TAGSTART bounds +--EXTENSIONS-- +xml +--FILE-- +"; +$parser = xml_parser_create(); +xml_parser_set_option($parser, XML_OPTION_SKIP_TAGSTART, 100); +$res = xml_parse_into_struct($parser,$sample,$vals,$index); +var_dump($vals); +?> +--EXPECT-- +array(3) { + [0]=> + array(3) { + ["tag"]=> + string(0) "" + ["type"]=> + string(4) "open" + ["level"]=> + int(1) + } + [1]=> + array(3) { + ["tag"]=> + string(0) "" + ["type"]=> + string(8) "complete" + ["level"]=> + int(2) + } + [2]=> + array(3) { + ["tag"]=> + string(0) "" + ["type"]=> + string(5) "close" + ["level"]=> + int(1) + } +} diff --git a/ext/xml/xml.c b/ext/xml/xml.c index fb1aa2f74bbf7..8394bbf3cb31b 100644 --- a/ext/xml/xml.c +++ b/ext/xml/xml.c @@ -663,9 +663,11 @@ void xml_startElementHandler(void *userData, const XML_Char *name, const XML_Cha array_init(&tag); array_init(&atr); - xml_add_to_info(parser, ZSTR_VAL(tag_name) + parser->toffset); + char *skipped_tag_name = SKIP_TAGSTART(ZSTR_VAL(tag_name)); - add_assoc_string(&tag, "tag", SKIP_TAGSTART(ZSTR_VAL(tag_name))); /* cast to avoid gcc-warning */ + xml_add_to_info(parser, skipped_tag_name); + + add_assoc_string(&tag, "tag", skipped_tag_name); add_assoc_string(&tag, "type", "open"); add_assoc_long(&tag, "level", parser->level); @@ -747,12 +749,14 @@ void xml_endElementHandler(void *userData, const XML_Char *name) add_assoc_string(zv, "type", "complete"); } } else { - xml_add_to_info(parser, ZSTR_VAL(tag_name) + parser->toffset); + char *skipped_tag_name = SKIP_TAGSTART(ZSTR_VAL(tag_name)); + + xml_add_to_info(parser, skipped_tag_name); zval *data = xml_get_separated_data(parser); if (EXPECTED(data)) { array_init(&tag); - add_assoc_string(&tag, "tag", SKIP_TAGSTART(ZSTR_VAL(tag_name))); /* cast to avoid gcc-warning */ + add_assoc_string(&tag, "tag", skipped_tag_name); add_assoc_string(&tag, "type", "close"); add_assoc_long(&tag, "level", parser->level); zend_hash_next_index_insert(Z_ARRVAL_P(data), &tag); diff --git a/ext/xsl/tests/xslt_non_dom_node.phpt b/ext/xsl/tests/xslt_non_dom_node.phpt index 9ead8a67fb581..0fbebc122874c 100644 --- a/ext/xsl/tests/xslt_non_dom_node.phpt +++ b/ext/xsl/tests/xslt_non_dom_node.phpt @@ -28,4 +28,4 @@ try { } ?> --EXPECT-- -Only objects that are instances of DOMNode can be converted to an XPath expression +Only objects that are instances of DOM nodes can be converted to an XPath expression diff --git a/ext/zend_test/tests/gh17797.phpt b/ext/zend_test/tests/gh17797.phpt new file mode 100644 index 0000000000000..271841b4389db --- /dev/null +++ b/ext/zend_test/tests/gh17797.phpt @@ -0,0 +1,32 @@ +--TEST-- +GH-17797 (zend_test_compile_string crash on invalid script path) +--EXTENSIONS-- +zend_test +--CREDITS-- +YuanchengJiang +--FILE-- +'; +try {zend_test_compile_string($source,$source,$c);} catch (Exception $e) { echo($e); } +?> +--EXPECTF-- + +Warning: Undefined variable $c in %s on line %d + +Deprecated: zend_test_compile_string(): Passing null to parameter #3 ($position) of type int is deprecated in %s on line %d + +Warning: require(sumfile.php): Failed to open stream: No such file or directory in on line %d + +Fatal error: Uncaught Error: Failed opening required 'sumfile.php' (include_path='.%s') in :%d +Stack trace: +#0 %s(%d): zend_test_compile_string(' on line %d diff --git a/ext/zend_test/tests/gh17899.phpt b/ext/zend_test/tests/gh17899.phpt new file mode 100644 index 0000000000000..184fe0b2aa39d --- /dev/null +++ b/ext/zend_test/tests/gh17899.phpt @@ -0,0 +1,35 @@ +--TEST-- +GH-17899 (zend_test_compile_string with opcache crash on invalid script path) +--EXTENSIONS-- +zend_test +--INI-- +opcache.enable_cli=1 +--CREDITS-- +YuanchengJiang +--FILE-- +'; +try {zend_test_compile_string($source,$source,$c);} catch (Exception $e) { echo($e); } +?> +--EXPECTF-- + +Warning: Undefined variable $c in %s on line %d + +Deprecated: zend_test_compile_string(): Passing null to parameter #3 ($position) of type int is deprecated in %s on line %d + +Warning: require(sumfile.php): Failed to open stream: No such file or directory in on line %d + +Fatal error: Uncaught Error: Failed opening required 'sumfile.php' (include_path='.%s') in :%d +Stack trace: +#0 %s(%d): zend_test_compile_string(' on line %d + diff --git a/ext/zip/php_zip.h b/ext/zip/php_zip.h index 4f4750993f990..1a1ffb1da3b8a 100644 --- a/ext/zip/php_zip.h +++ b/ext/zip/php_zip.h @@ -39,7 +39,7 @@ extern zend_module_entry zip_module_entry; /* Additionnal flags not from libzip */ #define ZIP_FL_OPEN_FILE_NOW (1u<<30) -#define PHP_ZIP_VERSION "1.22.4" +#define PHP_ZIP_VERSION "1.22.5" #ifdef HAVE_LIBZIP_VERSION #define LIBZIP_VERSION_STR zip_libzip_version() diff --git a/ext/zlib/tests/gh17745.phpt b/ext/zlib/tests/gh17745.phpt new file mode 100644 index 0000000000000..64331269dcdab --- /dev/null +++ b/ext/zlib/tests/gh17745.phpt @@ -0,0 +1,20 @@ +--TEST-- +GH-17745 (zlib extension incorrectly handles object arguments) +--EXTENSIONS-- +zlib +--FILE-- +level = 3; +var_dump(deflate_init(ZLIB_ENCODING_RAW, $obj)); + +class Options { + public int $level = 3; +} +var_dump(deflate_init(ZLIB_ENCODING_RAW, new Options)); +?> +--EXPECT-- +object(DeflateContext)#2 (0) { +} +object(DeflateContext)#3 (0) { +} diff --git a/ext/zlib/tests/leak_invalid_encoding_with_dict.phpt b/ext/zlib/tests/leak_invalid_encoding_with_dict.phpt new file mode 100644 index 0000000000000..da2a11849c0c2 --- /dev/null +++ b/ext/zlib/tests/leak_invalid_encoding_with_dict.phpt @@ -0,0 +1,20 @@ +--TEST-- +Memory leak when passing a dictionary with invalid encoding +--EXTENSIONS-- +zlib +--FILE-- + "dict"]); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +try { + deflate_init(123456, ["dictionary" => "dict"]); +} catch (ValueError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Encoding mode must be ZLIB_ENCODING_RAW, ZLIB_ENCODING_GZIP or ZLIB_ENCODING_DEFLATE +deflate_init(): Argument #1 ($encoding) must be one of ZLIB_ENCODING_RAW, ZLIB_ENCODING_GZIP, or ZLIB_ENCODING_DEFLATE diff --git a/ext/zlib/zlib.c b/ext/zlib/zlib.c index 4221bceacc8c7..91623b070b3d8 100644 --- a/ext/zlib/zlib.c +++ b/ext/zlib/zlib.c @@ -789,6 +789,7 @@ static bool zlib_create_dictionary_string(HashTable *options, char **dict, size_ zval *option_buffer; if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("dictionary"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); ZVAL_DEREF(option_buffer); switch (Z_TYPE_P(option_buffer)) { case IS_STRING: { @@ -869,6 +870,7 @@ PHP_FUNCTION(inflate_init) } if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("window"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); window = zval_get_long(option_buffer); } if (window < 8 || window > 15) { @@ -876,10 +878,6 @@ PHP_FUNCTION(inflate_init) RETURN_THROWS(); } - if (!zlib_create_dictionary_string(options, &dict, &dictlen)) { - RETURN_THROWS(); - } - switch (encoding) { case PHP_ZLIB_ENCODING_RAW: case PHP_ZLIB_ENCODING_GZIP: @@ -890,6 +888,10 @@ PHP_FUNCTION(inflate_init) RETURN_THROWS(); } + if (!zlib_create_dictionary_string(options, &dict, &dictlen)) { + RETURN_THROWS(); + } + object_init_ex(return_value, inflate_context_ce); ctx = Z_INFLATE_CONTEXT_P(return_value); @@ -1087,6 +1089,7 @@ PHP_FUNCTION(deflate_init) } if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("level"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); level = zval_get_long(option_buffer); } if (level < -1 || level > 9) { @@ -1095,6 +1098,7 @@ PHP_FUNCTION(deflate_init) } if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("memory"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); memory = zval_get_long(option_buffer); } if (memory < 1 || memory > 9) { @@ -1103,6 +1107,7 @@ PHP_FUNCTION(deflate_init) } if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("window"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); window = zval_get_long(option_buffer); } if (window < 8 || window > 15) { @@ -1111,6 +1116,7 @@ PHP_FUNCTION(deflate_init) } if (options && (option_buffer = zend_hash_str_find(options, ZEND_STRL("strategy"))) != NULL) { + ZVAL_DEINDIRECT(option_buffer); strategy = zval_get_long(option_buffer); } switch (strategy) { @@ -1125,10 +1131,6 @@ PHP_FUNCTION(deflate_init) RETURN_THROWS(); } - if (!zlib_create_dictionary_string(options, &dict, &dictlen)) { - RETURN_THROWS(); - } - switch (encoding) { case PHP_ZLIB_ENCODING_RAW: case PHP_ZLIB_ENCODING_GZIP: @@ -1139,6 +1141,10 @@ PHP_FUNCTION(deflate_init) RETURN_THROWS(); } + if (!zlib_create_dictionary_string(options, &dict, &dictlen)) { + RETURN_THROWS(); + } + object_init_ex(return_value, deflate_context_ce); ctx = Z_DEFLATE_CONTEXT_P(return_value); diff --git a/ext/zlib/zlib.stub.php b/ext/zlib/zlib.stub.php index 1083564f76505..09c44fec2e3a7 100644 --- a/ext/zlib/zlib.stub.php +++ b/ext/zlib/zlib.stub.php @@ -270,11 +270,11 @@ function gzread($stream, int $length): string|false {} */ function gzgets($stream, ?int $length = null): string|false {} -function deflate_init(int $encoding, array $options = []): DeflateContext|false {} +function deflate_init(int $encoding, array|object $options = []): DeflateContext|false {} function deflate_add(DeflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string|false {} -function inflate_init(int $encoding, array $options = []): InflateContext|false {} +function inflate_init(int $encoding, array|object $options = []): InflateContext|false {} function inflate_add(InflateContext $context, string $data, int $flush_mode = ZLIB_SYNC_FLUSH): string|false {} diff --git a/ext/zlib/zlib_arginfo.h b/ext/zlib/zlib_arginfo.h index 1b89be7ecd847..8ec8411f6ee9a 100644 --- a/ext/zlib/zlib_arginfo.h +++ b/ext/zlib/zlib_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 3660ad3239f93c84b6909c36ddfcc92dd0773c70 */ + * Stub hash: a86ccc292b5312e740a9d798bfcaf014513d5cce */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_ob_gzhandler, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, data, IS_STRING, 0) @@ -106,7 +106,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_deflate_init, 0, 1, DeflateContext, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, encoding, IS_LONG, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 0, "[]") + ZEND_ARG_TYPE_MASK(0, options, MAY_BE_ARRAY|MAY_BE_OBJECT, "[]") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_deflate_add, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) @@ -117,7 +117,7 @@ ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_OBJ_TYPE_MASK_EX(arginfo_inflate_init, 0, 1, InflateContext, MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, encoding, IS_LONG, 0) - ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, options, IS_ARRAY, 0, "[]") + ZEND_ARG_TYPE_MASK(0, options, MAY_BE_ARRAY|MAY_BE_OBJECT, "[]") ZEND_END_ARG_INFO() ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_inflate_add, 0, 2, MAY_BE_STRING|MAY_BE_FALSE) diff --git a/ext/zlib/zlib_fopen_wrapper.c b/ext/zlib/zlib_fopen_wrapper.c index b414b33a8724e..31b5212a720ac 100644 --- a/ext/zlib/zlib_fopen_wrapper.c +++ b/ext/zlib/zlib_fopen_wrapper.c @@ -33,24 +33,55 @@ struct php_gz_stream_data_t { static ssize_t php_gziop_read(php_stream *stream, char *buf, size_t count) { struct php_gz_stream_data_t *self = (struct php_gz_stream_data_t *) stream->abstract; - int read; + ssize_t total_read = 0; + + /* Despite the count argument of gzread() being "unsigned int", + * the return value is "int". Error returns are values < 0, otherwise the count is returned. + * To properly distinguish error values from success value, we therefore need to cap at INT_MAX. + */ + do { + unsigned int chunk_size = MIN(count, INT_MAX); + int read = gzread(self->gz_file, buf, chunk_size); + count -= chunk_size; + + if (gzeof(self->gz_file)) { + stream->eof = 1; + } - /* XXX this needs to be looped for the case count > UINT_MAX */ - read = gzread(self->gz_file, buf, count); + if (UNEXPECTED(read < 0)) { + return read; + } - if (gzeof(self->gz_file)) { - stream->eof = 1; - } + total_read += read; + buf += read; + } while (count > 0 && !stream->eof); - return read; + return total_read; } static ssize_t php_gziop_write(php_stream *stream, const char *buf, size_t count) { struct php_gz_stream_data_t *self = (struct php_gz_stream_data_t *) stream->abstract; + ssize_t total_written = 0; + + /* Despite the count argument of gzread() being "unsigned int", + * the return value is "int". Error returns are values < 0, otherwise the count is returned. + * To properly distinguish error values from success value, we therefore need to cap at INT_MAX. + */ + do { + unsigned int chunk_size = MIN(count, INT_MAX); + int written = gzwrite(self->gz_file, buf, chunk_size); + count -= chunk_size; + + if (UNEXPECTED(written < 0)) { + return written; + } + + total_written += written; + buf += written; + } while (count > 0); - /* XXX this needs to be looped for the case count > UINT_MAX */ - return gzwrite(self->gz_file, (char *) buf, count); + return total_written; } static int php_gziop_seek(php_stream *stream, zend_off_t offset, int whence, zend_off_t *newoffs) diff --git a/main/fopen_wrappers.c b/main/fopen_wrappers.c index 99a082846b481..c5719fc9bb755 100644 --- a/main/fopen_wrappers.c +++ b/main/fopen_wrappers.c @@ -603,7 +603,13 @@ PHPAPI zend_string *php_resolve_path(const char *filename, size_t filename_lengt const char *exec_fname = ZSTR_VAL(exec_filename); size_t exec_fname_length = ZSTR_LEN(exec_filename); - while ((--exec_fname_length < SIZE_MAX) && !IS_SLASH(exec_fname[exec_fname_length])); + while (exec_fname_length > 0) { + --exec_fname_length; + if (IS_SLASH(exec_fname[exec_fname_length])) { + break; + } + } + if (exec_fname_length > 0 && filename_length < (MAXPATHLEN - 2) && exec_fname_length + 1 + filename_length + 1 < MAXPATHLEN) { diff --git a/main/php_version.h b/main/php_version.h index 3a921856f4248..66972c5420f66 100644 --- a/main/php_version.h +++ b/main/php_version.h @@ -2,7 +2,7 @@ /* edit configure.ac to change version number */ #define PHP_MAJOR_VERSION 8 #define PHP_MINOR_VERSION 4 -#define PHP_RELEASE_VERSION 4 -#define PHP_EXTRA_VERSION "-dev" -#define PHP_VERSION "8.4.4-dev" -#define PHP_VERSION_ID 80404 +#define PHP_RELEASE_VERSION 5 +#define PHP_EXTRA_VERSION "" +#define PHP_VERSION "8.4.5" +#define PHP_VERSION_ID 80405 diff --git a/main/streams/plain_wrapper.c b/main/streams/plain_wrapper.c index 7b0813c3db623..85771eaf5ef3a 100644 --- a/main/streams/plain_wrapper.c +++ b/main/streams/plain_wrapper.c @@ -349,14 +349,15 @@ PHPAPI php_stream *_php_stream_fopen_from_pipe(FILE *file, const char *mode STRE static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t count) { php_stdio_stream_data *data = (php_stdio_stream_data*)stream->abstract; + ssize_t bytes_written; assert(data != NULL); if (data->fd >= 0) { #ifdef PHP_WIN32 - ssize_t bytes_written = _write(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count)); + bytes_written = _write(data->fd, buf, PLAIN_WRAP_BUF_SIZE(count)); #else - ssize_t bytes_written = write(data->fd, buf, count); + bytes_written = write(data->fd, buf, count); #endif if (bytes_written < 0) { if (PHP_IS_TRANSIENT_ERROR(errno)) { @@ -370,7 +371,6 @@ static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t coun php_error_docref(NULL, E_NOTICE, "Write of %zu bytes failed with errno=%d %s", count, errno, strerror(errno)); } } - return bytes_written; } else { #ifdef HAVE_FLUSHIO @@ -380,8 +380,15 @@ static ssize_t php_stdiop_write(php_stream *stream, const char *buf, size_t coun data->last_op = 'w'; #endif - return (ssize_t) fwrite(buf, 1, count, data->file); + bytes_written = (ssize_t) fwrite(buf, 1, count, data->file); } + + if (EG(active)) { + /* clear stat cache as mtime and ctime got changed */ + php_clear_stat_cache(0, NULL, 0); + } + + return bytes_written; } static ssize_t php_stdiop_read(php_stream *stream, char *buf, size_t count) @@ -460,6 +467,12 @@ static ssize_t php_stdiop_read(php_stream *stream, char *buf, size_t count) stream->eof = feof(data->file); } + + if (EG(active)) { + /* clear stat cache as atime got changed */ + php_clear_stat_cache(0, NULL, 0); + } + return ret; } @@ -540,6 +553,10 @@ static int php_stdiop_flush(php_stream *stream) * something completely different. */ if (data->file) { + if (EG(active)) { + /* clear stat cache as there might be a write so mtime and ctime might have changed */ + php_clear_stat_cache(0, NULL, 0); + } return fflush(data->file); } return 0; @@ -1154,6 +1171,12 @@ PHPAPI php_stream *_php_stream_fopen(const char *filename, const char *mode, zen ret = php_stream_fopen_from_fd_rel(fd, mode, persistent_id, (open_flags & O_APPEND) == 0); } + if (EG(active)) { + /* clear stat cache as mtime and ctime might got changed - phar can use stream before + * cache is initialized so we need to check if the execution is active. */ + php_clear_stat_cache(0, NULL, 0); + } + if (ret) { if (opened_path) { *opened_path = zend_string_init(realpath, strlen(realpath), 0); diff --git a/main/streams/streams.c b/main/streams/streams.c index dc8a8780a242d..e83acd5f7e161 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -2469,25 +2469,19 @@ PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], in vector_size = 10; } else { if(vector_size*2 < vector_size) { - /* overflow */ - php_stream_closedir(stream); - efree(vector); - return -1; + goto overflow; } vector_size *= 2; } - vector = (zend_string **) safe_erealloc(vector, vector_size, sizeof(char *), 0); + vector = (zend_string **) safe_erealloc(vector, vector_size, sizeof(zend_string *), 0); } vector[nfiles] = zend_string_init(sdp.d_name, strlen(sdp.d_name), 0); - nfiles++; - if(vector_size < 10 || nfiles == 0) { - /* overflow */ - php_stream_closedir(stream); - efree(vector); - return -1; + if(vector_size < 10 || nfiles + 1 == 0) { + goto overflow; } + nfiles++; } php_stream_closedir(stream); @@ -2497,5 +2491,13 @@ PHPAPI int _php_stream_scandir(const char *dirname, zend_string **namelist[], in qsort(*namelist, nfiles, sizeof(zend_string *), (int(*)(const void *, const void *))compare); } return nfiles; + +overflow: + php_stream_closedir(stream); + for (unsigned int i = 0; i < nfiles; i++) { + zend_string_efree(vector[i]); + } + efree(vector); + return -1; } /* }}} */ diff --git a/sapi/fpm/fpm/fpm_main.c b/sapi/fpm/fpm/fpm_main.c index d138738b27981..56796c327f911 100644 --- a/sapi/fpm/fpm/fpm_main.c +++ b/sapi/fpm/fpm/fpm_main.c @@ -1153,6 +1153,7 @@ static void init_request_info(void) } if (tflag) { + char *decoded_path_info = NULL; if (orig_path_info) { char old; @@ -1174,7 +1175,6 @@ static void init_request_info(void) * As we can extract PATH_INFO from PATH_TRANSLATED * it is probably also in SCRIPT_NAME and need to be removed */ - char *decoded_path_info = NULL; size_t decoded_path_info_len = 0; if (strchr(path_info, '%')) { decoded_path_info = estrdup(path_info); @@ -1197,11 +1197,13 @@ static void init_request_info(void) env_script_name[env_script_file_info_start] = 0; SG(request_info).request_uri = FCGI_PUTENV(request, "SCRIPT_NAME", env_script_name); } - if (decoded_path_info) { - efree(decoded_path_info); - } } - env_path_info = FCGI_PUTENV(request, "PATH_INFO", path_info); + if (decoded_path_info) { + env_path_info = FCGI_PUTENV(request, "PATH_INFO", decoded_path_info); + efree(decoded_path_info); + } else { + env_path_info = FCGI_PUTENV(request, "PATH_INFO", path_info); + } } if (!orig_script_filename || strcmp(orig_script_filename, pt) != 0) { diff --git a/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded-plus.phpt b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded-plus.phpt index 4bef11ec668f0..06a974ce40693 100644 --- a/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded-plus.phpt +++ b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded-plus.phpt @@ -39,7 +39,7 @@ $tester scriptFilename: "proxy:fcgi://" . $tester->getAddr() . $sourceFilePath . '/1%20+2', scriptName: $scriptName . '/1 +2' ) - ->expectBody([$scriptName, $scriptName . '/1 +2', $sourceFilePath, '/1%20+2', $scriptName . '/1%20+2']); + ->expectBody([$scriptName, $scriptName . '/1 +2', $sourceFilePath, '/1 +2', $scriptName . '/1 +2']); $tester->terminate(); $tester->close(); diff --git a/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded.phpt b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded.phpt index 22114e1abde47..29834f3e13149 100644 --- a/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded.phpt +++ b/sapi/fpm/tests/fcgi-env-pif-apache-pp-sn-strip-encoded.phpt @@ -39,7 +39,7 @@ $tester scriptFilename: "proxy:fcgi://" . $tester->getAddr() . $sourceFilePath . '/1%202', scriptName: $scriptName . '/1 2' ) - ->expectBody([$scriptName, $scriptName . '/1 2', $sourceFilePath, '/1%202', $scriptName . '/1%202']); + ->expectBody([$scriptName, $scriptName . '/1 2', $sourceFilePath, '/1 2', $scriptName . '/1 2']); $tester->terminate(); $tester->close(); diff --git a/sapi/phpdbg/phpdbg_lexer.l b/sapi/phpdbg/phpdbg_lexer.l index 6245262a00598..60d995526ea27 100644 --- a/sapi/phpdbg/phpdbg_lexer.l +++ b/sapi/phpdbg/phpdbg_lexer.l @@ -160,8 +160,9 @@ INPUT ("\\"[#"']|["]("\\\\"|"\\"["]|[^\n\000"])*["]|[']("\\"[']|"\\\\"|[^\ {GENERIC_ID} { phpdbg_init_param(yylval, STR_PARAM); - yylval->str = estrndup(yytext, yyleng - unescape_string(yytext)); - yylval->len = yyleng; + size_t len = yyleng - unescape_string(yytext); + yylval->str = estrndup(yytext, len); + yylval->len = len; return T_ID; } diff --git a/sapi/phpdbg/phpdbg_prompt.c b/sapi/phpdbg/phpdbg_prompt.c index d47f967e724dd..469263b517da8 100644 --- a/sapi/phpdbg/phpdbg_prompt.c +++ b/sapi/phpdbg/phpdbg_prompt.c @@ -189,6 +189,9 @@ static inline int phpdbg_call_register(phpdbg_param_t *stack) /* {{{ */ zval_ptr_dtor_str(&fci.function_name); efree(lc_name); + if (fci.named_params) { + zend_array_destroy(fci.named_params); + } return SUCCESS; } diff --git a/sapi/phpdbg/tests/register_function_leak.phpt b/sapi/phpdbg/tests/register_function_leak.phpt new file mode 100644 index 0000000000000..b5416ea95bcc8 --- /dev/null +++ b/sapi/phpdbg/tests/register_function_leak.phpt @@ -0,0 +1,24 @@ +--TEST-- +registering a function and calling it leaks arguments memory +--FILE-- + +--PHPDBG-- +register var_dump +var_dump "a" "b" +register flush +flush +r +q +--EXPECTF-- +[Successful compilation of %s] +prompt> [Registered var_dump] +prompt> string(1) "a" +string(1) "b" + +prompt> [Registered flush] +prompt> +prompt> Done +[Script ended normally] +prompt> diff --git a/win32/build/phpize.js.in b/win32/build/phpize.js.in index 49871481e8907..c5f57737c97d0 100644 --- a/win32/build/phpize.js.in +++ b/win32/build/phpize.js.in @@ -91,6 +91,7 @@ function find_config_w32(dirname) deps = get_module_dep(contents); + n = ""; item = new Module_Item(n, c, dir_line, deps, contents); MODULES.Add(n, item); }