fix(php): 修复继承访问控制和扩展清理逻辑

- 修改 canAccessProtectedProperty 替代 isInheritedFrom 处理保护属性访问
- 更新扩展模式下的映射表清理逻辑,使用 C++ 样式初始化替代 PHP 数组赋值
- 修复继承检查中私有成员重声明的处理,跳过私有父类属性/常量的兼容性检查
- 优化可见性检查逻辑,支持可见性放宽但阻止私有成员重声明
- 添加对父类可访问子类保护方法和常量的支持测试
- 新增属性和常量可见性放宽及私有成员重声明的测试用例
- 添加扩展清理映射表的运行时测试验证
pull/5/head
韩天峰 2 months ago
parent 252b112405
commit 8231208eaf
  1. 6
      phpunit/code/compiler_api/extension_clean_maps.php
  2. 11
      phpunit/code/inheritance_const_visibility_widen.php
  3. 11
      phpunit/code/inheritance_private_const_redeclare.php
  4. 11
      phpunit/code/inheritance_private_prop_redeclare.php
  5. 11
      phpunit/code/inheritance_prop_visibility_widen.php
  6. 14
      phpunit/code/inheritance_protected_child_const_access.php
  7. 16
      phpunit/code/inheritance_protected_child_method_access.php
  8. 26
      phpunit/src/CompilerBaseApiTest.php
  9. 30
      phpunit/src/InheritanceErrorTest.php
  10. 5
      src/Php/CompilerBase.php
  11. 19
      src/Php/Translator.php

@ -0,0 +1,6 @@
<?php
function main(): void
{
echo "ok\n";
}

@ -0,0 +1,11 @@
<?php
class InheritanceConstVisibilityWidenParent
{
protected const VALUE = 1;
}
class InheritanceConstVisibilityWidenChild extends InheritanceConstVisibilityWidenParent
{
public const VALUE = 2;
}

@ -0,0 +1,11 @@
<?php
class InheritancePrivateConstRedeclareParent
{
private const int VALUE = 1;
}
class InheritancePrivateConstRedeclareChild extends InheritancePrivateConstRedeclareParent
{
public const string VALUE = 'child';
}

@ -0,0 +1,11 @@
<?php
class InheritancePrivatePropRedeclareParent
{
private int $value = 1;
}
class InheritancePrivatePropRedeclareChild extends InheritancePrivatePropRedeclareParent
{
private string $value = 'child';
}

@ -0,0 +1,11 @@
<?php
class InheritancePropVisibilityWidenParent
{
protected int $value = 1;
}
class InheritancePropVisibilityWidenChild extends InheritancePropVisibilityWidenParent
{
public int $value = 2;
}

@ -0,0 +1,14 @@
<?php
class InheritanceProtectedChildConstAccessParent
{
public function readChild(): int
{
return InheritanceProtectedChildConstAccessChild::VALUE;
}
}
class InheritanceProtectedChildConstAccessChild extends InheritanceProtectedChildConstAccessParent
{
protected const VALUE = 1;
}

@ -0,0 +1,16 @@
<?php
class InheritanceProtectedChildMethodAccessParent
{
public function callChild(): void
{
InheritanceProtectedChildMethodAccessChild::run();
}
}
class InheritanceProtectedChildMethodAccessChild extends InheritanceProtectedChildMethodAccessParent
{
protected static function run(): void
{
}
}

