Skip to content

[DRAFT] [WIP] Initial zend_class_alias #18789

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 15 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
20 changes: 15 additions & 5 deletions Zend/Optimizer/zend_optimizer.c
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
#include "zend_call_graph.h"
#include "zend_inference.h"
#include "zend_dump.h"
#include "zend_class_alias.h"
#include "php.h"

#ifndef ZEND_OPTIMIZER_MAX_REGISTERED_PASSES
Expand Down Expand Up @@ -773,7 +774,8 @@ void zend_optimizer_shift_jump(zend_op_array *op_array, zend_op *opline, uint32_

static bool zend_optimizer_ignore_class(zval *ce_zv, zend_string *filename)
{
zend_class_entry *ce = Z_PTR_P(ce_zv);
zend_class_entry *ce;
Z_CE_FROM_ZVAL_P(ce, ce_zv);

if (ce->ce_flags & ZEND_ACC_PRELOADED) {
Bucket *ce_bucket = (Bucket*)((uintptr_t)ce_zv - XtOffsetOf(Bucket, val));
Expand Down Expand Up @@ -809,14 +811,22 @@ static bool zend_optimizer_ignore_function(zval *fbc_zv, zend_string *filename)

zend_class_entry *zend_optimizer_get_class_entry(
const zend_script *script, const zend_op_array *op_array, zend_string *lcname) {
zend_class_entry *ce = script ? zend_hash_find_ptr(&script->class_table, lcname) : NULL;
if (ce) {
return ce;
zval *ce_or_alias = script ? zend_hash_find(&script->class_table, lcname) : NULL;
if (ce_or_alias) {
if (EXPECTED(Z_TYPE_P(ce_or_alias) == IS_PTR)) {
return Z_PTR_P(ce_or_alias);
}
ZEND_ASSERT(Z_TYPE_P(ce_or_alias) == IS_ALIAS_PTR);
return Z_CLASS_ALIAS_P(ce_or_alias)->ce;
}

zval *ce_zv = zend_hash_find(CG(class_table), lcname);
if (ce_zv && !zend_optimizer_ignore_class(ce_zv, op_array ? op_array->filename : NULL)) {
return Z_PTR_P(ce_zv);
if (EXPECTED(Z_TYPE_P(ce_zv) == IS_PTR)) {
return Z_PTR_P(ce_zv);
}
ZEND_ASSERT(Z_TYPE_P(ce_zv) == IS_ALIAS_PTR);
return Z_CLASS_ALIAS_P(ce_zv)->ce;
}

if (op_array && op_array->scope && zend_string_equals_ci(op_array->scope->name, lcname)) {
Expand Down
24 changes: 13 additions & 11 deletions Zend/tests/attributes/034_target_values.phpt
Original file line number Diff line number Diff line change
Expand Up @@ -16,24 +16,26 @@ showFlag("TARGET_PROPERTY", Attribute::TARGET_PROPERTY);
showFlag("TARGET_CLASS_CONSTANT", Attribute::TARGET_CLASS_CONSTANT);
showFlag("TARGET_PARAMETER", Attribute::TARGET_PARAMETER);
showFlag("TARGET_CONSTANT", Attribute::TARGET_CONSTANT);
showFlag("TARGET_CLASS_ALIAS", Attribute::TARGET_CLASS_ALIAS);
showFlag("IS_REPEATABLE", Attribute::IS_REPEATABLE);

$all = Attribute::TARGET_CLASS | Attribute::TARGET_FUNCTION
| Attribute::TARGET_METHOD | Attribute::TARGET_PROPERTY
| Attribute::TARGET_CLASS_CONSTANT | Attribute::TARGET_PARAMETER
| Attribute::TARGET_CONSTANT;
| Attribute::TARGET_CONSTANT | Attribute::TARGET_CLASS_ALIAS;
var_dump($all, Attribute::TARGET_ALL, $all === Attribute::TARGET_ALL);

?>
--EXPECT--
Attribute::TARGET_CLASS = 1 (127 & 1 === 1)
Attribute::TARGET_FUNCTION = 2 (127 & 2 === 2)
Attribute::TARGET_METHOD = 4 (127 & 4 === 4)
Attribute::TARGET_PROPERTY = 8 (127 & 8 === 8)
Attribute::TARGET_CLASS_CONSTANT = 16 (127 & 16 === 16)
Attribute::TARGET_PARAMETER = 32 (127 & 32 === 32)
Attribute::TARGET_CONSTANT = 64 (127 & 64 === 64)
Attribute::IS_REPEATABLE = 128 (127 & 128 === 0)
int(127)
int(127)
Attribute::TARGET_CLASS = 1 (255 & 1 === 1)
Attribute::TARGET_FUNCTION = 2 (255 & 2 === 2)
Attribute::TARGET_METHOD = 4 (255 & 4 === 4)
Attribute::TARGET_PROPERTY = 8 (255 & 8 === 8)
Attribute::TARGET_CLASS_CONSTANT = 16 (255 & 16 === 16)
Attribute::TARGET_PARAMETER = 32 (255 & 32 === 32)
Attribute::TARGET_CONSTANT = 64 (255 & 64 === 64)
Attribute::TARGET_CLASS_ALIAS = 128 (255 & 128 === 128)
Attribute::IS_REPEATABLE = 256 (255 & 256 === 0)
int(255)
int(255)
bool(true)
19 changes: 19 additions & 0 deletions Zend/tests/attributes/class_alias/attributes_dict.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
--TEST--
Alias attributes must not be associative
--FILE--
<?php

#[ClassAlias('Other', ['test' => new Deprecated()])]
class Demo {}

$attr = new ReflectionClass( Demo::class )->getAttributes()[0];
$attr->newInstance();

?>
--EXPECTF--
Fatal error: Uncaught Error: ClassAlias::__construct(): Argument #2 ($attributes) must be a list, not an associative array in %s:%d
Stack trace:
#0 %s(%d): ClassAlias->__construct('Other', Array)
#1 %s(%d): ReflectionAttribute->newInstance()
#2 {main}
thrown in %s on line %d
14 changes: 14 additions & 0 deletions Zend/tests/attributes/class_alias/attributes_nonarray.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
--TEST--
Alias attributes must be an array
--FILE--
<?php

#[ClassAlias('Other', true)]
class Demo {}

$attr = new ReflectionClass( Demo::class )->getAttributes()[0];
$attr->newInstance();

?>
--EXPECTF--
Fatal error: ClassAlias::__construct(): Argument #2 ($attributes) must be of type array, true given in %s on line %d
12 changes: 12 additions & 0 deletions Zend/tests/attributes/class_alias/attributes_valid.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
--TEST--
Alias attributes do not need to exist for declaration
--FILE--
<?php

#[ClassAlias('Other', [new MissingAttribute()])]
class Demo {}

echo "Done\n"
?>
--EXPECT--
Done
11 changes: 11 additions & 0 deletions Zend/tests/attributes/class_alias/name_invalid.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Alias name must be valid
--FILE--
<?php

#[ClassAlias('never')]
class Demo {}

?>
--EXPECTF--
Fatal error: Cannot use "never" as a class alias as it is reserved in %s on line %d
11 changes: 11 additions & 0 deletions Zend/tests/attributes/class_alias/name_nonstring.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Parameter must be a string
--FILE--
<?php

#[ClassAlias([])]
class Demo {}

?>
--EXPECTF--
Fatal error: ClassAlias::__construct(): Argument #1 ($alias) must be of type string, array given in %s on line %d
15 changes: 15 additions & 0 deletions Zend/tests/attributes/class_alias/name_required.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Parameter is required
--FILE--
<?php

#[ClassAlias]
class Demo {}

?>
--EXPECTF--
Fatal error: Uncaught ArgumentCountError: ClassAlias::__construct() expects at least 1 argument, 0 given in %s:%d
Stack trace:
#0 %s(%d): ClassAlias->__construct()
#1 {main}
thrown in %s on line %d
11 changes: 11 additions & 0 deletions Zend/tests/attributes/class_alias/redeclaration_error.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Cannot redeclare an existing class
--FILE--
<?php

#[ClassAlias('Attribute')]
class Demo {}

?>
--EXPECTF--
Fatal error: Unable to declare alias 'Attribute' for 'Demo' in %s on line %d
25 changes: 25 additions & 0 deletions Zend/tests/attributes/class_alias/repeated_attribute.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
--TEST--
Attribute can be repeated
--FILE--
<?php

#[ClassAlias('First')]
#[ClassAlias('Second')]
class Demo {}

var_dump( class_exists( 'First' ) );
var_dump( class_exists( 'Second' ) );

$obj1 = new First();
var_dump( $obj1 );

$obj2 = new Second();
var_dump( $obj2 );
?>
--EXPECTF--
bool(true)
bool(true)
object(Demo)#%d (0) {
}
object(Demo)#%d (0) {
}
11 changes: 11 additions & 0 deletions Zend/tests/attributes/class_alias/target_validation.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Can only be used on classes
--FILE--
<?php

#[ClassAlias]
function demo() {}

?>
--EXPECTF--
Fatal error: Attribute "ClassAlias" cannot target function (allowed targets: class) in %s on line %d
17 changes: 17 additions & 0 deletions Zend/tests/attributes/class_alias/working_example.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
--TEST--
Working usage
--FILE--
<?php

#[ClassAlias('Other')]
class Demo {}

var_dump( class_exists( 'Other' ) );

$obj = new Other();
var_dump( $obj );
?>
--EXPECTF--
bool(true)
object(Demo)#%d (0) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Class alias listed in valid targets when used wrong (internal attribute)
--FILE--
<?php

#[Deprecated]
class Example {}

?>
--EXPECTF--
Fatal error: Attribute "Deprecated" cannot target class (allowed targets: function, method, class constant, constant, class alias) in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Class alias listed in valid targets when used wrong (userland attribute)
--FILE--
<?php

#[Attribute(Attribute::TARGET_CLASS_ALIAS)]
class MyAliasAttribute {}

#[MyAliasAttribute]
class Example {}

$ref = new ReflectionClass(Example::class);
$attribs = $ref->getAttributes();
var_dump($attribs);
$attribs[0]->newInstance();

?>
--EXPECTF--
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(16) "MyAliasAttribute"
}
}

Fatal error: Uncaught Error: Attribute "MyAliasAttribute" cannot target class (allowed targets: class alias) in %s:%d
Stack trace:
#0 %s(%d): ReflectionAttribute->newInstance()
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Error when attribute does not target class alias (internal attribute)
--FILE--
<?php

#[ClassAlias('Other', [new Override()])]
class Demo {}

?>
--EXPECTF--
Fatal error: Attribute "Override" cannot target class alias (allowed targets: method) in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
--TEST--
Error when attribute does not target class alias (useland attribute)
--FILE--
<?php

#[Attribute(Attribute::TARGET_FUNCTION)]
class MyFunctionAttribute {}

#[ClassAlias('Other', [new MyFunctionAttribute()])]
class Demo {}

$ref = new ReflectionClassAlias('Other');
$attribs = $ref->getAttributes();
var_dump($attribs);
$attribs[0]->newInstance();

?>
--EXPECTF--
array(1) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(19) "MyFunctionAttribute"
}
}

