1
0
mirror of https://github.com/danog/psalm.git synced 2025-01-21 21:31:13 +01:00

Fix #3697 - cast types via implied __toString method

This commit is contained in:
Brown 2020-06-29 09:13:19 -04:00
parent b54b832838
commit 38977d797e
3 changed files with 73 additions and 23 deletions

View File

@ -4,6 +4,7 @@ namespace Psalm\Internal\Analyzer\Statements\Expression;
use PhpParser;
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Internal\Analyzer\Statements\Expression\Call\Method\MethodCallReturnTypeFetcher;
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Issue\InvalidCast;
@ -89,7 +90,9 @@ class CastAnalyzer
return false;
}
if ($statements_analyzer->node_data->getType($stmt->expr)) {
$stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr);
if ($stmt_expr_type) {
$stmt_type = self::castStringAttempt($statements_analyzer, $context, $stmt->expr, true);
} else {
$stmt_type = Type::getString();
@ -97,12 +100,6 @@ class CastAnalyzer
$statements_analyzer->node_data->setType($stmt, $stmt_type);
if (($stmt_expr_type = $statements_analyzer->node_data->getType($stmt->expr))
&& $codebase->taint
) {
$stmt_type->parent_nodes = $stmt_expr_type->parent_nodes;
}
return true;
}
@ -197,6 +194,8 @@ class CastAnalyzer
$atomic_types = $stmt_type->getAtomicTypes();
$parent_nodes = [];
while ($atomic_types) {
$atomic_type = \array_pop($atomic_types);
@ -210,6 +209,7 @@ class CastAnalyzer
if ($atomic_type instanceof TString) {
$valid_strings[] = $atomic_type;
$parent_nodes = array_merge($parent_nodes, $stmt_type->parent_nodes);
continue;
}
@ -225,6 +225,8 @@ class CastAnalyzer
|| $atomic_type instanceof Type\Atomic\Scalar
) {
$castable_types[] = new TString();
$parent_nodes = array_merge($parent_nodes, $stmt_type->parent_nodes);
continue;
}
@ -252,16 +254,25 @@ class CastAnalyzer
$return_type = $codebase->methods->getMethodReturnType(
$intersection_method_id,
$self_class
) ?: Type::getString();
MethodCallReturnTypeFetcher::taintMethodCallResult(
$statements_analyzer,
$return_type,
$stmt,
$stmt,
$intersection_method_id,
$intersection_method_id,
$intersection_type->value . '::__toString',
$context
);
if ($return_type) {
$castable_types = array_merge(
$castable_types,
array_values($return_type->getAtomicTypes())
);
} else {
$castable_types[] = new TString();
}
$parent_nodes = array_merge($return_type->parent_nodes, $parent_nodes);
$castable_types = array_merge(
$castable_types,
array_values($return_type->getAtomicTypes())
);
continue 2;
}
@ -315,12 +326,18 @@ class CastAnalyzer
$valid_types = array_merge($valid_strings, $castable_types);
if (!$valid_types) {
return Type::getString();
$str_type = Type::getString();
} else {
$str_type = \Psalm\Internal\Type\TypeCombination::combineTypes(
$valid_types,
$codebase
);
}
return \Psalm\Internal\Type\TypeCombination::combineTypes(
$valid_types,
$codebase
);
if ($codebase->taint) {
$str_type->parent_nodes = $parent_nodes;
}
return $str_type;
}
}

View File

@ -27,7 +27,7 @@ class EncapsulatedStringAnalyzer
$part_type = $statements_analyzer->node_data->getType($part);
if ($part_type) {
CastAnalyzer::castStringAttempt($statements_analyzer, $context, $part);
$casted_part_type = CastAnalyzer::castStringAttempt($statements_analyzer, $context, $part);
if ($codebase->taint
&& $codebase->config->trackTaintsInPath($statements_analyzer->getFilePath())
@ -39,8 +39,8 @@ class EncapsulatedStringAnalyzer
$stmt_type->parent_nodes[] = $new_parent_node;
if ($part_type->parent_nodes) {
foreach ($part_type->parent_nodes as $parent_node) {
if ($casted_part_type->parent_nodes) {
foreach ($casted_part_type->parent_nodes as $parent_node) {
$codebase->taint->addPath($parent_node, $new_parent_node, 'concat');
}
}

View File

@ -1229,6 +1229,39 @@ class TaintTest extends TestCase
echo "$unsafe";',
'error_message' => 'TaintedInput',
],
'encapsulatedToStringMagic' => [
'<?php
class MyClass {
public function __toString() {
return $_GET["blah"];
}
}
$unsafe = new MyClass();
echo "unsafe: $unsafe";',
'error_message' => 'TaintedInput',
],
'castToStringMagic' => [
'<?php
class MyClass {
public function __toString() {
return $_GET["blah"];
}
}
$unsafe = new MyClass();
echo (string) $unsafe;', // Psalm does not yet warn without a (string) cast.
'error_message' => 'TaintedInput',
],
'implicitToStringMagic' => [
'<?php
class MyClass {
public function __toString() {
return $_GET["blah"];
}
}
$unsafe = new MyClass();
echo (string) $unsafe;', // Psalm does not yet warn without a (string) cast.
'error_message' => 'TaintedInput',
],
'namespacedFunction' => [
'<?php
namespace ns;