diff --git a/Zend/tests/attributes/deprecated/functions/argument_binding.phpt b/Zend/tests/attributes/deprecated/functions/argument_binding.phpt
new file mode 100644
index 000000000000..4d89d61388a2
--- /dev/null
+++ b/Zend/tests/attributes/deprecated/functions/argument_binding.phpt
@@ -0,0 +1,63 @@
+--TEST--
+#[\Deprecated]: positional and named arguments, null, and the errors of the constructor
+--FILE--
+getMessage(), "\n";
+ }
+}
+
+?>
+--EXPECTF--
+
+Deprecated: Function positional_message() is deprecated, message only in %s on line %d
+
+Deprecated: Function named_since() is deprecated since 1.0 in %s on line %d
+
+Deprecated: Function both_positional() is deprecated since 1.0, m in %s on line %d
+
+Deprecated: Function named_reversed() is deprecated since 1.0, m in %s on line %d
+
+Deprecated: Function null_message() is deprecated since 1.0 in %s on line %d
+
+Deprecated: Function null_since() is deprecated, m in %s on line %d
+
+Deprecated: Function empty_strings() is deprecated in %s on line %d
+Error: Named parameter $message overwrites previous argument
+Error: Named parameter $message overwrites previous argument
+Error: Unknown named parameter $unknown
+Error: Unknown named parameter $Message
+ArgumentCountError: Deprecated::__construct() expects at most 2 arguments, 3 given
diff --git a/Zend/tests/attributes/deprecated/functions/internal_repeated_calls.phpt b/Zend/tests/attributes/deprecated/functions/internal_repeated_calls.phpt
new file mode 100644
index 000000000000..b030b8929a22
--- /dev/null
+++ b/Zend/tests/attributes/deprecated/functions/internal_repeated_calls.phpt
@@ -0,0 +1,32 @@
+--TEST--
+#[\Deprecated]: the message of an internal function is the same on every call
+--FILE--
+setAccessible(true);
+$rp->setAccessible(true);
+(Closure::fromCallable([$rp, 'setAccessible']))(true);
+(new ReflectionMethod($rp, 'setAccessible'))->invoke($rp, true);
+
+set_error_handler(function (int $type, string $message) {
+ echo "handler: $message\n";
+ return true;
+});
+$rp->setAccessible(true);
+restore_error_handler();
+$rp->setAccessible(true);
+
+?>
+--EXPECTF--
+Deprecated: Method ReflectionProperty::setAccessible() is deprecated since 8.5, as it has no effect since PHP 8.1 in %s on line %d
+
+Deprecated: Method ReflectionProperty::setAccessible() is deprecated since 8.5, as it has no effect since PHP 8.1 in %s on line %d
+
+Deprecated: Method ReflectionProperty::setAccessible() is deprecated since 8.5, as it has no effect since PHP 8.1 in %s on line %d
+
+Deprecated: Method ReflectionProperty::setAccessible() is deprecated since 8.5, as it has no effect since PHP 8.1 in %s on line %d
+handler: Method ReflectionProperty::setAccessible() is deprecated since 8.5, as it has no effect since PHP 8.1
+
+Deprecated: Method ReflectionProperty::setAccessible() is deprecated since 8.5, as it has no effect since PHP 8.1 in %s on line %d
diff --git a/Zend/tests/attributes/nodiscard/007.phpt b/Zend/tests/attributes/nodiscard/007.phpt
index de4604e15e48..6d3df246f0aa 100644
--- a/Zend/tests/attributes/nodiscard/007.phpt
+++ b/Zend/tests/attributes/nodiscard/007.phpt
@@ -7,13 +7,23 @@ zend_test.observer.execute_internal=1
--FILE--
--EXPECTF--
-
-
+
+
Warning: The return value of function zend_test_nodiscard() should either be used or intentionally ignored by casting it as (void), custom message in %s on line %d
+
+
+
+Warning: The return value of function f() should either be used or intentionally ignored by casting it as (void), from a constant in %s on line %d
diff --git a/Zend/tests/attributes/nodiscard/argument_binding.phpt b/Zend/tests/attributes/nodiscard/argument_binding.phpt
new file mode 100644
index 000000000000..fc6e9b69c3a3
--- /dev/null
+++ b/Zend/tests/attributes/nodiscard/argument_binding.phpt
@@ -0,0 +1,41 @@
+--TEST--
+#[\NoDiscard]: positional and named message, null, and the errors of the constructor
+--FILE--
+getMessage(), "\n";
+ }
+}
+
+?>
+--EXPECTF--
+
+Warning: The return value of function positional_message() should either be used or intentionally ignored by casting it as (void), positional in %s on line %d
+
+Warning: The return value of function named_message() should either be used or intentionally ignored by casting it as (void), named in %s on line %d
+
+Warning: The return value of function null_message() should either be used or intentionally ignored by casting it as (void) in %s on line %d
+
+Warning: The return value of function empty_message() should either be used or intentionally ignored by casting it as (void) in %s on line %d
+Error: Unknown named parameter $since
+ArgumentCountError: NoDiscard::__construct() expects at most 1 argument, 2 given
+Error: Unknown named parameter $unknown
diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c
index e7923d76996c..de943385c536 100644
--- a/Zend/zend_execute.c
+++ b/Zend/zend_execute.c
@@ -1831,6 +1831,48 @@ ZEND_API zend_never_inline ZEND_COLD void zend_wrong_string_offset_error(void)
zend_throw_error(NULL, "%s", msg);
}
+/* Reads the arguments of a #[\Deprecated] (message, since) or #[\NoDiscard] (message)
+ * attribute when they are strings or null, positional or named, each at most once; anything
+ * else is left to the constructor and its errors */
+static bool get_attribute_string_args(const zend_attribute *attr, zend_string **message, zend_string **since)
+{
+ zend_string *strs[2] = {NULL, NULL};
+ uint32_t num_params = since ? 2 : 1;
+
+ if (attr->argc > num_params) {
+ return false;
+ }
+ for (uint32_t i = 0; i < attr->argc; i++) {
+ const zend_attribute_arg *arg = &attr->args[i];
+ uint32_t param;
+
+ if (arg->name == NULL) {
+ param = i;
+ } else if (zend_string_equals(arg->name, ZSTR_KNOWN(ZEND_STR_MESSAGE))) {
+ param = 0;
+ } else if (num_params == 2 && zend_string_equals(arg->name, ZSTR_KNOWN(ZEND_STR_SINCE))) {
+ param = 1;
+ } else {
+ return false;
+ }
+ if (strs[param] != NULL) {
+ return false;
+ }
+ if (Z_TYPE(arg->value) == IS_STRING) {
+ strs[param] = Z_STR(arg->value);
+ } else if (Z_TYPE(arg->value) == IS_NULL) {
+ strs[param] = ZSTR_EMPTY_ALLOC();
+ } else {
+ return false;
+ }
+ }
+ *message = strs[0] ? strs[0] : ZSTR_EMPTY_ALLOC();
+ if (since) {
+ *since = strs[1] ? strs[1] : ZSTR_EMPTY_ALLOC();
+ }
+ return true;
+}
+
ZEND_COLD static zend_result ZEND_FASTCALL get_deprecation_suffix_from_attribute(HashTable *attributes, zend_class_entry* scope, zend_string **message_suffix)
{
*message_suffix = ZSTR_EMPTY_ALLOC();
@@ -1858,6 +1900,11 @@ ZEND_COLD static zend_result ZEND_FASTCALL get_deprecation_suffix_from_attribute
ZVAL_UNDEF(&obj);
zval *z;
+ /* With an exception pending the constructor is not run and nothing is emitted; keep that */
+ if (!EG(exception) && get_attribute_string_args(deprecated, &message, &since)) {
+ goto build;
+ }
+
/* Construct the Deprecated object to correctly handle parameter processing. */
if (FAILURE == zend_get_attribute_object(&obj, zend_ce_deprecated, deprecated, scope, NULL)) {
goto out;
@@ -1877,6 +1924,7 @@ ZEND_COLD static zend_result ZEND_FASTCALL get_deprecation_suffix_from_attribute
since = Z_STR_P(z);
}
+build:
/* Construct the suffix. */
*message_suffix = zend_strpprintf_unchecked(
0,
@@ -1948,6 +1996,10 @@ ZEND_COLD static zend_result ZEND_FASTCALL get_nodiscard_suffix_from_attribute(H
ZVAL_UNDEF(&obj);
zval *z;
+ if (!EG(exception) && get_attribute_string_args(nodiscard, &message, NULL)) {
+ goto build;
+ }
+
/* Construct the NoDiscard object to correctly handle parameter processing. */
if (FAILURE == zend_get_attribute_object(&obj, zend_ce_nodiscard, nodiscard, scope, NULL)) {
goto out;
@@ -1960,6 +2012,7 @@ ZEND_COLD static zend_result ZEND_FASTCALL get_nodiscard_suffix_from_attribute(H
message = Z_STR_P(z);
}
+build:
/* Construct the suffix. */
*message_suffix = zend_strpprintf_unchecked(
0,
diff --git a/ext/zend_test/tests/observer_sqlite_create_function.phpt b/ext/zend_test/tests/observer_sqlite_create_function.phpt
index 5d809056193d..fa39d18cd162 100644
--- a/ext/zend_test/tests/observer_sqlite_create_function.phpt
+++ b/ext/zend_test/tests/observer_sqlite_create_function.phpt
@@ -30,9 +30,6 @@ echo 'Done' . PHP_EOL;
-
-
-
Deprecated: Method PDO::sqliteCreateFunction() is deprecated since 8.5, use Pdo\Sqlite::createFunction() instead in %s on line %d