Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
49 changes: 47 additions & 2 deletions Zend/zend_operators.c
Original file line number Diff line number Diff line change
Expand Up @@ -3853,7 +3853,7 @@ static zend_always_inline void zend_memnstr_ex_pre(unsigned int td[], const char
}
/* }}} */

ZEND_API const char* ZEND_FASTCALL zend_memnstr_ex(const char *haystack, const char *needle, size_t needle_len, const char *end) /* {{{ */
static zend_never_inline const char* ZEND_FASTCALL zend_memnstr_sunday(const char *haystack, const char *needle, size_t needle_len, const char *end)
{
unsigned int td[256];
size_t i;
Expand Down Expand Up @@ -3885,7 +3885,52 @@ ZEND_API const char* ZEND_FASTCALL zend_memnstr_ex(const char *haystack, const c

return NULL;
}
/* }}} */

static zend_never_inline const char* ZEND_FASTCALL zend_memnstr_sunday_short(const char *haystack, const char *needle, size_t needle_len, const char *end)
{
uint8_t td[256];
size_t i;
const char *p;

if (needle_len == 0 || (end - haystack) < needle_len) {
return NULL;
}

/* The dispatcher only sends short needles here, so every
* Sunday shift fits in one byte. Avoid filling a 1 KiB table per word. */
memset(td, (unsigned char)(needle_len + 1), sizeof(td));
for (i = 0; i < needle_len; i++) {
td[(unsigned char)needle[i]] = (unsigned char)(needle_len - i);
}

p = haystack;
end -= needle_len;

while (p <= end) {
for (i = 0; i < needle_len; i++) {
if (needle[i] != p[i]) {
break;
}
}
if (i == needle_len) {
return p;
}
if (UNEXPECTED(p == end)) {
return NULL;
}
p += td[(unsigned char)(p[needle_len])];
}

return NULL;
}

ZEND_API const char* ZEND_FASTCALL zend_memnstr_ex(const char *haystack, const char *needle, size_t needle_len, const char *end)
{
if (EXPECTED(needle_len < 32)) {
return zend_memnstr_sunday_short(haystack, needle, needle_len, end);
}
return zend_memnstr_sunday(haystack, needle, needle_len, end);
}

ZEND_API const char* ZEND_FASTCALL zend_memnrstr_ex(const char *haystack, const char *needle, size_t needle_len, const char *end) /* {{{ */
{
Expand Down
35 changes: 35 additions & 0 deletions ext/standard/tests/strings/str_search_short_sunday.phpt
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
--TEST--
Forward substring search with binary needles across the compact Sunday table boundary
--FILE--
<?php
$checked = 0;
$failed = 0;
foreach ([9, 15, 16, 30, 31, 32, 33, 254, 255, 256] as $length) {
// 0xfe occurs only at the end of the planted needle. The other bytes
// exercise unsigned shift-table indexing, embedded NULs and dense prefixes.
$needle = substr(str_repeat("\0\x80\xffa", $length), 0, $length - 1) . "\xfe";
foreach ([1023, 1024, 1025, 8191, 8192, 8193] as $size) {
$base = substr(str_repeat("\0\x80\xffa", $size), 0, $size);
foreach ([
[$base, false, [$base]],
// Bytes absent from the needle use the default needle_len + 1 shift.
[str_repeat('z', $size), false, [str_repeat('z', $size)]],
[$needle . $base, 0, ['', $base]],
[$base . $needle, $size, [$base, '']],
// After the first match, the next successful search uses a short remainder.
[$base . $needle . 'gap' . $needle . 'tail', $size, [$base, 'gap', 'tail']],
] as [$haystack, $position, $parts]) {
$failed += strpos($haystack, $needle) !== $position;
$failed += str_contains($haystack, $needle) !== ($position !== false);
$failed += substr_count($haystack, $needle) !== (count($parts) - 1);
$failed += explode($needle, $haystack) !== $parts;
$checked++;
}
}
}
echo "checked $checked\n";
echo $failed ? "FAILED ($failed)\n" : "ALL OK\n";
?>
--EXPECT--
checked 300
ALL OK
Loading