Skip to content

Commit 6e897cf

Browse files
committed
[pcrepp] fix match_partial for overly large input
1 parent d4ded13 commit 6e897cf

2 files changed

Lines changed: 53 additions & 16 deletions

File tree

src/pcrepp/pcre2pp.cc

Lines changed: 45 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,8 @@
3131

3232
#include "pcre2pp.hh"
3333

34+
#include <algorithm>
35+
3436
#include "config.h"
3537
#include "ww898/cp_utf8.hpp"
3638

@@ -136,28 +138,55 @@ code::find_in(string_fragment in, uint32_t options) const
136138
size_t
137139
code::match_partial(string_fragment in) const
138140
{
141+
// This function is used for error reporting to show where a line
142+
// diverges from the expected pattern. Cap the input length to
143+
// avoid excessive work on very large (e.g. multi-MB binary) lines.
144+
static constexpr size_t MAX_PARTIAL_LEN = 8192;
145+
139146
auto md = this->create_match_data();
140-
auto length = in.length();
147+
auto length = std::min((size_t) in.length(), MAX_PARTIAL_LEN);
148+
149+
// Try the full (capped) length first -- if it partially matches,
150+
// we're done.
151+
auto rc = pcre2_match(this->p_code.in(),
152+
in.udata(),
153+
length,
154+
0,
155+
PCRE2_PARTIAL_HARD,
156+
md.md_data.in(),
157+
nullptr);
141158

142-
do {
143-
auto rc = pcre2_match(this->p_code.in(),
144-
in.udata(),
145-
length,
146-
0,
147-
PCRE2_PARTIAL_HARD,
148-
md.md_data.in(),
149-
nullptr);
159+
if (rc == PCRE2_ERROR_PARTIAL) {
160+
return md.md_ovector[1];
161+
}
150162

151-
if (rc == PCRE2_ERROR_PARTIAL) {
152-
return md.md_ovector[1];
153-
}
163+
// Binary search for the largest length that yields a partial match.
164+
// For anchored patterns (the primary use case), partial-match is
165+
// monotonic over length.
166+
size_t lo = 1;
167+
size_t hi = length;
168+
size_t best = 0;
154169

155-
if (length > 0) {
156-
length -= 1;
170+
while (lo <= hi) {
171+
auto mid = lo + (hi - lo) / 2;
172+
173+
rc = pcre2_match(this->p_code.in(),
174+
in.udata(),
175+
mid,
176+
0,
177+
PCRE2_PARTIAL_HARD,
178+
md.md_data.in(),
179+
nullptr);
180+
181+
if (rc == PCRE2_ERROR_PARTIAL) {
182+
best = md.md_ovector[1];
183+
lo = mid + 1;
184+
} else {
185+
hi = mid - 1;
157186
}
158-
} while (length > 0);
187+
}
159188

160-
return 0;
189+
return best;
161190
}
162191

163192
const char*

src/pcrepp/test_pcre2pp.cc

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,14 @@ TEST_CASE("partial")
133133
auto matched = co.match_partial(string_fragment::from_const(INPUT));
134134
CHECK(matched == 3);
135135
}
136+
137+
{
138+
// Large input that doesn't match -- should return promptly
139+
// due to the input cap, not loop for millions of iterations.
140+
std::string big_input(32768, 'Z');
141+
auto matched = co.match_partial(string_fragment::from_str(big_input));
142+
CHECK(matched == 0);
143+
}
136144
}
137145

138146
TEST_CASE("capture_name")

0 commit comments

Comments
 (0)