Chrome 75 regexp, 'S' matches strange unicode range

Viewed 3784

We have a strange bug on the latest version of Chrome (75) that replace S to S

console.log(
  'AZERTYUIOPQSDFGHJKLMWXCVBN'.replace(/[\u00A0-\u9999<>&]/gim, char => `&#${char.charCodeAt(0)};`)
)  

//AZERTYUIOPQ&#83;DFGHJKLMWXCVBN

Do someone have any idea if the code is the problem or Chrome is the problem?

4 Answers

Fixed in 75.0.3770.142.


You have found an interesting bug:

These two tests are true for some reason that hinges on the unrelated character range:

> /[\u0178-\u017F]/i.test('s')
true
> /[\u0178-\u017F]/i.test('S')
true

Introduced by https://chromium-review.googlesource.com/c/v8/v8/+/1478710 (April).

The fix in https://chromium-review.googlesource.com/c/v8/v8/+/1648098 seems related, but Canary 77.0.3818.0 with v8 7.7.27 still exhibits this behavior. This is a separate bug: https://crbug.com/971636

The bug that introduced the issue (https://bugs.chromium.org/p/v8/issues/detail?id=8348) discusses how ECMAScript treats i and u differently:

  • i alone calls toUpperCase, which uses case mapping
  • iu invokes Unicode case folding

These are slightly different (this bug notwithstanding).

I also found what seems to be a different bug:

Here's a small test case, although the fix in v8 refers to Turkish case folding:

> text='ſ';
"ſ"
> new RegExp(text, 'i').test(text.toUpperCase())
true
> new RegExp(text, 'i').test('S')
false

It was introduced in the same revision, but it isn't quite the same bug — it's specific to the ſ character, whose uppercase version lies in the ASCII range and therefore triggers a different code path in V8's regexp compiler. Fixed separately at https://chromium-review.googlesource.com/c/v8/v8/+/1827683

Changing to:

'S'.replace(/[\u00A0-\u017E\u0180-\u9999<>&]/gim, char => `&#${char.charCodeAt(0)};`)

will avoid the Unicode character 'LATIN SMALL LETTER LONG S' which is \u017F. This is only a workaround.

The other end of the triggering range seems to be \u0073 which is 'LATIN SMALL LETTER S'.

The flags i and m aren't even used in that interval & you need to use the flag u which treats pattern as a sequence of Unicode code points.

console.log(
  'AZERTYUIOPQSDFGHJKLMWXCVBN'.replace(/[\u00A0-\u9999<>&]/gu, char => `&#${char.charCodeAt(0)};`)
) 

I saw the same issue in Chrome 75, but not Chrome 74. The issue was fixed by using the following regular expression:

'AZERTYUIOPQSDFGHJKLMWXCVBN'.replace(/[\u00A0-\u9999<>&]/g, char => `&#${char.charCodeAt(0)};`)

As mentioned in some of the comments, I don't believe you need i,u, or m modifiers.

Related