Skip to content

[RFC] Add #[\DelayedTargetValidation] attribute #18817

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 13 commits into
base: master
Choose a base branch
from

Conversation

DanielEScherzer
Copy link
Member

@DanielEScherzer DanielEScherzer commented Jun 9, 2025

@DanielEScherzer DanielEScherzer force-pushed the delayed-target-validation branch from 037775b to 26ad0f2 Compare July 6, 2025 17:30
@DanielEScherzer DanielEScherzer force-pushed the delayed-target-validation branch from 1978ce1 to 82f556a Compare August 1, 2025 14:38
@DanielEScherzer DanielEScherzer changed the title Add #[\DelayedTargetValidation] attribute [RFC] Add #[\DelayedTargetValidation] attribute Aug 1, 2025
@DanielEScherzer DanielEScherzer marked this pull request as ready for review August 1, 2025 15:43
@DanielEScherzer
Copy link
Member Author

CC @TimWolla @edorian for the NoDiscard-related changes, the "don't apply on property hooks" error was moved around so that it could be delayed

@TimWolla
Copy link
Member

TimWolla commented Aug 1, 2025

Currently on vacation, so can't review, but I trust that you are capable of correctly moving error messages 😄

@DanielEScherzer DanielEScherzer force-pushed the delayed-target-validation branch from abbef91 to 6b11fd2 Compare August 12, 2025 13:35
Copy link
Member

@TimWolla TimWolla left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some first thoughts. I don't currently have the mental capacity to perform an in-depth review (diff is larger than anticipated) and didn't look at the entire PR.

/**
* @strict-properties
*/
#[Attribute]
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
#[Attribute]
#[Attribute(Attribute::TARGET_ALL)]

Spelling it out explicitly is probably a good idea for documentation purposes.

Comment on lines -508 to +560
internal_attr->flags = Z_LVAL(attr->args[0].value);
if (attr->argc == 0) {
// Apply default of Attribute::TARGET_ALL
internal_attr->flags = ZEND_ATTRIBUTE_TARGET_ALL;
} else {
internal_attr->flags = Z_LVAL(attr->args[0].value);
}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would then become obsolete. I would suggest shipping this as an independent bugfix PR (or alternatively an assertion?).

Comment on lines +638 to +642
- Core:
. DelayedTargetValidation is an attribute that, when added, delays any errors
from *other* internal attributes about being applied to invalid targets from
compile-time to runtime.
RFC: https://wiki.php.net/rfc/delayedtargetvalidation_attribute
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Not sure if we ever added attributes here. Makes sense, but I would just mention the class name + RFC and not the full explanation. Can you just commit the related change for NoDiscard to master?

Copy link
Member

@iluuu1994 iluuu1994 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

After sleeping on this, my preferred approach would be:

  • Stop using zend_error_noreturn() in validator in favor of zend_throw_error() for target validation, instruct from UPGRADING.INTERNALS to do the same for extensions.
  • Use EG(exception) from zend_compile_attributes() to understand whether the validator succeeded.
  • Without #[DelayedTargetValidation], promote to a fatal error with zend_exception_uncaught_error().
  • With #[DelayedTargetValidation], record whether the validator has succeeded and store it in zend_attribute. EG(exception) would have to be removed/freed.
  • When it failed, repeat the validation on ReflectionAttribute::newInstance(). Skipping for successful validators prevents writing to locked shm. Validators should be idempotent, i.e. we should be able to rely on the fact that calling it again will also throw.

The benefit is that we don't need to handle this for every validator, and we don't need the ZEND_ATTRIBUTE_NO_TARGET_VALIDATION/ZEND_ATTRIBUTE_DELAYED_TARGET_VALIDATION flags. Then, this feature should work mostly automatically.

WDYT?

* attribute is applied to, attr->scope is just the overall class entry
* that the method is a part of. */
if (ce == zend_ce_nodiscard && attr->on_property_hook) {
zend_throw_error(NULL, "#[\\NoDiscard] is not supported for property hooks");;
RETURN_THROWS();
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This hack should become obsolete with #19465.

@@ -8422,6 +8446,10 @@ static zend_op_array *zend_compile_func_decl_ex(

CG(active_op_array) = op_array;

zend_oparray_context_begin(&orig_oparray_context, op_array);
CG(context).active_property_info_name = property_info_name;
CG(context).active_property_hook_kind = hook_kind;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same with this.

@DanielEScherzer
Copy link
Member Author

Stop using zend_error_noreturn() in validator in favor of zend_throw_error() for target validation, instruct from UPGRADING.INTERNALS to do the same for extensions.

zend_throw_error() will just use zend_error_noreturn() if I'm reading correctly,

php-src/Zend/zend.c

Lines 1831 to 1836 in 222f751

//TODO: we can't convert compile-time errors to exceptions yet???
if (EG(current_execute_data) && !CG(in_compilation)) {
zend_throw_exception(exception_ce, message, 0);
} else {
zend_error_noreturn(E_ERROR, "%s", message);
}

when validators are run, CG(in_compilation) is true, see the validate_nodiscard() logic

Skipping for successful validators prevents writing to locked shm.

No validators should be writing anything at runtime, e.g. for allow dynamic properties the validator just ZEND_ASSERTs that it passed the first time

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants