diff options
Diffstat (limited to 'test/ruby/test_regexp.rb')
| -rw-r--r-- | test/ruby/test_regexp.rb | 2062 |
1 files changed, 1988 insertions, 74 deletions
diff --git a/test/ruby/test_regexp.rb b/test/ruby/test_regexp.rb index 93ffb17ec4..805c57b472 100644 --- a/test/ruby/test_regexp.rb +++ b/test/ruby/test_regexp.rb @@ -1,15 +1,27 @@ +# coding: US-ASCII +# frozen_string_literal: false require 'test/unit' class TestRegexp < Test::Unit::TestCase def setup @verbose = $VERBOSE - $VERBOSE = nil end def teardown $VERBOSE = @verbose end + def test_has_NOENCODING + assert Regexp::NOENCODING + re = //n + assert_equal Regexp::NOENCODING, re.options + end + + def test_ruby_dev_999 + assert_match(/(?<=a).*b/, "aab") + assert_match(/(?<=\u3042).*b/, "\u3042ab") + end + def test_ruby_core_27247 assert_match(/(a){2}z/, "aaz") end @@ -28,26 +40,422 @@ class TestRegexp < Test::Unit::TestCase assert_equal("a".gsub(/a\Z/, ""), "") end - def test_yoshidam_net_20041111_1 - s = "[\xC2\xA0-\xC3\xBE]" - assert_match(Regexp.new(s, nil, "u"), "\xC3\xBE") - end - def test_ruby_dev_31309 assert_equal('Ruby', 'Ruby'.sub(/[^a-z]/i, '-')) end + def test_premature_end_char_property + ["\\p{", + "\\p{".dup.force_encoding("UTF-8"), + "\\p{".dup.force_encoding("US-ASCII") + ].each do |string| + assert_raise(RegexpError) do + Regexp.new(string) + end + end + end + def test_assert_normal_exit # moved from knownbug. It caused core. Regexp.union("a", "a") end def test_to_s - assert_equal '(?-mix:\000)', Regexp.new("\0").to_s + assert_equal '(?-mix:\x00)', Regexp.new("\0").to_s + + str = "abcd\u3042" + [:UTF_16BE, :UTF_16LE, :UTF_32BE, :UTF_32LE].each do |es| + enc = Encoding.const_get(es) + rs = Regexp.new(str.encode(enc)).to_s + assert_equal("(?-mix:abcd\u3042)".encode(enc), rs) + assert_equal(enc, rs.encoding) + end + end + + def test_to_s_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + str = "abcd\u3042" + [:UTF_16BE, :UTF_16LE, :UTF_32BE, :UTF_32LE].each do |es| + enc = Encoding.const_get(es) + rs = Regexp.new(str.encode(enc)).to_s + assert_equal("(?-mix:abcd\u3042)".encode(enc), rs) + assert_equal(enc, rs.encoding) + end + end + end + + def test_to_s_extended_subexp + re = /#\g#{"\n"}/x + re = /#{re}/ + assert_warn('', '[ruby-core:82328] [Bug #13798]') {re.to_s} + end + + def test_extended_comment_invalid_escape_bug_18294 + assert_separately([], <<-RUBY) + re = / C:\\\\[a-z]{5} # e.g. C:\\users /x + assert_match(re, 'C:\\users') + assert_not_match(re, 'C:\\user') + + re = / + foo # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = / + f[#o]o # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = / + f[[:alnum:]#]o # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = / + f(?# \\M-ca)oo # \\M-ca + bar + /x + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = /f(?# \\M-ca)oobar/ + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = /[-(?# fca)]oobar/ + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + + re = /f(?# ca\0\\M-ca)oobar/ + assert_match(re, 'foobar') + assert_not_match(re, 'foobaz') + RUBY + + assert_raise(SyntaxError) {eval "/\\users/x"} + assert_raise(SyntaxError) {eval "/[\\users]/x"} + assert_raise(SyntaxError) {eval "/(?<\\users)/x"} + assert_raise(SyntaxError) {eval "/# \\users/"} + end + + def test_nonextended_section_of_extended_regexp_bug_19379 + assert_separately([], <<-'RUBY') + re = /(?-x:#)/x + assert_match(re, '#') + assert_not_match(re, '-') + + re = /(?xi:# + y)/ + assert_match(re, 'Y') + assert_not_match(re, '-') + + re = /(?mix:# + y)/ + assert_match(re, 'Y') + assert_not_match(re, '-') + + re = /(?x-im:# + y)/i + assert_match(re, 'y') + assert_not_match(re, 'Y') + + re = /(?-imx:(?xim:# + y))/x + assert_match(re, 'y') + assert_not_match(re, '-') + + re = /(?x)# + y/ + assert_match(re, 'y') + assert_not_match(re, 'Y') + + re = /(?mx-i)# + y/i + assert_match(re, 'y') + assert_not_match(re, 'Y') + + re = /(?-imx:(?xim:# + (?-x)y#))/x + assert_match(re, 'Y#') + assert_not_match(re, '-#') + + re = /(?imx:# + (?-xim:#(?im)#(?x)# + )# + (?x)# + y)/ + assert_match(re, '###Y') + assert_not_match(re, '###-') + + re = %r{#c-\w+/comment/[\w-]+} + re = %r{https?://[^/]+#{re}}x + assert_match(re, 'http://foo#c-x/comment/bar') + assert_not_match(re, 'http://foo#cx/comment/bar') + RUBY + end + + def test_utf8_comment_in_usascii_extended_regexp_bug_19455 + assert_separately([], <<-RUBY) + assert_equal(Encoding::UTF_8, /(?#\u1000)/x.encoding) + assert_equal(Encoding::UTF_8, /#\u1000/x.encoding) + RUBY + end + + def test_union + assert_equal :ok, begin + Regexp.union( + "a", + Regexp.new("\xc2\xa1".force_encoding("euc-jp")), + Regexp.new("\xc2\xa1".force_encoding("utf-8"))) + :ng + rescue ArgumentError + :ok + end + re = Regexp.union(/\//, "") + re2 = eval(re.inspect) + assert_equal(re.to_s, re2.to_s) + assert_equal(re.source, re2.source) + assert_equal(re, re2) + end + + def test_word_boundary + assert_match(/\u3042\b /, "\u3042 ") + assert_not_match(/\u3042\ba/, "\u3042a") + end + + def test_named_capture + m = /&(?<foo>.*?);/.match("aaa & yyy") + assert_equal("amp", m["foo"]) + assert_equal("amp", m[:foo]) + assert_equal(5, m.begin(:foo)) + assert_equal(8, m.end(:foo)) + assert_equal([5,8], m.offset(:foo)) + + assert_equal("aaa [amp] yyy", + "aaa & yyy".sub(/&(?<foo>.*?);/, '[\k<foo>]')) + + assert_equal('#<MatchData "& y" foo:"amp">', + /&(?<foo>.*?); (y)/.match("aaa & yyy").inspect) + assert_equal('#<MatchData "& y" 1:"amp" 2:"y">', + /&(.*?); (y)/.match("aaa & yyy").inspect) + assert_equal('#<MatchData "& y" foo:"amp" bar:"y">', + /&(?<foo>.*?); (?<bar>y)/.match("aaa & yyy").inspect) + assert_equal('#<MatchData "& y" foo:"amp" foo:"y">', + /&(?<foo>.*?); (?<foo>y)/.match("aaa & yyy").inspect) + + /(?<_id>[A-Za-z_]+)/ =~ "!abc" + assert_not_nil(Regexp.last_match) + assert_equal("abc", Regexp.last_match(1)) + assert_equal("abc", Regexp.last_match(:_id)) + + /a/ =~ "b" # doesn't match. + assert_equal(nil, Regexp.last_match) + assert_equal(nil, Regexp.last_match(1)) + assert_equal(nil, Regexp.last_match(:foo)) + + bug11825_name = "\u{5b9d 77f3}" + bug11825_str = "\u{30eb 30d3 30fc}" + bug11825_re = /(?<#{bug11825_name}>)#{bug11825_str}/ + + assert_equal(["foo", "bar"], /(?<foo>.)(?<bar>.)/.names) + assert_equal(["foo"], /(?<foo>.)(?<foo>.)/.names) + assert_equal([], /(.)(.)/.names) + assert_equal([bug11825_name], bug11825_re.names) + + assert_equal(["foo", "bar"], /(?<foo>.)(?<bar>.)/.match("ab").names) + assert_equal(["foo"], /(?<foo>.)(?<foo>.)/.match("ab").names) + assert_equal([], /(.)(.)/.match("ab").names) + assert_equal([bug11825_name], bug11825_re.match(bug11825_str).names) + + assert_equal({"foo"=>[1], "bar"=>[2]}, + /(?<foo>.)(?<bar>.)/.named_captures) + assert_equal({"foo"=>[1, 2]}, + /(?<foo>.)(?<foo>.)/.named_captures) + assert_equal({}, /(.)(.)/.named_captures) + + assert_equal("a[b]c", "abc".sub(/(?<x>[bc])/, "[\\k<x>]")) + + assert_equal("o", "foo"[/(?<bar>o)/, "bar"]) + assert_equal("o", "foo"[/(?<@bar>o)/, "@bar"]) + assert_equal("o", "foo"[/(?<@bar>.)\g<@bar>\k<@bar>/, "@bar"]) + + s = "foo" + s[/(?<bar>o)/, "bar"] = "baz" + assert_equal("fbazo", s) + + /.*/ =~ "abc" + "a".sub("a", "") + assert_raise(IndexError) {Regexp.last_match(:_id)} + end + + def test_named_capture_with_nul + bug9902 = '[ruby-dev:48275] [Bug #9902]' + + m = /(?<a>.*)/.match("foo") + assert_raise(IndexError, bug9902) {m["a\0foo"]} + assert_raise(IndexError, bug9902) {m["a\0foo".to_sym]} + + m = Regexp.new("(?<foo\0bar>.*)").match("xxx") + assert_raise(IndexError, bug9902) {m["foo"]} + assert_raise(IndexError, bug9902) {m["foo".to_sym]} + assert_nothing_raised(IndexError, bug9902) { + assert_equal("xxx", m["foo\0bar"], bug9902) + assert_equal("xxx", m["foo\0bar".to_sym], bug9902) + } + end + + def test_named_capture_nonascii + bug9903 = '[ruby-dev:48278] [Bug #9903]' + + key = "\xb1\xb2".force_encoding(Encoding::EUC_JP) + m = /(?<#{key}>.*)/.match("xxx") + assert_equal("xxx", m[key]) + assert_raise(IndexError, bug9903) {m[key.dup.force_encoding(Encoding::Shift_JIS)]} + end + + def test_match_data_named_captures + assert_equal({'a' => '1', 'b' => '2', 'c' => nil}, /^(?<a>.)(?<b>.)(?<c>.)?/.match('12').named_captures) + assert_equal({'a' => '1', 'b' => '2', 'c' => '3'}, /^(?<a>.)(?<b>.)(?<c>.)?/.match('123').named_captures) + assert_equal({'a' => '1', 'b' => '2', 'c' => ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures) + + assert_equal({a: '1', b: '2', c: ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures(symbolize_names: true)) + assert_equal({'a' => '1', 'b' => '2', 'c' => ''}, /^(?<a>.)(?<b>.)(?<c>.?)/.match('12').named_captures(symbolize_names: false)) + + assert_equal({'a' => 'x'}, /(?<a>x)|(?<a>y)/.match('x').named_captures) + assert_equal({'a' => 'y'}, /(?<a>x)|(?<a>y)/.match('y').named_captures) + + assert_equal({'a' => '1', 'b' => '2'}, /^(.)(?<a>.)(?<b>.)/.match('012').named_captures) + assert_equal({'a' => '2'}, /^(?<a>.)(?<a>.)/.match('12').named_captures) + + assert_equal({}, /^(.)/.match('123').named_captures) + end + + def test_assign_named_capture + assert_equal("a", eval('/(?<foo>.)/ =~ "a"; foo')) + assert_equal(nil, eval('/(?<@foo>.)/ =~ "a"; defined?(@foo)')) + assert_equal("a", eval('foo = 1; /(?<foo>.)/ =~ "a"; foo')) + assert_equal("a", eval('1.times {|foo| /(?<foo>.)/ =~ "a"; break foo }')) + assert_nothing_raised { eval('/(?<Foo>.)/ =~ "a"') } + assert_nil(eval('/(?<Foo>.)/ =~ "a"; defined? Foo')) + end + + def test_assign_named_capture_to_reserved_word + /(?<nil>.)/ =~ "a" + assert_not_include(local_variables, :nil, "[ruby-dev:32675]") + + def (obj = Object.new).test(s, nil: :ng) + /(?<nil>.)/ =~ s + binding.local_variable_get(:nil) + end + assert_equal("b", obj.test("b")) + + tap do |nil: :ng| + /(?<nil>.)/ =~ "c" + assert_equal("c", binding.local_variable_get(:nil)) + end + end + + def test_assign_named_capture_to_const + %W[C \u{1d402}].each do |name| + assert_equal(:ok, Class.new.class_eval("#{name} = :ok; /(?<#{name}>.*)/ =~ 'ng'; #{name}")) + end + end + + def test_assign_named_capture_trace + bug = '[ruby-core:79940] [Bug #13287]' + assert_normal_exit("#{<<-"begin;"}\n#{<<-"end;"}", bug) + begin; + / (?<foo>.*)/ =~ "bar" && + true + end; + end + + def test_match_regexp + r = /./ + m = r.match("a") + assert_equal(r, m.regexp) + re = /foo/ + assert_equal(re, re.match("foo").regexp) + end + + def test_match_lambda_multithread + bug17507 = "[ruby-core:101901]" + str = "a-x-foo-bar-baz-z-b" + + worker = lambda do + m = /foo-([A-Za-z0-9_\.]+)-baz/.match(str) + assert_equal("bar", m[1], bug17507) + + # These two lines are needed to trigger the bug + File.exist? "/tmp" + str.gsub(/foo-bar-baz/, "foo-abc-baz") + end + + def self. threaded_test(worker) + 6.times.map {Thread.new {10_000.times {worker.call}}}.each(&:join) + end + + # The bug only occurs in a method calling a block/proc/lambda + threaded_test(worker) end def test_source + bug5484 = '[ruby-core:40364]' assert_equal('', //.source) + assert_equal('\:', /\:/.source, bug5484) + assert_equal(':', %r:\::.source, bug5484) + end + + def test_source_escaped + expected, result = "$*+.?^|".each_char.map {|c| + [ + ["\\#{c}", "\\#{c}", 1], + begin + re = eval("%r#{c}\\#{c}#{c}", nil, __FILE__, __LINE__) + t = eval("/\\#{c}/", nil, __FILE__, __LINE__).source + rescue SyntaxError => e + [e, t, nil] + else + [re.source, t, re =~ "a#{c}a"] + end + ] + }.transpose + assert_equal(expected, result) + end + + def test_source_escaped_paren + bug7610 = '[ruby-core:51088] [Bug #7610]' + bug8133 = '[ruby-core:53578] [Bug #8133]' + [ + ["(", ")", bug7610], ["[", "]", bug8133], + ["{", "}", bug8133], ["<", ">", bug8133], + ].each do |lparen, rparen, bug| + s = "\\#{lparen}a\\#{rparen}" + assert_equal(/#{s}/, eval("%r#{lparen}#{s}#{rparen}"), bug) + end + end + + def test_source_unescaped + expected, result = "!\"#%&',-/:;=@_`~".each_char.map {|c| + [ + ["#{c}", "\\#{c}", 1], + begin + re = eval("%r#{c}\\#{c}#{c}", nil, __FILE__, __LINE__) + t = eval("%r{\\#{c}}", nil, __FILE__, __LINE__).source + rescue SyntaxError => e + [e, t, nil] + else + [re.source, t, re =~ "a#{c}a"] + end + ] + }.transpose + assert_equal(expected, result) end def test_inspect @@ -56,10 +464,17 @@ class TestRegexp < Test::Unit::TestCase assert_equal('/\//i', /\//i.inspect) assert_equal('/\//i', %r"#{'/'}"i.inspect) assert_equal('/\/x/i', /\/x/i.inspect) - assert_equal('/\000/i', /#{"\0"}/i.inspect) + assert_equal('/\x00/i', /#{"\0"}/i.inspect) assert_equal("/\n/i", /#{"\n"}/i.inspect) - s = [0xff].pack("C") - assert_equal('/\/'+s.dump.delete('"')+'/i', /\/#{s}/i.inspect) + s = [0xf1, 0xf2, 0xf3].pack("C*") + assert_equal('/\/\xF1\xF2\xF3/i', /\/#{s}/i.inspect) + end + + def test_inspect_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + assert_equal('/(?-mix:\\/)|/', Regexp.union(/\//, "").inspect) + end end def test_char_to_option @@ -101,10 +516,12 @@ class TestRegexp < Test::Unit::TestCase def test_match_init_copy m = /foo/.match("foo") + assert_equal(/foo/, m.dup.regexp) assert_raise(TypeError) do m.instance_eval { initialize_copy(nil) } end assert_equal([0, 3], m.offset(0)) + assert_equal(/foo/, m.dup.regexp) end def test_match_size @@ -112,6 +529,69 @@ class TestRegexp < Test::Unit::TestCase assert_equal(5, m.size) end + def test_match_offset_begin_end + m = /(?<x>b..)/.match("foobarbaz") + assert_equal([3, 6], m.offset("x")) + assert_equal(3, m.begin("x")) + assert_equal(6, m.end("x")) + assert_raise(IndexError) { m.offset("y") } + assert_raise(IndexError) { m.offset(2) } + assert_raise(IndexError) { m.begin(2) } + assert_raise(IndexError) { m.end(2) } + + m = /(?<x>q..)?/.match("foobarbaz") + assert_equal([nil, nil], m.offset("x")) + assert_equal(nil, m.begin("x")) + assert_equal(nil, m.end("x")) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal([1, 2], m.offset(1)) + assert_equal([nil, nil], m.offset(2)) + assert_equal([2, 3], m.offset(3)) + end + + def test_match_byteoffset_begin_end + m = /(?<x>b..)/.match("foobarbaz") + assert_equal([3, 6], m.byteoffset("x")) + assert_equal(3, m.begin("x")) + assert_equal(6, m.end("x")) + assert_raise(IndexError) { m.byteoffset("y") } + assert_raise(IndexError) { m.byteoffset(2) } + assert_raise(IndexError) { m.begin(2) } + assert_raise(IndexError) { m.end(2) } + assert_raise(IndexError) { m.bytebegin(2) } + assert_raise(IndexError) { m.byteend(2) } + + m = /(?<x>q..)?/.match("foobarbaz") + assert_equal([nil, nil], m.byteoffset("x")) + assert_equal(nil, m.begin("x")) + assert_equal(nil, m.end("x")) + assert_equal(nil, m.bytebegin("x")) + assert_equal(nil, m.byteend("x")) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal([3, 6], m.byteoffset(1)) + assert_equal(3, m.bytebegin(1)) + assert_equal(6, m.byteend(1)) + assert_equal([nil, nil], m.byteoffset(2)) + assert_equal(nil, m.bytebegin(2)) + assert_equal(nil, m.byteend(2)) + assert_equal([6, 9], m.byteoffset(3)) + assert_equal(6, m.bytebegin(3)) + assert_equal(9, m.byteend(3)) + end + + def test_match_to_s + m = /(?<x>b..)/.match("foobarbaz") + assert_equal("bar", m.to_s) + end + + def test_match_pre_post + m = /(?<x>b..)/.match("foobarbaz") + assert_equal("foo", m.pre_match) + assert_equal("baz", m.post_match) + end + def test_match_array m = /(...)(...)(...)(...)?/.match("foobarbaz") assert_equal(["foobarbaz", "foo", "bar", "baz", nil], m.to_a) @@ -124,14 +604,86 @@ class TestRegexp < Test::Unit::TestCase def test_match_aref m = /(...)(...)(...)(...)?/.match("foobarbaz") + assert_equal("foobarbaz", m[0]) assert_equal("foo", m[1]) + assert_equal("foo", m[-4]) + assert_nil(m[-1]) + assert_nil(m[-11]) + assert_nil(m[-11, 1]) + assert_nil(m[-11..1]) + assert_nil(m[5]) + assert_nil(m[9]) assert_equal(["foo", "bar", "baz"], m[1..3]) + assert_equal(["foo", "bar", "baz"], m[1, 3]) + assert_equal([], m[3..1]) + assert_equal([], m[3, 0]) + assert_equal(nil, m[3, -1]) + assert_equal(nil, m[9, 1]) + assert_equal(["baz"], m[3, 1]) + assert_equal(["baz", nil], m[3, 5]) assert_nil(m[5]) + assert_raise(IndexError) { m[:foo] } + assert_raise(TypeError) { m[nil] } + assert_equal(["baz", nil], m[-2, 3]) end def test_match_values_at + idx = Object.new + def idx.to_int; 2; end m = /(...)(...)(...)(...)?/.match("foobarbaz") assert_equal(["foo", "bar", "baz"], m.values_at(1, 2, 3)) + assert_equal(["foo", "bar", "baz"], m.values_at(1..3)) + assert_equal(["foo", "bar", "baz", nil, nil], m.values_at(1..5)) + assert_equal([], m.values_at(3..1)) + assert_equal([nil, nil, nil, nil, nil], m.values_at(5..9)) + assert_equal(["bar"], m.values_at(idx)) + assert_raise(RangeError){ m.values_at(-11..1) } + assert_raise(TypeError){ m.values_at(nil) } + + m = /(?<a>\d+) *(?<op>[+\-*\/]) *(?<b>\d+)/.match("1 + 2") + assert_equal(["1", "2", "+"], m.values_at(:a, 'b', :op)) + assert_equal(["+"], m.values_at(idx)) + assert_raise(TypeError){ m.values_at(nil) } + assert_raise(IndexError){ m.values_at(:foo) } + end + + def test_match_string + m = /(?<x>b..)/.match("foobarbaz") + assert_equal("foobarbaz", m.string) + end + + def test_match_matchsubstring + m = /(.)(.)(\d+)(\d)(\w)?/.match("THX1138.") + assert_equal("HX1138", m.match(0)) + assert_equal("8", m.match(4)) + assert_nil(m.match(5)) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal("\u3043", m.match(1)) + assert_nil(m.match(2)) + assert_equal("\u3044", m.match(3)) + + m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042") + assert_equal("h", m.match(:foo)) + assert_nil(m.match(:n)) + assert_equal("oge\u3042", m.match(:bar)) + end + + def test_match_match_length + m = /(.)(.)(\d+)(\d)(\w)?/.match("THX1138.") + assert_equal(6, m.match_length(0)) + assert_equal(1, m.match_length(4)) + assert_nil(m.match_length(5)) + + m = /\A\u3042(.)(.)?(.)\z/.match("\u3042\u3043\u3044") + assert_equal(1, m.match_length(1)) + assert_nil(m.match_length(2)) + assert_equal(1, m.match_length(3)) + + m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042") + assert_equal(1, m.match_length(:foo)) + assert_nil(m.match_length(:n)) + assert_equal(4, m.match_length(:bar)) end def test_match_inspect @@ -139,30 +691,150 @@ class TestRegexp < Test::Unit::TestCase assert_equal('#<MatchData "foobarbaz" 1:"foo" 2:"bar" 3:"baz" 4:nil>', m.inspect) end + def test_match_data_deconstruct + m = /foo.+/.match("foobarbaz") + assert_equal([], m.deconstruct) + + m = /(foo).+(baz)/.match("foobarbaz") + assert_equal(["foo", "baz"], m.deconstruct) + + m = /(...)(...)(...)(...)?/.match("foobarbaz") + assert_equal(["foo", "bar", "baz", nil], m.deconstruct) + end + + def test_match_data_deconstruct_keys + m = /foo.+/.match("foobarbaz") + assert_equal({}, m.deconstruct_keys([:a])) + + m = /(?<a>foo).+(?<b>baz)/.match("foobarbaz") + assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys(nil)) + assert_equal({a: "foo", b: "baz"}, m.deconstruct_keys([:a, :b])) + assert_equal({b: "baz"}, m.deconstruct_keys([:b])) + assert_equal({}, m.deconstruct_keys([:c, :a])) + assert_equal({a: "foo"}, m.deconstruct_keys([:a, :c])) + assert_equal({}, m.deconstruct_keys([:a, :b, :c])) + + assert_raise(TypeError) { + m.deconstruct_keys(0) + } + + assert_raise(TypeError) { + m.deconstruct_keys(["a", "b"]) + } + end + + def test_match_no_match_no_matchdata + EnvUtil.without_gc do + h = {} + ObjectSpace.count_objects(h) + prev_matches = h[:T_MATCH] || 0 + _md = /[A-Z]/.match('1') # no match + ObjectSpace.count_objects(h) + new_matches = h[:T_MATCH] || 0 + assert_equal prev_matches, new_matches, "Bug [#20104]" + end + end + def test_initialize assert_raise(ArgumentError) { Regexp.new } - assert_equal(/foo/, Regexp.new(/foo/, Regexp::IGNORECASE)) - re = /foo/ - assert_raise(SecurityError) do - Thread.new { $SAFE = 4; re.instance_eval { initialize(re) } }.join + assert_equal(/foo/, assert_warning(/ignored/) {Regexp.new(/foo/, Regexp::IGNORECASE)}) + assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/)}) + assert_equal(/foo/, assert_no_warning(/ignored/) {Regexp.new(/foo/, timeout: nil)}) + + arg_encoding_none = //n.options # ARG_ENCODING_NONE is implementation defined value + + assert_deprecated_warning('') do + assert_equal(Encoding.find("US-ASCII"), Regexp.new("b..", Regexp::NOENCODING).encoding) + assert_equal("bar", "foobarbaz"[Regexp.new("b..", Regexp::NOENCODING)]) + assert_equal(//, Regexp.new("")) + assert_equal(//, Regexp.new("", timeout: 1)) + assert_equal(//n, Regexp.new("", Regexp::NOENCODING)) + assert_equal(//n, Regexp.new("", Regexp::NOENCODING, timeout: 1)) + + assert_equal(arg_encoding_none, Regexp.new("", Regexp::NOENCODING).options) + + assert_nil(Regexp.new("").timeout) + assert_equal(1.0, Regexp.new("", timeout: 1.0).timeout) + assert_nil(Regexp.compile("").timeout) + assert_equal(1.0, Regexp.compile("", timeout: 1.0).timeout) end - re.taint - assert_raise(SecurityError) do - Thread.new { $SAFE = 4; re.instance_eval { initialize(re) } }.join + + assert_raise(RegexpError) { Regexp.new(")(") } + assert_raise(RegexpError) { Regexp.new('[\\40000000000') } + assert_raise(RegexpError) { Regexp.new('[\\600000000000.') } + assert_raise(RegexpError) { Regexp.new("((?<v>))\\g<0>") } + end + + def test_initialize_from_regex_memory_corruption + assert_ruby_status([], <<-'end;') + 10_000.times { Regexp.new(Regexp.new("(?<name>)")) } + end; + end + + def test_initialize_bool_warning + assert_warning(/expected true or false as ignorecase/) do + Regexp.new("foo", :i) end + end - assert_equal("bar", "foobarbaz"[Regexp.new("b..", nil, "n")]) - assert_equal(//n, Regexp.new("", nil, "n")) + def test_initialize_option + assert_equal(//i, Regexp.new("", "i")) + assert_equal(//m, Regexp.new("", "m")) + assert_equal(//x, Regexp.new("", "x")) + assert_equal(//imx, Regexp.new("", "imx")) + assert_equal(//, Regexp.new("", "")) + assert_equal(//imx, Regexp.new("", "mimix")) - assert_raise(RegexpError) { Regexp.new(")(") } + assert_raise(ArgumentError) { Regexp.new("", "e") } + assert_raise(ArgumentError) { Regexp.new("", "n") } + assert_raise(ArgumentError) { Regexp.new("", "s") } + assert_raise(ArgumentError) { Regexp.new("", "u") } + assert_raise(ArgumentError) { Regexp.new("", "o") } + assert_raise(ArgumentError) { Regexp.new("", "j") } + assert_raise(ArgumentError) { Regexp.new("", "xmen") } + end + + def test_match_control_meta_escape + assert_equal(0, /\c\xFF/ =~ "\c\xFF") + assert_equal(0, /\c\M-\xFF/ =~ "\c\M-\xFF") + assert_equal(0, /\C-\xFF/ =~ "\C-\xFF") + assert_equal(0, /\C-\M-\xFF/ =~ "\C-\M-\xFF") + assert_equal(0, /\M-\xFF/ =~ "\M-\xFF") + assert_equal(0, /\M-\C-\xFF/ =~ "\M-\C-\xFF") + assert_equal(0, /\M-\c\xFF/ =~ "\M-\c\xFF") + + assert_nil(/\c\xFE/ =~ "\c\xFF") + assert_nil(/\c\M-\xFE/ =~ "\c\M-\xFF") + assert_nil(/\C-\xFE/ =~ "\C-\xFF") + assert_nil(/\C-\M-\xFE/ =~ "\C-\M-\xFF") + assert_nil(/\M-\xFE/ =~ "\M-\xFF") + assert_nil(/\M-\C-\xFE/ =~ "\M-\C-\xFF") + assert_nil(/\M-\c\xFE/ =~ "\M-\c\xFF") end def test_unescape - assert_raise(RegexpError) { s = '\\'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\\'; /#{ s }/ } assert_equal(/\xFF/n, /#{ s="\\xFF" }/n) assert_equal(/\177/, (s = '\177'; /#{ s }/)) + assert_raise(ArgumentError) { s = '\u'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u{ ffffffff }'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u{ ffffff }'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u{ ffff X }'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u{ }'; /#{ s }/ } + assert_equal("b", "abc"[(s = '\u{0062}'; /#{ s }/)]) + assert_equal("b", "abc"[(s = '\u0062'; /#{ s }/)]) + assert_raise(ArgumentError) { s = '\u0'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u000X'; /#{ s }/ } + assert_raise(ArgumentError) { s = "\xff" + '\u3042'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\u3042' + [0xff].pack("C"); /#{ s }/ } + assert_raise(SyntaxError) { s = ''; eval(%q(/\u#{ s }/)) } + + assert_equal(/a/, eval(%q(s="\u0061";/#{s}/n))) + assert_raise(RegexpError) { s = "\u3042"; eval(%q(/#{s}/n)) } + assert_raise(RegexpError) { s = "\u0061"; eval(%q(/\u3042#{s}/n)) } + assert_raise(RegexpError) { s1=[0xff].pack("C"); s2="\u3042"; eval(%q(/#{s1}#{s2}/)); [s1, s2] } - assert_raise(RegexpError) { s = '\x'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\x'; /#{ s }/ } assert_equal("\xe1", [0x00, 0xe1, 0xff].pack("C*")[/\M-a/]) assert_equal("\xdc", [0x00, 0xdc, 0xff].pack("C*")[/\M-\\/]) @@ -175,37 +847,93 @@ class TestRegexp < Test::Unit::TestCase assert_equal("\x9b", [0x00, 0x9b, 0xff].pack("C*")[/\M-\e/]) assert_equal("\x01", [0x00, 0x01, 0xff].pack("C*")[/\C-a/]) - assert_raise(RegexpError) { s = '\M'; /#{ s }/ } - #assert_raise(RegexpError) { s = '\M-\M-a'; /#{ s }/ } - assert_raise(RegexpError) { s = '\M-\\'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\M'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\M-\M-a'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\M-\\'; /#{ s }/ } + + assert_raise(ArgumentError) { s = '\C'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\c'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\C-\C-a'; /#{ s }/ } + + assert_raise(ArgumentError) { s = '\M-\z'; /#{ s }/ } + assert_raise(ArgumentError) { s = '\M-\777'; /#{ s }/ } - assert_raise(RegexpError) { s = '\C'; /#{ s }/ } - assert_raise(RegexpError) { s = '\c'; /#{ s }/ } - #assert_raise(RegexpError) { s = '\C-\C-a'; /#{ s }/ } + assert_equal("\u3042\u3042", "\u3042\u3042"[(s = "\u3042" + %q(\xe3\x81\x82); /#{s}/)]) + assert_raise(ArgumentError) { s = "\u3042" + %q(\xe3); /#{s}/ } + assert_raise(ArgumentError) { s = "\u3042" + %q(\xe3\xe3); /#{s}/ } + assert_raise(ArgumentError) { s = '\u3042' + [0xff].pack("C"); /#{s}/ } - #assert_raise(RegexpError) { s = '\M-\z'; /#{ s }/ } - #assert_raise(RegexpError) { s = '\M-\777'; /#{ s }/ } + assert_raise(SyntaxError) { eval("/\u3042/n") } s = ".........." 5.times { s.sub!(".", "") } assert_equal(".....", s) + + assert_equal("\\\u{3042}", Regexp.new("\\\u{3042}").source) end def test_equal - assert_equal(true, /abc/ == /abc/) - assert_equal(false, /abc/ == /abc/m) - assert_equal(false, /abc/ == /abd/) + bug5484 = '[ruby-core:40364]' + assert_equal(/abc/, /abc/) + assert_not_equal(/abc/, /abc/m) + assert_not_equal(/abc/, /abd/) + assert_equal(/\/foo/, Regexp.new('/foo'), bug5484) end def test_match assert_nil(//.match(nil)) + assert_equal("abc", /.../.match(:abc)[0]) assert_raise(TypeError) { /.../.match(Object.new)[0] } + assert_equal("bc", /../.match('abc', 1)[0]) + assert_equal("bc", /../.match('abc', -2)[0]) + assert_nil(/../.match("abc", -4)) + assert_nil(/../.match("abc", 4)) + + # use eval because only one warning is shown for the same regexp literal + pat = eval('/../n') + assert_equal('\x', assert_warning(/binary regexp/) {pat.match("\u3042" + '\x', 1)}[0]) + + r = nil + /.../.match("abc") {|m| r = m[0] } + assert_equal("abc", r) $_ = "abc"; assert_equal(1, ~/bc/) $_ = "abc"; assert_nil(~/d/) $_ = nil; assert_nil(~/./) end + def test_match_under_gc_compact_stress + omit "compaction doesn't work well on s390x" if RUBY_PLATFORM =~ /s390x/ # https://github.com/ruby/ruby/pull/5077 + EnvUtil.under_gc_compact_stress do + m = /(?<foo>.)(?<n>[^aeiou])?(?<bar>.+)/.match("hoge\u3042") + assert_equal("h", m.match(:foo)) + end + end + + def test_match_p + /backref/ =~ 'backref' + # must match here, but not in a separate method, e.g., assert_send, + # to check if $~ is affected or not. + assert_equal(false, //.match?(nil)) + assert_equal(true, //.match?("")) + assert_equal(true, /.../.match?(:abc)) + assert_raise(TypeError) { /.../.match?(Object.new) } + assert_equal(true, /b/.match?('abc')) + assert_equal(true, /b/.match?('abc', 1)) + assert_equal(true, /../.match?('abc', 1)) + assert_equal(true, /../.match?('abc', -2)) + assert_equal(false, /../.match?("abc", -4)) + assert_equal(false, /../.match?("abc", 4)) + assert_equal(true, /../.match?("\u3042xx", 1)) + assert_equal(false, /../.match?("\u3042x", 1)) + assert_equal(true, /\z/.match?("")) + assert_equal(true, /\z/.match?("abc")) + assert_equal(true, /R.../.match?("Ruby")) + assert_equal(false, /R.../.match?("Ruby", 1)) + assert_equal(false, /P.../.match?("Ruby")) + assert_equal('backref', $&) + end + def test_eqq assert_equal(false, /../ === nil) end @@ -217,7 +945,23 @@ class TestRegexp < Test::Unit::TestCase assert_equal("\\n", Regexp.quote("\n")) assert_equal("\\r", Regexp.quote("\r")) assert_equal("\\f", Regexp.quote("\f")) + assert_equal("\\v", Regexp.quote("\v")) + assert_equal("\u3042\\t", Regexp.quote("\u3042\t")) assert_equal("\\t\xff", Regexp.quote("\t" + [0xff].pack("C"))) + + bug13034 = '[ruby-core:78646] [Bug #13034]' + str = "\x00".force_encoding("UTF-16BE") + assert_equal(str, Regexp.quote(str), bug13034) + end + + def test_try_convert + assert_equal(/re/, Regexp.try_convert(/re/)) + assert_nil(Regexp.try_convert("re")) + + o = Object.new + assert_nil(Regexp.try_convert(o)) + def o.to_regexp() /foo/ end + assert_equal(/foo/, Regexp.try_convert(o)) end def test_union2 @@ -225,11 +969,13 @@ class TestRegexp < Test::Unit::TestCase assert_equal(/foo/, Regexp.union(/foo/)) assert_equal(/foo/, Regexp.union([/foo/])) assert_equal(/\t/, Regexp.union("\t")) + assert_equal(/(?-mix:\u3042)|(?-mix:\u3042)/, Regexp.union(/\u3042/, /\u3042/)) + assert_equal("\u3041", "\u3041"[Regexp.union(/\u3042/, "\u3041")]) end def test_dup assert_equal(//, //.dup) - assert_raise(TypeError) { //.instance_eval { initialize_copy(nil) } } + assert_raise(FrozenError) { //.dup.instance_eval { initialize_copy(/a/) } } end def test_regsub @@ -238,6 +984,7 @@ class TestRegexp < Test::Unit::TestCase assert_equal(s, "X".sub!(/./, s)) assert_equal('\\' + s, "X".sub!(/./, '\\' + s)) assert_equal('\k', "foo".sub!(/.../, '\k')) + assert_raise(RuntimeError) { "foo".sub!(/(?<x>o)/, '\k<x') } assert_equal('foo[bar]baz', "foobarbaz".sub!(/(b..)/, '[\0]')) assert_equal('foo[foo]baz', "foobarbaz".sub!(/(b..)/, '[\`]')) assert_equal('foo[baz]baz', "foobarbaz".sub!(/(b..)/, '[\\\']')) @@ -246,11 +993,40 @@ class TestRegexp < Test::Unit::TestCase assert_equal('foo[\z]baz', "foobarbaz".sub!(/(b..)/, '[\z]')) end - def test_KCODE - assert_nothing_raised { $KCODE = 'n' } - assert_equal('NONE', $KCODE) - assert_equal(false, $=) - assert_nothing_raised { $= = nil } + def test_regsub_K + bug8856 = '[ruby-dev:47694] [Bug #8856]' + result = "foobarbazquux/foobarbazquux".gsub(/foo\Kbar/, "") + assert_equal('foobazquux/foobazquux', result, bug8856) + end + + def test_regsub_no_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", rss: true) + code = proc do + "aaaaaaaaaaa".gsub(/a/, "") + end + + 1_000.times(&code) + begin; + 100_000.times(&code) + end; + end + + def test_regsub_no_memory_leak_many_captures + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", rss: true) + code = proc do + "aaaaaaaaaaa".gsub(/(a)(b)?(c)?(d)?(e)?(f)?(g)?(h)?/, "") + end + + 1_000.times(&code) + begin; + 100_000.times(&code) + end; + end + + def test_ignorecase + v = assert_deprecated_warning(/variable \$= is no longer effective/) { $= } + assert_equal(false, v) + assert_deprecated_warning(/variable \$= is no longer effective; ignored/) { $= = nil } end def test_match_setter @@ -261,6 +1037,40 @@ class TestRegexp < Test::Unit::TestCase assert_equal("foo", $&) end + def test_match_without_regexp + # create a MatchData for each assertion because the internal state may change + test = proc {|&blk| "abc".sub("a", ""); blk.call($~) } + + bug10877 = '[ruby-core:68209] [Bug #10877]' + bug18160 = '[Bug #18160]' + test.call {|m| assert_raise_with_message(IndexError, /foo/, bug10877) {m["foo"]} } + key = "\u{3042}" + [Encoding::UTF_8, Encoding::Shift_JIS, Encoding::EUC_JP].each do |enc| + idx = key.encode(enc) + pat = /#{idx}/ + EnvUtil.with_default_internal(enc) do + test.call {|m| assert_raise_with_message(IndexError, pat, bug10877) {m[idx]} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.offset(idx)} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.begin(idx)} } + test.call {|m| assert_raise_with_message(IndexError, pat, bug18160) {m.end(idx)} } + end + end + test.call {|m| assert_equal(/a/, m.regexp) } + test.call {|m| assert_equal("abc", m.string) } + test.call {|m| assert_equal(1, m.size) } + test.call {|m| assert_equal(0, m.begin(0)) } + test.call {|m| assert_equal(1, m.end(0)) } + test.call {|m| assert_equal([0, 1], m.offset(0)) } + test.call {|m| assert_equal([], m.captures) } + test.call {|m| assert_equal([], m.names) } + test.call {|m| assert_equal({}, m.named_captures) } + test.call {|m| assert_equal(/a/.match("abc"), m) } + test.call {|m| assert_equal(/a/.match("abc").hash, m.hash) } + test.call {|m| assert_equal("bc", m.post_match) } + test.call {|m| assert_equal("", m.pre_match) } + test.call {|m| assert_equal(["a", nil], m.values_at(0, 1)) } + end + def test_last_match /(...)(...)(...)(...)?/.match("foobarbaz") assert_equal("foobarbaz", Regexp.last_match(0)) @@ -284,35 +1094,30 @@ class TestRegexp < Test::Unit::TestCase assert_equal("baz", $+) end - def test_taint - m = Thread.new do - "foo"[/foo/] - $SAFE = 4 - /foo/.match("foo") - end.value - assert(m.tainted?) - assert_nothing_raised('[ruby-core:26137]') { - m = proc {$SAFE = 4; %r"#{ }"o}.call - } - assert(m.tainted?) + def test_rindex_regexp + # use eval because only one warning is shown for the same regexp literal + pat = eval('/b../n') + assert_equal(3, assert_warning(/binary regexp/) {"foobarbaz\u3042".rindex(pat, 5)}) end - def check(re, ss, fs = []) - re = Regexp.new(re) unless re.is_a?(Regexp) + def assert_regexp(re, ss, fs = [], msg = nil) + re = EnvUtil.suppress_warning {Regexp.new(re)} unless re.is_a?(Regexp) ss = [ss] unless ss.is_a?(Array) ss.each do |e, s| s ||= e - assert_match(re, s) + assert_match(re, s, msg) m = re.match(s) - assert_equal(e, m[0]) + assert_equal(e, m[0], msg) end fs = [fs] unless fs.is_a?(Array) - fs.each {|s| assert_no_match(re, s) } + fs.each {|s| assert_no_match(re, s, msg) } end + alias check assert_regexp - def failcheck(re) + def assert_fail(re) assert_raise(RegexpError) { %r"#{ re }" } end + alias failcheck assert_fail def test_parse check(/\*\+\?\{\}\|\(\)\<\>\`\'/, "*+?{}|()<>`'") @@ -320,16 +1125,22 @@ class TestRegexp < Test::Unit::TestCase check(/\A.\b.\b.\B.\B.\z/, %w(a.aaa .a...), %w(aaaaa .....)) check(/\A\s\S\z/, [' a', "\n."], [' ', "\n\n", 'a ']) check(/\A\d\D\z/, '0a', %w(00 aa)) + check(/\A\h\H\z/, %w(0g ag BH), %w(a0 af GG)) check(/\Afoo\Z\s\z/, "foo\n", ["foo", "foo\nbar"]) assert_equal(%w(a b c), "abc def".scan(/\G\w/)) + check(/\A\u3042\z/, "\u3042", ["", "\u3043", "a"]) check(/\A(..)\1\z/, %w(abab ....), %w(abba aba)) + failcheck('\1') + check(/\A\80\z/, "80", ["\100", ""]) check(/\A\77\z/, "?") check(/\A\78\z/, "\7" + '8', ["\100", ""]) - check(/\A\Qfoo\E\z/, "QfooE") - failcheck('\Aa++\z') + check(assert_warning(/Unknown escape/) {eval('/\A\Qfoo\E\z/')}, "QfooE") + check(/\Aa++\z/, "aaa") check('\Ax]\z', "x]") check(/x#foo/x, "x", "#foo") check(/\Ax#foo#{ "\n" }x\z/x, "xx", ["x", "x#foo\nx"]) + check(/\A\p{Alpha}\z/, ["a", "z"], [".", "", ".."]) + check(/\A\p{^Alpha}\z/, [".", "!"], ["!a", ""]) check(/\A\n\z/, "\n") check(/\A\t\z/, "\t") check(/\A\r\z/, "\r") @@ -339,6 +1150,8 @@ class TestRegexp < Test::Unit::TestCase check(/\A\v\z/, "\v") failcheck('(') failcheck('(?foo)') + failcheck('/\p{foobarbazqux}/') + failcheck('/\p{foobarbazqux' + 'a' * 1000 + '}/') failcheck('/[1-\w]/') end @@ -350,60 +1163,196 @@ class TestRegexp < Test::Unit::TestCase check(/[a-z]*\w/, %w(z az abz abcz), %w(!)) check(/[a-z]*\W/, %w(! a" ab# abc$), %w(A)) check(/((a|bb|ccc|dddd)(1|22|333|4444))/i, %w(a1 bb1 a22), %w(a2 b1)) + check(/\u0080/, (1..4).map {|i| ["\u0080", "\u0080" * i] }, ["\u0081"]) + check(/\u0080\u0080/, (2..4).map {|i| ["\u0080" * 2, "\u0080" * i] }, ["\u0081"]) + check(/\u0080\u0080\u0080/, (3..4).map {|i| ["\u0080" * 3, "\u0080" * i] }, ["\u0081"]) + check(/\u0080\u0080\u0080\u0080/, (4..4).map {|i| ["\u0080" * 4, "\u0080" * i] }, ["\u0081"]) + check(/[^\u3042\u3043\u3044]/, %W(a b \u0080 \u3041 \u3045), %W(\u3042 \u3043 \u3044)) + check(/a.+/m, %W(a\u0080 a\u0080\u0080 a\u0080\u0080\u0080), %W(a)) + check(/a.+z/m, %W(a\u0080z a\u0080\u0080z a\u0080\u0080\u0080z), %W(az)) check(/abc\B.\Bxyz/, %w(abcXxyz abc0xyz), %w(abc|xyz abc-xyz)) check(/\Bxyz/, [%w(xyz abcXxyz), %w(xyz abc0xyz)], %w(abc xyz abc-xyz)) check(/abc\B/, [%w(abc abcXxyz), %w(abc abc0xyz)], %w(abc xyz abc-xyz)) failcheck('(?<foo>abc)\1') - check(eval('/^(?:a?)?$/'), ["", "a"], ["aa"]) - check(eval('/^(?:a+)?$/'), ["", "a", "aa"], ["ab"]) + check(/^(A+|B+)(?>\g<1>)*[BC]$/, %w(AC BC ABC BAC AABBC), %w(AABB)) + check(/^(A+|B(?>\g<1>)*)[AC]$/, %w(AAAC BBBAAAAC), %w(BBBAAA)) + check(/^()(?>\g<1>)*$/, "", "a") + check(/^(?>(?=a)(#{ "a" * 1000 }|))++$/, ["a" * 1000, "a" * 2000, "a" * 3000], ["", "a" * 500, "b" * 1000]) + check(assert_warning(/nested repeat operator/) {eval('/^(?:a?)?$/')}, ["", "a"], ["aa"]) + check(assert_warning(/nested repeat operator/) {eval('/^(?:a+)?$/')}, ["", "a", "aa"], ["ab"]) + check(/^(?:a?)+?$/, ["", "a", "aa"], ["ab"]) check(/^a??[ab]/, [["a", "a"], ["a", "aa"], ["b", "b"], ["a", "ab"]], ["c"]) check(/^(?:a*){3,5}$/, ["", "a", "aa", "aaa", "aaaa", "aaaaa", "aaaaaa"], ["b"]) check(/^(?:a+){3,5}$/, ["aaa", "aaaa", "aaaaa", "aaaaaa"], ["", "a", "aa", "b"]) end + def test_parse_look_behind + check(/(?<=A)B(?=C)/, [%w(B ABC)], %w(aBC ABc aBc)) + check(/(?<!A)B(?!C)/, [%w(B aBc)], %w(ABC aBC ABc)) + failcheck('(?<=.*)') + failcheck('(?<!.*)') + check(/(?<=A|B.)C/, [%w(C AC), %w(C BXC)], %w(C BC)) + check(/(?<!A|B.)C/, [%w(C C), %w(C BC)], %w(AC BXC)) + + assert_not_match(/(?<!aa|b)c/i, "Aac") + assert_not_match(/(?<!b|aa)c/i, "Aac") + end + + def test_parse_kg + check(/\A(.)(.)\k<1>(.)\z/, %w(abac abab ....), %w(abcd aaba xxx)) + check(/\A(.)(.)\k<-1>(.)\z/, %w(abbc abba ....), %w(abcd aaba xxx)) + check(/\A(?<n>.)(?<x>\g<n>){0}(?<y>\k<n+0>){0}\g<x>\g<y>\z/, "aba", "abb") + check(/\A(?<n>.)(?<x>\g<n>){0}(?<y>\k<n+1>){0}\g<x>\g<y>\z/, "abb", "aba") + check(/\A(?<x>..)\k<x>\z/, %w(abab ....), %w(abac abba xxx)) + check(/\A(.)(..)\g<-1>\z/, "abcde", %w(.... ......)) + failcheck('\k<x>') + failcheck('\k<') + failcheck('\k<>') + failcheck('\k<.>') + failcheck('\k<x.>') + failcheck('\k<1.>') + failcheck('\k<x') + failcheck('\k<x+') + failcheck('()\k<-2>') + failcheck('()\g<-2>') + check(/\A(?<x>.)(?<x>.)\k<x>\z/, %w(aba abb), %w(abc .. ....)) + check(/\A(?<x>.)(?<x>.)\k<x>\z/i, %w(aba ABa abb ABb), %w(abc .. ....)) + check('\k\g', "kg") + failcheck('(.\g<1>)') + failcheck('(.\g<2>)') + failcheck('(?=\g<1>)') + failcheck('((?=\g<1>))') + failcheck('(\g<1>|.)') + failcheck('(.|\g<1>)') + check(/(!)(?<=(a)|\g<1>)/, ["!"], %w(a)) + check(/^(a|b\g<1>c)$/, %w(a bac bbacc bbbaccc), %w(bbac bacc)) + check(/^(a|b\g<2>c)(B\g<1>C){0}$/, %w(a bBaCc bBbBaCcCc bBbBbBaCcCcCc), %w(bBbBaCcC BbBaCcCc)) + check(/\A(?<n>.|X\g<n>)(?<x>\g<n>){0}(?<y>\k<n+0>){0}\g<x>\g<y>\z/, "XXaXbXXa", %w(XXabXa abb)) + check(/\A(?<n>.|X\g<n>)(?<x>\g<n>){0}(?<y>\k<n+1>){0}\g<x>\g<y>\z/, "XaXXbXXb", %w(aXXbXb aba)) + failcheck('(?<x>)(?<x>)(\g<x>)') + check(/^(?<x>foo)(bar)\k<x>/, %w(foobarfoo), %w(foobar barfoo)) + check(/^(?<a>f)(?<a>o)(?<a>o)(?<a>b)(?<a>a)(?<a>r)(?<a>b)(?<a>a)(?<a>z)\k<a>{9}$/, %w(foobarbazfoobarbaz foobarbazbazbarfoo foobarbazzabraboof), %w(foobar barfoo)) + end + def test_parse_curly_brace + check(/\A{/, ["{", ["{", "{x"]]) + check(/\A{ /, ["{ ", ["{ ", "{ x"]]) + check(/\A{,}\z/, "{,}") + check(/\A{}\z/, "{}") check(/\Aa{0}+\z/, "", %w(a aa aab)) check(/\Aa{1}+\z/, %w(a aa), ["", "aab"]) check(/\Aa{1,2}b{1,2}\z/, %w(ab aab abb aabb), ["", "aaabb", "abbb"]) check(/(?!x){0,1}/, [ ['', 'ab'], ['', ''] ]) + check(/c\z{0,1}/, [ ['c', 'abc'], ['c', 'cab']], ['abd']) + check(/\A{0,1}a/, [ ['a', 'abc'], ['a', '____abc']], ['bcd']) failcheck('.{100001}') failcheck('.{0,100001}') failcheck('.{1,0}') failcheck('{0}') end + def test_parse_comment + check(/\A(?#foo\)bar)\z/, "", "a") + failcheck('(?#') + end + + def test_char_type + check(/\u3042\d/, ["\u30421", "\u30422"]) + + # CClassTable cache test + assert_match(/\u3042\d/, "\u30421") + assert_match(/\u3042\d/, "\u30422") + end + def test_char_class failcheck('[]') failcheck('[x') check('\A[]]\z', "]", "") check('\A[]\.]+\z', %w(] . ]..]), ["", "["]) + check(/\A[\u3042]\z/, "\u3042", "\u3042aa") + check(/\A[\u3042\x61]+\z/, ["aa\u3042aa", "\u3042\u3042", "a"], ["", "b"]) + check(/\A[\u3042\x61\x62]+\z/, "abab\u3042abab\u3042") check(/\A[abc]+\z/, "abcba", ["", "ada"]) check(/\A[\w][\W]\z/, %w(a. b!), %w(.. ab)) check(/\A[\s][\S]\z/, [' a', "\n."], [' ', "\n\n", 'a ']) check(/\A[\d][\D]\z/, '0a', %w(00 aa)) + check(/\A[\h][\H]\z/, %w(0g ag BH), %w(a0 af GG)) + check(/\A[\p{Alpha}]\z/, ["a", "z"], [".", "", ".."]) + check(/\A[\p{^Alpha}]\z/, [".", "!"], ["!a", ""]) check(/\A[\xff]\z/, "\xff", ["", "\xfe"]) check(/\A[\80]+\z/, "8008", ["\\80", "\100", "\1000"]) check(/\A[\77]+\z/, "???") check(/\A[\78]+\z/, "\788\7") check(/\A[\0]\z/, "\0") + check(/\A[[:0]]\z/, [":", "0"], ["", ":0"]) check(/\A[0-]\z/, ["0", "-"], "0-") + check('\A[a-&&\w]\z', "a", "-") check('\A[--0]\z', ["-", "/", "0"], ["", "1"]) check('\A[\'--0]\z', %w(* + \( \) 0 ,), ["", ".", "1"]) check(/\A[a-b-]\z/, %w(a b -), ["", "c"]) + check('\A[a-b-&&\w]\z', %w(a b), ["", "-"]) + check('\A[a-b-&&\W]\z', "-", ["", "a", "b"]) + check('\A[a-c-e]\z', %w(a b c e -), %w(d)) + check(/\A[a-f&&[^b-c]&&[^e]]\z/, %w(a d f), %w(b c e g 0)) + check(/\A[[^b-c]&&[^e]&&a-f]\z/, %w(a d f), %w(b c e g 0)) check(/\A[\n\r\t]\z/, ["\n", "\r", "\t"]) failcheck('[9-1]') assert_match(/\A\d+\z/, "0123456789") + assert_no_match(/\d/, "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19") assert_match(/\A\w+\z/, "09azAZ_") - #assert_match(/\A\s+\z/, "\r\n\v\f\r\s") # pending for 187, [Bug #1196] + assert_no_match(/\w/, "\uff10\uff19\uff41\uff5a\uff21\uff3a") + assert_match(/\A\s+\z/, "\r\n\v\f\r\s") + assert_no_match(/\s/, "\u0085") end def test_posix_bracket check(/\A[[:alpha:]0]\z/, %w(0 a), %w(1 .)) + check(assert_warning(/duplicated range/) {eval('/\A[[:^alpha:]0]\z/')}, %w(0 1 .), "a") + check(assert_warning(/duplicated range/) {eval('/\A[[:alpha\:]]\z/')}, %w(a l p h a :), %w(b 0 1 .)) + check(assert_warning(/duplicated range/) {eval('/\A[[:alpha:foo]0]\z/')}, %w(0 a), %w(1 .)) + check(/\A[[:xdigit:]&&[:alpha:]]\z/, "a", %w(g 0)) check('\A[[:abcdefghijklmnopqrstu:]]+\z', "[]") failcheck('[[:alpha') - failcheck('[[:alpha:') + assert_warning(/duplicated range/) {failcheck('[[:alpha:')} failcheck('[[:alp:]]') + + assert_match(/\A[[:digit:]]+\z/, "\uff10\uff11\uff12\uff13\uff14\uff15\uff16\uff17\uff18\uff19") + assert_match(/\A[[:alnum:]]+\z/, "\uff10\uff19\uff41\uff5a\uff21\uff3a") + assert_match(/\A[[:space:]]+\z/, "\r\n\v\f\r\s\u0085") + assert_match(/\A[[:ascii:]]+\z/, "\x00\x7F") + assert_no_match(/[[:ascii:]]/, "\x80\xFF") + + assert_match(/[[:word:]]/, "\u{200C}") + assert_match(/[[:word:]]/, "\u{200D}") + end + + def test_cclass_R + assert_match(/\A\R\z/, "\r") + assert_match(/\A\R\z/, "\n") + assert_match(/\A\R\z/, "\f") + assert_match(/\A\R\z/, "\v") + assert_match(/\A\R\z/, "\r\n") + assert_match(/\A\R\z/, "\u0085") + assert_match(/\A\R\z/, "\u2028") + assert_match(/\A\R\z/, "\u2029") + end + + def test_cclass_X + assert_match(/\A\X\z/, "\u{20 200d}") + assert_match(/\A\X\z/, "\u{600 600}") + assert_match(/\A\X\z/, "\u{600 20}") + assert_match(/\A\X\z/, "\u{261d 1F3FB}") + assert_match(/\A\X\z/, "\u{1f600}") + assert_match(/\A\X\z/, "\u{20 324}") + assert_match(/\A\X\X\z/, "\u{a 324}") + assert_match(/\A\X\X\z/, "\u{d 324}") + assert_match(/\A\X\z/, "\u{1F477 1F3FF 200D 2640 FE0F}") + assert_match(/\A\X\z/, "\u{1F468 200D 1F393}") + assert_match(/\A\X\z/, "\u{1F46F 200D 2642 FE0F}") + assert_match(/\A\X\z/, "\u{1f469 200d 2764 fe0f 200d 1f469}") + + assert_warning('') {/\X/ =~ "\u{a0}"} end def test_backward @@ -422,11 +1371,20 @@ class TestRegexp < Test::Unit::TestCase assert_nil(~Regexp.allocate) assert_raise(TypeError) { Regexp.allocate.match("") } assert_raise(TypeError) { Regexp.allocate.to_s } - assert_raise(TypeError) { Regexp.allocate.inspect } + assert_match(/^#<Regexp:.*>$/, Regexp.allocate.inspect) assert_raise(TypeError) { Regexp.allocate.source } assert_raise(TypeError) { Regexp.allocate.casefold? } assert_raise(TypeError) { Regexp.allocate.options } + assert_equal(Encoding.find("ASCII-8BIT"), Regexp.allocate.encoding) + assert_equal(false, Regexp.allocate.fixed_encoding?) + assert_raise(TypeError) { Regexp.allocate.names } + assert_raise(TypeError) { Regexp.allocate.named_captures } + assert_not_respond_to(MatchData, :allocate) +=begin + assert_raise(TypeError) { MatchData.allocate.hash } + assert_raise(TypeError) { MatchData.allocate.regexp } + assert_raise(TypeError) { MatchData.allocate.names } assert_raise(TypeError) { MatchData.allocate.size } assert_raise(TypeError) { MatchData.allocate.length } assert_raise(TypeError) { MatchData.allocate.offset(0) } @@ -434,32 +1392,988 @@ class TestRegexp < Test::Unit::TestCase assert_raise(TypeError) { MatchData.allocate.end(0) } assert_raise(TypeError) { MatchData.allocate.to_a } assert_raise(TypeError) { MatchData.allocate[:foo] } + assert_raise(TypeError) { MatchData.allocate.captures } assert_raise(TypeError) { MatchData.allocate.values_at } assert_raise(TypeError) { MatchData.allocate.pre_match } assert_raise(TypeError) { MatchData.allocate.post_match } assert_raise(TypeError) { MatchData.allocate.to_s } + assert_match(/^#<MatchData:.*>$/, MatchData.allocate.inspect) assert_raise(TypeError) { MatchData.allocate.string } $~ = MatchData.allocate assert_raise(TypeError) { $& } assert_raise(TypeError) { $` } assert_raise(TypeError) { $' } assert_raise(TypeError) { $+ } +=end + end + + def test_unicode + assert_match(/^\u3042{0}\p{Any}$/, "a") + assert_match(/^\u3042{0}\p{Any}$/, "\u3041") + assert_match(/^\u3042{0}\p{Any}$/, "\0") + assert_match(/^\p{Lo}{4}$/u, "\u3401\u4E01\u{20001}\u{2A701}") + assert_no_match(/^\u3042{0}\p{Any}$/, "\0\0") + assert_no_match(/^\u3042{0}\p{Any}$/, "") + assert_raise(SyntaxError) { eval('/^\u3042{0}\p{' + "\u3042" + '}$/') } + assert_raise(SyntaxError) { eval('/^\u3042{0}\p{' + 'a' * 1000 + '}$/') } + assert_raise(SyntaxError) { eval('/^\u3042{0}\p{foobarbazqux}$/') } + assert_match(/^(\uff21)(a)\1\2$/i, "\uff21A\uff41a") + assert_no_match(/^(\uff21)\1$/i, "\uff21A") + assert_no_match(/^(\uff41)\1$/i, "\uff41a") + assert_match(/^\u00df$/i, "\u00df") + assert_match(/^\u00df$/i, "ss") + #assert_match(/^(\u00df)\1$/i, "\u00dfss") # this must be bug... + assert_match(/^\u00df{2}$/i, "\u00dfss") + assert_match(/^\u00c5$/i, "\u00c5") + assert_match(/^\u00c5$/i, "\u00e5") + assert_match(/^\u00c5$/i, "\u212b") + assert_match(/^(\u00c5)\1\1$/i, "\u00c5\u00e5\u212b") + assert_match(/^\u0149$/i, "\u0149") + assert_match(/^\u0149$/i, "\u02bcn") + #assert_match(/^(\u0149)\1$/i, "\u0149\u02bcn") # this must be bug... + assert_match(/^\u0149{2}$/i, "\u0149\u02bcn") + assert_match(/^\u0390$/i, "\u0390") + assert_match(/^\u0390$/i, "\u03b9\u0308\u0301") + #assert_match(/^(\u0390)\1$/i, "\u0390\u03b9\u0308\u0301") # this must be bug... + assert_match(/^\u0390{2}$/i, "\u0390\u03b9\u0308\u0301") + assert_match(/^\ufb05$/i, "\ufb05") + assert_match(/^\ufb05$/i, "\ufb06") + assert_match(/^\ufb05$/i, "st") + #assert_match(/^(\ufb05)\1\1$/i, "\ufb05\ufb06st") # this must be bug... + assert_match(/^\ufb05{3}$/i, "\ufb05\ufb06st") + assert_match(/^\u03b9\u0308\u0301$/i, "\u0390") + end + + def test_unicode_age + assert_unicode_age("\u261c", matches: %w"6.0 1.1", unmatches: []) + + assert_unicode_age("\u31f6", matches: %w"6.0 3.2", unmatches: %w"3.1 3.0 1.1") + assert_unicode_age("\u2754", matches: %w"6.0", unmatches: %w"5.0 4.0 3.0 2.0 1.1") + + assert_unicode_age("\u32FF", matches: %w"12.1", unmatches: %w"12.0") end - def test_regexp_poped - assert_nothing_raised { eval("a = 1; /\#{ a }/; a") } - assert_nothing_raised { eval("a = 1; /\#{ a }/o; a") } + def test_unicode_age_14_0 + @matches = %w"14.0" + @unmatches = %w"13.0" + + assert_unicode_age("\u{10570}") + assert_unicode_age("\u9FFF") + assert_unicode_age("\u{2A6DF}") + assert_unicode_age("\u{2B738}") + end + + def test_unicode_age_15_0 + @matches = %w"15.0" + @unmatches = %w"14.0" + + assert_unicode_age("\u{0CF3}", + "KANNADA SIGN COMBINING ANUSVARA ABOVE RIGHT") + assert_unicode_age("\u{0ECE}", "LAO YAMAKKAN") + assert_unicode_age("\u{10EFD}".."\u{10EFF}", + "ARABIC SMALL LOW WORD SAKTA..ARABIC SMALL LOW WORD MADDA") + assert_unicode_age("\u{1123F}".."\u{11241}", + "KHOJKI LETTER QA..KHOJKI VOWEL SIGN VOCALIC R") + assert_unicode_age("\u{11B00}".."\u{11B09}", + "DEVANAGARI HEAD MARK..DEVANAGARI SIGN MINDU") + assert_unicode_age("\u{11F00}".."\u{11F10}", + "KAWI SIGN CANDRABINDU..KAWI LETTER O") + assert_unicode_age("\u{11F12}".."\u{11F3A}", + "KAWI LETTER KA..KAWI VOWEL SIGN VOCALIC R") + assert_unicode_age("\u{11F3E}".."\u{11F59}", + "KAWI VOWEL SIGN E..KAWI DIGIT NINE") + assert_unicode_age("\u{1342F}", + "EGYPTIAN HIEROGLYPH V011D") + assert_unicode_age("\u{13439}".."\u{1343F}", + "EGYPTIAN HIEROGLYPH INSERT AT MIDDLE..EGYPTIAN HIEROGLYPH END WALLED ENCLOSURE") + assert_unicode_age("\u{13440}".."\u{13455}", + "EGYPTIAN HIEROGLYPH MIRROR HORIZONTALLY..EGYPTIAN HIEROGLYPH MODIFIER DAMAGED") + assert_unicode_age("\u{1B132}", "HIRAGANA LETTER SMALL KO") + assert_unicode_age("\u{1B155}", "KATAKANA LETTER SMALL KO") + assert_unicode_age("\u{1D2C0}".."\u{1D2D3}", + "KAKTOVIK NUMERAL ZERO..KAKTOVIK NUMERAL NINETEEN") + assert_unicode_age("\u{1DF25}".."\u{1DF2A}", + "LATIN SMALL LETTER D WITH MID-HEIGHT LEFT HOOK..LATIN SMALL LETTER T WITH MID-HEIGHT LEFT HOOK") + assert_unicode_age("\u{1E030}".."\u{1E06D}", + "MODIFIER LETTER CYRILLIC SMALL A..MODIFIER LETTER CYRILLIC SMALL STRAIGHT U WITH STROKE") + assert_unicode_age("\u{1E08F}", + "COMBINING CYRILLIC SMALL LETTER BYELORUSSIAN-UKRAINIAN I") + assert_unicode_age("\u{1E4D0}".."\u{1E4F9}", + "NAG MUNDARI LETTER O..NAG MUNDARI DIGIT NINE") + assert_unicode_age("\u{1F6DC}", "WIRELESS") + assert_unicode_age("\u{1F774}".."\u{1F776}", + "LOT OF FORTUNE..LUNAR ECLIPSE") + assert_unicode_age("\u{1F77B}".."\u{1F77F}", + "HAUMEA..ORCUS") + assert_unicode_age("\u{1F7D9}", "NINE POINTED WHITE STAR") + assert_unicode_age("\u{1FA75}".."\u{1FA77}", + "LIGHT BLUE HEART..PINK HEART") + assert_unicode_age("\u{1FA87}".."\u{1FA88}", + "MARACAS..FLUTE") + assert_unicode_age("\u{1FAAD}".."\u{1FAAF}", + "FOLDING HAND FAN..KHANDA") + assert_unicode_age("\u{1FABB}".."\u{1FABD}", + "HYACINTH..WING") + assert_unicode_age("\u{1FABF}", "GOOSE") + assert_unicode_age("\u{1FACE}".."\u{1FACF}", + "MOOSE..DONKEY") + assert_unicode_age("\u{1FADA}".."\u{1FADB}", + "GINGER ROOT..PEA POD") + assert_unicode_age("\u{1FAE8}", "SHAKING FACE") + assert_unicode_age("\u{1FAF7}".."\u{1FAF8}", + "LEFTWARDS PUSHING HAND..RIGHTWARDS PUSHING HAND") + assert_unicode_age("\u{2B739}", + "CJK UNIFIED IDEOGRAPH-2B739") + assert_unicode_age("\u{31350}".."\u{323AF}", + "CJK UNIFIED IDEOGRAPH-31350..CJK UNIFIED IDEOGRAPH-323AF") + end + + def test_unicode_age_15_1 + @matches = %w"15.1" + @unmatches = %w"15.0" + + # https://www.unicode.org/Public/15.1.0/ucd/DerivedAge.txt + assert_unicode_age("\u{2FFC}".."\u{2FFF}", + "IDEOGRAPHIC DESCRIPTION CHARACTER SURROUND FROM RIGHT..IDEOGRAPHIC DESCRIPTION CHARACTER ROTATION") + assert_unicode_age("\u{31EF}", + "IDEOGRAPHIC DESCRIPTION CHARACTER SUBTRACTION") + assert_unicode_age("\u{2EBF0}".."\u{2EE5D}", + "CJK UNIFIED IDEOGRAPH-2EBF0..CJK UNIFIED IDEOGRAPH-2EE5D") + end + + def test_unicode_age_16_0 + @matches = %w"16.0" + @unmatches = %w"15.1" + + # https://www.unicode.org/Public/16.0.0/ucd/DerivedAge.txt + assert_unicode_age("\u{0897}", + "ARABIC PEPET") + assert_unicode_age("\u{1B4E}".."\u{1B4F}", + "BALINESE INVERTED CARIK SIKI..BALINESE INVERTED CARIK PAREREN") + assert_unicode_age("\u{1B7F}", + "BALINESE PANTI BAWAK") + assert_unicode_age("\u{1C89}".."\u{1C8A}", + "CYRILLIC CAPITAL LETTER TJE..CYRILLIC SMALL LETTER TJE") + assert_unicode_age("\u{2427}".."\u{2429}", + "SYMBOL FOR DELETE SQUARE CHECKER BOARD FORM..SYMBOL FOR DELETE MEDIUM SHADE FORM") + assert_unicode_age("\u{31E4}".."\u{31E5}", + "CJK STROKE HXG..CJK STROKE SZP") + assert_unicode_age("\u{A7CB}".."\u{A7CD}", + "LATIN CAPITAL LETTER RAMS HORN..LATIN SMALL LETTER S WITH DIAGONAL STROKE") + assert_unicode_age("\u{A7DA}".."\u{A7DC}", + "LATIN CAPITAL LETTER LAMBDA..LATIN CAPITAL LETTER LAMBDA WITH STROKE") + assert_unicode_age("\u{105C0}".."\u{105F3}", + "TODHRI LETTER A..TODHRI LETTER OO") + assert_unicode_age("\u{10D40}".."\u{10D65}", + "GARAY DIGIT ZERO..GARAY CAPITAL LETTER OLD NA") + assert_unicode_age("\u{10D69}".."\u{10D85}", + "GARAY VOWEL SIGN E..GARAY SMALL LETTER OLD NA") + assert_unicode_age("\u{10D8E}".."\u{10D8F}", + "GARAY PLUS SIGN..GARAY MINUS SIGN") + assert_unicode_age("\u{10EC2}".."\u{10EC4}", + "ARABIC LETTER DAL WITH TWO DOTS VERTICALLY BELOW..ARABIC LETTER KAF WITH TWO DOTS VERTICALLY BELOW") + assert_unicode_age("\u{10EFC}", + "ARABIC COMBINING ALEF OVERLAY") + assert_unicode_age("\u{11380}".."\u{11389}", + "TULU-TIGALARI LETTER A..TULU-TIGALARI LETTER VOCALIC LL") + assert_unicode_age("\u{1138B}", + "TULU-TIGALARI LETTER EE") + assert_unicode_age("\u{1138E}", + "TULU-TIGALARI LETTER AI") + assert_unicode_age("\u{11390}".."\u{113B5}", + "TULU-TIGALARI LETTER OO..TULU-TIGALARI LETTER LLLA") + assert_unicode_age("\u{113B7}".."\u{113C0}", + "TULU-TIGALARI SIGN AVAGRAHA..TULU-TIGALARI VOWEL SIGN VOCALIC LL") + assert_unicode_age("\u{113C2}", + "TULU-TIGALARI VOWEL SIGN EE") + assert_unicode_age("\u{113C5}", + "TULU-TIGALARI VOWEL SIGN AI") + assert_unicode_age("\u{113C7}".."\u{113CA}", + "TULU-TIGALARI VOWEL SIGN OO..TULU-TIGALARI SIGN CANDRA ANUNASIKA") + assert_unicode_age("\u{113CC}".."\u{113D5}", + "TULU-TIGALARI SIGN ANUSVARA..TULU-TIGALARI DOUBLE DANDA") + assert_unicode_age("\u{113D7}".."\u{113D8}", + "TULU-TIGALARI SIGN OM PUSHPIKA..TULU-TIGALARI SIGN SHRII PUSHPIKA") + assert_unicode_age("\u{113E1}".."\u{113E2}", + "TULU-TIGALARI VEDIC TONE SVARITA..TULU-TIGALARI VEDIC TONE ANUDATTA") + assert_unicode_age("\u{116D0}".."\u{116E3}", + "MYANMAR PAO DIGIT ZERO..MYANMAR EASTERN PWO KAREN DIGIT NINE") + assert_unicode_age("\u{11BC0}".."\u{11BE1}", + "SUNUWAR LETTER DEVI..SUNUWAR SIGN PVO") + assert_unicode_age("\u{11BF0}".."\u{11BF9}", + "SUNUWAR DIGIT ZERO..SUNUWAR DIGIT NINE") + assert_unicode_age("\u{11F5A}", + "KAWI SIGN NUKTA") + assert_unicode_age("\u{13460}".."\u{143FA}", + "EGYPTIAN HIEROGLYPH-13460..EGYPTIAN HIEROGLYPH-143FA") + assert_unicode_age("\u{16100}".."\u{16139}", + "GURUNG KHEMA LETTER A..GURUNG KHEMA DIGIT NINE") + assert_unicode_age("\u{16D40}".."\u{16D79}", + "KIRAT RAI SIGN ANUSVARA..KIRAT RAI DIGIT NINE") + assert_unicode_age("\u{18CFF}", + "KHITAN SMALL SCRIPT CHARACTER-18CFF") + assert_unicode_age("\u{1CC00}".."\u{1CCF9}", + "UP-POINTING GO-KART..OUTLINED DIGIT NINE") + assert_unicode_age("\u{1CD00}".."\u{1CEB3}", + "BLOCK OCTANT-3..BLACK RIGHT TRIANGLE CARET") + assert_unicode_age("\u{1E5D0}".."\u{1E5FA}", + "OL ONAL LETTER O..OL ONAL DIGIT NINE") + assert_unicode_age("\u{1E5FF}", + "OL ONAL ABBREVIATION SIGN") + assert_unicode_age("\u{1F8B2}".."\u{1F8BB}", + "RIGHTWARDS ARROW WITH LOWER HOOK..SOUTH WEST ARROW FROM BAR") + assert_unicode_age("\u{1F8C0}".."\u{1F8C1}", + "LEFTWARDS ARROW FROM DOWNWARDS ARROW..RIGHTWARDS ARROW FROM DOWNWARDS ARROW") + assert_unicode_age("\u{1FA89}", + "HARP") + assert_unicode_age("\u{1FA8F}", + "SHOVEL") + assert_unicode_age("\u{1FABE}", + "LEAFLESS TREE") + assert_unicode_age("\u{1FAC6}", + "FINGERPRINT") + assert_unicode_age("\u{1FADC}", + "ROOT VEGETABLE") + assert_unicode_age("\u{1FADF}", + "SPLATTER") + assert_unicode_age("\u{1FAE9}", + "FACE WITH BAGS UNDER EYES") + assert_unicode_age("\u{1FBCB}".."\u{1FBEF}", + "WHITE CROSS MARK..TOP LEFT JUSTIFIED LOWER RIGHT QUARTER BLACK CIRCLE") + end + + UnicodeAgeRegexps = Hash.new do |h, age| + h[age] = [/\A\p{age=#{age}}+\z/u, /\A\P{age=#{age}}+\z/u].freeze + end + + def assert_unicode_age(char, mesg = nil, matches: @matches, unmatches: @unmatches) + if Range === char + char = char.to_a.join("") + end + + matches.each do |age| + pos, neg = UnicodeAgeRegexps[age] + assert_match(pos, char, mesg) + assert_not_match(neg, char, mesg) + end + + unmatches.each do |age| + pos, neg = UnicodeAgeRegexps[age] + assert_not_match(pos, char, mesg) + assert_match(neg, char, mesg) + end + end + + MatchData_A = eval("class MatchData_\u{3042} < MatchData; self; end") + + def test_matchdata + a = "haystack".match(/hay/) + b = "haystack".match(/hay/) + assert_equal(a, b, '[ruby-core:24748]') + h = {a => 42} + assert_equal(42, h[b], '[ruby-core:24748]') +=begin + assert_match(/#<TestRegexp::MatchData_\u{3042}:/, MatchData_A.allocate.inspect) +=end + + h = /^(?<@time>\d+): (?<body>.*)/.match("123456: hoge fuga") + assert_equal("123456", h["@time"]) + assert_equal("hoge fuga", h["body"]) + end + + def test_matchdata_large_capture_groups_stack + env = {"RUBY_THREAD_MACHINE_STACK_SIZE" => (256 * 1024).to_s} + assert_separately([env], <<~'RUBY') + n = 20000 + require "rbconfig/sizeof" + stack = RubyVM::DEFAULT_PARAMS[:thread_machine_stack_size] + size = RbConfig::SIZEOF["long"] + required = (n + 1) * 4 * size + if !stack || stack == 0 || stack >= required + omit "thread machine stack size not reduced (#{stack}:#{required})" + end + + inspect = Thread.new do + str = "\u{3042}" * n + m = Regexp.new("(.)" * n).match(str) + assert_not_nil(m) + assert_equal([n - 1, n], m.offset(n)) + m.inspect + end.value + + assert_include(inspect, "MatchData") + RUBY + end + + def test_match_integer_at + m = /(\d{4})(\d{2})(\d{2})/.match("20260308") + assert_equal(20260308, m.integer_at(0)) + assert_equal(2026, m.integer_at(1)) + assert_equal(3, m.integer_at(2)) + assert_equal(8, m.integer_at(3)) + assert_equal(nil, m.integer_at(4)) + assert_equal(8, m.integer_at(-1)) + assert_equal(3, m.integer_at(-2)) + assert_equal(2026, m.integer_at(-3)) + assert_equal(nil, m.integer_at(-4)) + + re = /[a-z]+|(\d+)/ + assert_equal(123, re.match("123").integer_at(1)) + assert_equal(nil, re.match("abc").integer_at(1)) + end + + def test_match_integer_at_name + m = /(?<y>\d{4})(?<m>\d{2})(?<d>\d{2})/.match("20260308") + assert_equal(2026, m.integer_at("y")) + assert_equal(3, m.integer_at("m")) + assert_equal(8, m.integer_at("d")) + end + + def test_match_integer_at_base + assert_equal(91, /\w+/.match("111").integer_at(0, 9)) + assert_equal(10_0000, /\w+/.match("10_0000").integer_at(0)) + assert_equal(0d1_0000, /\w+/.match("01_0000").integer_at(0)) + assert_equal(0o1_0000, /\w+/.match("01_0000").integer_at(0, 0)) + assert_equal(0b1_0000, /\w+/.match("0b1_0000").integer_at(0, 0)) + assert_equal(0o1_0000, /\w+/.match("0o1_0000").integer_at(0, 0)) + assert_equal(0d1_0000, /\w+/.match("0d1_0000").integer_at(0, 0)) + assert_equal(0x1_0000, /\w+/.match("0x1_0000").integer_at(0, 0)) + end + + def test_regexp_popped + EnvUtil.suppress_warning do + assert_nothing_raised { eval("a = 1; /\#{ a }/; a") } + assert_nothing_raised { eval("a = 1; /\#{ a }/o; a") } + end + end + + def test_invalid_fragment + bug2547 = '[ruby-core:27374]' + assert_raise(SyntaxError, bug2547) do + assert_warning(/ignored/) {eval('/#{"\\\\"}y/')} + end + end + + def test_dup_warn + assert_warning(/duplicated/) { Regexp.new('[\u3042\u3043\u3042]') } + assert_warning(/duplicated/) { Regexp.new('[\u3042\u3043\u3043]') } + assert_warning(/\A\z/) { Regexp.new('[\u3042\u3044\u3043]') } + assert_warning(/\A\z/) { Regexp.new('[\u3042\u3045\u3043]') } + assert_warning(/\A\z/) { Regexp.new('[\u3042\u3045\u3044]') } + assert_warning(/\A\z/) { Regexp.new('[\u3042\u3045\u3043-\u3044]') } + assert_warning(/duplicated/) { Regexp.new('[\u3042\u3045\u3042-\u3043]') } + assert_warning(/duplicated/) { Regexp.new('[\u3042\u3045\u3044-\u3045]') } + assert_warning(/\A\z/) { Regexp.new('[\u3042\u3046\u3044]') } + assert_warning(/duplicated/) { Regexp.new('[\u1000-\u2000\u3042-\u3046\u3044]') } + assert_warning(/duplicated/) { Regexp.new('[\u3044\u3041-\u3047]') } + assert_warning(/duplicated/) { Regexp.new('[\u3042\u3044\u3046\u3041-\u3047]') } + + bug7471 = '[ruby-core:50344]' + assert_warning('', bug7471) { Regexp.new('[\D]') =~ "\u3042" } + + bug8151 = '[ruby-core:53649]' + assert_warning(/\A\z/, bug8151) { Regexp.new('(?:[\u{33}])').to_s } + + assert_warning(%r[/.*/\Z]) { Regexp.new("[\n\n]") } + end + + def test_property_warn + assert_in_out_err('-w', 'x=/\p%s/', [], %r"warning: invalid Unicode Property \\p: /\\p%s/") + end + + def test_invalid_escape_error + bug3539 = '[ruby-core:31048]' + error = assert_raise(SyntaxError) {eval('/\x/', nil, bug3539)} + assert_match(/invalid hex escape/, error.message) + assert_equal(1, error.message.scan(/.*invalid .*escape.*/i).size, bug3539) + end + + def test_raw_hyphen_and_tk_char_type_after_range + bug6853 = '[ruby-core:47115]' + # use Regexp.new instead of literal to ignore a parser warning. + re = assert_warning(/without escape/) {Regexp.new('[0-1-\\s]')} + check(re, [' ', '-'], ['2', 'a'], bug6853) + end + + def test_error_message_on_failed_conversion + bug7539 = '[ruby-core:50733]' + assert_equal false, /x/=== 42 + assert_raise_with_message(TypeError, 'no implicit conversion of Integer into String', bug7539) { + Regexp.quote(42) + } + end + + def test_conditional_expression + bug8583 = '[ruby-dev:47480] [Bug #8583]' + + conds = {"xy"=>true, "yx"=>true, "xx"=>false, "yy"=>false} + assert_match_each(/\A((x)|(y))(?(2)y|x)\z/, conds, bug8583) + assert_match_each(/\A((?<x>x)|(?<y>y))(?(<x>)y|x)\z/, conds, bug8583) + + bug12418 = '[ruby-core:75694] [Bug #12418]' + assert_raise(RegexpError, bug12418){ Regexp.new('(0?0|(?(5)||)|(?(5)||))?') } + end + + def test_quick_search + assert_match_at('(?i) *TOOKY', 'Mozilla/5.0 (Linux; Android 4.0.3; TOOKY', [[34, 40]]) # Issue #120 + end + + def test_ss_in_look_behind + assert_match_at("(?i:ss)", "ss", [[0, 2]]) + assert_match_at("(?i:ss)", "Ss", [[0, 2]]) + assert_match_at("(?i:ss)", "SS", [[0, 2]]) + assert_match_at("(?i:ss)", "\u017fS", [[0, 2]]) # LATIN SMALL LETTER LONG S + assert_match_at("(?i:ss)", "s\u017f", [[0, 2]]) + assert_match_at("(?i:ss)", "\u00df", [[0, 1]]) # LATIN SMALL LETTER SHARP S + assert_match_at("(?i:ss)", "\u1e9e", [[0, 1]]) # LATIN CAPITAL LETTER SHARP S + assert_match_at("(?i:xssy)", "xssy", [[0, 4]]) + assert_match_at("(?i:xssy)", "xSsy", [[0, 4]]) + assert_match_at("(?i:xssy)", "xSSy", [[0, 4]]) + assert_match_at("(?i:xssy)", "x\u017fSy", [[0, 4]]) + assert_match_at("(?i:xssy)", "xs\u017fy", [[0, 4]]) + assert_match_at("(?i:xssy)", "x\u00dfy", [[0, 3]]) + assert_match_at("(?i:xssy)", "x\u1e9ey", [[0, 3]]) + assert_match_at("(?i:\u00df)", "ss", [[0, 2]]) + assert_match_at("(?i:\u00df)", "SS", [[0, 2]]) + assert_match_at("(?i:[\u00df])", "ss", [[0, 2]]) + assert_match_at("(?i:[\u00df])", "SS", [[0, 2]]) + assert_match_at("(?i)(?<!ss)\u2728", "qq\u2728", [[2, 3]]) # Issue #92 + assert_match_at("(?i)(?<!xss)\u2728", "qq\u2728", [[2, 3]]) + end + + def test_options_in_look_behind + assert_nothing_raised { + assert_match_at("(?<=(?i)ab)cd", "ABcd", [[2,4]]) + assert_match_at("(?<=(?i:ab))cd", "ABcd", [[2,4]]) + assert_match_at("(?<!(?i)ab)cd", "aacd", [[2,4]]) + assert_match_at("(?<!(?i:ab))cd", "aacd", [[2,4]]) + + assert_not_match("(?<=(?i)ab)cd", "ABCD") + assert_not_match("(?<=(?i:ab))cd", "ABCD") + assert_not_match("(?<!(?i)ab)cd", "ABcd") + assert_not_match("(?<!(?i:ab))cd", "ABcd") + } + end + + def test_quantifier_reduction + assert_equal('aa', eval('/(a+?)*/').match('aa')[0]) + assert_equal('aa', eval('/(?:a+?)*/').match('aa')[0]) + + quantifiers = %w'? * + ?? *? +?' + quantifiers.product(quantifiers) do |q1, q2| + EnvUtil.suppress_warning do + r1 = eval("/(a#{q1})#{q2}/").match('aa')[0] + r2 = eval("/(?:a#{q1})#{q2}/").match('aa')[0] + assert_equal(r1, r2) + end + end + end + + def test_once + pr1 = proc{|i| /#{i}/o} + assert_equal(/0/, pr1.call(0)) + assert_equal(/0/, pr1.call(1)) + assert_equal(/0/, pr1.call(2)) + end + + def test_once_recursive + pr2 = proc{|i| + if i > 0 + /#{pr2.call(i-1).to_s}#{i}/ + else + // + end + } + assert_equal(/(?-mix:(?-mix:(?-mix:)1)2)3/, pr2.call(3)) + end + + def test_once_multithread + m = Thread::Mutex.new + pr3 = proc{|i| + /#{m.unlock; sleep 0.5; i}/o + } + ary = [] + n = 0 + th1 = Thread.new{m.lock; ary << pr3.call(n+=1)} + th2 = Thread.new{m.lock; ary << pr3.call(n+=1)} + th1.join; th2.join + assert_equal([/1/, /1/], ary) + end + + def test_once_escape + pr4 = proc{|i| + catch(:xyzzy){ + /#{throw :xyzzy, i}/o =~ "" + :ng + } + } + assert_equal(0, pr4.call(0)) + assert_equal(1, pr4.call(1)) + end + + def test_eq_tilde_can_be_overridden + assert_separately([], <<-RUBY) + class Regexp + undef =~ + def =~(str) + "foo" + end + end + + assert_equal("foo", // =~ "") + RUBY + end + + def test_invalid_free_at_parse_depth_limit_over + assert_separately([], "#{<<-"begin;"}\n#{<<-"end;"}") + begin; + begin + require '-test-/regexp' + rescue LoadError + else + bug = '[ruby-core:79624] [Bug #13234]' + Bug::Regexp.parse_depth_limit = 10 + src = "[" * 100 + 3.times do + assert_raise_with_message(RegexpError, /parse depth limit over/, bug) do + Regexp.new(src) + end + end + end + end; + end + + def test_absent + assert_equal(0, /(?~(a|c)c)/ =~ "abb") + assert_equal("abb", $&) + + assert_equal(0, /\/\*((?~\*\/))\*\// =~ "/*abc*def/xyz*/ /* */") + assert_equal("abc*def/xyz", $1) + + assert_equal(0, /(?~(a)c)/ =~ "abb") + assert_nil($1) + + assert_equal(0, /(?~(a))/ =~ "") + assert_nil($1) + end + + def test_backref_overrun + assert_raise_with_message(SyntaxError, /invalid backref number/) do + eval(%["".match(/(())(?<X>)((?(90000)))/)]) + end + end + + def test_bug18631 + assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaaa") + assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaa") + assert_kind_of MatchData, /(?<x>a)(?<x>aa)\k<x>/.match("aaaab") + end + + def test_invalid_group + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + assert_raise_with_message(RegexpError, /invalid conditional pattern/) do + Regexp.new("((?(1)x|x|)x)+") + end + end; + end + + def test_too_big_number_for_repeat_range + assert_raise_with_message(SyntaxError, /too big number for repeat range/) do + eval(%[/|{1000000}/]) + end + end + + # This assertion is for porting x2() tests in testpy.py of Onigmo. + def assert_match_at(re, str, positions, msg = nil) + re = Regexp.new(re) unless re.is_a?(Regexp) + + match = re.match(str) + + assert_not_nil match, message(msg) { + "Expected #{re.inspect} to match #{str.inspect}" + } + + if match + actual_positions = (0...match.size).map { |i| + [match.begin(i), match.end(i)] + } + + assert_equal positions, actual_positions, message(msg) { + "Expected #{re.inspect} to match #{str.inspect} at: #{positions.inspect}" + } + end + end + + def assert_match_each(re, conds, msg = nil) + errs = conds.select {|str, match| match ^ (re =~ str)} + msg = message(msg) { + "Expected #{re.inspect} to\n" + + errs.map {|str, match| "\t#{'not ' unless match}match #{str.inspect}"}.join(",\n") + } + assert_empty(errs, msg) + end + + def test_s_timeout + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(0.2).inspect } + begin; + Regexp.timeout = timeout + assert_in_delta(timeout, Regexp.timeout, timeout * 2 * Float::EPSILON) + + t = Time.now + assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do + # A typical ReDoS case + /^(a*)*\1$/ =~ "a" * 1000000 + "x" + end + t = Time.now - t + + assert_operator(timeout, :<=, [timeout * 1.5, 1].max) + end; + end + + def test_s_timeout_corner_cases + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + assert_nil(Regexp.timeout) + + # This is just an implementation detail that users should not depend on: + # If Regexp.timeout is set to a value greater than the value that can be + # represented in the internal representation of timeout, it uses the + # maximum value that can be represented. + Regexp.timeout = Float::INFINITY + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout) + + Regexp.timeout = 1e300 + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.timeout) + + assert_raise(ArgumentError) { Regexp.timeout = Float::NAN } + assert_raise(ArgumentError) { Regexp.timeout = 0 } + assert_raise(ArgumentError) { Regexp.timeout = -1 } + + Regexp.timeout = nil + assert_nil(Regexp.timeout) + end; + end + + def test_s_timeout_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~"end;"}", "[Bug #20228]", rss: true) + Regexp.timeout = 0.001 + regex = /^(a*)*$/ + str = "a" * 1000000 + "x" + + code = proc do + regex =~ str + rescue + end + + 10.times(&code) + begin; + 1_000.times(&code) + end; + end + + def test_bug_20453 + re = Regexp.new("^(a*)x$", timeout: 0.001) + + assert_raise(Regexp::TimeoutError) do + re =~ "a" * 1000000 + "x" + end + end + + def test_bug_20886 + re = Regexp.new("d()*+|a*a*bc", timeout: 0.02) + assert_raise(Regexp::TimeoutError) do + re === "b" + "a" * 1000 + end + end + + def per_instance_redos_test(global_timeout, per_instance_timeout, expected_timeout) + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 60) + global_timeout = #{ EnvUtil.apply_timeout_scale(global_timeout).inspect } + per_instance_timeout = #{ (per_instance_timeout ? EnvUtil.apply_timeout_scale(per_instance_timeout) : nil).inspect } + expected_timeout = #{ EnvUtil.apply_timeout_scale(expected_timeout).inspect } + begin; + Regexp.timeout = global_timeout + + re = Regexp.new("^(a*)\\1b?a*$", timeout: per_instance_timeout) + if per_instance_timeout + assert_in_delta(per_instance_timeout, re.timeout, per_instance_timeout * 2 * Float::EPSILON) + else + assert_nil(re.timeout) + end + + t = Time.now + assert_raise_with_message(Regexp::TimeoutError, "regexp match timeout") do + re =~ "a" * 1000000 + "x" + end + t = Time.now - t + + assert_in_delta(expected_timeout, t, expected_timeout * 3 / 4) + end; + end + + def test_timeout_shorter_than_global + omit "timeout test is too unstable on s390x" if RUBY_PLATFORM =~ /s390x/ + per_instance_redos_test(10, 0.5, 0.5) + end + + def test_timeout_longer_than_global + omit "timeout test is too unstable on s390x" if RUBY_PLATFORM =~ /s390x/ + per_instance_redos_test(0.01, 0.5, 0.5) + end + + def test_timeout_nil + per_instance_redos_test(0.5, nil, 0.5) + end + + def test_timeout_corner_cases + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + begin; + assert_nil(//.timeout) + + # This is just an implementation detail that users should not depend on: + # If Regexp.timeout is set to a value greater than the value that can be + # represented in the internal representation of timeout, it uses the + # maximum value that can be represented. + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: Float::INFINITY).timeout) + + assert_equal(((1<<64)-1) / 1000000000.0, Regexp.new("foo", timeout: 1e300).timeout) + + assert_raise(ArgumentError) { Regexp.new("foo", timeout: Float::NAN) } + assert_raise(ArgumentError) { Regexp.new("foo", timeout: 0) } + assert_raise(ArgumentError) { Regexp.new("foo", timeout: -1) } + end; + end + + def test_timeout_memory_leak + assert_no_memory_leak([], "#{<<~"begin;"}", "#{<<~'end;'}", "[Bug #20650]", timeout: 100, rss: true) + regex = Regexp.new("^#{"(a*)" * 10_000}x$", timeout: 0.000001) + str = "a" * 1_000_000 + "x" + + code = proc do + regex =~ str + rescue + end + + 10.times(&code) + begin; + 1_000.times(&code) + end; + end + + def test_match_cache_exponential + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/^(a*)*$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_square + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/^a*b?a*$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_atomic + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/^a*?(?>a*a*)$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_atomic_complex + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/a*(?>a*)ab/ =~ "a" * 1000000 + "b") + end; + end + + def test_match_cache_positive_look_ahead + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 30) + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/^a*?(?=a*a*)$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_positive_look_ahead_complex + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}", timeout: 30) + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_equal(/(?:(?=a*)a)*/ =~ "a" * 1000000, 0) + end; + end + + def test_match_cache_negative_look_ahead + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/^a*?(?!a*a*)$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_positive_look_behind + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/(?<=abc|def)(a|a)*$/ =~ "abc" + "a" * 1000000 + "x") + end; + end + + def test_match_cache_negative_look_behind + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/(?<!x)(a|a)*$/ =~ "a" * 1000000 + "x") + end; + end + + def test_match_cache_with_peek_optimization + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + assert_nil(/a+z/ =~ "a" * 1000000 + "xz") + end; + end + + def test_cache_opcodes_initialize + str = 'test1-test2-test3-test4-test_5' + re = '^([0-9a-zA-Z\-/]*){1,256}$' + 100.times do + assert !Regexp.new(re).match?(str) + end + end + + def test_bug_19273 # [Bug #19273] + pattern = /(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))(?::(?:(?:-?b)|(?:-?(?:1_?(?:0_?)*)?0))){0,3}/ + assert_equal("10:0:0".match(pattern)[0], "10:0:0") + end + + def test_bug_19467 # [Bug #19467] + assert_separately([], "#{<<-"begin;"}\n#{<<-'end;'}") + timeout = #{ EnvUtil.apply_timeout_scale(10).inspect } + begin; + Regexp.timeout = timeout + + assert_nil(/\A.*a.*z\z/ =~ "a" * 1000000 + "y") + end; + end + + def test_bug_19476 # [Bug #19476] + assert_equal("123456789".match(/(?:x?\dx?){2,10}/)[0], "123456789") + assert_equal("123456789".match(/(?:x?\dx?){2,}/)[0], "123456789") + end + + def test_encoding_flags_are_preserved_when_initialized_with_another_regexp + re = Regexp.new("\u2018hello\u2019".encode("UTF-8")) + str = "".encode("US-ASCII") + + assert_nothing_raised do + str.match?(re) + str.match?(Regexp.new(re)) + end + end + + def test_bug_19537 # [Bug #19537] + str = 'aac' + re = '^([ab]{1,3})(a?)*$' + 100.times do + assert !Regexp.new(re).match?(str) + end + end + + def test_bug_20083 # [Bug #20083] + re = /([\s]*ABC)$/i + (1..100).each do |n| + text = "#{"0" * n}ABC" + assert text.match?(re) + end + end + + def test_bug_20098 # [Bug #20098] + assert(/a((.|.)|bc){,4}z/.match? 'abcbcbcbcz') + assert(/a(b+?c*){4,5}z/.match? 'abbbccbbbccbcbcz') + assert(/a(b+?(.|.)){2,3}z/.match? 'abbbcbbbcbbbcz') + assert(/a(b*?(.|.)[bc]){2,5}z/.match? 'abcbbbcbcccbcz') + assert(/^(?:.+){2,4}?b|b/.match? "aaaabaa") + end + + def test_bug_20207 # [Bug #20207] + assert(!'clan'.match?(/(?=.*a)(?!.*n)/)) + end + + def test_bug_20212 # [Bug #20212] + regex = Regexp.new( + /\A((?=.*?[a-z])(?!.*--)[a-z\d]+[a-z\d-]*[a-z\d]+).((?=.*?[a-z])(?!.*--)[a-z\d]+[a-z\d-]*[a-z\d]+).((?=.*?[a-z])(?!.*--)[a-z]+[a-z-]*[a-z]+).((?=.*?[a-z])(?!.*--)[a-z]+[a-z-]*[a-z]+)\Z/x + ) + string = "www.google.com" + 100.times.each { assert(regex.match?(string)) } + end + + def test_bug_20246 # [Bug #20246] + assert_equal '1.2.3', '1.2.3'[/(\d+)(\.\g<1>){2}/] + assert_equal '1.2.3', '1.2.3'[/((?:\d|foo|bar)+)(\.\g<1>){2}/] + end + + def test_linear_time_p + assert_send [Regexp, :linear_time?, /a/] + assert_send [Regexp, :linear_time?, 'a'] + assert_send [Regexp, :linear_time?, 'a', Regexp::IGNORECASE] + assert_not_send [Regexp, :linear_time?, /(a)\1/] + assert_not_send [Regexp, :linear_time?, "(a)\\1"] + + assert_not_send [Regexp, :linear_time?, /(?=(a))/] + assert_not_send [Regexp, :linear_time?, /(?!(a))/] + + assert_raise(TypeError) {Regexp.linear_time?(nil)} + assert_raise(TypeError) {Regexp.linear_time?(Regexp.allocate)} + end + + def test_linear_performance + pre = ->(n) {[Regexp.new("a?" * n + "a" * n), "a" * n]} + assert_linear_performance([10, 29], pre: pre) do |re, s| + re =~ s + end + end + + def test_bug_16145_and_bug_21176_caseinsensitive_small # [Bug#16145] [Bug#21176] + encodings = [Encoding::UTF_8, Encoding::ISO_8859_1] + encodings.each do |enc| + o_acute_lower = "\u00F3".encode(enc) + o_acute_upper = "\u00D3".encode(enc) + assert_match(/[x#{o_acute_lower}]/i, "abc#{o_acute_upper}", "should match o acute case insensitive") + + e_acute_lower = "\u00E9".encode(enc) + e_acute_upper = "\u00C9".encode(enc) + assert_match(/[x#{e_acute_lower}]/i, "CAF#{e_acute_upper}", "should match e acute case insensitive") + end end - def test_optimize_last_anycharstar - s = "1" + " " * 5000000 - assert_nothing_raised { s.match(/(\d) (.*)/) } - assert_equal("1", $1) - assert_equal(" " * 4999999, $2) + def test_too_many_range_repeat + source = '(?:foobar){0,100}' * 100000 + assert_raise(RegexpError) { Regexp.new(source) } + assert_raise(SyntaxError) { eval("/#{source}/") } end - def test_range_greedy - /wo{0,3}?/ =~ "woo" - assert_equal("w", $&) + def test_too_many_null_check + source = '(?:(?:foo)?|(?:bar)?)*' * 100000 + assert_raise(RegexpError) { Regexp.new(source) } + assert_raise(SyntaxError) { eval("/#{source}/") } end end |
