LibRegex: Reject RSeekTo crossing the current-to-EOL boundary
This commit is contained in:
parent
f4d4bd9ed1
commit
fedf0f78ca
7 changed files with 143 additions and 23 deletions
|
|
@ -266,36 +266,56 @@ Optional<size_t> Utf16View::find_code_unit_offset(char16_t needle, size_t start_
|
|||
return result - start + start_offset;
|
||||
}
|
||||
|
||||
Optional<size_t> Utf16View::find_last_code_unit_offset(char16_t needle, size_t end_offset) const
|
||||
Optional<size_t> Utf16View::find_last_code_point_offset(u32 needle, size_t end_offset) const
|
||||
{
|
||||
if (end_offset == 0)
|
||||
return {};
|
||||
|
||||
auto const limit = min(end_offset, length_in_code_units());
|
||||
|
||||
if (has_ascii_storage()) {
|
||||
if (!AK::is_ascii(needle))
|
||||
return {};
|
||||
auto ascii_end_offset = min(end_offset, length_in_code_units());
|
||||
auto ascii_view = StringView { m_string.ascii, ascii_end_offset };
|
||||
auto index = ascii_view.find_last(static_cast<char>(needle));
|
||||
if (!index.has_value())
|
||||
return {};
|
||||
return *index;
|
||||
auto ascii_view = StringView { m_string.ascii, limit };
|
||||
return ascii_view.find_last(static_cast<char>(needle));
|
||||
}
|
||||
|
||||
if (needle <= 0xFFFF) {
|
||||
auto const* start = m_string.utf16;
|
||||
auto const* end = m_string.utf16 + limit;
|
||||
auto const char16_needle = static_cast<char16_t>(needle);
|
||||
|
||||
Optional<size_t> last_found;
|
||||
auto const* search_start = start;
|
||||
while (true) {
|
||||
auto const* result = simdutf::find(search_start, end, char16_needle);
|
||||
if (result == end)
|
||||
break;
|
||||
last_found = result - start;
|
||||
search_start = result + 1;
|
||||
}
|
||||
return last_found;
|
||||
}
|
||||
|
||||
// Search for high surrogate and verify low surrogate.
|
||||
auto const adjusted = needle - 0x10000;
|
||||
auto const high = static_cast<char16_t>(0xD800 | (adjusted >> 10));
|
||||
auto const low = static_cast<char16_t>(0xDC00 | (adjusted & 0x3FF));
|
||||
|
||||
auto const* start = m_string.utf16;
|
||||
auto const* end = m_string.utf16 + end_offset;
|
||||
auto const* end = m_string.utf16 + limit;
|
||||
|
||||
auto const* last_result = simdutf::find(start, end, needle);
|
||||
Optional<size_t> last_found;
|
||||
auto const* search_start = start;
|
||||
while (true) {
|
||||
auto const* result = simdutf::find(last_result + 1, end, needle);
|
||||
if (result == end)
|
||||
auto const* result = simdutf::find(search_start, end, high);
|
||||
if (result == end || result + 1 >= m_string.utf16 + length_in_code_units())
|
||||
break;
|
||||
last_result = result;
|
||||
if (result[1] == low)
|
||||
last_found = result - start;
|
||||
search_start = result + 1;
|
||||
}
|
||||
if (last_result == end)
|
||||
return {};
|
||||
|
||||
return last_result - start;
|
||||
return last_found;
|
||||
}
|
||||
|
||||
Vector<Utf16View> Utf16View::split_view(char16_t separator, SplitBehavior split_behavior) const
|
||||
|
|
|
|||
|
|
@ -485,7 +485,7 @@ public:
|
|||
}
|
||||
|
||||
Optional<size_t> find_code_unit_offset(char16_t needle, size_t start_offset = 0) const;
|
||||
Optional<size_t> find_last_code_unit_offset(char16_t needle, size_t end_offset = NumericLimits<size_t>::max()) const;
|
||||
Optional<size_t> find_last_code_point_offset(u32 needle, size_t end_offset = NumericLimits<size_t>::max()) const;
|
||||
|
||||
constexpr Optional<size_t> find_code_unit_offset(Utf16View const& needle, size_t start_offset = 0) const
|
||||
{
|
||||
|
|
|
|||
|
|
@ -641,11 +641,35 @@ template<typename ByteCode>
|
|||
ALWAYS_INLINE ExecutionResult OpCode_RSeekTo<ByteCode>::execute(MatchInput const& input, MatchState& state) const
|
||||
{
|
||||
auto ch = argument(0);
|
||||
auto last_position = exchange(state.string_position_before_rseek, state.string_position);
|
||||
auto last_position_in_code_units = exchange(state.string_position_in_code_units_before_rseek, state.string_position_in_code_units);
|
||||
auto next = input.view.find_index_of_previous(ch, last_position, last_position_in_code_units);
|
||||
if (!next.has_value())
|
||||
|
||||
size_t search_from;
|
||||
size_t search_from_in_code_units;
|
||||
auto line_limited = false;
|
||||
|
||||
if (state.string_position_before_rseek == NumericLimits<size_t>::max()) {
|
||||
state.string_position_before_rseek = state.string_position;
|
||||
state.string_position_in_code_units_before_rseek = state.string_position_in_code_units;
|
||||
|
||||
if (!input.regex_options.has_flag_set(AllFlags::SingleLine)) {
|
||||
auto end_of_line = input.view.find_end_of_line(state.string_position, state.string_position_in_code_units);
|
||||
search_from = end_of_line.code_point_index + 1;
|
||||
search_from_in_code_units = end_of_line.code_unit_index + 1;
|
||||
line_limited = true;
|
||||
} else {
|
||||
search_from = NumericLimits<size_t>::max();
|
||||
search_from_in_code_units = NumericLimits<size_t>::max();
|
||||
}
|
||||
} else {
|
||||
search_from = state.string_position;
|
||||
search_from_in_code_units = state.string_position_in_code_units;
|
||||
}
|
||||
|
||||
auto next = input.view.find_index_of_previous(ch, search_from, search_from_in_code_units);
|
||||
if (!next.has_value() || next->code_unit_index < state.string_position_in_code_units_before_rseek) {
|
||||
if (line_limited)
|
||||
return ExecutionResult::Failed_ExecuteLowPrioForks;
|
||||
return ExecutionResult::Failed_ExecuteLowPrioForksButNoFurtherPossibleMatches;
|
||||
}
|
||||
state.string_position = next->code_point_index;
|
||||
state.string_position_in_code_units = next->code_unit_index;
|
||||
return ExecutionResult::Continue;
|
||||
|
|
|
|||
|
|
@ -16,6 +16,7 @@
|
|||
#include <AK/MemMem.h>
|
||||
#include <AK/StringBuilder.h>
|
||||
#include <AK/StringView.h>
|
||||
#include <AK/UnicodeUtils.h>
|
||||
#include <AK/Utf16String.h>
|
||||
#include <AK/Utf16View.h>
|
||||
#include <AK/Utf32View.h>
|
||||
|
|
@ -303,7 +304,7 @@ public:
|
|||
{
|
||||
return m_view.visit(
|
||||
[&](Utf16View const& view) -> Optional<FoundIndex> {
|
||||
auto result = view.find_last_code_unit_offset(code_point, end_code_unit_index);
|
||||
auto result = view.find_last_code_point_offset(code_point, end_code_unit_index);
|
||||
if (!result.has_value())
|
||||
return {};
|
||||
return FoundIndex { result.value(), view.code_point_offset_of(result.value()) };
|
||||
|
|
@ -316,7 +317,7 @@ public:
|
|||
Optional<FoundIndex> found_index;
|
||||
|
||||
for (; it != utf8_view.end(); ++it, ++current_code_point_index) {
|
||||
if (current_code_point_index > end_code_point_index)
|
||||
if (current_code_point_index >= end_code_point_index)
|
||||
break;
|
||||
if (*it == code_point) {
|
||||
auto byte_index = utf8_view.byte_offset_of(it);
|
||||
|
|
@ -334,6 +335,62 @@ public:
|
|||
});
|
||||
}
|
||||
|
||||
FoundIndex find_end_of_line(size_t start_code_point_index, size_t start_code_unit_index) const
|
||||
{
|
||||
constexpr auto is_newline = [](u32 ch) { return ch == '\n' || ch == '\r' || ch == 0x2028 || ch == 0x2029; };
|
||||
|
||||
return m_view.visit(
|
||||
[&](Utf16View const& view) -> FoundIndex {
|
||||
size_t code_unit_index = start_code_unit_index;
|
||||
size_t code_point_index = start_code_point_index;
|
||||
while (code_unit_index < view.length_in_code_units()) {
|
||||
auto code_unit = view.code_unit_at(code_unit_index);
|
||||
u32 ch = code_unit;
|
||||
size_t code_units_for_this = 1;
|
||||
if (AK::UnicodeUtils::is_utf16_high_surrogate(code_unit) && code_unit_index + 1 < view.length_in_code_units()) {
|
||||
auto next_code_unit = view.code_unit_at(code_unit_index + 1);
|
||||
if (AK::UnicodeUtils::is_utf16_low_surrogate(next_code_unit)) {
|
||||
ch = AK::UnicodeUtils::decode_utf16_surrogate_pair(code_unit, next_code_unit);
|
||||
code_units_for_this = 2;
|
||||
}
|
||||
}
|
||||
|
||||
if (is_newline(ch))
|
||||
return FoundIndex { code_unit_index, code_point_index };
|
||||
code_unit_index += code_units_for_this;
|
||||
++code_point_index;
|
||||
}
|
||||
return FoundIndex { view.length_in_code_units(), code_point_index };
|
||||
},
|
||||
[&](StringView const& view) -> FoundIndex {
|
||||
if (unicode()) {
|
||||
Utf8View utf8_view { view };
|
||||
auto it = utf8_view.begin();
|
||||
size_t current_code_point_index = 0;
|
||||
|
||||
// Skip to start position
|
||||
while (it != utf8_view.end() && current_code_point_index < start_code_point_index) {
|
||||
++it;
|
||||
++current_code_point_index;
|
||||
}
|
||||
|
||||
for (; it != utf8_view.end(); ++it, ++current_code_point_index) {
|
||||
if (is_newline(*it)) {
|
||||
return FoundIndex { utf8_view.byte_offset_of(it), current_code_point_index };
|
||||
}
|
||||
}
|
||||
|
||||
return FoundIndex { view.length(), utf8_view.length() };
|
||||
}
|
||||
|
||||
for (size_t i = start_code_unit_index; i < view.length(); ++i) {
|
||||
if (is_newline(static_cast<u8>(view[i])))
|
||||
return FoundIndex { i, i };
|
||||
}
|
||||
return FoundIndex { view.length(), view.length() };
|
||||
});
|
||||
}
|
||||
|
||||
private:
|
||||
NO_UNIQUE_ADDRESS Variant<StringView, Utf16View> m_view { StringView {} };
|
||||
NO_UNIQUE_ADDRESS bool m_unicode { false };
|
||||
|
|
|
|||
|
|
@ -340,6 +340,8 @@ RegexResult Matcher<Parser>::match(Vector<RegexStringView> const& views, Optiona
|
|||
state.repetition_marks.clear();
|
||||
state.modifier_stack.clear();
|
||||
state.current_options = input.regex_options;
|
||||
state.string_position_before_rseek = NumericLimits<size_t>::max();
|
||||
state.string_position_in_code_units_before_rseek = NumericLimits<size_t>::max();
|
||||
|
||||
if (auto const result = execute(input, state, operations); result == ExecuteResult::Matched) {
|
||||
succeeded = true;
|
||||
|
|
|
|||
|
|
@ -25,3 +25,9 @@ describe("basic functionality", () => {
|
|||
expect(accessedUnicode).toBeFalse();
|
||||
});
|
||||
});
|
||||
|
||||
describe("seek pattern with global flags", () => {
|
||||
test("doesn't consider matches from before the current position", () => {
|
||||
expect("1234\nabcdefg\n1234".match(/.*b(cd)/g)).toEqual(["abcd"]);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1271,6 +1271,17 @@ TEST_CASE(optimizer_alternation)
|
|||
}
|
||||
}
|
||||
|
||||
TEST_CASE(optimizer_rseekto)
|
||||
{
|
||||
Regex<ECMA262> re("^(.*)\\/(?:\\/(.*))$"); // should backtrack from the second '/'.
|
||||
|
||||
auto result = re.match("foo//bar"sv);
|
||||
EXPECT_EQ(result.success, true);
|
||||
EXPECT_EQ(result.matches.at(0).view, "foo//bar"sv);
|
||||
EXPECT_EQ(result.capture_group_matches.at(0).at(0).view, "foo"sv);
|
||||
EXPECT_EQ(result.capture_group_matches.at(0).at(1).view, "bar"sv);
|
||||
}
|
||||
|
||||
TEST_CASE(start_anchor)
|
||||
{
|
||||
// Ensure that a circumflex at the start only matches the start of the line.
|
||||
|
|
|
|||
Loading…
Reference in a new issue