diff --git a/ext/standard/array_search.php b/ext/standard/array_search.php index 666ec8a3974..3e282987fb7 100644 --- a/ext/standard/array_search.php +++ b/ext/standard/array_search.php @@ -17,6 +17,7 @@ use PHPCompiler\JIT\Context; use PHPCompiler\JIT\ExceptionBridge; use PHPCompiler\JIT\JitBoolArg; +use PHPCompiler\JIT\JitNativeString; use PHPCompiler\JIT\JitValueBox; use PHPCompiler\JIT\Variable as JITVariable; use PHPLLVM\Value; @@ -40,9 +41,10 @@ public function execute(Frame $frame): void 2, 'haystack' ); + // Z_PARAM_BOOL $strict — strict_types TypeError; else null→false + E_DEPRECATED (#29866). $strict = false; if (3 === $argc) { - $strict = $frame->calledArgs[2]->resolveIndirect()->toBool(); + $strict = VmMath::parseBoolBuiltinArgForFrame($frame, 2, 'array_search', 3, 'strict'); } $vm = null !== $frame->vmContext ? $frame->vmContext->runtime->vm() : null; if (null === $frame->returnVar) { @@ -70,7 +72,17 @@ public function call(Context $context, JITVariable ...$args): Value } $strict = $context->constantFromBool(false); if (3 === $argc) { - $strict = JitBoolArg::lower($context, $args[2], 'array_search() strict'); + // Compile-time null under strict: catchable TypeError then stop IR (peer substr_compare #29756). + if ($context->callerStrictTypes && self::isCompileTimeNull($args[2])) { + JitNativeString::ensureInsertBlock($context); + ExceptionBridge::emitTypeErrorAndAbort( + $context, + 'array_search(): Argument #3 ($strict) must be of type bool, null given' + ); + + return $context->constantFromBool(false); + } + $strict = JitBoolArg::lowerCoerceZParamBool($context, $args[2], 'array_search', 'strict', 3); } if (JITVariable::TYPE_STRING === $args[0]->type || JITVariable::TYPE_VALUE === $args[0]->type) { $this->jitString($context, $args[0], 'array_search() needle'); @@ -79,4 +91,9 @@ public function call(Context $context, JITVariable ...$args): Value return ArraySearchRuntime::search($context, $args[0], $args[1], $strict); } + + private static function isCompileTimeNull(JITVariable $arg): bool + { + return JITVariable::TYPE_NULL === $arg->type || ($arg->isNullConstant ?? false); + } } diff --git a/ext/standard/in_array.php b/ext/standard/in_array.php index 3abd4ac051c..e29f8336f9c 100644 --- a/ext/standard/in_array.php +++ b/ext/standard/in_array.php @@ -15,13 +15,17 @@ use PHPCompiler\Func\Internal; use PHPCompiler\JIT\Builtin\InArrayRuntime; use PHPCompiler\JIT\Context; +use PHPCompiler\JIT\ExceptionBridge; use PHPCompiler\JIT\JitBoolArg; +use PHPCompiler\JIT\JitNativeString; use PHPCompiler\JIT\Variable as JITVariable; use PHPCompiler\VM\Variable; use PHPLLVM\Value; /** * in_array() for arrays of scalar values (subset of PHP; JIT via InArrayRuntime). + * + * php-src: ext/standard/array.stub.php / array.c — PHP_FUNCTION(in_array) */ final class in_array extends Internal { @@ -48,9 +52,10 @@ public function execute(Frame $frame): void 2, 'haystack' ); + // Z_PARAM_BOOL $strict — strict_types TypeError; else null→false + E_DEPRECATED (#29866). $strict = false; - if (3 === \count($frame->calledArgs)) { - $strict = $frame->calledArgs[2]->resolveIndirect()->toBool(); + if (3 === $argc) { + $strict = VmMath::parseBoolBuiltinArgForFrame($frame, 2, 'in_array', 3, 'strict'); } if (null === $frame->returnVar) { return; @@ -81,8 +86,18 @@ public function call(Context $context, JITVariable ...$args): Value )); } $strict = $context->constantFromBool(false); - if (3 === \count($args)) { - $strict = JitBoolArg::lower($context, $args[2], 'in_array() strict'); + if (3 === $argc) { + // Compile-time null under strict: catchable TypeError then stop IR (peer substr_compare #29756). + if ($context->callerStrictTypes && self::isCompileTimeNull($args[2])) { + JitNativeString::ensureInsertBlock($context); + ExceptionBridge::emitTypeErrorAndAbort( + $context, + 'in_array(): Argument #3 ($strict) must be of type bool, null given' + ); + + return $context->constantFromBool(false); + } + $strict = JitBoolArg::lowerCoerceZParamBool($context, $args[2], 'in_array', 'strict', 3); } if (JITVariable::TYPE_STRING === $args[0]->type || JITVariable::TYPE_VALUE === $args[0]->type) { $this->jitString($context, $args[0], 'in_array() needle'); @@ -97,4 +112,9 @@ public function call(Context $context, JITVariable ...$args): Value return InArrayRuntime::inArray($context, $args[0], $args[1], $strict); } + + private static function isCompileTimeNull(JITVariable $arg): bool + { + return JITVariable::TYPE_NULL === $arg->type || ($arg->isNullConstant ?? false); + } } diff --git a/phpunit.xml.dist b/phpunit.xml.dist index 522d1dcb7d3..d0fe9abc476 100644 --- a/phpunit.xml.dist +++ b/phpunit.xml.dist @@ -288,6 +288,8 @@ ./test/compliance/SubstrCompareNullCiCoerceJITTest.php ./test/compliance/Base64DecodeStrictNullVMTest.php ./test/compliance/Base64DecodeStrictNullJITTest.php + ./test/compliance/InArrayArraySearchStrictNullVMTest.php + ./test/compliance/InArrayArraySearchStrictNullJITTest.php ./test/compliance/SubstrReplaceNullReplaceStrictVMTest.php ./test/compliance/SubstrReplaceNullReplaceStrictJITTest.php ./test/compliance/MetaphoneMaxPhonemesValueErrorVMTest.php diff --git a/test/compliance/InArrayArraySearchStrictNullJITTest.php b/test/compliance/InArrayArraySearchStrictNullJITTest.php new file mode 100644 index 00000000000..b8f308bcb49 --- /dev/null +++ b/test/compliance/InArrayArraySearchStrictNullJITTest.php @@ -0,0 +1,29 @@ + self::parsePHPT( + __DIR__.'/cases/stdlib/in_array_array_search_strict_null_jit.phpt', + 'in_array_array_search_strict_null_jit.phpt' + ); + } + + public function setUp(): void + { + $this->BIN = realpath(__DIR__.'/../../bin/jit.php'); + } +} diff --git a/test/compliance/InArrayArraySearchStrictNullVMTest.php b/test/compliance/InArrayArraySearchStrictNullVMTest.php new file mode 100644 index 00000000000..60988ee761f --- /dev/null +++ b/test/compliance/InArrayArraySearchStrictNullVMTest.php @@ -0,0 +1,30 @@ + self::parsePHPT( + __DIR__.'/cases/stdlib/in_array_array_search_strict_null.phpt', + 'in_array_array_search_strict_null.phpt' + ); + yield 'in_array_array_search_strict_null_weak.phpt' => self::parsePHPT( + __DIR__.'/cases/stdlib/in_array_array_search_strict_null_weak.phpt', + 'in_array_array_search_strict_null_weak.phpt' + ); + } + + public function setUp(): void + { + $this->BIN = realpath(__DIR__.'/../../bin/vm.php'); + } +} diff --git a/test/compliance/cases/stdlib/in_array_array_search_strict_null.phpt b/test/compliance/cases/stdlib/in_array_array_search_strict_null.phpt new file mode 100644 index 00000000000..7c966d4fe38 --- /dev/null +++ b/test/compliance/cases/stdlib/in_array_array_search_strict_null.phpt @@ -0,0 +1,29 @@ +--TEST-- +stdlib in_array/array_search(null $strict) under strict_types TypeError (#29866, ext/standard/array.c Z_PARAM_BOOL) +--FILE-- +getMessage(), "\n"; +} +try { + var_export(array_search(1, [1], null)); + echo " uncaught\n"; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +$s = null; +try { + var_export(in_array(1, [1], $s)); + echo " uncaught-var\n"; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +--EXPECT-- +in_array(): Argument #3 ($strict) must be of type bool, null given +array_search(): Argument #3 ($strict) must be of type bool, null given +in_array(): Argument #3 ($strict) must be of type bool, null given diff --git a/test/compliance/cases/stdlib/in_array_array_search_strict_null_jit.phpt b/test/compliance/cases/stdlib/in_array_array_search_strict_null_jit.phpt new file mode 100644 index 00000000000..7210bceabcd --- /dev/null +++ b/test/compliance/cases/stdlib/in_array_array_search_strict_null_jit.phpt @@ -0,0 +1,21 @@ +--TEST-- +JIT: in_array/array_search(null $strict) under strict_types TypeError (#29866, ext/standard/array.c Z_PARAM_BOOL) +--FILE-- +getMessage(), "\n"; +} +try { + var_export(array_search(1, [1], null)); + echo " uncaught\n"; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +--EXPECT-- +in_array(): Argument #3 ($strict) must be of type bool, null given +array_search(): Argument #3 ($strict) must be of type bool, null given diff --git a/test/compliance/cases/stdlib/in_array_array_search_strict_null_weak.phpt b/test/compliance/cases/stdlib/in_array_array_search_strict_null_weak.phpt new file mode 100644 index 00000000000..426cdea7f71 --- /dev/null +++ b/test/compliance/cases/stdlib/in_array_array_search_strict_null_weak.phpt @@ -0,0 +1,23 @@ +--TEST-- +stdlib in_array/array_search(null $strict) without strict_types — Deprecated + coerce (#29866, ext/standard/array.c) +--FILE-- +getMessage(), "\n"; +} +try { + var_export(array_search(1, [1], null)); + echo "\n"; +} catch (TypeError $e) { + echo $e->getMessage(), "\n"; +} +?> +--EXPECT-- +in_array(): Argument #3 ($strict) must be of type bool, null given +array_search(): Argument #3 ($strict) must be of type bool, null given diff --git a/test/repro/issue_29866_in_array_strict_null.php b/test/repro/issue_29866_in_array_strict_null.php new file mode 100644 index 00000000000..dbad7b71fa4 --- /dev/null +++ b/test/repro/issue_29866_in_array_strict_null.php @@ -0,0 +1,15 @@ +getMessage(), "\n"; +} +try { + var_export(array_search(1, [1], null)); + echo "\n"; +} catch (Throwable $e) { + echo get_class($e), ': ', $e->getMessage(), "\n"; +}