Add recursion protection when emitting deprecation warnings for class
constants, since the deprecation message can come from an attribute that is
using the same constant for the message, or otherwise result in recursion.
But, internal constants are persisted, and thus cannot have recursion
protection. Otherwise, if a user error handler triggers bailout before the
recursion flag is removed then a subsequent request (e.g. with `--repeat 2`)
would start with that flag already applied. Internal constants can presumably
be trusted not to use deprecation messages that come from recursive attributes.
Fixes GH-18463
Fixes GH-17711
The check for `!fbc || (fbc->common.fn_flags & ZEND_ACC_VARIADIC)` is
performed after `fbc` is set to NULL, so this always returns true.
This results in `ZEND_FCALL_MAY_HAVE_EXTRA_NAMED_PARAMS` always being
set for unpack sends. Fix it by moving the flag updates to the point
before setting `fbc` to NULL.
Closes GH-17534.
The current code expects the property name to be a string, but it can
also be a number via the {} syntax. Handle this consistently to a string
by using zval_get_string which will do the type coercion and refcount
update (instead of assuming string and doing an explicit string copy).
Closes GH-17236.
Only on Windows `IS_SLASH_P()` may read the previous byte, and so may
in unlikely cases read one byte out of bounds. Since `IS_SLASH_P()` is
in a public header (albeit not likely to be used by external extensions
or SAPIs), we introduce `IS_SLASH_P_EX()` which accepts a second
argument to prevent that OOB read.
It should be noted that the PHP userland function `dirname()` is not
affected by this issue, since it does not call `zend_dirname()` on
Windows.
Closes GH-16995.
Reproducer: https://github.com/php/php-src/issues/16727#issuecomment-2466256317
The root cause is a data race between two different threads:
1) We allocate a lower cased name for an anonymous class here:
f97353f228/Zend/zend_compile.c (L8109)
2) This gets looked up as an interned string here:
f97353f228/Zend/zend_compile.c (L8112)
Assuming that there are uppercase symbols in the string and therefore
`lcname != name` and that `lcname` is not yet in the interned string table,
the pointer value of `lcname` won't change.
3) Here we add the string into the interned string table:
f97353f228/Zend/zend_compile.c (L8223)
However, in the meantime another thread could've added the string into the interned string table.
This means that the following code will run, indirectly called via the `LITERAL_STR` macro,
freeing `lcname`: 62e53e6f49/ext/opcache/ZendAccelerator.c (L572-L575)
4) In the reproducer we then access the freed `lcname` string here:
f97353f228/Zend/zend_compile.c (L8229)
This is solved in my patch by retrieving the interned string pointer
and putting it in `lcname`.
Closes GH-16748.
This check was forgotten in the original implementation. Relaxing this
restriction shouldn't be hard, but needs some work. We either need to prevent
merging of cache slots for R/RW/W, or we need to introduce an additional check
when writing to the property indirectly. This check is currently present only
for direct writes.
Closes GH-16462
Instead of always saying that a name is reserved or deprecated and
cannot/should not be used as a class name, take the usage into account and say
the name cannot be used as an enum name, trait name, etc. In the process, for
class names add a missing "a".
Previously, seen symbols were never cleaned during the compilation of a single
file. This makes it impossible to use a class or function from a different
namespace if such a symbol is also declared within the same file. This is
inconsistent with how it would work when split into different files.
This flag was never necessary. We know a static variable is uninitialized (i.e.
the initializer has never been called) iff the zval in the static variable array
does not contain a reference.
Prompted by a related issue in ext-uopz reported by Christoph.
This flag is longer set since the merge of property hooks in
780a8280d2. This patch removes it completely,
because the corresponding error messages are unreachable.
Increase the reserved stack size in ASAN builds, as instrumentation use more stack.
Increase the max allowed stack size in some tests, and enable these tests under ASAN.
Use __builtin_frame_address(0), instead of some stack variable, when we need a stack address, as ASAN may store local variables outside of the real stack.
You cannot return or yield a reference to a nullsafe chain. This was
checked already in zend_compile_return but not yet in
zend_compile_yield.
Closes GH-14716.
* zend_compile: Rename `string_placeholder_count` to `placeholder_count` in `zend_compile_func_sprintf()`
This is intended to make the diff of a follow-up commit smaller.
* zend_compile: Add support for `%d` to `sprintf()` optimization
This extends the existing `sprintf()` optimization by support for the `%d`
placeholder, which effectively equivalent to an `(int)` cast followed by a
`(string)` cast.
For a synthetic test using:
<?php
$a = 'foo';
$b = 42;
for ($i = 0; $i < 100_000_000; $i++) {
sprintf("%s-%d", $a, $b);
}
This optimization yields a 1.3× performance improvement:
$ hyperfine 'sapi/cli/php -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php' \
'/tmp/unoptimized -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php'
Benchmark 1: sapi/cli/php -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php
Time (mean ± σ): 3.296 s ± 0.094 s [User: 3.287 s, System: 0.005 s]
Range (min … max): 3.213 s … 3.527 s 10 runs
Benchmark 2: /tmp/unoptimized -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php
Time (mean ± σ): 4.300 s ± 0.025 s [User: 4.290 s, System: 0.007 s]
Range (min … max): 4.266 s … 4.334 s 10 runs
Summary
sapi/cli/php -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php ran
1.30 ± 0.04 times faster than /tmp/unoptimized -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php
* Fix sprintf_rope_optimization_003.phpt test expecation for 32-bit integers
* zend_compile: Indent switch-case labels in zend_compile_func_sprintf()
* Add GMP test to sprintf() rope optimization
* Add `%s` test case to sprintf() GMP test
Inline the lookup whether a function is observed at all.
This strategy is also used for FRAMELESS calls. If the frameless call is observed, we instead allocate a call frame and push the arguments, to call the the function afterwards.
Doing so is still a performance benefit as opposed to executing individual INIT_FCALL+SEND_VAL ops. Thus, even if the frameless call turns out to be observed, the call overhead is slightly lower than before.
If the internal function is not observed at all, the unavoidable overhead is fetching the FLF zend_function pointer and the run-time cache needs to be inspected.
As part of this work, it turned out to be most viable to put the result operand on the ZEND_OP_DATA instead of ZEND_FRAMELESS_ICALL_3, allowing seamless interoperability with the DO_ICALL opcode.
This is a bit unusual in comparison to all other ZEND_OP_DATA usages, but seems to not pose problems overall.
There is also a small issue resolved: trampolines would always use the ZEND_CALL_TRAMPOLINE_SPEC_OBSERVER function due to zend_observer_fcall_op_array_extension being set to -1 too late.
* zend_compile: Add `zend_compile_rope_finalize()`
This just extracts the implementation as-is into a dedicated function to make
it reusable in preparation of a future commit.
* zend_compile: Use clearer parameter names for `zend_compile_rope_finalize()`
* zend_compile: Fix `zend_compile_rope_finalize()` for ropes containing a single constant string
Without this Opcache will trigger a use-after-free in
`zend_optimizer_compact_literals()`.
Co-authored-by: Ilija Tovilo <ilija.tovilo@me.com>
* zend_compile: Optimize `sprintf()` into a rope
This optimization will compile `sprintf()` using only `%s` placeholders into a
rope at compile time, effectively making those calls equivalent to the use of
string interpolation, with the added benefit of supporting arbitrary
expressions instead of just expressions starting with a `$`.
For a synthetic test using:
<?php
$a = 'foo';
$b = 'bar';
for ($i = 0; $i < 100_000_000; $i++) {
sprintf("%s-%s", $a, $b);
}
This optimization yields a 2.1× performance improvement:
$ hyperfine 'sapi/cli/php -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php' \
'/tmp/unoptimized -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php'
Benchmark 1: sapi/cli/php -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php
Time (mean ± σ): 1.869 s ± 0.033 s [User: 1.865 s, System: 0.003 s]
Range (min … max): 1.840 s … 1.945 s 10 runs
Benchmark 2: /tmp/unoptimized -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php
Time (mean ± σ): 4.011 s ± 0.034 s [User: 4.006 s, System: 0.005 s]
Range (min … max): 3.964 s … 4.079 s 10 runs
Summary
sapi/cli/php -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php ran
2.15 ± 0.04 times faster than /tmp/unoptimized -d zend_extension=php-src/modules/opcache.so -d opcache.enable_cli=1 test.php
This optimization comes with a small and probably insignificant behavioral
change: If one of the values cannot be (cleanly) converted to a string, for
example when attempting to insert an object that is not `Stringable`, the
resulting Exception will naturally not show the `sprintf()` call in the
resulting stack trace, because there is no call to `sprintf()`.
Nevertheless it will correctly point out the line of the `sprintf()` call as
the source of the Exception, pointing the user towards the correct location.
* zend_compile: Eagerly handle empty format strings in `sprintf()` optimization
* zend_compile: Add additional explanatory comments to zend_compile_func_sprintf()
* Add zero-argument test to sprintf_rope_optimization_001.phpt
---------
Co-authored-by: Ilija Tovilo <ilija.tovilo@me.com>