From 4719567fcf060b10e2df3d8514ac998d23718d8e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Wed, 18 Dec 2024 11:20:25 +0100 Subject: [PATCH 01/10] Add `#[\NoDiscard]` attribute --- Zend/Optimizer/optimize_func_calls.c | 2 +- Zend/tests/attributes/nodiscard/001.phpt | 86 +++++++++++++++ Zend/tests/attributes/nodiscard/002.phpt | 44 ++++++++ Zend/tests/attributes/nodiscard/003.phpt | 22 ++++ Zend/tests/attributes/nodiscard/005.phpt | 17 +++ Zend/tests/attributes/nodiscard/006.phpt | 20 ++++ Zend/tests/attributes/nodiscard/007.phpt | 21 ++++ Zend/tests/attributes/nodiscard/008.phpt | 18 ++++ Zend/tests/attributes/nodiscard/009.phpt | 14 +++ .../attributes/nodiscard/error_code_001.phpt | 21 ++++ .../nodiscard/property_readonly_001.phpt | 14 +++ .../nodiscard/property_readonly_002.phpt | 15 +++ .../attributes/nodiscard/suppress_assign.phpt | 66 ++++++++++++ .../attributes/nodiscard/suppress_cast.phpt | 66 ++++++++++++ .../nodiscard/throwing_error_handler_001.phpt | 35 ++++++ .../nodiscard/type_validation_001.phpt | 15 +++ .../nodiscard/type_validation_002.phpt | 20 ++++ .../nodiscard/type_validation_003.phpt | 18 ++++ .../nodiscard/type_validation_004.phpt | 18 ++++ .../nodiscard/unsupported_clone.phpt | 16 +++ .../nodiscard/unsupported_constructor.phpt | 16 +++ .../nodiscard/unsupported_never_function.phpt | 15 +++ .../unsupported_property_hook_get.phpt | 20 ++++ .../unsupported_property_hook_set.phpt | 20 ++++ .../nodiscard/unsupported_void_function.phpt | 15 +++ Zend/zend_API.c | 10 ++ Zend/zend_attributes.c | 27 +++++ Zend/zend_attributes.h | 1 + Zend/zend_attributes.stub.php | 11 ++ Zend/zend_attributes_arginfo.h | 33 +++++- Zend/zend_compile.c | 29 +++++ Zend/zend_compile.h | 5 +- Zend/zend_execute.c | 84 +++++++++++++++ Zend/zend_execute.h | 1 + Zend/zend_vm_def.h | 28 +++++ Zend/zend_vm_execute.h | 65 +++++++++++ build/gen_stub.php | 10 +- ext/date/php_date.stub.php | 9 ++ ext/date/php_date_arginfo.h | 102 ++++++++++++++++-- ext/standard/basic_functions.stub.php | 1 + ext/standard/basic_functions_arginfo.h | 13 ++- ext/zend_test/test.c | 7 ++ ext/zend_test/test.stub.php | 5 + ext/zend_test/test_arginfo.h | 34 +++++- 44 files changed, 1087 insertions(+), 22 deletions(-) create mode 100644 Zend/tests/attributes/nodiscard/001.phpt create mode 100644 Zend/tests/attributes/nodiscard/002.phpt create mode 100644 Zend/tests/attributes/nodiscard/003.phpt create mode 100644 Zend/tests/attributes/nodiscard/005.phpt create mode 100644 Zend/tests/attributes/nodiscard/006.phpt create mode 100644 Zend/tests/attributes/nodiscard/007.phpt create mode 100644 Zend/tests/attributes/nodiscard/008.phpt create mode 100644 Zend/tests/attributes/nodiscard/009.phpt create mode 100644 Zend/tests/attributes/nodiscard/error_code_001.phpt create mode 100644 Zend/tests/attributes/nodiscard/property_readonly_001.phpt create mode 100644 Zend/tests/attributes/nodiscard/property_readonly_002.phpt create mode 100644 Zend/tests/attributes/nodiscard/suppress_assign.phpt create mode 100644 Zend/tests/attributes/nodiscard/suppress_cast.phpt create mode 100644 Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt create mode 100644 Zend/tests/attributes/nodiscard/type_validation_001.phpt create mode 100644 Zend/tests/attributes/nodiscard/type_validation_002.phpt create mode 100644 Zend/tests/attributes/nodiscard/type_validation_003.phpt create mode 100644 Zend/tests/attributes/nodiscard/type_validation_004.phpt create mode 100644 Zend/tests/attributes/nodiscard/unsupported_clone.phpt create mode 100644 Zend/tests/attributes/nodiscard/unsupported_constructor.phpt create mode 100644 Zend/tests/attributes/nodiscard/unsupported_never_function.phpt create mode 100644 Zend/tests/attributes/nodiscard/unsupported_property_hook_get.phpt create mode 100644 Zend/tests/attributes/nodiscard/unsupported_property_hook_set.phpt create mode 100644 Zend/tests/attributes/nodiscard/unsupported_void_function.phpt diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c index ce6c43afaedbe..1ba932f75d257 100644 --- a/Zend/Optimizer/optimize_func_calls.c +++ b/Zend/Optimizer/optimize_func_calls.c @@ -79,7 +79,7 @@ static void zend_delete_call_instructions(zend_op_array *op_array, zend_op *opli static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_op *opline, zend_function *func) { if (func->type == ZEND_USER_FUNCTION - && !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_DEPRECATED)) + && !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) /* TODO: function copied from trait may be inconsistent ??? */ && !(func->op_array.fn_flags & (ZEND_ACC_TRAIT_CLONE)) && fcall->extended_value >= func->op_array.required_num_args diff --git a/Zend/tests/attributes/nodiscard/001.phpt b/Zend/tests/attributes/nodiscard/001.phpt new file mode 100644 index 0000000000000..f44d1cbc642cc --- /dev/null +++ b/Zend/tests/attributes/nodiscard/001.phpt @@ -0,0 +1,86 @@ +--TEST-- +#[\NoDiscard]: Basic test. +--FILE-- +test(); +$cls->test2(); +Clazz::test3(); + +call_user_func([$cls, "test"]); + +$closure(); + +$closure2(); + +?> +--EXPECTF-- +Warning: (F)The return value of function test() is expected to be consumed in %s on line %d + +Warning: (F)The return value of function test2() is expected to be consumed, this is important in %s on line %d + +Warning: (E)The return value of function test3() is expected to be consumed in %s on line %d + +Warning: (F)The return value of function test() is expected to be consumed in %s on line %d + +Warning: (F)The return value of function test() is expected to be consumed in %s on line %d + +Warning: (F)The return value of method Clazz::test() is expected to be consumed in %s on line %d + +Warning: (F)The return value of method Clazz::test2() is expected to be consumed, this is important in %s on line %d + +Warning: (F)The return value of method Clazz::test3() is expected to be consumed in %s on line %d + +Warning: (F)The return value of method Clazz::test() is expected to be consumed in %s on line %d + +Warning: (F)The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d + +Warning: (F)The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/002.phpt b/Zend/tests/attributes/nodiscard/002.phpt new file mode 100644 index 0000000000000..ce60a427dad6b --- /dev/null +++ b/Zend/tests/attributes/nodiscard/002.phpt @@ -0,0 +1,44 @@ +--TEST-- +#[\NoDiscard]: __call(), __callStatic(), and __invoke(). +--FILE-- +test(); +Clazz::test(); +$cls('foo'); + +?> +--EXPECTF-- +__call(test) + +Warning: (F)The return value of method Clazz::__call() is expected to be consumed in %s on line %d +__callStatic(test) + +Warning: (F)The return value of method Clazz::__callStatic() is expected to be consumed in %s on line %d +__invoke(foo) + +Warning: (F)The return value of method Clazz::__invoke() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/003.phpt b/Zend/tests/attributes/nodiscard/003.phpt new file mode 100644 index 0000000000000..17ee75c6269a3 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/003.phpt @@ -0,0 +1,22 @@ +--TEST-- +#[\NoDiscard]: Taken from trait. +--FILE-- +test(); + +?> +--EXPECTF-- +Warning: (F)The return value of method Clazz::test() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/005.phpt b/Zend/tests/attributes/nodiscard/005.phpt new file mode 100644 index 0000000000000..c0ad4627acc0a --- /dev/null +++ b/Zend/tests/attributes/nodiscard/005.phpt @@ -0,0 +1,17 @@ +--TEST-- +#[\NoDiscard]: Native function and method. +--FILE-- +setTimestamp(0); + +?> +--EXPECTF-- +Warning: (C)The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d + +Warning: (A)The return value of method DateTimeImmutable::setTimestamp() is expected to be consumed, as DateTimeImmutable::setTimestamp() does not modify the object itself in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/006.phpt b/Zend/tests/attributes/nodiscard/006.phpt new file mode 100644 index 0000000000000..d7cf68a9f751b --- /dev/null +++ b/Zend/tests/attributes/nodiscard/006.phpt @@ -0,0 +1,20 @@ +--TEST-- +#[\NoDiscard]: execute_ex overwritten +--EXTENSIONS-- +zend_test +--INI-- +zend_test.replace_zend_execute_ex=1 +opcache.jit=disable +--FILE-- + +--EXPECTF-- +Warning: (G)The return value of function test() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/007.phpt b/Zend/tests/attributes/nodiscard/007.phpt new file mode 100644 index 0000000000000..b663b1f7b8b0a --- /dev/null +++ b/Zend/tests/attributes/nodiscard/007.phpt @@ -0,0 +1,21 @@ +--TEST-- +#[\NoDiscard]: execute_internal overwritten +--EXTENSIONS-- +zend_test +--INI-- +zend_test.observer.execute_internal=1 +--FILE-- + +--EXPECTF-- + + + + +Warning: (A)The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d + diff --git a/Zend/tests/attributes/nodiscard/008.phpt b/Zend/tests/attributes/nodiscard/008.phpt new file mode 100644 index 0000000000000..1129d9903d7a2 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/008.phpt @@ -0,0 +1,18 @@ +--TEST-- +#[\NoDiscard]: Combining with #[\Deprecated]. +--FILE-- + +--EXPECTF-- +Deprecated: Function test() is deprecated in %s on line %d + +Warning: (F)The return value of function test() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/009.phpt b/Zend/tests/attributes/nodiscard/009.phpt new file mode 100644 index 0000000000000..8625e6b8a01e8 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/009.phpt @@ -0,0 +1,14 @@ +--TEST-- +#[\NoDiscard]: Combining with #[\Deprecated] (Internal). +--EXTENSIONS-- +zend_test +--FILE-- + +--EXPECTF-- +Deprecated: Function zend_test_deprecated_nodiscard() is deprecated, custom message in %s on line %d + +Warning: (B)The return value of function zend_test_deprecated_nodiscard() is expected to be consumed, custom message 2 in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/error_code_001.phpt b/Zend/tests/attributes/nodiscard/error_code_001.phpt new file mode 100644 index 0000000000000..2952587db7b87 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/error_code_001.phpt @@ -0,0 +1,21 @@ +--TEST-- +#[\NoDiscard]: Code is E_USER_WARNING. +--FILE-- + +--EXPECT-- +int(512) +int(512) +bool(true) diff --git a/Zend/tests/attributes/nodiscard/property_readonly_001.phpt b/Zend/tests/attributes/nodiscard/property_readonly_001.phpt new file mode 100644 index 0000000000000..6f9779023126e --- /dev/null +++ b/Zend/tests/attributes/nodiscard/property_readonly_001.phpt @@ -0,0 +1,14 @@ +--TEST-- +#[\NoDiscard]: NoDiscard::$message is readonly. +--FILE-- +message = 'bar'; + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Cannot modify readonly property NoDiscard::$message in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/property_readonly_002.phpt b/Zend/tests/attributes/nodiscard/property_readonly_002.phpt new file mode 100644 index 0000000000000..f01594ca4b26c --- /dev/null +++ b/Zend/tests/attributes/nodiscard/property_readonly_002.phpt @@ -0,0 +1,15 @@ +--TEST-- +#[\NoDiscard]: __construct() respects that properties are readonly. +--FILE-- +__construct("bar"); + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Cannot modify readonly property NoDiscard::$message in %s:%d +Stack trace: +#0 %s(%d): NoDiscard->__construct('bar') +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/suppress_assign.phpt b/Zend/tests/attributes/nodiscard/suppress_assign.phpt new file mode 100644 index 0000000000000..b09f1a39718a8 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/suppress_assign.phpt @@ -0,0 +1,66 @@ +--TEST-- +#[\NoDiscard]: Assigning to variable suppresses. +--FILE-- +test(); +$_ = $cls->test2(); +$_ = call_user_func([$cls, "test"]); +$_ = Clazz::test3(); + +$_ = $closure(); + +$_ = $closure2(); + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/attributes/nodiscard/suppress_cast.phpt b/Zend/tests/attributes/nodiscard/suppress_cast.phpt new file mode 100644 index 0000000000000..e8bbb26f25a34 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/suppress_cast.phpt @@ -0,0 +1,66 @@ +--TEST-- +#[\NoDiscard]: Casting to (bool) suppresses. +--FILE-- +test(); +(bool)$cls->test2(); +(bool)call_user_func([$cls, "test"]); +(bool)Clazz::test3(); + +(bool)$closure(); + +(bool)$closure2(); + +?> +DONE +--EXPECT-- +DONE diff --git a/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt b/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt new file mode 100644 index 0000000000000..b745adc67352a --- /dev/null +++ b/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt @@ -0,0 +1,35 @@ +--TEST-- +#[\NoDiscard]: Throwing error handler. +--FILE-- +getMessage(), PHP_EOL; +} + +#[\NoDiscard] +function test2(): stdClass { + return new stdClass(); +} + +try { + test2(); +} catch (ErrorException $e) { + echo "Caught: ", $e->getMessage(), PHP_EOL; +} + +?> +--EXPECT-- +Caught: (F)The return value of function test() is expected to be consumed +Caught: (F)The return value of function test2() is expected to be consumed diff --git a/Zend/tests/attributes/nodiscard/type_validation_001.phpt b/Zend/tests/attributes/nodiscard/type_validation_001.phpt new file mode 100644 index 0000000000000..e50468d942c76 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/type_validation_001.phpt @@ -0,0 +1,15 @@ +--TEST-- +#[\NoDiscard]: Type validation of $message parameter with int. +--FILE-- + +--EXPECTF-- +Warning: (F)The return value of function test() is expected to be consumed, 1234 in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/type_validation_002.phpt b/Zend/tests/attributes/nodiscard/type_validation_002.phpt new file mode 100644 index 0000000000000..254fae15ac47a --- /dev/null +++ b/Zend/tests/attributes/nodiscard/type_validation_002.phpt @@ -0,0 +1,20 @@ +--TEST-- +#[\NoDiscard]: Type validation of $message parameter with int and strict types. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: NoDiscard::__construct(): Argument #1 ($message) must be of type ?string, int given in %s:%d +Stack trace: +#0 %s(%d): NoDiscard->__construct(1234) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/type_validation_003.phpt b/Zend/tests/attributes/nodiscard/type_validation_003.phpt new file mode 100644 index 0000000000000..0b66addaea187 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/type_validation_003.phpt @@ -0,0 +1,18 @@ +--TEST-- +#[\NoDiscard]: Type validation of $message parameter with array. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: NoDiscard::__construct(): Argument #1 ($message) must be of type ?string, array given in %s:%d +Stack trace: +#0 %s(%d): NoDiscard->__construct(Array) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/type_validation_004.phpt b/Zend/tests/attributes/nodiscard/type_validation_004.phpt new file mode 100644 index 0000000000000..44d7c19b54863 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/type_validation_004.phpt @@ -0,0 +1,18 @@ +--TEST-- +#[\NoDiscard]: Type validation of $message parameter with native enum case. +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: NoDiscard::__construct(): Argument #1 ($message) must be of type ?string, Random\IntervalBoundary given in %s:%d +Stack trace: +#0 %s(%d): NoDiscard->__construct(Random\IntervalBoundary::ClosedOpen) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/unsupported_clone.phpt b/Zend/tests/attributes/nodiscard/unsupported_clone.phpt new file mode 100644 index 0000000000000..7fadfea40fa6a --- /dev/null +++ b/Zend/tests/attributes/nodiscard/unsupported_clone.phpt @@ -0,0 +1,16 @@ +--TEST-- +#[\NoDiscard]: Not allowed on '__clone'. +--FILE-- + +--EXPECTF-- +Fatal error: Method Clazz::__clone cannot be #[\NoDiscard] in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/unsupported_constructor.phpt b/Zend/tests/attributes/nodiscard/unsupported_constructor.phpt new file mode 100644 index 0000000000000..a11c6379938d3 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/unsupported_constructor.phpt @@ -0,0 +1,16 @@ +--TEST-- +#[\NoDiscard]: Not allowed on '__construct'. +--FILE-- + +--EXPECTF-- +Fatal error: Method Clazz::__construct cannot be #[\NoDiscard] in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/unsupported_never_function.phpt b/Zend/tests/attributes/nodiscard/unsupported_never_function.phpt new file mode 100644 index 0000000000000..ee7a81acbcde9 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/unsupported_never_function.phpt @@ -0,0 +1,15 @@ +--TEST-- +#[\NoDiscard]: Not allowed on never function. +--FILE-- + +--EXPECTF-- +Fatal error: A never returning function does not return a value, but #[\NoDiscard] requires a return value in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/unsupported_property_hook_get.phpt b/Zend/tests/attributes/nodiscard/unsupported_property_hook_get.phpt new file mode 100644 index 0000000000000..ccedf3ede315a --- /dev/null +++ b/Zend/tests/attributes/nodiscard/unsupported_property_hook_get.phpt @@ -0,0 +1,20 @@ +--TEST-- +#[\NoDiscard]: Not allowed on 'get' property hook. +--FILE-- +test; + +?> +--EXPECTF-- +Fatal error: #[\NoDiscard] is not supported for property hooks in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/unsupported_property_hook_set.phpt b/Zend/tests/attributes/nodiscard/unsupported_property_hook_set.phpt new file mode 100644 index 0000000000000..351593f5250bb --- /dev/null +++ b/Zend/tests/attributes/nodiscard/unsupported_property_hook_set.phpt @@ -0,0 +1,20 @@ +--TEST-- +#[\NoDiscard]: Not allowed on 'set' property hook. +--FILE-- +test = $value; + } + } +} + +$cls = new Foo(); +$cls->test = 'foo'; + +?> +--EXPECTF-- +Fatal error: #[\NoDiscard] is not supported for property hooks in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/unsupported_void_function.phpt b/Zend/tests/attributes/nodiscard/unsupported_void_function.phpt new file mode 100644 index 0000000000000..3c45f255473bc --- /dev/null +++ b/Zend/tests/attributes/nodiscard/unsupported_void_function.phpt @@ -0,0 +1,15 @@ +--TEST-- +#[\NoDiscard]: Not allowed on void function. +--FILE-- + +--EXPECTF-- +Fatal error: A void function does not return a value, but #[\NoDiscard] requires a return value in %s on line %d diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 8c239a952f32b..8b4a02aacfb1d 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -2693,6 +2693,12 @@ static void zend_check_magic_method_arg_type(uint32_t arg_num, const zend_class_ static void zend_check_magic_method_return_type(const zend_class_entry *ce, const zend_function *fptr, int error_type, int return_type) { + if (return_type == MAY_BE_VOID) { + if (fptr->common.fn_flags & ZEND_ACC_NODISCARD) { + zend_error_noreturn(error_type, "Method %s::%s cannot be #[\\NoDiscard]", ZSTR_VAL(ce->name), ZSTR_VAL(fptr->common.function_name)); + } + } + if (!(fptr->common.fn_flags & ZEND_ACC_HAS_RETURN_TYPE)) { /* For backwards compatibility reasons, do not enforce the return type if it is not set. */ return; @@ -2752,6 +2758,10 @@ static void zend_check_magic_method_no_return_type( zend_error_noreturn(error_type, "Method %s::%s() cannot declare a return type", ZSTR_VAL(ce->name), ZSTR_VAL(fptr->common.function_name)); } + + if (fptr->common.fn_flags & ZEND_ACC_NODISCARD) { + zend_error_noreturn(error_type, "Method %s::%s cannot be #[\\NoDiscard]", ZSTR_VAL(ce->name), ZSTR_VAL(fptr->common.function_name)); + } } ZEND_API void zend_check_magic_method_implementation(const zend_class_entry *ce, const zend_function *fptr, zend_string *lcname, int error_type) /* {{{ */ diff --git a/Zend/zend_attributes.c b/Zend/zend_attributes.c index d7bcb1f54e889..48f79c12610f6 100644 --- a/Zend/zend_attributes.c +++ b/Zend/zend_attributes.c @@ -31,6 +31,7 @@ ZEND_API zend_class_entry *zend_ce_sensitive_parameter; ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value; ZEND_API zend_class_entry *zend_ce_override; ZEND_API zend_class_entry *zend_ce_deprecated; +ZEND_API zend_class_entry *zend_ce_nodiscard; static zend_object_handlers attributes_object_handlers_sensitive_parameter_value; @@ -193,6 +194,29 @@ ZEND_METHOD(Deprecated, __construct) } } +ZEND_METHOD(NoDiscard, __construct) +{ + zend_string *message = NULL; + zval value; + + ZEND_PARSE_PARAMETERS_START(0, 1) + Z_PARAM_OPTIONAL + Z_PARAM_STR_OR_NULL(message) + ZEND_PARSE_PARAMETERS_END(); + + if (message) { + ZVAL_STR(&value, message); + } else { + ZVAL_NULL(&value); + } + zend_update_property_ex(zend_ce_nodiscard, Z_OBJ_P(ZEND_THIS), ZSTR_KNOWN(ZEND_STR_MESSAGE), &value); + + /* The assignment might fail due to 'readonly'. */ + if (UNEXPECTED(EG(exception))) { + RETURN_THROWS(); + } +} + static zend_attribute *get_attribute(HashTable *attributes, zend_string *lcname, uint32_t offset) { if (attributes) { @@ -520,6 +544,9 @@ void zend_register_attribute_ce(void) zend_ce_deprecated = register_class_Deprecated(); attr = zend_mark_internal_attribute(zend_ce_deprecated); + + zend_ce_nodiscard = register_class_NoDiscard(); + attr = zend_mark_internal_attribute(zend_ce_nodiscard); } void zend_attributes_shutdown(void) diff --git a/Zend/zend_attributes.h b/Zend/zend_attributes.h index 8a825247c00f8..468488800ebf8 100644 --- a/Zend/zend_attributes.h +++ b/Zend/zend_attributes.h @@ -47,6 +47,7 @@ extern ZEND_API zend_class_entry *zend_ce_sensitive_parameter; extern ZEND_API zend_class_entry *zend_ce_sensitive_parameter_value; extern ZEND_API zend_class_entry *zend_ce_override; extern ZEND_API zend_class_entry *zend_ce_deprecated; +extern ZEND_API zend_class_entry *zend_ce_nodiscard; typedef struct { zend_string *name; diff --git a/Zend/zend_attributes.stub.php b/Zend/zend_attributes.stub.php index 0a35b0c57cb44..6351ccd771838 100644 --- a/Zend/zend_attributes.stub.php +++ b/Zend/zend_attributes.stub.php @@ -84,3 +84,14 @@ final class Deprecated public function __construct(?string $message = null, ?string $since = null) {} } + +/** + * @strict-properties + */ +#[Attribute(Attribute::TARGET_METHOD|Attribute::TARGET_FUNCTION)] +final class NoDiscard +{ + public readonly ?string $message; + + public function __construct(?string $message = null) {} +} diff --git a/Zend/zend_attributes_arginfo.h b/Zend/zend_attributes_arginfo.h index 018caa47d0ac5..aecb216291071 100644 --- a/Zend/zend_attributes_arginfo.h +++ b/Zend/zend_attributes_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 2358a0d820edd06a1702c84104bfd545af08311c */ + * Stub hash: 6b54bc195be211caabb395b621380681953c1f5a */ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Attribute___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, flags, IS_LONG, 0, "Attribute::TARGET_ALL") @@ -29,6 +29,10 @@ ZEND_BEGIN_ARG_INFO_EX(arginfo_class_Deprecated___construct, 0, 0, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, since, IS_STRING, 1, "null") ZEND_END_ARG_INFO() +ZEND_BEGIN_ARG_INFO_EX(arginfo_class_NoDiscard___construct, 0, 0, 0) + ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, message, IS_STRING, 1, "null") +ZEND_END_ARG_INFO() + ZEND_METHOD(Attribute, __construct); ZEND_METHOD(ReturnTypeWillChange, __construct); ZEND_METHOD(AllowDynamicProperties, __construct); @@ -38,6 +42,7 @@ ZEND_METHOD(SensitiveParameterValue, getValue); ZEND_METHOD(SensitiveParameterValue, __debugInfo); ZEND_METHOD(Override, __construct); ZEND_METHOD(Deprecated, __construct); +ZEND_METHOD(NoDiscard, __construct); static const zend_function_entry class_Attribute_methods[] = { ZEND_ME(Attribute, __construct, arginfo_class_Attribute___construct, ZEND_ACC_PUBLIC) @@ -76,6 +81,11 @@ static const zend_function_entry class_Deprecated_methods[] = { ZEND_FE_END }; +static const zend_function_entry class_NoDiscard_methods[] = { + ZEND_ME(NoDiscard, __construct, arginfo_class_NoDiscard___construct, ZEND_ACC_PUBLIC) + ZEND_FE_END +}; + static zend_class_entry *register_class_Attribute(void) { zend_class_entry ce, *class_entry; @@ -253,3 +263,24 @@ static zend_class_entry *register_class_Deprecated(void) return class_entry; } + +static zend_class_entry *register_class_NoDiscard(void) +{ + zend_class_entry ce, *class_entry; + + INIT_CLASS_ENTRY(ce, "NoDiscard", class_NoDiscard_methods); + class_entry = zend_register_internal_class_with_flags(&ce, NULL, ZEND_ACC_FINAL|ZEND_ACC_NO_DYNAMIC_PROPERTIES); + + zval property_message_default_value; + ZVAL_UNDEF(&property_message_default_value); + zend_declare_typed_property(class_entry, ZSTR_KNOWN(ZEND_STR_MESSAGE), &property_message_default_value, ZEND_ACC_PUBLIC|ZEND_ACC_READONLY, NULL, (zend_type) ZEND_TYPE_INIT_MASK(MAY_BE_STRING|MAY_BE_NULL)); + + zend_string *attribute_name_Attribute_class_NoDiscard_0 = zend_string_init_interned("Attribute", sizeof("Attribute") - 1, 1); + zend_attribute *attribute_Attribute_class_NoDiscard_0 = zend_add_class_attribute(class_entry, attribute_name_Attribute_class_NoDiscard_0, 1); + zend_string_release(attribute_name_Attribute_class_NoDiscard_0); + zval attribute_Attribute_class_NoDiscard_0_arg0; + ZVAL_LONG(&attribute_Attribute_class_NoDiscard_0_arg0, ZEND_ATTRIBUTE_TARGET_METHOD | ZEND_ATTRIBUTE_TARGET_FUNCTION); + ZVAL_COPY_VALUE(&attribute_Attribute_class_NoDiscard_0->args[0].value, &attribute_Attribute_class_NoDiscard_0_arg0); + + return class_entry; +} diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index debf46d126de6..c2e94ce20345f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -8377,6 +8377,35 @@ static zend_op_array *zend_compile_func_decl_ex( } } + zend_attribute *nodiscard_attribute = zend_get_attribute_str( + op_array->attributes, + "nodiscard", + sizeof("nodiscard")-1 + ); + + if (nodiscard_attribute) { + if (is_hook) { + zend_error_noreturn(E_COMPILE_ERROR, "#[\\NoDiscard] is not supported for property hooks"); + } + + if (op_array->fn_flags & ZEND_ACC_HAS_RETURN_TYPE) { + zend_arg_info *return_info = CG(active_op_array)->arg_info - 1; + if (ZEND_TYPE_CONTAINS_CODE(return_info->type, IS_VOID)) { + zend_error_noreturn(E_COMPILE_ERROR, + "A void %s does not return a value, but #[\\NoDiscard] requires a return value", + CG(active_class_entry) != NULL ? "method" : "function"); + } + + if (ZEND_TYPE_CONTAINS_CODE(return_info->type, IS_NEVER)) { + zend_error_noreturn(E_COMPILE_ERROR, + "A never returning %s does not return a value, but #[\\NoDiscard] requires a return value", + CG(active_class_entry) != NULL ? "method" : "function"); + } + } + + op_array->fn_flags |= ZEND_ACC_NODISCARD; + } + zend_compile_stmt(stmt_ast); if (is_method) { diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 63572ab6623cc..14134051ef8e3 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -333,7 +333,7 @@ typedef struct _zend_oparray_context { /* Class cannot be serialized or unserialized | | | */ #define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ /* | | | */ -/* Function Flags (unused: 29-30) | | | */ +/* Function Flags (unused: 30) | | | */ /* ============== | | | */ /* | | | */ /* deprecation flag | | | */ @@ -395,6 +395,9 @@ typedef struct _zend_oparray_context { /* has #[\Override] attribute | | | */ #define ZEND_ACC_OVERRIDE (1 << 28) /* | X | | */ /* | | | */ +/* has #[\NoDiscard] attribute | | | */ +#define ZEND_ACC_NODISCARD (1 << 29) /* | X | | */ +/* | | | */ /* op_array uses strict mode types | | | */ #define ZEND_ACC_STRICT_TYPES (1U << 31) /* | X | | */ diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 44021b785ba95..9237ea794c9d5 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1902,6 +1902,90 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_function(const zend_functi zend_string_release(message_suffix); } +ZEND_COLD static zend_result ZEND_FASTCALL get_nodiscard_suffix_from_attribute(HashTable *attributes, zend_class_entry* scope, zend_string **message_suffix) +{ + *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (!attributes) { + return SUCCESS; + } + + zend_attribute *nodiscard = zend_get_attribute_str(attributes, "nodiscard", sizeof("nodiscard")-1); + + if (!nodiscard) { + return SUCCESS; + } + + if (nodiscard->argc == 0) { + return SUCCESS; + } + + zend_result result = FAILURE; + + zend_string *message = ZSTR_EMPTY_ALLOC(); + + zval obj; + ZVAL_UNDEF(&obj); + zval *z; + + /* Construct the NoDiscard object to correctly handle parameter processing. */ + if (FAILURE == zend_get_attribute_object(&obj, zend_ce_nodiscard, nodiscard, scope, NULL)) { + goto out; + } + + /* Extract the $message property. */ + z = zend_read_property_ex(zend_ce_nodiscard, Z_OBJ_P(&obj), ZSTR_KNOWN(ZEND_STR_MESSAGE), false, NULL); + ZEND_ASSERT(z != &EG(uninitialized_zval)); + if (Z_TYPE_P(z) == IS_STRING) { + message = zend_string_copy(Z_STR_P(z)); + } + + /* Construct the suffix. */ + *message_suffix = zend_strpprintf_unchecked( + 0, + "%s%S", + ZSTR_LEN(message) > 0 ? ", " : "", + message + ); + + result = SUCCESS; + + out: + + zend_string_release(message); + zval_ptr_dtor(&obj); + + return result; +} + +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_nodiscard_function(char *prefix, const zend_function *fbc) +{ + zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); + + if (get_nodiscard_suffix_from_attribute(fbc->common.attributes, fbc->common.scope, &message_suffix) == FAILURE) { + return; + } + + int code = fbc->type == ZEND_INTERNAL_FUNCTION ? E_WARNING : E_USER_WARNING; + + if (fbc->common.scope) { + zend_error_unchecked(code, "%sThe return value of method %s::%s() is expected to be consumed%S", + prefix, + ZSTR_VAL(fbc->common.scope->name), + ZSTR_VAL(fbc->common.function_name), + message_suffix + ); + } else { + zend_error_unchecked(code, "%sThe return value of function %s() is expected to be consumed%S", + prefix, + ZSTR_VAL(fbc->common.function_name), + message_suffix + ); + } + + zend_string_release(message_suffix); +} + ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_class_constant(const zend_class_constant *c, const zend_string *constant_name) { zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index a1fbe049f3f99..a0607c114f40a 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -62,6 +62,7 @@ extern ZEND_API const zend_internal_function zend_pass_function; ZEND_API ZEND_COLD void ZEND_FASTCALL zend_missing_arg_error(zend_execute_data *execute_data); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_function(const zend_function *fbc); +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_nodiscard_function(char *prefix, const zend_function *fbc); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_class_constant(const zend_class_constant *c, const zend_string *constant_name); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_false_to_array_deprecated(void); ZEND_COLD void ZEND_FASTCALL zend_param_must_be_ref(const zend_function *func, uint32_t arg_num); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 7e471b5acd8b6..0bf95aaba19fa 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2936,6 +2936,12 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } EG(vm_stack_top) = (zval*)execute_data; + + if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { + zend_function *fbc = EX(func); + zend_nodiscard_function("(F)", fbc); + } + execute_data = EX(prev_execute_data); if (UNEXPECTED(EG(exception) != NULL)) { @@ -2971,6 +2977,11 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) } old_execute_data = execute_data; + if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { + zend_function *fbc = EX(func); + zend_nodiscard_function("(E)", fbc); + } + execute_data = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -2990,6 +3001,8 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) destroy_op_array(&EX(func)->op_array); efree_size(EX(func), sizeof(zend_op_array)); old_execute_data = execute_data; + ZEND_ASSERT(EX(return_value)); + execute_data = EG(current_execute_data) = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -3010,6 +3023,12 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) } else { if (EXPECTED((call_info & ZEND_CALL_CODE) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + + if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { + zend_function *fbc = EX(func); + zend_nodiscard_function("(G)", fbc); + } + i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); @@ -4108,6 +4127,9 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) } if (!RETURN_VALUE_USED(opline)) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(C)", fbc); + } i_zval_ptr_dtor(ret); } @@ -4237,6 +4259,9 @@ ZEND_VM_C_LABEL(fcall_by_name_end): } if (!RETURN_VALUE_USED(opline)) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(B)", fbc); + } i_zval_ptr_dtor(ret); } } @@ -4351,6 +4376,9 @@ ZEND_VM_C_LABEL(fcall_end): } if (!RETURN_VALUE_USED(opline)) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(A)", fbc); + } i_zval_ptr_dtor(ret); } } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 9209399a5cdbf..8bf3771a8b068 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1148,6 +1148,12 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } EG(vm_stack_top) = (zval*)execute_data; + + if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { + zend_function *fbc = EX(func); + zend_nodiscard_function("(F)", fbc); + } + execute_data = EX(prev_execute_data); if (UNEXPECTED(EG(exception) != NULL)) { @@ -1183,6 +1189,11 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper } old_execute_data = execute_data; + if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { + zend_function *fbc = EX(func); + zend_nodiscard_function("(E)", fbc); + } + execute_data = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -1202,6 +1213,8 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper destroy_op_array(&EX(func)->op_array); efree_size(EX(func), sizeof(zend_op_array)); old_execute_data = execute_data; + ZEND_ASSERT(EX(return_value)); + execute_data = EG(current_execute_data) = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -1222,6 +1235,12 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper } else { if (EXPECTED((call_info & ZEND_CALL_CODE) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + + if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { + zend_function *fbc = EX(func); + zend_nodiscard_function("(G)", fbc); + } + i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); @@ -1327,6 +1346,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_RETV } if (!0) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(C)", fbc); + } i_zval_ptr_dtor(ret); } @@ -1391,6 +1413,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_RETV } if (!1) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(C)", fbc); + } i_zval_ptr_dtor(ret); } @@ -1456,6 +1481,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_OBS } if (!RETURN_VALUE_USED(opline)) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(C)", fbc); + } i_zval_ptr_dtor(ret); } @@ -1631,6 +1659,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S } if (!0) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(B)", fbc); + } i_zval_ptr_dtor(ret); } } @@ -1733,6 +1764,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S } if (!1) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(B)", fbc); + } i_zval_ptr_dtor(ret); } } @@ -1837,6 +1871,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_ } if (!RETURN_VALUE_USED(opline)) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(B)", fbc); + } i_zval_ptr_dtor(ret); } } @@ -1950,6 +1987,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV } if (!0) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(A)", fbc); + } i_zval_ptr_dtor(ret); } } @@ -2068,6 +2108,9 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV } if (!1) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(A)", fbc); + } i_zval_ptr_dtor(ret); } } @@ -2187,6 +2230,9 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_OBS } if (!RETURN_VALUE_USED(opline)) { + if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { + zend_nodiscard_function("(A)", fbc); + } i_zval_ptr_dtor(ret); } } @@ -58685,6 +58731,12 @@ ZEND_API void execute_ex(zend_execute_data *ex) OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } EG(vm_stack_top) = (zval*)execute_data; + + if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { + zend_function *fbc = EX(func); + zend_nodiscard_function("(F)", fbc); + } + execute_data = EX(prev_execute_data); if (UNEXPECTED(EG(exception) != NULL)) { @@ -58720,6 +58772,11 @@ ZEND_API void execute_ex(zend_execute_data *ex) } old_execute_data = execute_data; + if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { + zend_function *fbc = EX(func); + zend_nodiscard_function("(E)", fbc); + } + execute_data = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -58739,6 +58796,8 @@ ZEND_API void execute_ex(zend_execute_data *ex) destroy_op_array(&EX(func)->op_array); efree_size(EX(func), sizeof(zend_op_array)); old_execute_data = execute_data; + ZEND_ASSERT(EX(return_value)); + execute_data = EG(current_execute_data) = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -58759,6 +58818,12 @@ ZEND_API void execute_ex(zend_execute_data *ex) } else { if (EXPECTED((call_info & ZEND_CALL_CODE) == 0)) { EG(current_execute_data) = EX(prev_execute_data); + + if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { + zend_function *fbc = EX(func); + zend_nodiscard_function("(G)", fbc); + } + i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); diff --git a/build/gen_stub.php b/build/gen_stub.php index aef737b37e526..f3cd13fe8a52f 100755 --- a/build/gen_stub.php +++ b/build/gen_stub.php @@ -1516,9 +1516,13 @@ private function getArginfoFlagsByPhpVersions(): array } foreach ($this->attributes as $attr) { - if ($attr->class === "Deprecated") { - $flags[] = "ZEND_ACC_DEPRECATED"; - break; + switch ($attr->class) { + case "Deprecated": + $flags[] = "ZEND_ACC_DEPRECATED"; + break; + case "NoDiscard": + $flags[] = "ZEND_ACC_NODISCARD"; + break; } } diff --git a/ext/date/php_date.stub.php b/ext/date/php_date.stub.php index d0119aac88ad5..f375c60ff0a4c 100644 --- a/ext/date/php_date.stub.php +++ b/ext/date/php_date.stub.php @@ -524,29 +524,38 @@ public function getMicrosecond(): int {} public function diff(DateTimeInterface $targetObject, bool $absolute = false): DateInterval {} /** @tentative-return-type */ + #[\NoDiscard(message: "as DateTimeImmutable::modify() does not modify the object itself")] public function modify(string $modifier): DateTimeImmutable {} /** @tentative-return-type */ + #[\NoDiscard(message: "as DateTimeImmutable::add() does not modify the object itself")] public function add(DateInterval $interval): DateTimeImmutable {} /** @tentative-return-type */ + #[\NoDiscard(message: "as DateTimeImmutable::sub() does not modify the object itself")] public function sub(DateInterval $interval): DateTimeImmutable {} /** @tentative-return-type */ + #[\NoDiscard(message: "as DateTimeImmutable::setTimezone() does not modify the object itself")] public function setTimezone(DateTimeZone $timezone): DateTimeImmutable {} /** @tentative-return-type */ + #[\NoDiscard(message: "as DateTimeImmutable::setTime() does not modify the object itself")] public function setTime(int $hour, int $minute, int $second = 0, int $microsecond = 0): DateTimeImmutable {} /** @tentative-return-type */ + #[\NoDiscard(message: "as DateTimeImmutable::setDate() does not modify the object itself")] public function setDate(int $year, int $month, int $day): DateTimeImmutable {} /** @tentative-return-type */ + #[\NoDiscard(message: "as DateTimeImmutable::setISODate() does not modify the object itself")] public function setISODate(int $year, int $week, int $dayOfWeek = 1): DateTimeImmutable {} /** @tentative-return-type */ + #[\NoDiscard(message: "as DateTimeImmutable::setTimestamp() does not modify the object itself")] public function setTimestamp(int $timestamp): DateTimeImmutable {} + #[\NoDiscard(message: "as DateTimeImmutable::setMicrosecond() does not modify the object itself")] public function setMicrosecond(int $microsecond): static {} /** @tentative-return-type */ diff --git a/ext/date/php_date_arginfo.h b/ext/date/php_date_arginfo.h index 8ce0114206cfe..82e9f0f1718ae 100644 --- a/ext/date/php_date_arginfo.h +++ b/ext/date/php_date_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d7a318f6fd85e23c6352323e03c323035a511738 */ + * Stub hash: 093743b4fe7a698d1262cc1a81b60a85064fdccb */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_MASK_EX(arginfo_strtotime, 0, 1, MAY_BE_LONG|MAY_BE_FALSE) ZEND_ARG_TYPE_INFO(0, datetime, IS_STRING, 0) @@ -725,15 +725,15 @@ static const zend_function_entry class_DateTimeImmutable_methods[] = { ZEND_RAW_FENTRY("getTimestamp", zif_date_timestamp_get, arginfo_class_DateTimeImmutable_getTimestamp, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("getMicrosecond", zim_DateTime_getMicrosecond, arginfo_class_DateTimeImmutable_getMicrosecond, ZEND_ACC_PUBLIC, NULL, NULL) ZEND_RAW_FENTRY("diff", zif_date_diff, arginfo_class_DateTimeImmutable_diff, ZEND_ACC_PUBLIC, NULL, NULL) - ZEND_ME(DateTimeImmutable, modify, arginfo_class_DateTimeImmutable_modify, ZEND_ACC_PUBLIC) - ZEND_ME(DateTimeImmutable, add, arginfo_class_DateTimeImmutable_add, ZEND_ACC_PUBLIC) - ZEND_ME(DateTimeImmutable, sub, arginfo_class_DateTimeImmutable_sub, ZEND_ACC_PUBLIC) - ZEND_ME(DateTimeImmutable, setTimezone, arginfo_class_DateTimeImmutable_setTimezone, ZEND_ACC_PUBLIC) - ZEND_ME(DateTimeImmutable, setTime, arginfo_class_DateTimeImmutable_setTime, ZEND_ACC_PUBLIC) - ZEND_ME(DateTimeImmutable, setDate, arginfo_class_DateTimeImmutable_setDate, ZEND_ACC_PUBLIC) - ZEND_ME(DateTimeImmutable, setISODate, arginfo_class_DateTimeImmutable_setISODate, ZEND_ACC_PUBLIC) - ZEND_ME(DateTimeImmutable, setTimestamp, arginfo_class_DateTimeImmutable_setTimestamp, ZEND_ACC_PUBLIC) - ZEND_ME(DateTimeImmutable, setMicrosecond, arginfo_class_DateTimeImmutable_setMicrosecond, ZEND_ACC_PUBLIC) + ZEND_ME(DateTimeImmutable, modify, arginfo_class_DateTimeImmutable_modify, ZEND_ACC_PUBLIC|ZEND_ACC_NODISCARD) + ZEND_ME(DateTimeImmutable, add, arginfo_class_DateTimeImmutable_add, ZEND_ACC_PUBLIC|ZEND_ACC_NODISCARD) + ZEND_ME(DateTimeImmutable, sub, arginfo_class_DateTimeImmutable_sub, ZEND_ACC_PUBLIC|ZEND_ACC_NODISCARD) + ZEND_ME(DateTimeImmutable, setTimezone, arginfo_class_DateTimeImmutable_setTimezone, ZEND_ACC_PUBLIC|ZEND_ACC_NODISCARD) + ZEND_ME(DateTimeImmutable, setTime, arginfo_class_DateTimeImmutable_setTime, ZEND_ACC_PUBLIC|ZEND_ACC_NODISCARD) + ZEND_ME(DateTimeImmutable, setDate, arginfo_class_DateTimeImmutable_setDate, ZEND_ACC_PUBLIC|ZEND_ACC_NODISCARD) + ZEND_ME(DateTimeImmutable, setISODate, arginfo_class_DateTimeImmutable_setISODate, ZEND_ACC_PUBLIC|ZEND_ACC_NODISCARD) + ZEND_ME(DateTimeImmutable, setTimestamp, arginfo_class_DateTimeImmutable_setTimestamp, ZEND_ACC_PUBLIC|ZEND_ACC_NODISCARD) + ZEND_ME(DateTimeImmutable, setMicrosecond, arginfo_class_DateTimeImmutable_setMicrosecond, ZEND_ACC_PUBLIC|ZEND_ACC_NODISCARD) ZEND_ME(DateTimeImmutable, createFromMutable, arginfo_class_DateTimeImmutable_createFromMutable, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_ME(DateTimeImmutable, createFromInterface, arginfo_class_DateTimeImmutable_createFromInterface, ZEND_ACC_PUBLIC|ZEND_ACC_STATIC) ZEND_FE_END @@ -989,6 +989,88 @@ static zend_class_entry *register_class_DateTimeImmutable(zend_class_entry *clas class_entry = zend_register_internal_class_with_flags(&ce, NULL, 0); zend_class_implements(class_entry, 1, class_entry_DateTimeInterface); + + zend_string *attribute_name_NoDiscard_func_modify_0 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_modify_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "modify", sizeof("modify") - 1), attribute_name_NoDiscard_func_modify_0, 1); + zend_string_release(attribute_name_NoDiscard_func_modify_0); + zval attribute_NoDiscard_func_modify_0_arg0; + zend_string *attribute_NoDiscard_func_modify_0_arg0_str = zend_string_init("as DateTimeImmutable::modify() does not modify the object itself", strlen("as DateTimeImmutable::modify() does not modify the object itself"), 1); + ZVAL_STR(&attribute_NoDiscard_func_modify_0_arg0, attribute_NoDiscard_func_modify_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_modify_0->args[0].value, &attribute_NoDiscard_func_modify_0_arg0); + attribute_NoDiscard_func_modify_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_string *attribute_name_NoDiscard_func_add_0 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_add_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "add", sizeof("add") - 1), attribute_name_NoDiscard_func_add_0, 1); + zend_string_release(attribute_name_NoDiscard_func_add_0); + zval attribute_NoDiscard_func_add_0_arg0; + zend_string *attribute_NoDiscard_func_add_0_arg0_str = zend_string_init("as DateTimeImmutable::add() does not modify the object itself", strlen("as DateTimeImmutable::add() does not modify the object itself"), 1); + ZVAL_STR(&attribute_NoDiscard_func_add_0_arg0, attribute_NoDiscard_func_add_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_add_0->args[0].value, &attribute_NoDiscard_func_add_0_arg0); + attribute_NoDiscard_func_add_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_string *attribute_name_NoDiscard_func_sub_0 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_sub_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "sub", sizeof("sub") - 1), attribute_name_NoDiscard_func_sub_0, 1); + zend_string_release(attribute_name_NoDiscard_func_sub_0); + zval attribute_NoDiscard_func_sub_0_arg0; + zend_string *attribute_NoDiscard_func_sub_0_arg0_str = zend_string_init("as DateTimeImmutable::sub() does not modify the object itself", strlen("as DateTimeImmutable::sub() does not modify the object itself"), 1); + ZVAL_STR(&attribute_NoDiscard_func_sub_0_arg0, attribute_NoDiscard_func_sub_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_sub_0->args[0].value, &attribute_NoDiscard_func_sub_0_arg0); + attribute_NoDiscard_func_sub_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_string *attribute_name_NoDiscard_func_settimezone_0 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_settimezone_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "settimezone", sizeof("settimezone") - 1), attribute_name_NoDiscard_func_settimezone_0, 1); + zend_string_release(attribute_name_NoDiscard_func_settimezone_0); + zval attribute_NoDiscard_func_settimezone_0_arg0; + zend_string *attribute_NoDiscard_func_settimezone_0_arg0_str = zend_string_init("as DateTimeImmutable::setTimezone() does not modify the object itself", strlen("as DateTimeImmutable::setTimezone() does not modify the object itself"), 1); + ZVAL_STR(&attribute_NoDiscard_func_settimezone_0_arg0, attribute_NoDiscard_func_settimezone_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_settimezone_0->args[0].value, &attribute_NoDiscard_func_settimezone_0_arg0); + attribute_NoDiscard_func_settimezone_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_string *attribute_name_NoDiscard_func_settime_0 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_settime_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "settime", sizeof("settime") - 1), attribute_name_NoDiscard_func_settime_0, 1); + zend_string_release(attribute_name_NoDiscard_func_settime_0); + zval attribute_NoDiscard_func_settime_0_arg0; + zend_string *attribute_NoDiscard_func_settime_0_arg0_str = zend_string_init("as DateTimeImmutable::setTime() does not modify the object itself", strlen("as DateTimeImmutable::setTime() does not modify the object itself"), 1); + ZVAL_STR(&attribute_NoDiscard_func_settime_0_arg0, attribute_NoDiscard_func_settime_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_settime_0->args[0].value, &attribute_NoDiscard_func_settime_0_arg0); + attribute_NoDiscard_func_settime_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_string *attribute_name_NoDiscard_func_setdate_0 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_setdate_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "setdate", sizeof("setdate") - 1), attribute_name_NoDiscard_func_setdate_0, 1); + zend_string_release(attribute_name_NoDiscard_func_setdate_0); + zval attribute_NoDiscard_func_setdate_0_arg0; + zend_string *attribute_NoDiscard_func_setdate_0_arg0_str = zend_string_init("as DateTimeImmutable::setDate() does not modify the object itself", strlen("as DateTimeImmutable::setDate() does not modify the object itself"), 1); + ZVAL_STR(&attribute_NoDiscard_func_setdate_0_arg0, attribute_NoDiscard_func_setdate_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_setdate_0->args[0].value, &attribute_NoDiscard_func_setdate_0_arg0); + attribute_NoDiscard_func_setdate_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_string *attribute_name_NoDiscard_func_setisodate_0 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_setisodate_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "setisodate", sizeof("setisodate") - 1), attribute_name_NoDiscard_func_setisodate_0, 1); + zend_string_release(attribute_name_NoDiscard_func_setisodate_0); + zval attribute_NoDiscard_func_setisodate_0_arg0; + zend_string *attribute_NoDiscard_func_setisodate_0_arg0_str = zend_string_init("as DateTimeImmutable::setISODate() does not modify the object itself", strlen("as DateTimeImmutable::setISODate() does not modify the object itself"), 1); + ZVAL_STR(&attribute_NoDiscard_func_setisodate_0_arg0, attribute_NoDiscard_func_setisodate_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_setisodate_0->args[0].value, &attribute_NoDiscard_func_setisodate_0_arg0); + attribute_NoDiscard_func_setisodate_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_string *attribute_name_NoDiscard_func_settimestamp_0 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_settimestamp_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "settimestamp", sizeof("settimestamp") - 1), attribute_name_NoDiscard_func_settimestamp_0, 1); + zend_string_release(attribute_name_NoDiscard_func_settimestamp_0); + zval attribute_NoDiscard_func_settimestamp_0_arg0; + zend_string *attribute_NoDiscard_func_settimestamp_0_arg0_str = zend_string_init("as DateTimeImmutable::setTimestamp() does not modify the object itself", strlen("as DateTimeImmutable::setTimestamp() does not modify the object itself"), 1); + ZVAL_STR(&attribute_NoDiscard_func_settimestamp_0_arg0, attribute_NoDiscard_func_settimestamp_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_settimestamp_0->args[0].value, &attribute_NoDiscard_func_settimestamp_0_arg0); + attribute_NoDiscard_func_settimestamp_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_string *attribute_name_NoDiscard_func_setmicrosecond_0 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_setmicrosecond_0 = zend_add_function_attribute(zend_hash_str_find_ptr(&class_entry->function_table, "setmicrosecond", sizeof("setmicrosecond") - 1), attribute_name_NoDiscard_func_setmicrosecond_0, 1); + zend_string_release(attribute_name_NoDiscard_func_setmicrosecond_0); + zval attribute_NoDiscard_func_setmicrosecond_0_arg0; + zend_string *attribute_NoDiscard_func_setmicrosecond_0_arg0_str = zend_string_init("as DateTimeImmutable::setMicrosecond() does not modify the object itself", strlen("as DateTimeImmutable::setMicrosecond() does not modify the object itself"), 1); + ZVAL_STR(&attribute_NoDiscard_func_setmicrosecond_0_arg0, attribute_NoDiscard_func_setmicrosecond_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_setmicrosecond_0->args[0].value, &attribute_NoDiscard_func_setmicrosecond_0_arg0); + attribute_NoDiscard_func_setmicrosecond_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + return class_entry; } diff --git a/ext/standard/basic_functions.stub.php b/ext/standard/basic_functions.stub.php index 1934610dc0fed..2ed8415ec3190 100644 --- a/ext/standard/basic_functions.stub.php +++ b/ext/standard/basic_functions.stub.php @@ -2732,6 +2732,7 @@ function proc_nice(int $priority): bool {} * @param resource $stream * @param int $would_block */ +#[\NoDiscard(message: "as locking the stream might have failed")] function flock($stream, int $operation, &$would_block = null): bool {} /** diff --git a/ext/standard/basic_functions_arginfo.h b/ext/standard/basic_functions_arginfo.h index 61feae1d88c68..65636a6eb0bcb 100644 --- a/ext/standard/basic_functions_arginfo.h +++ b/ext/standard/basic_functions_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: b0a0ccc94c6db2831f7f0b8e67562cd6a734fcdf */ + * Stub hash: 3c0741b456b5caca376d2e6a0780c0415bb28734 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_set_time_limit, 0, 1, _IS_BOOL, 0) ZEND_ARG_TYPE_INFO(0, seconds, IS_LONG, 0) @@ -3205,7 +3205,7 @@ static const zend_function_entry ext_functions[] = { #if defined(HAVE_NICE) ZEND_FE(proc_nice, arginfo_proc_nice) #endif - ZEND_FE(flock, arginfo_flock) + ZEND_RAW_FENTRY("flock", zif_flock, arginfo_flock, ZEND_ACC_NODISCARD, NULL, NULL) ZEND_FE(get_meta_tags, arginfo_get_meta_tags) ZEND_FE(pclose, arginfo_pclose) ZEND_FE(popen, arginfo_popen) @@ -4039,6 +4039,15 @@ static void register_basic_functions_symbols(int module_number) ZVAL_COPY_VALUE(&attribute_Deprecated_func_utf8_decode_0->args[1].value, &attribute_Deprecated_func_utf8_decode_0_arg1); attribute_Deprecated_func_utf8_decode_0->args[1].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + zend_string *attribute_name_NoDiscard_func_flock_0 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_flock_0 = zend_add_function_attribute(zend_hash_str_find_ptr(CG(function_table), "flock", sizeof("flock") - 1), attribute_name_NoDiscard_func_flock_0, 1); + zend_string_release(attribute_name_NoDiscard_func_flock_0); + zval attribute_NoDiscard_func_flock_0_arg0; + zend_string *attribute_NoDiscard_func_flock_0_arg0_str = zend_string_init("as locking the stream might have failed", strlen("as locking the stream might have failed"), 1); + ZVAL_STR(&attribute_NoDiscard_func_flock_0_arg0, attribute_NoDiscard_func_flock_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_flock_0->args[0].value, &attribute_NoDiscard_func_flock_0_arg0); + attribute_NoDiscard_func_flock_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "password_hash", sizeof("password_hash") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "password_verify", sizeof("password_verify") - 1), 0, ZSTR_KNOWN(ZEND_STR_SENSITIVEPARAMETER), 0); diff --git a/ext/zend_test/test.c b/ext/zend_test/test.c index bcc3351e03313..aad4128ed8ce2 100644 --- a/ext/zend_test/test.c +++ b/ext/zend_test/test.c @@ -123,6 +123,13 @@ static ZEND_FUNCTION(zend_test_deprecated_attr) ZEND_PARSE_PARAMETERS_NONE(); } +static ZEND_FUNCTION(zend_test_deprecated_nodiscard) +{ + ZEND_PARSE_PARAMETERS_NONE(); + + RETURN_LONG(1); +} + /* Create a string without terminating null byte. Must be terminated with * zend_terminate_string() before destruction, otherwise a warning is issued * in debug builds. */ diff --git a/ext/zend_test/test.stub.php b/ext/zend_test/test.stub.php index 59cb9661e4e43..88007ae01fe56 100644 --- a/ext/zend_test/test.stub.php +++ b/ext/zend_test/test.stub.php @@ -218,6 +218,11 @@ function zend_test_deprecated(mixed $arg = null): void {} #[\Deprecated(message: "custom message")] function zend_test_deprecated_attr(): void {} + + #[\Deprecated(message: "custom message")] + #[\NoDiscard(message: "custom message 2")] + function zend_test_deprecated_nodiscard(): int {} + /** @alias zend_test_void_return */ function zend_test_aliased(): void {} diff --git a/ext/zend_test/test_arginfo.h b/ext/zend_test/test_arginfo.h index c558b58f65169..fc9a4e654bc7d 100644 --- a/ext/zend_test/test_arginfo.h +++ b/ext/zend_test/test_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: 3082e62e96d5f4383c98638513463c676a7c3a69 */ + * Stub hash: 9e70ab275967137c764d0983ea09a95f7594e799 */ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_array_return, 0, 0, IS_ARRAY, 0) ZEND_END_ARG_INFO() @@ -22,6 +22,9 @@ ZEND_END_ARG_INFO() #define arginfo_zend_test_deprecated_attr arginfo_zend_test_void_return +ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_deprecated_nodiscard, 0, 0, IS_LONG, 0) +ZEND_END_ARG_INFO() + #define arginfo_zend_test_aliased arginfo_zend_test_void_return #define arginfo_zend_test_deprecated_aliased arginfo_zend_test_void_return @@ -126,8 +129,7 @@ ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_is_string_marked_as_va ZEND_ARG_TYPE_INFO(0, string, IS_STRING, 0) ZEND_END_ARG_INFO() -ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_get_map_ptr_last, 0, 0, IS_LONG, 0) -ZEND_END_ARG_INFO() +#define arginfo_zend_get_map_ptr_last arginfo_zend_test_deprecated_nodiscard ZEND_BEGIN_ARG_WITH_RETURN_TYPE_INFO_EX(arginfo_zend_test_crash, 0, 0, IS_VOID, 0) ZEND_ARG_TYPE_INFO_WITH_DEFAULT_VALUE(0, message, IS_STRING, 1, "null") @@ -185,7 +187,7 @@ ZEND_END_ARG_INFO() #define arginfo_ZendTestNS2_ZendSubNS_namespaced_deprecated_aliased_func arginfo_zend_test_void_return -#define arginfo_class__ZendTestClass_is_object arginfo_zend_get_map_ptr_last +#define arginfo_class__ZendTestClass_is_object arginfo_zend_test_deprecated_nodiscard #define arginfo_class__ZendTestClass___toString arginfo_zend_get_current_func_name @@ -260,6 +262,7 @@ static ZEND_FUNCTION(zend_test_void_return); static ZEND_FUNCTION(zend_test_compile_string); static ZEND_FUNCTION(zend_test_deprecated); static ZEND_FUNCTION(zend_test_deprecated_attr); +static ZEND_FUNCTION(zend_test_deprecated_nodiscard); static ZEND_FUNCTION(zend_create_unterminated_string); static ZEND_FUNCTION(zend_terminate_string); static ZEND_FUNCTION(zend_leak_variable); @@ -355,6 +358,11 @@ static const zend_function_entry ext_functions[] = { #else ZEND_RAW_FENTRY("zend_test_deprecated_attr", zif_zend_test_deprecated_attr, arginfo_zend_test_deprecated_attr, ZEND_ACC_DEPRECATED) #endif +#if (PHP_VERSION_ID >= 80400) + ZEND_RAW_FENTRY("zend_test_deprecated_nodiscard", zif_zend_test_deprecated_nodiscard, arginfo_zend_test_deprecated_nodiscard, ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD, NULL, NULL) +#else + ZEND_RAW_FENTRY("zend_test_deprecated_nodiscard", zif_zend_test_deprecated_nodiscard, arginfo_zend_test_deprecated_nodiscard, ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD) +#endif #if (PHP_VERSION_ID >= 80400) ZEND_RAW_FENTRY("zend_test_aliased", zif_zend_test_void_return, arginfo_zend_test_aliased, 0, NULL, NULL) #else @@ -560,6 +568,24 @@ static void register_test_symbols(int module_number) ZVAL_COPY_VALUE(&attribute_Deprecated_func_zend_test_deprecated_attr_0->args[0].value, &attribute_Deprecated_func_zend_test_deprecated_attr_0_arg0); attribute_Deprecated_func_zend_test_deprecated_attr_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + zend_string *attribute_name_Deprecated_func_zend_test_deprecated_nodiscard_0 = zend_string_init_interned("Deprecated", sizeof("Deprecated") - 1, 1); + zend_attribute *attribute_Deprecated_func_zend_test_deprecated_nodiscard_0 = zend_add_function_attribute(zend_hash_str_find_ptr(CG(function_table), "zend_test_deprecated_nodiscard", sizeof("zend_test_deprecated_nodiscard") - 1), attribute_name_Deprecated_func_zend_test_deprecated_nodiscard_0, 1); + zend_string_release(attribute_name_Deprecated_func_zend_test_deprecated_nodiscard_0); + zval attribute_Deprecated_func_zend_test_deprecated_nodiscard_0_arg0; + zend_string *attribute_Deprecated_func_zend_test_deprecated_nodiscard_0_arg0_str = zend_string_init("custom message", strlen("custom message"), 1); + ZVAL_STR(&attribute_Deprecated_func_zend_test_deprecated_nodiscard_0_arg0, attribute_Deprecated_func_zend_test_deprecated_nodiscard_0_arg0_str); + ZVAL_COPY_VALUE(&attribute_Deprecated_func_zend_test_deprecated_nodiscard_0->args[0].value, &attribute_Deprecated_func_zend_test_deprecated_nodiscard_0_arg0); + attribute_Deprecated_func_zend_test_deprecated_nodiscard_0->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + + zend_string *attribute_name_NoDiscard_func_zend_test_deprecated_nodiscard_1 = zend_string_init_interned("NoDiscard", sizeof("NoDiscard") - 1, 1); + zend_attribute *attribute_NoDiscard_func_zend_test_deprecated_nodiscard_1 = zend_add_function_attribute(zend_hash_str_find_ptr(CG(function_table), "zend_test_deprecated_nodiscard", sizeof("zend_test_deprecated_nodiscard") - 1), attribute_name_NoDiscard_func_zend_test_deprecated_nodiscard_1, 1); + zend_string_release(attribute_name_NoDiscard_func_zend_test_deprecated_nodiscard_1); + zval attribute_NoDiscard_func_zend_test_deprecated_nodiscard_1_arg0; + zend_string *attribute_NoDiscard_func_zend_test_deprecated_nodiscard_1_arg0_str = zend_string_init("custom message 2", strlen("custom message 2"), 1); + ZVAL_STR(&attribute_NoDiscard_func_zend_test_deprecated_nodiscard_1_arg0, attribute_NoDiscard_func_zend_test_deprecated_nodiscard_1_arg0_str); + ZVAL_COPY_VALUE(&attribute_NoDiscard_func_zend_test_deprecated_nodiscard_1->args[0].value, &attribute_NoDiscard_func_zend_test_deprecated_nodiscard_1_arg0); + attribute_NoDiscard_func_zend_test_deprecated_nodiscard_1->args[0].name = ZSTR_KNOWN(ZEND_STR_MESSAGE); + zend_string *attribute_name_ZendTestParameterAttribute_func_zend_test_parameter_with_attribute_arg0_0 = zend_string_init_interned("ZendTestParameterAttribute", sizeof("ZendTestParameterAttribute") - 1, 1); zend_attribute *attribute_ZendTestParameterAttribute_func_zend_test_parameter_with_attribute_arg0_0 = zend_add_parameter_attribute(zend_hash_str_find_ptr(CG(function_table), "zend_test_parameter_with_attribute", sizeof("zend_test_parameter_with_attribute") - 1), 0, attribute_name_ZendTestParameterAttribute_func_zend_test_parameter_with_attribute_arg0_0, 1); zend_string_release(attribute_name_ZendTestParameterAttribute_func_zend_test_parameter_with_attribute_arg0_0); From 24551e95731fb9732afe1a822865220993839f42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Fri, 24 Jan 2025 21:03:10 +0100 Subject: [PATCH 02/10] Check NoDiscard together with Deprecated --- Zend/tests/attributes/nodiscard/001.phpt | 22 ++-- Zend/tests/attributes/nodiscard/002.phpt | 6 +- Zend/tests/attributes/nodiscard/003.phpt | 2 +- Zend/tests/attributes/nodiscard/005.phpt | 2 +- Zend/tests/attributes/nodiscard/006.phpt | 2 +- Zend/tests/attributes/nodiscard/007.phpt | 2 +- Zend/tests/attributes/nodiscard/008.phpt | 2 +- .../nodiscard/throwing_error_handler_001.phpt | 4 +- .../nodiscard/type_validation_001.phpt | 2 +- Zend/zend_compile.c | 4 +- Zend/zend_object_handlers.c | 2 +- Zend/zend_vm_def.h | 46 +++---- Zend/zend_vm_execute.h | 119 +++++++----------- 13 files changed, 81 insertions(+), 134 deletions(-) diff --git a/Zend/tests/attributes/nodiscard/001.phpt b/Zend/tests/attributes/nodiscard/001.phpt index f44d1cbc642cc..82fc49450cd1c 100644 --- a/Zend/tests/attributes/nodiscard/001.phpt +++ b/Zend/tests/attributes/nodiscard/001.phpt @@ -63,24 +63,24 @@ $closure2(); ?> --EXPECTF-- -Warning: (F)The return value of function test() is expected to be consumed in %s on line %d +Warning: (B)The return value of function test() is expected to be consumed in %s on line %d -Warning: (F)The return value of function test2() is expected to be consumed, this is important in %s on line %d +Warning: (B)The return value of function test2() is expected to be consumed, this is important in %s on line %d -Warning: (E)The return value of function test3() is expected to be consumed in %s on line %d +Warning: (B)The return value of function test3() is expected to be consumed in %s on line %d -Warning: (F)The return value of function test() is expected to be consumed in %s on line %d +Warning: (A)The return value of function test() is expected to be consumed in %s on line %d -Warning: (F)The return value of function test() is expected to be consumed in %s on line %d +Warning: (A)The return value of function test() is expected to be consumed in %s on line %d -Warning: (F)The return value of method Clazz::test() is expected to be consumed in %s on line %d +Warning: (A)The return value of method Clazz::test() is expected to be consumed in %s on line %d -Warning: (F)The return value of method Clazz::test2() is expected to be consumed, this is important in %s on line %d +Warning: (A)The return value of method Clazz::test2() is expected to be consumed, this is important in %s on line %d -Warning: (F)The return value of method Clazz::test3() is expected to be consumed in %s on line %d +Warning: (B)The return value of method Clazz::test3() is expected to be consumed in %s on line %d -Warning: (F)The return value of method Clazz::test() is expected to be consumed in %s on line %d +Warning: (A)The return value of method Clazz::test() is expected to be consumed in %s on line %d -Warning: (F)The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d +Warning: (A)The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d -Warning: (F)The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d +Warning: (A)The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/002.phpt b/Zend/tests/attributes/nodiscard/002.phpt index ce60a427dad6b..58e130e4cd460 100644 --- a/Zend/tests/attributes/nodiscard/002.phpt +++ b/Zend/tests/attributes/nodiscard/002.phpt @@ -33,12 +33,12 @@ $cls('foo'); ?> --EXPECTF-- +Warning: (A)The return value of method Clazz::test() is expected to be consumed in %s on line %d __call(test) -Warning: (F)The return value of method Clazz::__call() is expected to be consumed in %s on line %d +Warning: (A)The return value of method Clazz::test() is expected to be consumed in %s on line %d __callStatic(test) -Warning: (F)The return value of method Clazz::__callStatic() is expected to be consumed in %s on line %d +Warning: (A)The return value of method Clazz::__invoke() is expected to be consumed in %s on line %d __invoke(foo) -Warning: (F)The return value of method Clazz::__invoke() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/003.phpt b/Zend/tests/attributes/nodiscard/003.phpt index 17ee75c6269a3..23d838aa9f337 100644 --- a/Zend/tests/attributes/nodiscard/003.phpt +++ b/Zend/tests/attributes/nodiscard/003.phpt @@ -19,4 +19,4 @@ $cls->test(); ?> --EXPECTF-- -Warning: (F)The return value of method Clazz::test() is expected to be consumed in %s on line %d +Warning: (A)The return value of method Clazz::test() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/005.phpt b/Zend/tests/attributes/nodiscard/005.phpt index c0ad4627acc0a..ddef8f4a972e9 100644 --- a/Zend/tests/attributes/nodiscard/005.phpt +++ b/Zend/tests/attributes/nodiscard/005.phpt @@ -12,6 +12,6 @@ $date->setTimestamp(0); ?> --EXPECTF-- -Warning: (C)The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d +Warning: (B)The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d Warning: (A)The return value of method DateTimeImmutable::setTimestamp() is expected to be consumed, as DateTimeImmutable::setTimestamp() does not modify the object itself in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/006.phpt b/Zend/tests/attributes/nodiscard/006.phpt index d7cf68a9f751b..61fee1c9562b3 100644 --- a/Zend/tests/attributes/nodiscard/006.phpt +++ b/Zend/tests/attributes/nodiscard/006.phpt @@ -17,4 +17,4 @@ test(); ?> --EXPECTF-- -Warning: (G)The return value of function test() is expected to be consumed in %s on line %d +Warning: (A)The return value of function test() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/007.phpt b/Zend/tests/attributes/nodiscard/007.phpt index b663b1f7b8b0a..9f9eabd798d5f 100644 --- a/Zend/tests/attributes/nodiscard/007.phpt +++ b/Zend/tests/attributes/nodiscard/007.phpt @@ -14,8 +14,8 @@ fclose($f); ?> --EXPECTF-- - Warning: (A)The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d + diff --git a/Zend/tests/attributes/nodiscard/008.phpt b/Zend/tests/attributes/nodiscard/008.phpt index 1129d9903d7a2..8fcbe7e468cb0 100644 --- a/Zend/tests/attributes/nodiscard/008.phpt +++ b/Zend/tests/attributes/nodiscard/008.phpt @@ -15,4 +15,4 @@ test(); --EXPECTF-- Deprecated: Function test() is deprecated in %s on line %d -Warning: (F)The return value of function test() is expected to be consumed in %s on line %d +Warning: (B)The return value of function test() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt b/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt index b745adc67352a..89bd9f9ffba4c 100644 --- a/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt +++ b/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt @@ -31,5 +31,5 @@ try { ?> --EXPECT-- -Caught: (F)The return value of function test() is expected to be consumed -Caught: (F)The return value of function test2() is expected to be consumed +Caught: (B)The return value of function test() is expected to be consumed +Caught: (B)The return value of function test2() is expected to be consumed diff --git a/Zend/tests/attributes/nodiscard/type_validation_001.phpt b/Zend/tests/attributes/nodiscard/type_validation_001.phpt index e50468d942c76..6c4ed19e8a862 100644 --- a/Zend/tests/attributes/nodiscard/type_validation_001.phpt +++ b/Zend/tests/attributes/nodiscard/type_validation_001.phpt @@ -12,4 +12,4 @@ test(); ?> --EXPECTF-- -Warning: (F)The return value of function test() is expected to be consumed, 1234 in %s on line %d +Warning: (B)The return value of function test() is expected to be consumed, 1234 in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index c2e94ce20345f..55d4921799f83 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -3927,7 +3927,7 @@ ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, zend_function *fbc) /* ZEND_ASSERT(!(fbc->common.fn_flags & ZEND_ACC_CALL_VIA_TRAMPOLINE)); if (fbc->type == ZEND_INTERNAL_FUNCTION && !(CG(compiler_options) & ZEND_COMPILE_IGNORE_INTERNAL_FUNCTIONS)) { if (init_op->opcode == ZEND_INIT_FCALL && !zend_execute_internal) { - if (!(fbc->common.fn_flags & ZEND_ACC_DEPRECATED)) { + if (!(fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD))) { return ZEND_DO_ICALL; } else { return ZEND_DO_FCALL_BY_NAME; @@ -3935,7 +3935,7 @@ ZEND_API uint8_t zend_get_call_op(const zend_op *init_op, zend_function *fbc) /* } } else if (!(CG(compiler_options) & ZEND_COMPILE_IGNORE_USER_FUNCTIONS)){ if (zend_execute_ex == execute_ex) { - if (!(fbc->common.fn_flags & ZEND_ACC_DEPRECATED)) { + if (!(fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD))) { return ZEND_DO_UCALL; } else { return ZEND_DO_FCALL_BY_NAME; diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index c36261f0fb2c9..836273de910ca 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -1617,7 +1617,7 @@ 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)); + | (fbc->common.fn_flags & (ZEND_ACC_RETURN_REFERENCE|ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)); if (fbc->common.attributes) { func->attributes = fbc->common.attributes; GC_TRY_ADDREF(func->attributes); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 0bf95aaba19fa..3950c95ba7b6e 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -2936,12 +2936,6 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } EG(vm_stack_top) = (zval*)execute_data; - - if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { - zend_function *fbc = EX(func); - zend_nodiscard_function("(F)", fbc); - } - execute_data = EX(prev_execute_data); if (UNEXPECTED(EG(exception) != NULL)) { @@ -2977,11 +2971,6 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) } old_execute_data = execute_data; - if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { - zend_function *fbc = EX(func); - zend_nodiscard_function("(E)", fbc); - } - execute_data = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -3001,8 +2990,6 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) destroy_op_array(&EX(func)->op_array); efree_size(EX(func), sizeof(zend_op_array)); old_execute_data = execute_data; - ZEND_ASSERT(EX(return_value)); - execute_data = EG(current_execute_data) = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -3023,12 +3010,6 @@ ZEND_VM_HOT_HELPER(zend_leave_helper, ANY, ANY) } else { if (EXPECTED((call_info & ZEND_CALL_CODE) == 0)) { EG(current_execute_data) = EX(prev_execute_data); - - if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { - zend_function *fbc = EX(func); - zend_nodiscard_function("(G)", fbc); - } - i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); @@ -4127,9 +4108,6 @@ ZEND_VM_HOT_HANDLER(129, ZEND_DO_ICALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) } if (!RETURN_VALUE_USED(opline)) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(C)", fbc); - } i_zval_ptr_dtor(ret); } @@ -4178,8 +4156,13 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER)) SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) { - zend_deprecated_function(fbc); + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { + zend_deprecated_function(fbc); + } + if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !RETURN_VALUE_USED(opline)) { + zend_nodiscard_function("(B)", fbc); + } if (UNEXPECTED(EG(exception) != NULL)) { UNDEF_RESULT(); if (!RETURN_VALUE_USED(opline)) { @@ -4259,9 +4242,6 @@ ZEND_VM_C_LABEL(fcall_by_name_end): } if (!RETURN_VALUE_USED(opline)) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(B)", fbc); - } i_zval_ptr_dtor(ret); } } @@ -4285,8 +4265,13 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) { - zend_deprecated_function(fbc); + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { + zend_deprecated_function(fbc); + } + if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !RETURN_VALUE_USED(opline)) { + zend_nodiscard_function("(A)", fbc); + } if (UNEXPECTED(EG(exception) != NULL)) { if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); @@ -4376,9 +4361,6 @@ ZEND_VM_C_LABEL(fcall_end): } if (!RETURN_VALUE_USED(opline)) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(A)", fbc); - } i_zval_ptr_dtor(ret); } } diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 8bf3771a8b068..9a568515729f1 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1148,12 +1148,6 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } EG(vm_stack_top) = (zval*)execute_data; - - if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { - zend_function *fbc = EX(func); - zend_nodiscard_function("(F)", fbc); - } - execute_data = EX(prev_execute_data); if (UNEXPECTED(EG(exception) != NULL)) { @@ -1189,11 +1183,6 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper } old_execute_data = execute_data; - if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { - zend_function *fbc = EX(func); - zend_nodiscard_function("(E)", fbc); - } - execute_data = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -1213,8 +1202,6 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper destroy_op_array(&EX(func)->op_array); efree_size(EX(func), sizeof(zend_op_array)); old_execute_data = execute_data; - ZEND_ASSERT(EX(return_value)); - execute_data = EG(current_execute_data) = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -1235,12 +1222,6 @@ static zend_never_inline ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_leave_helper } else { if (EXPECTED((call_info & ZEND_CALL_CODE) == 0)) { EG(current_execute_data) = EX(prev_execute_data); - - if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { - zend_function *fbc = EX(func); - zend_nodiscard_function("(G)", fbc); - } - i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); @@ -1346,9 +1327,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_RETV } if (!0) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(C)", fbc); - } i_zval_ptr_dtor(ret); } @@ -1413,9 +1391,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_RETV } if (!1) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(C)", fbc); - } i_zval_ptr_dtor(ret); } @@ -1481,9 +1456,6 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_ICALL_SPEC_OBS } if (!RETURN_VALUE_USED(opline)) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(C)", fbc); - } i_zval_ptr_dtor(ret); } @@ -1580,8 +1552,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) { - zend_deprecated_function(fbc); + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { + zend_deprecated_function(fbc); + } + if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !0) { + zend_nodiscard_function("(B)", fbc); + } if (UNEXPECTED(EG(exception) != NULL)) { UNDEF_RESULT(); if (!0) { @@ -1659,9 +1636,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S } if (!0) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(B)", fbc); - } i_zval_ptr_dtor(ret); } } @@ -1685,8 +1659,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) { - zend_deprecated_function(fbc); + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { + zend_deprecated_function(fbc); + } + if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !1) { + zend_nodiscard_function("(B)", fbc); + } if (UNEXPECTED(EG(exception) != NULL)) { UNDEF_RESULT(); if (!1) { @@ -1764,9 +1743,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S } if (!1) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(B)", fbc); - } i_zval_ptr_dtor(ret); } } @@ -1790,8 +1766,13 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_ SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) { - zend_deprecated_function(fbc); + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { + zend_deprecated_function(fbc); + } + if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !RETURN_VALUE_USED(opline)) { + zend_nodiscard_function("(B)", fbc); + } if (UNEXPECTED(EG(exception) != NULL)) { UNDEF_RESULT(); if (!RETURN_VALUE_USED(opline)) { @@ -1871,9 +1852,6 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_ } if (!RETURN_VALUE_USED(opline)) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(B)", fbc); - } i_zval_ptr_dtor(ret); } } @@ -1897,8 +1875,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) { - zend_deprecated_function(fbc); + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { + zend_deprecated_function(fbc); + } + if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !0) { + zend_nodiscard_function("(A)", fbc); + } if (UNEXPECTED(EG(exception) != NULL)) { if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); @@ -1987,9 +1970,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV } if (!0) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(A)", fbc); - } i_zval_ptr_dtor(ret); } } @@ -2018,8 +1998,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) { - zend_deprecated_function(fbc); + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { + zend_deprecated_function(fbc); + } + if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !1) { + zend_nodiscard_function("(A)", fbc); + } if (UNEXPECTED(EG(exception) != NULL)) { if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); @@ -2108,9 +2093,6 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV } if (!1) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(A)", fbc); - } i_zval_ptr_dtor(ret); } } @@ -2139,8 +2121,13 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_OBS SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0)) { - zend_deprecated_function(fbc); + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { + zend_deprecated_function(fbc); + } + if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !RETURN_VALUE_USED(opline)) { + zend_nodiscard_function("(A)", fbc); + } if (UNEXPECTED(EG(exception) != NULL)) { if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE)) { OBJ_RELEASE(ZEND_CLOSURE_OBJECT(call->func)); @@ -2230,9 +2217,6 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_OBS } if (!RETURN_VALUE_USED(opline)) { - if (UNEXPECTED((fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0)) { - zend_nodiscard_function("(A)", fbc); - } i_zval_ptr_dtor(ret); } } @@ -58731,12 +58715,6 @@ ZEND_API void execute_ex(zend_execute_data *ex) OBJ_RELEASE(ZEND_CLOSURE_OBJECT(EX(func))); } EG(vm_stack_top) = (zval*)execute_data; - - if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { - zend_function *fbc = EX(func); - zend_nodiscard_function("(F)", fbc); - } - execute_data = EX(prev_execute_data); if (UNEXPECTED(EG(exception) != NULL)) { @@ -58772,11 +58750,6 @@ ZEND_API void execute_ex(zend_execute_data *ex) } old_execute_data = execute_data; - if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { - zend_function *fbc = EX(func); - zend_nodiscard_function("(E)", fbc); - } - execute_data = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -58796,8 +58769,6 @@ ZEND_API void execute_ex(zend_execute_data *ex) destroy_op_array(&EX(func)->op_array); efree_size(EX(func), sizeof(zend_op_array)); old_execute_data = execute_data; - ZEND_ASSERT(EX(return_value)); - execute_data = EG(current_execute_data) = EX(prev_execute_data); zend_vm_stack_free_call_frame_ex(call_info, old_execute_data); @@ -58818,12 +58789,6 @@ ZEND_API void execute_ex(zend_execute_data *ex) } else { if (EXPECTED((call_info & ZEND_CALL_CODE) == 0)) { EG(current_execute_data) = EX(prev_execute_data); - - if (UNEXPECTED((EX(func)->common.fn_flags & ZEND_ACC_NODISCARD) != 0) && !EX(return_value)) { - zend_function *fbc = EX(func); - zend_nodiscard_function("(G)", fbc); - } - i_free_compiled_variables(execute_data); #ifdef ZEND_PREFER_RELOAD call_info = EX_CALL_INFO(); From 3f767411db6b847c44fff2990292c9ca6df7cdf7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Fri, 24 Jan 2025 23:10:25 +0100 Subject: [PATCH 03/10] Leverage RETVAL opcode specialization --- Zend/zend_vm_def.h | 12 ++++++++---- Zend/zend_vm_execute.h | 36 ++++++++++++++++++++++++------------ 2 files changed, 32 insertions(+), 16 deletions(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 3950c95ba7b6e..d5cc6aab375e9 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4156,11 +4156,13 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER)) SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + const uint32_t no_discard = (!RETURN_VALUE_USED(opline)) * ZEND_ACC_NODISCARD; + + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { zend_deprecated_function(fbc); } - if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !RETURN_VALUE_USED(opline)) { + if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { zend_nodiscard_function("(B)", fbc); } if (UNEXPECTED(EG(exception) != NULL)) { @@ -4265,11 +4267,13 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + const uint32_t no_discard = (!RETURN_VALUE_USED(opline)) * ZEND_ACC_NODISCARD; + + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { zend_deprecated_function(fbc); } - if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !RETURN_VALUE_USED(opline)) { + if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { zend_nodiscard_function("(A)", fbc); } if (UNEXPECTED(EG(exception) != NULL)) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 9a568515729f1..d00e6b2c65ef5 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1552,11 +1552,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + const uint32_t no_discard = (!0) * ZEND_ACC_NODISCARD; + + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { zend_deprecated_function(fbc); } - if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !0) { + if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { zend_nodiscard_function("(B)", fbc); } if (UNEXPECTED(EG(exception) != NULL)) { @@ -1659,11 +1661,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + const uint32_t no_discard = (!1) * ZEND_ACC_NODISCARD; + + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { zend_deprecated_function(fbc); } - if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !1) { + if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { zend_nodiscard_function("(B)", fbc); } if (UNEXPECTED(EG(exception) != NULL)) { @@ -1766,11 +1770,13 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_ SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + const uint32_t no_discard = (!RETURN_VALUE_USED(opline)) * ZEND_ACC_NODISCARD; + + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { zend_deprecated_function(fbc); } - if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !RETURN_VALUE_USED(opline)) { + if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { zend_nodiscard_function("(B)", fbc); } if (UNEXPECTED(EG(exception) != NULL)) { @@ -1875,11 +1881,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + const uint32_t no_discard = (!0) * ZEND_ACC_NODISCARD; + + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { zend_deprecated_function(fbc); } - if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !0) { + if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { zend_nodiscard_function("(A)", fbc); } if (UNEXPECTED(EG(exception) != NULL)) { @@ -1998,11 +2006,13 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + const uint32_t no_discard = (!1) * ZEND_ACC_NODISCARD; + + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { zend_deprecated_function(fbc); } - if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !1) { + if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { zend_nodiscard_function("(A)", fbc); } if (UNEXPECTED(EG(exception) != NULL)) { @@ -2121,11 +2131,13 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_OBS SAVE_OPLINE(); EX(call) = call->prev_execute_data; - if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) != 0)) { + const uint32_t no_discard = (!RETURN_VALUE_USED(opline)) * ZEND_ACC_NODISCARD; + + if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { zend_deprecated_function(fbc); } - if (EG(exception) == NULL && (fbc->common.fn_flags & ZEND_ACC_NODISCARD) != 0 && !RETURN_VALUE_USED(opline)) { + if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { zend_nodiscard_function("(A)", fbc); } if (UNEXPECTED(EG(exception) != NULL)) { From fac46db2b07eea95157e3f4df77f697638bda28a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 28 Jan 2025 14:48:18 +0100 Subject: [PATCH 04/10] Remove prefix parameter from `zend_nodiscard_function()` --- Zend/tests/attributes/nodiscard/001.phpt | 22 +++++++++---------- Zend/tests/attributes/nodiscard/002.phpt | 6 ++--- Zend/tests/attributes/nodiscard/003.phpt | 2 +- Zend/tests/attributes/nodiscard/005.phpt | 4 ++-- Zend/tests/attributes/nodiscard/006.phpt | 2 +- Zend/tests/attributes/nodiscard/007.phpt | 2 +- Zend/tests/attributes/nodiscard/008.phpt | 2 +- Zend/tests/attributes/nodiscard/009.phpt | 2 +- .../nodiscard/throwing_error_handler_001.phpt | 4 ++-- .../nodiscard/type_validation_001.phpt | 2 +- Zend/zend_execute.c | 8 +++---- Zend/zend_execute.h | 2 +- Zend/zend_vm_def.h | 4 ++-- Zend/zend_vm_execute.h | 12 +++++----- 14 files changed, 36 insertions(+), 38 deletions(-) diff --git a/Zend/tests/attributes/nodiscard/001.phpt b/Zend/tests/attributes/nodiscard/001.phpt index 82fc49450cd1c..3a604efb2abf8 100644 --- a/Zend/tests/attributes/nodiscard/001.phpt +++ b/Zend/tests/attributes/nodiscard/001.phpt @@ -63,24 +63,24 @@ $closure2(); ?> --EXPECTF-- -Warning: (B)The return value of function test() is expected to be consumed in %s on line %d +Warning: The return value of function test() is expected to be consumed in %s on line %d -Warning: (B)The return value of function test2() is expected to be consumed, this is important in %s on line %d +Warning: The return value of function test2() is expected to be consumed, this is important in %s on line %d -Warning: (B)The return value of function test3() is expected to be consumed in %s on line %d +Warning: The return value of function test3() is expected to be consumed in %s on line %d -Warning: (A)The return value of function test() is expected to be consumed in %s on line %d +Warning: The return value of function test() is expected to be consumed in %s on line %d -Warning: (A)The return value of function test() is expected to be consumed in %s on line %d +Warning: The return value of function test() is expected to be consumed in %s on line %d -Warning: (A)The return value of method Clazz::test() is expected to be consumed in %s on line %d +Warning: The return value of method Clazz::test() is expected to be consumed in %s on line %d -Warning: (A)The return value of method Clazz::test2() is expected to be consumed, this is important in %s on line %d +Warning: The return value of method Clazz::test2() is expected to be consumed, this is important in %s on line %d -Warning: (B)The return value of method Clazz::test3() is expected to be consumed in %s on line %d +Warning: The return value of method Clazz::test3() is expected to be consumed in %s on line %d -Warning: (A)The return value of method Clazz::test() is expected to be consumed in %s on line %d +Warning: The return value of method Clazz::test() is expected to be consumed in %s on line %d -Warning: (A)The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d +Warning: The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d -Warning: (A)The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d +Warning: The return value of function {closure:%s:%d}() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/002.phpt b/Zend/tests/attributes/nodiscard/002.phpt index 58e130e4cd460..a2636ac00f606 100644 --- a/Zend/tests/attributes/nodiscard/002.phpt +++ b/Zend/tests/attributes/nodiscard/002.phpt @@ -33,12 +33,12 @@ $cls('foo'); ?> --EXPECTF-- -Warning: (A)The return value of method Clazz::test() is expected to be consumed in %s on line %d +Warning: The return value of method Clazz::test() is expected to be consumed in %s on line %d __call(test) -Warning: (A)The return value of method Clazz::test() is expected to be consumed in %s on line %d +Warning: The return value of method Clazz::test() is expected to be consumed in %s on line %d __callStatic(test) -Warning: (A)The return value of method Clazz::__invoke() is expected to be consumed in %s on line %d +Warning: The return value of method Clazz::__invoke() is expected to be consumed in %s on line %d __invoke(foo) diff --git a/Zend/tests/attributes/nodiscard/003.phpt b/Zend/tests/attributes/nodiscard/003.phpt index 23d838aa9f337..316273c19750f 100644 --- a/Zend/tests/attributes/nodiscard/003.phpt +++ b/Zend/tests/attributes/nodiscard/003.phpt @@ -19,4 +19,4 @@ $cls->test(); ?> --EXPECTF-- -Warning: (A)The return value of method Clazz::test() is expected to be consumed in %s on line %d +Warning: The return value of method Clazz::test() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/005.phpt b/Zend/tests/attributes/nodiscard/005.phpt index ddef8f4a972e9..f351d1c848774 100644 --- a/Zend/tests/attributes/nodiscard/005.phpt +++ b/Zend/tests/attributes/nodiscard/005.phpt @@ -12,6 +12,6 @@ $date->setTimestamp(0); ?> --EXPECTF-- -Warning: (B)The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d +Warning: The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d -Warning: (A)The return value of method DateTimeImmutable::setTimestamp() is expected to be consumed, as DateTimeImmutable::setTimestamp() does not modify the object itself in %s on line %d +Warning: The return value of method DateTimeImmutable::setTimestamp() is expected to be consumed, as DateTimeImmutable::setTimestamp() does not modify the object itself in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/006.phpt b/Zend/tests/attributes/nodiscard/006.phpt index 61fee1c9562b3..43ad192bdfc5d 100644 --- a/Zend/tests/attributes/nodiscard/006.phpt +++ b/Zend/tests/attributes/nodiscard/006.phpt @@ -17,4 +17,4 @@ test(); ?> --EXPECTF-- -Warning: (A)The return value of function test() is expected to be consumed in %s on line %d +Warning: The return value of function test() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/007.phpt b/Zend/tests/attributes/nodiscard/007.phpt index 9f9eabd798d5f..51f4cf0cec232 100644 --- a/Zend/tests/attributes/nodiscard/007.phpt +++ b/Zend/tests/attributes/nodiscard/007.phpt @@ -16,6 +16,6 @@ fclose($f); -Warning: (A)The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d +Warning: The return value of function flock() is expected to be consumed, as locking the stream might have failed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/008.phpt b/Zend/tests/attributes/nodiscard/008.phpt index 8fcbe7e468cb0..b97d93e71c222 100644 --- a/Zend/tests/attributes/nodiscard/008.phpt +++ b/Zend/tests/attributes/nodiscard/008.phpt @@ -15,4 +15,4 @@ test(); --EXPECTF-- Deprecated: Function test() is deprecated in %s on line %d -Warning: (B)The return value of function test() is expected to be consumed in %s on line %d +Warning: The return value of function test() is expected to be consumed in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/009.phpt b/Zend/tests/attributes/nodiscard/009.phpt index 8625e6b8a01e8..fbf625746b7c9 100644 --- a/Zend/tests/attributes/nodiscard/009.phpt +++ b/Zend/tests/attributes/nodiscard/009.phpt @@ -11,4 +11,4 @@ zend_test_deprecated_nodiscard(); --EXPECTF-- Deprecated: Function zend_test_deprecated_nodiscard() is deprecated, custom message in %s on line %d -Warning: (B)The return value of function zend_test_deprecated_nodiscard() is expected to be consumed, custom message 2 in %s on line %d +Warning: The return value of function zend_test_deprecated_nodiscard() is expected to be consumed, custom message 2 in %s on line %d diff --git a/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt b/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt index 89bd9f9ffba4c..9703dcf700b24 100644 --- a/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt +++ b/Zend/tests/attributes/nodiscard/throwing_error_handler_001.phpt @@ -31,5 +31,5 @@ try { ?> --EXPECT-- -Caught: (B)The return value of function test() is expected to be consumed -Caught: (B)The return value of function test2() is expected to be consumed +Caught: The return value of function test() is expected to be consumed +Caught: The return value of function test2() is expected to be consumed diff --git a/Zend/tests/attributes/nodiscard/type_validation_001.phpt b/Zend/tests/attributes/nodiscard/type_validation_001.phpt index 6c4ed19e8a862..3051285cfedb2 100644 --- a/Zend/tests/attributes/nodiscard/type_validation_001.phpt +++ b/Zend/tests/attributes/nodiscard/type_validation_001.phpt @@ -12,4 +12,4 @@ test(); ?> --EXPECTF-- -Warning: (B)The return value of function test() is expected to be consumed, 1234 in %s on line %d +Warning: The return value of function test() is expected to be consumed, 1234 in %s on line %d diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index 9237ea794c9d5..a2bf456ffa3f2 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -1958,7 +1958,7 @@ ZEND_COLD static zend_result ZEND_FASTCALL get_nodiscard_suffix_from_attribute(H return result; } -ZEND_API ZEND_COLD void ZEND_FASTCALL zend_nodiscard_function(char *prefix, const zend_function *fbc) +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_nodiscard_function(const zend_function *fbc) { zend_string *message_suffix = ZSTR_EMPTY_ALLOC(); @@ -1969,15 +1969,13 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_nodiscard_function(char *prefix, cons int code = fbc->type == ZEND_INTERNAL_FUNCTION ? E_WARNING : E_USER_WARNING; if (fbc->common.scope) { - zend_error_unchecked(code, "%sThe return value of method %s::%s() is expected to be consumed%S", - prefix, + zend_error_unchecked(code, "The return value of method %s::%s() is expected to be consumed%S", ZSTR_VAL(fbc->common.scope->name), ZSTR_VAL(fbc->common.function_name), message_suffix ); } else { - zend_error_unchecked(code, "%sThe return value of function %s() is expected to be consumed%S", - prefix, + zend_error_unchecked(code, "The return value of function %s() is expected to be consumed%S", ZSTR_VAL(fbc->common.function_name), message_suffix ); diff --git a/Zend/zend_execute.h b/Zend/zend_execute.h index a0607c114f40a..3baf442cfef67 100644 --- a/Zend/zend_execute.h +++ b/Zend/zend_execute.h @@ -62,7 +62,7 @@ extern ZEND_API const zend_internal_function zend_pass_function; ZEND_API ZEND_COLD void ZEND_FASTCALL zend_missing_arg_error(zend_execute_data *execute_data); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_function(const zend_function *fbc); -ZEND_API ZEND_COLD void ZEND_FASTCALL zend_nodiscard_function(char *prefix, const zend_function *fbc); +ZEND_API ZEND_COLD void ZEND_FASTCALL zend_nodiscard_function(const zend_function *fbc); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_deprecated_class_constant(const zend_class_constant *c, const zend_string *constant_name); ZEND_API ZEND_COLD void ZEND_FASTCALL zend_false_to_array_deprecated(void); ZEND_COLD void ZEND_FASTCALL zend_param_must_be_ref(const zend_function *func, uint32_t arg_num); diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index d5cc6aab375e9..59fac400cd067 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4163,7 +4163,7 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER)) zend_deprecated_function(fbc); } if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { - zend_nodiscard_function("(B)", fbc); + zend_nodiscard_function(fbc); } if (UNEXPECTED(EG(exception) != NULL)) { UNDEF_RESULT(); @@ -4274,7 +4274,7 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) zend_deprecated_function(fbc); } if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { - zend_nodiscard_function("(A)", fbc); + zend_nodiscard_function(fbc); } if (UNEXPECTED(EG(exception) != NULL)) { if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE)) { diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index d00e6b2c65ef5..f1630cf579a84 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1559,7 +1559,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S zend_deprecated_function(fbc); } if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { - zend_nodiscard_function("(B)", fbc); + zend_nodiscard_function(fbc); } if (UNEXPECTED(EG(exception) != NULL)) { UNDEF_RESULT(); @@ -1668,7 +1668,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S zend_deprecated_function(fbc); } if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { - zend_nodiscard_function("(B)", fbc); + zend_nodiscard_function(fbc); } if (UNEXPECTED(EG(exception) != NULL)) { UNDEF_RESULT(); @@ -1777,7 +1777,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_ zend_deprecated_function(fbc); } if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { - zend_nodiscard_function("(B)", fbc); + zend_nodiscard_function(fbc); } if (UNEXPECTED(EG(exception) != NULL)) { UNDEF_RESULT(); @@ -1888,7 +1888,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV zend_deprecated_function(fbc); } if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { - zend_nodiscard_function("(A)", fbc); + zend_nodiscard_function(fbc); } if (UNEXPECTED(EG(exception) != NULL)) { if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE)) { @@ -2013,7 +2013,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV zend_deprecated_function(fbc); } if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { - zend_nodiscard_function("(A)", fbc); + zend_nodiscard_function(fbc); } if (UNEXPECTED(EG(exception) != NULL)) { if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE)) { @@ -2138,7 +2138,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_OBS zend_deprecated_function(fbc); } if ((fbc->common.fn_flags & no_discard) != 0 && EG(exception) == NULL) { - zend_nodiscard_function("(A)", fbc); + zend_nodiscard_function(fbc); } if (UNEXPECTED(EG(exception) != NULL)) { if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_CLOSURE)) { From 7cc44afb8199f72a025de03732cdcb559bb56772 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Tue, 28 Jan 2025 15:21:17 +0100 Subject: [PATCH 05/10] JIT support --- ext/opcache/jit/zend_jit_internal.h | 1 + ext/opcache/jit/zend_jit_ir.c | 46 ++++++++++++++++++++++----- ext/opcache/jit/zend_jit_vm_helpers.c | 28 ++++++++++++++++ 3 files changed, 67 insertions(+), 8 deletions(-) diff --git a/ext/opcache/jit/zend_jit_internal.h b/ext/opcache/jit/zend_jit_internal.h index e769c6caefb98..bcf1cb1ea1fc7 100644 --- a/ext/opcache/jit/zend_jit_internal.h +++ b/ext/opcache/jit/zend_jit_internal.h @@ -233,6 +233,7 @@ ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL zend_jit_loop_counter_helper(ZEND_OPCODE_H void ZEND_FASTCALL zend_jit_copy_extra_args_helper(EXECUTE_DATA_D); void ZEND_FASTCALL zend_jit_copy_extra_args_helper_no_skip_recv(EXECUTE_DATA_D); bool ZEND_FASTCALL zend_jit_deprecated_helper(OPLINE_D); +bool ZEND_FASTCALL zend_jit_nodiscard_helper(OPLINE_D); void ZEND_FASTCALL zend_jit_undefined_long_key(EXECUTE_DATA_D); void ZEND_FASTCALL zend_jit_undefined_long_key_ex(zend_long key EXECUTE_DATA_DC); void ZEND_FASTCALL zend_jit_undefined_string_key(EXECUTE_DATA_D); diff --git a/ext/opcache/jit/zend_jit_ir.c b/ext/opcache/jit/zend_jit_ir.c index 2158438aea03c..8fd39cf3f54b7 100644 --- a/ext/opcache/jit/zend_jit_ir.c +++ b/ext/opcache/jit/zend_jit_ir.c @@ -10128,7 +10128,7 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen ir_GUARD_NOT( ir_AND_U32( ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, fn_flags))), - ir_CONST_U32(ZEND_ACC_DEPRECATED)), + ir_CONST_U32(ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)), ir_CONST_ADDR(exit_addr)); } } @@ -10168,16 +10168,46 @@ static int zend_jit_do_fcall(zend_jit_ctx *jit, const zend_op *opline, const zen } ir_GUARD(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler)); ir_MERGE_WITH_EMPTY_FALSE(if_deprecated); + + if (!RETURN_VALUE_USED(opline)) { + ir_ref if_nodiscard, ret; + + if_nodiscard = ir_IF(ir_AND_U32( + ir_LOAD_U32(ir_ADD_OFFSET(func_ref, offsetof(zend_op_array, fn_flags))), + ir_CONST_U32(ZEND_ACC_NODISCARD))); + ir_IF_TRUE_cold(if_nodiscard); + + if (GCC_GLOBAL_REGS) { + ret = ir_CALL(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_nodiscard_helper)); + } else { + ret = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_nodiscard_helper), rx); + } + ir_GUARD(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler)); + ir_MERGE_WITH_EMPTY_FALSE(if_nodiscard); + } } - } else if (func->common.fn_flags & ZEND_ACC_DEPRECATED) { - ir_ref ret; + } else { + if (func->common.fn_flags & ZEND_ACC_DEPRECATED) { + ir_ref ret; - if (GCC_GLOBAL_REGS) { - ret = ir_CALL(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_deprecated_helper)); - } else { - ret = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_deprecated_helper), rx); + if (GCC_GLOBAL_REGS) { + ret = ir_CALL(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_deprecated_helper)); + } else { + ret = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_deprecated_helper), rx); + } + ir_GUARD(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler)); + } + + if ((func->common.fn_flags & ZEND_ACC_NODISCARD) && !RETURN_VALUE_USED(opline)) { + ir_ref ret; + + if (GCC_GLOBAL_REGS) { + ret = ir_CALL(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_nodiscard_helper)); + } else { + ret = ir_CALL_1(IR_BOOL, ir_CONST_FC_FUNC(zend_jit_nodiscard_helper), rx); + } + ir_GUARD(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler)); } - ir_GUARD(ret, jit_STUB_ADDR(jit, jit_stub_exception_handler)); } } diff --git a/ext/opcache/jit/zend_jit_vm_helpers.c b/ext/opcache/jit/zend_jit_vm_helpers.c index b7fb661615d1c..744199e9b9704 100644 --- a/ext/opcache/jit/zend_jit_vm_helpers.c +++ b/ext/opcache/jit/zend_jit_vm_helpers.c @@ -204,6 +204,34 @@ bool ZEND_FASTCALL zend_jit_deprecated_helper(OPLINE_D) return 1; } +bool ZEND_FASTCALL zend_jit_nodiscard_helper(OPLINE_D) +{ + zend_execute_data *call = (zend_execute_data *) opline; + zend_function *fbc = call->func; + + zend_nodiscard_function(fbc); + + if (EG(exception)) { +#ifndef HAVE_GCC_GLOBAL_REGS + zend_execute_data *execute_data = EG(current_execute_data); +#endif + const zend_op *opline = EG(opline_before_exception); + if (opline && RETURN_VALUE_USED(opline)) { + ZVAL_UNDEF(EX_VAR(opline->result.var)); + } + + zend_vm_stack_free_args(call); + + if (UNEXPECTED(ZEND_CALL_INFO(call) & ZEND_CALL_RELEASE_THIS)) { + OBJ_RELEASE(Z_OBJ(call->This)); + } + + zend_vm_stack_free_call_frame(call); + return 0; + } + return 1; +} + void ZEND_FASTCALL zend_jit_undefined_long_key(EXECUTE_DATA_D) { const zend_op *opline = EX(opline); From c9b8cbc1b2659b07e9fd0e64ded2d5b6aca2777a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Wed, 8 Jan 2025 16:54:41 +0100 Subject: [PATCH 06/10] Add `(void)` cast --- Zend/Optimizer/block_pass.c | 4 ++- Zend/Optimizer/dce.c | 3 +- .../attributes/nodiscard/suppress_cast.phpt | 24 +++++++------- .../nodiscard/suppress_cast_destructor.phpt | 31 +++++++++++++++++++ Zend/zend_ast.h | 1 + Zend/zend_compile.c | 16 ++++++++++ Zend/zend_compile.h | 1 + Zend/zend_language_parser.y | 2 ++ Zend/zend_language_scanner.l | 4 +++ ext/tokenizer/tokenizer_data.c | 1 + ext/tokenizer/tokenizer_data.stub.php | 5 +++ ext/tokenizer/tokenizer_data_arginfo.h | 3 +- 12 files changed, 80 insertions(+), 15 deletions(-) create mode 100644 Zend/tests/attributes/nodiscard/suppress_cast_destructor.phpt diff --git a/Zend/Optimizer/block_pass.c b/Zend/Optimizer/block_pass.c index 6fcbd04f12af5..2b6d71c385457 100644 --- a/Zend/Optimizer/block_pass.c +++ b/Zend/Optimizer/block_pass.c @@ -274,7 +274,9 @@ static void zend_optimize_block(zend_basic_block *block, zend_op_array *op_array * If it's not local, then the other blocks successors must also eventually either FREE or consume the temporary, * hence removing the temporary is not safe in the general case, especially when other consumers are not FREE. * A FREE may not be removed without also removing the source's result, because otherwise that would cause a memory leak. */ - if (opline->op1_type == IS_TMP_VAR) { + if (opline->extended_value == ZEND_FREE_VOID_CAST) { + /* Keep the ZEND_FREE opcode alive. */ + } else if (opline->op1_type == IS_TMP_VAR) { src = VAR_SOURCE(opline->op1); if (src) { switch (src->opcode) { diff --git a/Zend/Optimizer/dce.c b/Zend/Optimizer/dce.c index 414abe01f96ac..a00fd8bc6ad30 100644 --- a/Zend/Optimizer/dce.c +++ b/Zend/Optimizer/dce.c @@ -80,7 +80,6 @@ static inline bool may_have_side_effects( case ZEND_IS_IDENTICAL: case ZEND_IS_NOT_IDENTICAL: case ZEND_QM_ASSIGN: - case ZEND_FREE: case ZEND_FE_FREE: case ZEND_TYPE_CHECK: case ZEND_DEFINED: @@ -127,6 +126,8 @@ static inline bool may_have_side_effects( case ZEND_ARRAY_KEY_EXISTS: /* No side effects */ return 0; + case ZEND_FREE: + return opline->extended_value == ZEND_FREE_VOID_CAST; case ZEND_ADD_ARRAY_ELEMENT: /* TODO: We can't free two vars. Keep instruction alive. "$b"]; */ if ((opline->op1_type & (IS_VAR|IS_TMP_VAR)) && (opline->op2_type & (IS_VAR|IS_TMP_VAR))) { diff --git a/Zend/tests/attributes/nodiscard/suppress_cast.phpt b/Zend/tests/attributes/nodiscard/suppress_cast.phpt index e8bbb26f25a34..2f639371cb3dd 100644 --- a/Zend/tests/attributes/nodiscard/suppress_cast.phpt +++ b/Zend/tests/attributes/nodiscard/suppress_cast.phpt @@ -1,5 +1,5 @@ --TEST-- -#[\NoDiscard]: Casting to (bool) suppresses. +#[\NoDiscard]: Casting to (void) suppresses. --FILE-- test(); -(bool)$cls->test2(); -(bool)call_user_func([$cls, "test"]); -(bool)Clazz::test3(); +(void)$cls->test(); +(void)$cls->test2(); +(void)call_user_func([$cls, "test"]); +(void)Clazz::test3(); -(bool)$closure(); +(void)$closure(); -(bool)$closure2(); +(void)$closure2(); ?> DONE diff --git a/Zend/tests/attributes/nodiscard/suppress_cast_destructor.phpt b/Zend/tests/attributes/nodiscard/suppress_cast_destructor.phpt new file mode 100644 index 0000000000000..272e37305ea96 --- /dev/null +++ b/Zend/tests/attributes/nodiscard/suppress_cast_destructor.phpt @@ -0,0 +1,31 @@ +--TEST-- +#[\NoDiscard]: Casting to (void) destroys the value. +--FILE-- + +--EXPECT-- +Before +WithDestructor::__destruct +After diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index d3689af53ff7e..b2f79d6d1b932 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -83,6 +83,7 @@ enum _zend_ast_kind { ZEND_AST_UNARY_PLUS, ZEND_AST_UNARY_MINUS, ZEND_AST_CAST, + ZEND_AST_CAST_VOID, ZEND_AST_EMPTY, ZEND_AST_ISSET, ZEND_AST_SILENCE, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 55d4921799f83..f90ab334e978f 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -10590,6 +10590,19 @@ static void zend_compile_include_or_eval(znode *result, zend_ast *ast) /* {{{ */ } /* }}} */ +static void zend_compile_void_cast(znode *result, zend_ast *ast) +{ + zend_ast *expr_ast = ast->child[0]; + znode expr_node; + zend_op *opline; + + zend_do_extended_fcall_begin(); + zend_compile_expr(&expr_node, expr_ast); + + opline = zend_emit_op(NULL, ZEND_FREE, &expr_node, NULL); + opline->extended_value = ZEND_FREE_VOID_CAST; +} + static void zend_compile_isset_or_empty(znode *result, zend_ast *ast) /* {{{ */ { zend_ast *var_ast = ast->child[0]; @@ -11486,6 +11499,9 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_THROW: zend_compile_expr(NULL, ast); break; + case ZEND_AST_CAST_VOID: + zend_compile_void_cast(NULL, ast); + break; default: { znode result; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 14134051ef8e3..ff32b8ea3fb08 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -1095,6 +1095,7 @@ ZEND_API zend_string *zend_type_to_string(zend_type type); #define ZEND_FREE_ON_RETURN (1<<0) #define ZEND_FREE_SWITCH (1<<1) +#define ZEND_FREE_VOID_CAST (1<<2) #define ZEND_SEND_BY_VAL 0u #define ZEND_SEND_BY_REF 1u diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index d2a29e670d8bf..0c66f887d330c 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -217,6 +217,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_OBJECT_CAST "'(object)'" %token T_BOOL_CAST "'(bool)'" %token T_UNSET_CAST "'(unset)'" +%token T_VOID_CAST "'(void)'" %token T_OBJECT_OPERATOR "'->'" %token T_NULLSAFE_OBJECT_OPERATOR "'?->'" %token T_DOUBLE_ARROW "'=>'" @@ -534,6 +535,7 @@ statement: { $$ = zend_ast_create(ZEND_AST_TRY, $3, $5, $6); } | T_GOTO T_STRING ';' { $$ = zend_ast_create(ZEND_AST_GOTO, $2); } | T_STRING ':' { $$ = zend_ast_create(ZEND_AST_LABEL, $1); } + | T_VOID_CAST expr ';' { $$ = zend_ast_create(ZEND_AST_CAST_VOID, $2); } ; catch_list: diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 7ae73875926eb..4c883b81c5f7d 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1657,6 +1657,10 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN(T_UNSET_CAST); } +"("{TABS_AND_SPACES}("void"){TABS_AND_SPACES}")" { + RETURN_TOKEN(T_VOID_CAST); +} + "eval" { RETURN_TOKEN_WITH_IDENT(T_EVAL); } diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index a046ab50e1498..a1e131032bcfb 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -153,6 +153,7 @@ char *get_token_type_name(int token_type) case T_OBJECT_CAST: return "T_OBJECT_CAST"; case T_BOOL_CAST: return "T_BOOL_CAST"; case T_UNSET_CAST: return "T_UNSET_CAST"; + case T_VOID_CAST: return "T_VOID_CAST"; case T_OBJECT_OPERATOR: return "T_OBJECT_OPERATOR"; case T_NULLSAFE_OBJECT_OPERATOR: return "T_NULLSAFE_OBJECT_OPERATOR"; case T_DOUBLE_ARROW: return "T_DOUBLE_ARROW"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 45f3c89f2de3a..c1e1fd254dfaa 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -642,6 +642,11 @@ * @cvalue T_UNSET_CAST */ const T_UNSET_CAST = UNKNOWN; +/** + * @var int + * @cvalue T_VOID_CAST + */ +const T_VOID_CAST = UNKNOWN; /** * @var int * @cvalue T_OBJECT_OPERATOR diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index 61f6ac1ec3659..9c488d19f1890 100644 --- a/ext/tokenizer/tokenizer_data_arginfo.h +++ b/ext/tokenizer/tokenizer_data_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit the .stub.php file instead. - * Stub hash: d917cab61a2b436a16d2227cdb438add45e42d69 */ + * Stub hash: 19d25d22098f46283b517352cbb302db962b50fd */ static void register_tokenizer_data_symbols(int module_number) { @@ -131,6 +131,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_OBJECT_CAST", T_OBJECT_CAST, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_BOOL_CAST", T_BOOL_CAST, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_UNSET_CAST", T_UNSET_CAST, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_VOID_CAST", T_VOID_CAST, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_OBJECT_OPERATOR", T_OBJECT_OPERATOR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_NULLSAFE_OBJECT_OPERATOR", T_NULLSAFE_OBJECT_OPERATOR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_DOUBLE_ARROW", T_DOUBLE_ARROW, CONST_PERSISTENT); From 7a5a4754ef7464dbbebd31cb75a1452b79c34827 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Fri, 24 Jan 2025 23:12:31 +0100 Subject: [PATCH 07/10] Fix test expectation --- ext/standard/tests/file/userstreams_004.phpt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ext/standard/tests/file/userstreams_004.phpt b/ext/standard/tests/file/userstreams_004.phpt index 959f02f5b1cd7..fa68e5aa9307a 100644 --- a/ext/standard/tests/file/userstreams_004.phpt +++ b/ext/standard/tests/file/userstreams_004.phpt @@ -19,7 +19,7 @@ class test_wrapper extends test_wrapper_base { } function test($name, $fd, $mode) { echo "------ $name: -------\n"; - flock($fd, $mode); + (void)flock($fd, $mode); $data = stream_get_meta_data($fd); var_dump($data['wrapper_data']->mode === $mode); } From baadb12f514d2895f7f542a2adceed7ac1b647e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 30 Jan 2025 08:50:30 +0100 Subject: [PATCH 08/10] fixup! Leverage RETVAL opcode specialization --- Zend/zend_vm_def.h | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 59fac400cd067..70d0c25649033 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -4156,7 +4156,7 @@ ZEND_VM_HOT_HANDLER(131, ZEND_DO_FCALL_BY_NAME, ANY, ANY, SPEC(RETVAL,OBSERVER)) SAVE_OPLINE(); EX(call) = call->prev_execute_data; - const uint32_t no_discard = (!RETURN_VALUE_USED(opline)) * ZEND_ACC_NODISCARD; + const uint32_t no_discard = RETURN_VALUE_USED(opline) ? 0 : ZEND_ACC_NODISCARD; if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { @@ -4267,7 +4267,7 @@ ZEND_VM_HOT_HANDLER(60, ZEND_DO_FCALL, ANY, ANY, SPEC(RETVAL,OBSERVER)) SAVE_OPLINE(); EX(call) = call->prev_execute_data; - const uint32_t no_discard = (!RETURN_VALUE_USED(opline)) * ZEND_ACC_NODISCARD; + const uint32_t no_discard = RETURN_VALUE_USED(opline) ? 0 : ZEND_ACC_NODISCARD; if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { From 0d0ac091b14bda2f7ff7b0fee71677f96f47310b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 30 Jan 2025 08:51:24 +0100 Subject: [PATCH 09/10] fixup! Add `#[\NoDiscard]` attribute --- Zend/Optimizer/optimize_func_calls.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Zend/Optimizer/optimize_func_calls.c b/Zend/Optimizer/optimize_func_calls.c index 1ba932f75d257..f6d795538b1c5 100644 --- a/Zend/Optimizer/optimize_func_calls.c +++ b/Zend/Optimizer/optimize_func_calls.c @@ -78,8 +78,10 @@ static void zend_delete_call_instructions(zend_op_array *op_array, zend_op *opli static void zend_try_inline_call(zend_op_array *op_array, zend_op *fcall, zend_op *opline, zend_function *func) { + const uint32_t no_discard = RETURN_VALUE_USED(opline) ? 0 : ZEND_ACC_NODISCARD; + if (func->type == ZEND_USER_FUNCTION - && !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_DEPRECATED|ZEND_ACC_NODISCARD)) + && !(func->op_array.fn_flags & (ZEND_ACC_ABSTRACT|ZEND_ACC_HAS_TYPE_HINTS|ZEND_ACC_DEPRECATED|no_discard)) /* TODO: function copied from trait may be inconsistent ??? */ && !(func->op_array.fn_flags & (ZEND_ACC_TRAIT_CLONE)) && fcall->extended_value >= func->op_array.required_num_args From 8fb967eda028740acf27bf79426e6415ba54e178 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tim=20D=C3=BCsterhus?= Date: Thu, 30 Jan 2025 09:07:55 +0100 Subject: [PATCH 10/10] fixup! fixup! Leverage RETVAL opcode specialization --- Zend/zend_vm_execute.h | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index f1630cf579a84..9d058e6539ab4 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -1552,7 +1552,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S SAVE_OPLINE(); EX(call) = call->prev_execute_data; - const uint32_t no_discard = (!0) * ZEND_ACC_NODISCARD; + const uint32_t no_discard = 0 ? 0 : ZEND_ACC_NODISCARD; if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { @@ -1661,7 +1661,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_S SAVE_OPLINE(); EX(call) = call->prev_execute_data; - const uint32_t no_discard = (!1) * ZEND_ACC_NODISCARD; + const uint32_t no_discard = 1 ? 0 : ZEND_ACC_NODISCARD; if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { @@ -1770,7 +1770,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_BY_NAME_ SAVE_OPLINE(); EX(call) = call->prev_execute_data; - const uint32_t no_discard = (!RETURN_VALUE_USED(opline)) * ZEND_ACC_NODISCARD; + const uint32_t no_discard = RETURN_VALUE_USED(opline) ? 0 : ZEND_ACC_NODISCARD; if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { @@ -1881,7 +1881,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV SAVE_OPLINE(); EX(call) = call->prev_execute_data; - const uint32_t no_discard = (!0) * ZEND_ACC_NODISCARD; + const uint32_t no_discard = 0 ? 0 : ZEND_ACC_NODISCARD; if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { @@ -2006,7 +2006,7 @@ static ZEND_VM_HOT ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_RETV SAVE_OPLINE(); EX(call) = call->prev_execute_data; - const uint32_t no_discard = (!1) * ZEND_ACC_NODISCARD; + const uint32_t no_discard = 1 ? 0 : ZEND_ACC_NODISCARD; if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) { @@ -2131,7 +2131,7 @@ static ZEND_VM_COLD ZEND_OPCODE_HANDLER_RET ZEND_FASTCALL ZEND_DO_FCALL_SPEC_OBS SAVE_OPLINE(); EX(call) = call->prev_execute_data; - const uint32_t no_discard = (!RETURN_VALUE_USED(opline)) * ZEND_ACC_NODISCARD; + const uint32_t no_discard = RETURN_VALUE_USED(opline) ? 0 : ZEND_ACC_NODISCARD; if (UNEXPECTED((fbc->common.fn_flags & (ZEND_ACC_DEPRECATED|no_discard)) != 0)) { if ((fbc->common.fn_flags & ZEND_ACC_DEPRECATED) != 0) {