diff --git a/Zend/tests/access_modifiers/private_namespace_array_map_001.phpt b/Zend/tests/access_modifiers/private_namespace_array_map_001.phpt new file mode 100644 index 0000000000000..4ef0377c4ba90 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_array_map_001.phpt @@ -0,0 +1,27 @@ +--TEST-- +private(namespace) method with array_map - same namespace +--FILE-- + +--EXPECT-- +array(3) { + [0]=> + int(2) + [1]=> + int(4) + [2]=> + int(6) +} diff --git a/Zend/tests/access_modifiers/private_namespace_array_map_002.phpt b/Zend/tests/access_modifiers/private_namespace_array_map_002.phpt new file mode 100644 index 0000000000000..bffc87f015b6c --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_array_map_002.phpt @@ -0,0 +1,26 @@ +--TEST-- +private(namespace) method with array_map - different namespace +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: array_map(): Argument #1 ($callback) must be a valid callback or null, cannot access private(namespace) method Foo\A::double() in %s:%d +Stack trace: +#0 %s(%d): array_map(Array, Array) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_asymmetric_001.phpt b/Zend/tests/access_modifiers/private_namespace_asymmetric_001.phpt new file mode 100644 index 0000000000000..f7f3e4b1678e6 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_asymmetric_001.phpt @@ -0,0 +1,24 @@ +--TEST-- +Asymmetric visibility: public private(namespace)(set) property - read from same namespace +--FILE-- +value; + } + } + + $manager = new ConfigManager(); + echo $manager->getValue() . "\n"; +} + +?> +--EXPECT-- +100 diff --git a/Zend/tests/access_modifiers/private_namespace_asymmetric_002.phpt b/Zend/tests/access_modifiers/private_namespace_asymmetric_002.phpt new file mode 100644 index 0000000000000..8d021284edd91 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_asymmetric_002.phpt @@ -0,0 +1,25 @@ +--TEST-- +Asymmetric visibility: public private(namespace)(set) property - write from same namespace +--FILE-- +value = 200; + echo $config->value . "\n"; + } + } + + $manager = new ConfigManager(); + $manager->setValue(); +} + +?> +--EXPECT-- +200 diff --git a/Zend/tests/access_modifiers/private_namespace_asymmetric_003.phpt b/Zend/tests/access_modifiers/private_namespace_asymmetric_003.phpt new file mode 100644 index 0000000000000..fb3b1900a9a6e --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_asymmetric_003.phpt @@ -0,0 +1,30 @@ +--TEST-- +Asymmetric visibility: public private(namespace)(set) property - write from different namespace fails +--FILE-- +value = 200; + } + } + + $consumer = new Consumer(); + $consumer->tryWrite(); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Cannot modify private(namespace)(set) property App\Config::$value from scope Other\Consumer in %s:%d +Stack trace: +#0 %s(%d): Other\Consumer->tryWrite() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_asymmetric_004.phpt b/Zend/tests/access_modifiers/private_namespace_asymmetric_004.phpt new file mode 100644 index 0000000000000..3c93e5b407191 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_asymmetric_004.phpt @@ -0,0 +1,26 @@ +--TEST-- +Asymmetric visibility: public private(namespace)(set) property - read from different namespace succeeds +--FILE-- +value; + } + } + + $consumer = new Consumer(); + echo $consumer->readValue() . "\n"; +} + +?> +--EXPECT-- +100 diff --git a/Zend/tests/access_modifiers/private_namespace_asymmetric_invalid_001.phpt b/Zend/tests/access_modifiers/private_namespace_asymmetric_invalid_001.phpt new file mode 100644 index 0000000000000..0af9b24e7099c --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_asymmetric_invalid_001.phpt @@ -0,0 +1,15 @@ +--TEST-- +Asymmetric visibility with incompatible modifiers (protected and private(namespace)) +--FILE-- + +--EXPECTF-- +Fatal error: Property Test\A::$prop1 has incompatible visibility modifiers: protected and private(namespace) operate on different axes (inheritance vs namespace) and cannot be combined in asymmetric visibility in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_asymmetric_invalid_002.phpt b/Zend/tests/access_modifiers/private_namespace_asymmetric_invalid_002.phpt new file mode 100644 index 0000000000000..e095e4e0cd703 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_asymmetric_invalid_002.phpt @@ -0,0 +1,15 @@ +--TEST-- +Asymmetric visibility with incompatible modifiers (private(namespace) and protected(set)) +--FILE-- + +--EXPECTF-- +Fatal error: Property Test\A::$prop1 has incompatible visibility modifiers: protected and private(namespace) operate on different axes (inheritance vs namespace) and cannot be combined in asymmetric visibility in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_asymmetric_invalid_003.phpt b/Zend/tests/access_modifiers/private_namespace_asymmetric_invalid_003.phpt new file mode 100644 index 0000000000000..8b6969fbb2996 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_asymmetric_invalid_003.phpt @@ -0,0 +1,15 @@ +--TEST-- +Asymmetric visibility: private(namespace) private(set) is invalid (reversed hierarchy) +--FILE-- + +--EXPECTF-- +Fatal error: Visibility of property Test\A::$prop1 must not be weaker than set visibility in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_asymmetric_valid_combinations.phpt b/Zend/tests/access_modifiers/private_namespace_asymmetric_valid_combinations.phpt new file mode 100644 index 0000000000000..21dbd93694350 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_asymmetric_valid_combinations.phpt @@ -0,0 +1,49 @@ +--TEST-- +Valid asymmetric visibility combinations with private(namespace) +--FILE-- +prop1 . "\n"; + echo "prop2: " . $this->prop2 . "\n"; + echo "prop3: " . $this->prop3 . "\n"; + echo "prop4: " . $this->prop4 . "\n"; + echo "prop5: " . $this->prop5 . "\n"; + echo "prop6: " . $this->prop6 . "\n"; + echo "prop7: " . $this->prop7 . "\n"; + } +} + +$a = new A(); +$a->test(); + +echo "All valid combinations work correctly!\n"; + +?> +--EXPECT-- +prop1: test1 +prop2: test2 +prop3: test3 +prop4: test4 +prop5: test5 +prop6: test6 +prop7: test7 +All valid combinations work correctly! diff --git a/Zend/tests/access_modifiers/private_namespace_basic_001.phpt b/Zend/tests/access_modifiers/private_namespace_basic_001.phpt new file mode 100644 index 0000000000000..3c3f1504228b8 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_basic_001.phpt @@ -0,0 +1,23 @@ +--TEST-- +private(namespace) method access from same namespace (same class) +--FILE-- +validate(); + } + } + + $service = new UserService(); + echo $service->process() . "\n"; +} + +?> +--EXPECT-- +validated diff --git a/Zend/tests/access_modifiers/private_namespace_basic_002.phpt b/Zend/tests/access_modifiers/private_namespace_basic_002.phpt new file mode 100644 index 0000000000000..f29c92eb0509d --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_basic_002.phpt @@ -0,0 +1,26 @@ +--TEST-- +private(namespace) method access from same namespace (different class) +--FILE-- +validate(); + } + } + + $auth = new AuthService(); + echo $auth->authenticate() . "\n"; +} + +?> +--EXPECT-- +validated diff --git a/Zend/tests/access_modifiers/private_namespace_basic_003.phpt b/Zend/tests/access_modifiers/private_namespace_basic_003.phpt new file mode 100644 index 0000000000000..d44a1d7196fac --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_basic_003.phpt @@ -0,0 +1,32 @@ +--TEST-- +private(namespace) method access from different namespace fails +--FILE-- +validate(); + } + } + + $controller = new UserController(); + $controller->process(); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to private(namespace) method App\Services\UserService::validate() from scope App\Controllers\UserController in %s:%d +Stack trace: +#0 %s(%d): App\Controllers\UserController->process() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_basic_004.phpt b/Zend/tests/access_modifiers/private_namespace_basic_004.phpt new file mode 100644 index 0000000000000..1380f3a9c11da --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_basic_004.phpt @@ -0,0 +1,21 @@ +--TEST-- +private(namespace) property access from same namespace (same class) +--FILE-- +id; + } + } + + $user = new User(); + echo $user->getId() . "\n"; +} + +?> +--EXPECT-- +42 diff --git a/Zend/tests/access_modifiers/private_namespace_basic_005.phpt b/Zend/tests/access_modifiers/private_namespace_basic_005.phpt new file mode 100644 index 0000000000000..4887e2f76293f --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_basic_005.phpt @@ -0,0 +1,24 @@ +--TEST-- +private(namespace) property access from same namespace (different class) +--FILE-- +id; + } + } + + $repo = new UserRepository(); + echo $repo->getUserId() . "\n"; +} + +?> +--EXPECT-- +42 diff --git a/Zend/tests/access_modifiers/private_namespace_basic_006.phpt b/Zend/tests/access_modifiers/private_namespace_basic_006.phpt new file mode 100644 index 0000000000000..3eab91067aa67 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_basic_006.phpt @@ -0,0 +1,30 @@ +--TEST-- +private(namespace) property access from different namespace fails +--FILE-- +id; + } + } + + $controller = new UserController(); + $controller->showUserId(); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Cannot access private(namespace) property App\Models\User::$id from scope App\Controllers\UserController in %s:%d +Stack trace: +#0 %s(%d): App\Controllers\UserController->showUserId() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_basic_007.phpt b/Zend/tests/access_modifiers/private_namespace_basic_007.phpt new file mode 100644 index 0000000000000..b978fa82f46d7 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_basic_007.phpt @@ -0,0 +1,32 @@ +--TEST-- +private(namespace) access from sub-namespace fails (exact namespace match required) +--FILE-- +test(); + } + } + + $consumer = new Consumer(); + $consumer->callTest(); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to private(namespace) method App\Service::test() from scope App\Sub\Consumer in %s:%d +Stack trace: +#0 %s(%d): App\Sub\Consumer->callTest() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_basic_008.phpt b/Zend/tests/access_modifiers/private_namespace_basic_008.phpt new file mode 100644 index 0000000000000..fb4a61159fd3f --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_basic_008.phpt @@ -0,0 +1,32 @@ +--TEST-- +private(namespace) access from parent namespace fails (exact namespace match required) +--FILE-- +authenticate(); + } + } + + $consumer = new Consumer(); + $consumer->doAuth(); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to private(namespace) method App\Services\Auth\AuthService::authenticate() from scope App\Services\Consumer in %s:%d +Stack trace: +#0 %s(%d): App\Services\Consumer->doAuth() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_call_user_func_001.phpt b/Zend/tests/access_modifiers/private_namespace_call_user_func_001.phpt new file mode 100644 index 0000000000000..483c291469a7e --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_call_user_func_001.phpt @@ -0,0 +1,20 @@ +--TEST-- +private(namespace) method visibility with call_user_func - same namespace +--FILE-- + +--EXPECT-- +string(7) "A::test" diff --git a/Zend/tests/access_modifiers/private_namespace_call_user_func_002.phpt b/Zend/tests/access_modifiers/private_namespace_call_user_func_002.phpt new file mode 100644 index 0000000000000..c7d0d218aad97 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_call_user_func_002.phpt @@ -0,0 +1,26 @@ +--TEST-- +private(namespace) method visibility with call_user_func - different namespace +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, cannot access private(namespace) method Foo\A::test() in %s:%d +Stack trace: +#0 %s(%d): call_user_func(Array) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_call_user_func_003.phpt b/Zend/tests/access_modifiers/private_namespace_call_user_func_003.phpt new file mode 100644 index 0000000000000..40e28996a54d5 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_call_user_func_003.phpt @@ -0,0 +1,25 @@ +--TEST-- +private(namespace) method visibility with call_user_func - global namespace +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, cannot access private(namespace) method Foo\A::test() in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_call_user_func_array_001.phpt b/Zend/tests/access_modifiers/private_namespace_call_user_func_array_001.phpt new file mode 100644 index 0000000000000..ed88d5840fbc8 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_call_user_func_array_001.phpt @@ -0,0 +1,20 @@ +--TEST-- +private(namespace) method visibility with call_user_func_array - same namespace +--FILE-- + +--EXPECT-- +string(14) "A::test: hello" diff --git a/Zend/tests/access_modifiers/private_namespace_call_user_func_array_002.phpt b/Zend/tests/access_modifiers/private_namespace_call_user_func_array_002.phpt new file mode 100644 index 0000000000000..dbf4aebe8df83 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_call_user_func_array_002.phpt @@ -0,0 +1,26 @@ +--TEST-- +private(namespace) method visibility with call_user_func_array - different namespace +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: call_user_func_array(): Argument #1 ($callback) must be a valid callback, cannot access private(namespace) method Foo\A::test() in %s:%d +Stack trace: +#0 %s(%d): call_user_func_array(Array, Array) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_001.phpt b/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_001.phpt new file mode 100644 index 0000000000000..6d608209cb76e --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_001.phpt @@ -0,0 +1,21 @@ +--TEST-- +private(namespace) method visibility with Closure::fromCallable - same namespace +--FILE-- + +--EXPECT-- +string(7) "A::test" diff --git a/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_002.phpt b/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_002.phpt new file mode 100644 index 0000000000000..645fc46f2aa55 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_002.phpt @@ -0,0 +1,27 @@ +--TEST-- +private(namespace) method visibility with Closure::fromCallable - different namespace +--FILE-- +getMessage(); + } +} + +?> +--EXPECTF-- +Failed to create closure from callable: cannot access private(namespace) method Foo\A::test() diff --git a/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_003.phpt b/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_003.phpt new file mode 100644 index 0000000000000..96d118830bdf0 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_003.phpt @@ -0,0 +1,27 @@ +--TEST-- +private(namespace) method visibility with Closure::fromCallable - global namespace +--FILE-- +getMessage(); + } +} + +?> +--EXPECTF-- +Failed to create closure from callable: cannot access private(namespace) method Foo\A::test() diff --git a/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_static_001.phpt b/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_static_001.phpt new file mode 100644 index 0000000000000..5e9eca3a34751 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_static_001.phpt @@ -0,0 +1,20 @@ +--TEST-- +private(namespace) static method with Closure::fromCallable - same namespace +--FILE-- + +--EXPECT-- +string(7) "A::test" diff --git a/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_static_002.phpt b/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_static_002.phpt new file mode 100644 index 0000000000000..8e0aa8ca7ad05 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_closure_fromcallable_static_002.phpt @@ -0,0 +1,26 @@ +--TEST-- +private(namespace) static method with Closure::fromCallable - different namespace +--FILE-- +getMessage(); + } +} + +?> +--EXPECTF-- +Failed to create closure from callable: cannot access private(namespace) method Foo\A::test() diff --git a/Zend/tests/access_modifiers/private_namespace_edge_001.phpt b/Zend/tests/access_modifiers/private_namespace_edge_001.phpt new file mode 100644 index 0000000000000..b04e4efc82abc --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_edge_001.phpt @@ -0,0 +1,30 @@ +--TEST-- +Reflection: ReflectionMethod::isNamespacePrivate() returns true for private(namespace) methods +--FILE-- +getMethod('test'); + echo "test isNamespacePrivate: " . ($test->isNamespacePrivate() ? 'true' : 'false') . "\n"; + echo "test isPrivate: " . ($test->isPrivate() ? 'true' : 'false') . "\n"; + + $priv = $rc->getMethod('priv'); + echo "priv isNamespacePrivate: " . ($priv->isNamespacePrivate() ? 'true' : 'false') . "\n"; + echo "priv isPrivate: " . ($priv->isPrivate() ? 'true' : 'false') . "\n"; +} + +?> +--EXPECT-- +test isNamespacePrivate: true +test isPrivate: false +priv isNamespacePrivate: false +priv isPrivate: true diff --git a/Zend/tests/access_modifiers/private_namespace_edge_002.phpt b/Zend/tests/access_modifiers/private_namespace_edge_002.phpt new file mode 100644 index 0000000000000..1e1e2e30e89a2 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_edge_002.phpt @@ -0,0 +1,30 @@ +--TEST-- +Reflection: ReflectionProperty::isNamespacePrivate() returns true for private(namespace) properties +--FILE-- +getProperty('nsPriv'); + echo "nsPriv isNamespacePrivate: " . ($nsPriv->isNamespacePrivate() ? 'true' : 'false') . "\n"; + echo "nsPriv isPrivate: " . ($nsPriv->isPrivate() ? 'true' : 'false') . "\n"; + + $priv = $rc->getProperty('priv'); + echo "priv isNamespacePrivate: " . ($priv->isNamespacePrivate() ? 'true' : 'false') . "\n"; + echo "priv isPrivate: " . ($priv->isPrivate() ? 'true' : 'false') . "\n"; +} + +?> +--EXPECT-- +nsPriv isNamespacePrivate: true +nsPriv isPrivate: false +priv isNamespacePrivate: false +priv isPrivate: true diff --git a/Zend/tests/access_modifiers/private_namespace_edge_003.phpt b/Zend/tests/access_modifiers/private_namespace_edge_003.phpt new file mode 100644 index 0000000000000..423b037bbf733 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_edge_003.phpt @@ -0,0 +1,29 @@ +--TEST-- +Reflection: ReflectionProperty::isNamespacePrivateSet() for asymmetric visibility +--FILE-- +getProperty('asymmetric'); + echo "asymmetric isPublic: " . ($asym->isPublic() ? 'true' : 'false') . "\n"; + echo "asymmetric isNamespacePrivateSet: " . ($asym->isNamespacePrivateSet() ? 'true' : 'false') . "\n"; + + $reg = $rc->getProperty('regular'); + echo "regular isNamespacePrivate: " . ($reg->isNamespacePrivate() ? 'true' : 'false') . "\n"; + echo "regular isNamespacePrivateSet: " . ($reg->isNamespacePrivateSet() ? 'true' : 'false') . "\n"; +} + +?> +--EXPECT-- +asymmetric isPublic: true +asymmetric isNamespacePrivateSet: true +regular isNamespacePrivate: true +regular isNamespacePrivateSet: false diff --git a/Zend/tests/access_modifiers/private_namespace_edge_004.phpt b/Zend/tests/access_modifiers/private_namespace_edge_004.phpt new file mode 100644 index 0000000000000..cebba6d340c0e --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_edge_004.phpt @@ -0,0 +1,24 @@ +--TEST-- +private(namespace) in global namespace allows access from global namespace only +--FILE-- +test(); + } +} + +$consumer = new GlobalConsumer(); +echo $consumer->callTest() . "\n"; + +?> +--EXPECT-- +global diff --git a/Zend/tests/access_modifiers/private_namespace_edge_005.phpt b/Zend/tests/access_modifiers/private_namespace_edge_005.phpt new file mode 100644 index 0000000000000..d0a05b726956b --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_edge_005.phpt @@ -0,0 +1,32 @@ +--TEST-- +private(namespace) in global namespace blocks access from namespaced code +--FILE-- +test(); + } + } + + $consumer = new Consumer(); + $consumer->callTest(); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to private(namespace) method GlobalService::test() from scope App\Consumer in %s:%d +Stack trace: +#0 %s(%d): App\Consumer->callTest() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_edge_006.phpt b/Zend/tests/access_modifiers/private_namespace_edge_006.phpt new file mode 100644 index 0000000000000..124b6131098ee --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_edge_006.phpt @@ -0,0 +1,27 @@ +--TEST-- +private(namespace) method can be used as callable from same namespace +--FILE-- +testCallable(); +} + +?> +--EXPECT-- +42 diff --git a/Zend/tests/access_modifiers/private_namespace_eval.phpt b/Zend/tests/access_modifiers/private_namespace_eval.phpt new file mode 100644 index 0000000000000..42de9d187cd19 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_eval.phpt @@ -0,0 +1,74 @@ +--TEST-- +private(namespace) visibility in eval'd code +--FILE-- +prop; +echo "Property access: $val\n"; +$result = $obj->method(); +echo "Method call: $result\n"; +'); + +// Test 2: eval without namespace declaration should fail +echo "\nTest 2: eval without namespace declaration\n"; +try { + eval('$val = $obj->prop;'); + echo "UNEXPECTED: Should have failed\n"; +} catch (\Error $e) { + echo "Expected error: " . $e->getMessage() . "\n"; +} + +// Test 3: function defined in eval with namespace should work +echo "\nTest 3: function defined in eval\n"; +eval(' +namespace Foo; +function testFunc($obj) { + return $obj->prop * 2; +} +'); + +$result = \Foo\testFunc($obj); +echo "Function result: $result\n"; + +// Test 4: eval from within the namespace (top-level code) +echo "\nTest 4: eval accessing same object again\n"; +eval(' +namespace Foo; +$val = $obj->prop + 10; +echo "Modified access: $val\n"; +'); + +echo "\nDone\n"; + +?> +--EXPECT-- +Test 1: eval with namespace declaration +Property access: 42 +Method call: called + +Test 2: eval without namespace declaration +Expected error: Cannot access private(namespace) property Foo\Test::$prop from scope {main} + +Test 3: function defined in eval +Function result: 84 + +Test 4: eval accessing same object again +Modified access: 52 + +Done diff --git a/Zend/tests/access_modifiers/private_namespace_first_class_callable_001.phpt b/Zend/tests/access_modifiers/private_namespace_first_class_callable_001.phpt new file mode 100644 index 0000000000000..11e76f7a9d593 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_first_class_callable_001.phpt @@ -0,0 +1,21 @@ +--TEST-- +private(namespace) method with first-class callable - same namespace +--FILE-- +test(...); +var_dump($fn()); + +?> +--EXPECT-- +string(7) "A::test" diff --git a/Zend/tests/access_modifiers/private_namespace_first_class_callable_002.phpt b/Zend/tests/access_modifiers/private_namespace_first_class_callable_002.phpt new file mode 100644 index 0000000000000..02033fdb36a7b --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_first_class_callable_002.phpt @@ -0,0 +1,25 @@ +--TEST-- +private(namespace) method with first-class callable - different namespace +--FILE-- +test(...); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to private(namespace) method Foo\A::test() from global scope in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_first_class_callable_003.phpt b/Zend/tests/access_modifiers/private_namespace_first_class_callable_003.phpt new file mode 100644 index 0000000000000..46c6e84aebca2 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_first_class_callable_003.phpt @@ -0,0 +1,25 @@ +--TEST-- +private(namespace) method with first-class callable - global namespace +--FILE-- +test(...); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to private(namespace) method Foo\A::test() from global scope in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_first_class_callable_static_001.phpt b/Zend/tests/access_modifiers/private_namespace_first_class_callable_static_001.phpt new file mode 100644 index 0000000000000..afbb496ea6300 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_first_class_callable_static_001.phpt @@ -0,0 +1,20 @@ +--TEST-- +private(namespace) static method with first-class callable - same namespace +--FILE-- + +--EXPECT-- +string(7) "A::test" diff --git a/Zend/tests/access_modifiers/private_namespace_first_class_callable_static_002.phpt b/Zend/tests/access_modifiers/private_namespace_first_class_callable_static_002.phpt new file mode 100644 index 0000000000000..99d9abeab3135 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_first_class_callable_static_002.phpt @@ -0,0 +1,24 @@ +--TEST-- +private(namespace) static method with first-class callable - different namespace +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to private(namespace) method Foo\A::test() from global scope in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_inheritance_001.phpt b/Zend/tests/access_modifiers/private_namespace_inheritance_001.phpt new file mode 100644 index 0000000000000..19573e0be7ab8 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_inheritance_001.phpt @@ -0,0 +1,31 @@ +--TEST-- +private(namespace) methods not accessible from child class in different namespace +--FILE-- +test(); + } + } + + $child = new Child(); + $child->callTest(); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to private(namespace) method App\ParentClass::test() from scope Other\Child in %s:%d +Stack trace: +#0 %s(%d): Other\Child->callTest() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_inheritance_002.phpt b/Zend/tests/access_modifiers/private_namespace_inheritance_002.phpt new file mode 100644 index 0000000000000..9685620aef5a8 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_inheritance_002.phpt @@ -0,0 +1,29 @@ +--TEST-- +private(namespace) properties not accessible from child class in different namespace +--FILE-- +value; + } + } + + $child = new Child(); + echo $child->getValue(); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Cannot access private(namespace) property Other\Child::$value from scope Other\Child in %s:%d +Stack trace: +#0 %s(%d): Other\Child->getValue() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_inheritance_003.phpt b/Zend/tests/access_modifiers/private_namespace_inheritance_003.phpt new file mode 100644 index 0000000000000..769012ed64d53 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_inheritance_003.phpt @@ -0,0 +1,18 @@ +--TEST-- +Cannot reduce visibility from protected to private(namespace) in child class +--FILE-- + +--EXPECTF-- +Fatal error: Access level to App\Child::test() must be protected (as in class App\ParentClass) or weaker in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_inheritance_004.phpt b/Zend/tests/access_modifiers/private_namespace_inheritance_004.phpt new file mode 100644 index 0000000000000..d34a599619b23 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_inheritance_004.phpt @@ -0,0 +1,18 @@ +--TEST-- +Cannot reduce visibility from public to private(namespace) in child class +--FILE-- + +--EXPECTF-- +Fatal error: Access level to App\Child::test() must be public (as in class App\ParentClass) in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_property_hooks_001.phpt b/Zend/tests/access_modifiers/private_namespace_property_hooks_001.phpt new file mode 100644 index 0000000000000..57907b95da90a --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_property_hooks_001.phpt @@ -0,0 +1,35 @@ +--TEST-- +private(namespace) property with hooks - same namespace access +--FILE-- + strtoupper($this->token); + set => strtolower($value); + } + + public function __construct() { + $this->token = "ABC123"; + } +} + +class SessionStore { + public function test(SessionManager $session): void { + // Same namespace - should work + var_dump($session->token); + $session->token = "XYZ789"; + var_dump($session->token); + } +} + +$store = new SessionStore(); +$session = new SessionManager(); +$store->test($session); + +?> +--EXPECT-- +string(6) "ABC123" +string(6) "XYZ789" diff --git a/Zend/tests/access_modifiers/private_namespace_property_hooks_002.phpt b/Zend/tests/access_modifiers/private_namespace_property_hooks_002.phpt new file mode 100644 index 0000000000000..e17f3d1272f56 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_property_hooks_002.phpt @@ -0,0 +1,29 @@ +--TEST-- +private(namespace) property with hooks - different namespace fails +--FILE-- + strtoupper($this->token); + } + + public function __construct() { + $this->token = "abc123"; + } + } +} + +namespace App\Controllers { + $session = new \App\Auth\SessionManager(); + // Different namespace - should fail + var_dump($session->token); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Cannot access private(namespace) property App\Auth\SessionManager::$token from scope {main} in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_property_hooks_003.phpt b/Zend/tests/access_modifiers/private_namespace_property_hooks_003.phpt new file mode 100644 index 0000000000000..2b3dc1911075b --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_property_hooks_003.phpt @@ -0,0 +1,47 @@ +--TEST-- +Asymmetric visibility with property hooks - public private(namespace)(set) +--FILE-- + $this->value * 2; + set => $value + 10; + } + + public function __construct() { + $this->value = 5; + } + } + + class ConfigManager { + public function update(Settings $settings): void { + // Same namespace - can set + $settings->value = 20; + var_dump($settings->value); + } + } +} + +namespace App\Controllers { + $settings = new \App\Config\Settings(); + // Different namespace - can read (public) + var_dump($settings->value); + + $manager = new \App\Config\ConfigManager(); + $manager->update($settings); + + // Different namespace - cannot write (private(namespace)(set)) + $settings->value = 100; +} + +?> +--EXPECTF-- +int(30) +int(60) + +Fatal error: Uncaught Error: Cannot modify private(namespace)(set) property App\Config\Settings::$value from global scope in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_property_hooks_004.phpt b/Zend/tests/access_modifiers/private_namespace_property_hooks_004.phpt new file mode 100644 index 0000000000000..26658917faa21 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_property_hooks_004.phpt @@ -0,0 +1,38 @@ +--TEST-- +private(namespace) virtual property with hooks - same namespace +--FILE-- + end($this->storage) ?: 'none'; + set { + $this->storage[] = $value; + } + } +} + +class CacheMonitor { + public function track(CacheManager $cache): void { + // Same namespace - should work + var_dump($cache->lastKey); + $cache->lastKey = "key1"; + var_dump($cache->lastKey); + $cache->lastKey = "key2"; + var_dump($cache->lastKey); + } +} + +$monitor = new CacheMonitor(); +$cache = new CacheManager(); +$monitor->track($cache); + +?> +--EXPECT-- +string(4) "none" +string(4) "key1" +string(4) "key2" diff --git a/Zend/tests/access_modifiers/private_namespace_property_hooks_005.phpt b/Zend/tests/access_modifiers/private_namespace_property_hooks_005.phpt new file mode 100644 index 0000000000000..cade703acd144 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_property_hooks_005.phpt @@ -0,0 +1,47 @@ +--TEST-- +private(namespace) property hooks in traits - uses receiver class namespace +--FILE-- + $this->timestamp; + set => time(); + } + } +} + +namespace App\Models { + use App\Traits\Timestamped; + + class User { + use Timestamped; + } + + class UserRepository { + public function test(User $user): void { + // Same namespace as User (App\Models), not trait (App\Traits) + $user->timestamp = 12345; + var_dump($user->timestamp > 0); + } + } +} + +namespace App\Controllers { + $user = new \App\Models\User(); + $repo = new \App\Models\UserRepository(); + $repo->test($user); + + // Different namespace from User - should fail + var_dump($user->timestamp); +} + +?> +--EXPECTF-- +bool(true) + +Fatal error: Uncaught Error: Cannot access private(namespace) property App\Models\User::$timestamp from scope {main} in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_property_hooks_006.phpt b/Zend/tests/access_modifiers/private_namespace_property_hooks_006.phpt new file mode 100644 index 0000000000000..4b1e3756a5c29 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_property_hooks_006.phpt @@ -0,0 +1,46 @@ +--TEST-- +private(namespace) property with hook - inheritance from different namespace +--FILE-- + strtoupper($this->value); + } + + public function __construct() { + $this->value = "base"; + } + + public function test(): void { + // Works - same namespace + var_dump($this->value); + } + } +} + +namespace App\Other { + class Child extends \App\Auth\Base { + public function tryAccess(): void { + // Fails - different namespace from where property was declared + var_dump($this->value); + } + } +} + +namespace { + $child = new \App\Other\Child(); + $child->test(); // Parent method works + $child->tryAccess(); // Child method fails +} + +?> +--EXPECTF-- +string(4) "BASE" + +Fatal error: Uncaught Error: Cannot access private(namespace) property App\Other\Child::$value from scope App\Other\Child in %s:%d +Stack trace: +#0 %s(%d): App\Other\Child->tryAccess() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_reflection_invoke_001.phpt b/Zend/tests/access_modifiers/private_namespace_reflection_invoke_001.phpt new file mode 100644 index 0000000000000..097880428c029 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_reflection_invoke_001.phpt @@ -0,0 +1,21 @@ +--TEST-- +private(namespace) method via ReflectionMethod::invoke - same namespace +--FILE-- +invoke($obj, 'hello')); + +?> +--EXPECT-- +string(14) "A::test: hello" diff --git a/Zend/tests/access_modifiers/private_namespace_reflection_invoke_002.phpt b/Zend/tests/access_modifiers/private_namespace_reflection_invoke_002.phpt new file mode 100644 index 0000000000000..b7d3d039a8dfd --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_reflection_invoke_002.phpt @@ -0,0 +1,31 @@ +--TEST-- +private(namespace) method via ReflectionMethod::invoke - different namespace requires setAccessible +--FILE-- +invoke($obj, 'hello')); + + // setAccessible has no effect (deprecated) - still works + $method->setAccessible(true); + var_dump($method->invoke($obj, 'hello')); +} + +?> +--EXPECTF-- +string(14) "A::test: hello" + +Deprecated: Method ReflectionMethod::setAccessible() is deprecated since 8.5, as it has no effect in %s on line %d +string(14) "A::test: hello" diff --git a/Zend/tests/access_modifiers/private_namespace_static_callable_001.phpt b/Zend/tests/access_modifiers/private_namespace_static_callable_001.phpt new file mode 100644 index 0000000000000..e474140793a0d --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_static_callable_001.phpt @@ -0,0 +1,21 @@ +--TEST-- +private(namespace) static method via callable array - same namespace +--FILE-- + +--EXPECT-- +string(7) "A::test" +string(7) "A::test" diff --git a/Zend/tests/access_modifiers/private_namespace_static_callable_002.phpt b/Zend/tests/access_modifiers/private_namespace_static_callable_002.phpt new file mode 100644 index 0000000000000..623be14451eeb --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_static_callable_002.phpt @@ -0,0 +1,25 @@ +--TEST-- +private(namespace) static method via callable array - different namespace +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, cannot access private(namespace) method Foo\A::test() in %s:%d +Stack trace: +#0 %s(%d): call_user_func(Array) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_static_callable_003.phpt b/Zend/tests/access_modifiers/private_namespace_static_callable_003.phpt new file mode 100644 index 0000000000000..55b4321fcafc2 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_static_callable_003.phpt @@ -0,0 +1,26 @@ +--TEST-- +private(namespace) static method via callable array - string class name +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught TypeError: call_user_func(): Argument #1 ($callback) must be a valid callback, class "A" not found in %s:%d +Stack trace: +#0 %s(%d): call_user_func(Array) +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_trait_001.phpt b/Zend/tests/access_modifiers/private_namespace_trait_001.phpt new file mode 100644 index 0000000000000..98278b5099488 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_trait_001.phpt @@ -0,0 +1,29 @@ +--TEST-- +Traits: private(namespace) method uses receiver class namespace +--FILE-- +log("working"); + } + } + + $service = new Service(); + $service->doWork(); +} + +?> +--EXPECT-- +Logged: working diff --git a/Zend/tests/access_modifiers/private_namespace_trait_002.phpt b/Zend/tests/access_modifiers/private_namespace_trait_002.phpt new file mode 100644 index 0000000000000..4c81d9c353ba2 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_trait_002.phpt @@ -0,0 +1,32 @@ +--TEST-- +Traits: private(namespace) method uses receiver class namespace (different from trait namespace) +--FILE-- +log("working"); + } + } + + $service = new Service(); + $service->doWork(); +} + +?> +--EXPECT-- +Logged: working diff --git a/Zend/tests/access_modifiers/private_namespace_trait_003.phpt b/Zend/tests/access_modifiers/private_namespace_trait_003.phpt new file mode 100644 index 0000000000000..506636828e9cf --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_trait_003.phpt @@ -0,0 +1,36 @@ +--TEST-- +Traits: private(namespace) method from trait can't be accessed from different namespace +--FILE-- +helper(); + } + } + + $consumer = new Consumer(); + $consumer->test(); +} + +?> +--EXPECTF-- +Fatal error: Uncaught Error: Call to private(namespace) method App\Service::helper() from scope Other\Consumer in %s:%d +Stack trace: +#0 %s(%d): Other\Consumer->test() +#1 {main} + thrown in %s on line %d diff --git a/Zend/tests/access_modifiers/private_namespace_variable_function_001.phpt b/Zend/tests/access_modifiers/private_namespace_variable_function_001.phpt new file mode 100644 index 0000000000000..51c96eee1578f --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_variable_function_001.phpt @@ -0,0 +1,20 @@ +--TEST-- +private(namespace) method via variable function call - same namespace +--FILE-- + +--EXPECT-- +string(7) "A::test" diff --git a/Zend/tests/access_modifiers/private_namespace_variable_function_002.phpt b/Zend/tests/access_modifiers/private_namespace_variable_function_002.phpt new file mode 100644 index 0000000000000..eec9273bc82f8 --- /dev/null +++ b/Zend/tests/access_modifiers/private_namespace_variable_function_002.phpt @@ -0,0 +1,25 @@ +--TEST-- +private(namespace) method via variable function call - different namespace +--FILE-- + +--EXPECTF-- +Fatal error: Uncaught Error: Call to private(namespace) method Foo\A::test() from global scope in %s:%d +Stack trace: +#0 {main} + thrown in %s on line %d diff --git a/Zend/zend_API.c b/Zend/zend_API.c index 4a08952677627..d3bb44b7b84e7 100644 --- a/Zend/zend_API.c +++ b/Zend/zend_API.c @@ -3770,6 +3770,8 @@ ZEND_API void zend_release_fcall_info_cache(zend_fcall_info_cache *fcc) { } } +static zend_always_inline zend_string* zend_get_caller_namespace_ex(const zend_execute_data *ex); + static zend_always_inline bool zend_is_callable_check_func(const zval *callable, const zend_execute_data *frame, zend_fcall_info_cache *fcc, bool strict_class, char **error, bool suppress_deprecation) /* {{{ */ { zend_class_entry *ce_org = fcc->calling_scope; @@ -3927,6 +3929,25 @@ static zend_always_inline bool zend_is_callable_check_func(const zval *callable, goto get_function_via_handler; } } + + /* Check namespace visibility - only do early check if __call/__callstatic exists */ + if (fcc->function_handler && UNEXPECTED(fcc->function_handler->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE) && + (fcc->calling_scope && + ((fcc->object && fcc->calling_scope->__call) || + (!fcc->object && fcc->calling_scope->__callstatic)))) { + zend_string *method_namespace = zend_get_class_namespace(fcc->function_handler->common.scope); + zend_string *caller_namespace = zend_get_caller_namespace_ex(frame); + + bool namespace_match = zend_string_equals(method_namespace, caller_namespace); + zend_string_release(method_namespace); + zend_string_release(caller_namespace); + + if (!namespace_match) { + retval = false; + fcc->function_handler = NULL; + goto get_function_via_handler; + } + } } else { get_function_via_handler: if (fcc->object && fcc->calling_scope == ce_org) { @@ -3981,7 +4002,8 @@ static zend_always_inline bool zend_is_callable_check_func(const zval *callable, } } if (retval - && !(fcc->function_handler->common.fn_flags & ZEND_ACC_PUBLIC)) { + && !(fcc->function_handler->common.fn_flags & ZEND_ACC_PUBLIC) + && !(fcc->function_handler->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) { scope = get_scope(frame); ZEND_ASSERT(!(fcc->function_handler->common.fn_flags & ZEND_ACC_PUBLIC)); if (!zend_check_method_accessible(fcc->function_handler, scope)) { @@ -3994,6 +4016,26 @@ static zend_always_inline bool zend_is_callable_check_func(const zval *callable, retval = false; } } + + /* Check namespace visibility */ + if (retval && fcc->function_handler && UNEXPECTED(fcc->function_handler->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) { + zend_string *method_namespace = zend_get_class_namespace(fcc->function_handler->common.scope); + zend_string *caller_namespace = zend_get_caller_namespace_ex(frame); + + bool namespace_match = zend_string_equals(method_namespace, caller_namespace); + zend_string_release(method_namespace); + zend_string_release(caller_namespace); + + if (!namespace_match) { + if (error) { + if (*error) { + efree(*error); + } + zend_spprintf(error, 0, "cannot access %s method %s::%s()", zend_visibility_string(fcc->function_handler->common.fn_flags), ZSTR_VAL(fcc->calling_scope->name), ZSTR_VAL(fcc->function_handler->common.function_name)); + } + retval = false; + } + } } } else if (error) { if (fcc->calling_scope) { @@ -4449,9 +4491,33 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z "Property with asymmetric visibility %s::$%s must have type", ZSTR_VAL(ce->name), ZSTR_VAL(name)); } - uint32_t get_visibility = zend_visibility_to_set_visibility(access_type & ZEND_ACC_PPP_MASK); + /* Validate asymmetric visibility hierarchy. + * + * Visibility modifiers form two partial orders + * - Inheritance axis: public ⊇ protected ⊇ private + * - Namespace axis: public ⊇ private(namespace) ⊇ private + * + * protected and private(namespace) are incomparable (neither is a subset of the other). + * + * For asymmetric properties, the set visibility must satisfy: C[set] ⊇ C[base] + * This means the set of callers who can write must be a subset of those who can read. + */ + uint32_t get_visibility = access_type & ZEND_ACC_PPP_MASK; uint32_t set_visibility = access_type & ZEND_ACC_PPP_SET_MASK; - if (get_visibility > set_visibility) { + + /* Check for incompatible combinations (protected and private(namespace) on different axes). */ + if ((get_visibility == ZEND_ACC_PROTECTED && set_visibility == ZEND_ACC_NAMESPACE_PRIVATE_SET) || + (get_visibility == ZEND_ACC_NAMESPACE_PRIVATE && set_visibility == ZEND_ACC_PROTECTED_SET)) { + zend_error_noreturn(ce->type == ZEND_INTERNAL_CLASS ? E_CORE_ERROR : E_COMPILE_ERROR, + "Property %s::$%s has incompatible visibility modifiers: " + "protected and private(namespace) operate on different axes (inheritance vs namespace) " + "and cannot be combined in asymmetric visibility", + ZSTR_VAL(ce->name), ZSTR_VAL(name)); + } + + /* Check hierarchy using numeric comparison within each axis. */ + uint32_t get_visibility_as_set = zend_visibility_to_set_visibility(get_visibility); + if (get_visibility_as_set > set_visibility) { zend_error_noreturn(ce->type == ZEND_INTERNAL_CLASS ? E_CORE_ERROR : E_COMPILE_ERROR, "Visibility of property %s::$%s must not be weaker than set visibility", ZSTR_VAL(ce->name), ZSTR_VAL(name)); @@ -4459,7 +4525,8 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z /* Remove equivalent set visibility. */ if (((access_type & (ZEND_ACC_PUBLIC|ZEND_ACC_PUBLIC_SET)) == (ZEND_ACC_PUBLIC|ZEND_ACC_PUBLIC_SET)) || ((access_type & (ZEND_ACC_PROTECTED|ZEND_ACC_PROTECTED_SET)) == (ZEND_ACC_PROTECTED|ZEND_ACC_PROTECTED_SET)) - || ((access_type & (ZEND_ACC_PRIVATE|ZEND_ACC_PRIVATE_SET)) == (ZEND_ACC_PRIVATE|ZEND_ACC_PRIVATE_SET))) { + || ((access_type & (ZEND_ACC_PRIVATE|ZEND_ACC_PRIVATE_SET)) == (ZEND_ACC_PRIVATE|ZEND_ACC_PRIVATE_SET)) + || ((access_type & (ZEND_ACC_NAMESPACE_PRIVATE|ZEND_ACC_NAMESPACE_PRIVATE_SET)) == (ZEND_ACC_NAMESPACE_PRIVATE|ZEND_ACC_NAMESPACE_PRIVATE_SET))) { access_type &= ~ZEND_ACC_PPP_SET_MASK; } /* private(set) properties are implicitly final. */ @@ -4542,6 +4609,11 @@ ZEND_API zend_property_info *zend_declare_typed_property(zend_class_entry *ce, z property_info->name = zend_string_copy(name); } else if (access_type & ZEND_ACC_PRIVATE) { property_info->name = zend_mangle_property_name(ZSTR_VAL(ce->name), ZSTR_LEN(ce->name), ZSTR_VAL(name), ZSTR_LEN(name), is_persistent_class(ce)); + } else if (access_type & ZEND_ACC_NAMESPACE_PRIVATE) { + /* Mangle with namespace name to prevent external access while allowing same-namespace access */ + zend_string *namespace = zend_get_class_namespace(ce); + property_info->name = zend_mangle_property_name(ZSTR_VAL(namespace), ZSTR_LEN(namespace), ZSTR_VAL(name), ZSTR_LEN(name), is_persistent_class(ce)); + zend_string_release(namespace); } else { ZEND_ASSERT(access_type & ZEND_ACC_PROTECTED); property_info->name = zend_mangle_property_name("*", 1, ZSTR_VAL(name), ZSTR_LEN(name), is_persistent_class(ce)); @@ -5299,3 +5371,71 @@ ZEND_API zend_result zend_get_default_from_internal_arg_info( #endif return get_default_via_ast(default_value_zval, default_value); } + +/* Namespace extraction helpers for private(namespace) visibility */ + +/* Extract namespace from a fully-qualified name + * Examples: + * "Foo\\Bar\\ClassName" -> "Foo\\Bar" + * "ClassName" -> "" (empty string for global namespace) + */ +ZEND_API zend_string* zend_extract_namespace(const zend_string *name) +{ + const char *class_name = ZSTR_VAL(name); + const char *last_separator = zend_memrchr(class_name, '\\', ZSTR_LEN(name)); + + if (last_separator == NULL) { + /* No namespace separator found: global namespace */ + return ZSTR_EMPTY_ALLOC(); + } + + /* Extract namespace part (everything before the last backslash) */ + size_t namespace_len = last_separator - class_name; + return zend_string_init(class_name, namespace_len, 0); +} + +/* Get namespace from a class entry */ +ZEND_API zend_string* zend_get_class_namespace(const zend_class_entry *ce) +{ + return zend_extract_namespace(ce->name); +} + +/* Get the namespace of the currently executing code */ +static zend_always_inline zend_string* zend_get_caller_namespace_ex(const zend_execute_data *ex) +{ + if (!ex || !ex->func) { + /* No execution context - global namespace */ + return ZSTR_EMPTY_ALLOC(); + } + + /* Case 1: Called from a method: use the class namespace + * For trait methods, scope is the class that uses the trait, + * not the trait itself. This is the desired behavior. */ + if (ex->func->common.scope) { + return zend_get_class_namespace(ex->func->common.scope); + } + + /* Case 2: Called from a user function, eval code, or top-level code */ + if (ex->func->type == ZEND_USER_FUNCTION || ex->func->type == ZEND_EVAL_CODE) { + zend_op_array *op_array = &ex->func->op_array; + + /* Use the namespace_name field we added to op_array */ + if (op_array->namespace_name) { + /* Increment refcount since caller will release it */ + return zend_string_copy(op_array->namespace_name); + } + + /* Fallback: Extract namespace from function name */ + if (op_array->function_name) { + return zend_extract_namespace(op_array->function_name); + } + } + + /* Case 3: Internal function or global namespace */ + return ZSTR_EMPTY_ALLOC(); +} + +ZEND_API zend_string* zend_get_caller_namespace(void) +{ + return zend_get_caller_namespace_ex(EG(current_execute_data)); +} diff --git a/Zend/zend_API.h b/Zend/zend_API.h index e6d5a024cf61b..c9def46d624e0 100644 --- a/Zend/zend_API.h +++ b/Zend/zend_API.h @@ -927,6 +927,11 @@ ZEND_API bool zend_is_countable(const zval *countable); ZEND_API zend_result zend_get_default_from_internal_arg_info( zval *default_value_zval, const zend_internal_arg_info *arg_info); +/* Namespace extraction helpers for private(namespace) visibility */ +ZEND_API zend_string* zend_extract_namespace(const zend_string *name); +ZEND_API zend_string* zend_get_class_namespace(const zend_class_entry *ce); +ZEND_API zend_string* zend_get_caller_namespace(void); + END_EXTERN_C() #if ZEND_DEBUG diff --git a/Zend/zend_closures.c b/Zend/zend_closures.c index 4ecb6b2c493b9..328961ef302b8 100644 --- a/Zend/zend_closures.c +++ b/Zend/zend_closures.c @@ -784,6 +784,9 @@ static void zend_create_closure_ex(zval *res, zend_function *func, zend_class_en closure->func.common.fn_flags &= ~ZEND_ACC_IMMUTABLE; zend_string_addref(closure->func.op_array.function_name); + if (closure->func.op_array.namespace_name) { + zend_string_addref(closure->func.op_array.namespace_name); + } if (closure->func.op_array.refcount) { (*closure->func.op_array.refcount)++; } diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index 50ba8029873ad..530f5db037b23 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -873,6 +873,10 @@ static const char *zend_modifier_token_to_string(uint32_t token) return "protected(set)"; case T_PRIVATE_SET: return "private(set)"; + case T_PRIVATE_NAMESPACE: + return "private(namespace)"; + case T_PRIVATE_NAMESPACE_SET: + return "private(namespace)(set)"; EMPTY_SWITCH_DEFAULT_CASE() } } @@ -927,6 +931,16 @@ uint32_t zend_modifier_token_to_flag(zend_modifier_target target, uint32_t token return ZEND_ACC_PRIVATE_SET; } break; + case T_PRIVATE_NAMESPACE: + if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_METHOD) { + return ZEND_ACC_NAMESPACE_PRIVATE; + } + break; + case T_PRIVATE_NAMESPACE_SET: + if (target == ZEND_MODIFIER_TARGET_PROPERTY || target == ZEND_MODIFIER_TARGET_CPP) { + return ZEND_ACC_NAMESPACE_PRIVATE_SET; + } + break; } char *member; @@ -1023,6 +1037,7 @@ uint32_t zend_add_anonymous_class_modifier(uint32_t flags, uint32_t new_flag) uint32_t zend_add_member_modifier(uint32_t flags, uint32_t new_flag, zend_modifier_target target) /* {{{ */ { uint32_t new_flags = flags | new_flag; + /* Prevent combining visibility modifiers (public, protected, private, private(namespace)) */ if ((flags & ZEND_ACC_PPP_MASK) && (new_flag & ZEND_ACC_PPP_MASK)) { zend_throw_exception(zend_ce_compile_error, "Multiple access type modifiers are not allowed", 0); @@ -1259,6 +1274,9 @@ ZEND_API void function_add_ref(zend_function *function) /* {{{ */ if (function->common.function_name) { zend_string_addref(function->common.function_name); } + if (function->type == ZEND_USER_FUNCTION && function->op_array.namespace_name) { + zend_string_addref(function->op_array.namespace_name); + } } /* }}} */ @@ -1296,6 +1314,9 @@ ZEND_API zend_result do_bind_function(zend_function *func, const zval *lcname) / if (func->common.function_name) { zend_string_addref(func->common.function_name); } + if (func->type == ZEND_USER_FUNCTION && func->op_array.namespace_name) { + zend_string_addref(func->op_array.namespace_name); + } zend_observer_function_declared_notify(&func->op_array, Z_STR_P(lcname)); return SUCCESS; } @@ -8563,6 +8584,12 @@ static zend_op_array *zend_compile_func_decl_ex( op_array->fn_flags |= ZEND_ACC_PRELOADED; } + /* Only set namespace_name for standalone functions, not for methods. + * Methods get their namespace from the class name at runtime via zend_get_caller_namespace(). */ + if (CG(file_context).current_namespace && !CG(active_class_entry)) { + op_array->namespace_name = zend_string_copy(CG(file_context).current_namespace); + } + op_array->fn_flags |= (orig_op_array->fn_flags & ZEND_ACC_STRICT_TYPES); op_array->fn_flags |= decl->flags; op_array->line_start = decl->start_lineno; diff --git a/Zend/zend_compile.h b/Zend/zend_compile.h index 86fab4b57ded6..d18f7dff5ee2c 100644 --- a/Zend/zend_compile.h +++ b/Zend/zend_compile.h @@ -216,11 +216,18 @@ typedef struct _zend_oparray_context { /* Common flags | | | */ /* ============ | | | */ /* | | | */ -/* Visibility flags (public < protected < private) | | | */ +/* Visibility flags | | | */ +/* Two partial orders (not linear): | | | */ +/* - Inheritance axis: public ⊇ protected ⊇ private | | | */ +/* - Namespace axis: public ⊇ private(namespace) ⊇ private| | | */ +/* protected and private(namespace) are incomparable | | | */ #define ZEND_ACC_PUBLIC (1 << 0) /* | X | X | X */ #define ZEND_ACC_PROTECTED (1 << 1) /* | X | X | X */ #define ZEND_ACC_PRIVATE (1 << 2) /* | X | X | X */ /* | | | */ +/* Namespace-scoped visibility | | | */ +#define ZEND_ACC_NAMESPACE_PRIVATE (1 << 15) /* | X | X | */ +/* | | | */ /* Property or method overrides private one | | | */ #define ZEND_ACC_CHANGED (1 << 3) /* | X | X | */ /* | | | */ @@ -274,6 +281,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_PUBLIC_SET (1 << 10) /* | | X | */ #define ZEND_ACC_PROTECTED_SET (1 << 11) /* | | X | */ #define ZEND_ACC_PRIVATE_SET (1 << 12) /* | | X | */ +#define ZEND_ACC_NAMESPACE_PRIVATE_SET (1 << 13) /* | | X | */ /* | | | */ /* Class Flags (unused: 31) | | | */ /* =========== | | | */ @@ -359,7 +367,7 @@ typedef struct _zend_oparray_context { #define ZEND_ACC_VARIADIC (1 << 14) /* | X | | */ /* | | | */ /* op_array has finally blocks (user only) | | | */ -#define ZEND_ACC_HAS_FINALLY_BLOCK (1 << 15) /* | X | | */ +#define ZEND_ACC_HAS_FINALLY_BLOCK (1 << 30) /* | X | | */ /* | | | */ /* "main" op_array with | | | */ /* ZEND_DECLARE_CLASS_DELAYED opcodes | | | */ @@ -418,8 +426,8 @@ typedef struct _zend_oparray_context { /* | | | */ /* #define ZEND_ACC2_EXAMPLE (1 << 0) | X | | */ -#define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE) -#define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET) +#define ZEND_ACC_PPP_MASK (ZEND_ACC_PUBLIC | ZEND_ACC_PROTECTED | ZEND_ACC_PRIVATE | ZEND_ACC_NAMESPACE_PRIVATE) +#define ZEND_ACC_PPP_SET_MASK (ZEND_ACC_PUBLIC_SET | ZEND_ACC_PROTECTED_SET | ZEND_ACC_PRIVATE_SET | ZEND_ACC_NAMESPACE_PRIVATE_SET) static zend_always_inline uint32_t zend_visibility_to_set_visibility(uint32_t visibility) { @@ -430,6 +438,8 @@ static zend_always_inline uint32_t zend_visibility_to_set_visibility(uint32_t vi return ZEND_ACC_PROTECTED_SET; case ZEND_ACC_PRIVATE: return ZEND_ACC_PRIVATE_SET; + case ZEND_ACC_NAMESPACE_PRIVATE: + return ZEND_ACC_NAMESPACE_PRIVATE_SET; EMPTY_SWITCH_DEFAULT_CASE(); } } @@ -560,6 +570,7 @@ struct _zend_op_array { zend_string *filename; uint32_t line_start; uint32_t line_end; + zend_string *namespace_name; uint32_t last_literal; uint32_t num_dynamic_func_defs; diff --git a/Zend/zend_execute.c b/Zend/zend_execute.c index d411dcbc3b953..2fa2008b5676d 100644 --- a/Zend/zend_execute.c +++ b/Zend/zend_execute.c @@ -943,6 +943,8 @@ ZEND_API ZEND_COLD void ZEND_FASTCALL zend_asymmetric_visibility_property_modifi const char *visibility; if (prop_info->flags & ZEND_ACC_PRIVATE_SET) { visibility = "private(set)"; + } else if (prop_info->flags & ZEND_ACC_NAMESPACE_PRIVATE_SET) { + visibility = "private(namespace)(set)"; } else { ZEND_ASSERT(prop_info->flags & ZEND_ACC_PROTECTED_SET); if (prop_info->flags & ZEND_ACC_READONLY) { diff --git a/Zend/zend_inheritance.c b/Zend/zend_inheritance.c index 1f128764bdd3d..bd61221866c0d 100644 --- a/Zend/zend_inheritance.c +++ b/Zend/zend_inheritance.c @@ -126,6 +126,9 @@ static zend_always_inline zend_function *zend_duplicate_function(zend_function * if (EXPECTED(func->op_array.function_name)) { zend_string_addref(func->op_array.function_name); } + if (func->op_array.namespace_name) { + zend_string_addref(func->op_array.namespace_name); + } return func; } } @@ -204,6 +207,8 @@ const char *zend_visibility_string(uint32_t fn_flags) /* {{{ */ { if (fn_flags & ZEND_ACC_PUBLIC) { return "public"; + } else if (fn_flags & ZEND_ACC_NAMESPACE_PRIVATE) { + return "private(namespace)"; } else if (fn_flags & ZEND_ACC_PRIVATE) { return "private"; } else { @@ -215,7 +220,9 @@ const char *zend_visibility_string(uint32_t fn_flags) /* {{{ */ static const char *zend_asymmetric_visibility_string(uint32_t fn_flags) /* {{{ */ { - if (fn_flags & ZEND_ACC_PRIVATE_SET) { + if (fn_flags & ZEND_ACC_NAMESPACE_PRIVATE_SET) { + return "private(namespace)(set)"; + } else if (fn_flags & ZEND_ACC_PRIVATE_SET) { return "private(set)"; } else if (fn_flags & ZEND_ACC_PROTECTED_SET) { return "protected(set)"; @@ -1146,12 +1153,15 @@ static inheritance_status do_inheritance_check_on_method( } \ } while(0) - if (UNEXPECTED((parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_ABSTRACT|ZEND_ACC_CTOR)) == ZEND_ACC_PRIVATE)) { + if (UNEXPECTED( + ((parent_flags & (ZEND_ACC_PRIVATE|ZEND_ACC_ABSTRACT|ZEND_ACC_CTOR)) == ZEND_ACC_PRIVATE) + || ((parent_flags & (ZEND_ACC_NAMESPACE_PRIVATE|ZEND_ACC_ABSTRACT|ZEND_ACC_CTOR)) == ZEND_ACC_NAMESPACE_PRIVATE) + )) { if (flags & ZEND_INHERITANCE_SET_CHILD_CHANGED) { SEPARATE_METHOD(); child->common.fn_flags |= ZEND_ACC_CHANGED; } - /* The parent method is private and not an abstract so we don't need to check any inheritance rules */ + /* The parent method is private/private(namespace) and not abstract so we don't need to check any inheritance rules */ return INHERITANCE_SUCCESS; } @@ -1451,14 +1461,14 @@ static void do_inherit_property(zend_property_info *parent_info, zend_string *ke if (UNEXPECTED(child)) { zend_property_info *child_info = Z_PTR_P(child); - if (parent_info->flags & (ZEND_ACC_PRIVATE|ZEND_ACC_CHANGED)) { + if (parent_info->flags & (ZEND_ACC_PRIVATE|ZEND_ACC_NAMESPACE_PRIVATE|ZEND_ACC_CHANGED)) { child_info->flags |= ZEND_ACC_CHANGED; } if (parent_info->flags & ZEND_ACC_FINAL) { zend_error_noreturn(E_COMPILE_ERROR, "Cannot override final property %s::$%s", ZSTR_VAL(parent_info->ce->name), ZSTR_VAL(key)); } - if (!(parent_info->flags & ZEND_ACC_PRIVATE)) { + if (!(parent_info->flags & (ZEND_ACC_PRIVATE|ZEND_ACC_NAMESPACE_PRIVATE))) { if (!(parent_info->ce->ce_flags & ZEND_ACC_INTERFACE)) { child_info->prototype = parent_info->prototype; } diff --git a/Zend/zend_language_parser.y b/Zend/zend_language_parser.y index e4d61006fe12f..ef1185abe6161 100644 --- a/Zend/zend_language_parser.y +++ b/Zend/zend_language_parser.y @@ -158,6 +158,8 @@ static YYSIZE_T zend_yytnamerr(char*, const char*); %token T_PRIVATE_SET "'private(set)'" %token T_PROTECTED_SET "'protected(set)'" %token T_PUBLIC_SET "'public(set)'" +%token T_PRIVATE_NAMESPACE "'private(namespace)'" +%token T_PRIVATE_NAMESPACE_SET "'private(namespace)(set)'" %token T_READONLY "'readonly'" %token T_VAR "'var'" %token T_UNSET "'unset'" @@ -1108,6 +1110,8 @@ member_modifier: | T_PUBLIC_SET { $$ = T_PUBLIC_SET; } | T_PROTECTED_SET { $$ = T_PROTECTED_SET; } | T_PRIVATE_SET { $$ = T_PRIVATE_SET; } + | T_PRIVATE_NAMESPACE { $$ = T_PRIVATE_NAMESPACE; } + | T_PRIVATE_NAMESPACE_SET { $$ = T_PRIVATE_NAMESPACE_SET; } | T_STATIC { $$ = T_STATIC; } | T_ABSTRACT { $$ = T_ABSTRACT; } | T_FINAL { $$ = T_FINAL; } diff --git a/Zend/zend_language_scanner.l b/Zend/zend_language_scanner.l index 3ecb2f8d0ee45..eb528d51f8866 100644 --- a/Zend/zend_language_scanner.l +++ b/Zend/zend_language_scanner.l @@ -619,6 +619,16 @@ static zend_op_array *zend_compile(int type) zend_file_context_begin(&original_file_context); zend_oparray_context_begin(&original_oparray_context, op_array); zend_compile_top_stmt(CG(ast)); + + /* Capture namespace for top-level code visibility checking. + * For files with non-bracketed namespace declarations, this will be the file's namespace. + * For files with multiple bracketed namespace blocks, this may be NULL or the last namespace. + * Note: This is a best-effort approach - perfect namespace tracking for multiple + * bracketed namespaces in one file would require runtime tracking. */ + if (CG(file_context).current_namespace) { + op_array->namespace_name = zend_string_copy(CG(file_context).current_namespace); + } + CG(zend_lineno) = last_lineno; zend_emit_final_return(type == ZEND_USER_FUNCTION); op_array->line_start = 1; @@ -1365,6 +1375,11 @@ int start_line = CG(zend_lineno); ZVAL_UNDEF(zendlval); restart: + /* Clean up any previous token value before scanning next token */ + if (Z_TYPE_P(zendlval) == IS_STRING) { + zval_ptr_dtor_str(zendlval); + ZVAL_UNDEF(zendlval); + } SCNG(yy_text) = YYCURSOR; /*!re2c @@ -1794,6 +1809,14 @@ OPTIONAL_WHITESPACE_OR_COMMENTS ({WHITESPACE}|{MULTI_LINE_COMMENT}|{SINGLE_LINE_ RETURN_TOKEN_WITH_IDENT(T_PRIVATE_SET); } +"private(namespace)" { + RETURN_TOKEN_WITH_IDENT(T_PRIVATE_NAMESPACE); +} + +"private(namespace)(set)" { + RETURN_TOKEN_WITH_IDENT(T_PRIVATE_NAMESPACE_SET); +} + "public" { RETURN_TOKEN_WITH_IDENT(T_PUBLIC); } diff --git a/Zend/zend_object_handlers.c b/Zend/zend_object_handlers.c index 470fb76ec14e1..67ad3892bfc91 100644 --- a/Zend/zend_object_handlers.c +++ b/Zend/zend_object_handlers.c @@ -309,9 +309,20 @@ static zend_never_inline zend_property_info *zend_get_parent_private_property(co } /* }}} */ +static zend_always_inline const zend_class_entry *get_fake_or_executed_scope(void); + static ZEND_COLD zend_never_inline void zend_bad_property_access(const zend_property_info *property_info, const zend_class_entry *ce, const zend_string *member) /* {{{ */ { - zend_throw_error(NULL, "Cannot access %s property %s::$%s", zend_visibility_string(property_info->flags), ZSTR_VAL(ce->name), ZSTR_VAL(member)); + if (property_info->flags & ZEND_ACC_NAMESPACE_PRIVATE) { + const zend_class_entry *scope = get_fake_or_executed_scope(); + zend_throw_error(NULL, "Cannot access %s property %s::$%s from scope %s", + zend_visibility_string(property_info->flags), + ZSTR_VAL(ce->name), + ZSTR_VAL(member), + scope ? ZSTR_VAL(scope->name) : "{main}"); + } else { + zend_throw_error(NULL, "Cannot access %s property %s::$%s", zend_visibility_string(property_info->flags), ZSTR_VAL(ce->name), ZSTR_VAL(member)); + } } /* }}} */ @@ -368,8 +379,14 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c uintptr_t offset; if (cache_slot && EXPECTED(ce == CACHED_PTR_EX(cache_slot))) { - *info_ptr = CACHED_PTR_EX(cache_slot + 2); - return (uintptr_t)CACHED_PTR_EX(cache_slot + 1); + const zend_property_info *cached_prop_info = CACHED_PTR_EX(cache_slot + 2); + /* Disable caching for namespace_private properties since visibility depends on caller's namespace */ + if (UNEXPECTED(cached_prop_info && (cached_prop_info->flags & ZEND_ACC_NAMESPACE_PRIVATE))) { + /* Fall through to do the visibility check */ + } else { + *info_ptr = cached_prop_info; + return (uintptr_t)CACHED_PTR_EX(cache_slot + 1); + } } if (UNEXPECTED(zend_hash_num_elements(&ce->properties_info) == 0) @@ -391,7 +408,8 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c property_info = (zend_property_info*)Z_PTR_P(zv); flags = property_info->flags; - if (flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED)) { + + if (flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED|ZEND_ACC_NAMESPACE_PRIVATE)) { const zend_class_entry *scope = get_fake_or_executed_scope(); if (property_info->ce != scope) { @@ -410,24 +428,41 @@ static zend_always_inline uintptr_t zend_get_property_offset(zend_class_entry *c goto found; } } - if (flags & ZEND_ACC_PRIVATE) { - if (property_info->ce != ce) { - goto dynamic; - } else { + /* Check private/protected, but not namespace_private (handled separately below) */ + if (!(flags & ZEND_ACC_NAMESPACE_PRIVATE)) { + if (flags & ZEND_ACC_PRIVATE) { + if (property_info->ce != ce) { + goto dynamic; + } else { wrong: - /* Information was available, but we were denied access. Error out. */ - if (!silent) { - zend_bad_property_access(property_info, ce, member); + /* Information was available, but we were denied access. Error out. */ + if (!silent) { + zend_bad_property_access(property_info, ce, member); + } + return ZEND_WRONG_PROPERTY_OFFSET; + } + } else { + ZEND_ASSERT(flags & ZEND_ACC_PROTECTED); + if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) { + goto wrong; } - return ZEND_WRONG_PROPERTY_OFFSET; - } - } else { - ZEND_ASSERT(flags & ZEND_ACC_PROTECTED); - if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) { - goto wrong; } } } + + /* Check namespace visibility (must be outside scope check) */ + if (flags & ZEND_ACC_NAMESPACE_PRIVATE) { + zend_string *property_namespace = zend_get_class_namespace(property_info->ce); + zend_string *caller_namespace = zend_get_caller_namespace(); + + bool namespace_match = zend_string_equals(property_namespace, caller_namespace); + zend_string_release(property_namespace); + zend_string_release(caller_namespace); + + if (!namespace_match) { + goto wrong; + } + } } found: @@ -491,7 +526,7 @@ ZEND_API zend_property_info *zend_get_property_info(const zend_class_entry *ce, property_info = (zend_property_info*)Z_PTR_P(zv); flags = property_info->flags; - if (flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED)) { + if (flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED|ZEND_ACC_NAMESPACE_PRIVATE)) { const zend_class_entry *scope = get_fake_or_executed_scope(); if (property_info->ce != scope) { if (flags & ZEND_ACC_CHANGED) { @@ -505,24 +540,41 @@ ZEND_API zend_property_info *zend_get_property_info(const zend_class_entry *ce, goto found; } } - if (flags & ZEND_ACC_PRIVATE) { - if (property_info->ce != ce) { - goto dynamic; - } else { + /* Check private/protected, but not namespace_private (handled separately below) */ + if (!(flags & ZEND_ACC_NAMESPACE_PRIVATE)) { + if (flags & ZEND_ACC_PRIVATE) { + if (property_info->ce != ce) { + goto dynamic; + } else { wrong: - /* Information was available, but we were denied access. Error out. */ - if (!silent) { - zend_bad_property_access(property_info, ce, member); + /* Information was available, but we were denied access. Error out. */ + if (!silent) { + zend_bad_property_access(property_info, ce, member); + } + return ZEND_WRONG_PROPERTY_INFO; + } + } else { + ZEND_ASSERT(flags & ZEND_ACC_PROTECTED); + if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) { + goto wrong; } - return ZEND_WRONG_PROPERTY_INFO; - } - } else { - ZEND_ASSERT(flags & ZEND_ACC_PROTECTED); - if (UNEXPECTED(!is_protected_compatible_scope(property_info->prototype->ce, scope))) { - goto wrong; } } } + + /* Check namespace visibility (must be outside scope check) */ + if (flags & ZEND_ACC_NAMESPACE_PRIVATE) { + zend_string *property_namespace = zend_get_class_namespace(property_info->ce); + zend_string *caller_namespace = zend_get_caller_namespace(); + + bool namespace_match = zend_string_equals(property_namespace, caller_namespace); + zend_string_release(property_namespace); + zend_string_release(caller_namespace); + + if (!namespace_match) { + goto wrong; + } + } } found: @@ -593,6 +645,22 @@ ZEND_API bool ZEND_FASTCALL zend_asymmetric_property_has_set_access(const zend_p if (prop_info->ce == scope) { return true; } + + /* Check namespace_private(set) visibility */ + if (prop_info->flags & ZEND_ACC_NAMESPACE_PRIVATE_SET) { + zend_string *property_namespace = zend_get_class_namespace(prop_info->ce); + zend_string *caller_namespace = zend_get_caller_namespace(); + + bool namespace_match = zend_string_equals(property_namespace, caller_namespace); + zend_string_release(property_namespace); + zend_string_release(caller_namespace); + + if (namespace_match) { + return true; + } + return false; + } + return EXPECTED((prop_info->flags & ZEND_ACC_PROTECTED_SET) && is_protected_compatible_scope(prop_info->prototype->ce, scope)); } @@ -1871,7 +1939,7 @@ ZEND_API zend_function *zend_std_get_method(zend_object **obj_ptr, zend_string * fbc = Z_FUNC_P(func); /* Check access level */ - if (fbc->op_array.fn_flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED)) { + if (fbc->op_array.fn_flags & (ZEND_ACC_CHANGED|ZEND_ACC_PRIVATE|ZEND_ACC_PROTECTED|ZEND_ACC_NAMESPACE_PRIVATE)) { const zend_class_entry *scope = zend_get_executed_scope(); if (fbc->common.scope != scope) { @@ -1885,8 +1953,30 @@ 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))) { + /* Check private/protected, but not namespace_private (handled separately below) */ + if (!(fbc->op_array.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) { + if (UNEXPECTED(fbc->op_array.fn_flags & ZEND_ACC_PRIVATE) + || 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 { + zend_bad_method_call(fbc, method_name, scope); + fbc = NULL; + } + } + } + } + + /* Check namespace visibility */ + if (fbc && UNEXPECTED(fbc->op_array.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) { + zend_string *method_namespace = zend_get_class_namespace(fbc->common.scope); + zend_string *caller_namespace = zend_get_caller_namespace(); + + bool namespace_match = zend_string_equals(method_namespace, caller_namespace); + zend_string_release(method_namespace); + zend_string_release(caller_namespace); + + if (!namespace_match) { if (zobj->ce->__call) { fbc = zend_get_call_trampoline_func(zobj->ce->__call, method_name); } else { @@ -1952,6 +2042,25 @@ ZEND_API zend_function *zend_std_get_static_method(const zend_class_entry *ce, z fbc = fallback_fbc; } } + + /* Check namespace visibility */ + if (fbc && UNEXPECTED(fbc->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) { + zend_string *method_namespace = zend_get_class_namespace(fbc->common.scope); + zend_string *caller_namespace = zend_get_caller_namespace(); + + bool namespace_match = zend_string_equals(method_namespace, caller_namespace); + zend_string_release(method_namespace); + zend_string_release(caller_namespace); + + if (!namespace_match) { + const zend_class_entry *scope = zend_get_executed_scope(); + zend_function *fallback_fbc = get_static_method_fallback(ce, function_name); + if (!fallback_fbc) { + zend_bad_method_call(fbc, function_name, scope); + } + fbc = fallback_fbc; + } + } } else { fbc = get_static_method_fallback(ce, function_name); } @@ -2031,6 +2140,28 @@ ZEND_API zval *zend_std_get_static_property_with_info(zend_class_entry *ce, zend return NULL; } } + + /* Check namespace visibility */ + if (UNEXPECTED(property_info->flags & ZEND_ACC_NAMESPACE_PRIVATE)) { + zend_string *property_namespace = zend_get_class_namespace(property_info->ce); + zend_string *caller_namespace = zend_get_caller_namespace(); + + bool namespace_match = zend_string_equals(property_namespace, caller_namespace); + zend_string_release(property_namespace); + zend_string_release(caller_namespace); + + if (!namespace_match) { + if (type != BP_VAR_IS) { + const zend_class_entry *scope = get_fake_or_executed_scope(); + zend_throw_error(NULL, "Cannot access %s property %s::$%s from scope %s", + zend_visibility_string(property_info->flags), + ZSTR_VAL(ce->name), + ZSTR_VAL(property_name), + scope ? ZSTR_VAL(scope->name) : "{main}"); + } + return NULL; + } + } } if (UNEXPECTED((property_info->flags & ZEND_ACC_STATIC) == 0)) { @@ -2113,6 +2244,23 @@ ZEND_API zend_function *zend_std_get_constructor(zend_object *zobj) /* {{{ */ constructor = NULL; } } + + /* Check namespace visibility */ + if (constructor && UNEXPECTED(constructor->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE)) { + zend_string *method_namespace = zend_get_class_namespace(constructor->common.scope); + zend_string *caller_namespace = zend_get_caller_namespace(); + + bool namespace_match = zend_string_equals(method_namespace, caller_namespace); + zend_string_release(method_namespace); + zend_string_release(caller_namespace); + + if (!namespace_match) { + const zend_class_entry *scope = get_fake_or_executed_scope(); + zend_bad_constructor_call(constructor, scope); + zend_object_store_ctor_failed(zobj); + constructor = NULL; + } + } } return constructor; diff --git a/Zend/zend_objects_API.h b/Zend/zend_objects_API.h index 86c3a49f8c8c5..74e4ec5fe28ca 100644 --- a/Zend/zend_objects_API.h +++ b/Zend/zend_objects_API.h @@ -139,6 +139,11 @@ static inline zend_property_info *zend_get_typed_property_info_for_slot(zend_obj static zend_always_inline bool zend_check_method_accessible(const zend_function *fn, const zend_class_entry *scope) { + /* Skip namespace-private check here - it's handled separately via namespace visibility checks */ + if (fn->common.fn_flags & ZEND_ACC_NAMESPACE_PRIVATE) { + return true; + } + if (!(fn->common.fn_flags & ZEND_ACC_PUBLIC) && fn->common.scope != scope && (UNEXPECTED(fn->common.fn_flags & ZEND_ACC_PRIVATE) diff --git a/Zend/zend_opcode.c b/Zend/zend_opcode.c index 1962c7b5a56d1..7de8c9428ec43 100644 --- a/Zend/zend_opcode.c +++ b/Zend/zend_opcode.c @@ -66,6 +66,7 @@ void init_op_array(zend_op_array *op_array, uint8_t type, int initial_ops_size) op_array->filename = zend_string_copy(zend_get_compiled_filename()); op_array->doc_comment = NULL; op_array->attributes = NULL; + op_array->namespace_name = NULL; op_array->arg_info = NULL; op_array->num_args = 0; @@ -566,6 +567,10 @@ ZEND_API void destroy_op_array(zend_op_array *op_array) zend_string_release_ex(op_array->function_name, 0); } + if (op_array->namespace_name) { + zend_string_release_ex(op_array->namespace_name, 0); + } + if (!op_array->refcount || --(*op_array->refcount) > 0) { return; } diff --git a/ext/opcache/zend_persist.c b/ext/opcache/zend_persist.c index ef69cceb0250b..04f71989e6fee 100644 --- a/ext/opcache/zend_persist.c +++ b/ext/opcache/zend_persist.c @@ -422,6 +422,16 @@ static void zend_persist_op_array_ex(zend_op_array *op_array, zend_persistent_sc } } + if (op_array->namespace_name) { + zend_string *old_name = op_array->namespace_name; + zend_accel_store_interned_string(op_array->namespace_name); + /* Remember old namespace name, so it can be released multiple times if shared. */ + if (op_array->namespace_name != old_name + && !zend_shared_alloc_get_xlat_entry(&op_array->namespace_name)) { + zend_shared_alloc_register_xlat_entry(&op_array->namespace_name, old_name); + } + } + if (op_array->scope) { zend_class_entry *scope = zend_shared_alloc_get_xlat_entry(op_array->scope); @@ -790,6 +800,12 @@ static zend_op_array *zend_persist_class_method(zend_op_array *op_array, const z if (old_function_name) { zend_string_release_ex(old_function_name, 0); } + /* Same for namespace_name */ + zend_string *old_namespace_name = + zend_shared_alloc_get_xlat_entry(&old_op_array->namespace_name); + if (old_namespace_name) { + zend_string_release_ex(old_namespace_name, 0); + } return old_op_array; } diff --git a/ext/opcache/zend_persist_calc.c b/ext/opcache/zend_persist_calc.c index c638d66619d0f..42f188414208a 100644 --- a/ext/opcache/zend_persist_calc.c +++ b/ext/opcache/zend_persist_calc.c @@ -227,6 +227,16 @@ static void zend_persist_op_array_calc_ex(zend_op_array *op_array) } } + if (op_array->namespace_name) { + const zend_string *old_name = op_array->namespace_name; + ADD_INTERNED_STRING(op_array->namespace_name); + /* Remember old namespace name, so it can be released multiple times if shared. */ + if (op_array->namespace_name != old_name + && !zend_shared_alloc_get_xlat_entry(&op_array->namespace_name)) { + zend_shared_alloc_register_xlat_entry(&op_array->namespace_name, old_name); + } + } + if (op_array->scope) { if (zend_shared_alloc_get_xlat_entry(op_array->opcodes)) { /* already stored */ @@ -393,6 +403,12 @@ static void zend_persist_class_method_calc(zend_op_array *op_array) if (old_function_name) { zend_string_release_ex(old_function_name, 0); } + /* Same for namespace_name */ + zend_string *old_namespace_name = + zend_shared_alloc_get_xlat_entry(&old_op_array->namespace_name); + if (old_namespace_name) { + zend_string_release_ex(old_namespace_name, 0); + } } } diff --git a/ext/reflection/php_reflection.c b/ext/reflection/php_reflection.c index a86ce16feb407..df70c7d13e843 100644 --- a/ext/reflection/php_reflection.c +++ b/ext/reflection/php_reflection.c @@ -3558,6 +3558,13 @@ ZEND_METHOD(ReflectionMethod, isProtected) } /* }}} */ +/* {{{ Returns whether this method is namespace-private */ +ZEND_METHOD(ReflectionMethod, isNamespacePrivate) +{ + _function_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_NAMESPACE_PRIVATE); +} +/* }}} */ + /* {{{ Returns whether this function is deprecated */ ZEND_METHOD(ReflectionFunctionAbstract, isDeprecated) { @@ -5833,6 +5840,13 @@ ZEND_METHOD(ReflectionProperty, isProtected) } /* }}} */ +/* {{{ Returns whether this property is namespace-private */ +ZEND_METHOD(ReflectionProperty, isNamespacePrivate) +{ + _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_NAMESPACE_PRIVATE); +} +/* }}} */ + ZEND_METHOD(ReflectionProperty, isPrivateSet) { _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_PRIVATE_SET); @@ -5843,6 +5857,11 @@ ZEND_METHOD(ReflectionProperty, isProtectedSet) _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_PROTECTED_SET); } +ZEND_METHOD(ReflectionProperty, isNamespacePrivateSet) +{ + _property_check_flag(INTERNAL_FUNCTION_PARAM_PASSTHRU, ZEND_ACC_NAMESPACE_PRIVATE_SET); +} + /* {{{ Returns whether this property is static */ ZEND_METHOD(ReflectionProperty, isStatic) { diff --git a/ext/reflection/php_reflection.stub.php b/ext/reflection/php_reflection.stub.php index be372ac729912..2bcc4be17340f 100644 --- a/ext/reflection/php_reflection.stub.php +++ b/ext/reflection/php_reflection.stub.php @@ -193,6 +193,8 @@ public function isPrivate(): bool {} /** @tentative-return-type */ public function isProtected(): bool {} + public function isNamespacePrivate(): bool {} + /** @tentative-return-type */ public function isAbstract(): bool {} @@ -513,10 +515,14 @@ public function isPrivate(): bool {} /** @tentative-return-type */ public function isProtected(): bool {} + public function isNamespacePrivate(): bool {} + public function isPrivateSet(): bool {} public function isProtectedSet(): bool {} + public function isNamespacePrivateSet(): bool {} + /** @tentative-return-type */ public function isStatic(): bool {} diff --git a/ext/reflection/php_reflection_arginfo.h b/ext/reflection/php_reflection_arginfo.h index d1f1ffed0cfb6..b9c98d3bda47b 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 the .stub.php file instead. - * Stub hash: 576229f7a0c4afd2f8902db6ce87daa51256965e */ + * Stub hash: 02e8231e06f69efb10e575b3f3265daf1988589a */ ZEND_BEGIN_ARG_WITH_TENTATIVE_RETURN_TYPE_INFO_EX(arginfo_class_Reflection_getModifierNames, 0, 1, IS_ARRAY, 0) ZEND_ARG_TYPE_INFO(0, modifiers, IS_LONG, 0) @@ -149,6 +149,8 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionMethod_isProtected arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionMethod_isNamespacePrivate arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + #define arginfo_class_ReflectionMethod_isAbstract arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionMethod_isFinal arginfo_class_ReflectionFunctionAbstract_inNamespace @@ -419,10 +421,14 @@ ZEND_END_ARG_INFO() #define arginfo_class_ReflectionProperty_isProtected arginfo_class_ReflectionFunctionAbstract_inNamespace +#define arginfo_class_ReflectionProperty_isNamespacePrivate arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + #define arginfo_class_ReflectionProperty_isPrivateSet arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType #define arginfo_class_ReflectionProperty_isProtectedSet arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType +#define arginfo_class_ReflectionProperty_isNamespacePrivateSet arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType + #define arginfo_class_ReflectionProperty_isStatic arginfo_class_ReflectionFunctionAbstract_inNamespace #define arginfo_class_ReflectionProperty_isReadOnly arginfo_class_ReflectionFunctionAbstract_hasTentativeReturnType @@ -777,6 +783,7 @@ ZEND_METHOD(ReflectionMethod, __toString); ZEND_METHOD(ReflectionMethod, isPublic); ZEND_METHOD(ReflectionMethod, isPrivate); ZEND_METHOD(ReflectionMethod, isProtected); +ZEND_METHOD(ReflectionMethod, isNamespacePrivate); ZEND_METHOD(ReflectionMethod, isAbstract); ZEND_METHOD(ReflectionMethod, isFinal); ZEND_METHOD(ReflectionMethod, isConstructor); @@ -867,8 +874,10 @@ ZEND_METHOD(ReflectionProperty, isInitialized); ZEND_METHOD(ReflectionProperty, isPublic); ZEND_METHOD(ReflectionProperty, isPrivate); ZEND_METHOD(ReflectionProperty, isProtected); +ZEND_METHOD(ReflectionProperty, isNamespacePrivate); ZEND_METHOD(ReflectionProperty, isPrivateSet); ZEND_METHOD(ReflectionProperty, isProtectedSet); +ZEND_METHOD(ReflectionProperty, isNamespacePrivateSet); ZEND_METHOD(ReflectionProperty, isStatic); ZEND_METHOD(ReflectionProperty, isReadOnly); ZEND_METHOD(ReflectionProperty, isDefault); @@ -1065,6 +1074,7 @@ static const zend_function_entry class_ReflectionMethod_methods[] = { ZEND_ME(ReflectionMethod, isPublic, arginfo_class_ReflectionMethod_isPublic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionMethod, isPrivate, arginfo_class_ReflectionMethod_isPrivate, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionMethod, isProtected, arginfo_class_ReflectionMethod_isProtected, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionMethod, isNamespacePrivate, arginfo_class_ReflectionMethod_isNamespacePrivate, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionMethod, isAbstract, arginfo_class_ReflectionMethod_isAbstract, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionMethod, isFinal, arginfo_class_ReflectionMethod_isFinal, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionMethod, isConstructor, arginfo_class_ReflectionMethod_isConstructor, ZEND_ACC_PUBLIC) @@ -1170,8 +1180,10 @@ static const zend_function_entry class_ReflectionProperty_methods[] = { ZEND_ME(ReflectionProperty, isPublic, arginfo_class_ReflectionProperty_isPublic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isPrivate, arginfo_class_ReflectionProperty_isPrivate, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isProtected, arginfo_class_ReflectionProperty_isProtected, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isNamespacePrivate, arginfo_class_ReflectionProperty_isNamespacePrivate, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isPrivateSet, arginfo_class_ReflectionProperty_isPrivateSet, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isProtectedSet, arginfo_class_ReflectionProperty_isProtectedSet, ZEND_ACC_PUBLIC) + ZEND_ME(ReflectionProperty, isNamespacePrivateSet, arginfo_class_ReflectionProperty_isNamespacePrivateSet, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isStatic, arginfo_class_ReflectionProperty_isStatic, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isReadOnly, arginfo_class_ReflectionProperty_isReadOnly, ZEND_ACC_PUBLIC) ZEND_ME(ReflectionProperty, isDefault, arginfo_class_ReflectionProperty_isDefault, ZEND_ACC_PUBLIC) diff --git a/ext/spl/php_spl.c b/ext/spl/php_spl.c index 6de7a6d6635af..41c5e2cbf69c5 100644 --- a/ext/spl/php_spl.c +++ b/ext/spl/php_spl.c @@ -435,6 +435,9 @@ static zend_class_entry *spl_perform_autoload(zend_string *class_name, zend_stri func = emalloc(sizeof(zend_op_array)); memcpy(func, alfi->func_ptr, sizeof(zend_op_array)); zend_string_addref(func->op_array.function_name); + if (func->op_array.namespace_name) { + zend_string_addref(func->op_array.namespace_name); + } } zval param; @@ -545,6 +548,7 @@ PHP_FUNCTION(spl_autoload_register) memcpy(copy, alfi->func_ptr, sizeof(zend_op_array)); alfi->func_ptr->common.function_name = NULL; + alfi->func_ptr->op_array.namespace_name = NULL; alfi->func_ptr = copy; } } else { diff --git a/ext/tokenizer/tokenizer_data.c b/ext/tokenizer/tokenizer_data.c index 0900c51d3d95a..a48bca5fb6701 100644 --- a/ext/tokenizer/tokenizer_data.c +++ b/ext/tokenizer/tokenizer_data.c @@ -95,6 +95,8 @@ char *get_token_type_name(int token_type) case T_PRIVATE_SET: return "T_PRIVATE_SET"; case T_PROTECTED_SET: return "T_PROTECTED_SET"; case T_PUBLIC_SET: return "T_PUBLIC_SET"; + case T_PRIVATE_NAMESPACE: return "T_PRIVATE_NAMESPACE"; + case T_PRIVATE_NAMESPACE_SET: return "T_PRIVATE_NAMESPACE_SET"; case T_READONLY: return "T_READONLY"; case T_VAR: return "T_VAR"; case T_UNSET: return "T_UNSET"; diff --git a/ext/tokenizer/tokenizer_data.stub.php b/ext/tokenizer/tokenizer_data.stub.php index 57c8edad8acb6..cffef55533bbc 100644 --- a/ext/tokenizer/tokenizer_data.stub.php +++ b/ext/tokenizer/tokenizer_data.stub.php @@ -352,6 +352,16 @@ * @cvalue T_PUBLIC_SET */ const T_PUBLIC_SET = UNKNOWN; +/** + * @var int + * @cvalue T_PRIVATE_NAMESPACE + */ +const T_PRIVATE_NAMESPACE = UNKNOWN; +/** + * @var int + * @cvalue T_PRIVATE_NAMESPACE_SET + */ +const T_PRIVATE_NAMESPACE_SET = UNKNOWN; /** * @var int * @cvalue T_READONLY diff --git a/ext/tokenizer/tokenizer_data_arginfo.h b/ext/tokenizer/tokenizer_data_arginfo.h index 3a3cdaa468133..3c7c3a14aa250 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 the .stub.php file instead. - * Stub hash: c5235344b7c651d27c2c33c90696a418a9c96837 */ + * Stub hash: 68bff3a7ddb4b64f1803d058accff49bf54324c6 */ static void register_tokenizer_data_symbols(int module_number) { @@ -73,6 +73,8 @@ static void register_tokenizer_data_symbols(int module_number) REGISTER_LONG_CONSTANT("T_PRIVATE_SET", T_PRIVATE_SET, CONST_PERSISTENT); 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_PRIVATE_NAMESPACE", T_PRIVATE_NAMESPACE, CONST_PERSISTENT); + REGISTER_LONG_CONSTANT("T_PRIVATE_NAMESPACE_SET", T_PRIVATE_NAMESPACE_SET, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_READONLY", T_READONLY, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_VAR", T_VAR, CONST_PERSISTENT); REGISTER_LONG_CONSTANT("T_UNSET", T_UNSET, CONST_PERSISTENT);