From 0a8bb321152bf9d42179471ce572fe6878378d46 Mon Sep 17 00:00:00 2001 From: Brown Date: Mon, 24 Feb 2020 14:50:34 -0500 Subject: [PATCH] Fix #2866 - prevent use of impure __toString via concatenation in pure contexts --- .../Expression/BinaryOpAnalyzer.php | 69 +++++++++++++++++-- tests/PureAnnotationTest.php | 19 ++++- 2 files changed, 81 insertions(+), 7 deletions(-) diff --git a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php index e961e56a612..d7056a934fd 100644 --- a/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php +++ b/src/Psalm/Internal/Analyzer/Statements/Expression/BinaryOpAnalyzer.php @@ -929,6 +929,7 @@ function (\Psalm\Internal\Clause $c) use ($mixed_var_ids) { if ($stmt instanceof PhpParser\Node\Expr\BinaryOp\Equal && $stmt_left_type && $stmt_right_type + && $context->mutation_free ) { if ($stmt_left_type->hasString() && $stmt_right_type->hasObjectType()) { foreach ($stmt_right_type->getAtomicTypes() as $atomic_type) { @@ -944,9 +945,7 @@ function (\Psalm\Internal\Clause $c) use ($mixed_var_ids) { continue; } - if ($context->mutation_free - && !$storage->mutation_free - ) { + if (!$storage->mutation_free) { if (IssueBuffer::accepts( new ImpureMethodCall( 'Cannot call a possibly-mutating method ' @@ -974,9 +973,7 @@ function (\Psalm\Internal\Clause $c) use ($mixed_var_ids) { continue; } - if ($context->mutation_free - && !$storage->mutation_free - ) { + if (!$storage->mutation_free) { if (IssueBuffer::accepts( new ImpureMethodCall( 'Cannot call a possibly-mutating method ' @@ -1834,6 +1831,36 @@ public static function analyzeConcatOp( // fall through } } + + if ($context->mutation_free) { + foreach ($left_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof TNamedObject) { + try { + $storage = $codebase->methods->getStorage( + new \Psalm\Internal\MethodIdentifier( + $atomic_type->value, + '__tostring' + ) + ); + } catch (\UnexpectedValueException $e) { + continue; + } + + if (!$storage->mutation_free) { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call a possibly-mutating method ' + . $atomic_type->value . '::__toString from a pure context', + new CodeLocation($statements_analyzer, $left) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } } foreach ($right_type->getAtomicTypes() as $right_type_part) { @@ -1880,6 +1907,36 @@ public static function analyzeConcatOp( // fall through } } + + if ($context->mutation_free) { + foreach ($right_type->getAtomicTypes() as $atomic_type) { + if ($atomic_type instanceof TNamedObject) { + try { + $storage = $codebase->methods->getStorage( + new \Psalm\Internal\MethodIdentifier( + $atomic_type->value, + '__tostring' + ) + ); + } catch (\UnexpectedValueException $e) { + continue; + } + + if (!$storage->mutation_free) { + if (IssueBuffer::accepts( + new ImpureMethodCall( + 'Cannot call a possibly-mutating method ' + . $atomic_type->value . '::__toString from a pure context', + new CodeLocation($statements_analyzer, $right) + ), + $statements_analyzer->getSuppressedIssues() + )) { + // fall through + } + } + } + } + } } if (!$left_type_match diff --git a/tests/PureAnnotationTest.php b/tests/PureAnnotationTest.php index 03539dead66..7f60bf113ab 100644 --- a/tests/PureAnnotationTest.php +++ b/tests/PureAnnotationTest.php @@ -400,7 +400,7 @@ public static function zero(): string { }', 'error_message' => 'ImpureStaticProperty', ], - 'preventImpureToString' => [ + 'preventImpureToStringViaComparison' => [ ' 'ImpureMethodCall' ], + 'preventImpureToStringViaConcatenation' => [ + ' 'ImpureMethodCall' + ], ]; } }