@ -422,6 +422,32 @@ YAML);
$this->assertArrayNotHasKey('cxxflags', $options);
}
public function testExtensionCleanClearsRuntimeMapsWithValidCpp(): void
{
global $translator;
$compiler = CompilerTest::create(ROOT_PATH);
$translator = $compiler;
$ref = new \ReflectionClass($compiler);
$buildMode = $ref->getProperty('buildMode');
$buildMode->setAccessible(true);
$buildMode->setValue($compiler, CompilerBase::BUILD_MODE_EXT);
$testFile = ROOT_PATH . '/phpunit/code/compiler_api/extension_clean_maps.php';
$compiler->addFiles([$testFile]);
$compiler->prepareFile($testFile);
$compiler->convertFile($testFile);
$extensionFile = $compiler->genExtension();
$code = file_get_contents($extensionFile);
$this->assertStringContainsString('#include <cstring>', $code);
$this->assertStringContainsString('std::memset(php_func_map, 0, sizeof(php_func_map));', $code);
$this->assertStringContainsString('std::memset(php_class_map, 0, sizeof(php_class_map));', $code);
$this->assertStringContainsString('std::memset(php_property_map, 0, sizeof(php_property_map));', $code);
$this->assertStringNotContainsString('func_map = {}', $code);
$this->assertStringNotContainsString('class_map = {}', $code);
$this->assertStringNotContainsString('property_map = {}', $code);
}
public function testObjectiveCppCompileCommandOptionsKeepCppOptions(): void
{
$this->setPropertyValue('cxxStd', 'c++20');

@ -78,6 +78,16 @@ class InheritanceErrorTest extends TestCase
$this->assertCompiles('inheritance_error_visibility.php');
}
public function testParentScopeMayAccessChildProtectedMethod()
{
$this->assertCompiles('inheritance_protected_child_method_access.php');
}
public function testParentScopeMayAccessChildProtectedConstant()
{
$this->assertCompiles('inheritance_protected_child_const_access.php');
}
public function testMethodStaticMismatch()
{
$this->exec('must be compatible', 'inheritance_error_static.php');
@ -123,6 +133,16 @@ class InheritanceErrorTest extends TestCase
$this->exec('must be compatible', 'inheritance_error_prop_visibility.php');
}
public function testPropertyVisibilityMayBeWidened()
{
$this->assertCompiles('inheritance_prop_visibility_widen.php');
}
public function testPrivateParentPropertyMayBeRedeclared()
{
$this->assertCompiles('inheritance_private_prop_redeclare.php');
}
public function testConstantTypeMismatch()
{
$this->exec('must be compatible', 'inheritance_error_const_type.php');
@ -133,6 +153,16 @@ class InheritanceErrorTest extends TestCase
$this->exec('must be compatible', 'inheritance_error_const_visibility.php');
}
public function testConstantVisibilityMayBeWidened()
{
$this->assertCompiles('inheritance_const_visibility_widen.php');
}
public function testPrivateParentConstantMayBeRedeclared()
{
$this->assertCompiles('inheritance_private_const_redeclare.php');
}
public function testPropertyReadonlyMismatch()
{
$this->exec('must be compatible', 'inheritance_error_prop_readonly.php');

@ -5483,7 +5483,10 @@ class CompilerBase extends \PhpAot\Core\Translator
if (!$this->classDef) {
return false;
}
return $this->isInheritedFrom($this->classDef->getNamespacedName(false), $classDef->getNamespacedName(false));
return $this->canAccessProtectedProperty(
$this->classDef->getNamespacedName(false),
$classDef->getNamespacedName(false)
);
}
// 类外部调用,只允许调用 public 方法
return true;

@ -778,7 +778,8 @@ class Translator extends Preprocessor
$this->genClassCeList();
$this->indentLevel++;
$code = $this->genIncludeHeaderFiles();
$code = '#include <cstring>' . PHP_EOL;
$code .= $this->genIncludeHeaderFiles();
if ($this->isBuildModeBin()) {
$cliHeaders = [
@ -1018,9 +1019,9 @@ CODE;
// 扩展模式,需要在 RSHUTDOWN 阶段中清理函数、类、属性表
if ($this->isBuildModeExt()) {
$code .= self::FUNC_MAP." = {}\n";
$code .= self::CLASS_MAP." = {}\n";
$code .= self::PROP_MAP." = {}\n";
$code .= 'std::memset(' . self::PREFIX . self::FUNC_MAP . ', 0, sizeof(' . self::PREFIX . self::FUNC_MAP . '));' . PHP_EOL;
$code .= 'std::memset(' . self::PREFIX . self::CLASS_MAP . ', 0, sizeof(' . self::PREFIX . self::CLASS_MAP . '));' . PHP_EOL;
$code .= 'std::memset(' . self::PREFIX . self::PROP_MAP . ', 0, sizeof(' . self::PREFIX . self::PROP_MAP . '));' . PHP_EOL;
}
$code .= '}' . PHP_EOL . PHP_EOL;
@ -3421,12 +3422,15 @@ CODE;
foreach ($this->classDef->properties as $name => $childProp) {
if ($chainNode->hasProperty($name)) {
$parentProp = $chainNode->getProperty($name);
if ($parentProp->flags & Modifiers::PRIVATE) {
continue;
}
if ($childProp->type !== $parentProp->type || $childProp->class !== $parentProp->class) {
$this->fatalError($classStmt,
"Declaration of `{$className}::\${$name}` must be compatible " .
"with `{$parentClass}::\${$name}`");
}
if (($childProp->flags & Modifiers::VISIBILITY_MASK) !== ($parentProp->flags & Modifiers::VISIBILITY_MASK)) {
if ($this->getVisibilityRank($childProp->flags) < $this->getVisibilityRank($parentProp->flags)) {
$this->fatalError($classStmt,
"Declaration of `{$className}::\${$name}` must be compatible " .
"with `{$parentClass}::\${$name}`");
@ -3455,12 +3459,15 @@ CODE;
foreach ($this->classDef->constants as $name => $childConst) {
if ($chainNode->hasConstant($name)) {
$parentConst = $chainNode->getConstant($name);
if ($parentConst->flags & Modifiers::PRIVATE) {
continue;
}
if ($childConst->type !== $parentConst->type || $childConst->class !== $parentConst->class) {
$this->fatalError($classStmt,
"Declaration of `{$className}::{$name}` must be compatible " .
"with `{$parentClass}::{$name}`");
}
if (($childConst->flags & Modifiers::VISIBILITY_MASK) !== ($parentConst->flags & Modifiers::VISIBILITY_MASK)) {
if ($this->getVisibilityRank($childConst->flags) < $this->getVisibilityRank($parentConst->flags)) {
$this->fatalError($classStmt,
"Declaration of `{$className}::{$name}` must be compatible " .
"with `{$parentClass}::{$name}`");

Loading…
Cancel
Save