Skip to content

Commit 89bb38d

Browse files
committed
Freeze all instances of Regexp, except subclass instances
* Instances of a subclass of Regexp are not frozen for compatibility. * [Feature #8948] * Use a less confusing example in test_regexp2, the ivar was named @encoding but had no effect on Regexp#encoding.
1 parent b003bdb commit 89bb38d

6 files changed

Lines changed: 127 additions & 33 deletions

File tree

core/marshal/dump_spec.rb

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -473,14 +473,26 @@ def _dump(level)
473473
Marshal.dump(//im).should == "\x04\bI/\x00\x05\x06:\x06EF"
474474
end
475475

476-
it "dumps a Regexp with instance variables" do
477-
o = Regexp.new("")
476+
it "dumps a Regexp subclass with instance variables" do
477+
o = UserRegexp.new("")
478478
o.instance_variable_set(:@ivar, :ivar)
479-
Marshal.dump(o).should == "\x04\bI/\x00\x00\a:\x06EF:\n@ivar:\tivar"
479+
Marshal.dump(o).should == "\x04\bIC:\x0FUserRegexp/\x00\x00\a:\x06EF:\n@ivar:\tivar"
480480
end
481481

482-
it "dumps an extended Regexp" do
483-
Marshal.dump(Regexp.new("").extend(Meths)).should == "\x04\bIe:\nMeths/\x00\x00\x06:\x06EF"
482+
it "dumps an extended Regexp subclass" do
483+
Marshal.dump(UserRegexp.new("").extend(Meths)).should == "\x04\bIe:\nMethsC:\x0FUserRegexp/\x00\x00\x06:\x06EF"
484+
end
485+
486+
ruby_version_is ""..."4.1" do
487+
it "dumps a Regexp with instance variables" do
488+
o = Regexp.new("")
489+
o.instance_variable_set(:@ivar, :ivar)
490+
Marshal.dump(o).should == "\x04\bI/\x00\x00\a:\x06EF:\n@ivar:\tivar"
491+
end
492+
493+
it "dumps an extended Regexp" do
494+
Marshal.dump(Regexp.new("").extend(Meths)).should == "\x04\bIe:\nMeths/\x00\x00\x06:\x06EF"
495+
end
484496
end
485497

486498
it "dumps a Regexp subclass" do

core/marshal/shared/load.rb

Lines changed: 43 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -901,14 +901,52 @@ def io.binmode; raise "binmode"; end
901901
end
902902

903903
describe "for a Regexp" do
904-
it "loads an extended Regexp" do
905-
obj = /[a-z]/.dup.extend(Meths, MethsMore)
906-
new_obj = Marshal.send(@method, "\004\be:\nMethse:\016MethsMore/\n[a-z]\000")
904+
ruby_version_is "4.1" do
905+
it "raises FrozenError for an extended Regexp" do
906+
-> {
907+
Marshal.send(@method, "\004\be:\nMethse:\016MethsMore/\n[a-z]\000")
908+
}.should raise_error(FrozenError)
909+
end
910+
911+
it "raises FrozenError when regexp has instance variables" do
912+
-> {
913+
Marshal.send(@method, "\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/")
914+
}.should raise_error(FrozenError)
915+
end
916+
end
917+
918+
ruby_version_is ""..."4.1" do
919+
it "loads an extended Regexp" do
920+
obj = /[a-z]/.dup.extend(Meths, MethsMore)
921+
new_obj = Marshal.send(@method, "\004\be:\nMethse:\016MethsMore/\n[a-z]\000")
922+
923+
new_obj.should == obj
924+
new_obj_metaclass_ancestors = class << new_obj; ancestors; end
925+
new_obj_metaclass_ancestors[@num_self_class, 3].should ==
926+
[Meths, MethsMore, Regexp]
927+
end
928+
929+
it "restore the regexp instance variables" do
930+
obj = Regexp.new("hello")
931+
obj.instance_variable_set(:@regexp_ivar, [42])
932+
933+
new_obj = Marshal.send(@method, "\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/")
934+
new_obj.instance_variables.should == [:@regexp_ivar]
935+
new_obj.instance_variable_get(:@regexp_ivar).should == [42]
936+
end
937+
end
938+
939+
it "loads a Regexp subclass instance variables" do
940+
obj = UserRegexp.new('abc')
941+
obj.instance_variable_set(:@noise, 'much')
942+
943+
new_obj = Marshal.send(@method, Marshal.dump(obj))
907944

908945
new_obj.should == obj
946+
new_obj.instance_variable_get(:@noise).should == 'much'
909947
new_obj_metaclass_ancestors = class << new_obj; ancestors; end
910-
new_obj_metaclass_ancestors[@num_self_class, 3].should ==
911-
[Meths, MethsMore, Regexp]
948+
new_obj_metaclass_ancestors[@num_self_class, 2].should ==
949+
[UserRegexp, Regexp]
912950
end
913951

914952
it "loads a Regexp subclass instance variables when it is extended with a module" do
@@ -924,15 +962,6 @@ def io.binmode; raise "binmode"; end
924962
[Meths, UserRegexp, Regexp]
925963
end
926964

927-
it "restore the regexp instance variables" do
928-
obj = Regexp.new("hello")
929-
obj.instance_variable_set(:@regexp_ivar, [42])
930-
931-
new_obj = Marshal.send(@method, "\x04\bI/\nhello\x00\a:\x06EF:\x11@regexp_ivar[\x06i/")
932-
new_obj.instance_variables.should == [:@regexp_ivar]
933-
new_obj.instance_variable_get(:@regexp_ivar).should == [42]
934-
end
935-
936965
it "preserves Regexp encoding" do
937966
source_object = Regexp.new("a".encode("utf-32le"))
938967
regexp = Marshal.send(@method, Marshal.dump(source_object))

core/regexp/initialize_spec.rb

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,21 @@
99
-> { //.send(:initialize, "") }.should raise_error(FrozenError)
1010
end
1111

12-
it "raises a TypeError on an initialized non-literal Regexp" do
13-
-> { Regexp.new("").send(:initialize, "") }.should raise_error(TypeError)
12+
ruby_version_is "4.1" do
13+
it "raises a FrozenError on an initialized non-literal Regexp" do
14+
regexp = Regexp.new("")
15+
-> { regexp.send(:initialize, "") }.should raise_error(FrozenError)
16+
end
17+
end
18+
19+
ruby_version_is ""..."4.1" do
20+
it "raises a TypeError on an initialized non-literal Regexp" do
21+
-> { Regexp.new("").send(:initialize, "") }.should raise_error(TypeError)
22+
end
23+
end
24+
25+
it "raises a TypeError on an initialized non-literal Regexp subclass" do
26+
r = Class.new(Regexp).new("")
27+
-> { r.send(:initialize, "") }.should raise_error(TypeError)
1428
end
1529
end

core/regexp/shared/new.rb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,12 @@
55
Regexp.send(@method, '').is_a?(Regexp).should == true
66
end
77

8+
ruby_version_is "4.1" do
9+
it "is frozen" do
10+
Regexp.send(@method, '').should.frozen?
11+
end
12+
end
13+
814
it "works by default for subclasses with overridden #initialize" do
915
class RegexpSpecsSubclass < Regexp
1016
def initialize(*args)

core/string/match_spec.rb

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -137,9 +137,17 @@ def obj.method_missing(*args) "." end
137137
end
138138

139139
it "calls match on the regular expression" do
140-
regexp = /./.dup
141-
regexp.should_receive(:match).and_return(:foo)
142-
'hello'.match(regexp).should == :foo
140+
# Can't use regexp.should_receive(:match).and_return(:foo) since regexps are frozen
141+
ScratchPad.clear
142+
regexp = Class.new(Regexp) {
143+
def match(*args)
144+
ScratchPad.record [:match, *args]
145+
super(*args)
146+
end
147+
}.new('.')
148+
149+
'hello'.match(regexp)
150+
ScratchPad.recorded.should == [:match, 'hello']
143151
end
144152
end
145153

optional/capi/encoding_spec.rb

Lines changed: 34 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -489,12 +489,20 @@
489489
@s.rb_enc_copy("string", @obj).encoding.should == Encoding::US_ASCII
490490
end
491491

492-
it "raises a RuntimeError if the second argument is a Symbol" do
492+
it "raises a RuntimeError if the first argument is a Symbol" do
493493
-> { @s.rb_enc_copy(:symbol, @obj) }.should raise_error(RuntimeError)
494494
end
495495

496-
it "sets the encoding of a Regexp to that of the second argument" do
497-
@s.rb_enc_copy(/regexp/.dup, @obj).encoding.should == Encoding::US_ASCII
496+
ruby_version_is "4.1" do
497+
it "raises a FrozenError if the first argument is a Regexp" do
498+
-> { @s.rb_enc_copy(/regexp/.dup, @obj) }.should raise_error(FrozenError)
499+
end
500+
end
501+
502+
ruby_version_is ""..."4.1" do
503+
it "sets the encoding of a Regexp to that of the second argument" do
504+
@s.rb_enc_copy(/regexp/.dup, @obj).encoding.should == Encoding::US_ASCII
505+
end
498506
end
499507
end
500508

@@ -544,8 +552,16 @@
544552
-> { @s.rb_enc_associate(:symbol, "US-ASCII") }.should raise_error(RuntimeError)
545553
end
546554

547-
it "sets the encoding of a Regexp to the encoding" do
548-
@s.rb_enc_associate(/regexp/.dup, "BINARY").encoding.should == Encoding::BINARY
555+
ruby_version_is "4.1" do
556+
it "raises a FrozenError if the argument is a Regexp" do
557+
-> { @s.rb_enc_associate(/regexp/.dup, "BINARY") }.should raise_error(FrozenError)
558+
end
559+
end
560+
561+
ruby_version_is ""..."4.1" do
562+
it "sets the encoding of a Regexp to the encoding" do
563+
@s.rb_enc_associate(/regexp/.dup, "BINARY").encoding.should == Encoding::BINARY
564+
end
549565
end
550566

551567
it "sets the encoding of a String to a default when the encoding is NULL" do
@@ -560,10 +576,19 @@
560576
enc.should == Encoding::BINARY
561577
end
562578

563-
it "sets the encoding of a Regexp to the encoding" do
564-
index = @s.rb_enc_find_index("UTF-8")
565-
enc = @s.rb_enc_associate_index(/regexp/.dup, index).encoding
566-
enc.should == Encoding::UTF_8
579+
ruby_version_is "4.1" do
580+
it "raises a FrozenError if the argument is a Regexp" do
581+
index = @s.rb_enc_find_index("UTF-8")
582+
-> { @s.rb_enc_associate_index(/regexp/.dup, index) }.should raise_error(FrozenError)
583+
end
584+
end
585+
586+
ruby_version_is ""..."4.1" do
587+
it "sets the encoding of a Regexp to the encoding" do
588+
index = @s.rb_enc_find_index("UTF-8")
589+
enc = @s.rb_enc_associate_index(/regexp/.dup, index).encoding
590+
enc.should == Encoding::UTF_8
591+
end
567592
end
568593

569594
it "sets the encoding of a Symbol to the encoding" do

0 commit comments

Comments
 (0)