Skip to content

Commit 14faee1

Browse files
authored
Fix negative offset false positive on constant string
1 parent bd1a196 commit 14faee1

File tree

4 files changed

+35
-5
lines changed

4 files changed

+35
-5
lines changed

src/Type/Constant/ConstantStringType.php

+6-3
Original file line numberDiff line numberDiff line change
@@ -366,7 +366,8 @@ public function isUppercaseString(): TrinaryLogic
366366
public function hasOffsetValueType(Type $offsetType): TrinaryLogic
367367
{
368368
if ($offsetType->isInteger()->yes()) {
369-
$strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1);
369+
$strlen = strlen($this->value);
370+
$strLenType = IntegerRangeType::fromInterval(-$strlen, $strlen - 1);
370371
return $strLenType->isSuperTypeOf($offsetType);
371372
}
372373

@@ -376,15 +377,17 @@ public function hasOffsetValueType(Type $offsetType): TrinaryLogic
376377
public function getOffsetValueType(Type $offsetType): Type
377378
{
378379
if ($offsetType->isInteger()->yes()) {
380+
$strlen = strlen($this->value);
381+
$strLenType = IntegerRangeType::fromInterval(-$strlen, $strlen - 1);
382+
379383
if ($offsetType instanceof ConstantIntegerType) {
380-
if ($offsetType->getValue() < strlen($this->value)) {
384+
if ($strLenType->isSuperTypeOf($offsetType)->yes()) {
381385
return new self($this->value[$offsetType->getValue()]);
382386
}
383387

384388
return new ErrorType();
385389
}
386390

387-
$strLenType = IntegerRangeType::fromInterval(0, strlen($this->value) - 1);
388391
$intersected = TypeCombinator::intersect($strLenType, $offsetType);
389392
if ($intersected instanceof IntegerRangeType) {
390393
$finiteTypes = $intersected->getFiniteTypes();

tests/PHPStan/Analyser/nsrt/string-offsets.php

+16-2
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,12 @@
99
* @param int<3, 10> $threeToTen
1010
* @param int<10, max> $tenOrMore
1111
* @param int<-10, -5> $negative
12+
* @param int<min, -6> $smallerMinusSix
1213
* @param lowercase-string $lowercase
1314
*
1415
* @return void
1516
*/
16-
function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i, string $lowercase) {
17+
function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $smallerMinusSix, int $i, string $lowercase) {
1718
$s = "world";
1819
if (rand(0, 1)) {
1920
$s = "hello";
@@ -26,10 +27,23 @@ function doFoo($oneToThree, $threeToTen, $tenOrMore, $negative, int $i, string $
2627
assertType("'e'|'l'|'o'|'r'", $s[$oneToThree]);
2728
assertType('*ERROR*', $s[$tenOrMore]);
2829
assertType("''|'d'|'l'|'o'", $s[$threeToTen]);
29-
assertType("*ERROR*", $s[$negative]);
30+
assertType("non-empty-string", $s[$negative]);
31+
assertType("*ERROR*", $s[$smallerMinusSix]);
3032

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

3436
assertType("lowercase-string&non-empty-string", $lowercase[$i]);
3537
}
38+
39+
function bug12122()
40+
{
41+
// see https://3v4l.org/8EMdX
42+
$foo = 'fo';
43+
assertType('*ERROR*', $foo[2]);
44+
assertType("'o'", $foo[1]);
45+
assertType("'f'", $foo[0]);
46+
assertType("'o'", $foo[-1]);
47+
assertType("'f'", $foo[-2]);
48+
assertType('*ERROR*', $foo[-3]);
49+
}

tests/PHPStan/Rules/Arrays/NonexistentOffsetInArrayDimFetchRuleTest.php

+5
Original file line numberDiff line numberDiff line change
@@ -924,4 +924,9 @@ public function testInternalClassesWithOverloadedOffsetAccessInvalid84(): void
924924
$this->analyse([__DIR__ . '/data/internal-classes-overload-offset-access-invalid-php84.php'], []);
925925
}
926926

927+
public function testBug12122(): void
928+
{
929+
$this->analyse([__DIR__ . '/data/bug-12122.php'], []);
930+
}
931+
927932
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
<?php
2+
3+
namespace Bug12122;
4+
5+
function doFoo() {
6+
$foo = 'mystring';
7+
var_dump($foo[-1]);
8+
}

0 commit comments

Comments
 (0)