Comments (7)
I found these snippets:
https://psalm.dev/r/4bae3e6a7b
<?php
class Ensure
{
public static function bool(array $state, string $key) :bool
{
$value = $state['this_rightfully_errors'];
$possible_not_set = $state[$key];
if(!is_bool($value)){
throw new \InvalidArgumentException();
}
return $value;
}
}
Psalm output (using commit 16b24bd):
INFO: PossiblyUndefinedStringArrayOffset - 8:18 - Possibly undefined array offset ''this_rightfully_errors'' is risky given expected type 'array-key'. Consider using isset beforehand.
INFO: MixedAssignment - 9:3 - Unable to determine the type that $possible_not_set is being assigned to
INFO: UnusedVariable - 9:3 - $possible_not_set is never referenced or the value is not used
from psalm.
The responsible method seems to be:
ArrayAnalyzer::checkLiteralStringArrayOffset
which only raises this issue inside this top-level if statement:
if ($offset_type->hasLiteralString() && !$expected_offset_type->hasLiteralClassString() ) {
// issue raised in here.
}
I think the issue should also be raised if $offset_type is TString|TNonEmptyString|etc.
from psalm.
Also, if any string literal is known to be in the array, other string literals are assumed to be in the array aswell.
https://psalm.dev/r/c07a298ef3
from psalm.
I found these snippets:
https://psalm.dev/r/c07a298ef3
<?php
/**
* @param array<string,string> $input
* @param "foo"|"bar" $key
*/
function test(array $input, string $key) :void {
if (array_key_exists("foo", $input)) {
$safe = $input["foo"];
echo "$safe";
$oops = $input[$key];
echo "$oops";
}
}
Psalm output (using commit 16b24bd):
No issues!
from psalm.
A couple more false negatives:
https://psalm.dev/r/db19108369
from psalm.
I found these snippets:
https://psalm.dev/r/db19108369
<?php
declare(strict_types=1);
const FOO = 'foo';
class BarClass {
const BAR = 'bar';
public static function get(): string
{
return 'string';
}
}
/**
* @psalm-suppress MixedAssignment
* @psalm-suppress UnusedVariable
* @psalm-suppress UnusedParam
*
* @param array<array-key,mixed> $input
*/
function keyIsString(array $input, string $key, string $key2, string $key3): void {
// Should error
$value = $input[FOO];
// Should error
$value = $input[BarClass::BAR];
// Should error, but does not.
$value = $input[BarClass::get()];
// Must be okay.
$value3 = $input[$key3] ?? 'foo';
// Should error, but does not.
$value2 = $input[$key2];
// Should not error with isset.
if (!isset($input[$key])){
return;
}
// Should not error after isset.
$value = $input[$key];
}
/**
* @param array<string,string> $input
* @param "foo"|"bar" $key
*/
function test(array $input, string $key) :void {
if (array_key_exists("foo", $input)) {
$safe = $input["foo"];
echo "$safe";
// This must error - It could actually be null.
$oops = $input[$key];
echo "$oops";
}
}
/**
* @return array<string,string>
*/
function getArray() :array
{
return [];
}
$arr = getArray();
$v = str_repeat((string) rand(0, 1), 10);
// Must error
$func_call = $arr[$v];
echo $func_call;
/**
* @psalm-immutable
*/
class ObjectWithArr
{
/**
* @var array<string,string>
*/
public array $arr = [];
/**
* @param array<string,string> $arr
*/
public function __construct(array $arr)
{
$this->arr = $arr;
}
}
$object = new ObjectWithArr(getArray());
if (isset($object->arr['foo'])) {
// Okay, because object is immutable.
echo $object->arr['foo'];
}
Psalm output (using commit 16b24bd):
INFO: PossiblyUndefinedStringArrayOffset - 26:14 - Possibly undefined array offset ''foo'' is risky given expected type 'array-key'. Consider using isset beforehand.
INFO: PossiblyUndefinedStringArrayOffset - 29:14 - Possibly undefined array offset ''bar'' is risky given expected type 'array-key'. Consider using isset beforehand.
from psalm.
I have been testing the following modifications to ArrayFetchAnalyzer, it seems to catch all of the false negatives, without causing new false positives.
But I don't understand all the implications yet - complicated class.
<?php
declare(strict_types=1);
use Psalm\CodeLocation;
use Psalm\Context;
use Psalm\Internal\Analyzer\Statements\Expression\Fetch\ArrayFetchAnalyzer;
use Psalm\Internal\Analyzer\StatementsAnalyzer;
use Psalm\Issue\PossiblyUndefinedIntArrayOffset;
use Psalm\Issue\PossiblyUndefinedStringArrayOffset;
use Psalm\IssueBuffer;
use Psalm\Type\Atomic\TInt;
use Psalm\Type\Atomic\TLiteralInt;
use Psalm\Type\Atomic\TLiteralString;
use Psalm\Type\Atomic\TString;
use Psalm\Type\Union;
/**
* @internal
*
* @see ArrayFetchAnalyzer::checkLiteralIntArrayOffset()
* @see ArrayFetchAnalyzer::checkLiteralStringArrayOffset()
* @see https://github.com/vimeo/psalm/issues/10992
*/
final class ArrayFetchAnalyzerPatch
{
private static function checkLiteralIntArrayOffset(
Union $offset_type,
Union $expected_offset_type,
?string $array_var_id,
PhpParser\Node\Expr\ArrayDimFetch $stmt,
Context $context,
StatementsAnalyzer $statements_analyzer
) :void {
if ($context->inside_isset || $context->inside_unset) {
return;
}
foreach ($offset_type->getAtomicTypes() as $offset_type_part) {
if ( ! $offset_type_part instanceof TInt) {
// This method always seems to be called, regardless of whether the array offset is an int.
continue;
}
if ( ! $array_var_id) {
// This could happen if we try to access an offset on the result of a function call,
// or any other scenario where we don't know the array variable.
IssueBuffer::maybeAdd(
new PossiblyUndefinedIntArrayOffset(
'Possibly undefined array offset: $array_var_id not known.',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
);
return;
}
if (
! $offset_type_part instanceof TLiteralInt
|| ! isset($context->vars_in_scope[$index = $array_var_id.'['.$offset_type_part->value.']'])
|| $context->vars_in_scope[$index]->possibly_undefined
) {
$prefix = $stmt->dim instanceof PhpParser\Node\Expr\FuncCall
? 'Possibly undefined array offset from function call return type'
: 'Possibly undefined array offset';
IssueBuffer::maybeAdd(
new PossiblyUndefinedIntArrayOffset(
$prefix.' \''
.$offset_type_part->getId().'\' '
.'is risky given expected type \''
.$expected_offset_type->getId().'\'.'
.' Consider using isset beforehand.',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
);
}
}
}
private static function checkLiteralStringArrayOffset(
Union $offset_type,
Union $expected_offset_type,
?string $array_var_id,
PhpParser\Node\Expr\ArrayDimFetch $stmt,
Context $context,
StatementsAnalyzer $statements_analyzer
) :void {
if ($context->inside_isset || $context->inside_unset) {
return;
}
// This method seems only to be called the first time we try to fetch this particular
// array index.
// Unless we know that this exact value in scope, it's risky to fetch the array key.
foreach ($offset_type->getAtomicTypes() as $offset_type_part) {
if (!$offset_type_part instanceof TString){
continue;
}
if ( ! $array_var_id) {
// This could happen if we try to access an offset on the result of a function call,
// or any other scenario where we don't know the array variable.
IssueBuffer::maybeAdd(
new PossiblyUndefinedStringArrayOffset(
'Possibly undefined array offset: $array_var_id not known.',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
);
return;
}
if (
! $offset_type_part instanceof TLiteralString
|| ! isset($context->vars_in_scope[$index = $array_var_id.'[\''.$offset_type_part->value.'\']'])
|| $context->vars_in_scope[$index]->possibly_undefined
) {
$prefix = $stmt->dim instanceof PhpParser\Node\Expr\FuncCall
? 'Possibly undefined array offset from function call return type'
: 'Possibly undefined array offset';
IssueBuffer::maybeAdd(
new PossiblyUndefinedStringArrayOffset(
$prefix.' \''
.$offset_type_part->getId().'\' '
.'is risky given expected type \''
.$expected_offset_type->getId().'\'.'
.' Consider using isset beforehand.',
new CodeLocation($statements_analyzer->getSource(), $stmt)
),
$statements_analyzer->getSuppressedIssues()
);
}
}
}
}
from psalm.
Related Issues (20)
- properties-of<Class> not compatible with array type HOT 3
- psalm not understanding properties-of<Class> assignment to a ParentClass property, if properties-of<ParentClass> is used HOT 1
- Unbound template type is not narrowed correctly HOT 2
- The "Generator" class is reserved for internal use and cannot be manually instantiated HOT 1
- Psalm adds float to inferred return type when two integer numbers are added together HOT 4
- Plugin exception details HOT 1
- Misplaced variable causes IncorrectDocblockException HOT 1
- Undetected Trust Boundary violation (CWE-501) HOT 1
- UnexpectedValueException $storage should not be null for PhpParser\Parser::gettokens HOT 1
- How to test against class-string<Foo> if the class might not exist yet? HOT 1
- Bug: Internal Psalm Error: Could not get class storage for <interface> HOT 2
- Converting iterable to array with iterator_to_array causes error HOT 1
- Invalid type for SQLite3Result::columnName HOT 2
- Broken @psalm-yield documentation example
- Getting MismatchingDocblockParamType error even when there's no docblock HOT 2
- Unable to infer not-null state HOT 1
- Trouble with switch case in try catch block HOT 1
- Supression is not working in echo shorthand HOT 1
- `@psalm-suppress InternalClass` doesn't work for internal attributes HOT 1
Recommend Projects
-
React
A declarative, efficient, and flexible JavaScript library for building user interfaces.
-
Vue.js
🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.
-
Typescript
TypeScript is a superset of JavaScript that compiles to clean JavaScript output.
-
TensorFlow
An Open Source Machine Learning Framework for Everyone
-
Django
The Web framework for perfectionists with deadlines.
-
Laravel
A PHP framework for web artisans
-
D3
Bring data to life with SVG, Canvas and HTML. 📊📈🎉
-
Recommend Topics
-
javascript
JavaScript (JS) is a lightweight interpreted programming language with first-class functions.
-
web
Some thing interesting about web. New door for the world.
-
server
A server is a program made to process requests and deliver data to clients.
-
Machine learning
Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.
-
Visualization
Some thing interesting about visualization, use data art
-
Game
Some thing interesting about game, make everyone happy.
Recommend Org
-
Facebook
We are working to build community through open source technology. NB: members must have two-factor auth.
-
Microsoft
Open source projects and samples from Microsoft.
-
Google
Google ❤️ Open Source for everyone.
-
Alibaba
Alibaba Open Source for everyone
-
D3
Data-Driven Documents codes.
-
Tencent
China tencent open source team.
from psalm.