Project

General

Profile

Bug #16158

"st" Character Sequence In Regex Look-Behind Causes Illegal Pattern Error When Combined With POSIX Bracket Expressions And Case Insensitivity Flag

Added by michaeltomko (Michael Tomko) about 1 month ago. Updated 28 days ago.

Status:
Open
Priority:
Normal
Assignee:
-
Target version:
-
ruby -v:
ruby 2.7.0dev (2019-09-11 master 146677a1e7) [x86_64-openbsd6.5]
[ruby-core:94863]

Description

This is my first Ruby bug submission. Please let me know if there is anything else that I can provide that would be helpful. Thanks for your time!

I've tried just about as many combinations as I can think of and I have been able to narrow down the issue to the following components being present in a regular expression.

  • The character sequence "st" either preceded by any characters OR being a part of a top-level alternation inside of a look-behind. The issue occurs with both positive and negative look-behinds. ex: (?<!Costa) or (?<!Bob|Sally|Stan) or (?<= st)
  • Case insensitivity either being set globally or inside of the regex with (?i) preceding the look-behind.
  • Any curly-style POSIX bracket expression included anywhere in the regex. ex: \p{Space} or \p{L}

Here are some examples of the error. I have tested this on 2.5.0 locally and on 2.5.3 with Rubular.

2.5.0 :044 > pat = /(?<!a st)\p{Space}/i
Traceback (most recent call last):
SyntaxError ((irb):44: invalid pattern in look-behind: /(?<!a st)\p{Space}/i)

2.5.0 :047 > pat = /(?i)(?<!a st)\p{Space}/
Traceback (most recent call last):
SyntaxError ((irb):47: invalid pattern in look-behind: /(?i)(?<!a st)\p{Space}/)

2.5.0 :016 > pat = /(?<!Costa)Mesa(\p{Space}|\p{Punct})+(AZ|Arizona)/i
Traceback (most recent call last):
SyntaxError ((irb):16: invalid pattern in look-behind: /(?<=Costa)Mesa(\p{Space}|\p{Punct})+(AZ|Arizona)/i)

My expectation would be that this regular expression would compile as written, as it does in JRuby and in MacOS regex testing apps like Patterns or Reggy.

It does compile as expected if the case insensitivity flag is removed or instantiated after the look-behind, if the "st" character sequence is first in the look-behind and not apart of an alternation, or if different types of operators are substituted for the POSIX bracket expressions.

2.5.0 :007 > pat = /((?<!Cosa)Mesa|Arlington(?=([:space:]|[:punct:])+(AZ|Arizona)))/
 => /((?<!Cosa)Mesa|Arlington(?=(\p{Space}|\p{Punct})+(AZ|Arizona)))/

2.5.0 :008 > pat = /((?<!Cosa)Mesa|Arlington(?=([:space:]|[:punct:])+(AZ|Arizona)))/i
 => /((?<!Cosa)Mesa|Arlington(?=([:space:]|[:punct:])+(AZ|Arizona)))/i

2.5.0 :009 > pat = /((?<!Cosa)Mesa|Arlington(?=([:space:]|[:punct:])+(AZ|Arizona)))/i
 => /((?<!Cosa)Mesa|Arlington(?=(\s|\W)+(AZ|Arizona)))/i

2.5.0 :056 > pat = /(?<!a st)(?i)(?<!juice)\p{Space}/
 => /(?<!a st)(?i)(?<!juice)\p{Space}/

2.5.0 :058 > pat = /(?<!a st)(?i)(?<!stark)\p{Space}/
 => /(?<!a st)(?i)(?<!stark)\p{Space}/

History

Updated by michaeltomko (Michael Tomko) about 1 month ago

Sorry, bad paste on the OP with the success examples.

2.5.0 :007 > pat = /((?<!Costa)Mesa|Arlington(?=(\p{Space}|\p{Punct})+(AZ|Arizona)))/
 => /((?<!Costa)Mesa|Arlington(?=(\p{Space}|\p{Punct})+(AZ|Arizona)))/

2.5.0 :008 > pat = /((?<!Costa)Mesa|Arlington(?=([:space:]|[:punct:])+(AZ|Arizona)))/i
 => /((?<!Costa)Mesa|Arlington(?=([:space:]|[:punct:])+(AZ|Arizona)))/i

2.5.0 :009 > pat = /((?<!Costa)Mesa|Arlington(?=(\s|\W)+(AZ|Arizona)))/i
 => /((?<!Costa)Mesa|Arlington(?=(\s|\W)+(AZ|Arizona)))/i

2.5.0 :056 > pat = /(?<!a st)(?i)(?<!juice)\p{Space}/
 => /(?<!a st)(?i)(?<!juice)\p{Space}/

2.5.0 :058 > pat = /(?<!a st)(?i)(?<!stark)\p{Space}/
 => /(?<!a st)(?i)(?<!stark)\p{Space}/

Updated by jeremyevans0 (Jeremy Evans) about 1 month ago

  • Status changed from Open to Feedback

I tried on Ruby 2.5.6 and was not able to reproduce:

