From 5f75c5b48c3a627e72627607a02ac447882fed95 Mon Sep 17 00:00:00 2001 From: tianfenghan Date: Thu, 3 Sep 2026 19:48:23 +0800 Subject: [PATCH] test(enum): add comprehensive enum method declaration rules tests - Add test cases for forbidden magic methods in enums (__construct, __destruct, __clone, etc.) - Create tests for trait-injected forbidden methods failing during composition - Add tests for trait aliases to forbidden methods detection - Verify __call, __callStatic and __invoke remain allowed in enums - Include proper setup and teardown for temporary test directories - Provide data provider for forbidden magic method testing scenarios feat(enum): enforce forbidden magic method restrictions on enums - Add ENUM_FORBIDDEN_MAGIC_METHODS constant with restricted methods list - Implement assertEnumMayIncludeMethod to validate enum method declarations - Prevent forbidden magic methods from being added to enums during preprocessing - Share validation logic between direct method declarations and trait composition - Add --- .../src/EnumMethodDeclarationRulesTest.php | 193 ++++++++++++++++++ src/Preprocessor.php | 41 ++++ src/Translator.php | 1 + 3 files changed, 235 insertions(+) create mode 100644 phpunit/src/EnumMethodDeclarationRulesTest.php diff --git a/phpunit/src/EnumMethodDeclarationRulesTest.php b/phpunit/src/EnumMethodDeclarationRulesTest.php new file mode 100644 index 00000000..fd4ebb14 --- /dev/null +++ b/phpunit/src/EnumMethodDeclarationRulesTest.php @@ -0,0 +1,193 @@ +testRoot = sys_get_temp_dir() . '/typephp-enum-method-' . bin2hex(random_bytes(8)); + mkdir($this->testRoot, 0777, true); + } + + protected function tearDown(): void + { + if (!is_dir($this->testRoot)) { + return; + } + + $iterator = new RecursiveIteratorIterator( + new RecursiveDirectoryIterator($this->testRoot, FilesystemIterator::SKIP_DOTS), + RecursiveIteratorIterator::CHILD_FIRST, + ); + foreach ($iterator as $entry) { + if ($entry->isDir()) { + rmdir($entry->getPathname()); + } else { + unlink($entry->getPathname()); + } + } + rmdir($this->testRoot); + } + + /** + * @dataProvider forbiddenMagicMethodProvider + */ + public function testForbiddenMagicMethodFailsDuringPrepare(string $method): void + { + $source = <<compilerFor($source); + + $this->expectException(TestError::class); + $this->expectExceptionMessage("Enum `Suit` cannot include magic method `{$method}`"); + $compiler->prepareFile($file); + } + + public static function forbiddenMagicMethodProvider(): iterable + { + foreach ([ + '__construct', + '__destruct', + '__clone', + '__get', + '__set', + '__unset', + '__isset', + '__sleep', + '__wakeup', + '__set_state', + '__serialize', + '__unserialize', + '__ToString', + '__debugInfo', + ] as $method) { + yield $method => [$method]; + } + } + + public function testTraitInjectedForbiddenMethodFailsDuringComposition(): void + { + $source = <<<'PHP' +compilerFor($source); + $compiler->prepareFile($file); + + $this->expectException(TestError::class); + $this->expectExceptionMessage('Enum `Suit` cannot include magic method `__construct`'); + $compiler->composeTraitDeclarations([$file]); + } + + public function testTraitAliasToForbiddenMethodFailsDuringComposition(): void + { + $source = <<<'PHP' +compilerFor($source); + $compiler->prepareFile($file); + + $this->expectException(TestError::class); + $this->expectExceptionMessage('Enum `Suit` cannot include magic method `__destruct`'); + $compiler->composeTraitDeclarations([$file]); + } + + public function testCallCallStaticAndInvokeRemainAllowed(): void + { + $source = <<<'PHP' +compilerFor($source); + $compiler->prepareFile($file); + $compiler->convertFile($file); + + self::assertFileExists($compiler->getCppFile($file)); + } + + /** @return array{CompilerTest, string} */ + private function compilerFor(string $source): array + { + $file = $this->testRoot . '/program.php'; + file_put_contents($file, $source); + + global $translator; + $compiler = CompilerTest::create($this->testRoot); + $translator = $compiler; + $compiler->addFiles([$file]); + + return [$compiler, $file]; + } +} diff --git a/src/Preprocessor.php b/src/Preprocessor.php index e549f932..dd84c317 100644 --- a/src/Preprocessor.php +++ b/src/Preprocessor.php @@ -46,8 +46,48 @@ use PhpParser\NodeVisitor\NameResolver; class Preprocessor extends CompilerBase { + /** + * Magic methods forbidden on PHP enums. Enum cases are runtime-managed + * singletons, so construction, cloning, destruction, serialization and + * magic property/string/debug handlers cannot be supplied by user code. + * __call, __callStatic and __invoke remain valid enum methods. + */ + private const array ENUM_FORBIDDEN_MAGIC_METHODS = [ + '__construct' => true, + '__destruct' => true, + '__clone' => true, + '__get' => true, + '__set' => true, + '__unset' => true, + '__isset' => true, + '__sleep' => true, + '__wakeup' => true, + '__set_state' => true, + '__serialize' => true, + '__unserialize' => true, + '__tostring' => true, + '__debuginfo' => true, + ]; + protected string $targetName = 'app'; + /** + * Validate every method that will become part of an enum. This is shared + * with the Trait-composition phase so a Trait method or alias cannot defer + * the error to Zend class registration at runtime. + */ + protected function assertEnumMayIncludeMethod(Node $node, string $name): void + { + if (!$this->classDef->enum || !isset(self::ENUM_FORBIDDEN_MAGIC_METHODS[strtolower($name)])) { + return; + } + + $this->fatalError( + $node, + "Enum `{$this->classDef->getNamespacedName(false)}` cannot include magic method `{$name}`", + ); + } + /** * Discover Native class names before parsing any signatures or fields. * @@ -2328,6 +2368,7 @@ class Preprocessor extends CompilerBase $this->method = $name; $this->assertKeywordMethodMayBeDeclared($v, $name, $this->classDef->nativeObject); $this->assertNativeMagicMethodSupported($v, $name); + $this->assertEnumMayIncludeMethod($v, $name); $flags = $this->parseModifiers($v->flags); $abstract = $flags & Modifiers::ABSTRACT; if ($this->classDef->nativeObject && ($flags & Modifiers::STATIC)) { diff --git a/src/Translator.php b/src/Translator.php index b6307b69..396c2c46 100644 --- a/src/Translator.php +++ b/src/Translator.php @@ -6652,6 +6652,7 @@ CODE; { $name = $methodStmt->name->toString(); $this->assertNativeMagicMethodSupported($methodStmt, $name); + $this->assertEnumMayIncludeMethod($methodStmt, $name); if ($this->classDef->hasMethod($name) || $this->classDef->hasAbstractMethod($name)) { return; }