mirror of
https://github.com/danog/psalm.git
synced 2024-11-26 20:34:47 +01:00
Add support for magic property comprehension
This commit is contained in:
parent
6eb62591ab
commit
8f6d432dd0
@ -408,6 +408,8 @@ class PropertyAssignmentAnalyzer
|
||||
if (!in_array('PossiblyNullReference', $suppressed_issues, true)) {
|
||||
$statements_analyzer->removeSuppressedIssues(['PossiblyNullReference']);
|
||||
}
|
||||
|
||||
self::taintProperty($statements_analyzer, $stmt, $property_id, $assignment_value_type);
|
||||
}
|
||||
|
||||
/*
|
||||
@ -456,44 +458,7 @@ class PropertyAssignmentAnalyzer
|
||||
}
|
||||
}
|
||||
|
||||
if ($codebase->taint) {
|
||||
$method_source = new TypeSource(
|
||||
$property_id,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
);
|
||||
|
||||
if ($codebase->taint->hasPreviousSink($method_source)) {
|
||||
if ($assignment_value_type->sources) {
|
||||
$codebase->taint->addSinks(
|
||||
$statements_analyzer,
|
||||
$assignment_value_type->sources,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$method_source
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($assignment_value_type->sources) {
|
||||
foreach ($assignment_value_type->sources as $type_source) {
|
||||
if ($codebase->taint->hasPreviousSource($type_source)
|
||||
|| $assignment_value_type->tainted
|
||||
) {
|
||||
$codebase->taint->addSources(
|
||||
$statements_analyzer,
|
||||
[$method_source],
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$type_source
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif ($assignment_value_type->tainted) {
|
||||
throw new \UnexpectedValueException(
|
||||
'sources should exist for tainted var in '
|
||||
. $statements_analyzer->getFileName() . ':'
|
||||
. $stmt->getLine()
|
||||
);
|
||||
}
|
||||
}
|
||||
self::taintProperty($statements_analyzer, $stmt, $property_id, $assignment_value_type);
|
||||
|
||||
if (!$codebase->properties->propertyExists(
|
||||
$property_id,
|
||||
@ -946,6 +911,56 @@ class PropertyAssignmentAnalyzer
|
||||
return null;
|
||||
}
|
||||
|
||||
private static function taintProperty(
|
||||
StatementsAnalyzer $statements_analyzer,
|
||||
PhpParser\Node\Expr\PropertyFetch $stmt,
|
||||
string $property_id,
|
||||
Type\Union $assignment_value_type
|
||||
) : void {
|
||||
$codebase = $statements_analyzer->getCodebase();
|
||||
|
||||
if (!$codebase->taint) {
|
||||
return;
|
||||
}
|
||||
|
||||
$method_source = new TypeSource(
|
||||
$property_id,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt)
|
||||
);
|
||||
|
||||
if ($codebase->taint->hasPreviousSink($method_source)) {
|
||||
if ($assignment_value_type->sources) {
|
||||
$codebase->taint->addSinks(
|
||||
$statements_analyzer,
|
||||
$assignment_value_type->sources,
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$method_source
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
if ($assignment_value_type->sources) {
|
||||
foreach ($assignment_value_type->sources as $type_source) {
|
||||
if ($codebase->taint->hasPreviousSource($type_source)
|
||||
|| $assignment_value_type->tainted
|
||||
) {
|
||||
$codebase->taint->addSources(
|
||||
$statements_analyzer,
|
||||
[$method_source],
|
||||
new CodeLocation($statements_analyzer->getSource(), $stmt),
|
||||
$type_source
|
||||
);
|
||||
}
|
||||
}
|
||||
} elseif ($assignment_value_type->tainted) {
|
||||
throw new \UnexpectedValueException(
|
||||
'sources should exist for tainted var in '
|
||||
. $statements_analyzer->getFileName() . ':'
|
||||
. $stmt->getLine()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param StatementsAnalyzer $statements_analyzer
|
||||
* @param PhpParser\Node\Expr\StaticPropertyFetch $stmt
|
||||
|
@ -544,6 +544,20 @@ class PropertyFetchAnalyzer
|
||||
|
||||
$stmt->inferredType = $fake_method_call->inferredType ?? Type::getMixed();
|
||||
|
||||
$property_id = $lhs_type_part->value . '::$' . $prop_name;
|
||||
|
||||
if ($codebase->taint) {
|
||||
$method_source = new TypeSource(
|
||||
$property_id,
|
||||
new CodeLocation($statements_analyzer, $stmt->name)
|
||||
);
|
||||
|
||||
if ($codebase->taint->hasPreviousSource($method_source)) {
|
||||
$stmt->inferredType->tainted = 1;
|
||||
$stmt->inferredType->sources = [$method_source];
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* If we have an explicit list of all allowed magic properties on the class, and we're
|
||||
* not in that list, fall through
|
||||
|
@ -657,4 +657,37 @@ class TaintTest extends TestCase
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
|
||||
public function testTaintedInputFromMagicProperty() : void
|
||||
{
|
||||
$this->expectException(\Psalm\Exception\CodeException::class);
|
||||
$this->expectExceptionMessage('TaintedInput');
|
||||
|
||||
$this->project_analyzer->trackTaintedInputs();
|
||||
|
||||
$this->addFile(
|
||||
'somefile.php',
|
||||
'<?php
|
||||
class A {
|
||||
/** @var array<string, string> */
|
||||
private $vars = [];
|
||||
|
||||
public function __get(string $s) : string {
|
||||
return $this->vars[$s];
|
||||
}
|
||||
|
||||
public function __set(string $s, string $t) {
|
||||
$this->vars[$s] = $t;
|
||||
}
|
||||
}
|
||||
|
||||
function getAppendedUserId() : void {
|
||||
$a = new A();
|
||||
$a->userId = (string) $_GET["user_id"];
|
||||
echo $a->userId;
|
||||
}'
|
||||
);
|
||||
|
||||
$this->analyzeFile('somefile.php', new Context());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user