From 25ab3be6c5bef071fcd98bc542f876b2120a37bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janosch=20Mu=CC=88ller?= Date: Thu, 19 Jan 2023 22:16:41 +0100 Subject: [PATCH 1/5] Make MatchLength#to_re return a Regexp --- lib/regexp_parser/expression/methods/match_length.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/regexp_parser/expression/methods/match_length.rb b/lib/regexp_parser/expression/methods/match_length.rb index 4cf2cb7..597bcdf 100644 --- a/lib/regexp_parser/expression/methods/match_length.rb +++ b/lib/regexp_parser/expression/methods/match_length.rb @@ -63,7 +63,7 @@ def inspect end def to_re - "(?:#{reify.call}){#{min_rep},#{max_rep unless max_rep == Float::INFINITY}}" + /(?:#{reify.call}){#{min_rep},#{max_rep unless max_rep == Float::INFINITY}}/ end private From 89c64ae2350d9905303524b3e680739084e3da84 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janosch=20Mu=CC=88ller?= Date: Thu, 19 Jan 2023 22:19:25 +0100 Subject: [PATCH 2/5] Check for Regexp#match? only once --- lib/regexp_parser/expression/methods/match_length.rb | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/regexp_parser/expression/methods/match_length.rb b/lib/regexp_parser/expression/methods/match_length.rb index 597bcdf..cf73b56 100644 --- a/lib/regexp_parser/expression/methods/match_length.rb +++ b/lib/regexp_parser/expression/methods/match_length.rb @@ -70,9 +70,13 @@ def to_re attr_accessor :base_min, :base_max, :min_rep, :max_rep, :exp_class, :reify - def test_regexp - @test_regexp ||= Regexp.new("^#{to_re}$").tap do |regexp| - regexp.respond_to?(:match?) || def regexp.match?(str); !!match(str) end + if Regexp.method_defined?(:match?) # ruby >= 2.4 + def test_regexp + @test_regexp ||= /^#{to_re}$/ + end + else + def test_regexp + @test_regexp ||= /^#{to_re}$/.tap { |r| def r.match?(s); !!r.match(s) end } end end end From 04d49cb33907de8f3b78abc10e227fa4ac463e9d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janosch=20Mu=CC=88ller?= Date: Thu, 19 Jan 2023 22:20:09 +0100 Subject: [PATCH 3/5] Fix #clone for recursive subexp call --- CHANGELOG.md | 5 ++++ .../expression/classes/backreference.rb | 14 ++++++++++- spec/expression/clone_spec.rb | 25 +++++++++++++++++++ 3 files changed, 43 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 65d7d11..60d6c73 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,11 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +### Fixed + +- fixed `SystemStackError` when cloning recursive subexpression calls + * e.g. `Regexp::Parser.parse(/a|b\g<0>/).dup` + ## [2.6.1] - 2022-11-16 - [Janosch Müller](mailto:janosch84@gmail.com) ### Fixed diff --git a/lib/regexp_parser/expression/classes/backreference.rb b/lib/regexp_parser/expression/classes/backreference.rb index cee7386..8406f9c 100644 --- a/lib/regexp_parser/expression/classes/backreference.rb +++ b/lib/regexp_parser/expression/classes/backreference.rb @@ -5,7 +5,19 @@ class Base < Regexp::Expression::Base attr_accessor :referenced_expression def initialize_copy(orig) - self.referenced_expression = orig.referenced_expression.dup + exp_id = [self.class, self.starts_at] + + # prevent infinite recursion for recursive subexp calls + copied = @@copied ||= {} + self.referenced_expression = + if copied[exp_id] + orig.referenced_expression + else + copied[exp_id] = true + orig.referenced_expression.dup + end + copied.clear + super end end diff --git a/spec/expression/clone_spec.rb b/spec/expression/clone_spec.rb index 8f150e7..216518c 100644 --- a/spec/expression/clone_spec.rb +++ b/spec/expression/clone_spec.rb @@ -105,6 +105,31 @@ expect { root_1.clone }.not_to(change { root_1.referenced_expression.object_id }) end + specify('Backreference::Base#clone works for recursive subexp calls') do + root = RP.parse('a|b\g<0>') + copy = root.clone + + expect(copy.to_s).to eq root.to_s + + root_call = root.dig(0, 1, 1) + copy_call = copy.dig(0, 1, 1) + + expect(root).to eq copy + expect(root.object_id).not_to eq copy.object_id + + expect(root_call).to eq copy_call + expect(root_call.object_id).not_to eq copy_call.object_id + + expect(root_call.referenced_expression).not_to be_nil + expect(root_call.referenced_expression.object_id).to eq root.object_id + + expect(copy_call.referenced_expression).not_to be_nil + + # Mapping the reference to the cloned referenced_expression would + # probably require a context or 2-way bindings in the tree. Maybe later ... + # expect(copy_call.referenced_expression.object_id).to eq copy.object_id + end + specify('Sequence#clone') do root = RP.parse(/(a|b)/) copy = root.clone From acc4755b2a34538ebe82622e237e941d2d3fbc1a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janosch=20Mu=CC=88ller?= Date: Thu, 19 Jan 2023 22:22:06 +0100 Subject: [PATCH 4/5] Fix typo --- lib/regexp_parser/expression/methods/match_length.rb | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/regexp_parser/expression/methods/match_length.rb b/lib/regexp_parser/expression/methods/match_length.rb index cf73b56..a30b644 100644 --- a/lib/regexp_parser/expression/methods/match_length.rb +++ b/lib/regexp_parser/expression/methods/match_length.rb @@ -76,7 +76,7 @@ def test_regexp end else def test_regexp - @test_regexp ||= /^#{to_re}$/.tap { |r| def r.match?(s); !!r.match(s) end } + @test_regexp ||= /^#{to_re}$/.tap { |r| def r.match?(s); !!match(s) end } end end end From 1963e45b066ed8f438653669a1e0774f4009bf13 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Janosch=20Mu=CC=88ller?= Date: Thu, 19 Jan 2023 22:25:36 +0100 Subject: [PATCH 5/5] Release v2.6.2 --- CHANGELOG.md | 2 ++ lib/regexp_parser/version.rb | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 60d6c73..cba6481 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [2.6.2] - 2023-01-19 - [Janosch Müller](mailto:janosch84@gmail.com) + ### Fixed - fixed `SystemStackError` when cloning recursive subexpression calls diff --git a/lib/regexp_parser/version.rb b/lib/regexp_parser/version.rb index 243d245..988a20f 100644 --- a/lib/regexp_parser/version.rb +++ b/lib/regexp_parser/version.rb @@ -1,5 +1,5 @@ class Regexp class Parser - VERSION = '2.6.1' + VERSION = '2.6.2' end end