String search sees NUL as newline by default.

That is the most intuitive option for binary searches.
The behavior can still be changed, as seen in the added example.
This commit is contained in:
Rot127 2026-06-28 15:17:38 +02:00 committed by Rot127
parent e6362c2942
commit c3a90e9226
7 changed files with 63 additions and 17 deletions

View file

@ -2901,6 +2901,7 @@ static const RzCmdDescDetailEntry slash_z_Examples_detail_entries[] = {
{ .text = "/z", .arg_str = " (ABC*)D li", .comment = "Search the exact string \"(ABC*)D\" but case insensitive." },
{ .text = "/z", .arg_str = " \\\\d\\\\sC*\\\\w ri", .comment = "Search the regular expression \"\\d\\sC*\\w\" but case insensitive." },
{ .text = "/z", .arg_str = " \"и.{3}м\" ei", .comment = "Search the extended regular expression \"и.{3}м\" but case insensitive." },
{ .text = "/z", .arg_str = " \"(*LF).+\" r ascii", .comment = "Search any ascii string with line feed is a newline." },
{ 0 },
};
static const RzCmdDescDetail slash_z_details[] = {

View file

@ -1132,6 +1132,9 @@ commands:
- text: "/z"
arg_str: " \"и.{3}м\" ei"
comment: "Search the extended regular expression \"и.{3}м\" but case insensitive."
- text: "/z"
arg_str: " \"(*LF).+\" r ascii"
comment: "Search any ascii string with line feed is a newline."
subcommands:
- name: "/z"
summary: String search.

View file

@ -80,6 +80,7 @@ typedef struct {
RzRegex32 *re32;
};
void *jit_stack; ///< The JIT stack for this pattern. Must be used only by one thread at a time.
RzRegexCompContext *ccontext;
} RzRegexMulti;
typedef struct {
@ -106,7 +107,7 @@ RZ_API RZ_OWN RzRegex16 *rz_regex_new_16(RZ_NONNULL const char *pattern, RzRegex
RZ_API RZ_OWN RzRegex32 *rz_regex_new_32(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags,
RzRegexCompContext *ccontext);
RZ_API RZ_OWN RzRegexMulti *rz_regex_new_multi(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags,
RzRegexCompContext *ccontext, RzRegexType type);
RzRegexType type);
RZ_API RZ_OWN RzRegex *rz_regex_new_bytes(RZ_NONNULL const ut8 *pattern, size_t pattern_len, RzRegexFlags cflags, RzRegexFlags jflags,
RzRegexCompContext *ccontext);
RZ_API void rz_regex_free(RZ_OWN RzRegex *regex);

View file

@ -422,19 +422,19 @@ static RzDetectedString *setup_str_regex(const char *re_pattern, RzRegexFlags cf
return NULL;
case RZ_STRING_ENC_UTF8:
case RZ_STRING_ENC_8BIT:
re = rz_regex_new_multi(re_pattern, cflags, RZ_REGEX_DEFAULT, NULL, RZ_REGEX_UTF8);
re = rz_regex_new_multi(re_pattern, cflags, RZ_REGEX_DEFAULT, RZ_REGEX_UTF8);
break;
case RZ_STRING_ENC_UTF16LE:
case RZ_STRING_ENC_UTF16BE:
re = rz_regex_new_multi(re_pattern, cflags, RZ_REGEX_DEFAULT, NULL, RZ_REGEX_UTF16);
re = rz_regex_new_multi(re_pattern, cflags, RZ_REGEX_DEFAULT, RZ_REGEX_UTF16);
break;
case RZ_STRING_ENC_UTF32LE:
case RZ_STRING_ENC_UTF32BE:
re = rz_regex_new_multi(re_pattern, cflags, RZ_REGEX_DEFAULT, NULL, RZ_REGEX_UTF32);
re = rz_regex_new_multi(re_pattern, cflags, RZ_REGEX_DEFAULT, RZ_REGEX_UTF32);
break;
}
} else {
re = rz_regex_new_multi(re_pattern, cflags, RZ_REGEX_DEFAULT, NULL, RZ_REGEX_UTF8);
re = rz_regex_new_multi(re_pattern, cflags, RZ_REGEX_DEFAULT, RZ_REGEX_UTF8);
}
if (!re) {
RZ_LOG_ERROR("Failed to compile regex pattern: '%s'\n", re_pattern);

View file

@ -227,6 +227,7 @@ RZ_API RZ_OWN RzRegex32 *rz_regex_new_32(RZ_NONNULL const char *pattern, RzRegex
* \brief Compile an Regex pattern of \p type.
*
* NOTE: The pattern and matching will always be in the host's endianness.
* NOTE: The patterns are compiled with PCRE2_NEWLINE_NUL.
*
* \param pattern The regex pattern string. It must be an UTF-8 encoded string.
* \param cflags The compilation flags or zero for default.
@ -234,13 +235,11 @@ RZ_API RZ_OWN RzRegex32 *rz_regex_new_32(RZ_NONNULL const char *pattern, RzRegex
* \param jflags The compilation flags for the JIT compiler.
* You can pass RZ_REGEX_JIT_PARTIAL_SOFT or RZ_REGEX_JIT_PARTIAL_HARD if you
* intend to use the pattern for partial matching. Otherwise set it to RZ_REGEX_DEFAULT.
* \param ccontext A compile context or NULL.
* \param type The string encoding type the pattern should match.
*
* \return The compiled regex or NULL in case of failure.
*/
RZ_API RZ_OWN RzRegexMulti *rz_regex_new_multi(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags,
RzRegexCompContext *ccontext, RzRegexType type) {
RZ_API RZ_OWN RzRegexMulti *rz_regex_new_multi(RZ_NONNULL const char *pattern, RzRegexFlags cflags, RzRegexFlags jflags, RzRegexType type) {
RzRegexMulti *re = RZ_NEW0(RzRegexMulti);
if (!re) {
return NULL;
@ -255,16 +254,28 @@ RZ_API RZ_OWN RzRegexMulti *rz_regex_new_multi(RZ_NONNULL const char *pattern, R
rz_warn_if_reached();
free(re);
return NULL;
case RZ_REGEX_UTF8:
case RZ_REGEX_UTF8: {
RzRegexCompContext *ccontext = pcre2_compile_context_create_8(NULL);
pcre2_set_newline_8(ccontext, PCRE2_NEWLINE_NUL);
re->re8 = rz_regex_new(pattern, cflags, jflags, ccontext);
re->ccontext = ccontext;
break;
case RZ_REGEX_UTF16:
}
case RZ_REGEX_UTF16: {
RzRegexCompContext *ccontext = pcre2_compile_context_create_16(NULL);
pcre2_set_newline_16(ccontext, PCRE2_NEWLINE_NUL);
re->re16 = rz_regex_new_16(pattern, cflags, jflags, ccontext);
re->ccontext = ccontext;
break;
case RZ_REGEX_UTF32:
}
case RZ_REGEX_UTF32: {
RzRegexCompContext *ccontext = pcre2_compile_context_create_32(NULL);
pcre2_set_newline_32(ccontext, PCRE2_NEWLINE_NUL);
re->re32 = rz_regex_new_32(pattern, cflags, jflags, ccontext);
re->ccontext = ccontext;
break;
}
}
if (!re->re8 && !re->re16 && !re->re32) {
free(re);
return NULL;
@ -457,12 +468,15 @@ RZ_API void rz_regex_free_multi(RZ_NULLABLE RZ_OWN RzRegexMulti *regex_multi) {
switch (regex_multi->re_type) {
case RZ_REGEX_UTF8:
rz_regex_free(regex_multi->re8);
pcre2_compile_context_free_8(regex_multi->ccontext);
break;
case RZ_REGEX_UTF16:
rz_regex_free_16(regex_multi->re16);
pcre2_compile_context_free_16(regex_multi->ccontext);
break;
case RZ_REGEX_UTF32:
rz_regex_free_32(regex_multi->re32);
pcre2_compile_context_free_32(regex_multi->ccontext);
break;
}
#ifdef SUPPORTS_PCRE2_JIT

View file

@ -299,11 +299,11 @@ e scr.columns=70
/z??~:-6..
EOF
EXPECT=<<EOF
| /z (ABC*)D li # Search the exact string "(ABC*)D" but case
insensitive.
| /z \\d\\sC*\\w ri # Search the regular expression "\d\sC*\w" but
case insensitive.
| /z "и.{3}м" ei # Search the extended regular expression
"и.{3}м" but case insensitive.
| /z \\d\\sC*\\w ri # Search the regular expression "\d\sC*\w" but
case insensitive.
| /z "и.{3}м" ei # Search the extended regular expression
"и.{3}м" but case insensitive.
| /z "(*LF).+" r ascii # Search any ascii string with line feed is a
newline.
EOF
RUN

View file

@ -1548,3 +1548,30 @@ EXPECT=<<EOF
EOF
EXPECT_ERR=
RUN
NAME=Strings with new lines and \0 terminators
FILE==
CMDS=<<EOF
# Searching in empty buffer should not find any strings
/z .+ ri ascii
# Now write strings with new lines and NUL byte mixed
wx 41414141414141004141414141410a0041414141414141004141414141410a00
# NUL is a new line by default
/z .+ r ascii
# Overwrite default newline (NUL), \0 can be matched now with dot.
/z "(*LF).+" r ascii
EOF
EXPECT=<<EOF
0x00000000 7 hit.string.ascii.0 7
0x00000008 7 hit.string.ascii.1 7
0x00000010 7 hit.string.ascii.2 7
0x00000018 7 hit.string.ascii.3 7
0x00000000 14 hit.string.ascii.0 14
0x0000000f 15 hit.string.ascii.1 15
0x0000001f 481 hit.string.ascii.2 1e1
EOF
EXPECT_ERR=
RUN