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
58 changes: 54 additions & 4 deletions ext/gd/GdExtensionPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,75 @@
namespace PHPCompiler\ext\gd;

/**
* ext/gd surface advertisement — php-src ext/gd/gd.c (#11675, #6215).
* ext/gd surface advertisement — php-src ext/gd/gd.c (#11675, #6215, #22740).
*
* Decode builtins ({@see imagecreatefromstring}) register when this returns true.
* Canvas drawing ({@see imagecreate}) remains stubbed until libgd parity (#3496).
* PHP-in-PHP decode/draw implementations stay in-tree, but Zend never registers
* {@code gd_info} / {@code imagecreate} / {@code GdImage} unless ext/gd is linked.
* Advertise the logical {@code gd} module only when host Zend has php-gd
* ({@see advertisesExtension()}) — same host-extension gate as #22691 (intl) /
* #11627 (curl). Do not phantom-advertise on images without php-gd (#22740).
*/
final class GdExtensionPolicy
{
/**
* extension_loaded('gd') / CREDITS_MODULES — match host Zend php-gd (#22740, re-#11675).
*
* php-src-strict: Docker reference image ships without php-gd; withhold stubs
* so function_exists / get_extension_funcs agree with Zend.
*/
public static function advertisesExtension(): bool
{
return true;
return \extension_loaded('gd');
}

/**
* imagecreatefromstring / imagepng / … — only with loaded ext/gd (#6215).
*/
public static function advertisesDecodeFromString(): bool
{
return self::advertisesExtension();
}

/**
* imagecreate / drawing surface — only with loaded ext/gd (#3496, #20415).
*/
public static function advertisesDrawing(): bool
{
return self::advertisesExtension();
}

/**
* Compliance / fixture filenames that exercise ext/gd surface (#22740).
*/
public static function isGdComplianceCase(string $testFileName): bool
{
return str_contains($testFileName, 'gd_')
|| str_contains($testFileName, 'extension_loaded_gd')
|| str_contains($testFileName, 'imagecreate')
|| str_contains($testFileName, 'imageavif')
|| str_contains($testFileName, 'imagecrop')
|| str_contains($testFileName, 'imagefilter')
|| str_contains($testFileName, 'imageflip')
|| str_contains($testFileName, 'imagettf')
|| str_contains($testFileName, 'imagewebp');
}

/** Phantom-registration guards that assert gd is withheld (#22740). */
public static function isGdPhantomComplianceCase(string $testFileName): bool
{
return str_contains($testFileName, 'gd_phantom')
|| str_contains($testFileName, 'extension_loaded_gd_phantom');
}

/**
* Run functional gd compliance when ext/gd is advertised, or phantom guards (#22740).
*/
public static function runsGdCompliance(string $testFileName): bool
{
if (self::advertisesExtension()) {
return !self::isGdPhantomComplianceCase($testFileName);
}

return self::isGdPhantomComplianceCase($testFileName);
}
}
6 changes: 4 additions & 2 deletions ext/gd/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
/**
* gd extension module entry (php-src ext/gd/gd.c; issue #7407).
*
* libgd drawing parity tracked in #3496; register under {@see standard} so
* extension_loaded('gd') stays false until libgd ships (#11675).
* Register under {@see standard}; advertise {@code gd} via
* {@see getAdditionalExtensionNames()} only when host Zend has php-gd
* ({@see GdExtensionPolicy}, #22740 / re-#11675). PHP-in-PHP decode/draw stays
* in-tree for hosts with php-gd (#3496 / #6215).
*/
class Module extends ModuleAbstract
{
Expand Down
7 changes: 6 additions & 1 deletion test/compliance/ImageTtfTextVMTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace PHPCompiler;

use PHPCompiler\ext\gd\GdExtensionPolicy;
use PHPCompiler\ext\gd\VmGdFreeType;

require_once __DIR__.'/../BaseTest.php';
Expand All @@ -15,7 +16,8 @@ final class ImageTtfTextVMTest extends BaseTest

public static function providePHPTests(): \Generator
{
if (!VmGdFreeType::available()
if (!GdExtensionPolicy::advertisesExtension()
|| !VmGdFreeType::available()
|| !\is_readable('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf')) {
return;
}
Expand All @@ -31,6 +33,9 @@ public static function providePHPTests(): \Generator

public function setUp(): void
{
if (!GdExtensionPolicy::advertisesExtension()) {
$this->markTestSkipped('host php-gd required for #6532 (#22740)');
}
if (!VmGdFreeType::available()
|| !\is_readable('/usr/share/fonts/truetype/dejavu/DejaVuSans.ttf')) {
$this->markTestSkipped('libfreetype + DejaVuSans required for #6532');
Expand Down
5 changes: 5 additions & 0 deletions test/compliance/JITTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,11 @@ public static function providePHPTests(): \Generator
&& !\PHPCompiler\ext\gd\VmGdFreeType::available()) {
continue;
}
// Host without php-gd: withhold functional gd_* / image* cases; keep phantom (#22740).
if (!\PHPCompiler\ext\gd\GdExtensionPolicy::runsGdCompliance($name)
&& \PHPCompiler\ext\gd\GdExtensionPolicy::isGdComplianceCase($name)) {
continue;
}
// VM-first (#6212/#6248/#6064): JIT hangs/OOM on create_listen / datagram accept scripts; defer JIT PHPT.
if (str_contains($name, 'socket_create_listen')
|| str_contains($name, 'socket_datagram')
Expand Down
5 changes: 5 additions & 0 deletions test/compliance/VMTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,11 @@ public static function providePHPTests(): \Generator
&& !\PHPCompiler\ext\gd\VmGdFreeType::available()) {
continue;
}
// Host without php-gd: withhold functional gd_* / image* cases; keep phantom (#22740).
if (!\PHPCompiler\ext\gd\GdExtensionPolicy::runsGdCompliance($name)
&& \PHPCompiler\ext\gd\GdExtensionPolicy::isGdComplianceCase($name)) {
continue;
}
if (!CompilerVersion::supportsStrIncrement()
&& (str_contains($name, 'str_increment') || str_contains($name, 'str_decrement'))
&& !str_contains($name, 'str_increment_phantom')) {
Expand Down
23 changes: 14 additions & 9 deletions test/compliance/cases/stdlib/extension_loaded_gd_phantom.phpt
Original file line number Diff line number Diff line change
@@ -1,17 +1,22 @@
--TEST--
stdlib extension_loaded('gd') true when decode builtins ship (#6215, #11675, ext/gd/gd.c)
stdlib extension_loaded('gd') false without host php-gd (#22740, re-#11675, ext/gd/gd.c)
--FILE--
<?php
declare(strict_types=1);

echo 'loaded=', (int) extension_loaded('gd'), "\n";
echo 'in_list=', (int) in_array('gd', get_loaded_extensions(), true), "\n";
echo 'decode=', (int) function_exists('imagecreatefromstring'), "\n";
echo 'draw=', (int) function_exists('imagecreate'), "\n";
echo 'class=', (int) class_exists('GdImage'), "\n";
echo 'funcs=', (int) (false !== get_extension_funcs('gd')), "\n";
echo 'gd_info=', (int) function_exists('gd_info'), "\n";
echo 'imagecreate=', (int) function_exists('imagecreate'), "\n";
echo 'imagecreatefromstring=', (int) function_exists('imagecreatefromstring'), "\n";
echo 'GdImage=', (int) class_exists('GdImage', false), "\n";
?>
--EXPECT--
loaded=1
in_list=1
decode=1
draw=1
class=1
loaded=0
in_list=0
funcs=0
gd_info=0
imagecreate=0
imagecreatefromstring=0
GdImage=0
8 changes: 8 additions & 0 deletions test/repro/gd_extension_loaded_phantom.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
<?php
declare(strict_types=1);

echo 'loaded=', extension_loaded('gd') ? 'yes' : 'no', "\n";
echo 'gd_info=', function_exists('gd_info') ? 'yes' : 'no', "\n";
echo 'imagecreate=', function_exists('imagecreate') ? 'yes' : 'no', "\n";
echo 'in_list=', in_array('gd', get_loaded_extensions(), true) ? 'yes' : 'no', "\n";
echo 'funcs=', false !== get_extension_funcs('gd') ? 'yes' : 'no', "\n";
69 changes: 69 additions & 0 deletions test/unit/GdExtensionPolicyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

use PHPCompiler\ext\gd\GdExtensionPolicy;
use PHPUnit\Framework\TestCase;

/** @group gd_extension_policy */
final class GdExtensionPolicyTest extends TestCase
{
public function testAdvertisesWithHostPhpGd(): void
{
if (!\extension_loaded('gd')) {
self::assertFalse(GdExtensionPolicy::advertisesExtension());
self::assertFalse(GdExtensionPolicy::advertisesDecodeFromString());
self::assertFalse(GdExtensionPolicy::advertisesDrawing());

$runtime = new Runtime();
self::assertFalse(
ext\standard\ModuleRegistry::extensionLoaded('gd')
);
self::assertFalse(
ext\standard\VmReflection::functionExists($runtime->vmContext, 'gd_info')
);
self::assertFalse(
ext\standard\VmReflection::functionExists($runtime->vmContext, 'imagecreate')
);
self::assertFalse(
ext\standard\VmReflection::classExists($runtime->vmContext, 'GdImage')
);

return;
}

self::assertTrue(GdExtensionPolicy::advertisesExtension());
self::assertTrue(GdExtensionPolicy::advertisesDecodeFromString());
self::assertTrue(GdExtensionPolicy::advertisesDrawing());

$runtime = new Runtime();
self::assertTrue(
ext\standard\ModuleRegistry::extensionLoaded('gd')
);
self::assertTrue(
ext\standard\VmReflection::functionExists($runtime->vmContext, 'gd_info')
);
self::assertTrue(
ext\standard\VmReflection::functionExists($runtime->vmContext, 'imagecreate')
);
self::assertTrue(
ext\standard\VmReflection::classExists($runtime->vmContext, 'GdImage')
);
}

public function testRunsGdComplianceAllowsPhantomWhenWithheld(): void
{
if (GdExtensionPolicy::advertisesExtension()) {
self::assertTrue(GdExtensionPolicy::runsGdCompliance('gd_imagecreate.phpt'));
self::assertFalse(GdExtensionPolicy::runsGdCompliance('extension_loaded_gd_phantom.phpt'));

return;
}

self::assertFalse(GdExtensionPolicy::runsGdCompliance('gd_imagecreate.phpt'));
self::assertTrue(GdExtensionPolicy::runsGdCompliance('extension_loaded_gd_phantom.phpt'));
self::assertTrue(GdExtensionPolicy::runsGdCompliance('gd_phantom_guard.phpt'));
}
}
44 changes: 31 additions & 13 deletions test/unit/GdModuleSkeletonTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@

namespace PHPCompiler;

use PHPCompiler\ext\gd\GdExtensionPolicy;
use PHPCompiler\ext\standard\VmReflection;
use PHPUnit\Framework\TestCase;

/**
* gd extension module skeleton registration (issue #7407).
* gd extension module registration gated on host php-gd (#7407, #22740).
*
* @group gd_skeleton
*/
Expand All @@ -18,8 +19,36 @@ public function test_gd_module_skeleton_functions_and_extension_loaded(): void
{
$runtime = new Runtime();
$ctx = $runtime->vmContext;
$fns = [
'imagecreate',
'imagecreatetruecolor',
'imagealphablending',
'imagesavealpha',
'imagecolorallocatealpha',
'imageantialias',
'imagesetthickness',
];

foreach (['imagecreate', 'imagecreatetruecolor', 'imagealphablending', 'imagesavealpha', 'imagecolorallocatealpha', 'imageantialias', 'imagesetthickness'] as $fn) {
if (!GdExtensionPolicy::advertisesExtension()) {
foreach ($fns as $fn) {
self::assertFalse(VmReflection::functionExists($ctx, $fn), $fn);
}
self::assertFalse(ext\standard\ModuleRegistry::extensionLoaded('gd'));

$code = <<<'PHP'
<?php
echo (int) function_exists('imagecreate');
echo (int) extension_loaded('gd');
PHP;
$block = $runtime->parseAndCompile($code, 'gd_module.php');
ob_start();
$runtime->run($block);
self::assertSame('00', ob_get_clean());

return;
}

foreach ($fns as $fn) {
self::assertTrue(VmReflection::functionExists($ctx, $fn), $fn);
}

Expand All @@ -39,15 +68,4 @@ public function test_gd_module_skeleton_functions_and_extension_loaded(): void
$runtime->run($block);
self::assertSame('11111111', ob_get_clean());
}

public function test_imagecreate_stub_throws_error(): void
{
$runtime = new Runtime();
$fn = new \PHPCompiler\ext\gd\imagecreate();
$frame = $fn->getFrame($runtime->vmContext);

$this->expectException(\Error::class);
$this->expectExceptionMessage('imagecreate() is not implemented in this compiler build (issue #3496)');
$fn->execute($frame);
}
}
5 changes: 5 additions & 0 deletions test/unit/ImagecreatefromstringTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@

namespace PHPCompiler;

use PHPCompiler\ext\gd\GdExtensionPolicy;
use PHPUnit\Framework\TestCase;

/**
Expand All @@ -13,6 +14,10 @@ final class ImagecreatefromstringTest extends TestCase
{
public function test_png_decode_and_round_trip(): void
{
if (!GdExtensionPolicy::advertisesExtension()) {
self::markTestSkipped('host php-gd required for imagecreatefromstring (#22740)');
}

$runtime = new Runtime();
$code = <<<'PHP'
<?php
Expand Down