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
6 changes: 6 additions & 0 deletions bin/compile.php
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,12 @@ function phpc_compile_skip_aot_bundle(string $normalized): bool
return true;
}

// Mini-spine for #29111: must exercise IncludeHelper inherit (not SourceBundler)
// so REMAP=0 Literal collision on CONST_FETCH is covered.
if (str_contains($normalized, 'test/selfhost/issue_29111_const_fetch_spine/')) {
return true;
}

// Inventory compile_driver pulls Runtime's transitive closure through
// LiteralIncludeDiscovery — bundling OOMs through 24GiB (#23970).
return str_contains($normalized, 'test/selfhost/')
Expand Down
22 changes: 19 additions & 3 deletions lib/Block.php
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,12 @@ public function getOperand(int $offset): ?Operand {
return $operand;
}
if ($operand instanceof Operand\Literal) {
$fallback = $operand;
// Keep the first Literal at this slot. inheritScopeFrom appends parent
// Literals after the callee's; overwriting would make CONST_FETCH resolve
// e.g. parent "defined" instead of callee "true" (#29111).
if (null === $fallback) {
$fallback = $operand;
}

continue;
}
Expand Down Expand Up @@ -904,6 +909,10 @@ private function isArgSlot(int $slot): bool
* assign fresh slots if the parent's index is already used so {@see getOperand()}
* cannot return a callee temp for a parent local (#22845 MiniWebApp nested
* $appName). CFG branch inheritance must keep shared slot indices (default).
* Zend full-spine may set PHP_COMPILER_INCLUDE_SCOPE_REMAP=0 so VarOperand
* indices stay shared for pace (#22642); Literal operands still remapped on
* collision so CONST_FETCH cannot resolve a parent string (e.g. "defined")
* instead of the callee's "true" (#29111).
*/
public function inheritScopeFrom(Block $parent, bool $remapCollidingSlots = false): void
{
Expand All @@ -913,8 +922,15 @@ public function inheritScopeFrom(Block $parent, bool $remapCollidingSlots = fals
}
$parentSlot = $parent->scope[$operand];
$slot = $parentSlot;
if ($remapCollidingSlots && null !== $this->operandForScopeSlot($slot)) {
$slot = $this->nextScopeSlot();
$occupant = $this->operandForScopeSlot($slot);
if (null !== $occupant) {
if ($remapCollidingSlots) {
$slot = $this->nextScopeSlot();
} elseif ($operand instanceof Operand\Literal) {
// REMAP=0 keeps VarOperand indices shared; never alias parent
// Literals onto callee slots used by CONST_FETCH (#29111).
$slot = $this->nextScopeSlot();
}
}
$this->scope[$operand] = $slot;
if ($parent->args->contains($operand)) {
Expand Down
16 changes: 16 additions & 0 deletions test/selfhost/issue_29111_const_fetch_spine/data.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
<?php
// Nested bool literals — CONST_FETCH 'true' / 'false' (#29111).
return array(
'acdt' => array(
0 => array(
'dst' => true,
'offset' => 37800,
'timezone_id' => 'Australia/Adelaide',
),
1 => array(
'dst' => false,
'offset' => 34200,
'timezone_id' => 'Australia/Adelaide',
),
),
);
15 changes: 15 additions & 0 deletions test/selfhost/issue_29111_const_fetch_spine/main.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
<?php
declare(strict_types=1);
/**
* Mini-spine: predecessor `defined()` Literals then require nested bool array.
* Compiled with SourceBundler skipped + PHP_COMPILER_INCLUDE_SCOPE_REMAP=0 (#29111).
*/
defined('PHP_VERSION');
defined('PHP_OS');
defined('DIRECTORY_SEPARATOR');
defined('PHP_EOL');
defined('PHP_INT_MAX');
defined('PHP_INT_MIN');
defined('PHP_FLOAT_MAX');
$tz = require __DIR__.'/data.php';
echo count($tz), "\n";
12 changes: 12 additions & 0 deletions test/selfhost/issue_29111_const_fetch_spine/main_real_tz.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
<?php
declare(strict_types=1);
/**
* Same shape as spine {main} hitting TimezoneAbbreviationsData (#29111).
*/
defined('PHP_VERSION');
defined('PHP_OS');
defined('DIRECTORY_SEPARATOR');
defined('PHP_EOL');
defined('PHP_INT_MAX');
$tz = require __DIR__.'/../../../ext/standard/TimezoneAbbreviationsData.php';
echo count($tz), "\n";
48 changes: 48 additions & 0 deletions test/unit/BootstrapAotJitCompileTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,54 @@ public function testTimezoneAbbreviationsDataRequireCompiles(): void
$this->assertStringNotContainsString('loadHashtablePointer on native string', $stderr);
}

/**
* Mini-spine IncludeHelper path with REMAP=0 must not hit Undefined constant "defined" (#29111).
*/
public function testConstFetchSpineRemapOffCompiles(): void
{
$this->skipUnlessLlvmReady();
$repoRoot = dirname(__DIR__, 2);
$entry = $repoRoot.'/test/selfhost/issue_29111_const_fetch_spine/main.php';
$this->assertFileExists($entry);

$outfile = tempnam(sys_get_temp_dir(), 'issue29111_aot_');
$this->assertNotFalse($outfile);
unlink($outfile);

$env = [];
foreach (array_merge($_ENV, $_SERVER) as $key => $value) {
if (is_string($value)) {
$env[$key] = $value;
}
}
LlvmToolchain::applyProcessEnv($env, $repoRoot);
$env['PHP_COMPILER_INCLUDE_SCOPE_REMAP'] = '0';

$compileArgv = array_merge(
LlvmToolchain::envPrefix($repoRoot),
[PHP_BINARY, $repoRoot.'/bin/compile.php', '-o', $outfile, $entry]
);
$descriptorSpec = [
0 => ['pipe', 'r'],
1 => ['pipe', 'w'],
2 => ['pipe', 'w'],
];
$compile = proc_open($compileArgv, $descriptorSpec, $pipes, $repoRoot, $env);
$this->assertIsResource($compile);
fclose($pipes[0]);
$stdout = stream_get_contents($pipes[1]);
fclose($pipes[1]);
$stderr = stream_get_contents($pipes[2]);
fclose($pipes[2]);
$code = proc_close($compile);
$combined = $stdout.$stderr;
$this->assertStringNotContainsString('Undefined constant "defined"', $combined);
$this->assertStringNotContainsString('Undefined constant \'defined\'', $combined);
$this->assertSame(0, $code, $combined);
$this->assertFileExists($outfile);
@unlink($outfile);
}

/** Self-host AOT: `new Runtime()` must not segfault LLVM 9 (#2600). */
public function testSelfHostAotNewRuntimeCompiles(): void
{
Expand Down
101 changes: 101 additions & 0 deletions test/unit/IncludeScopeConstFetchLiteralTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
<?php

declare(strict_types=1);

namespace PHPCompiler\Test\Unit;

use PHPCfg\Operand\Literal;
use PHPCompiler\OpCode;
use PHPCompiler\Runtime;
use PHPUnit\Framework\TestCase;

/**
* Include inherit with PHP_COMPILER_INCLUDE_SCOPE_REMAP=0 must not corrupt CONST_FETCH
* literals (TimezoneAbbreviationsData / spine {main}, #29111).
*/
final class IncludeScopeConstFetchLiteralTest extends TestCase
{
public function testRemapOffDoesNotReplaceCalleeTrueWithParentDefined(): void
{
$runtime = new Runtime();
$parent = $runtime->parseAndCompile(
'<?php defined("PHP_VERSION"); defined("PHP_OS");',
'issue_29111_parent.php'
);
$child = $runtime->parseAndCompile(
'<?php return ["dst" => true];',
'issue_29111_child.php'
);
$this->assertNotNull($parent);
$this->assertNotNull($child);

$before = [];
foreach ($child->opCodes as $i => $op) {
if (OpCode::TYPE_CONST_FETCH !== $op->type) {
continue;
}
$name = $child->getOperand($op->arg2);
$this->assertInstanceOf(Literal::class, $name);
$before[$i] = $name->value;
}
$this->assertNotEmpty($before);
$this->assertContains('true', $before);

$child->inheritScopeFrom($parent, false);

foreach ($child->opCodes as $i => $op) {
if (OpCode::TYPE_CONST_FETCH !== $op->type || !isset($before[$i])) {
continue;
}
$name = $child->getOperand($op->arg2);
$this->assertInstanceOf(Literal::class, $name);
$this->assertSame(
$before[$i],
$name->value,
'CONST_FETCH op '.$i.' must keep callee literal after inheritScopeFrom(REMAP=0)'
);
$this->assertNotSame(
'defined',
$name->value,
'parent defined() name must not steal CONST_FETCH slot (#29111)'
);
}
}

public function testRemapOffStillAllowsVarOperandSharedSlots(): void
{
$runtime = new Runtime();
$parent = $runtime->parseAndCompile(
'<?php $x = 1;',
'issue_29111_var_parent.php'
);
$child = $runtime->parseAndCompile(
'<?php $y = 2;',
'issue_29111_var_child.php'
);
$this->assertNotNull($parent);
$this->assertNotNull($child);

$child->inheritScopeFrom($parent, false);

$parentSlots = [];
foreach ($parent->scopedOperands() as $op) {
$slot = $parent->slotForOperand($op);
if (null !== $slot) {
$parentSlots[$slot] = true;
}
}
$shared = 0;
foreach ($child->scopedOperands() as $op) {
$slot = $child->slotForOperand($op);
if (null !== $slot && isset($parentSlots[$slot])) {
++$shared;
}
}
$this->assertGreaterThan(
0,
$shared,
'REMAP=0 must still share some VarOperand slot indices for spine pace (#22642)'
);
}
}
Loading