$ irb25
irb(main):001:0> pat = /(?<!a st)\p{Space}/i
=> /(?<!a st)\p{Space}/i
irb(main):002:0> pat = /(?i)(?<!a st)\p{Space}/
=> /(?i)(?<!a st)\p{Space}/
irb(main):003:0> pat = /(?<!Costa)Mesa(\p{Space}|\p{Punct})+(AZ|Arizona)/i
=> /(?<!Costa)Mesa(\p{Space}|\p{Punct})+(AZ|Arizona)/i
irb(main):004:0> RUBY_VERSION
=> "2.5.6"

Can you please try 2.5.6 and see if the problem has been fixed?

Updated by Dan0042 (Daniel DeLorme) about 1 month ago

I can reproduce the bug in all versions of ruby.

$ 2.5 ruby -ve 'pat = /(?<!a st)\p{Space}/i'
ruby 2.5.6p167 (2019-05-30 revision 67709) [x86_64-linux]
-e:1: invalid pattern in look-behind: /(?<!a st)\p{Space}/i

$ 2.6 ruby -ve 'pat = /(?<!a st)\p{Space}/i'
ruby 2.6.3p69 (2019-07-28 revision 67716) [x86_64-linux]
-e:1: invalid pattern in look-behind: /(?<!a st)\p{Space}/i

$ 2.7 ruby -ve 'pat = /(?<!a st)\p{Space}/i'
ruby 2.7.0dev (2019-09-02T16:53:49Z master a848b62819) [x86_64-linux]
-e:1: invalid pattern in look-behind: /(?<!a st)\p{Space}/i

Updated by jeremyevans0 (Jeremy Evans) about 1 month ago

  • ruby -v changed from 2.5.0, 2.5.3 to ruby 2.7.0dev (2019-09-11 master 146677a1e7) [x86_64-openbsd6.5]
  • Status changed from Feedback to Open

This appears to be an issue if the default encoding is UTF-8:

$ ruby -ve 'pat = /(?<!a st)\p{Space}/i'
ruby 2.6.4p104 (2019-08-28 revision 67798) [x86_64-openbsd]
$ LC_CTYPE=en_US.UTF-8 ruby -ve 'pat = /(?<!a st)\p{Space}/i'    
ruby 2.6.4p104 (2019-08-28 revision 67798) [x86_64-openbsd]
-e:1: invalid pattern in look-behind: /(?<!a st)\p{Space}/i

Updated by michaeltomko (Michael Tomko) about 1 month ago

Thank you both.

I can confirm the encoding being a factor. It's an issue even if it is not the default.

2.5.6 :013 > str = "(?<!a st)\\p{Space}".force_encoding("ISO-8859-5")
 => "(?<!a st)\\p{Space}"
2.5.6 :014 > Regexp.new(str,"i")
 => /(?<!a st)\p{Space}/i
2.5.6 :015 > str = "(?<!a st)\\p{Space}".force_encoding("UTF-8")
 => "(?<!a st)\\p{Space}"
2.5.6 :016 > Regexp.new(str,"i")
Traceback (most recent call last):
        3: from (irb):16
        2: from (irb):16:in `new'
        1: from (irb):16:in `initialize'
RegexpError (invalid pattern in look-behind: /(?<!a st)\p{Space}/i)

Updated by duerst (Martin Dürst) 28 days ago

I've had a hunch, and have now been able to confirm this hunch:

The problem must be related to the fact that there is a 'st' ligature (U+FB06) in Unicode. The problem occurs for all the other Latin ligatures just before U+FB06, i.e. for 'ff', 'fi', 'fl', 'ffi' 'ffl', long s with t, and st. It also occurs for the components of the Armenian ligatures just following, e.g.

$ ruby -ve 'pat = /(?<!a \u0574\u0576)\p{Space}/i'
ruby 2.7.0dev (2019-07-06T03:43:38Z trunk f296c260ef) [x86_64-cygwin]
-e:1: invalid pattern in look-behind: /(?<!a \u0574\u0576)\p{Space}/i

It doesn't occur for Hebrew ligatures:

$ ruby -ve 'pat = /(?<!a \u05D9\u05B4)\p{Space}/i'
ruby 2.7.0dev (2019-07-06T03:43:38Z trunk f296c260ef) [x86_64-cygwin]

My guess is that this is because Latin and Armenian have case conversion, but Hebrew doesn't. This would match with the fact that the error is only produced when matching is case-insensitive.

Updated by duerst (Martin Dürst) 28 days ago

Some more information: The onigmo documentation says (https://github.com/k-takata/Onigmo/blob/master/doc/RE#L270):

                     Subexp of look-behind must be fixed-width.
                     But top-level alternatives can be of various lengths.
                     ex. (?<=a|bc) is OK. (?<=aaa(?:b|cd)) is not allowed.

Now what onigmo does internally is apparently that it considers the st ligature as case equivalent to upper-case ST, which is again case equivalent to lowercase st. You can see that as follows:

$ ruby -ve 'puts(/\uFB06/i =~ "most")'
ruby 2.7.0dev (2019-07-06T03:43:38Z trunk f296c260ef) [x86_64-cygwin]
2

The st ligature is a single character, so its length is 1, but the length of ST and st is 2. So with the //i option, st seems to no longer be fixed width, and that's why onigmo refuses to deal with this and produces an error. So in some way, this is as per spec, although it's surprising and annoying.

Also available in: Atom PDF