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
23 changes: 20 additions & 3 deletions lib/Compiler.php
Original file line number Diff line number Diff line change
Expand Up @@ -13308,12 +13308,29 @@ protected function compileExpr(Op\Expr $expr, Block $block): array {
if (null !== $mergeEcho && !$dimForWrite) {
$block->forceFreshVarSlot($expr->result, $mergeEcho);
}
$prefix = [];
$dimSlot = null !== $expr->dim
? $this->compileOperand($expr->dim, $block, true)
: null;
$resultSlot = $this->compileOperand($expr->result, $block, false);
// Echo/ternary merge phi must not share a slot with dim keys (#3790 / #5506).
// Literals: rematerialize the constant. Non-literals (e.g. `new T()` result):
// copy into a fresh temp — never forceFreshVarSlot the live producer operand,
// which remaps TYPE_NEW's result away from the object and turns the key into
// null→"" (#29532, zend_hash Illegal offset type).
if (null !== $mergeEcho && null !== $dimSlot && $dimSlot === $mergeEcho && null !== $expr->dim) {
$dimSlot = $this->freshLiteralConstantSlot($expr->dim, $block);
if ($expr->dim instanceof Operand\Literal) {
$dimSlot = $this->freshLiteralConstantSlot($expr->dim, $block);
} else {
$dimTemp = new Operand\Temporary();
$srcOp = $block->getOperand($dimSlot);
if (null !== $srcOp?->type) {
$dimTemp->type = $srcOp->type;
}
$freshDim = $block->forceFreshVarSlot($dimTemp);
$prefix[] = new OpCode(OpCode::TYPE_ASSIGN, $freshDim, $freshDim, $dimSlot);
$dimSlot = $freshDim;
}
}
if (null !== $dimSlot && $resultSlot === $dimSlot) {
$block->forceFreshVarSlot($expr->result);
Expand All @@ -13323,12 +13340,12 @@ protected function compileExpr(Op\Expr $expr, Block $block): array {
? OpCode::TYPE_ARRAY_DIM_FETCH_WRITE
: OpCode::TYPE_ARRAY_DIM_FETCH;

return [new OpCode(
return array_merge($prefix, [new OpCode(
$fetchType,
$resultSlot,
$this->compileArrayDimFetchContainerSlot($expr, $block),
$dimSlot
)];
)]);
case Op\Expr\ConstFetch::class:
$nsName = null;
if (!is_null($expr->nsName)) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
--TEST--
Language: object array key via new inside try — TypeError Illegal offset type (#29532)
--FILE--
<?php
error_reporting(E_ALL);

class T {}

class S {
public function __toString(): string {
echo "TOSTRING\n";
return 'k';
}
}

$a = [];
try {
$a[new T()] = 1;
echo "AFTER_PLAIN\n";
} catch (Throwable $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}

$b = [];
try {
$b[new S()] = 1;
echo 'keys=', json_encode(array_keys($b)), "\n";
echo "AFTER_STR\n";
} catch (Throwable $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}

$c = [];
$o = new T();
try {
$c[$o] = 1;
echo "AFTER_VAR\n";
} catch (Throwable $e) {
echo 'var ', get_class($e), ': ', $e->getMessage(), "\n";
}

echo "done\n";
--EXPECT--
TypeError: Illegal offset type
TypeError: Illegal offset type
var TypeError: Illegal offset type
done
48 changes: 48 additions & 0 deletions test/repro/illegal_offset_object_key_new_in_try_29532.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<?php
/**
* Issue #29532 — object as array key must TypeError (Illegal offset type).
* Inline `new` inside try must not be remapped to null→"" via merge-echo phi.
*/
error_reporting(E_ALL);

class T
{
}

class S
{
public function __toString(): string
{
echo "TOSTRING\n";

return 'k';
}
}

$a = [];
try {
$a[new T()] = 1;
echo "AFTER_PLAIN\n";
} catch (Throwable $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}

$b = [];
try {
$b[new S()] = 1;
echo 'keys=', json_encode(array_keys($b)), "\n";
echo "AFTER_STR\n";
} catch (Throwable $e) {
echo get_class($e), ': ', $e->getMessage(), "\n";
}

$c = [];
$o = new T();
try {
$c[$o] = 1;
echo "AFTER_VAR\n";
} catch (Throwable $e) {
echo 'var ', get_class($e), ': ', $e->getMessage(), "\n";
}

echo "done\n";
37 changes: 37 additions & 0 deletions test/unit/IllegalOffsetObjectKeyNewInTryTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\Test\Unit;

use PHPCompiler\Runtime;
use PHPUnit\Framework\TestCase;

/**
* Issue #29532 — `$a[new T()] = …` inside try must TypeError like Zend
* (zend_hash.c Illegal offset type); __toString must not run.
*/
final class IllegalOffsetObjectKeyNewInTryTest extends TestCase
{
public function testObjectKeyNewInsideTryThrowsIllegalOffsetType(): void
{
$code = file_get_contents(dirname(__DIR__).'/repro/illegal_offset_object_key_new_in_try_29532.php');
$this->assertNotFalse($code);

$runtime = new Runtime();
$block = $runtime->parseAndCompile($code, 'illegal_offset_object_key_new_in_try_29532.php');
ob_start();
$runtime->run($block);
$output = ob_get_clean();

$this->assertSame(
"TypeError: Illegal offset type\n"
."TypeError: Illegal offset type\n"
."var TypeError: Illegal offset type\n"
."done\n",
$output
);
$this->assertStringNotContainsString('TOSTRING', $output);
$this->assertStringNotContainsString('AFTER_', $output);
}
}
Loading