LibRegex: Track optional capture groups for match_length_minimum
Backreferences can match the empty string when the referenced group didn't participate in the match, so we shouldn't add their length to the match_length_minimum, as it makes us skip valid matches.
This commit is contained in:
parent
53a98f26d4
commit
6f1b7c8d50
3 changed files with 43 additions and 1 deletions
|
|
@ -182,6 +182,7 @@ ALWAYS_INLINE void Parser::reset()
|
|||
m_parser_state.error = Error::NoError;
|
||||
m_parser_state.error_token = { TokenType::Eof, 0, {} };
|
||||
m_parser_state.capture_group_minimum_lengths.clear();
|
||||
m_parser_state.optional_capture_groups.clear();
|
||||
m_parser_state.capture_groups_count = 0;
|
||||
m_parser_state.named_capture_groups_count = 0;
|
||||
m_parser_state.named_capture_groups.clear();
|
||||
|
|
@ -1011,6 +1012,7 @@ bool ECMA262Parser::parse_disjunction(ByteCode& stack, size_t& match_length_mini
|
|||
{
|
||||
size_t total_match_length_minimum = NumericLimits<size_t>::max();
|
||||
Vector<ByteCode> alternatives;
|
||||
size_t initial_capture_groups_count = m_parser_state.capture_groups_count;
|
||||
|
||||
TemporaryChange<size_t> alternative_id_change { m_current_alternative_id, 1 };
|
||||
|
||||
|
|
@ -1033,6 +1035,7 @@ bool ECMA262Parser::parse_disjunction(ByteCode& stack, size_t& match_length_mini
|
|||
|
||||
if (alternatives.size() > 1) {
|
||||
m_parser_state.greedy_lookaround = false;
|
||||
mark_capture_groups_as_optional_from(initial_capture_groups_count);
|
||||
}
|
||||
Optimizer::append_alternation(stack, alternatives.span());
|
||||
match_length_minimum = total_match_length_minimum;
|
||||
|
|
@ -1060,6 +1063,8 @@ bool ECMA262Parser::parse_term(ByteCode& stack, size_t& match_length_minimum, Pa
|
|||
|
||||
ByteCode atom_stack;
|
||||
size_t minimum_atom_length = 0;
|
||||
size_t initial_capture_groups_count = m_parser_state.capture_groups_count;
|
||||
|
||||
auto parse_with_quantifier = [&] {
|
||||
bool did_parse_one = false;
|
||||
if (m_should_use_browser_extended_grammar)
|
||||
|
|
@ -1078,6 +1083,9 @@ bool ECMA262Parser::parse_term(ByteCode& stack, size_t& match_length_minimum, Pa
|
|||
if (!parse_with_quantifier())
|
||||
return false;
|
||||
|
||||
if (minimum_atom_length == 0)
|
||||
mark_capture_groups_as_optional_from(initial_capture_groups_count);
|
||||
|
||||
stack.extend(move(atom_stack));
|
||||
match_length_minimum += minimum_atom_length;
|
||||
return true;
|
||||
|
|
@ -1128,6 +1136,7 @@ bool ECMA262Parser::parse_assertion(ByteCode& stack, [[maybe_unused]] size_t& ma
|
|||
}
|
||||
if (should_parse_forward_assertion && try_skip("!"sv)) {
|
||||
enter_capture_group_scope();
|
||||
size_t initial_capture_groups_count = m_parser_state.capture_groups_count;
|
||||
ScopeGuard quit_scope {
|
||||
[this] {
|
||||
exit_capture_group_scope();
|
||||
|
|
@ -1137,6 +1146,7 @@ bool ECMA262Parser::parse_assertion(ByteCode& stack, [[maybe_unused]] size_t& ma
|
|||
return false;
|
||||
stack.insert_bytecode_lookaround(move(assertion_stack), ByteCode::LookAroundType::NegatedLookAhead);
|
||||
clear_all_capture_groups_in_scope(stack);
|
||||
mark_capture_groups_as_optional_from(initial_capture_groups_count);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1161,6 +1171,7 @@ bool ECMA262Parser::parse_assertion(ByteCode& stack, [[maybe_unused]] size_t& ma
|
|||
}
|
||||
if (try_skip("<!"sv)) {
|
||||
enter_capture_group_scope();
|
||||
size_t initial_capture_groups_count = m_parser_state.capture_groups_count;
|
||||
ScopeGuard quit_scope {
|
||||
[this] {
|
||||
exit_capture_group_scope();
|
||||
|
|
@ -1170,6 +1181,7 @@ bool ECMA262Parser::parse_assertion(ByteCode& stack, [[maybe_unused]] size_t& ma
|
|||
return false;
|
||||
stack.insert_bytecode_lookaround(move(assertion_stack), ByteCode::LookAroundType::NegatedLookBehind, length_dummy);
|
||||
clear_all_capture_groups_in_scope(stack);
|
||||
mark_capture_groups_as_optional_from(initial_capture_groups_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1207,6 +1219,7 @@ bool ECMA262Parser::parse_quantifiable_assertion(ByteCode& stack, size_t&, Parse
|
|||
}
|
||||
if (try_skip("!"sv)) {
|
||||
enter_capture_group_scope();
|
||||
size_t initial_capture_groups_count = m_parser_state.capture_groups_count;
|
||||
ScopeGuard quit_scope {
|
||||
[this] {
|
||||
exit_capture_group_scope();
|
||||
|
|
@ -1217,6 +1230,7 @@ bool ECMA262Parser::parse_quantifiable_assertion(ByteCode& stack, size_t&, Parse
|
|||
|
||||
stack.insert_bytecode_lookaround(move(assertion_stack), ByteCode::LookAroundType::NegatedLookAhead);
|
||||
clear_all_capture_groups_in_scope(stack);
|
||||
mark_capture_groups_as_optional_from(initial_capture_groups_count);
|
||||
return true;
|
||||
}
|
||||
|
||||
|
|
@ -1636,7 +1650,8 @@ bool ECMA262Parser::parse_atom_escape(ByteCode& stack, size_t& match_length_mini
|
|||
// See if this is a "back"-reference (we've already parsed the group it refers to)
|
||||
auto maybe_length = m_parser_state.capture_group_minimum_lengths.get(escape.value());
|
||||
if (maybe_length.has_value()) {
|
||||
match_length_minimum += maybe_length.value();
|
||||
if (!m_parser_state.optional_capture_groups.contains(escape.value()))
|
||||
match_length_minimum += maybe_length.value();
|
||||
stack.insert_bytecode_compare_values({ { CharacterCompareType::Reference, (ByteCodeValueType)escape.value() } });
|
||||
return true;
|
||||
}
|
||||
|
|
@ -1679,6 +1694,8 @@ bool ECMA262Parser::parse_atom_escape(ByteCode& stack, size_t& match_length_mini
|
|||
auto maybe_length = m_parser_state.capture_group_minimum_lengths.get(group_index);
|
||||
if (maybe_length.has_value()) {
|
||||
// Backward reference
|
||||
if (!m_parser_state.optional_capture_groups.contains(group_index))
|
||||
match_length_minimum += maybe_length.value();
|
||||
stack.insert_bytecode_compare_values({ { CharacterCompareType::NamedReference, static_cast<ByteCodeValueType>(group_index) } });
|
||||
} else {
|
||||
// Self-reference or forward reference
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <AK/FlyString.h>
|
||||
#include <AK/Forward.h>
|
||||
#include <AK/HashMap.h>
|
||||
#include <AK/HashTable.h>
|
||||
#include <AK/Types.h>
|
||||
#include <AK/Vector.h>
|
||||
#include <LibUnicode/Forward.h>
|
||||
|
|
@ -123,6 +124,7 @@ protected:
|
|||
bool in_negated_character_class { false };
|
||||
AllOptions regex_options;
|
||||
HashMap<size_t, size_t> capture_group_minimum_lengths;
|
||||
HashTable<size_t> optional_capture_groups;
|
||||
OrderedHashMap<FlyString, Vector<NamedCaptureGroup>> named_capture_groups;
|
||||
|
||||
struct UnresolvedNamedReference {
|
||||
|
|
@ -323,6 +325,12 @@ private:
|
|||
stack.insert_bytecode_clear_capture_group(index);
|
||||
}
|
||||
|
||||
void mark_capture_groups_as_optional_from(size_t first_group)
|
||||
{
|
||||
for (size_t i = first_group + 1; i <= m_parser_state.capture_groups_count; ++i)
|
||||
m_parser_state.optional_capture_groups.set(i);
|
||||
}
|
||||
|
||||
// ECMA-262's flavour of regex is a bit weird in that it allows backrefs to reference "future" captures, and such backrefs
|
||||
// always match the empty string. So we have to know how many capturing parenthesis there are, but we don't want to always
|
||||
// parse it twice, so we'll just do so when it's actually needed.
|
||||
|
|
|
|||
|
|
@ -1485,6 +1485,23 @@ TEST_CASE(zero_width_backreference)
|
|||
EXPECT_EQ(result.matches.first().view.to_byte_string(), "b"sv);
|
||||
EXPECT_EQ(result.capture_group_matches.first()[0].view.to_byte_string(), ""sv);
|
||||
}
|
||||
{
|
||||
Regex<ECMA262> re("(x)?\\1y"sv);
|
||||
auto result = re.match("y"sv);
|
||||
|
||||
EXPECT_EQ(result.success, true);
|
||||
EXPECT_EQ(result.matches.first().view, "y"sv);
|
||||
EXPECT(result.capture_group_matches.first()[0].view.is_null());
|
||||
}
|
||||
{
|
||||
Regex<ECMA262> re("(?!(y)y)(\\1)z"sv, ECMAScriptFlags::Global);
|
||||
auto result = re.match("xyyz"sv);
|
||||
|
||||
EXPECT_EQ(result.success, true);
|
||||
EXPECT_EQ(result.matches.first().view, "z"sv);
|
||||
EXPECT(result.capture_group_matches.first()[0].view.is_null());
|
||||
EXPECT_EQ(result.capture_group_matches.first()[1].view.to_byte_string(), ""sv);
|
||||
}
|
||||
}
|
||||
|
||||
TEST_CASE(account_for_opcode_size_calculating_incoming_jump_edges)
|
||||
|
|
|
|||
Loading…
Reference in a new issue