Skip to content

Commit f9ae7a9

Browse files
committed
[GR-18163] Fix non-String argument conversion in Regexp.new
PullRequest: truffleruby/3462
2 parents 4e6b7fd + 338ecc7 commit f9ae7a9

File tree

7 files changed

+62
-27
lines changed

7 files changed

+62
-27
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@ Compatibility:
2525
* Fix exception message when there are missing or extra keyword arguments - it contains all the missing/extra keywords now (#1522, @andrykonchin).
2626
* Always terminate native strings with enough `\0` bytes (#2704, @eregon).
2727
* Support `#dup` and `#clone` on foreign strings (@eregon).
28+
* Fix `Regexp.new` to coerce non-String arguments (#2705, @andrykonchin).
2829

2930
Performance:
3031

spec/ruby/core/regexp/compile_spec.rb

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,7 @@
1313
describe "Regexp.compile given a Regexp" do
1414
it_behaves_like :regexp_new_regexp, :compile
1515
end
16+
17+
describe "Regexp.new given a non-String/Regexp" do
18+
it_behaves_like :regexp_new_non_string_or_regexp, :compile
19+
end

spec/ruby/core/regexp/new_spec.rb

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -11,17 +11,9 @@
1111

1212
describe "Regexp.new given a Regexp" do
1313
it_behaves_like :regexp_new_regexp, :new
14-
it_behaves_like :regexp_new_string_binary, :compile
14+
it_behaves_like :regexp_new_string_binary, :new
1515
end
1616

17-
describe "Regexp.new given an Integer" do
18-
it "raises a TypeError" do
19-
-> { Regexp.new(1) }.should raise_error(TypeError)
20-
end
21-
end
22-
23-
describe "Regexp.new given a Float" do
24-
it "raises a TypeError" do
25-
-> { Regexp.new(1.0) }.should raise_error(TypeError)
26-
end
27-
end
17+
describe "Regexp.new given a non-String/Regexp" do
18+
it_behaves_like :regexp_new_non_string_or_regexp, :new
19+
end

spec/ruby/core/regexp/shared/new.rb

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,32 @@ class RegexpSpecsSubclassTwo < Regexp; end
2424
end
2525
end
2626

27+
describe :regexp_new_non_string_or_regexp, shared: true do
28+
it "calls #to_str method for non-String/Regexp argument" do
29+
obj = Object.new
30+
def obj.to_str() "a" end
31+
32+
Regexp.send(@method, obj).should == /a/
33+
end
34+
35+
it "raises TypeError if there is no #to_str method for non-String/Regexp argument" do
36+
obj = Object.new
37+
-> { Regexp.send(@method, obj) }.should raise_error(TypeError, "no implicit conversion of Object into String")
38+
39+
-> { Regexp.send(@method, 1) }.should raise_error(TypeError, "no implicit conversion of Integer into String")
40+
-> { Regexp.send(@method, 1.0) }.should raise_error(TypeError, "no implicit conversion of Float into String")
41+
-> { Regexp.send(@method, :symbol) }.should raise_error(TypeError, "no implicit conversion of Symbol into String")
42+
-> { Regexp.send(@method, []) }.should raise_error(TypeError, "no implicit conversion of Array into String")
43+
end
44+
45+
it "raises TypeError if #to_str returns non-String value" do
46+
obj = Object.new
47+
def obj.to_str() [] end
48+
49+
-> { Regexp.send(@method, obj) }.should raise_error(TypeError, /can't convert Object to String/)
50+
end
51+
end
52+
2753
describe :regexp_new_string, shared: true do
2854
it "uses the String argument as an unescaped literal to construct a Regexp object" do
2955
Regexp.send(@method, "^hi{2,3}fo.o$").should == /^hi{2,3}fo.o$/
@@ -97,6 +123,16 @@ class RegexpSpecsSubclassTwo < Regexp; end
97123
(r.options & Regexp::EXTENDED).should_not == 0
98124
end
99125

126+
it "does not try to convert the second argument to Integer with #to_int method call" do
127+
ScratchPad.clear
128+
obj = Object.new
129+
def obj.to_int() ScratchPad.record(:called) end
130+
131+
Regexp.send(@method, "Hi", obj)
132+
133+
ScratchPad.recorded.should == nil
134+
end
135+
100136
ruby_version_is ""..."3.2" do
101137
it "treats any non-Integer, non-nil, non-false second argument as IGNORECASE" do
102138
r = Regexp.send(@method, 'Hi', Object.new)
Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
fails:Regexp.compile given a String ignores the third argument if it is 'e' or 'euc' (case-insensitive)
2-
fails:Regexp.compile given a String ignores the third argument if it is 's' or 'sjis' (case-insensitive)
3-
fails:Regexp.compile given a String ignores the third argument if it is 'u' or 'utf8' (case-insensitive)
4-
fails:Regexp.compile given a Regexp does not honour options given as additional arguments
51
fails(immutable regexp):Regexp.compile works by default for subclasses with overridden #initialize

spec/tags/core/regexp/new_tags.txt

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1 @@
1-
fails:Regexp.new given a String ignores the third argument if it is 'e' or 'euc' (case-insensitive)
2-
fails:Regexp.new given a String ignores the third argument if it is 's' or 'sjis' (case-insensitive)
3-
fails:Regexp.new given a String ignores the third argument if it is 'u' or 'utf8' (case-insensitive)
4-
fails:Regexp.new given a Regexp does not honour options given as additional arguments
51
fails(immutable regexp):Regexp.new works by default for subclasses with overridden #initialize

src/main/ruby/truffleruby/core/regexp.rb

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -130,22 +130,32 @@ def self.union(*patterns)
130130
end
131131
Truffle::Graal.always_split(method(:union))
132132

133-
def self.new(pattern, opts=nil, lang=nil)
133+
def self.new(pattern, opts=undefined, encoding=nil)
134134
if Primitive.object_kind_of?(pattern, Regexp)
135+
warn 'flags ignored' unless Primitive.undefined?(opts)
135136
opts = pattern.options
136137
pattern = pattern.source
137-
elsif Primitive.object_kind_of?(pattern, Integer) or Primitive.object_kind_of?(pattern, Float)
138-
raise TypeError, "can't convert #{pattern.class} into String"
139-
elsif Primitive.object_kind_of?(opts, Integer)
138+
else
139+
pattern = Truffle::Type.rb_convert_type pattern, String, :to_str
140+
end
141+
142+
if Primitive.object_kind_of?(opts, Integer)
140143
opts = opts & (OPTION_MASK | KCODE_MASK) if opts > 0
141-
elsif opts
144+
elsif !Primitive.undefined?(opts) && opts
142145
opts = IGNORECASE
143146
else
144147
opts = 0
145148
end
146149

147-
code = lang[0] if lang
148-
opts |= NOENCODING if code == ?n or code == ?N
150+
if encoding
151+
encoding = Truffle::Type.rb_convert_type encoding, String, :to_str
152+
code = encoding[0]
153+
if code == ?n or code == ?N
154+
opts |= NOENCODING
155+
else
156+
warn "encoding option is ignored - #{encoding}"
157+
end
158+
end
149159

150160
Primitive.regexp_compile pattern, opts # may be overridden by subclasses
151161
end

0 commit comments

Comments
 (0)