Fatal error: Uncaught Error: Attribute "MyFunctionAttribute" cannot target class alias (allowed targets: function) in %s:%d
Stack trace:
#0 %s(%d): ReflectionAttribute->newInstance()
#1 {main}
thrown in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
--TEST--
Validation of attribute repetition (not allowed; internal attribute)
--FILE--
<?php

#[ClassAlias('Other', [new Deprecated(), new Deprecated()])]
class Demo {}

?>
--EXPECTF--
Fatal error: Attribute "Deprecated" must not be repeated in %s on line %d
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
Validation of attribute repetition (not allowed; userland attribute)
--FILE--
<?php

#[Attribute]
class MyAttribute {}

#[ClassAlias('Other', [new MyAttribute(), new MyAttribute()])]
class Demo {}

$attributes = new ReflectionClassAlias('Other')->getAttributes();
var_dump($attributes);
$attributes[0]->newInstance();

?>
--EXPECTF--
array(2) {
[0]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(11) "MyAttribute"
}
[1]=>
object(ReflectionAttribute)#%d (1) {
["name"]=>
string(11) "MyAttribute"
}
}

Fatal error: Uncaught Error: Attribute "MyAttribute" must not be repeated in %s:%d
Stack trace:
#0 %s(%d): ReflectionAttribute->newInstance()
#1 {main}
thrown in %s on line %d
15 changes: 15 additions & 0 deletions Zend/tests/attributes/class_alias_target/repeatable-internal.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
--TEST--
Validation of attribute repetition (is allowed; internal attribute)
--EXTENSIONS--
zend_test
--FILE--
<?php

#[ClassAlias('Other', [new ZendTestRepeatableAttribute(), new ZendTestRepeatableAttribute()])]
class Demo {}

echo "Done\n";

?>
--EXPECT--
Done
Loading
Loading