From e2d1e83b8715fd2e8f4a3811cf7174d9f9ea25a0 Mon Sep 17 00:00:00 2001 From: robchett Date: Tue, 17 Oct 2023 18:49:28 +0100 Subject: [PATCH] Fix memory explosion with calls to method_exists --- .../Method/AtomicMethodCallAnalysisResult.php | 2 +- .../Call/Method/AtomicMethodCallAnalyzer.php | 9 +++++---- .../Method/ExistingAtomicMethodCallAnalyzer.php | 3 ++- .../Call/Method/MissingMethodCallHandler.php | 10 +++++----- tests/MethodCallTest.php | 16 ++++++++++++++++ 5 files changed, 29 insertions(+), 11 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php index e83742e070b..94703207fc6 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalysisResult.php @@ -26,7 +26,7 @@ class AtomicMethodCallAnalysisResult public array $invalid_method_call_types = []; /** - * @var array + * @var array */ public array $existent_method_ids = []; diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php index 4e6d3188211..64567213200 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/AtomicMethodCallAnalyzer.php @@ -338,7 +338,7 @@ public static function analyze( $all_intersection_return_type = null; $all_intersection_existent_method_ids = []; - // insersection types are also fun, they also complicate matters + // intersection types are also fun, they also complicate matters if ($intersection_types) { [$all_intersection_return_type, $all_intersection_existent_method_ids] = self::getIntersectionReturnType( @@ -525,7 +525,7 @@ public static function analyze( /** * @param TNamedObject|TTemplateParam $lhs_type_part * @param array $intersection_types - * @return array{?Union, array} + * @return array{?Union, array} */ private static function getIntersectionReturnType( StatementsAnalyzer $statements_analyzer, @@ -646,7 +646,8 @@ private static function handleInvalidClass( && $stmt->name instanceof PhpParser\Node\Identifier && isset($lhs_type_part->methods[strtolower($stmt->name->name)]) ) { - $result->existent_method_ids[] = $lhs_type_part->methods[strtolower($stmt->name->name)]; + $method_id = $lhs_type_part->methods[strtolower($stmt->name->name)]; + $result->existent_method_ids[$method_id] = true; } elseif (!$is_intersection) { if ($stmt->name instanceof PhpParser\Node\Identifier) { $codebase->analyzer->addMixedMemberName( @@ -915,7 +916,7 @@ private static function handleCallableObject( ?TemplateResult $inferred_template_result = null ): void { $method_id = 'object::__invoke'; - $result->existent_method_ids[] = $method_id; + $result->existent_method_ids[$method_id] = true; $result->has_valid_method_call_type = true; if ($lhs_type_part_callable !== null) { diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php index 35fda473155..05210818b64 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/ExistingAtomicMethodCallAnalyzer.php @@ -87,7 +87,8 @@ public static function analyze( $cased_method_id = $fq_class_name . '::' . $stmt_name->name; - $result->existent_method_ids[] = $method_id->__toString(); + + $result->existent_method_ids[$method_id->__toString()] = true; if ($context->collect_initializations && $context->calling_method_id) { [$calling_method_class] = explode('::', $context->calling_method_id); diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php index 8e9346d803d..1f287b98d57 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/Call/Method/MissingMethodCallHandler.php @@ -52,7 +52,7 @@ public static function handleMagicMethod( if ($stmt->isFirstClassCallable()) { if (isset($class_storage->pseudo_methods[$method_name_lc])) { $result->has_valid_method_call_type = true; - $result->existent_method_ids[] = $method_id->__toString(); + $result->existent_method_ids[$method_id->__toString()] = true; $result->return_type = self::createFirstClassCallableReturnType( $class_storage->pseudo_methods[$method_name_lc], ); @@ -110,7 +110,7 @@ public static function handleMagicMethod( if ($found_method_and_class_storage) { $result->has_valid_method_call_type = true; - $result->existent_method_ids[] = $method_id->__toString(); + $result->existent_method_ids[$method_id->__toString()] = true; [$pseudo_method_storage, $defining_class_storage] = $found_method_and_class_storage; @@ -198,7 +198,7 @@ public static function handleMagicMethod( } $result->has_valid_method_call_type = true; - $result->existent_method_ids[] = $method_id->__toString(); + $result->existent_method_ids[$method_id->__toString()] = true; $array_values = array_map( static fn(PhpParser\Node\Arg $arg): PhpParser\Node\Expr\ArrayItem => new VirtualArrayItem( @@ -235,7 +235,7 @@ public static function handleMagicMethod( } /** - * @param array $all_intersection_existent_method_ids + * @param array $all_intersection_existent_method_ids */ public static function handleMissingOrMagicMethod( StatementsAnalyzer $statements_analyzer, @@ -267,7 +267,7 @@ public static function handleMissingOrMagicMethod( && $found_method_and_class_storage ) { $result->has_valid_method_call_type = true; - $result->existent_method_ids[] = $method_id->__toString(); + $result->existent_method_ids[$method_id->__toString()] = true; [$pseudo_method_storage, $defining_class_storage] = $found_method_and_class_storage; diff --git a/tests/MethodCallTest.php b/tests/MethodCallTest.php index 1f00196809e..9b8ba5b72b2 100644 --- a/tests/MethodCallTest.php +++ b/tests/MethodCallTest.php @@ -556,6 +556,22 @@ function foo(DateTime $d1, DateTime $d2) : void { ); }', ], + 'methodExistsDoesntExhaustMemory' => [ + 'code' => 'a() : []; + method_exists($c, \'b\') ? $c->b() : []; + method_exists($c, \'c\') ? $c->c() : []; + method_exists($c, \'d\') ? $c->d() : []; + method_exists($c, \'e\') ? $c->e() : []; + method_exists($c, \'f\') ? $c->f() : []; + method_exists($c, \'g\') ? $c->g() : []; + method_exists($c, \'h\') ? $c->h() : []; + method_exists($c, \'i\') ? $c->i() : []; + }', + ], 'callMethodAfterCheckingExistence' => [ 'code' => '