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:
parent
b54b832838
commit
38977d797e
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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');
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
Loading…
x
Reference in New Issue
Block a user