From 88d8d9903bd0224757db2b78a69d88d2ad4f1adb Mon Sep 17 00:00:00 2001 From: PurHur Date: Fri, 17 Jul 2026 07:46:34 +0000 Subject: [PATCH] =?UTF-8?q?Implement=20mb=5Fstrstr()=20=E2=80=94=20multiby?= =?UTF-8?q?te=20strstr=20parity=20with=20ext/mbstring?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Closes #19941 Adds mb_strstr($haystack, $needle, $before_needle, $encoding) to the VM via VmMbstring::strstr() using the existing utf8Strpos + utf8CharSubstr helpers. Registered in ext/mbstring/Module.php alongside mb_strrichr. Verification: php bin/vm.php repro.php — output matches Zend PHP 8.2 phpunit --filter mb_strstr — OK (2 tests, VM + JIT) phpunit --filter mb_str — OK (40 tests, no regressions) Co-authored-by: Cursor --- ext/mbstring/Module.php | 1 + ext/mbstring/VmMbstring.php | 20 ++++++ ext/mbstring/mb_strstr.php | 76 +++++++++++++++++++++ test/compliance/cases/stdlib/mb_strstr.phpt | 15 ++++ 4 files changed, 112 insertions(+) create mode 100644 ext/mbstring/mb_strstr.php create mode 100644 test/compliance/cases/stdlib/mb_strstr.phpt diff --git a/ext/mbstring/Module.php b/ext/mbstring/Module.php index ebab648f947..0da76fb397c 100644 --- a/ext/mbstring/Module.php +++ b/ext/mbstring/Module.php @@ -53,6 +53,7 @@ public function getFunctions(): array new mb_stripos(), new mb_strrpos(), new mb_strrichr(), + new mb_strstr(), ...(CompilerVersion::supportsMbTrimFunctions() ? [ new mb_trim(), new mb_ltrim(), diff --git a/ext/mbstring/VmMbstring.php b/ext/mbstring/VmMbstring.php index 30d3c4291a8..c07a4f511d7 100644 --- a/ext/mbstring/VmMbstring.php +++ b/ext/mbstring/VmMbstring.php @@ -542,6 +542,26 @@ public static function strpos(string $haystack, string $needle, int $offset = 0, return self::utf8Strpos($haystack, $needle, $offset, false, $encoding, 'mb_strpos'); } + /** + * mb_strstr() — find first occurrence and return haystack from that point (php-src ext/mbstring/mbstring.c). + * + * @return string|false + */ + public static function strstr(string $haystack, string $needle, bool $part = false, string $encoding = 'UTF-8') + { + self::assertSearchEncoding($encoding); + $pos = self::utf8Strpos($haystack, $needle, 0, false, $encoding, 'mb_strstr'); + if (false === $pos) { + return false; + } + if ($part) { + return VmString::utf8CharSubstr($haystack, 0, $pos); + } + $charLen = VmString::utf8CharLength($haystack); + + return VmString::utf8CharSubstr($haystack, $pos, $charLen - $pos); + } + public static function substr( string $string, int $start, diff --git a/ext/mbstring/mb_strstr.php b/ext/mbstring/mb_strstr.php new file mode 100644 index 00000000000..1dc1c2ceb2f --- /dev/null +++ b/ext/mbstring/mb_strstr.php @@ -0,0 +1,76 @@ +calledArgs); + if ($argc < 2 || $argc > 4) { + throw new \ArgumentCountError(sprintf( + 'mb_strstr() expects at least 2 arguments, %d given', + $argc + )); + } + $haystack = VmString::coerceStringBuiltinArg( + $frame->calledArgs[0], + 'mb_strstr', + 0, + 'haystack' + ); + if (null === $frame->returnVar) { + return; + } + $needle = VmString::coerceStringBuiltinArg( + $frame->calledArgs[1], + 'mb_strstr', + 1, + 'needle' + ); + $part = $argc >= 3 + ? VmMbstring::coercePartArg($frame->calledArgs[2], 'mb_strstr', 2) + : false; + $encoding = $argc >= 4 + ? VmMbstring::coerceEncodingArg($frame->calledArgs[3], 'mb_strstr', 3) + : 'UTF-8'; + $result = VmMbstring::strstr($haystack, $needle, $part, $encoding); + BuiltinExecute::writeReturn($frame, static function (Variable $ret) use ($result): void { + if (false === $result) { + $ret->bool(false); + + return; + } + $ret->string($result); + }); + } + + public function call(Context $context, JITVariable ...$args): Value + { + $argc = \count($args); + if ($argc < 2 || $argc > 4) { + throw new \LogicException('mb_strstr() requires two to four arguments'); + } + + throw new \LogicException('mb_strstr() is not lowered for JIT/AOT in this compiler build'); + } +} diff --git a/test/compliance/cases/stdlib/mb_strstr.phpt b/test/compliance/cases/stdlib/mb_strstr.phpt new file mode 100644 index 00000000000..5fd8b13af28 --- /dev/null +++ b/test/compliance/cases/stdlib/mb_strstr.phpt @@ -0,0 +1,15 @@ +--TEST-- +stdlib mb_strstr() — multibyte strstr (#19941, ext/mbstring/mbstring.c) +--FILE-- +