LibRegex: Fix negated class validation for nested string properties

We were incorrectly checking for negated character class when string
properties appeared in nested classes. Now we track negation state in
the parser and correctly reject invalid string properties in negated
classes.
This commit is contained in:
aplefull 2025-12-08 12:29:40 +01:00 committed by Ali Mohammad Pur
parent f3a32a0b1a
commit ff06a4a9e5
3 changed files with 22 additions and 12 deletions

View file

@ -1841,13 +1841,21 @@ bool ECMA262Parser::parse_character_class(ByteCode& stack, size_t& match_length_
Vector<CompareTypeAndValuePair> compares;
auto uses_explicit_or_semantics = false;
bool is_negated = false;
if (match(TokenType::Circumflex)) {
// Negated charclass
consume();
compares.empend(CompareTypeAndValuePair { CharacterCompareType::Inverse, 0 });
uses_explicit_or_semantics = true;
is_negated = true;
}
auto previous_negated_state = m_parser_state.in_negated_character_class;
m_parser_state.in_negated_character_class = is_negated;
ArmedScopeGuard restore_negated_state { [&] {
m_parser_state.in_negated_character_class = previous_negated_state;
} };
// ClassContents :: [empty]
if (match(TokenType::RightBracket)) {
consume();
@ -1858,12 +1866,18 @@ bool ECMA262Parser::parse_character_class(ByteCode& stack, size_t& match_length_
}
// ClassContents :: [~UnicodeSetsMode] NonemptyClassRanges[?UnicodeMode]
if (!flags.unicode_sets && !parse_nonempty_class_ranges(compares, flags))
if (!flags.unicode_sets && !parse_nonempty_class_ranges(compares, flags)) {
restore_negated_state.disarm();
return false;
}
// ClassContents :: [+UnicodeSetsMode] ClassSetExpression
if (flags.unicode_sets && !parse_class_set_expression(compares))
if (flags.unicode_sets && !parse_class_set_expression(compares)) {
restore_negated_state.disarm();
return false;
}
restore_negated_state.disarm();
if (uses_explicit_or_semantics && compares.size() > 2) {
compares.insert(1, CompareTypeAndValuePair { CharacterCompareType::Or, 0 });
@ -2433,11 +2447,7 @@ bool ECMA262Parser::parse_class_set_operand(Vector<regex::CompareTypeAndValuePai
strings.append(MUST(current_string.to_string()));
consume(TokenType::RightCurly, Error::MismatchingBrace);
bool is_negated = any_of(compares, [](auto const& compare) {
return compare.type == CharacterCompareType::Inverse;
});
if (is_negated && any_of(strings, has_multiple_code_points)) {
if (m_parser_state.in_negated_character_class && any_of(strings, has_multiple_code_points)) {
set_error(Error::NegatedCharacterClassStrings);
return false;
}
@ -2573,11 +2583,7 @@ bool ECMA262Parser::parse_nested_class(Vector<regex::CompareTypeAndValuePair>& c
auto strings = Unicode::get_property_strings(property);
bool is_negated = any_of(compares, [](auto const& compare) {
return compare.type == CharacterCompareType::Inverse;
});
if (is_negated && any_of(strings, has_multiple_code_points)) {
if (m_parser_state.in_negated_character_class && any_of(strings, has_multiple_code_points)) {
set_error(Error::NegatedCharacterClassStrings);
return;
}

View file

@ -119,6 +119,7 @@ protected:
size_t named_capture_groups_count { 0 };
size_t match_length_minimum { 0 };
size_t repetition_mark_count { 0 };
bool in_negated_character_class { false };
AllOptions regex_options;
HashMap<size_t, size_t> capture_group_minimum_lengths;
OrderedHashMap<FlyString, Vector<NamedCaptureGroup>> named_capture_groups;

View file

@ -872,6 +872,9 @@ TEST_CASE(ECMA262_unicode_sets_parser_error)
constexpr _test tests[] {
{ "[[]"sv, regex::Error::InvalidPattern },
{ "[[x[]]]"sv, regex::Error::NoError }, // #23691, should not crash on empty charclass within AndOr.
{ "[[^\\u0430-\\u044f][\\p{RGI_Emoji}]]"sv, regex::Error::NoError },
{ "[^[[\\p{RGI_Emoji}]--[A-Z]]]"sv, regex::Error::NegatedCharacterClassStrings },
{ "[^[^\\p{RGI_Emoji}]]"sv, regex::Error::NegatedCharacterClassStrings },
};
for (auto test : tests) {