Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions src/Type/Php/OutputBufferingDynamicReturnTypeExtension.php
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,10 @@
use PHPStan\DependencyInjection\AutowiredService;
use PHPStan\Reflection\FunctionReflection;
use PHPStan\Reflection\ParametersAcceptorSelector;
use PHPStan\Type\Constant\ConstantBooleanType;
use PHPStan\Type\DynamicFunctionReturnTypeExtension;
use PHPStan\Type\IntegerRangeType;
use PHPStan\Type\Type;
use PHPStan\Type\TypeCombinator;
use PHPStan\Type\TypeUtils;
use function in_array;

/**
Expand Down Expand Up @@ -48,7 +47,10 @@ public function getTypeFromFunctionCall(

$outputBufferLevelType = $scope->getType(new FuncCall(new Name('ob_get_level'), []));
if (IntegerRangeType::createAllGreaterThanOrEqualTo(1)->isSuperTypeOf($outputBufferLevelType)->yes()) {
return TypeCombinator::remove($defaultReturnType, new ConstantBooleanType(false));
// checking error state return values of ob_* functions is essentially useless
// as this usually means that your system is out of memory and your process is going to die anyway.
// that's why error states oftentimes are not checked.
return TypeUtils::toBenevolentUnion($defaultReturnType);
}

return $defaultReturnType;
Expand Down
52 changes: 26 additions & 26 deletions tests/PHPStan/Analyser/nsrt/output-buffering.php
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@ function activeBuffer(): void
{
ob_start();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_contents());
assertType('int', ob_get_length());
assertType('(string|false)', ob_get_contents());
assertType('(int|false)', ob_get_length());
}

function obCleanAndFlushKeepBuffer(): void
Expand All @@ -27,17 +27,17 @@ function obCleanAndFlushKeepBuffer(): void
assertType('int<1, max>', ob_get_level());
ob_clean();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_contents());
assertType('(string|false)', ob_get_contents());
ob_flush();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_contents());
assertType('(string|false)', ob_get_contents());
}

function getCleanClosesBuffer(): void
{
ob_start();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
assertType('int<0, max>', ob_get_level());
assertType('string|false', ob_get_contents());
}
Expand All @@ -46,7 +46,7 @@ function getFlushClosesBuffer(): void
{
ob_start();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_flush());
assertType('(string|false)', ob_get_flush());
assertType('int<0, max>', ob_get_level());
assertType('string|false', ob_get_contents());
}
Expand All @@ -55,7 +55,7 @@ function endCleanClosesBuffer(): void
{
ob_start();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_contents());
assertType('(string|false)', ob_get_contents());
ob_end_clean();
assertType('int<0, max>', ob_get_level());
assertType('string|false', ob_get_contents());
Expand All @@ -65,7 +65,7 @@ function endFlushClosesBuffer(): void
{
ob_start();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_contents());
assertType('(string|false)', ob_get_contents());
ob_end_flush();
assertType('int<0, max>', ob_get_level());
assertType('string|false', ob_get_contents());
Expand All @@ -77,10 +77,10 @@ function nested(): void
assertType('int<1, max>', ob_get_level());
ob_start();
assertType('int<2, max>', ob_get_level());
assertType('string', ob_get_contents());
assertType('(string|false)', ob_get_contents());
ob_end_clean();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_contents());
assertType('(string|false)', ob_get_contents());
ob_end_clean();
assertType('int<0, max>', ob_get_level());
assertType('string|false', ob_get_contents());
Expand All @@ -99,15 +99,15 @@ function fullyQualified(): void
{
\ob_start();
assertType('int<1, max>', ob_get_level());
assertType('string', \ob_get_contents());
assertType('string', ob_get_contents());
assertType('(string|false)', \ob_get_contents());
assertType('(string|false)', ob_get_contents());
}

function levelNarrowedToConstInt(): void
{
if (ob_get_level() === 2) {
assertType('2', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
// closing call decrements the const-int level, keeping it exact
assertType('1', ob_get_level());
}
Expand All @@ -123,18 +123,18 @@ function levelNarrowedToIntRange(): void
{
if (ob_get_level() >= 1) {
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
}
}

function levelNarrowedToUnionInt(): void
{
if (ob_get_level() === 1 || ob_get_level() === 3) {
assertType('1|3', ob_get_level());
assertType('string', ob_get_contents());
assertType('(string|false)', ob_get_contents());
ob_start();
assertType('2|4', ob_get_level());
assertType('string', ob_get_contents());
assertType('(string|false)', ob_get_contents());
}
}

Expand All @@ -150,7 +150,7 @@ function levelNarrowedToBoundedIntRange(): void
{
if (ob_get_level() >= 2 && ob_get_level() <= 5) {
assertType('int<2, 5>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
// closing call shifts the whole range down, preserving the upper bound
assertType('int<1, 4>', ob_get_level());
}
Expand Down Expand Up @@ -180,7 +180,7 @@ function pureCallableKeepsLevel(callable $cb): void
ob_start();
$cb();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
}

function impureFunctionForgetsLevel(): void
Expand All @@ -206,7 +206,7 @@ function pureFunctionKeepsLevel(): void
ob_start();
$x=pureFunction();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
}

class Service
Expand Down Expand Up @@ -245,7 +245,7 @@ function pureMethodKeepsLevel(Service $service): void
ob_start();
$x=$service->pureMethod();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
}

function impureStaticMethodForgetsLevel(): void
Expand All @@ -269,23 +269,23 @@ function arrayMapPureCallbackKeepsLevel(array $a): void
ob_start();
array_map('strtoupper', $a);
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
}

function laterInvokedCallableKeepsLevel(callable $cb): void
{
ob_start();
register_shutdown_function($cb);
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
}

function builtinKeepsLevel(): void
{
ob_start();
printf('hello');
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
}

class WithImpureConstructor
Expand Down Expand Up @@ -326,15 +326,15 @@ function pureConstructorKeepsLevel(): void
ob_start();
new WithPureConstructor();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
}

function noConstructorKeepsLevel(): void
{
ob_start();
new WithoutConstructor();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
}

/** @param class-string $className */
Expand All @@ -351,7 +351,7 @@ function builtinConstructorKeepsLevel(): void
ob_start();
new \ArrayObject();
assertType('int<1, max>', ob_get_level());
assertType('string', ob_get_clean());
assertType('(string|false)', ob_get_clean());
}

function withRequire(): void
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1261,6 +1261,11 @@ public function testBug14878(): void
$this->analyse([__DIR__ . '/../../Analyser/nsrt/bug-14878.php'], []);
}

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

public function testBug14847(): void
{
$this->analyse([__DIR__ . '/data/bug-14847.php'], [
Expand Down
15 changes: 15 additions & 0 deletions tests/PHPStan/Rules/Comparison/data/bug-14985.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php

declare(strict_types=1);

namespace Bug14985;

ob_start();
$a = ob_get_clean();

// There is no check whether ob_start() was successful, so the if condition cannot be guaranteed to be always false, as PHPStan claims.
if ($a === false) {
echo "false";
}


Loading