Skip to content

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

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,225 @@
--TEST--
#[\DelayedTargetValidation] prevents target errors at compile time
--FILE--
<?php

#[DelayedTargetValidation]
#[NoDiscard]
class Demo {

#[DelayedTargetValidation]
#[Attribute]
public const FOO = 'BAR';

#[DelayedTargetValidation]
#[Attribute]
public string $v;

#[DelayedTargetValidation]
#[Attribute]
public function __construct(
#[DelayedTargetValidation]
#[Attribute]
public string $v2
) {
$this->v = $v2;
echo __METHOD__ . "\n";
}
}

#[DelayedTargetValidation]
#[Attribute]
function demoFn() {
echo __FUNCTION__ . "\n";
}

#[DelayedTargetValidation]
#[Attribute]
const EXAMPLE = true;

$cases = [
new ReflectionClass('Demo'),
new ReflectionClassConstant('Demo', 'FOO'),
new ReflectionProperty('Demo', 'v'),
new ReflectionMethod('Demo', '__construct'),
new ReflectionParameter([ 'Demo', '__construct' ], 'v2'),
new ReflectionProperty('Demo', 'v2'),
new ReflectionFunction('demoFn'),
new ReflectionConstant('EXAMPLE'),
];
foreach ($cases as $r) {
echo str_repeat("*", 20) . "\n";
echo $r . "\n";
$attributes = $r->getAttributes();
var_dump($attributes);
try {
$attributes[1]->newInstance();
} catch (Error $e) {
echo get_class($e) . ": " . $e->getMessage() . "\n";
}
}

?>
--EXPECTF--
********************
Class [ <user> class Demo ] {
@@ %s %d-%d

- Constants [1] {
Constant [ public string FOO ] { BAR }
}

- Static properties [0] {
}

- Static methods [0] {
}

- Properties [2] {
Property [ public string $v ]
Property [ public string $v2 ]
}

- Methods [1] {
Method [ <user, ctor> public method __construct ] {
@@ %s %d - %d

- Parameters [1] {
Parameter #0 [ <required> string $v2 ]
}
}
}
}

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "NoDiscard"
}
}
Error: Attribute "NoDiscard" cannot target class (allowed targets: function, method)
********************
Constant [ public string FOO ] { BAR }

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target class constant (allowed targets: class)
********************
Property [ public string $v ]

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target property (allowed targets: class)
********************
Method [ <user, ctor> public method __construct ] {
@@ %s %d - %d

- Parameters [1] {
Parameter #0 [ <required> string $v2 ]
}
}

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target method (allowed targets: class)
********************
Parameter #0 [ <required> string $v2 ]
array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target parameter (allowed targets: class)
********************
Property [ public string $v2 ]

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target property (allowed targets: class)
********************
Function [ <user> function demoFn ] {
@@ %s %d - %d
}

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target function (allowed targets: class)
********************
Constant [ bool EXAMPLE ] { 1 }

array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(23) "DelayedTargetValidation"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(9) "Attribute"
}
}
Error: Attribute "Attribute" cannot target constant (allowed targets: class)
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
--TEST--
#[\DelayedTargetValidation] prevents target errors at compile time
--FILE--
<?php

#[DelayedTargetValidation]
#[NoDiscard]
class Demo {

#[DelayedTargetValidation]
#[Attribute]
public const FOO = 'BAR';

#[DelayedTargetValidation]
#[Attribute]
public string $v;

#[DelayedTargetValidation]
#[Attribute]
public function __construct(
#[DelayedTargetValidation]
#[Attribute]
public string $v2
) {
$this->v = $v2;
echo __METHOD__ . "\n";
}
}

#[DelayedTargetValidation]
#[Attribute]
function demoFn() {
echo __FUNCTION__ . "\n";
}

$o = new Demo( "foo" );
demoFn();

#[DelayedTargetValidation]
#[Attribute]
const EXAMPLE = true;

?>
--EXPECT--
Demo::__construct
demoFn
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
--TEST--
#[\DelayedTargetValidation] does not prevent repetition errors
--FILE--
<?php

#[DelayedTargetValidation]
#[NoDiscard]
#[NoDiscard]
class Demo {}

