From 19a1005bc30df8c0e4a1e5ebc70a62fc764621ee Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Mon, 12 Dec 2022 03:03:20 -0400 Subject: [PATCH 1/2] Forbid most magic methods on enums Fixes vimeo/psalm#8889 Additionally this fixes case-sensitivity of MethodSignatureMustOmitReturnType issue Fixes vimeo/psalm#8888 --- config.xsd | 1 + docs/running_psalm/error_levels.md | 1 + docs/running_psalm/issues.md | 1 + .../running_psalm/issues/InvalidEnumMethod.md | 15 +++++++ .../Analyzer/FunctionLikeAnalyzer.php | 4 ++ .../Internal/Analyzer/MethodAnalyzer.php | 45 +++++++++++++++++-- src/Psalm/Issue/InvalidEnumMethod.php | 9 ++++ tests/EnumTest.php | 11 +++++ 8 files changed, 84 insertions(+), 3 deletions(-) create mode 100644 docs/running_psalm/issues/InvalidEnumMethod.md create mode 100644 src/Psalm/Issue/InvalidEnumMethod.php diff --git a/config.xsd b/config.xsd index 00caa5508af..5b4744f49cb 100644 --- a/config.xsd +++ b/config.xsd @@ -264,6 +264,7 @@ + diff --git a/docs/running_psalm/error_levels.md b/docs/running_psalm/error_levels.md index 9eeb12e7f72..a0799b89277 100644 --- a/docs/running_psalm/error_levels.md +++ b/docs/running_psalm/error_levels.md @@ -45,6 +45,7 @@ Level 5 and above allows a more non-verifiable code, and higher levels are even - [InaccessibleProperty](issues/InaccessibleProperty.md) - [InterfaceInstantiation](issues/InterfaceInstantiation.md) - [InvalidAttribute](issues/InvalidAttribute.md) + - [InvalidEnumMethod](issues/InvalidEnumMethod.md) - [InvalidExtendClass](issues/InvalidExtendClass.md) - [InvalidGlobal](issues/InvalidGlobal.md) - [InvalidParamDefault](issues/InvalidParamDefault.md) diff --git a/docs/running_psalm/issues.md b/docs/running_psalm/issues.md index 6729a85fede..d84f2bf3c2b 100644 --- a/docs/running_psalm/issues.md +++ b/docs/running_psalm/issues.md @@ -69,6 +69,7 @@ - [InvalidDocblockParamName](issues/InvalidDocblockParamName.md) - [InvalidEnumBackingType](issues/InvalidEnumBackingType.md) - [InvalidEnumCaseValue](issues/InvalidEnumCaseValue.md) + - [InvalidEnumMethod](issues/InvalidEnumMethod.md) - [InvalidExtendClass](issues/InvalidExtendClass.md) - [InvalidFalsableReturnType](issues/InvalidFalsableReturnType.md) - [InvalidFunctionCall](issues/InvalidFunctionCall.md) diff --git a/docs/running_psalm/issues/InvalidEnumMethod.md b/docs/running_psalm/issues/InvalidEnumMethod.md new file mode 100644 index 00000000000..575cd6b77f3 --- /dev/null +++ b/docs/running_psalm/issues/InvalidEnumMethod.md @@ -0,0 +1,15 @@ +# InvalidEnumMethod + +Enums may not define most of the magic methods like `__get`, `__toString`, etc. + +```php +is_enum) { + MethodAnalyzer::checkForbiddenEnumMethod($storage); + } + if (!$context->calling_method_id || !$context->collect_initializations) { $context->calling_method_id = strtolower((string)$method_id); } diff --git a/src/Psalm/Internal/Analyzer/MethodAnalyzer.php b/src/Psalm/Internal/Analyzer/MethodAnalyzer.php index 9ade34375ff..e11abef5195 100644 --- a/src/Psalm/Internal/Analyzer/MethodAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/MethodAnalyzer.php @@ -9,6 +9,7 @@ use Psalm\Context; use Psalm\Internal\Codebase\InternalCallMapHandler; use Psalm\Internal\MethodIdentifier; +use Psalm\Issue\InvalidEnumMethod; use Psalm\Issue\InvalidStaticInvocation; use Psalm\Issue\MethodSignatureMustOmitReturnType; use Psalm\Issue\NonStaticSelfCall; @@ -27,6 +28,24 @@ */ class MethodAnalyzer extends FunctionLikeAnalyzer { + // https://github.com/php/php-src/blob/a83923044c48982c80804ae1b45e761c271966d3/Zend/zend_enum.c#L77-L95 + private const FORBIDDEN_ENUM_METHODS = [ + '__construct', + '__destruct', + '__clone', + '__get', + '__set', + '__unset', + '__isset', + '__tostring', + '__debuginfo', + '__serialize', + '__unserialize', + '__sleep', + '__wakeup', + '__set_state', + ]; + /** @psalm-external-mutation-free */ public function __construct( PhpParser\Node\Stmt\ClassMethod $function, @@ -266,13 +285,17 @@ public static function checkMethodSignatureMustOmitReturnType( return; } - $cased_method_name = $method_storage->cased_name; + if ($method_storage->cased_name === null) { + return; + } + + $method_name_lc = strtolower($method_storage->cased_name); $methodsOfInterest = ['__clone', '__construct', '__destruct']; - if (in_array($cased_method_name, $methodsOfInterest)) { + if (in_array($method_name_lc, $methodsOfInterest, true)) { IssueBuffer::maybeAdd( new MethodSignatureMustOmitReturnType( - 'Method ' . $cased_method_name . ' must not declare a return type', + 'Method ' . $method_storage->cased_name . ' must not declare a return type', $code_location ) ); @@ -288,4 +311,20 @@ public function getMethodId(?string $context_self = null): MethodIdentifier strtolower($function_name) ); } + + public static function checkForbiddenEnumMethod(MethodStorage $method_storage): void + { + if ($method_storage->cased_name === null || $method_storage->location === null) { + return; + } + + $method_name_lc = strtolower($method_storage->cased_name); + if (in_array($method_name_lc, self::FORBIDDEN_ENUM_METHODS, true)) { + IssueBuffer::maybeAdd(new InvalidEnumMethod( + 'Enums cannot define ' . $method_storage->cased_name, + $method_storage->location, + $method_storage->defining_fqcln . '::' . $method_storage->cased_name + )); + } + } } diff --git a/src/Psalm/Issue/InvalidEnumMethod.php b/src/Psalm/Issue/InvalidEnumMethod.php new file mode 100644 index 00000000000..7b0d3558103 --- /dev/null +++ b/src/Psalm/Issue/InvalidEnumMethod.php @@ -0,0 +1,9 @@ + [], 'php_version' => '8.1', ], + 'forbiddenMethod' => [ + 'code' => ' 'InvalidEnumMethod', + 'ignored_issues' => [], + 'php_version' => '8.1', + ], ]; } } From 9db0eb3e03a809564c87eec3f80938628f0c2c5e Mon Sep 17 00:00:00 2001 From: Bruce Weirdan Date: Mon, 12 Dec 2022 03:12:55 -0400 Subject: [PATCH 2/2] InvalidEnumMethod requires PHP8.1+ --- tests/DocumentationTest.php | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/DocumentationTest.php b/tests/DocumentationTest.php index bb9857c9a08..3811396bb66 100644 --- a/tests/DocumentationTest.php +++ b/tests/DocumentationTest.php @@ -310,6 +310,7 @@ public function providerInvalidCodeParse(): array case 'DuplicateEnumCaseValue': case 'InvalidEnumBackingType': case 'InvalidEnumCaseValue': + case 'InvalidEnumMethod': case 'NoEnumProperties': case 'OverriddenFinalConstant': $php_version = '8.1'; @@ -436,7 +437,7 @@ public function testIssuesIndex(): void throw new UnexpectedValueException("Issues index not found"); } - $issues_index_contents = file($issues_index, FILE_IGNORE_NEW_LINES|FILE_SKIP_EMPTY_LINES); + $issues_index_contents = file($issues_index, FILE_IGNORE_NEW_LINES | FILE_SKIP_EMPTY_LINES); if ($issues_index_contents === false) { throw new UnexpectedValueException("Issues index returned false"); }