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";
+}