1
0
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:
Matthew Brown 2019-08-05 23:19:22 -04:00
parent 6eb62591ab
commit 8f6d432dd0
3 changed files with 100 additions and 38 deletions

View File

@ -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

View File

@ -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

View File

@ -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());
}
}