From 12e3ca1fd2ee5ae13ce5e43c6ef10ec314cbe887 Mon Sep 17 00:00:00 2001 From: PurHur Date: Mon, 10 Aug 2026 11:12:12 +0000 Subject: [PATCH] Stdlib: substr_compare null $case_insensitive Z_PARAM_BOOL (#29756) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Match php-src string.c — TypeError under strict_types; soft-null DEP+coerce otherwise. Early-return after compile-time null TypeError so AOT try/catch does not emit IR after a terminator. Co-authored-by: Cursor --- ext/standard/substr_compare.php | 32 ++++++++++++++++--- phpunit.xml.dist | 4 +++ .../SubstrCompareNullCiCoerceJITTest.php | 29 +++++++++++++++++ .../SubstrCompareNullCiCoerceVMTest.php | 26 +++++++++++++++ .../SubstrCompareNullCiStrictJITTest.php | 29 +++++++++++++++++ .../SubstrCompareNullCiStrictVMTest.php | 26 +++++++++++++++ .../stdlib/substr_compare_null_ci_coerce.phpt | 23 +++++++++++++ .../substr_compare_null_ci_coerce_jit.phpt | 23 +++++++++++++ .../stdlib/substr_compare_null_ci_strict.phpt | 21 ++++++++++++ .../substr_compare_null_ci_strict_jit.phpt | 21 ++++++++++++ .../cases/substr_compare_null_ci_strict.phpt | 14 ++++++++ test/repro/issue_substr_compare_null_ci.php | 9 ++++++ .../issue_substr_compare_null_ci_aot.php | 9 ++++++ 13 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 test/compliance/SubstrCompareNullCiCoerceJITTest.php create mode 100644 test/compliance/SubstrCompareNullCiCoerceVMTest.php create mode 100644 test/compliance/SubstrCompareNullCiStrictJITTest.php create mode 100644 test/compliance/SubstrCompareNullCiStrictVMTest.php create mode 100644 test/compliance/cases/stdlib/substr_compare_null_ci_coerce.phpt create mode 100644 test/compliance/cases/stdlib/substr_compare_null_ci_coerce_jit.phpt create mode 100644 test/compliance/cases/stdlib/substr_compare_null_ci_strict.phpt create mode 100644 test/compliance/cases/stdlib/substr_compare_null_ci_strict_jit.phpt create mode 100644 test/fixtures/aot/cases/substr_compare_null_ci_strict.phpt create mode 100644 test/repro/issue_substr_compare_null_ci.php create mode 100644 test/repro/issue_substr_compare_null_ci_aot.php diff --git a/ext/standard/substr_compare.php b/ext/standard/substr_compare.php index 0ffbebdad8a..32499aff11a 100644 --- a/ext/standard/substr_compare.php +++ b/ext/standard/substr_compare.php @@ -10,6 +10,8 @@ use PHPCompiler\JIT\Builtin\StringSubstrCompare; use PHPCompiler\JIT\Builtin\TypeErrorRaise; use PHPCompiler\JIT\Context; +use PHPCompiler\JIT\ExceptionBridge; +use PHPCompiler\JIT\JitBoolArg; use PHPCompiler\JIT\JitOperandTypeLabel; use PHPCompiler\JIT\JitStringArg; use PHPCompiler\JIT\JitStringBuiltinArg; @@ -48,10 +50,16 @@ public function execute(Frame $frame): void $length = self::requireIntArg($frame->calledArgs[3], 'substr_compare', 4, 'length'); } } + // Z_PARAM_BOOL $case_insensitive — strict TypeError; soft-null DEP+coerce (#29756). $caseInsensitive = false; if (5 === $argc) { - $ci = $frame->calledArgs[4]->resolveIndirect(); - $caseInsensitive = $ci->toBool(); + $caseInsensitive = VmMath::parseBoolBuiltinArgForFrame( + $frame, + 4, + 'substr_compare', + 5, + 'case_insensitive' + ); } if (null === $frame->returnVar) { return; @@ -105,13 +113,27 @@ public function call(Context $context, JITVariable ...$args): Value $lengthVal = self::lowerStrictIntArg($context, $args[3], 'substr_compare', 4, 'length'); } } + // Z_PARAM_BOOL $case_insensitive — strict TypeError; soft-null DEP+coerce (#29756). + // Compile-time null under strict: emit catchable TypeError and stop (do not continue + // after terminator — lowerCoerceZParamBool would soft-null-DEP into a dead block). $ci = $i32->constInt(0, false); if (5 === $argc) { - if (JITVariable::TYPE_NATIVE_BOOL !== $args[4]->type) { - throw new \LogicException('substr_compare() case_insensitive must be a boolean in this compiler build'); + if ($context->callerStrictTypes && self::isCompileTimeNull($args[4])) { + ExceptionBridge::emitTypeErrorAndAbort( + $context, + 'substr_compare(): Argument #5 ($case_insensitive) must be of type bool, null given' + ); + + return $i64->constInt(0, false); } $ci = $context->builder->zExt( - $this->jitBool($context, $args[4], 'substr_compare() case_insensitive'), + JitBoolArg::lowerCoerceZParamBool( + $context, + $args[4], + 'substr_compare', + 'case_insensitive', + 5 + ), $i32 ); } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index a168cf66dfd..932be57536f 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -205,6 +205,10 @@ ./test/compliance/RoundNullModeForward84JITTest.php ./test/compliance/SubstrCompareNullOffsetVMTest.php ./test/compliance/SubstrCompareNullOffsetJITTest.php + ./test/compliance/SubstrCompareNullCiStrictVMTest.php + ./test/compliance/SubstrCompareNullCiStrictJITTest.php + ./test/compliance/SubstrCompareNullCiCoerceVMTest.php + ./test/compliance/SubstrCompareNullCiCoerceJITTest.php ./test/compliance/MetaphoneMaxPhonemesValueErrorVMTest.php ./test/compliance/MetaphoneMaxPhonemesValueErrorJITTest.php ./test/compliance/FilePutContentsEmptyPathValueErrorVMTest.php diff --git a/test/compliance/SubstrCompareNullCiCoerceJITTest.php b/test/compliance/SubstrCompareNullCiCoerceJITTest.php new file mode 100644 index 00000000000..1d0a4f1d899 --- /dev/null +++ b/test/compliance/SubstrCompareNullCiCoerceJITTest.php @@ -0,0 +1,29 @@ + self::parsePHPT( + __DIR__.'/cases/stdlib/substr_compare_null_ci_coerce_jit.phpt', + 'substr_compare_null_ci_coerce_jit.phpt' + ); + } + + public function setUp(): void + { + $this->BIN = realpath(__DIR__.'/../../bin/jit.php'); + } +} diff --git a/test/compliance/SubstrCompareNullCiCoerceVMTest.php b/test/compliance/SubstrCompareNullCiCoerceVMTest.php new file mode 100644 index 00000000000..7d1084f635a --- /dev/null +++ b/test/compliance/SubstrCompareNullCiCoerceVMTest.php @@ -0,0 +1,26 @@ + self::parsePHPT( + __DIR__.'/cases/stdlib/substr_compare_null_ci_coerce.phpt', + 'substr_compare_null_ci_coerce.phpt' + ); + } + + public function setUp(): void + { + $this->BIN = realpath(__DIR__.'/../../bin/vm.php'); + } +} diff --git a/test/compliance/SubstrCompareNullCiStrictJITTest.php b/test/compliance/SubstrCompareNullCiStrictJITTest.php new file mode 100644 index 00000000000..758d7b24a79 --- /dev/null +++ b/test/compliance/SubstrCompareNullCiStrictJITTest.php @@ -0,0 +1,29 @@ + self::parsePHPT( + __DIR__.'/cases/stdlib/substr_compare_null_ci_strict_jit.phpt', + 'substr_compare_null_ci_strict_jit.phpt' + ); + } + + public function setUp(): void + { + $this->BIN = realpath(__DIR__.'/../../bin/jit.php'); + } +} diff --git a/test/compliance/SubstrCompareNullCiStrictVMTest.php b/test/compliance/SubstrCompareNullCiStrictVMTest.php new file mode 100644 index 00000000000..2c6a6ad4935 --- /dev/null +++ b/test/compliance/SubstrCompareNullCiStrictVMTest.php @@ -0,0 +1,26 @@ + self::parsePHPT( + __DIR__.'/cases/stdlib/substr_compare_null_ci_strict.phpt', + 'substr_compare_null_ci_strict.phpt' + ); + } + + public function setUp(): void + { + $this->BIN = realpath(__DIR__.'/../../bin/vm.php'); + } +} diff --git a/test/compliance/cases/stdlib/substr_compare_null_ci_coerce.phpt b/test/compliance/cases/stdlib/substr_compare_null_ci_coerce.phpt new file mode 100644 index 00000000000..2ebd977004f --- /dev/null +++ b/test/compliance/cases/stdlib/substr_compare_null_ci_coerce.phpt @@ -0,0 +1,23 @@ +--TEST-- +stdlib substr_compare(null $case_insensitive) soft-null DEP+coerce (#29756, ext/standard/string.c Z_PARAM_BOOL) +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +DEP +OK diff --git a/test/compliance/cases/stdlib/substr_compare_null_ci_coerce_jit.phpt b/test/compliance/cases/stdlib/substr_compare_null_ci_coerce_jit.phpt new file mode 100644 index 00000000000..6abb3cfd99d --- /dev/null +++ b/test/compliance/cases/stdlib/substr_compare_null_ci_coerce_jit.phpt @@ -0,0 +1,23 @@ +--TEST-- +JIT: substr_compare(null $case_insensitive) soft-null DEP+coerce (#29756, ext/standard/string.c Z_PARAM_BOOL) +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +DEP +OK diff --git a/test/compliance/cases/stdlib/substr_compare_null_ci_strict.phpt b/test/compliance/cases/stdlib/substr_compare_null_ci_strict.phpt new file mode 100644 index 00000000000..000f84bb38c --- /dev/null +++ b/test/compliance/cases/stdlib/substr_compare_null_ci_strict.phpt @@ -0,0 +1,21 @@ +--TEST-- +stdlib substr_compare(null $case_insensitive) under strict_types TypeError (#29756, ext/standard/string.c Z_PARAM_BOOL) +--FILE-- +getMessage(), "\n"; +} +try { + substr_compare('abc', 'ab', 0, 2, 1); + echo "uncaught-int\n"; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +--EXPECT-- +substr_compare(): Argument #5 ($case_insensitive) must be of type bool, null given +substr_compare(): Argument #5 ($case_insensitive) must be of type bool, int given diff --git a/test/compliance/cases/stdlib/substr_compare_null_ci_strict_jit.phpt b/test/compliance/cases/stdlib/substr_compare_null_ci_strict_jit.phpt new file mode 100644 index 00000000000..c7b73150457 --- /dev/null +++ b/test/compliance/cases/stdlib/substr_compare_null_ci_strict_jit.phpt @@ -0,0 +1,21 @@ +--TEST-- +JIT: substr_compare(null $case_insensitive) under strict_types TypeError (#29756, ext/standard/string.c Z_PARAM_BOOL) +--FILE-- +getMessage(), "\n"; +} +try { + substr_compare('abc', 'ab', 0, 2, 1); + echo "uncaught-int\n"; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +--EXPECT-- +substr_compare(): Argument #5 ($case_insensitive) must be of type bool, null given +substr_compare(): Argument #5 ($case_insensitive) must be of type bool, int given diff --git a/test/fixtures/aot/cases/substr_compare_null_ci_strict.phpt b/test/fixtures/aot/cases/substr_compare_null_ci_strict.phpt new file mode 100644 index 00000000000..75ed32ba55f --- /dev/null +++ b/test/fixtures/aot/cases/substr_compare_null_ci_strict.phpt @@ -0,0 +1,14 @@ +--TEST-- +AOT: substr_compare(null $case_insensitive) under strict_types TypeError (#29756, ext/standard/string.c Z_PARAM_BOOL) +--FILE-- +getMessage(), "\n"; +} +?> +--EXPECT-- +substr_compare(): Argument #5 ($case_insensitive) must be of type bool, null given diff --git a/test/repro/issue_substr_compare_null_ci.php b/test/repro/issue_substr_compare_null_ci.php new file mode 100644 index 00000000000..5a4dc4b94a2 --- /dev/null +++ b/test/repro/issue_substr_compare_null_ci.php @@ -0,0 +1,9 @@ +getMessage(), "\n"; +} diff --git a/test/repro/issue_substr_compare_null_ci_aot.php b/test/repro/issue_substr_compare_null_ci_aot.php new file mode 100644 index 00000000000..37ba17fe659 --- /dev/null +++ b/test/repro/issue_substr_compare_null_ci_aot.php @@ -0,0 +1,9 @@ +getMessage(), "\n"; +}