diff --git a/Zend/tests/friends/anonymous_class.phpt b/Zend/tests/friends/anonymous_class.phpt new file mode 100644 index 000000000000..aff946dfb7c1 --- /dev/null +++ b/Zend/tests/friends/anonymous_class.phpt @@ -0,0 +1,81 @@ +--TEST-- +Friends: anonymous classes can have friends +--FILE-- +protectedInstance(); + echo "\n"; + $obj->privateInstance(); + echo "\n"; + $obj->protectedProp = 1; + $obj->privateProp = 2; + var_dump($obj); + var_dump($obj::FIRST); + var_dump($obj::SECOND); + } +} + +$obj = new class () { + friend Bar; + + protected $protectedProp; + private $privateProp; + + protected const FIRST = 1; + private const SECOND = 2; + + protected static function protectedStatic() { + echo __METHOD__ . "() called, backtrace:\n"; + debug_print_backtrace(); + } + + private static function privateStatic() { + echo __METHOD__ . "() called, backtrace:\n"; + debug_print_backtrace(); + } + + protected function protectedInstance() { + echo __METHOD__ . "() called, backtrace:\n"; + debug_print_backtrace(); + } + + private function privateInstance() { + echo __METHOD__ . "() called, backtrace:\n"; + debug_print_backtrace(); + } +}; + +Bar::testAccess($obj); + +?> +--EXPECTF-- +class@anonymous%s:%d$0::protectedStatic() called, backtrace: +#0 %s(%d): class@anonymous::protectedStatic() +#1 %s(%d): Bar::testAccess(Object(class@anonymous)) + +class@anonymous%s:%d$0::privateStatic() called, backtrace: +#0 %s(%d): class@anonymous::privateStatic() +#1 %s(%d): Bar::testAccess(Object(class@anonymous)) + +class@anonymous%s:%d$0::protectedInstance() called, backtrace: +#0 %s(%d): class@anonymous->protectedInstance() +#1 %s(%d): Bar::testAccess(Object(class@anonymous)) + +class@anonymous%s:%d$0::privateInstance() called, backtrace: +#0 %s(%d): class@anonymous->privateInstance() +#1 %s(%d): Bar::testAccess(Object(class@anonymous)) + +object(class@anonymous)#1 (2) { + ["protectedProp":protected]=> + int(1) + ["privateProp":"class@anonymous":private]=> + int(2) +} +int(1) +int(2) diff --git a/Zend/tests/friends/ast_printing.phpt b/Zend/tests/friends/ast_printing.phpt new file mode 100644 index 000000000000..9aa8e0409baa --- /dev/null +++ b/Zend/tests/friends/ast_printing.phpt @@ -0,0 +1,23 @@ +--TEST-- +Friends: AST printing +--FILE-- +getMessage(); +} + +?> +--EXPECT-- +assert(function () { + class Foo { + friend Bar; + } + +} && false) diff --git a/Zend/tests/friends/clone_access.phpt b/Zend/tests/friends/clone_access.phpt new file mode 100644 index 000000000000..45b67b473514 --- /dev/null +++ b/Zend/tests/friends/clone_access.phpt @@ -0,0 +1,48 @@ +--TEST-- +Friends: allows access to cloning +--FILE-- + +--EXPECTF-- +Error: Call to private method Foo::__clone() from global scope in %s:%d +Stack trace: +#0 {main} + + +----- + +Foo::__clone() called, backtrace: +#0 %s(%d): Foo->__clone() +#1 %s(%d): Bar::testCloneAccess() diff --git a/Zend/tests/friends/compiler_errors/interface_error.phpt b/Zend/tests/friends/compiler_errors/interface_error.phpt new file mode 100644 index 000000000000..47a020efe76e --- /dev/null +++ b/Zend/tests/friends/compiler_errors/interface_error.phpt @@ -0,0 +1,12 @@ +--TEST-- +Friends: cannot be used on interfaces +--FILE-- + +--EXPECTF-- +Fatal error: Cannot add friends to interfaces. Bar is used in Foo in %s on line %d diff --git a/Zend/tests/friends/compiler_errors/repetition_basic.phpt b/Zend/tests/friends/compiler_errors/repetition_basic.phpt new file mode 100644 index 000000000000..8e817c3d6bd7 --- /dev/null +++ b/Zend/tests/friends/compiler_errors/repetition_basic.phpt @@ -0,0 +1,13 @@ +--TEST-- +Friends: same friend cannot be used multiple times +--FILE-- + +--EXPECTF-- +Fatal error: Cannot add Bar as a friend of Foo multiple times. in %s on line %d diff --git a/Zend/tests/friends/compiler_errors/repetition_case_insensitive.phpt b/Zend/tests/friends/compiler_errors/repetition_case_insensitive.phpt new file mode 100644 index 000000000000..8a58a903a6d9 --- /dev/null +++ b/Zend/tests/friends/compiler_errors/repetition_case_insensitive.phpt @@ -0,0 +1,13 @@ +--TEST-- +Friends: same friend cannot be used multiple times (case insensitive) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot add BAR as a friend of Foo multiple times. in %s on line %d diff --git a/Zend/tests/friends/compiler_errors/repetition_name_resolution.phpt b/Zend/tests/friends/compiler_errors/repetition_name_resolution.phpt new file mode 100644 index 000000000000..3421367d6419 --- /dev/null +++ b/Zend/tests/friends/compiler_errors/repetition_name_resolution.phpt @@ -0,0 +1,16 @@ +--TEST-- +Friends: same friend cannot be used multiple times (after name resolution) +--FILE-- + +--EXPECTF-- +Fatal error: Cannot add SomeNamespace\Bar as a friend of Foo multiple times. in %s on line %d diff --git a/Zend/tests/friends/compiler_errors/reserved_parent.phpt b/Zend/tests/friends/compiler_errors/reserved_parent.phpt new file mode 100644 index 000000000000..a4e25f1fc8ab --- /dev/null +++ b/Zend/tests/friends/compiler_errors/reserved_parent.phpt @@ -0,0 +1,12 @@ +--TEST-- +Friends: 'parent' cannot be used as a friend name +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use "parent" as friend name, as it is reserved in %s on line %d diff --git a/Zend/tests/friends/compiler_errors/reserved_self.phpt b/Zend/tests/friends/compiler_errors/reserved_self.phpt new file mode 100644 index 000000000000..524fdee358ee --- /dev/null +++ b/Zend/tests/friends/compiler_errors/reserved_self.phpt @@ -0,0 +1,12 @@ +--TEST-- +Friends: 'self' cannot be used as a friend name +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use "self" as friend name, as it is reserved in %s on line %d diff --git a/Zend/tests/friends/compiler_errors/reserved_static.phpt b/Zend/tests/friends/compiler_errors/reserved_static.phpt new file mode 100644 index 000000000000..692845d036c3 --- /dev/null +++ b/Zend/tests/friends/compiler_errors/reserved_static.phpt @@ -0,0 +1,12 @@ +--TEST-- +Friends: 'static' cannot be used as a friend name +--FILE-- + +--EXPECTF-- +Fatal error: Cannot use "static" as friend name, as it is reserved in %s on line %d diff --git a/Zend/tests/friends/compiler_errors/trait_error.phpt b/Zend/tests/friends/compiler_errors/trait_error.phpt new file mode 100644 index 000000000000..85606ef7ea7d --- /dev/null +++ b/Zend/tests/friends/compiler_errors/trait_error.phpt @@ -0,0 +1,12 @@ +--TEST-- +Friends: cannot be used on traits +--FILE-- + +--EXPECTF-- +Fatal error: Cannot add friends to traits. Bar is used in Foo in %s on line %d diff --git a/Zend/tests/friends/constant_access.phpt b/Zend/tests/friends/constant_access.phpt new file mode 100644 index 000000000000..9bad49622c20 --- /dev/null +++ b/Zend/tests/friends/constant_access.phpt @@ -0,0 +1,53 @@ +--TEST-- +Friends: allows access to constants +--FILE-- + +--EXPECTF-- +Error: Cannot access protected constant Foo::FIRST in %s:%d +Stack trace: +#0 {main} + +Error: Cannot access private constant Foo::SECOND in %s:%d +Stack trace: +#0 {main} + + + +----- + +int(1) +int(2) diff --git a/Zend/tests/friends/constant_access_inherited.phpt b/Zend/tests/friends/constant_access_inherited.phpt new file mode 100644 index 000000000000..a4ee5b8a9afc --- /dev/null +++ b/Zend/tests/friends/constant_access_inherited.phpt @@ -0,0 +1,40 @@ +--TEST-- +Friends: allows access to inherited non-overridden constants +--FILE-- + +--EXPECTF-- +int(1) + +Error: Cannot access protected constant FooChild::SECOND in %s:%d +Stack trace: +#0 %s(%d): Bar::testConstantAccess() +#1 {main} diff --git a/Zend/tests/friends/constructor_access.phpt b/Zend/tests/friends/constructor_access.phpt new file mode 100644 index 000000000000..4d03b6f1156d --- /dev/null +++ b/Zend/tests/friends/constructor_access.phpt @@ -0,0 +1,70 @@ +--TEST-- +Friends: allows access to constructor +--FILE-- + +--EXPECTF-- +Error: Call to protected ConstructorProtected::__construct() from global scope in %s:%d +Stack trace: +#0 {main} + +Error: Call to private ConstructorPrivate::__construct() from global scope in %s:%d +Stack trace: +#0 {main} + + + +----- + +ConstructorProtected::__construct() called, backtrace: +#0 %s(%d): ConstructorProtected->__construct() +#1 %s(%d): Bar::testConstructorAccess() + +ConstructorPrivate::__construct() called, backtrace: +#0 %s(%d): ConstructorPrivate->__construct() +#1 %s(%d): Bar::testConstructorAccess() diff --git a/Zend/tests/friends/constructor_access_parent.phpt b/Zend/tests/friends/constructor_access_parent.phpt new file mode 100644 index 000000000000..d82ed2c3f6cc --- /dev/null +++ b/Zend/tests/friends/constructor_access_parent.phpt @@ -0,0 +1,46 @@ +--TEST-- +Friends: allows access to constructor as a `parent::` invocation +--FILE-- + +--EXPECTF-- +Error: Call to private Foo::__construct() from global scope in %s:%d +Stack trace: +#0 {main} + + + +----- + +Foo::__construct() called, backtrace: +#0 %s(%d): Foo->__construct() +#1 %s(%d): Bar->__construct() diff --git a/Zend/tests/friends/destructor_access.phpt b/Zend/tests/friends/destructor_access.phpt new file mode 100644 index 000000000000..4b1ea6d5e5da --- /dev/null +++ b/Zend/tests/friends/destructor_access.phpt @@ -0,0 +1,46 @@ +--TEST-- +Friends: allows access to destructors +--FILE-- + +--EXPECTF-- +Error: Call to private Foo::__destruct() from global scope in %s:%d +Stack trace: +#0 {main} + + +----- + +Foo::__destruct() called, backtrace: +#0 %s(%d): Foo->__destruct() +#1 %s(%d): Bar::testDestructorAccess() diff --git a/Zend/tests/friends/destructor_access_parent.phpt b/Zend/tests/friends/destructor_access_parent.phpt new file mode 100644 index 000000000000..b27a0588e189 --- /dev/null +++ b/Zend/tests/friends/destructor_access_parent.phpt @@ -0,0 +1,47 @@ +--TEST-- +Friends: allows access to destructors via `parent::` +--FILE-- + +--EXPECTF-- +Error: Call to private Foo::__destruct() from global scope in %s:%d +Stack trace: +#0 {main} + + +----- + +Foo::__destruct() called, backtrace: +#0 %s(%d): Foo->__destruct() +#1 %s(%d): Bar->__destruct() diff --git a/Zend/tests/friends/enum_constant_access.phpt b/Zend/tests/friends/enum_constant_access.phpt new file mode 100644 index 000000000000..82fb2d5a05b9 --- /dev/null +++ b/Zend/tests/friends/enum_constant_access.phpt @@ -0,0 +1,56 @@ +--TEST-- +Friends: allows access to constants on enums +--FILE-- + +--EXPECTF-- +Error: Cannot access protected constant Foo::FIRST in %s:%d +Stack trace: +#0 {main} + +Error: Cannot access private constant Foo::SECOND in %s:%d +Stack trace: +#0 {main} + + + +----- + +int(1) +int(2) diff --git a/Zend/tests/friends/enum_methods_access.phpt b/Zend/tests/friends/enum_methods_access.phpt new file mode 100644 index 000000000000..a65eca71de8b --- /dev/null +++ b/Zend/tests/friends/enum_methods_access.phpt @@ -0,0 +1,111 @@ +--TEST-- +Friends: allows access to methods on enums +--FILE-- +protectedInstance(); + echo "\n"; + $f->privateInstance(); + } +} + +// Confirm that the presence of a friend does not negate normal visibility +// enforcement for non friends +try { + Foo::protectedStatic(); +} catch (Error $e) { + echo $e . "\n\n"; +} +try { + Foo::privateStatic(); +} catch (Error $e) { + echo $e . "\n\n"; +} +$f = Foo::First; +try { + $f->protectedInstance(); +} catch (Error $e) { + echo $e . "\n\n"; +} +try { + $f->privateInstance(); +} catch (Error $e) { + echo $e . "\n\n"; +} + +echo "\n\n-----\n\n"; + +// But friend works +Bar::testMethodAccess(); + +?> +--EXPECTF-- +Error: Call to protected method Foo::protectedStatic() from global scope in %s:%d +Stack trace: +#0 {main} + +Error: Call to private method Foo::privateStatic() from global scope in %s:%d +Stack trace: +#0 {main} + +Error: Call to protected method Foo::protectedInstance() from global scope in %s:%d +Stack trace: +#0 {main} + +Error: Call to private method Foo::privateInstance() from global scope in %s:%d +Stack trace: +#0 {main} + + + +----- + +Foo::protectedStatic() called, backtrace: +#0 %s(%d): Foo::protectedStatic() +#1 %s(%d): Bar::testMethodAccess() + +Foo::privateStatic() called, backtrace: +#0 %s(%d): Foo::privateStatic() +#1 %s(%d): Bar::testMethodAccess() + +Foo::protectedInstance() called, backtrace: +#0 %s(%d): Foo->protectedInstance() +#1 %s(%d): Bar::testMethodAccess() + +Foo::privateInstance() called, backtrace: +#0 %s(%d): Foo->privateInstance() +#1 %s(%d): Bar::testMethodAccess() diff --git a/Zend/tests/friends/example_001.phpt b/Zend/tests/friends/example_001.phpt new file mode 100644 index 000000000000..d660b9f80269 --- /dev/null +++ b/Zend/tests/friends/example_001.phpt @@ -0,0 +1,59 @@ +--TEST-- +Friends: RFC example 1 (User and UserFactory) +--FILE-- + new User(1, "Alice"), + 2 => new User(2, "Bob"), + default => null, + }; + } +} + +$factory = new UserFactory; + +$alice = $factory->newFromId(1); +var_dump($alice); + +$bob = $factory->newFromId(2); +var_dump($bob); + +// Creation outside of the factory fails +try { + $unknown = new User(3, "Camille"); +} catch (Error $e) { + echo $e; +} + +?> +--EXPECTF-- +object(User)#%d (2) { + ["userId"]=> + int(1) + ["username"]=> + string(5) "Alice" +} +object(User)#%d (2) { + ["userId"]=> + int(2) + ["username"]=> + string(3) "Bob" +} +Error: Call to private User::__construct() from global scope in %s:%d +Stack trace: +#0 {main} diff --git a/Zend/tests/friends/example_002.phpt b/Zend/tests/friends/example_002.phpt new file mode 100644 index 000000000000..ca6cb28982f8 --- /dev/null +++ b/Zend/tests/friends/example_002.phpt @@ -0,0 +1,62 @@ +--TEST-- +Friends: RFC example 2 (User and UserBuilder) +--FILE-- +userId = $userId; + return $u; + } + + public function newWithName(string $username): User { + $u = new User(); + $u->username = $username; + return $u; + } +} + +$builder = new UserBuilder(); + +$alice = $builder->newWithId(1); +var_dump($alice); + +$bob = $builder->newWithName("Bob"); +var_dump($bob); + +// Creation outside of the builder fails +try { + $unknown = new User(); +} catch (Error $e) { + echo $e; +} + +?> +--EXPECTF-- +object(User)#%d (2) { + ["userId"]=> + int(1) + ["username"]=> + NULL +} +object(User)#%d (2) { + ["userId"]=> + NULL + ["username"]=> + string(3) "Bob" +} +Error: Call to private User::__construct() from global scope in %s:%d +Stack trace: +#0 {main} diff --git a/Zend/tests/friends/method_access.phpt b/Zend/tests/friends/method_access.phpt new file mode 100644 index 000000000000..30057f3061e7 --- /dev/null +++ b/Zend/tests/friends/method_access.phpt @@ -0,0 +1,108 @@ +--TEST-- +Friends: allows access to methods +--FILE-- +protectedInstance(); + echo "\n"; + $f->privateInstance(); + } +} + +// Confirm that the presence of a friend does not negate normal visibility +// enforcement for non friends +try { + Foo::protectedStatic(); +} catch (Error $e) { + echo $e . "\n\n"; +} +try { + Foo::privateStatic(); +} catch (Error $e) { + echo $e . "\n\n"; +} +$f = new Foo(); +try { + $f->protectedInstance(); +} catch (Error $e) { + echo $e . "\n\n"; +} +try { + $f->privateInstance(); +} catch (Error $e) { + echo $e . "\n\n"; +} + +echo "\n\n-----\n\n"; + +// But friend works +Bar::testMethodAccess(); + +?> +--EXPECTF-- +Error: Call to protected method Foo::protectedStatic() from global scope in %s:%d +Stack trace: +#0 {main} + +Error: Call to private method Foo::privateStatic() from global scope in %s:%d +Stack trace: +#0 {main} + +Error: Call to protected method Foo::protectedInstance() from global scope in %s:%d +Stack trace: +#0 {main} + +Error: Call to private method Foo::privateInstance() from global scope in %s:%d +Stack trace: +#0 {main} + + + +----- + +Foo::protectedStatic() called, backtrace: +#0 %s(%d): Foo::protectedStatic() +#1 %s(%d): Bar::testMethodAccess() + +Foo::privateStatic() called, backtrace: +#0 %s(%d): Foo::privateStatic() +#1 %s(%d): Bar::testMethodAccess() + +Foo::protectedInstance() called, backtrace: +#0 %s(%d): Foo->protectedInstance() +#1 %s(%d): Bar::testMethodAccess() + +Foo::privateInstance() called, backtrace: +#0 %s(%d): Foo->privateInstance() +#1 %s(%d): Bar::testMethodAccess() diff --git a/Zend/tests/friends/method_access_parent.phpt b/Zend/tests/friends/method_access_parent.phpt new file mode 100644 index 000000000000..1c762afeca9e --- /dev/null +++ b/Zend/tests/friends/method_access_parent.phpt @@ -0,0 +1,61 @@ +--TEST-- +Friends: allows access to methods as a `parent::` invocation +--FILE-- +privateInstance(); +} catch (Error $e) { + echo $e . "\n\n"; +} + +echo "\n\n-----\n\n"; + +// But friend works +Bar::privateStatic(); +$b = new Bar(); +$b->privateInstance(); + +?> +--EXPECTF-- +Fatal error: Cannot use "parent" when current class scope has no parent in %s on line %d diff --git a/Zend/tests/friends/not_inherited.phpt b/Zend/tests/friends/not_inherited.phpt new file mode 100644 index 000000000000..de2b4032e280 --- /dev/null +++ b/Zend/tests/friends/not_inherited.phpt @@ -0,0 +1,51 @@ +--TEST-- +Friends: friendship is not inherited +--FILE-- + +--EXPECTF-- +Foo::privateStatic() +Foo::privateStatic() + + +----- + + +Fatal error: Uncaught Error: Call to private method Foo::privateStatic() from scope Baz in %s:%d +Stack trace: +#0 %s(%d): Baz::testFooAccessAgain() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/friends/not_reciprocal.phpt b/Zend/tests/friends/not_reciprocal.phpt new file mode 100644 index 000000000000..dadc37c1b9ff --- /dev/null +++ b/Zend/tests/friends/not_reciprocal.phpt @@ -0,0 +1,48 @@ +--TEST-- +Friends: friendship is not reciprocal +--FILE-- + +--EXPECTF-- +Foo::privateStatic() + + +----- + + +Fatal error: Uncaught Error: Call to private method Bar::privateStatic() from scope Foo in %s:%d +Stack trace: +#0 %s(%d): Foo::testBarAccess() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/friends/not_reserved.phpt b/Zend/tests/friends/not_reserved.phpt new file mode 100644 index 000000000000..5235eeddc13d --- /dev/null +++ b/Zend/tests/friends/not_reserved.phpt @@ -0,0 +1,54 @@ +--TEST-- +Friends: `friend` is not reserved +--FILE-- +Friend); + return $this; + } + } + + class Subclass extends Friend {} +} + +namespace InterfaceDemo { + interface Friend {} +} + +namespace TraitDemo { + trait Friend {} +} + +namespace EnumDemo { + enum Friend { + case Friend; + } +} + +namespace { + use ClazzDemo\Friend; + use ClazzDemo\Subclass; + + $f = new Friend; + $f->Friend = "demo"; + $f->Friend(); + + $f = new Subclass; + $f->Friend = "demo"; + $f->Friend(); + + var_dump(\EnumDemo\Friend::Friend); +} + +?> +--EXPECT-- +string(4) "demo" +string(4) "demo" +enum(EnumDemo\Friend::Friend) diff --git a/Zend/tests/friends/not_transitive.phpt b/Zend/tests/friends/not_transitive.phpt new file mode 100644 index 000000000000..45ab3d88dd57 --- /dev/null +++ b/Zend/tests/friends/not_transitive.phpt @@ -0,0 +1,59 @@ +--TEST-- +Friends: friendship is not transitive +--FILE-- + +--EXPECTF-- +Foo::privateStatic() +Bar::privateStatic() + + +----- + + +Fatal error: Uncaught Error: Call to private method Foo::privateStatic() from scope Baz in %s:%d +Stack trace: +#0 %s(%d): Baz::testFooAccess() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/friends/property_access.phpt b/Zend/tests/friends/property_access.phpt new file mode 100644 index 000000000000..5aab46bd2e9b --- /dev/null +++ b/Zend/tests/friends/property_access.phpt @@ -0,0 +1,85 @@ +--TEST-- +Friends: allows access to properties +--FILE-- +protectedInstance = 3; + $f->privateInstance = 4; + var_dump($f); + } +} + +// Confirm that the presence of a friend does not negate normal visibility +// enforcement for non friends +try { + Foo::$protectedStatic = 1; +} catch (Error $e) { + echo $e . "\n\n"; +} +try { + Foo::$privateStatic = 2; +} catch (Error $e) { + echo $e . "\n\n"; +} +$f = new Foo(); +try { + $f->protectedInstance = 3; +} catch (Error $e) { + echo $e . "\n\n"; +} +try { + $f->privateInstance = 4; +} catch (Error $e) { + echo $e . "\n\n"; +} + +echo "\n\n-----\n\n"; + +// But friend works +Bar::testPropertyAccess(); + +?> +--EXPECTF-- +Error: Cannot access protected property Foo::$protectedStatic in %s:%d +Stack trace: +#0 {main} + +Error: Cannot access private property Foo::$privateStatic in %s:%d +Stack trace: +#0 {main} + +Error: Cannot access protected property Foo::$protectedInstance in %s:%d +Stack trace: +#0 {main} + +Error: Cannot access private property Foo::$privateInstance in %s:%d +Stack trace: +#0 {main} + + + +----- + + +Fatal error: Uncaught Error: Cannot access protected property Foo::$protectedStatic in %s:%d +Stack trace: +#0 %s(%d): Bar::testPropertyAccess() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/friends/property_access_aviz.phpt b/Zend/tests/friends/property_access_aviz.phpt new file mode 100644 index 000000000000..e41e3a74beca --- /dev/null +++ b/Zend/tests/friends/property_access_aviz.phpt @@ -0,0 +1,87 @@ +--TEST-- +Friends: allows access to properties with asymmetric visibility +--FILE-- +protectedInstance = 3; + $f->privateInstance = 4; + var_dump($f); + } +} + +// Confirm that the presence of a friend does not negate normal visibility +// enforcement for non friends +try { + Foo::$protectedStatic = 1; +} catch (Error $e) { + echo $e . "\n\n"; +} +try { + Foo::$privateStatic = 2; +} catch (Error $e) { + echo $e . "\n\n"; +} +$f = new Foo(); +try { + $f->protectedInstance = 3; +} catch (Error $e) { + echo $e . "\n\n"; +} +try { + $f->privateInstance = 4; +} catch (Error $e) { + echo $e . "\n\n"; +} + +echo "\n\n-----\n\n"; + +// But friend works +Bar::testPropertyAccess(); + +?> +--EXPECTF-- +Error: Cannot modify protected(set) property Foo::$protectedStatic from global scope in %s:%d +Stack trace: +#0 {main} + +Error: Cannot modify private(set) property Foo::$privateStatic from global scope in %s:%d +Stack trace: +#0 {main} + +Error: Cannot modify protected(set) property Foo::$protectedInstance from global scope in %s:%d +Stack trace: +#0 {main} + +Error: Cannot modify private(set) property Foo::$privateInstance from global scope in %s:%d +Stack trace: +#0 {main} + + + +----- + +int(1) +int(2) +object(Foo)#%d (2) { + ["protectedInstance"]=> + int(3) + ["privateInstance"]=> + int(4) +} diff --git a/Zend/tests/friends/property_access_inherited.phpt b/Zend/tests/friends/property_access_inherited.phpt new file mode 100644 index 000000000000..50eec13560a4 --- /dev/null +++ b/Zend/tests/friends/property_access_inherited.phpt @@ -0,0 +1,67 @@ +--TEST-- +Friends: allows access to inherited non-overridden instance properties +--FILE-- +protectedInstance = 1; + $fc->privateInstance = 2; + + try { + $fc->protectedInstance2 = 3; + } catch (Error $e) { + echo $e . "\n\n"; + } + try { + $fc->privateInstance2 = 4; + } catch (Error $e) { + echo $e . "\n\n"; + } + + var_dump($fc); + } +} + +Bar::testPropertyAccess(); + +?> +--EXPECTF-- +Error: Cannot access protected property FooChild::$protectedInstance2 in %s:%d +Stack trace: +#0 %s(%d): Bar::testPropertyAccess() +#1 {main} + +Error: Cannot access private property FooChild::$privateInstance2 in %s:%d +Stack trace: +#0 %s(%d): Bar::testPropertyAccess() +#1 {main} + +object(FooChild)#%d (5) { + ["protectedInstance":protected]=> + int(1) + ["privateInstance":"Foo":private]=> + int(2) + ["protectedInstance2":protected]=> + NULL + ["privateInstance2":"Foo":private]=> + NULL + ["privateInstance2":"FooChild":private]=> + NULL +} diff --git a/Zend/tests/friends/property_access_parent.phpt b/Zend/tests/friends/property_access_parent.phpt new file mode 100644 index 000000000000..0fd5c8baa0b6 --- /dev/null +++ b/Zend/tests/friends/property_access_parent.phpt @@ -0,0 +1,51 @@ +--TEST-- +Friends: allows access to properties via `parent::` in a property hook +--FILE-- +privateInstance = 1; +} catch (Error $e) { + echo $e . "\n\n"; +} + +echo "\n\n-----\n\n"; + +// But friend works +$b = new Bar(); +$b->privateInstance = 2; +var_dump($b); + +?> +--EXPECTF-- +Error: Cannot access private property Foo::$privateInstance in %s:%d +Stack trace: +#0 {main} + + + +----- + +object(Bar)#%d (1) { + ["privateInstance":"Foo":private]=> + int(2) +} diff --git a/Zend/zend.h b/Zend/zend.h index 0d5303192b57..ea412e01bfc7 100644 --- a/Zend/zend.h +++ b/Zend/zend.h @@ -226,6 +226,9 @@ struct _zend_class_entry { uint32_t enum_backing_type; HashTable *backed_enum_table; + uint32_t num_friends; + zend_class_name *friend_names; + zend_string *doc_comment; union { diff --git a/Zend/zend_ast.c b/Zend/zend_ast.c index a7e26711cd17..e6447bd662ba 100644 --- a/Zend/zend_ast.c +++ b/Zend/zend_ast.c @@ -1851,6 +1851,7 @@ static ZEND_COLD void zend_ast_export_stmt(smart_str *str, zend_ast *ast, int in case ZEND_AST_METHOD: case ZEND_AST_CLASS: case ZEND_AST_USE_TRAIT: + case ZEND_AST_FRIEND: case ZEND_AST_NAMESPACE: case ZEND_AST_DECLARE: break; @@ -2519,6 +2520,11 @@ static ZEND_COLD void zend_ast_export_ex(smart_str *str, zend_ast *ast, int prio APPEND_NODE_1("break"); case ZEND_AST_CONTINUE: APPEND_NODE_1("continue"); + case ZEND_AST_FRIEND: + smart_str_appends(str, "friend "); + zend_ast_export_name(str, ast->child[0], 0, indent); + smart_str_appendc(str, ';'); + break; /* 2 child nodes */ case ZEND_AST_DIM: diff --git a/Zend/zend_ast.h b/Zend/zend_ast.h index 24b77d7d3493..c2af43a0c925 100644 --- a/Zend/zend_ast.h +++ b/Zend/zend_ast.h @@ -111,6 +111,7 @@ enum _zend_ast_kind { ZEND_AST_BREAK, ZEND_AST_CONTINUE, ZEND_AST_PROPERTY_HOOK_SHORT_BODY, + ZEND_AST_FRIEND, /* 2 child nodes */ ZEND_AST_DIM = 2 << ZEND_AST_NUM_CHILDREN_SHIFT, diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a96af71aa900..db2641ced407 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2090,6 +2090,8 @@ ZEND_API void zend_initialize_class_data(zend_class_entry *ce, bool nullify_hand ce->attributes = NULL; ce->enum_backing_type = IS_UNDEF; ce->backed_enum_table = NULL; + ce->num_friends = 0; + ce->friend_names = NULL; if (nullify_handlers) { ce->constructor = NULL; @@ -2781,7 +2783,8 @@ static inline bool zend_is_unticked_stmt(const zend_ast *ast) /* {{{ */ { return ast->kind == ZEND_AST_STMT_LIST || ast->kind == ZEND_AST_LABEL || ast->kind == ZEND_AST_PROP_DECL || ast->kind == ZEND_AST_CLASS_CONST_GROUP - || ast->kind == ZEND_AST_USE_TRAIT || ast->kind == ZEND_AST_METHOD; + || ast->kind == ZEND_AST_USE_TRAIT || ast->kind == ZEND_AST_METHOD + || ast->kind == ZEND_AST_FRIEND; } /* }}} */ @@ -9469,6 +9472,55 @@ static void zend_compile_use_trait(const zend_ast *ast) /* {{{ */ } /* }}} */ +static void zend_compile_friend(const zend_ast *ast) +{ + zend_class_entry *ce = CG(active_class_entry); + // Not yet supported for internal classes + ZEND_ASSERT(ce->type == ZEND_USER_CLASS); + + if (ce->ce_flags & ZEND_ACC_INTERFACE) { + zend_string *friend_name = zend_ast_get_str(ast->child[0]); + zend_error_noreturn(E_COMPILE_ERROR, "Cannot add friends to interfaces. " + "%s is used in %s", ZSTR_VAL(friend_name), ZSTR_VAL(ce->name)); + } + + if (ce->ce_flags & ZEND_ACC_TRAIT) { + zend_string *friend_name = zend_ast_get_str(ast->child[0]); + zend_error_noreturn(E_COMPILE_ERROR, "Cannot add friends to traits. " + "%s is used in %s", ZSTR_VAL(friend_name), ZSTR_VAL(ce->name)); + } + + zend_string *resolved = zend_resolve_const_class_name_reference(ast->child[0], "friend name"); + zend_string *resolved_lc = zend_string_tolower(resolved); + + // Check if the friend is already declared + // Optimization: skip the check for the first friend being added + if (ce->num_friends) { + ZEND_ASSERT(ce->ce_flags & ZEND_ACC_HAS_FRIENDS); + for (uint32_t i = 0; i < ce->num_friends; ++i) { + // No need for case insensitive comparison here since names are + // stored in lower case already + if (zend_string_equals(ce->friend_names[i].lc_name, resolved_lc)) { + zend_error_noreturn( + E_COMPILE_ERROR, + "Cannot add %s as a friend of %s multiple times.", + ZSTR_VAL(resolved), + ZSTR_VAL(ce->name) + ); + } + } + } else { + ZEND_ASSERT((ce->ce_flags & ZEND_ACC_HAS_FRIENDS) == 0); + ce->ce_flags |= ZEND_ACC_HAS_FRIENDS; + } + + ce->friend_names = erealloc(ce->friend_names, sizeof(zend_class_name) * (ce->num_friends + 1)); + + ce->friend_names[ce->num_friends].name = resolved; + ce->friend_names[ce->num_friends].lc_name = resolved_lc; + ce->num_friends++; +} + static void zend_compile_implements(zend_ast *ast) /* {{{ */ { const zend_ast_list *list = zend_ast_get_list(ast); @@ -12059,6 +12111,9 @@ static void zend_compile_stmt(zend_ast *ast) /* {{{ */ case ZEND_AST_USE_TRAIT: zend_compile_use_trait(ast); break; + case ZEND_AST_FRIEND: + zend_compile_friend(ast); + break; case ZEND_AST_CLASS: zend_compile_class_decl(NULL, ast, false); break; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 0e31332c97f0..e969f2c05a64 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -274,7 +274,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */ #define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */ /* | | | */ -/* Class Flags (unused: 31) | | | */ +/* Class Flags (unused: none, use ZEND_ACC2_*) | | | */ /* =========== | | | */ /* | | | */ /* Special class types | | | */ @@ -340,6 +340,9 @@ typedef struct _zend_oparray_context { /* Class cannot be serialized or unserialized | | | */ #define ZEND_ACC_NOT_SERIALIZABLE (1 << 29) /* X | | | */ /* | | | */ +/* Class has friends | | | */ +#define ZEND_ACC_HAS_FRIENDS (1U << 31) /* X | | | */ +/* | | | */ /* Class Flags 2 (ce_flags2) (unused: 0-31) | | | */ /* ========================= | | | */ /* | | | */ diff --git a/Zend/zend_constants.c b/Zend/zend_constants.c index 18292203bee9..2572446cccd9 100644 --- a/Zend/zend_constants.c +++ b/Zend/zend_constants.c @@ -255,7 +255,7 @@ ZEND_API bool zend_verify_const_access(const zend_class_constant *c, const zend_ if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_PUBLIC) { return 1; } else if (ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_PRIVATE) { - return (c->ce == scope); + return (c->ce == scope || UNEXPECTED(zend_check_friend(c->ce, scope))); } else { ZEND_ASSERT(ZEND_CLASS_CONST_FLAGS(c) & ZEND_ACC_PROTECTED); return zend_check_protected(c->ce, scope); diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index b4dda00404ea..3f127bbdaf36 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -158,6 +158,7 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_PROTECTED_SET "'protected(set)'" %token T_PUBLIC_SET "'public(set)'" %token T_READONLY "'readonly'" +%token T_FRIEND "'friend'" %token T_VAR "'var'" %token T_UNSET "'unset'" %token T_ISSET "'isset'" @@ -1007,6 +1008,7 @@ class_statement: | attributes attributed_class_statement { $$ = zend_ast_with_attributes($2, $1); } | T_USE class_name_list trait_adaptations { $$ = zend_ast_create(ZEND_AST_USE_TRAIT, $2, $3); } + | T_FRIEND class_name ';' { $$ = zend_ast_create(ZEND_AST_FRIEND, $2); } ; class_name_list: diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 07f2d44cb5c6..f7301fb2aac6 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -1802,6 +1802,19 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_READONLY); } +/* + * The friend keyword must be followed by whitespace and another identifier. + * This avoids the BC break of using friend in classes, namespaces, functions and constants. + */ +"friend"{WHITESPACE_OR_COMMENTS}("extends"|"implements") { + yyless(6); + RETURN_TOKEN_WITH_STR(T_STRING, 0); +} +"friend"{WHITESPACE_OR_COMMENTS}[a-zA-Z_\x80-\xff] { + yyless(6); + RETURN_TOKEN_WITH_IDENT(T_FRIEND); +} + "unset" { RETURN_TOKEN_WITH_IDENT(T_UNSET); } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 571aa9e92abc..c281a9529209 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -413,7 +413,12 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c } } if (flags & ZEND_ACC_PRIVATE) { - if (property_info->ce != ce) { + // Compare the calling scope with the scope of the property + // declaration for friendship before assuming the desire to + // create a dynamic property + if (zend_check_friend(property_info->ce, scope)) { + goto found; + } else if (property_info->ce != ce) { goto dynamic; } else { wrong: @@ -425,7 +430,10 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c } } else { ZEND_ASSERT(flags & ZEND_ACC_PROTECTED); - if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) { + if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope)) + && !zend_check_friend(property_info->ce, scope) + ) { + // Friendship with the class that *defines* the property is required goto wrong; } } @@ -508,7 +516,12 @@ ZEND_API zend_property_info *zend_get_property_info(const zend_class_entry *ce, } } if (flags & ZEND_ACC_PRIVATE) { - if (property_info->ce != ce) { + // Compare the calling scope with the scope of the property + // declaration for friendship before assuming the desire to + // create a dynamic property + if (zend_check_friend(property_info->ce, scope)) { + goto found; + } else if (property_info->ce != ce) { goto dynamic; } else { wrong: @@ -520,7 +533,10 @@ ZEND_API zend_property_info *zend_get_property_info(const zend_class_entry *ce, } } else { ZEND_ASSERT(flags & ZEND_ACC_PROTECTED); - if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) { + if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope)) + && !zend_check_friend(property_info->ce, scope) + ) { + // Friendship with the class that *defines* the property is required goto wrong; } } @@ -595,8 +611,12 @@ ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_p if (prop_info->ce == scope) { return true; } - return EXPECTED((prop_info->flags & ZEND_ACC_PROTECTED_SET) - && is_protected_compatible_scope(prop_info->prototype->ce, scope)); + if (EXPECTED((prop_info->flags & ZEND_ACC_PROTECTED_SET) + && is_protected_compatible_scope(prop_info->prototype->ce, scope)) + ) { + return true; + } + return zend_check_friend(prop_info->ce, scope); } static void zend_property_guard_dtor(zval *el) /* {{{ */ { @@ -1751,19 +1771,49 @@ ZEND_API bool zend_check_protected(const zend_class_entry *ce, const zend_class_ fbc_scope = fbc_scope->parent; } + const zend_class_entry *call_scope = scope; + /* Is the function's scope the same as our current object context, * or any of the parents of our context? */ - while (scope) { - if (scope==ce) { + while (call_scope) { + if (call_scope==ce) { return 1; } - scope = scope->parent; + call_scope = call_scope->parent; + } + + if (zend_check_friend(ce, scope)) { + return 1; } return 0; } /* }}} */ +/** + * Check whether the `scope` class is a friend of the given `ce` class + */ +ZEND_API bool zend_check_friend(const zend_class_entry *ce, const zend_class_entry *scope) { + if ((ce->ce_flags & ZEND_ACC_HAS_FRIENDS) == 0) { + return false; + } + if (scope == NULL) { + // Null scope = global + return false; + } + zend_string *potential_friend = zend_string_tolower(scope->name); + for (uint32_t i = 0; i < ce->num_friends; ++i) { + // No need for case insensitive comparison here since names are + // stored in lower case already + if (zend_string_equals(ce->friend_names[i].lc_name, potential_friend)) { + zend_string_release(potential_friend); + return true; + } + } + zend_string_release(potential_friend); + return false; +} + ZEND_API ZEND_ATTRIBUTE_NONNULL zend_function *zend_get_call_trampoline_func( const zend_function *fbc, zend_string *method_name) /* {{{ */ { @@ -1972,8 +2022,12 @@ ZEND_API zend_function *zend_std_get_method(zend_object **obj_ptr, zend_string * goto exit; } } - if (UNEXPECTED(fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) - || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fbc), scope))) { + if ( + (UNEXPECTED(fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) + && !zend_check_friend(fbc->common.scope, scope) + ) + || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fbc), scope)) + ) { if (zobj->ce->__call) { fbc = zend_get_call_trampoline_func(zobj->ce->__call, method_name); } else { diff --git a/Zend/zend_object_handlers.h b/Zend/zend_object_handlers.h index d0dd804e8a41..09020d30abcf 100644 --- a/Zend/zend_object_handlers.h +++ b/Zend/zend_object_handlers.h @@ -309,6 +309,8 @@ ZEND_API int zend_objects_not_comparable(zval *o1, zval *o2); ZEND_API bool zend_check_protected(const zend_class_entry *ce, const zend_class_entry *scope); +ZEND_API bool zend_check_friend(const zend_class_entry *ce, const zend_class_entry *scope); + ZEND_API zend_result zend_check_property_access(const zend_object *zobj, zend_string *prop_info_name, bool is_dynamic); ZEND_API ZEND_ATTRIBUTE_NONNULL zend_function *zend_get_call_trampoline_func(const zend_function *fbc, zend_string *method_name); diff --git a/Zend/zend_objects_API.h b/Zend/zend_objects_API.h index 694ef398e374..0908500e4cd2 100644 --- a/Zend/zend_objects_API.h +++ b/Zend/zend_objects_API.h @@ -141,7 +141,9 @@ static zend_always_inline bool zend_check_method_accessible(const zend_function if (!(fn->common.fn_flags & ZEND_ACC_PUBLIC) && fn->common.scope != scope && (UNEXPECTED(fn->common.fn_flags & ZEND_ACC_PRIVATE) - || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fn), scope)))) { + || UNEXPECTED(!zend_check_protected(zend_get_function_root_class(fn), scope))) + && UNEXPECTED(!zend_check_friend(zend_get_function_root_class(fn), scope)) + ) { return false; } diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 0c6b22473514..fcee0ed9c6c2 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -375,6 +375,14 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->num_traits > 0) { _destroy_zend_class_traits_info(ce); } + + if (ce->num_friends > 0) { + for (uint32_t i = 0; i < ce->num_friends; i++) { + zend_string_release_ex(ce->friend_names[i].name, 0); + zend_string_release_ex(ce->friend_names[i].lc_name, 0); + } + efree(ce->friend_names); + } } if (ce->default_properties_table) { @@ -535,6 +543,7 @@ ZEND_API void destroy_zend_class(zval *zv) if (ce->attributes) { zend_hash_release(ce->attributes); } + ZEND_ASSERT(ce->num_friends == 0); free(ce); break; } diff --git a/Zend/zend_vm_def.h b/Zend/zend_vm_def.h index 05923bbc2b61..802865e1bdd2 100644 --- a/Zend/zend_vm_def.h +++ b/Zend/zend_vm_def.h @@ -3851,7 +3851,11 @@ ZEND_VM_HANDLER(113, ZEND_INIT_STATIC_METHOD_CALL, UNUSED|CLASS_FETCH|CONST|VAR, zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -10005,7 +10009,9 @@ ZEND_VM_HANDLER(209, ZEND_INIT_PARENT_PROPERTY_HOOK_CALL, CONST, UNUSED|NUM, NUM UNDEF_RESULT(); HANDLE_EXCEPTION(); } - if (prop_info->flags & ZEND_ACC_PRIVATE) { + if (prop_info->flags & ZEND_ACC_PRIVATE + && !zend_check_friend(parent_ce, ce) + ) { zend_throw_error(NULL, "Cannot access private property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); UNDEF_RESULT(); HANDLE_EXCEPTION(); diff --git a/Zend/zend_vm_execute.h b/Zend/zend_vm_execute.h index 2b5b9e5fcd47..d9f15a1766c6 100644 --- a/Zend/zend_vm_execute.h +++ b/Zend/zend_vm_execute.h @@ -7622,7 +7622,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -10351,7 +10355,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -11143,7 +11151,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -11998,7 +12010,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_PARENT_P UNDEF_RESULT(); HANDLE_EXCEPTION(); } - if (prop_info->flags & ZEND_ACC_PRIVATE) { + if (prop_info->flags & ZEND_ACC_PRIVATE + && !zend_check_friend(parent_ce, ce) + ) { zend_throw_error(NULL, "Cannot access private property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); UNDEF_RESULT(); HANDLE_EXCEPTION(); @@ -12965,7 +12979,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -25597,7 +25615,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -28292,7 +28314,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -29497,7 +29523,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -32168,7 +32198,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -34380,7 +34414,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -36462,7 +36500,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -36884,7 +36926,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -39047,7 +39093,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_FUNC_CCONV ZEND_INIT_STATIC_M zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -60280,7 +60330,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -63009,7 +63063,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -63699,7 +63757,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -64554,7 +64616,9 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_PARENT_PROPER UNDEF_RESULT(); HANDLE_EXCEPTION(); } - if (prop_info->flags & ZEND_ACC_PRIVATE) { + if (prop_info->flags & ZEND_ACC_PRIVATE + && !zend_check_friend(parent_ce, ce) + ) { zend_throw_error(NULL, "Cannot access private property %s::$%s", ZSTR_VAL(parent_ce->name), ZSTR_VAL(property_name)); UNDEF_RESULT(); HANDLE_EXCEPTION(); @@ -65521,7 +65585,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -78053,7 +78121,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -80748,7 +80820,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -81953,7 +82029,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -84624,7 +84704,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -86836,7 +86920,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -88918,7 +89006,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -89340,7 +89432,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } @@ -91503,7 +91599,11 @@ static ZEND_OPCODE_HANDLER_RET ZEND_OPCODE_HANDLER_CCONV ZEND_INIT_STATIC_METHOD zend_throw_error(NULL, "Cannot call constructor"); HANDLE_EXCEPTION(); } - if (Z_TYPE(EX(This)) == IS_OBJECT && Z_OBJ(EX(This))->ce != ce->constructor->common.scope && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE)) { + if (Z_TYPE(EX(This)) == IS_OBJECT + && Z_OBJ(EX(This))->ce != ce->constructor->common.scope + && (ce->constructor->common.fn_flags & ZEND_ACC_PRIVATE) + && !(zend_check_friend(ce, Z_OBJ(EX(This))->ce)) + ) { zend_throw_error(NULL, "Cannot call private %s::__construct()", ZSTR_VAL(ce->name)); HANDLE_EXCEPTION(); } diff --git a/ext/opcache/zend_file_cache.c b/ext/opcache/zend_file_cache.c index af59b9b2c34a..92639cab7451 100644 --- a/ext/opcache/zend_file_cache.c +++ b/ext/opcache/zend_file_cache.c @@ -929,6 +929,17 @@ static void zend_file_cache_serialize_class(zval *zv, } } + if (ce->num_friends) { + SERIALIZE_PTR(ce->friend_names); + zend_class_name *friend_names = ce->friend_names; + UNSERIALIZE_PTR(friend_names); + + for (uint32_t i = 0; i < ce->num_friends; i++) { + SERIALIZE_STR(friend_names[i].name); + SERIALIZE_STR(friend_names[i].lc_name); + } + } + SERIALIZE_PTR(ce->constructor); SERIALIZE_PTR(ce->destructor); SERIALIZE_PTR(ce->clone); @@ -1804,6 +1815,15 @@ static void zend_file_cache_unserialize_class(zval *zv, } } + if (ce->num_friends) { + UNSERIALIZE_PTR(ce->friend_names); + + for (uint32_t i = 0; i < ce->num_friends; i++) { + UNSERIALIZE_STR(ce->friend_names[i].name); + UNSERIALIZE_STR(ce->friend_names[i].lc_name); + } + } + UNSERIALIZE_PTR(ce->constructor); UNSERIALIZE_PTR(ce->destructor); UNSERIALIZE_PTR(ce->clone); diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index 973e441edc80..f5ea1c79c7d1 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -1128,6 +1128,14 @@ zend_class_entry *zend_persist_class_entry(zend_class_entry *orig_ce) } ZEND_ASSERT(ce->backed_enum_table == NULL); + + if (ce->num_friends) { + for (uint32_t i = 0; i < ce->num_friends; i++) { + zend_accel_store_interned_string(ce->friend_names[i].name); + zend_accel_store_interned_string(ce->friend_names[i].lc_name); + } + ce->friend_names = zend_shared_memdup_free(ce->friend_names, sizeof(zend_class_name) * ce->num_friends); + } } return ce; diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index 9ff37079193b..2a356b985544 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -594,6 +594,14 @@ void zend_persist_class_entry_calc(zend_class_entry *ce) ADD_SIZE(sizeof(zend_trait_precedence*) * (i + 1)); } } + + if (ce->num_friends) { + for (uint32_t i = 0; i < ce->num_friends; i++) { + ADD_INTERNED_STRING(ce->friend_names[i].name); + ADD_INTERNED_STRING(ce->friend_names[i].lc_name); + } + ADD_SIZE(sizeof(zend_class_name) * ce->num_friends); + } } } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index 94d8bb7d149c..d4766a16a155 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -5472,6 +5472,28 @@ ZEND_METHOD(ReflectionClass, getTraitAliases) } /* }}} */ +/* {{{ Returns an array of names of friends used by this class */ +ZEND_METHOD(ReflectionClass, getFriendNames) +{ + reflection_object *intern; + zend_class_entry *ce; + uint32_t i; + + ZEND_PARSE_PARAMETERS_NONE(); + GET_REFLECTION_OBJECT_PTR(ce); + + if (!ce->num_friends) { + RETURN_EMPTY_ARRAY(); + } + + array_init(return_value); + + for (i=0; i < ce->num_friends; i++) { + add_next_index_str(return_value, zend_string_copy(ce->friend_names[i].name)); + } +} +/* }}} */ + /* {{{ Returns the class' parent class, or, if none exists, FALSE */ ZEND_METHOD(ReflectionClass, getParentClass) { @@ -6678,11 +6700,11 @@ static bool check_visibility(uint32_t visibility, zend_class_entry *ce, zend_cla return false; } if (visibility & ZEND_ACC_PRIVATE) { - return false; + return zend_check_friend(ce, scope); } ZEND_ASSERT(visibility & ZEND_ACC_PROTECTED); if (!instanceof_function(scope, ce) && !instanceof_function(ce, scope)) { - return false; + return zend_check_friend(ce, scope); } } return true; diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index dd605100f8ba..228ac53a4b6b 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -346,6 +346,8 @@ public function getTraitNames(): array {} /** @tentative-return-type */ public function getTraitAliases(): array {} + public function getFriendNames(): array {} + /** @tentative-return-type */ public function isTrait(): bool {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index 65571f38d43c..01be358b22ac 100644 --- a/ext/reflection/php_reflection_arginfo.h +++ b/ext/reflection/php_reflection_arginfo.h @@ -1,5 +1,5 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: c80946cc8c8215bb6527e09bb71b3a97a76a6a98 + * Stub hash: fb95c24e0834855fbbf93969c22ee22d9c57a8fc * Has decl header: yes */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) @@ -263,6 +263,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionClass_getTraitAliases arginfo_class_ReflectionFunctionAbstract_getParameters +#define arginfo_class_ReflectionClass_getFriendNames arginfo_class_ReflectionFunctionAbstract_getClosureUsedVariables + #define arginfo_class_ReflectionClass_isTrait arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionClass_isEnum arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType @@ -831,6 +833,7 @@ ZEND_METHOD(ReflectionClass, isInterface); ZEND_METHOD(ReflectionClass, getTraits); ZEND_METHOD(ReflectionClass, getTraitNames); ZEND_METHOD(ReflectionClass, getTraitAliases); +ZEND_METHOD(ReflectionClass, getFriendNames); ZEND_METHOD(ReflectionClass, isTrait); ZEND_METHOD(ReflectionClass, isEnum); ZEND_METHOD(ReflectionClass, isAbstract); @@ -1128,6 +1131,7 @@ static const zend_function_entry class_ReflectionClass_methods[] = { ZEND_ME(ReflectionClass, getTraits, arginfo_class_ReflectionClass_getTraits, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getTraitNames, arginfo_class_ReflectionClass_getTraitNames, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, getTraitAliases, arginfo_class_ReflectionClass_getTraitAliases, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionClass, getFriendNames, arginfo_class_ReflectionClass_getFriendNames, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isTrait, arginfo_class_ReflectionClass_isTrait, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isEnum, arginfo_class_ReflectionClass_isEnum, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionClass, isAbstract, arginfo_class_ReflectionClass_isAbstract, ZEND_ACC_PUBLIC) diff --git a/ext/reflection/php_reflection_decl.h b/ext/reflection/php_reflection_decl.h index a87e1635419b..2718bcb4472c 100644 --- a/ext/reflection/php_reflection_decl.h +++ b/ext/reflection/php_reflection_decl.h @@ -1,12 +1,12 @@ /* This is a generated file, edit php_reflection.stub.php instead. - * Stub hash: c80946cc8c8215bb6527e09bb71b3a97a76a6a98 */ + * Stub hash: fb95c24e0834855fbbf93969c22ee22d9c57a8fc */ -#ifndef ZEND_PHP_REFLECTION_DECL_c80946cc8c8215bb6527e09bb71b3a97a76a6a98_H -#define ZEND_PHP_REFLECTION_DECL_c80946cc8c8215bb6527e09bb71b3a97a76a6a98_H +#ifndef ZEND_PHP_REFLECTION_DECL_fb95c24e0834855fbbf93969c22ee22d9c57a8fc_H +#define ZEND_PHP_REFLECTION_DECL_fb95c24e0834855fbbf93969c22ee22d9c57a8fc_H typedef enum zend_enum_PropertyHookType { ZEND_ENUM_PropertyHookType_Get = 1, ZEND_ENUM_PropertyHookType_Set = 2, } zend_enum_PropertyHookType; -#endif /* ZEND_PHP_REFLECTION_DECL_c80946cc8c8215bb6527e09bb71b3a97a76a6a98_H */ +#endif /* ZEND_PHP_REFLECTION_DECL_fb95c24e0834855fbbf93969c22ee22d9c57a8fc_H */ diff --git a/ext/reflection/tests/ReflectionClass_getFriendNames.phpt b/ext/reflection/tests/ReflectionClass_getFriendNames.phpt new file mode 100644 index 000000000000..cdc4bf3b5ef3 --- /dev/null +++ b/ext/reflection/tests/ReflectionClass_getFriendNames.phpt @@ -0,0 +1,34 @@ +--TEST-- +ReflectionClass::getFriendNames() method +--FILE-- +getFriendNames()); + +$rc = new ReflectionClass(ReflectionClass::class); +var_dump($rc->getFriendNames()); + +?> +--EXPECT-- +array(4) { + [0]=> + string(3) "Bar" + [1]=> + string(8) "Demo\Bar" + [2]=> + string(3) "Baz" + [3]=> + string(8) "Demo\Qux" +} +array(0) { +} diff --git a/ext/reflection/tests/ReflectionClass_toString_001.phpt b/ext/reflection/tests/ReflectionClass_toString_001.phpt index fd5d83e91741..c073e7c9b35b 100644 --- a/ext/reflection/tests/ReflectionClass_toString_001.phpt +++ b/ext/reflection/tests/ReflectionClass_toString_001.phpt @@ -30,7 +30,7 @@ Class [ class ReflectionClass implements Stringable, Refle Property [ public string $name ] } - - Methods [64] { + - Methods [65] { Method [ private method __clone ] { - Parameters [0] { @@ -259,6 +259,13 @@ Class [ class ReflectionClass implements Stringable, Refle - Tentative return [ array ] } + Method [ public method getFriendNames ] { + + - Parameters [0] { + } + - Return [ array ] + } + Method [ public method isTrait ] { - Parameters [0] { diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_friend.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_friend.phpt new file mode 100644 index 000000000000..4b692b3418d6 --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isReadable_friend.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test ReflectionProperty::isReadable() with a friend +--FILE-- +isReadable('C', null)); +var_dump(new ReflectionProperty('A', 'protectedProp')->isReadable('C', null)); +var_dump(new ReflectionProperty('A', 'privateProp')->isReadable('C', null)); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt b/ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt index ae9f446c6771..1caa39cb56bf 100644 --- a/ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt +++ b/ext/reflection/tests/ReflectionProperty_isReadable_invalid_scope.phpt @@ -4,6 +4,8 @@ Test ReflectionProperty::isReadable() invalid scope getMessage(), "\n"; } +try { + $r->isReadable('C', null); +} catch (Error $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + ?> --EXPECT-- Error: Class "B" not found +Error: Class "C" not found diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_friend.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_friend.phpt new file mode 100644 index 000000000000..062044b9924d --- /dev/null +++ b/ext/reflection/tests/ReflectionProperty_isWritable_friend.phpt @@ -0,0 +1,24 @@ +--TEST-- +Test ReflectionProperty::isWritable() with a friend +--FILE-- +isWritable('C', null)); +var_dump(new ReflectionProperty('A', 'protectedProp')->isWritable('C', null)); +var_dump(new ReflectionProperty('A', 'privateProp')->isWritable('C', null)); + +?> +--EXPECT-- +bool(true) +bool(true) +bool(true) diff --git a/ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt b/ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt index 7474e521b35e..e5dc691ff5b3 100644 --- a/ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt +++ b/ext/reflection/tests/ReflectionProperty_isWritable_invalid_scope.phpt @@ -4,6 +4,8 @@ Test ReflectionProperty::isWritable() invalid scope getMessage(), "\n"; } +try { + $r->isWritable('C', null); +} catch (Error $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} + ?> --EXPECT-- Error: Class "B" not found +Error: Class "C" not found diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 87b15b8bb345..ee0426f705bc 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -94,6 +94,7 @@ char *get_token_type_name(int token_type) case T_PROTECTED_SET: return "T_PROTECTED_SET"; case T_PUBLIC_SET: return "T_PUBLIC_SET"; case T_READONLY: return "T_READONLY"; + case T_FRIEND: return "T_FRIEND"; case T_VAR: return "T_VAR"; case T_UNSET: return "T_UNSET"; case T_ISSET: return "T_ISSET"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 57c8edad8acb..c5d492e0d02f 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -357,6 +357,11 @@ * @cvalue T_READONLY */ const T_READONLY = UNKNOWN; +/** + * @var int + * @cvalue T_FRIEND + */ +const T_FRIEND = UNKNOWN; /** * @var int * @cvalue T_VAR diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index b82842ede0f1..25d19645ba92 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 tokenizer_data.stub.php instead. - * Stub hash: c5235344b7c651d27c2c33c90696a418a9c96837 */ + * Stub hash: bd0be4aeae74201c37c8f49a49f27f077e109b34 */ static void register_tokenizer_data_symbols(int module_number) { @@ -74,6 +74,7 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_PROTECTED_SET", T_PROTECTED_SET, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_PUBLIC_SET", T_PUBLIC_SET, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_READONLY", T_READONLY, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_FRIEND", T_FRIEND, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_VAR", T_VAR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_UNSET", T_UNSET, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_ISSET", T_ISSET, CONST_PERSISTENT);