?>
--EXPECTF--
Fatal error: Attribute "NoDiscard" must not be repeated in %s on line %d
41 changes: 23 additions & 18 deletions Zend/zend_attributes.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ 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;
ZEND_API zend_class_entry *zend_ce_delayed_target_validation;

static zend_object_handlers attributes_object_handlers_sensitive_parameter_value;

Expand Down Expand Up @@ -72,25 +73,21 @@ uint32_t zend_attribute_attribute_get_flags(zend_attribute *attr, zend_class_ent
static void validate_allow_dynamic_properties(
zend_attribute *attr, uint32_t target, zend_class_entry *scope)
{
const char *msg = NULL;
if (scope->ce_flags & ZEND_ACC_TRAIT) {
zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to trait %s",
ZSTR_VAL(scope->name)
);
msg = "Cannot apply #[AllowDynamicProperties] to trait %s";
} else if (scope->ce_flags & ZEND_ACC_INTERFACE) {
msg = "Cannot apply #[AllowDynamicProperties] to interface %s";
} else if (scope->ce_flags & ZEND_ACC_READONLY_CLASS) {
msg = "Cannot apply #[AllowDynamicProperties] to readonly class %s";
} else if (scope->ce_flags & ZEND_ACC_ENUM) {
msg = "Cannot apply #[AllowDynamicProperties] to enum %s";
}
if (scope->ce_flags & ZEND_ACC_INTERFACE) {
zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to interface %s",
ZSTR_VAL(scope->name)
);
}
if (scope->ce_flags & ZEND_ACC_READONLY_CLASS) {
zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to readonly class %s",
ZSTR_VAL(scope->name)
);
}
if (scope->ce_flags & ZEND_ACC_ENUM) {
zend_error_noreturn(E_ERROR, "Cannot apply #[AllowDynamicProperties] to enum %s",
ZSTR_VAL(scope->name)
);
if (msg != NULL) {
if (target & ZEND_ATTRIBUTE_NO_TARGET_VALIDATION) {
return;
}
zend_error_noreturn(E_ERROR, msg, ZSTR_VAL(scope->name) );
}
scope->ce_flags |= ZEND_ACC_ALLOW_DYNAMIC_PROPERTIES;
}
Expand Down Expand Up @@ -487,7 +484,12 @@ ZEND_API zend_internal_attribute *zend_mark_internal_attribute(zend_class_entry
if (zend_string_equals(attr->name, zend_ce_attribute->name)) {
internal_attr = pemalloc(sizeof(zend_internal_attribute), 1);
internal_attr->ce = ce;
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);
}
internal_attr->validator = NULL;

zend_string *lcname = zend_string_tolower_ex(ce->name, 1);
Expand Down Expand Up @@ -548,6 +550,9 @@ void zend_register_attribute_ce(void)

zend_ce_nodiscard = register_class_NoDiscard();
attr = zend_mark_internal_attribute(zend_ce_nodiscard);

zend_ce_delayed_target_validation = register_class_DelayedTargetValidation();
attr = zend_mark_internal_attribute(zend_ce_delayed_target_validation);
}

void zend_attributes_shutdown(void)
Expand Down
5 changes: 5 additions & 0 deletions Zend/zend_attributes.h
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@
#define ZEND_ATTRIBUTE_IS_REPEATABLE (1<<7)
#define ZEND_ATTRIBUTE_FLAGS ((1<<8) - 1)

/* Not a real flag, just passed to validators when target validation is *
* suppressed; must not conflict with any of the real flags above. */
#define ZEND_ATTRIBUTE_NO_TARGET_VALIDATION (1<<8)

/* Flags for zend_attribute.flags */
#define ZEND_ATTRIBUTE_PERSISTENT (1<<0)
#define ZEND_ATTRIBUTE_STRICT_TYPES (1<<1)
Expand All @@ -50,6 +54,7 @@ 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;
extern ZEND_API zend_class_entry *zend_ce_delayed_target_validation;

typedef struct {
zend_string *name;
Expand Down
6 changes: 6 additions & 0 deletions Zend/zend_attributes.stub.php
Original file line number Diff line number Diff line change
Expand Up @@ -97,3 +97,9 @@ final class NoDiscard

public function __construct(?string $message = null) {}
}

/**
* @strict-properties
*/
#[Attribute]
final class DelayedTargetValidation {}
Loading