diff --git a/ext/standard/JitStreamFilter.php b/ext/standard/JitStreamFilter.php index 4ba1e0b2c19..3842f5d9da7 100644 --- a/ext/standard/JitStreamFilter.php +++ b/ext/standard/JitStreamFilter.php @@ -157,7 +157,23 @@ private static function attach(Context $context, string $abi, string $functionNa JitLongArg::lower($context, $args[0], $functionName.'() stream'), $context->getTypeFromString('int64') ); - $filterName = JitStringBuiltinArg::lower($context, $args[1], $functionName, 1, 'filtername'); + // Z_PARAM_STR $filter_name — TypeError under declare(strict_types=1) (#31408). + $filterName = JitStringBuiltinArg::lowerStrictOrCoercible( + $context, + $args[1], + $functionName, + 1, + 'filter_name' + ); + // Catchable TypeError seals the insert block; open a dead BB so later attach + // IR does not land after a terminator (#31408 / peer #30250 JitDl). + if ($context->callerStrictTypes + && (JITVariable::TYPE_NULL === $args[1]->type || ($args[1]->isNullConstant ?? false)) + ) { + BasicBlockHelper::ensureOpenInsertBlock($context, $functionName.'_strict_null_dead'); + + return self::boolBox($context, $context->getTypeFromString('int1')->constInt(0, false)); + } $i64 = $context->getTypeFromString('int64'); $readWrite = $i64->constInt(VmStreamFilterChain::ALL, false); if ($argc >= 3) { diff --git a/ext/standard/stream_filter_append.php b/ext/standard/stream_filter_append.php index b08b4b4457d..40b0e4c94a7 100644 --- a/ext/standard/stream_filter_append.php +++ b/ext/standard/stream_filter_append.php @@ -8,6 +8,7 @@ use PHPCompiler\Func\Internal; use PHPCompiler\JIT\Context; use PHPCompiler\JIT\Variable as JITVariable; +use PHPCompiler\VM\InternalStrictArg; use PHPLLVM\Value; /** stream_filter_append() — attach a filter to a stream (#3283, ext/standard/streams.c). */ @@ -31,10 +32,11 @@ public function execute(Frame $frame): void 'stream_filter_append', 1 ); - $filterName = VmString::coerceStringBuiltinArg( - $frame->calledArgs[1], - 'stream_filter_append', + // Z_PARAM_STR $filter_name — TypeError under declare(strict_types=1) (#31408). + $filterName = InternalStrictArg::resolveCoercibleStringArg( + $frame, 1, + 'stream_filter_append', 'filter_name' ); $readWrite = VmStreamFilterChain::ALL; diff --git a/ext/standard/stream_filter_prepend.php b/ext/standard/stream_filter_prepend.php index fcf5fb9573f..c339a9f51b4 100644 --- a/ext/standard/stream_filter_prepend.php +++ b/ext/standard/stream_filter_prepend.php @@ -8,6 +8,7 @@ use PHPCompiler\Func\Internal; use PHPCompiler\JIT\Context; use PHPCompiler\JIT\Variable as JITVariable; +use PHPCompiler\VM\InternalStrictArg; use PHPLLVM\Value; /** stream_filter_prepend() — attach a filter at the head of a stream chain (#3283). */ @@ -31,10 +32,11 @@ public function execute(Frame $frame): void 'stream_filter_prepend', 1 ); - $filterName = VmString::coerceStringBuiltinArg( - $frame->calledArgs[1], - 'stream_filter_prepend', + // Z_PARAM_STR $filter_name — TypeError under declare(strict_types=1) (#31408). + $filterName = InternalStrictArg::resolveCoercibleStringArg( + $frame, 1, + 'stream_filter_prepend', 'filter_name' ); $readWrite = VmStreamFilterChain::ALL; diff --git a/test/repro/maintainer_gap_stream_filter_append_null_name.php b/test/repro/maintainer_gap_stream_filter_append_null_name.php index 4f0835c6711..658be216fae 100644 --- a/test/repro/maintainer_gap_stream_filter_append_null_name.php +++ b/test/repro/maintainer_gap_stream_filter_append_null_name.php @@ -1,9 +1,18 @@ getMessage(), "\n"; +} +try { + stream_filter_prepend($h, null); + echo "NO_THROW_PREPEND\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} fclose($h); diff --git a/test/unit/Issue31408StreamFilterNullNameStrictAotTest.php b/test/unit/Issue31408StreamFilterNullNameStrictAotTest.php new file mode 100644 index 00000000000..ccc5ef5e226 --- /dev/null +++ b/test/unit/Issue31408StreamFilterNullNameStrictAotTest.php @@ -0,0 +1,69 @@ +markTestSkipped('LLVM 9 toolchain not available'); + } + $root = dirname(__DIR__, 2); + $src = sys_get_temp_dir().'/phpc_31408_'.getmypid().'.php'; + $bin = sys_get_temp_dir().'/phpc_31408_'.getmypid().'.bin'; + file_put_contents($src, <<<'PHP' +getMessage(), "\n"; +} +try { + stream_filter_prepend($h, null); + echo "NO_THROW_PREPEND\n"; +} catch (Throwable $e) { + echo $e::class, ': ', $e->getMessage(), "\n"; +} +fclose($h); +PHP); + $compile = escapeshellarg(PHP_BINARY).' ' + .escapeshellarg($root.'/bin/compile.php') + .' -o '.escapeshellarg($bin).' '.escapeshellarg($src).' 2>&1'; + exec($compile, $compileOut, $compileRc); + $this->assertSame(0, $compileRc, implode("\n", $compileOut)); + $this->assertFileExists($bin); + try { + for ($i = 0; $i < 3; ++$i) { + $runOut = []; + exec(escapeshellarg($bin).' 2>&1', $runOut, $runRc); + $this->assertSame(0, $runRc, 'run '.($i + 1).': '.implode("\n", $runOut)); + $joined = implode("\n", $runOut)."\n"; + $this->assertSame( + "TypeError: stream_filter_append(): Argument #2 (\$filter_name) must be of type string, null given\n" + ."TypeError: stream_filter_prepend(): Argument #2 (\$filter_name) must be of type string, null given\n", + $joined + ); + $this->assertStringNotContainsString('unable to locate filter', $joined); + } + } finally { + @unlink($src); + @unlink($bin); + } + } +} diff --git a/test/unit/Issue31408StreamFilterNullNameStrictTest.php b/test/unit/Issue31408StreamFilterNullNameStrictTest.php new file mode 100644 index 00000000000..5e21193b899 --- /dev/null +++ b/test/unit/Issue31408StreamFilterNullNameStrictTest.php @@ -0,0 +1,33 @@ +assertNotFalse($code); + $rt = new Runtime(); + $block = $rt->parseAndCompile($code, 'maintainer_gap_stream_filter_append_null_name.php'); + ob_start(); + $rt->run($block); + $out = ob_get_clean(); + $this->assertSame( + "TypeError: stream_filter_append(): Argument #2 (\$filter_name) must be of type string, null given\n" + ."TypeError: stream_filter_prepend(): Argument #2 (\$filter_name) must be of type string, null given\n", + $out + ); + $this->assertStringNotContainsString('unable to locate filter', $out); + $this->assertStringNotContainsString('NO_THROW', $out); + } +}