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
1,277 changes: 638 additions & 639 deletions docs/bootstrap-inventory.md

Large diffs are not rendered by default.

9 changes: 8 additions & 1 deletion ext/soap/Module.php
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,11 @@ public function getExtensionVersion(): string

public function init(Runtime $runtime): void
{
require_once __DIR__.'/bootstrap_soapfault.php';
parent::init($runtime);
if (!SoapExtensionPolicy::advertisesExtension()) {
return;
}
require_once __DIR__.'/bootstrap_soapfault.php';
// php-src SOAP_RINIT: use_soap_error_handler = 0
SoapExtensionPolicy::setUseSoapErrorHandler(false);
foreach (SoapConstants::registeredConstants() as $name => $value) {
Expand All @@ -44,6 +47,10 @@ public function init(Runtime $runtime): void

public function getFunctions(): array
{
if (!SoapExtensionPolicy::advertisesExtension()) {
return [];
}

return [
new is_soap_fault(),
new use_soap_error_handler(),
Expand Down
46 changes: 43 additions & 3 deletions ext/soap/SoapExtensionPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,66 @@
namespace PHPCompiler\ext\soap;

/**
* ext/soap advertisement — php-src ext/soap/soap.c (#20124 / #3724 / #20267).
* ext/soap advertisement — php-src ext/soap/soap.c (#20124 / #3724 / #20267 / #22859).
*
* SoapClient / SoapServer / SoapFault stay in-tree (PHP-in-PHP) but are withheld from
* extension_loaded() / class_exists() on the reference harness when host Zend has no
* php-soap — same shape as yaml/brotli (#6275 / #17563). Enable via host ext/soap or
* `PHP_COMPILER_PROFILE=8.4` ({@see CompilerVersion::supportsSoap()}).
*
* Soap module is always loaded (VM-only surface); SoapFault follows.
* Tracks SOAP_GLOBAL(use_soap_error_handler) for soap_error_handler (#20267).
*/
final class SoapExtensionPolicy
{
/** php-src SOAP_GLOBAL(use_soap_error_handler) — MINIT/RINIT default false. */
private static bool $useSoapErrorHandler = false;

/**
* extension_loaded('soap') / CREDITS_MODULES — match Zend without phantom soap (#22859).
*/
public static function advertisesExtension(): bool
{
return true;
if (\extension_loaded('soap')) {
return true;
}

return \PHPCompiler\CompilerVersion::supportsSoap();
}

public static function advertisesExceptionClass(): bool
{
return self::advertisesExtension();
}

/** Compliance filenames that exercise SoapClient / SoapServer / SoapFault. */
public static function isSoapComplianceCase(string $testFileName): bool
{
return str_contains($testFileName, 'soap_')
|| str_contains($testFileName, 'soapclient')
|| str_contains($testFileName, 'soapserver')
|| str_contains($testFileName, 'soapfault')
|| str_contains($testFileName, 'is_soap_fault')
|| str_contains($testFileName, 'use_soap_error_handler')
|| str_contains($testFileName, 'extension_loaded_soap');
}

/** Phantom-registration guards that assert soap is withheld (#22859). */
public static function isSoapPhantomComplianceCase(string $testFileName): bool
{
return str_contains($testFileName, 'soap_phantom')
|| str_contains($testFileName, 'extension_loaded_soap_phantom');
}

/** Run functional soap compliance when advertised, else phantom only (#22859). */
public static function runsSoapCompliance(string $testFileName): bool
{
if (self::advertisesExtension()) {
return !self::isSoapPhantomComplianceCase($testFileName);
}

return self::isSoapPhantomComplianceCase($testFileName);
}

public static function useSoapErrorHandler(): bool
{
return self::$useSoapErrorHandler;
Expand Down
6 changes: 6 additions & 0 deletions ext/standard/BuiltinIntrospectionPolicy.php
Original file line number Diff line number Diff line change
Expand Up @@ -224,6 +224,9 @@ public static function functionIsAdvertised(string $functionName): bool
if ('fastcgi_finish_request' === $lc) {
return VmFastCgi::registersFinishRequestFunction();
}
if (\in_array($lc, ['is_soap_fault', 'use_soap_error_handler'], true)) {
return \PHPCompiler\ext\soap\SoapExtensionPolicy::advertisesExtension();
}
if (\in_array($lc, ['class_has_method', 'class_has_property', 'class_has_constant'], true)) {
return CompilerVersion::supportsClassHasFunctions();
}
Expand Down Expand Up @@ -288,6 +291,9 @@ public static function extensionIsAdvertised(string $extension): bool
if ('gnupg' === $ext) {
return \PHPCompiler\ext\gnupg\GnupgExtensionPolicy::advertisesExtension();
}
if ('soap' === $ext) {
return \PHPCompiler\ext\soap\SoapExtensionPolicy::advertisesExtension();
}

return true;
}
Expand Down
6 changes: 4 additions & 2 deletions ext/standard/ModuleRegistry.php
Original file line number Diff line number Diff line change
Expand Up @@ -153,8 +153,10 @@ public static function registerModule(Module $module): void
&& !\PHPCompiler\ext\snmp\SnmpExtensionPolicy::advertisesExtension();
$withholdFfiSurface = 'ffi' === $primary
&& !\PHPCompiler\ext\ffi\FfiExtensionPolicy::advertisesExtension();
$withholdSoapSurface = 'soap' === $primary
&& !\PHPCompiler\ext\soap\SoapExtensionPolicy::advertisesExtension();

if (!$withholdOpensslSurface && !$withholdSqlite3Surface && !$withholdLdapSurface && !$withholdInotifySurface && !$withholdXslSurface && !$withholdXmlrpcSurface && !$withholdWddxSurface && !$withholdYamlSurface && !$withholdRedisSurface && !$withholdMongodbSurface && !$withholdSnmpSurface && !$withholdFfiSurface) {
if (!$withholdOpensslSurface && !$withholdSqlite3Surface && !$withholdLdapSurface && !$withholdInotifySurface && !$withholdXslSurface && !$withholdXmlrpcSurface && !$withholdWddxSurface && !$withholdYamlSurface && !$withholdRedisSurface && !$withholdMongodbSurface && !$withholdSnmpSurface && !$withholdFfiSurface && !$withholdSoapSurface) {
self::register($module->getExtensionName(), $moduleVersion);
} elseif ($registerSqlite3ExtensionLoaded) {
self::register($module->getExtensionName(), $moduleVersion);
Expand All @@ -181,7 +183,7 @@ public static function registerModule(Module $module): void
continue;
}
$fnName = strtolower($func->getName());
if ($withholdOpensslSurface || $withholdSqlite3Surface || $withholdLdapSurface || $withholdInotifySurface || $withholdXslSurface || $withholdXmlrpcSurface || $withholdWddxSurface || $withholdYamlSurface || $withholdRedisSurface || $withholdMongodbSurface || $withholdSnmpSurface || $withholdFfiSurface) {
if ($withholdOpensslSurface || $withholdSqlite3Surface || $withholdLdapSurface || $withholdInotifySurface || $withholdXslSurface || $withholdXmlrpcSurface || $withholdWddxSurface || $withholdYamlSurface || $withholdRedisSurface || $withholdMongodbSurface || $withholdSnmpSurface || $withholdFfiSurface || $withholdSoapSurface) {
self::registerBuiltinLookup($fnName);

continue;
Expand Down
12 changes: 12 additions & 0 deletions lib/CompilerVersion.php
Original file line number Diff line number Diff line change
Expand Up @@ -2791,6 +2791,18 @@ public static function supportsWddx(): bool
}


/**
* ext/soap SoapClient/SoapServer/SoapFault — withheld on reference profile (#22859 / #3724).
*
* Gated on stable 8.4.0 / {@see languageProfileVersion()} so 8.4.0-dev reference profile matches
* Zend 8.2 phantom gate (host php-soap absent). Enable forward profile via `PHP_COMPILER_PROFILE=8.4`
* or install host ext/soap ({@see \PHPCompiler\ext\soap\SoapExtensionPolicy::advertisesExtension()}).
*/
public static function supportsSoap(): bool
{
return version_compare(self::languageProfileVersion(), '8.4.0', '>=');
}

/**
* ext/yaml via pure PHP {@see \PHPCompiler\ext\yaml\VmYaml} — withheld on reference profile (#6275).
*
Expand Down
5 changes: 5 additions & 0 deletions test/compliance/JITTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,11 @@ public static function providePHPTests(): \Generator
&& \PHPCompiler\ext\gd\GdExtensionPolicy::isGdComplianceCase($name)) {
continue;
}
// Host/profile without soap: withhold functional soap_* cases; keep phantom (#22859).
if (!\PHPCompiler\ext\soap\SoapExtensionPolicy::runsSoapCompliance($name)
&& \PHPCompiler\ext\soap\SoapExtensionPolicy::isSoapComplianceCase($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 @@ -23,6 +23,11 @@ public static function providePHPTests(): \Generator
&& \PHPCompiler\ext\gd\GdExtensionPolicy::isGdComplianceCase($name)) {
continue;
}
// Host/profile without soap: withhold functional soap_* cases; keep phantom (#22859).
if (!\PHPCompiler\ext\soap\SoapExtensionPolicy::runsSoapCompliance($name)
&& \PHPCompiler\ext\soap\SoapExtensionPolicy::isSoapComplianceCase($name)) {
continue;
}
if (!CompilerVersion::supportsStrIncrement()
&& (str_contains($name, 'str_increment') || str_contains($name, 'str_decrement'))
&& !str_contains($name, 'str_increment_phantom')) {
Expand Down
24 changes: 24 additions & 0 deletions test/compliance/cases/stdlib/extension_loaded_soap_phantom.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
--TEST--
stdlib extension_loaded('soap') false without host php-soap / forward profile (#22859, ext/soap/soap.c)
--FILE--
<?php
declare(strict_types=1);

echo 'loaded=', (int) extension_loaded('soap'), "\n";
echo 'in_list=', (int) in_array('soap', get_loaded_extensions(), true), "\n";
echo 'funcs=', (int) (false !== get_extension_funcs('soap')), "\n";
echo 'is_soap_fault=', (int) function_exists('is_soap_fault'), "\n";
echo 'use_soap_error_handler=', (int) function_exists('use_soap_error_handler'), "\n";
echo 'SoapClient=', (int) class_exists('SoapClient', false), "\n";
echo 'SoapServer=', (int) class_exists('SoapServer', false), "\n";
echo 'SoapFault=', (int) class_exists('SoapFault', false), "\n";
?>
--EXPECT--
loaded=0
in_list=0
funcs=0
is_soap_fault=0
use_soap_error_handler=0
SoapClient=0
SoapServer=0
SoapFault=0
7 changes: 7 additions & 0 deletions test/repro/extension_loaded_soap_phantom.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
<?php
/** Repro #22859 — soap withheld on default/reference profile without host php-soap. */
echo 'ext=', extension_loaded('soap') ? 'yes' : 'no', "\n";
echo 'SoapClient=', class_exists('SoapClient', false) ? 'yes' : 'no', "\n";
echo 'SoapServer=', class_exists('SoapServer', false) ? 'yes' : 'no', "\n";
echo 'SoapFault=', class_exists('SoapFault', false) ? 'yes' : 'no', "\n";
echo 'is_soap_fault=', function_exists('is_soap_fault') ? 'yes' : 'no', "\n";
39 changes: 39 additions & 0 deletions test/unit/SoapExtensionPolicyTest.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
<?php

declare(strict_types=1);

namespace PHPCompiler;

use PHPCompiler\ext\soap\SoapExtensionPolicy;
use PHPUnit\Framework\TestCase;

/** @group soap_extension_policy */
final class SoapExtensionPolicyTest extends TestCase
{
public function testWithholdsOnReferenceWithoutHostSoap(): void
{
if (\extension_loaded('soap') || \PHPCompiler\CompilerVersion::supportsSoap()) {
$this->markTestSkipped('soap advertised on this host/profile');
}

self::assertFalse(SoapExtensionPolicy::advertisesExtension());
self::assertFalse(SoapExtensionPolicy::advertisesExceptionClass());

$runtime = new Runtime();
self::assertFalse(
ext\standard\ModuleRegistry::extensionLoaded('soap')
);
self::assertFalse(
ext\standard\VmReflection::functionExists($runtime->vmContext, 'is_soap_fault')
);
self::assertFalse(
ext\standard\VmReflection::classExists($runtime->vmContext, 'SoapClient')
);
self::assertFalse(
ext\standard\VmReflection::classExists($runtime->vmContext, 'SoapServer')
);
self::assertFalse(
ext\standard\VmReflection::classExists($runtime->vmContext, 'SoapFault')
);
}
}
Loading