diff --git a/phpunit/code/compiler_api/extension_clean_maps.php b/phpunit/code/compiler_api/extension_clean_maps.php new file mode 100644 index 00000000..74dd0adb --- /dev/null +++ b/phpunit/code/compiler_api/extension_clean_maps.php @@ -0,0 +1,6 @@ +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 ', $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'); diff --git a/phpunit/src/InheritanceErrorTest.php b/phpunit/src/InheritanceErrorTest.php index ab54eac6..dcc38a33 100644 --- a/phpunit/src/InheritanceErrorTest.php +++ b/phpunit/src/InheritanceErrorTest.php @@ -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'); diff --git a/src/Php/CompilerBase.php b/src/Php/CompilerBase.php index 4c928170..73608a11 100644 --- a/src/Php/CompilerBase.php +++ b/src/Php/CompilerBase.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; diff --git a/src/Php/Translator.php b/src/Php/Translator.php index cfe04d6c..70ab0473 100644 --- a/src/Php/Translator.php +++ b/src/Php/Translator.php @@ -778,7 +778,8 @@ class Translator extends Preprocessor $this->genClassCeList(); $this->indentLevel++; - $code = $this->genIncludeHeaderFiles(); + $code = '#include ' . 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}`");