LibJS: Prevent escaped surrogates from combining in Unicode regexes
Escaped surrogate sequences should not combine with adjacent literal
surrogates in Unicode mode.
We now use `\u{XXXX}` braces instead of `\uXXXX` when escaping code
units in Unicode mode, so LibRegex treats each as a standalone code
point. Also prevent GenericLexer from combining `\uXXXX` and `\u{XXXX}`.
This commit is contained in:
parent
6c411563d3
commit
d95baf67f6
3 changed files with 33 additions and 2 deletions
|
|
@ -452,6 +452,11 @@ protected:
|
|||
if (!combine_surrogate_pairs || !consume_specific("\\u"sv))
|
||||
return *high_surrogate;
|
||||
|
||||
if (next_is('{')) {
|
||||
retreat(2);
|
||||
return *high_surrogate;
|
||||
}
|
||||
|
||||
auto low_surrogate = decode_one_surrogate();
|
||||
if (!low_surrogate.has_value())
|
||||
return UnicodeEscapeError::MalformedUnicodeEscape;
|
||||
|
|
|
|||
|
|
@ -6,6 +6,7 @@
|
|||
*/
|
||||
|
||||
#include <AK/Function.h>
|
||||
#include <AK/UnicodeUtils.h>
|
||||
#include <LibJS/Runtime/AbstractOperations.h>
|
||||
#include <LibJS/Runtime/GlobalObject.h>
|
||||
#include <LibJS/Runtime/PrimitiveString.h>
|
||||
|
|
@ -97,7 +98,6 @@ ErrorOr<String, ParseRegexPatternError> parse_regex_pattern(Utf16View const& pat
|
|||
|
||||
StringBuilder builder;
|
||||
|
||||
// FIXME: We need to escape multi-byte code units for LibRegex to parse since the lexer there doesn't handle unicode.
|
||||
auto previous_code_unit_was_backslash = false;
|
||||
for (size_t i = 0; i < pattern.length_in_code_units(); ++i) {
|
||||
u16 code_unit = pattern.code_unit_at(i);
|
||||
|
|
@ -109,7 +109,22 @@ ErrorOr<String, ParseRegexPatternError> parse_regex_pattern(Utf16View const& pat
|
|||
// As such, we're going to remove the (invalid) backslash and pretend it never existed.
|
||||
if (!previous_code_unit_was_backslash)
|
||||
builder.append('\\');
|
||||
builder.appendff("u{:04x}", code_unit);
|
||||
|
||||
if ((unicode || unicode_sets) && AK::UnicodeUtils::is_utf16_high_surrogate(code_unit) && i + 1 < pattern.length_in_code_units()) {
|
||||
u16 next_code_unit = pattern.code_unit_at(i + 1);
|
||||
if (AK::UnicodeUtils::is_utf16_low_surrogate(next_code_unit)) {
|
||||
u32 combined = AK::UnicodeUtils::decode_utf16_surrogate_pair(code_unit, next_code_unit);
|
||||
builder.appendff("u{{{:x}}}", combined);
|
||||
++i;
|
||||
previous_code_unit_was_backslash = false;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (unicode || unicode_sets)
|
||||
builder.appendff("u{{{:04x}}}", code_unit);
|
||||
else
|
||||
builder.appendff("u{:04x}", code_unit);
|
||||
} else {
|
||||
builder.append_code_point(code_unit);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -555,3 +555,14 @@ test("Unicode case-insensitive matching", () => {
|
|||
testMatch(/[\u03A9]/i, "\u2126", null);
|
||||
testMatch(/[\u03A9]/iv, "\u2126", ["\u2126"]);
|
||||
});
|
||||
|
||||
test("surrogate pairs", () => {
|
||||
expect(eval(`/[\uD83D\uDC38]/u`).exec("\u{1F438}")?.[0]).toBe("\u{1F438}");
|
||||
expect(eval(`/[\uD83D\uDC38]/`).exec("\u{1F438}")?.[0]).toBe("\uD83D");
|
||||
expect(eval(`/[\\uD83D\uDC38]/u`).exec("\u{1F438}")).toBeNull();
|
||||
expect(eval(`/[\\u{D83D}\uDC38]/u`).exec("\u{1F438}")).toBeNull();
|
||||
expect(eval(`/[\uD83D\\uDC38]/u`).exec("\u{1F438}")).toBeNull();
|
||||
expect(eval(`/[\uD83D\\u{DC38}]/u`).exec("\u{1F438}")).toBeNull();
|
||||
expect(eval(`/[\\uD83D\uDC38]/`).exec("\u{1F438}")?.[0]).toBe("\uD83D");
|
||||
expect(eval(`/[\uD83D\\uDC38]/`).exec("\u{1F438}")?.[0]).toBe("\uD83D");
|
||||
});
|
||||
|
|
|
|||
Loading…
Reference in a new issue