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
31 changes: 31 additions & 0 deletions lib/VM.php
Original file line number Diff line number Diff line change
Expand Up @@ -5483,6 +5483,32 @@ static function () use ($arg3, $writeTarget, $dnfCtx, $strict): void {
}
} else {
$scriptFile = '' !== $frame->scriptPath ? $frame->scriptPath : null;
// Resource as array subject — Zend Warning/null on read, scalar Error on write (#30028).
if (
Variable::TYPE_OBJECT === $container->type
&& VM\ResourceSupport::isResourceObject($container->toObject())
) {
if ($forWrite) {
$catchFrame = $this->dispatchVmError(
TypeCheck::SCALAR_USED_AS_ARRAY_MESSAGE,
$frame
);
if (null !== $catchFrame) {
$frame = $catchFrame;
goto restart;
}
break;
}
if (!$fetchIs) {
$this->context->errors->arrayOffsetOnResource(
$this->context,
$frame,
$scriptFile
);
}
$arg1->null();
break;
}
if (!$forWrite && TypeCheck::isScalarNonContainerDimRead($container)) {
if (!$fetchIs) {
$resolved = $container->resolveIndirect();
Expand Down Expand Up @@ -9389,6 +9415,11 @@ function () use ($frame, $op, $arg1, $strict, $arraySpec, $vmContext): void {
break;
}
if (!$op->issetOnProperty) {
// Resource as array subject — isset soft-false like scalars (zend_execute.c, #30028).
if (VM\ResourceSupport::isResourceObject($object)) {
$dst->bool(false);
break;
}
// isset($obj[$k]) without has_dimension / ArrayAccess — Zend Error
// (ResourceBundle has read_dimension only; #25145).
$catchFrame = $this->dispatchVmError(
Expand Down
30 changes: 30 additions & 0 deletions lib/VM/ErrorReporter.php
Original file line number Diff line number Diff line change
Expand Up @@ -325,6 +325,20 @@ public static function arrayOffsetOnNonContainerMessage(string $typeName): strin
return "Trying to access array offset on value of type {$typeName}";
}

/**
* Zend E_WARNING for dim-read on a resource container (zend_execute.c, #30028).
*
* PHP 8.3+ shortens "on value of type resource" → "on resource"; PROFILE≥8.3 matches.
*/
public static function arrayOffsetOnResourceMessage(): string
{
if (version_compare(\PHPCompiler\CompilerVersion::languageProfileVersion(), '8.3.0', '>=')) {
return 'Trying to access array offset on resource';
}

return self::arrayOffsetOnNonContainerMessage('resource');
}

/**
* Zend E_WARNING for ZEND_FETCH_DIM_R on scalars (zend_execute.c, #4867).
*/
Expand All @@ -342,6 +356,22 @@ public function arrayOffsetOnNonContainer(
);
}

/**
* Zend E_WARNING for ZEND_FETCH_DIM_R when the container is a resource (#30028).
*/
public function arrayOffsetOnResource(
?Context $context = null,
?Frame $frame = null,
?string $file = null
): void {
$this->emitWarning(
self::arrayOffsetOnResourceMessage(),
$context,
$frame,
$file
);
}

/**
* Zend E_WARNING for undefined instance property read (zend_object_handlers.c, #14938).
*/
Expand Down
6 changes: 6 additions & 0 deletions lib/VM/VmEmptyDimension.php
Original file line number Diff line number Diff line change
Expand Up @@ -93,6 +93,12 @@ public static function evaluate(

return null;
}
// Resource as array subject — empty soft-true like scalars (zend_execute.c, #30028).
if (ResourceSupport::isResourceObject($object)) {
$dst->bool(true);

return null;
}
if (!$vm->objectImplementsArrayAccess($object)) {
$className = $object->class->name;
$catchFrame = $vm->propagateEmptyDimensionError(
Expand Down
36 changes: 36 additions & 0 deletions test/compliance/ResourceDimFetch30028JITTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

/**
* JIT: resource as array subject — Warning/null read, scalar Error write, soft isset/empty (#30028).
*/
require_once __DIR__.'/../BaseTest.php';

final class ResourceDimFetch30028JITTest extends BaseTest
{
protected static string $DIR = __DIR__;

public static function providePHPTests(): \Generator
{
yield 'resource_dim_fetch.phpt' => self::parsePHPT(
__DIR__.'/cases/language/resource_dim_fetch.phpt',
'resource_dim_fetch.phpt'
);
}

public function setUp(): void
{
$this->BIN = realpath(__DIR__.'/../../bin/jit.php');
putenv('PHP_COMPILER_PROFILE=8.4');
$_ENV['PHP_COMPILER_PROFILE'] = '8.4';
}

public function tearDown(): void
{
putenv('PHP_COMPILER_PROFILE');
unset($_ENV['PHP_COMPILER_PROFILE']);
}
}
36 changes: 36 additions & 0 deletions test/compliance/ResourceDimFetch30028VMTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

/**
* VM: resource as array subject — Warning/null read, scalar Error write, soft isset/empty (#30028).
*/
require_once __DIR__.'/../BaseTest.php';

final class ResourceDimFetch30028VMTest extends BaseTest
{
protected static string $DIR = __DIR__;

public static function providePHPTests(): \Generator
{
yield 'resource_dim_fetch.phpt' => self::parsePHPT(
__DIR__.'/cases/language/resource_dim_fetch.phpt',
'resource_dim_fetch.phpt'
);
}

public function setUp(): void
{
$this->BIN = realpath(__DIR__.'/../../bin/vm.php');
putenv('PHP_COMPILER_PROFILE=8.4');
$_ENV['PHP_COMPILER_PROFILE'] = '8.4';
}

public function tearDown(): void
{
putenv('PHP_COMPILER_PROFILE');
unset($_ENV['PHP_COMPILER_PROFILE']);
}
}
36 changes: 36 additions & 0 deletions test/compliance/cases/language/resource_dim_fetch.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
--TEST--
Language: resource as array subject — Warning on read, scalar Error on write, soft isset/empty (#30028)
--FILE--
<?php
function warn_capture(int $errno, string $message): bool
{
echo 'W:', $message, "\n";

return true;
}
set_error_handler('warn_capture');

$f = fopen('php://memory', 'r');
try {
var_export($f[0]);
echo "\n";
} catch (Throwable $e) {
echo get_class($e), ':', $e->getMessage(), "\n";
}
try {
$f[0] = 1;
echo "write ok\n";
} catch (Throwable $e) {
echo get_class($e), ':', $e->getMessage(), "\n";
}
var_export(isset($f[0]));
echo "\n";
var_export(empty($f[0]));
echo "\n";
fclose($f);
--EXPECTF--
W:Trying to access array offset on %Sresource
NULL
Error:Cannot use a scalar value as an array
false
true
74 changes: 74 additions & 0 deletions test/unit/ResourceDimFetchTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\Test\Unit;

use PHPCompiler\VM\ErrorReporter;
use PHPUnit\Framework\TestCase;

/**
* Resource used as array container — Zend dim semantics (#30028).
*/
final class ResourceDimFetchTest extends TestCase
{
public function testResourceOffsetWarningMessageUnderProfile84(): void
{
$prev = getenv('PHP_COMPILER_PROFILE');
putenv('PHP_COMPILER_PROFILE=8.4');
try {
$this->assertSame(
'Trying to access array offset on resource',
ErrorReporter::arrayOffsetOnResourceMessage()
);
} finally {
if (false === $prev) {
putenv('PHP_COMPILER_PROFILE');
} else {
putenv('PHP_COMPILER_PROFILE='.$prev);
}
}
}

public function testVmResourceDimMatchesZendShape(): void
{
$code = <<<'PHP'
<?php
set_error_handler(static function (int $errno, string $message): bool {
echo 'W:', $message, "\n";
return true;
});
$f = fopen('php://memory', 'r');
try {
var_export($f[0]);
echo "\n";
} catch (Throwable $e) {
echo get_class($e), ':', $e->getMessage(), "\n";
}
try {
$f[0] = 1;
} catch (Throwable $e) {
echo get_class($e), ':', $e->getMessage(), "\n";
}
var_export(isset($f[0]));
echo "\n";
var_export(empty($f[0]));
echo "\n";
fclose($f);
PHP;
$path = sys_get_temp_dir().'/phpc_resource_dim_30028.php';
file_put_contents($path, $code);
$cmd = 'PHP_COMPILER_PROFILE=8.4 '
.escapeshellarg(PHP_BINARY).' -d error_reporting=E_ALL -d display_errors=1 '
.escapeshellarg(dirname(__DIR__, 2).'/bin/vm.php').' '
.escapeshellarg($path).' 2>&1';
$output = shell_exec($cmd) ?? '';
@unlink($path);

$this->assertStringContainsString('W:Trying to access array offset on resource', $output);
$this->assertStringContainsString("NULL\n", $output);
$this->assertStringContainsString('Error:Cannot use a scalar value as an array', $output);
$this->assertStringContainsString("false\ntrue\n", $output);
$this->assertStringNotContainsString('Cannot use object of type Resource as array', $output);
}
}
Loading