Skip to content

Commit

Permalink
Fix negative offset false positive on constant string
Browse files Browse the repository at this point in the history
  • Loading branch information
staabm authored Feb 12, 2025
1 parent bd1a196 commit 14faee1
Show file tree
Hide file tree
Showing 4 changed files with 35 additions and 5 deletions.
9 changes: 6 additions & 3 deletions src/Type/Constant/ConstantStringType.php
Original file line number Diff line number Diff line change
Expand Up @@ -366,7 +366,8 @@ public function isUppercaseString(): TrinaryLogic
public function hasOffsetValueType(Type $offsetType): TrinaryLogic
{
if ($offsetType->isInteger()->yes()) {
$strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1);
$strlen = strlen($this->value);
$strLenType = IntegerRangeType::fromInterval(-$strlen, $strlen - 1);
return $strLenType->isSuperTypeOf($offsetType);
}

Expand All @@ -376,15 +377,17 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic
public function getOffsetValueType(Type $offsetType): Type
{
if ($offsetType->isInteger()->yes()) {
$strlen = strlen($this->value);
$strLenType = IntegerRangeType::fromInterval(-$strlen, $strlen - 1);

if ($offsetType instanceof ConstantIntegerType) {
if ($offsetType->getValue() < strlen($this->value)) {
if ($strLenType->isSuperTypeOf($offsetType)->yes()) {
return new self($this->value[$offsetType->getValue()]);
}

return new ErrorType();
}

$strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1);
$intersected = TypeCombinator::intersect($strLenType, $offsetType);
if ($intersected instanceof IntegerRangeType) {
$finiteTypes = $intersected->getFiniteTypes();
Expand Down
18 changes: 16 additions & 2 deletions tests/PHPStan/Analyser/nsrt/string-offsets.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,11 +9,12 @@
* @param int<3, 10> $threeToTen
* @param int<10, max> $tenOrMore
* @param int<-10, -5> $negative
* @param int<min, -6> $smallerMinusSix
* @param lowercase-string $lowercase
*
* @return void
*/
function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i, string $lowercase) {
function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $smallerMinusSix, int $i, string $lowercase) {
$s = "world";
if (rand(0, 1)) {
$s = "hello";
Expand All @@ -26,10 +27,23 @@ function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i, string $
assertType("'e'|'l'|'o'|'r'", $s[$oneToThree]);
assertType('*ERROR*', $s[$tenOrMore]);
assertType("''|'d'|'l'|'o'", $s[$threeToTen]);
assertType("*ERROR*", $s[$negative]);
assertType("non-empty-string", $s[$negative]);
assertType("*ERROR*", $s[$smallerMinusSix]);

$longString = "myF5HnJv799kWf8VRI7g97vwnABTwN9y2CzAVELCBfRqyqkdTzXg7BkGXcwuIOscAiT6tSuJGzVZOJnYXvkiKQzYBNjjkCPOzSKXR5YHRlVxV1BetqZz4XOmaH9mtacJ9azNYL6bNXezSBjX13BSZy02SK2udzQLbTPNQwlKadKaNkUxjtWegkb8QDFaXbzH1JENVSLVH0FYd6POBU82X1xu7FDDKYLzwsWJHBGVhG8iugjEGwLj22x5ViosUyKR";
assertType("non-empty-string", $longString[$i]);

assertType("lowercase-string&non-empty-string", $lowercase[$i]);
}

function bug12122()
{
// see https://3v4l.org/8EMdX
$foo = 'fo';
assertType('*ERROR*', $foo[2]);
assertType("'o'", $foo[1]);
assertType("'f'", $foo[0]);
assertType("'o'", $foo[-1]);
assertType("'f'", $foo[-2]);
assertType('*ERROR*', $foo[-3]);
}
Original file line number Diff line number Diff line change
Expand Up @@ -924,4 +924,9 @@ public function testInternalClassesWithOverloadedOffsetAccessInvalid84(): void
$this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access-invalid-php84.php'], []);
}

public function testBug12122(): void
{
$this->analyse([__DIR__ . '/data/bug-12122.php'], []);
}

}
8 changes: 8 additions & 0 deletions tests/PHPStan/Rules/Arrays/data/bug-12122.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php

namespace Bug12122;

function doFoo() {
$foo = 'mystring';
var_dump($foo[-1]);
}

0 comments on commit 14faee1

Please sign in to comment.