mirror of
https://github.com/danog/psalm.git
synced 2025-01-21 21:31:13 +01:00
Improve tainting of specializable classes
This commit is contained in:
parent
078b8b7b1a
commit
8f2e28c36b
@ -888,12 +888,17 @@ class CommentAnalyzer
|
||||
) {
|
||||
$info->mutation_free = true;
|
||||
$info->external_mutation_free = true;
|
||||
$info->taint_specialize = true;
|
||||
}
|
||||
|
||||
if (isset($parsed_docblock->tags['psalm-external-mutation-free'])) {
|
||||
$info->external_mutation_free = true;
|
||||
}
|
||||
|
||||
if (isset($parsed_docblock->tags['psalm-taint-specialize'])) {
|
||||
$info->taint_specialize = true;
|
||||
}
|
||||
|
||||
if (isset($parsed_docblock->tags['psalm-override-property-visibility'])) {
|
||||
$info->override_property_visibility = true;
|
||||
}
|
||||
|
@ -748,6 +748,30 @@ abstract class FunctionLikeAnalyzer extends SourceAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if ($codebase->taint
|
||||
&& $this->function instanceof ClassMethod
|
||||
&& $cased_method_id
|
||||
&& $this->function->name->name === '__construct'
|
||||
&& isset($context->vars_in_scope['$this'])
|
||||
&& $context->vars_in_scope['$this']->parent_nodes
|
||||
) {
|
||||
$method_source = TaintNode::getForMethodReturn(
|
||||
(string) $method_id,
|
||||
$cased_method_id,
|
||||
$storage->location
|
||||
);
|
||||
|
||||
$codebase->taint->addTaintNode($method_source);
|
||||
|
||||
foreach ($context->vars_in_scope['$this']->parent_nodes as $parent_node) {
|
||||
$codebase->taint->addPath(
|
||||
$parent_node,
|
||||
$method_source,
|
||||
'$this'
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($add_mutations) {
|
||||
if ($this->return_vars_in_scope !== null) {
|
||||
$context->vars_in_scope = TypeAnalyzer::combineKeyedTypes(
|
||||
|
@ -690,7 +690,7 @@ class ArrayAssignmentAnalyzer
|
||||
|
||||
if ($root_array_expr instanceof PhpParser\Node\Expr\PropertyFetch) {
|
||||
if ($root_array_expr->name instanceof PhpParser\Node\Identifier) {
|
||||
PropertyAssignmentAnalyzer::analyzeInstance(
|
||||
InstancePropertyAssignmentAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$root_array_expr,
|
||||
$root_array_expr->name->name,
|
||||
@ -711,7 +711,7 @@ class ArrayAssignmentAnalyzer
|
||||
} elseif ($root_array_expr instanceof PhpParser\Node\Expr\StaticPropertyFetch
|
||||
&& $root_array_expr->name instanceof PhpParser\Node\Identifier
|
||||
) {
|
||||
PropertyAssignmentAnalyzer::analyzeStatic(
|
||||
StaticPropertyAssignmentAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$root_array_expr,
|
||||
null,
|
||||
|
@ -50,7 +50,7 @@ use Psalm\Internal\Taint\TaintNode;
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class PropertyAssignmentAnalyzer
|
||||
class InstancePropertyAssignmentAnalyzer
|
||||
{
|
||||
/**
|
||||
* @param StatementsAnalyzer $statements_analyzer
|
||||
@ -63,7 +63,7 @@ class PropertyAssignmentAnalyzer
|
||||
*
|
||||
* @return false|null
|
||||
*/
|
||||
public static function analyzeInstance(
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
$stmt,
|
||||
$prop_name,
|
||||
@ -402,7 +402,17 @@ class PropertyAssignmentAnalyzer
|
||||
$has_regular_setter = true;
|
||||
$property_exists = true;
|
||||
|
||||
self::taintProperty($statements_analyzer, $stmt, $property_id, $assignment_value_type);
|
||||
if (!$context->collect_initializations) {
|
||||
self::taintProperty(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$property_id,
|
||||
$class_storage,
|
||||
$assignment_value_type,
|
||||
$context
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
}
|
||||
@ -463,7 +473,16 @@ class PropertyAssignmentAnalyzer
|
||||
* not in that list, fall through
|
||||
*/
|
||||
if (!$var_id || !$class_storage->sealed_properties) {
|
||||
self::taintProperty($statements_analyzer, $stmt, $property_id, $assignment_value_type);
|
||||
if (!$context->collect_initializations) {
|
||||
self::taintProperty(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$property_id,
|
||||
$class_storage,
|
||||
$assignment_value_type,
|
||||
$context
|
||||
);
|
||||
}
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -506,7 +525,18 @@ class PropertyAssignmentAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
self::taintProperty($statements_analyzer, $stmt, $property_id, $assignment_value_type);
|
||||
if ($codebase->taint && !$context->collect_initializations) {
|
||||
$class_storage = $codebase->classlike_storage_provider->get($fq_class_name);
|
||||
|
||||
self::taintProperty(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$property_id,
|
||||
$class_storage,
|
||||
$assignment_value_type,
|
||||
$context
|
||||
);
|
||||
}
|
||||
|
||||
if (!$codebase->properties->propertyExists(
|
||||
$property_id,
|
||||
@ -1051,7 +1081,7 @@ class PropertyAssignmentAnalyzer
|
||||
ExpressionAnalyzer::analyze($statements_analyzer, $prop->default, $context);
|
||||
|
||||
if ($prop_default_type = $statements_analyzer->node_data->getType($prop->default)) {
|
||||
if (self::analyzeInstance(
|
||||
if (self::analyze(
|
||||
$statements_analyzer,
|
||||
$prop,
|
||||
$prop->name->name,
|
||||
@ -1070,7 +1100,9 @@ class PropertyAssignmentAnalyzer
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\PropertyFetch $stmt,
|
||||
string $property_id,
|
||||
Type\Union $assignment_value_type
|
||||
\Psalm\Storage\ClassLikeStorage $class_storage,
|
||||
Type\Union $assignment_value_type,
|
||||
Context $context
|
||||
) : void {
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
@ -1078,309 +1110,90 @@ class PropertyAssignmentAnalyzer
|
||||
return;
|
||||
}
|
||||
|
||||
$code_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
|
||||
$var_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var);
|
||||
$property_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
|
||||
|
||||
$localized_property_node = new TaintNode(
|
||||
$property_id . '-' . $code_location->file_name . ':' . $code_location->raw_file_start,
|
||||
$property_id,
|
||||
$code_location,
|
||||
null
|
||||
);
|
||||
|
||||
$codebase->taint->addTaintNode($localized_property_node);
|
||||
|
||||
$property_node = new TaintNode(
|
||||
$property_id,
|
||||
$property_id,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
$codebase->taint->addTaintNode($property_node);
|
||||
|
||||
$codebase->taint->addPath($localized_property_node, $property_node, 'property-assignment');
|
||||
|
||||
if ($assignment_value_type->parent_nodes) {
|
||||
foreach ($assignment_value_type->parent_nodes as $parent_node) {
|
||||
$codebase->taint->addPath($parent_node, $localized_property_node, '=');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StatementsAnalyzer $statements_analyzer
|
||||
* @param PhpParser\Node\Expr\StaticPropertyFetch $stmt
|
||||
* @param PhpParser\Node\Expr|null $assignment_value
|
||||
* @param Type\Union $assignment_value_type
|
||||
* @param Context $context
|
||||
*
|
||||
* @return false|null
|
||||
*/
|
||||
public static function analyzeStatic(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\StaticPropertyFetch $stmt,
|
||||
$assignment_value,
|
||||
Type\Union $assignment_value_type,
|
||||
Context $context
|
||||
) {
|
||||
$var_id = ExpressionIdentifier::getArrayVarId(
|
||||
$stmt,
|
||||
$context->self,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
$fq_class_name = (string) $statements_analyzer->node_data->getType($stmt->class);
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$prop_name = $stmt->name;
|
||||
|
||||
if (!$prop_name instanceof PhpParser\Node\Identifier) {
|
||||
if (ExpressionAnalyzer::analyze($statements_analyzer, $prop_name, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($fq_class_name && !$context->ignore_variable_property) {
|
||||
$codebase->analyzer->addMixedMemberName(
|
||||
strtolower($fq_class_name) . '::$',
|
||||
$context->calling_method_id ?: $statements_analyzer->getFileName()
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$property_id = $fq_class_name . '::$' . $prop_name;
|
||||
|
||||
if (!$codebase->properties->propertyExists($property_id, false, $statements_analyzer, $context)) {
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedPropertyAssignment(
|
||||
'Static property ' . $property_id . ' is not defined',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClassLikeAnalyzer::checkPropertyVisibility(
|
||||
$property_id,
|
||||
$context,
|
||||
$statements_analyzer,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$declaring_property_class = (string) $codebase->properties->getDeclaringClassForProperty(
|
||||
$fq_class_name . '::$' . $prop_name->name,
|
||||
false
|
||||
);
|
||||
|
||||
$declaring_property_id = strtolower((string) $declaring_property_class) . '::$' . $prop_name;
|
||||
|
||||
if ($codebase->alter_code && $stmt->class instanceof PhpParser\Node\Name) {
|
||||
$moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$stmt->class,
|
||||
$fq_class_name,
|
||||
$context->calling_method_id
|
||||
if ($class_storage->specialize_instance) {
|
||||
$var_id = ExpressionIdentifier::getArrayVarId(
|
||||
$stmt->var,
|
||||
null,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
if (!$moved_class) {
|
||||
foreach ($codebase->property_transforms as $original_pattern => $transformation) {
|
||||
if ($declaring_property_id === $original_pattern) {
|
||||
list($old_declaring_fq_class_name) = explode('::$', $declaring_property_id);
|
||||
list($new_fq_class_name, $new_property_name) = explode('::$', $transformation);
|
||||
$var_property_id = ExpressionIdentifier::getArrayVarId(
|
||||
$stmt,
|
||||
null,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
$file_manipulations = [];
|
||||
if ($var_id) {
|
||||
$var_node = TaintNode::getForAssignment(
|
||||
$var_id,
|
||||
$var_location
|
||||
);
|
||||
|
||||
if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) {
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $stmt->class->getAttribute('startFilePos'),
|
||||
(int) $stmt->class->getAttribute('endFilePos') + 1,
|
||||
Type::getStringFromFQCLN(
|
||||
$new_fq_class_name,
|
||||
$statements_analyzer->getNamespace(),
|
||||
$statements_analyzer->getAliasedClassesFlipped(),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
$codebase->taint->addTaintNode($var_node);
|
||||
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $stmt->name->getAttribute('startFilePos'),
|
||||
(int) $stmt->name->getAttribute('endFilePos') + 1,
|
||||
'$' . $new_property_name
|
||||
);
|
||||
$property_node = TaintNode::getForAssignment(
|
||||
$var_property_id ?: $var_id . '->$property',
|
||||
$property_location
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
|
||||
$codebase->taint->addTaintNode($property_node);
|
||||
|
||||
$codebase->taint->addPath(
|
||||
$property_node,
|
||||
$var_node,
|
||||
'property-assignment'
|
||||
. ($stmt->name instanceof PhpParser\Node\Identifier ? '-' . $stmt->name : '')
|
||||
);
|
||||
|
||||
if ($assignment_value_type->parent_nodes) {
|
||||
foreach ($assignment_value_type->parent_nodes as $parent_node) {
|
||||
$codebase->taint->addPath($parent_node, $property_node, '=');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$class_storage = $codebase->classlike_storage_provider->get($declaring_property_class);
|
||||
$stmt_var_type = clone $context->vars_in_scope[$var_id];
|
||||
|
||||
$property_storage = $class_storage->properties[$prop_name->name];
|
||||
|
||||
if ($var_id) {
|
||||
$context->vars_in_scope[$var_id] = $assignment_value_type;
|
||||
}
|
||||
|
||||
$class_property_type = $codebase->properties->getPropertyType(
|
||||
$property_id,
|
||||
true,
|
||||
$statements_analyzer,
|
||||
$context
|
||||
);
|
||||
|
||||
if (!$class_property_type) {
|
||||
$class_property_type = Type::getMixed();
|
||||
|
||||
if (!$assignment_value_type->hasMixed()) {
|
||||
if ($property_storage->suggested_type) {
|
||||
$property_storage->suggested_type = Type::combineUnionTypes(
|
||||
$assignment_value_type,
|
||||
$property_storage->suggested_type
|
||||
);
|
||||
} else {
|
||||
$property_storage->suggested_type = Type::combineUnionTypes(
|
||||
Type::getNull(),
|
||||
$assignment_value_type
|
||||
);
|
||||
if ($context->vars_in_scope[$var_id]->parent_nodes) {
|
||||
foreach ($context->vars_in_scope[$var_id]->parent_nodes as $parent_node) {
|
||||
$codebase->taint->addPath($parent_node, $var_node, '=');
|
||||
}
|
||||
}
|
||||
|
||||
$stmt_var_type->parent_nodes = [$var_node];
|
||||
|
||||
$context->vars_in_scope[$var_id] = $stmt_var_type;
|
||||
}
|
||||
} else {
|
||||
$class_property_type = clone $class_property_type;
|
||||
}
|
||||
$code_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
|
||||
|
||||
if ($assignment_value_type->hasMixed()) {
|
||||
return null;
|
||||
}
|
||||
$localized_property_node = new TaintNode(
|
||||
$property_id . '-' . $code_location->file_name . ':' . $code_location->raw_file_start,
|
||||
$property_id,
|
||||
$code_location,
|
||||
null
|
||||
);
|
||||
|
||||
if ($class_property_type->hasMixed()) {
|
||||
return null;
|
||||
}
|
||||
$codebase->taint->addTaintNode($localized_property_node);
|
||||
|
||||
$class_property_type = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
||||
$codebase,
|
||||
$class_property_type,
|
||||
$fq_class_name,
|
||||
$fq_class_name,
|
||||
$class_storage->parent_class
|
||||
);
|
||||
$property_node = new TaintNode(
|
||||
$property_id,
|
||||
$property_id,
|
||||
null,
|
||||
null
|
||||
);
|
||||
|
||||
$union_comparison_results = new \Psalm\Internal\Analyzer\TypeComparisonResult();
|
||||
$codebase->taint->addTaintNode($property_node);
|
||||
|
||||
$type_match_found = TypeAnalyzer::isContainedBy(
|
||||
$codebase,
|
||||
$assignment_value_type,
|
||||
$class_property_type,
|
||||
true,
|
||||
true,
|
||||
$union_comparison_results
|
||||
);
|
||||
$codebase->taint->addPath($localized_property_node, $property_node, 'property-assignment');
|
||||
|
||||
if ($union_comparison_results->type_coerced) {
|
||||
if ($union_comparison_results->type_coerced_from_mixed) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedPropertyTypeCoercion(
|
||||
$var_id . ' expects \'' . $class_property_type->getId() . '\', '
|
||||
. ' parent type `' . $assignment_value_type->getId() . '` provided',
|
||||
new CodeLocation(
|
||||
$statements_analyzer->getSource(),
|
||||
$assignment_value ?: $stmt,
|
||||
$context->include_location
|
||||
),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// keep soldiering on
|
||||
}
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new PropertyTypeCoercion(
|
||||
$var_id . ' expects \'' . $class_property_type->getId() . '\', '
|
||||
. ' parent type \'' . $assignment_value_type->getId() . '\' provided',
|
||||
new CodeLocation(
|
||||
$statements_analyzer->getSource(),
|
||||
$assignment_value ?: $stmt,
|
||||
$context->include_location
|
||||
),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// keep soldiering on
|
||||
if ($assignment_value_type->parent_nodes) {
|
||||
foreach ($assignment_value_type->parent_nodes as $parent_node) {
|
||||
$codebase->taint->addPath($parent_node, $localized_property_node, '=');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($union_comparison_results->to_string_cast) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ImplicitToStringCast(
|
||||
$var_id . ' expects \'' . $class_property_type . '\', '
|
||||
. '\'' . $assignment_value_type . '\' provided with a __toString method',
|
||||
new CodeLocation(
|
||||
$statements_analyzer->getSource(),
|
||||
$assignment_value ?: $stmt,
|
||||
$context->include_location
|
||||
)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
if (!$type_match_found && !$union_comparison_results->type_coerced) {
|
||||
if (TypeAnalyzer::canBeContainedBy($codebase, $assignment_value_type, $class_property_type)) {
|
||||
if (IssueBuffer::accepts(
|
||||
new PossiblyInvalidPropertyAssignmentValue(
|
||||
$var_id . ' with declared type \''
|
||||
. $class_property_type->getId() . '\' cannot be assigned type \''
|
||||
. $assignment_value_type->getId() . '\'',
|
||||
new CodeLocation(
|
||||
$statements_analyzer->getSource(),
|
||||
$assignment_value ?: $stmt
|
||||
),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidPropertyAssignmentValue(
|
||||
$var_id . ' with declared type \'' . $class_property_type->getId()
|
||||
. '\' cannot be assigned type \''
|
||||
. $assignment_value_type->getId() . '\'',
|
||||
new CodeLocation(
|
||||
$statements_analyzer->getSource(),
|
||||
$assignment_value ?: $stmt
|
||||
),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($var_id) {
|
||||
$context->vars_in_scope[$var_id] = $assignment_value_type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -0,0 +1,331 @@
|
||||
<?php
|
||||
namespace Psalm\Internal\Analyzer\Statements\Expression\Assignment;
|
||||
|
||||
use PhpParser;
|
||||
use PhpParser\Node\Expr\PropertyFetch;
|
||||
use PhpParser\Node\Stmt\PropertyProperty;
|
||||
use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\ExpressionIdentifier;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\InstancePropertyFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
||||
use Psalm\Internal\FileManipulation\FileManipulationBuffer;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\DeprecatedProperty;
|
||||
use Psalm\Issue\ImplicitToStringCast;
|
||||
use Psalm\Issue\InaccessibleProperty;
|
||||
use Psalm\Issue\InternalProperty;
|
||||
use Psalm\Issue\InvalidPropertyAssignment;
|
||||
use Psalm\Issue\InvalidPropertyAssignmentValue;
|
||||
use Psalm\Issue\LoopInvalidation;
|
||||
use Psalm\Issue\MixedAssignment;
|
||||
use Psalm\Issue\MixedPropertyAssignment;
|
||||
use Psalm\Issue\MixedPropertyTypeCoercion;
|
||||
use Psalm\Issue\NoInterfaceProperties;
|
||||
use Psalm\Issue\NullPropertyAssignment;
|
||||
use Psalm\Issue\PossiblyFalsePropertyAssignmentValue;
|
||||
use Psalm\Issue\PossiblyInvalidPropertyAssignment;
|
||||
use Psalm\Issue\PossiblyInvalidPropertyAssignmentValue;
|
||||
use Psalm\Issue\PossiblyNullPropertyAssignment;
|
||||
use Psalm\Issue\PossiblyNullPropertyAssignmentValue;
|
||||
use Psalm\Issue\PropertyTypeCoercion;
|
||||
use Psalm\Issue\UndefinedClass;
|
||||
use Psalm\Issue\UndefinedPropertyAssignment;
|
||||
use Psalm\Issue\UndefinedMagicPropertyAssignment;
|
||||
use Psalm\Issue\UndefinedThisPropertyAssignment;
|
||||
use Psalm\IssueBuffer;
|
||||
use Psalm\Type;
|
||||
use Psalm\Type\Atomic\TNamedObject;
|
||||
use Psalm\Type\Atomic\TNull;
|
||||
use Psalm\Type\Atomic\TObject;
|
||||
use function count;
|
||||
use function in_array;
|
||||
use function strtolower;
|
||||
use function explode;
|
||||
use Psalm\Internal\Taint\TaintNode;
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
class StaticPropertyAssignmentAnalyzer
|
||||
{
|
||||
/**
|
||||
* @param StatementsAnalyzer $statements_analyzer
|
||||
* @param PhpParser\Node\Expr\StaticPropertyFetch $stmt
|
||||
* @param PhpParser\Node\Expr|null $assignment_value
|
||||
* @param Type\Union $assignment_value_type
|
||||
* @param Context $context
|
||||
*
|
||||
* @return false|null
|
||||
*/
|
||||
public static function analyze(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\StaticPropertyFetch $stmt,
|
||||
$assignment_value,
|
||||
Type\Union $assignment_value_type,
|
||||
Context $context
|
||||
) {
|
||||
$var_id = ExpressionIdentifier::getArrayVarId(
|
||||
$stmt,
|
||||
$context->self,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
$fq_class_name = (string) $statements_analyzer->node_data->getType($stmt->class);
|
||||
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
$prop_name = $stmt->name;
|
||||
|
||||
if (!$prop_name instanceof PhpParser\Node\Identifier) {
|
||||
if (ExpressionAnalyzer::analyze($statements_analyzer, $prop_name, $context) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if ($fq_class_name && !$context->ignore_variable_property) {
|
||||
$codebase->analyzer->addMixedMemberName(
|
||||
strtolower($fq_class_name) . '::$',
|
||||
$context->calling_method_id ?: $statements_analyzer->getFileName()
|
||||
);
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$property_id = $fq_class_name . '::$' . $prop_name;
|
||||
|
||||
if (!$codebase->properties->propertyExists($property_id, false, $statements_analyzer, $context)) {
|
||||
if (IssueBuffer::accepts(
|
||||
new UndefinedPropertyAssignment(
|
||||
'Static property ' . $property_id . ' is not defined',
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
if (ClassLikeAnalyzer::checkPropertyVisibility(
|
||||
$property_id,
|
||||
$context,
|
||||
$statements_analyzer,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
) === false) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$declaring_property_class = (string) $codebase->properties->getDeclaringClassForProperty(
|
||||
$fq_class_name . '::$' . $prop_name->name,
|
||||
false
|
||||
);
|
||||
|
||||
$declaring_property_id = strtolower((string) $declaring_property_class) . '::$' . $prop_name;
|
||||
|
||||
if ($codebase->alter_code && $stmt->class instanceof PhpParser\Node\Name) {
|
||||
$moved_class = $codebase->classlikes->handleClassLikeReferenceInMigration(
|
||||
$codebase,
|
||||
$statements_analyzer,
|
||||
$stmt->class,
|
||||
$fq_class_name,
|
||||
$context->calling_method_id
|
||||
);
|
||||
|
||||
if (!$moved_class) {
|
||||
foreach ($codebase->property_transforms as $original_pattern => $transformation) {
|
||||
if ($declaring_property_id === $original_pattern) {
|
||||
list($old_declaring_fq_class_name) = explode('::$', $declaring_property_id);
|
||||
list($new_fq_class_name, $new_property_name) = explode('::$', $transformation);
|
||||
|
||||
$file_manipulations = [];
|
||||
|
||||
if (strtolower($new_fq_class_name) !== strtolower($old_declaring_fq_class_name)) {
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $stmt->class->getAttribute('startFilePos'),
|
||||
(int) $stmt->class->getAttribute('endFilePos') + 1,
|
||||
Type::getStringFromFQCLN(
|
||||
$new_fq_class_name,
|
||||
$statements_analyzer->getNamespace(),
|
||||
$statements_analyzer->getAliasedClassesFlipped(),
|
||||
null
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
$file_manipulations[] = new \Psalm\FileManipulation(
|
||||
(int) $stmt->name->getAttribute('startFilePos'),
|
||||
(int) $stmt->name->getAttribute('endFilePos') + 1,
|
||||
'$' . $new_property_name
|
||||
);
|
||||
|
||||
FileManipulationBuffer::add($statements_analyzer->getFilePath(), $file_manipulations);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$class_storage = $codebase->classlike_storage_provider->get($declaring_property_class);
|
||||
|
||||
$property_storage = $class_storage->properties[$prop_name->name];
|
||||
|
||||
if ($var_id) {
|
||||
$context->vars_in_scope[$var_id] = $assignment_value_type;
|
||||
}
|
||||
|
||||
$class_property_type = $codebase->properties->getPropertyType(
|
||||
$property_id,
|
||||
true,
|
||||
$statements_analyzer,
|
||||
$context
|
||||
);
|
||||
|
||||
if (!$class_property_type) {
|
||||
$class_property_type = Type::getMixed();
|
||||
|
||||
if (!$assignment_value_type->hasMixed()) {
|
||||
if ($property_storage->suggested_type) {
|
||||
$property_storage->suggested_type = Type::combineUnionTypes(
|
||||
$assignment_value_type,
|
||||
$property_storage->suggested_type
|
||||
);
|
||||
} else {
|
||||
$property_storage->suggested_type = Type::combineUnionTypes(
|
||||
Type::getNull(),
|
||||
$assignment_value_type
|
||||
);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
$class_property_type = clone $class_property_type;
|
||||
}
|
||||
|
||||
if ($assignment_value_type->hasMixed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ($class_property_type->hasMixed()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$class_property_type = \Psalm\Internal\Type\TypeExpander::expandUnion(
|
||||
$codebase,
|
||||
$class_property_type,
|
||||
$fq_class_name,
|
||||
$fq_class_name,
|
||||
$class_storage->parent_class
|
||||
);
|
||||
|
||||
$union_comparison_results = new \Psalm\Internal\Analyzer\TypeComparisonResult();
|
||||
|
||||
$type_match_found = TypeAnalyzer::isContainedBy(
|
||||
$codebase,
|
||||
$assignment_value_type,
|
||||
$class_property_type,
|
||||
true,
|
||||
true,
|
||||
$union_comparison_results
|
||||
);
|
||||
|
||||
if ($union_comparison_results->type_coerced) {
|
||||
if ($union_comparison_results->type_coerced_from_mixed) {
|
||||
if (IssueBuffer::accepts(
|
||||
new MixedPropertyTypeCoercion(
|
||||
$var_id . ' expects \'' . $class_property_type->getId() . '\', '
|
||||
. ' parent type `' . $assignment_value_type->getId() . '` provided',
|
||||
new CodeLocation(
|
||||
$statements_analyzer->getSource(),
|
||||
$assignment_value ?: $stmt,
|
||||
$context->include_location
|
||||
),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// keep soldiering on
|
||||
}
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new PropertyTypeCoercion(
|
||||
$var_id . ' expects \'' . $class_property_type->getId() . '\', '
|
||||
. ' parent type \'' . $assignment_value_type->getId() . '\' provided',
|
||||
new CodeLocation(
|
||||
$statements_analyzer->getSource(),
|
||||
$assignment_value ?: $stmt,
|
||||
$context->include_location
|
||||
),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// keep soldiering on
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($union_comparison_results->to_string_cast) {
|
||||
if (IssueBuffer::accepts(
|
||||
new ImplicitToStringCast(
|
||||
$var_id . ' expects \'' . $class_property_type . '\', '
|
||||
. '\'' . $assignment_value_type . '\' provided with a __toString method',
|
||||
new CodeLocation(
|
||||
$statements_analyzer->getSource(),
|
||||
$assignment_value ?: $stmt,
|
||||
$context->include_location
|
||||
)
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
// fall through
|
||||
}
|
||||
}
|
||||
|
||||
if (!$type_match_found && !$union_comparison_results->type_coerced) {
|
||||
if (TypeAnalyzer::canBeContainedBy($codebase, $assignment_value_type, $class_property_type)) {
|
||||
if (IssueBuffer::accepts(
|
||||
new PossiblyInvalidPropertyAssignmentValue(
|
||||
$var_id . ' with declared type \''
|
||||
. $class_property_type->getId() . '\' cannot be assigned type \''
|
||||
. $assignment_value_type->getId() . '\'',
|
||||
new CodeLocation(
|
||||
$statements_analyzer->getSource(),
|
||||
$assignment_value ?: $stmt
|
||||
),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
if (IssueBuffer::accepts(
|
||||
new InvalidPropertyAssignmentValue(
|
||||
$var_id . ' with declared type \'' . $class_property_type->getId()
|
||||
. '\' cannot be assigned type \''
|
||||
. $assignment_value_type->getId() . '\'',
|
||||
new CodeLocation(
|
||||
$statements_analyzer->getSource(),
|
||||
$assignment_value ?: $stmt
|
||||
),
|
||||
$property_id
|
||||
),
|
||||
$statements_analyzer->getSuppressedIssues()
|
||||
)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($var_id) {
|
||||
$context->vars_in_scope[$var_id] = $assignment_value_type;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
@ -5,7 +5,8 @@ use PhpParser;
|
||||
use Psalm\Internal\Analyzer\CommentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Block\ForeachAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\ArrayAssignmentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\PropertyAssignmentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\InstancePropertyAssignmentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\StaticPropertyAssignmentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Analyzer\TypeAnalyzer;
|
||||
@ -824,7 +825,7 @@ class AssignmentAnalyzer
|
||||
}
|
||||
|
||||
if ($prop_name) {
|
||||
PropertyAssignmentAnalyzer::analyzeInstance(
|
||||
InstancePropertyAssignmentAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$assign_var,
|
||||
$prop_name,
|
||||
@ -885,7 +886,7 @@ class AssignmentAnalyzer
|
||||
}
|
||||
|
||||
if ($context->check_classes) {
|
||||
PropertyAssignmentAnalyzer::analyzeStatic(
|
||||
StaticPropertyAssignmentAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$assign_var,
|
||||
$assign_value,
|
||||
@ -1285,7 +1286,7 @@ class AssignmentAnalyzer
|
||||
if ($stmt instanceof PhpParser\Node\Expr\PropertyFetch && $stmt->name instanceof PhpParser\Node\Identifier) {
|
||||
$prop_name = $stmt->name->name;
|
||||
|
||||
PropertyAssignmentAnalyzer::analyzeInstance(
|
||||
InstancePropertyAssignmentAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$prop_name,
|
||||
|
@ -7,6 +7,7 @@ use Psalm\Internal\Analyzer\ClassLikeAnalyzer;
|
||||
use Psalm\Internal\Analyzer\NamespaceAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\ExpressionAnalyzer;
|
||||
use Psalm\Internal\Analyzer\StatementsAnalyzer;
|
||||
use Psalm\Internal\Taint\TaintNode;
|
||||
use Psalm\CodeLocation;
|
||||
use Psalm\Context;
|
||||
use Psalm\Issue\AbstractInstantiation;
|
||||
@ -568,6 +569,32 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
|
||||
$stmt_type->reference_free = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($codebase->taint
|
||||
&& $codebase->config->trackTaintsInPath($statements_analyzer->getFilePath())
|
||||
&& ($stmt_type = $statements_analyzer->node_data->getType($stmt))
|
||||
) {
|
||||
$code_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
|
||||
|
||||
if ($storage->external_mutation_free) {
|
||||
$method_source = TaintNode::getForMethodReturn(
|
||||
(string) $method_id,
|
||||
$fq_class_name . '::__construct',
|
||||
$storage->location,
|
||||
$code_location
|
||||
);
|
||||
} else {
|
||||
$method_source = TaintNode::getForMethodReturn(
|
||||
(string) $method_id,
|
||||
$fq_class_name . '::__construct',
|
||||
$storage->location
|
||||
);
|
||||
}
|
||||
|
||||
$codebase->taint->addTaintNode($method_source);
|
||||
|
||||
$stmt_type->parent_nodes = [$method_source];
|
||||
}
|
||||
} else {
|
||||
ArgumentsAnalyzer::analyze(
|
||||
$statements_analyzer,
|
||||
@ -579,6 +606,8 @@ class NewAnalyzer extends \Psalm\Internal\Analyzer\Statements\Expression\CallAna
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
|
||||
if (!$config->remember_property_assignments_after_call && !$context->collect_initializations) {
|
||||
$context->removeAllObjectVars();
|
||||
}
|
||||
|
@ -177,7 +177,13 @@ class InstancePropertyFetchAnalyzer
|
||||
|
||||
$property_id = $lhs_type_part->value . '::$' . $stmt->name->name;
|
||||
|
||||
self::processTaints($statements_analyzer, $stmt, $stmt_type, $property_id);
|
||||
self::processTaints(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$stmt_type,
|
||||
$property_id,
|
||||
$codebase->classlike_storage_provider->get($lhs_type_part->value)
|
||||
);
|
||||
|
||||
$codebase->properties->propertyExists(
|
||||
$property_id,
|
||||
@ -558,7 +564,13 @@ class InstancePropertyFetchAnalyzer
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
||||
|
||||
self::processTaints($statements_analyzer, $stmt, $stmt_type, $property_id);
|
||||
self::processTaints(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$stmt_type,
|
||||
$property_id,
|
||||
$class_storage
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -663,7 +675,13 @@ class InstancePropertyFetchAnalyzer
|
||||
|
||||
$statements_analyzer->node_data->setType($stmt, $stmt_type);
|
||||
|
||||
self::processTaints($statements_analyzer, $stmt, $stmt_type, $property_id);
|
||||
self::processTaints(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$stmt_type,
|
||||
$property_id,
|
||||
$class_storage
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -894,7 +912,13 @@ class InstancePropertyFetchAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
self::processTaints($statements_analyzer, $stmt, $class_property_type, $property_id);
|
||||
self::processTaints(
|
||||
$statements_analyzer,
|
||||
$stmt,
|
||||
$class_property_type,
|
||||
$property_id,
|
||||
$class_storage
|
||||
);
|
||||
|
||||
if ($stmt_type = $statements_analyzer->node_data->getType($stmt)) {
|
||||
$statements_analyzer->node_data->setType(
|
||||
@ -1038,11 +1062,68 @@ class InstancePropertyFetchAnalyzer
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\PropertyFetch $stmt,
|
||||
Type\Union $type,
|
||||
string $property_id
|
||||
string $property_id,
|
||||
\Psalm\Storage\ClassLikeStorage $class_storage
|
||||
) : void {
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if ($codebase->taint && $codebase->config->trackTaintsInPath($statements_analyzer->getFilePath())) {
|
||||
if (!$codebase->taint || !$codebase->config->trackTaintsInPath($statements_analyzer->getFilePath())) {
|
||||
return;
|
||||
}
|
||||
|
||||
$var_location = new CodeLocation($statements_analyzer->getSource(), $stmt->var);
|
||||
$property_location = new CodeLocation($statements_analyzer->getSource(), $stmt);
|
||||
|
||||
if ($class_storage->specialize_instance) {
|
||||
$var_id = ExpressionIdentifier::getArrayVarId(
|
||||
$stmt->var,
|
||||
null,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
$var_property_id = ExpressionIdentifier::getArrayVarId(
|
||||
$stmt,
|
||||
null,
|
||||
$statements_analyzer
|
||||
);
|
||||
|
||||
if ($var_id) {
|
||||
$var_node = TaintNode::getForAssignment(
|
||||
$var_id,
|
||||
$var_location
|
||||
);
|
||||
|
||||
$codebase->taint->addTaintNode($var_node);
|
||||
|
||||
$property_node = TaintNode::getForAssignment(
|
||||
$var_property_id ?: $var_id . '->$property',
|
||||
$property_location
|
||||
);
|
||||
|
||||
$codebase->taint->addTaintNode($property_node);
|
||||
|
||||
$codebase->taint->addPath(
|
||||
$var_node,
|
||||
$property_node,
|
||||
'property-fetch'
|
||||
. ($stmt->name instanceof PhpParser\Node\Identifier ? '-' . $stmt->name : '')
|
||||
);
|
||||
|
||||
$var_type = $statements_analyzer->node_data->getType($stmt->var);
|
||||
|
||||
if ($var_type && $var_type->parent_nodes) {
|
||||
foreach ($var_type->parent_nodes as $parent_node) {
|
||||
$codebase->taint->addPath(
|
||||
$parent_node,
|
||||
$var_node,
|
||||
'='
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$type->parent_nodes = [$property_node];
|
||||
}
|
||||
} else {
|
||||
$code_location = new CodeLocation($statements_analyzer, $stmt->name);
|
||||
|
||||
$localized_property_node = new TaintNode(
|
||||
@ -1065,7 +1146,7 @@ class InstancePropertyFetchAnalyzer
|
||||
|
||||
$codebase->taint->addPath($property_node, $localized_property_node, 'property-fetch');
|
||||
|
||||
$type->parent_nodes = [$localized_property_node];
|
||||
$type->parent_nodes[] = $localized_property_node;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -9,7 +9,7 @@ use Psalm\Internal\Analyzer\Statements\Block\IfAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Block\SwitchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Block\TryAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Block\WhileAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\PropertyAssignmentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Assignment\InstancePropertyAssignmentAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ClassConstFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ConstFetchAnalyzer;
|
||||
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\VariableFetchAnalyzer;
|
||||
@ -445,7 +445,7 @@ class StatementsAnalyzer extends SourceAnalyzer implements StatementsSource
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Global_) {
|
||||
Statements\GlobalAnalyzer::analyze($statements_analyzer, $stmt, $context, $global_context);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Property) {
|
||||
PropertyAssignmentAnalyzer::analyzeStatement($statements_analyzer, $stmt, $context);
|
||||
InstancePropertyAssignmentAnalyzer::analyzeStatement($statements_analyzer, $stmt, $context);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\ClassConst) {
|
||||
ClassConstFetchAnalyzer::analyzeClassConstAssignment($statements_analyzer, $stmt, $context);
|
||||
} elseif ($stmt instanceof PhpParser\Node\Stmt\Class_) {
|
||||
|
@ -242,6 +242,14 @@ class Populator
|
||||
}
|
||||
}
|
||||
|
||||
if ($storage->specialize_instance) {
|
||||
foreach ($storage->methods as $method) {
|
||||
if (!$method->is_static) {
|
||||
$method->specialize_call = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if ($storage->internal
|
||||
&& !$storage->is_interface
|
||||
&& !$storage->is_trait
|
||||
|
@ -247,6 +247,24 @@ class Taint
|
||||
}
|
||||
}
|
||||
|
||||
if (strpos($path_type, 'property-fetch-') === 0) {
|
||||
$previous_path_types = array_reverse($generated_source->path_types);
|
||||
|
||||
foreach ($previous_path_types as $previous_path_type) {
|
||||
if ($previous_path_type === 'property-assignment') {
|
||||
break;
|
||||
}
|
||||
|
||||
if (strpos($previous_path_type, 'property-assignment-') === 0) {
|
||||
if (substr($previous_path_type, 20) === substr($path_type, 15)) {
|
||||
break;
|
||||
}
|
||||
|
||||
continue 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($sinks[$to_id])) {
|
||||
$matching_taints = array_intersect($sinks[$to_id]->taints, $new_taints);
|
||||
|
||||
|
@ -1350,6 +1350,7 @@ class ReflectorVisitor extends PhpParser\NodeVisitorAbstract implements PhpParse
|
||||
|
||||
$storage->mutation_free = $docblock_info->mutation_free;
|
||||
$storage->external_mutation_free = $docblock_info->external_mutation_free;
|
||||
$storage->specialize_instance = $docblock_info->taint_specialize;
|
||||
|
||||
$storage->override_property_visibility = $docblock_info->override_property_visibility;
|
||||
$storage->override_method_visibility = $docblock_info->override_method_visibility;
|
||||
|
@ -99,6 +99,11 @@ class ClassLikeDocblockComment
|
||||
*/
|
||||
public $external_mutation_free = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $taint_specialize = false;
|
||||
|
||||
/**
|
||||
* @var array<int, string>
|
||||
*/
|
||||
|
@ -251,6 +251,11 @@ class ClassLikeStorage
|
||||
*/
|
||||
public $mutation_free = false;
|
||||
|
||||
/**
|
||||
* @var bool
|
||||
*/
|
||||
public $specialize_instance = false;
|
||||
|
||||
/**
|
||||
* @var array<lowercase-string, MethodStorage>
|
||||
*/
|
||||
|
@ -1464,4 +1464,106 @@ class TaintTest extends TestCase
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
|
||||
public function testTaintPropertyPassingObject() : void
|
||||
{
|
||||
$this->expectException(\Psalm\Exception\CodeException::class);
|
||||
$this->expectExceptionMessage('TaintedInput');
|
||||
|
||||
$this->project_analyzer->trackTaintedInputs();
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?phps
|
||||
/** @psalm-immutable */
|
||||
class User {
|
||||
public string $id;
|
||||
|
||||
public function __construct(string $userId) {
|
||||
$this->id = $userId;
|
||||
}
|
||||
}
|
||||
|
||||
class UserUpdater {
|
||||
public static function doDelete(PDO $pdo, User $user) : void {
|
||||
self::deleteUser($pdo, $user->id);
|
||||
}
|
||||
|
||||
public static function deleteUser(PDO $pdo, string $userId) : void {
|
||||
$pdo->exec("delete from users where user_id = " . $userId);
|
||||
}
|
||||
}
|
||||
|
||||
$userObj = new User((string) $_GET["user_id"]);
|
||||
UserUpdater::doDelete(new PDO(), $userObj);'
|
||||
);
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
|
||||
public function testTaintPropertyPassingObjectWithDifferentValue() : void
|
||||
{
|
||||
$this->project_analyzer->trackTaintedInputs();
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?phps
|
||||
/** @psalm-immutable */
|
||||
class User {
|
||||
public string $id;
|
||||
public $name = "Luke";
|
||||
|
||||
public function __construct(string $userId) {
|
||||
$this->id = $userId;
|
||||
}
|
||||
}
|
||||
|
||||
class UserUpdater {
|
||||
public static function doDelete(PDO $pdo, User $user) : void {
|
||||
self::deleteUser($pdo, $user->name);
|
||||
}
|
||||
|
||||
public static function deleteUser(PDO $pdo, string $userId) : void {
|
||||
$pdo->exec("delete from users where user_id = " . $userId);
|
||||
}
|
||||
}
|
||||
|
||||
$userObj = new User((string) $_GET["user_id"]);
|
||||
UserUpdater::doDelete(new PDO(), $userObj);'
|
||||
);
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
|
||||
public function testTaintPropertyWithoutPassingObject() : void
|
||||
{
|
||||
$this->project_analyzer->trackTaintedInputs();
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?phps
|
||||
/** @psalm-immutable */
|
||||
class User {
|
||||
public string $id;
|
||||
|
||||
public function __construct(string $userId) {
|
||||
$this->id = $userId;
|
||||
}
|
||||
}
|
||||
|
||||
class UserUpdater {
|
||||
public static function doDelete(PDO $pdo, User $user) : void {
|
||||
self::deleteUser($pdo, $user->id);
|
||||
}
|
||||
|
||||
public static function deleteUser(PDO $pdo, string $userId) : void {
|
||||
$pdo->exec("delete from users where user_id = " . $userId);
|
||||
}
|
||||
}
|
||||
|
||||
$userObj = new User((string) $_GET["user_id"]);'
|
||||
);
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user