Skip to content

Commit 965a85c

Browse files
committed
stream_get_contents() does not return FALSE unless an invalid position is provided
1 parent 5d8c209 commit 965a85c

File tree

3 files changed

+76
-0
lines changed

3 files changed

+76
-0
lines changed
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
<?php declare(strict_types = 1);
2+
3+
namespace PHPStan\Type\Php;
4+
5+
use PhpParser\Node\Expr\FuncCall;
6+
use PHPStan\Analyser\Scope;
7+
use PHPStan\DependencyInjection\AutowiredService;
8+
use PHPStan\Reflection\FunctionReflection;
9+
use PHPStan\Reflection\ParametersAcceptorSelector;
10+
use PHPStan\Type\Constant\ConstantBooleanType;
11+
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
12+
use PHPStan\Type\Type;
13+
use PHPStan\Type\TypeCombinator;
14+
use function count;
15+
16+
#[AutowiredService]
17+
final class StreamGetContentsFunctionReturnTypeExtension implements DynamicFunctionReturnTypeExtension
18+
{
19+
20+
public function isFunctionSupported(FunctionReflection $functionReflection): bool
21+
{
22+
return $functionReflection->getName() === 'stream_get_contents';
23+
}
24+
25+
public function getTypeFromFunctionCall(FunctionReflection $functionReflection, FuncCall $functionCall, Scope $scope): ?Type
26+
{
27+
if (count($functionCall->getArgs()) >= 3) {
28+
return null;
29+
}
30+
31+
// stream_get_contents() does not return FALSE unless an invalid offset is provided.
32+
$returnType = ParametersAcceptorSelector::selectFromArgs(
33+
$scope,
34+
$functionCall->getArgs(),
35+
$functionReflection->getVariants(),
36+
)->getReturnType();
37+
38+
return TypeCombinator::remove($returnType, new ConstantBooleanType(false));
39+
}
40+
41+
}

tests/PHPStan/Rules/Methods/CallMethodsRuleTest.php

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3572,4 +3572,19 @@ public function testBug13171(): void
35723572
]);
35733573
}
35743574

3575+
public function testBug3396(): void
3576+
{
3577+
$this->checkThisOnly = false;
3578+
$this->checkNullables = false;
3579+
$this->checkUnionTypes = true;
3580+
$this->checkExplicitMixed = false;
3581+
3582+
$this->analyse([__DIR__ . '/data/bug-3396.php'], [
3583+
[
3584+
'Parameter #1 $s of method Bug3396\HelloWorld::takesString() expects string, string|false given.',
3585+
18,
3586+
],
3587+
]);
3588+
}
3589+
35753590
}
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
<?php
2+
3+
namespace Bug3396;
4+
5+
class HelloWorld
6+
{
7+
8+
public function takesString(string $s): void
9+
{
10+
}
11+
12+
public function sayHello(): void
13+
{
14+
$stream = fopen("file.txt", "rb");
15+
if ($stream === false) throw new \Error("wtf");
16+
$this->takesString(stream_get_contents($stream));
17+
$this->takesString(stream_get_contents($stream, 1));
18+
$this->takesString(stream_get_contents($stream, 1, 1));
19+
}
20+
}

0 commit comments

Comments
 (0)