diff --git a/ext/standard/BuiltinIntrospectionPolicy.php b/ext/standard/BuiltinIntrospectionPolicy.php index 4c10da9f230..f39a3773ce1 100644 --- a/ext/standard/BuiltinIntrospectionPolicy.php +++ b/ext/standard/BuiltinIntrospectionPolicy.php @@ -27,6 +27,9 @@ public static function functionIsAdvertised(string $functionName): bool if ('mb_str_pad' === $lc) { return CompilerVersion::advertisesMbStrPad(); } + if (\in_array($lc, ['str_increment', 'str_decrement'], true)) { + return CompilerVersion::advertisesStrIncrement(); + } if (str_starts_with($lc, 'bc')) { return CompilerVersion::advertisesBcmath(); } diff --git a/lib/CompilerVersion.php b/lib/CompilerVersion.php index 96f12c86a9d..e79cf8116a0 100644 --- a/lib/CompilerVersion.php +++ b/lib/CompilerVersion.php @@ -175,13 +175,47 @@ public static function languageProfileVersion(): string } /** - * PHP 8.3+ str_increment() / str_decrement() (ext/standard/string.c, issue #5697, #12378, #14518, #14709, #15026). + * PHP 8.3+ str_increment() / str_decrement() (ext/standard/string.c, issue #5697, #12378, #14518, #14709, #15026, #16292). * - * Forward profile on 8.4.0-dev — advertisesBuiltinSince treats -dev as 8.4.0 (#13284 phantom withheld on 8.2). + * Withheld on 8.4.0-dev reference profile (matches Zend 8.2 function_exists gate). Enable via + * stable 8.4.0+ or explicit `PHP_COMPILER_PROFILE=8.3` / `8.4` forward profile. */ public static function supportsStrIncrement(): bool { - return self::advertisesBuiltinSince('8.3.0'); + if (version_compare(self::VERSION, '8.3', '<')) { + return false; + } + + if (version_compare(self::VERSION, '8.4.0', '>=')) { + return true; + } + + $raw = getenv('PHP_COMPILER_PROFILE'); + if (!\is_string($raw) || '' === trim($raw)) { + return false; + } + + return version_compare(self::languageProfileVersion(), '8.3.0', '>='); + } + + /** + * str_increment()/str_decrement() visible to function_exists() — stable runtime or forward 8.3+ (#16292). + * + * Callable under forward profile via {@see supportsStrIncrement()}; withheld from introspection on 8.4.0-dev + * reference harness like Zend 8.2. + */ + public static function advertisesStrIncrement(): bool + { + if (version_compare(self::VERSION, '8.4.0', '>=')) { + return true; + } + + $raw = getenv('PHP_COMPILER_PROFILE'); + if (!\is_string($raw) || '' === trim($raw)) { + return false; + } + + return version_compare(self::languageProfileVersion(), '8.3.0', '>='); } /** diff --git a/test/compliance/cases/stdlib/forward_profile_builtins.phpt b/test/compliance/cases/stdlib/forward_profile_builtins.phpt index 12a4eaa0fe0..fdcdfe19c7f 100644 --- a/test/compliance/cases/stdlib/forward_profile_builtins.phpt +++ b/test/compliance/cases/stdlib/forward_profile_builtins.phpt @@ -1,5 +1,7 @@ --TEST-- stdlib forward profile builtins str_increment registered (#14518); json_validate stable 8.4+ (#15196) +--ENV-- +PHP_COMPILER_PROFILE=8.3 --FILE-- --EXPECT-- -jv=no +jv=yes si=yes sd=yes b diff --git a/test/repro/maintainer_gap_str_increment_phantom_function_exists.php b/test/repro/maintainer_gap_str_increment_phantom_function_exists.php new file mode 100644 index 00000000000..7141c537149 --- /dev/null +++ b/test/repro/maintainer_gap_str_increment_phantom_function_exists.php @@ -0,0 +1,20 @@ +assertFalse(CompilerVersion::supportsMbStrPad()); } + public function testStrIncrementWithheldOnReferenceProfile(): void + { + $this->assertFalse(CompilerVersion::supportsStrIncrement()); + $this->assertFalse(CompilerVersion::advertisesStrIncrement()); + } + public function testStrIncrementAdvertisedOnForwardProfile(): void { - $this->assertTrue(CompilerVersion::supportsStrIncrement()); + $prev = getenv('PHP_COMPILER_PROFILE'); + putenv('PHP_COMPILER_PROFILE=8.3'); + try { + $this->assertTrue(CompilerVersion::supportsStrIncrement()); + $this->assertTrue(CompilerVersion::advertisesStrIncrement()); + } finally { + if (false === $prev) { + putenv('PHP_COMPILER_PROFILE'); + } else { + putenv('PHP_COMPILER_PROFILE='.$prev); + } + } } public function testClassHasFunctionsAdvertisedOnForwardProfile(): void diff --git a/test/unit/CompilerVersionGateTest.php b/test/unit/CompilerVersionGateTest.php index 9c644799b40..3329db02463 100644 --- a/test/unit/CompilerVersionGateTest.php +++ b/test/unit/CompilerVersionGateTest.php @@ -14,9 +14,44 @@ public function testVersionReports84Dev(): void $this->assertSame('8.4.0-dev', CompilerVersion::VERSION); } + public function testSupportsStrIncrementFalseOnReferenceProfile(): void + { + $this->assertFalse(CompilerVersion::supportsStrIncrement()); + } + public function testSupportsStrIncrementTrueOnForwardProfile(): void { - $this->assertTrue(CompilerVersion::supportsStrIncrement()); + $prev = getenv('PHP_COMPILER_PROFILE'); + putenv('PHP_COMPILER_PROFILE=8.3'); + try { + $this->assertTrue(CompilerVersion::supportsStrIncrement()); + } finally { + if (false === $prev) { + putenv('PHP_COMPILER_PROFILE'); + } else { + putenv('PHP_COMPILER_PROFILE='.$prev); + } + } + } + + public function testAdvertisesStrIncrementFalseOnReferenceProfile(): void + { + $this->assertFalse(CompilerVersion::advertisesStrIncrement()); + } + + public function testAdvertisesStrIncrementTrueOnForwardProfile(): void + { + $prev = getenv('PHP_COMPILER_PROFILE'); + putenv('PHP_COMPILER_PROFILE=8.3'); + try { + $this->assertTrue(CompilerVersion::advertisesStrIncrement()); + } finally { + if (false === $prev) { + putenv('PHP_COMPILER_PROFILE'); + } else { + putenv('PHP_COMPILER_PROFILE='.$prev); + } + } } public function testSupportsClassHasFunctionsTrueOnForwardProfile(): void @@ -574,12 +609,30 @@ public function testSupportsFinalGlobalTypedConstantsAlwaysFalse(): void $this->assertFalse(CompilerVersion::supportsFinalGlobalTypedConstants()); } - public function testVmRegistersStrIncrementOnForwardProfile(): void + public function testVmDoesNotRegisterStrIncrementOnReferenceProfile(): void { $runtime = new Runtime(); $ctx = $runtime->vmContext; - $this->assertTrue(isset($ctx->functions['str_decrement'])); - $this->assertTrue(isset($ctx->functions['str_increment'])); + $this->assertFalse(isset($ctx->functions['str_decrement'])); + $this->assertFalse(isset($ctx->functions['str_increment'])); + } + + public function testVmRegistersStrIncrementOnForwardProfile(): void + { + $prev = getenv('PHP_COMPILER_PROFILE'); + putenv('PHP_COMPILER_PROFILE=8.3'); + try { + $runtime = new Runtime(); + $ctx = $runtime->vmContext; + $this->assertTrue(isset($ctx->functions['str_decrement'])); + $this->assertTrue(isset($ctx->functions['str_increment'])); + } finally { + if (false === $prev) { + putenv('PHP_COMPILER_PROFILE'); + } else { + putenv('PHP_COMPILER_PROFILE='.$prev); + } + } } public function testVmRegistersClassHasFunctionsOnForwardProfile(): void diff --git a/test/unit/ForwardProfilePhantomIntrospectionTest.php b/test/unit/ForwardProfilePhantomIntrospectionTest.php index 4c431b2acc5..0e2fd0d5998 100644 --- a/test/unit/ForwardProfilePhantomIntrospectionTest.php +++ b/test/unit/ForwardProfilePhantomIntrospectionTest.php @@ -58,4 +58,28 @@ public function testBcmathCallableButExtensionNotAdvertisedOnForwardProfile(): v } } } + + public function testStrIncrementCallableAndAdvertisedOnForwardProfile83(): void + { + $prev = getenv('PHP_COMPILER_PROFILE'); + putenv('PHP_COMPILER_PROFILE=8.3'); + try { + $this->assertTrue(CompilerVersion::supportsStrIncrement()); + $this->assertTrue(CompilerVersion::advertisesStrIncrement()); + $this->assertTrue(BuiltinIntrospectionPolicy::functionIsAdvertised('str_increment')); + + $runtime = new Runtime(); + $ctx = $runtime->vmContext; + $this->assertTrue(isset($ctx->functions['str_increment'])); + $this->assertTrue( + \PHPCompiler\ext\standard\VmReflection::functionExists($ctx, 'str_increment') + ); + } finally { + if (false === $prev) { + putenv('PHP_COMPILER_PROFILE'); + } else { + putenv('PHP_COMPILER_PROFILE='.$prev); + } + } + } }