From 31e6add241b429a029e81b5a30a129525c0e1384 Mon Sep 17 00:00:00 2001 From: Jeremy Evans Date: Fri, 17 Feb 2017 14:58:06 -0800 Subject: [PATCH] Add FrozenError as a subclass of RuntimeError FrozenError will be used instead of RuntimeError for exceptions raised when there is an attempt to modify a frozen object. The reason for this change is to differentiate exceptions related to frozen objects from generic exceptions such as those generated by Kernel#raise without an exception class. --- error.c | 25 ++++++++++++++++--------- include/ruby/ruby.h | 1 + object.c | 2 +- test/-ext-/string/test_enc_associate.rb | 4 ++-- test/date/test_date_marshal.rb | 4 ++-- test/dbm/test_dbm.rb | 2 +- test/gdbm/test_gdbm.rb | 2 +- test/pathname/test_pathname.rb | 2 +- test/ruby/test_array.rb | 26 +++++++++++++------------- test/ruby/test_class.rb | 4 ++-- test/ruby/test_enumerator.rb | 4 ++-- test/ruby/test_eval.rb | 2 +- test/ruby/test_hash.rb | 4 ++-- test/ruby/test_lazy_enumerator.rb | 2 +- test/ruby/test_marshal.rb | 2 +- test/ruby/test_module.rb | 18 +++++++++--------- test/ruby/test_object.rb | 12 ++++++------ test/ruby/test_pack.rb | 2 +- test/ruby/test_rand.rb | 4 ++-- test/ruby/test_range.rb | 2 +- test/ruby/test_rubyoptions.rb | 2 +- test/ruby/test_string.rb | 22 +++++++++++----------- test/ruby/test_thread.rb | 4 ++-- test/ruby/test_transcode.rb | 4 ++-- test/ruby/test_variable.rb | 4 ++-- test/stringio/test_stringio.rb | 6 +++--- test/test_delegate.rb | 4 ++-- test/test_set.rb | 4 ++-- 28 files changed, 91 insertions(+), 83 deletions(-) diff --git a/error.c b/error.c index d174464bbf..0e8df52fc9 100644 --- a/error.c +++ b/error.c @@ -773,6 +773,7 @@ VALUE rb_eSignal; VALUE rb_eFatal; VALUE rb_eStandardError; VALUE rb_eRuntimeError; +VALUE rb_eFrozenError; VALUE rb_eTypeError; VALUE rb_eArgError; VALUE rb_eIndexError; @@ -1952,21 +1953,26 @@ syserr_eqq(VALUE self, VALUE exc) * Document-class: RuntimeError * * A generic error class raised when an invalid operation is attempted. + * Kernel.raise will raise a RuntimeError if no Exception class is + * specified. * - * [1, 2, 3].freeze << 4 + * raise "ouch" * * raises the exception: * - * RuntimeError: can't modify frozen Array + * RuntimeError: ouch + */ + +/* + * Document-class: FrozenError * - * Kernel.raise will raise a RuntimeError if no Exception class is - * specified. + * Raised when there is an attempt to modify a frozen object. * - * raise "ouch" + * [1, 2, 3].freeze << 4 * * raises the exception: * - * RuntimeError: ouch + * FrozenError: can't modify frozen Array */ /* @@ -2169,6 +2175,7 @@ Init_Exception(void) rb_define_method(rb_eNoMethodError, "private_call?", nometh_err_private_call_p, 0); rb_eRuntimeError = rb_define_class("RuntimeError", rb_eStandardError); + rb_eFrozenError = rb_define_class("FrozenError", rb_eRuntimeError); rb_eSecurityError = rb_define_class("SecurityError", rb_eException); rb_eNoMemError = rb_define_class("NoMemoryError", rb_eException); rb_eEncodingError = rb_define_class("EncodingError", rb_eStandardError); @@ -2460,7 +2467,7 @@ rb_load_fail(VALUE path, const char *err) void rb_error_frozen(const char *what) { - rb_raise(rb_eRuntimeError, "can't modify frozen %s", what); + rb_raise(rb_eFrozenError, "can't modify frozen %s", what); } void @@ -2473,11 +2480,11 @@ rb_error_frozen_object(VALUE frozen_obj) VALUE path = rb_ary_entry(debug_info, 0); VALUE line = rb_ary_entry(debug_info, 1); - rb_raise(rb_eRuntimeError, "can't modify frozen %"PRIsVALUE", created at %"PRIsVALUE":%"PRIsVALUE, + rb_raise(rb_eFrozenError, "can't modify frozen %"PRIsVALUE", created at %"PRIsVALUE":%"PRIsVALUE, CLASS_OF(frozen_obj), path, line); } else { - rb_raise(rb_eRuntimeError, "can't modify frozen %"PRIsVALUE, + rb_raise(rb_eFrozenError, "can't modify frozen %"PRIsVALUE, CLASS_OF(frozen_obj)); } } diff --git a/include/ruby/ruby.h b/include/ruby/ruby.h index 4aa388849b..93368e60b5 100644 --- a/include/ruby/ruby.h +++ b/include/ruby/ruby.h @@ -1922,6 +1922,7 @@ RUBY_EXTERN VALUE rb_eKeyError; RUBY_EXTERN VALUE rb_eRangeError; RUBY_EXTERN VALUE rb_eIOError; RUBY_EXTERN VALUE rb_eRuntimeError; +RUBY_EXTERN VALUE rb_eFrozenError; RUBY_EXTERN VALUE rb_eSecurityError; RUBY_EXTERN VALUE rb_eSystemCallError; RUBY_EXTERN VALUE rb_eThreadError; diff --git a/object.c b/object.c index 6f4b1239c8..d94ec09191 100644 --- a/object.c +++ b/object.c @@ -1100,7 +1100,7 @@ rb_obj_infect(VALUE obj1, VALUE obj2) * * produces: * - * prog.rb:3:in `<<': can't modify frozen Array (RuntimeError) + * prog.rb:3:in `<<': can't modify frozen Array (FrozenError) * from prog.rb:3 * * Objects of the following classes are always frozen: Integer, diff --git a/test/-ext-/string/test_enc_associate.rb b/test/-ext-/string/test_enc_associate.rb index 4fad8e1cc8..95d1f00cd2 100644 --- a/test/-ext-/string/test_enc_associate.rb +++ b/test/-ext-/string/test_enc_associate.rb @@ -7,8 +7,8 @@ def test_frozen s = Bug::String.new("abc") s.force_encoding(Encoding::US_ASCII) s.freeze - assert_raise(RuntimeError) {s.associate_encoding!(Encoding::US_ASCII)} - assert_raise(RuntimeError) {s.associate_encoding!(Encoding::UTF_8)} + assert_raise(FrozenError) {s.associate_encoding!(Encoding::US_ASCII)} + assert_raise(FrozenError) {s.associate_encoding!(Encoding::UTF_8)} end Encoding.list.select(&:dummy?).each do |enc| diff --git a/test/date/test_date_marshal.rb b/test/date/test_date_marshal.rb index 10e396b71b..99a7239f95 100644 --- a/test/date/test_date_marshal.rb +++ b/test/date/test_date_marshal.rb @@ -30,13 +30,13 @@ def test_marshal a = d.marshal_dump d.freeze assert(d.frozen?) - assert_raise(RuntimeError){d.marshal_load(a)} + assert_raise(FrozenError){d.marshal_load(a)} d = DateTime.now a = d.marshal_dump d.freeze assert(d.frozen?) - assert_raise(RuntimeError){d.marshal_load(a)} + assert_raise(FrozenError){d.marshal_load(a)} end end diff --git a/test/dbm/test_dbm.rb b/test/dbm/test_dbm.rb index 0812b4023f..93198fdea2 100644 --- a/test/dbm/test_dbm.rb +++ b/test/dbm/test_dbm.rb @@ -625,7 +625,7 @@ def test_newdb_open def test_freeze DBM.open("#{@tmproot}/a") {|d| d.freeze - assert_raise(RuntimeError) { d["k"] = "v" } + assert_raise(FrozenError) { d["k"] = "v" } } end end diff --git a/test/gdbm/test_gdbm.rb b/test/gdbm/test_gdbm.rb index aaf0bf7459..054a4e8dde 100644 --- a/test/gdbm/test_gdbm.rb +++ b/test/gdbm/test_gdbm.rb @@ -721,7 +721,7 @@ def test_newdb_open def test_freeze GDBM.open("#{@tmproot}/a.dbm") {|d| d.freeze - assert_raise(RuntimeError) { d["k"] = "v" } + assert_raise(FrozenError) { d["k"] = "v" } } end end diff --git a/test/pathname/test_pathname.rb b/test/pathname/test_pathname.rb index 848d4e4190..ba3e8981cd 100644 --- a/test/pathname/test_pathname.rb +++ b/test/pathname/test_pathname.rb @@ -633,7 +633,7 @@ def test_freeze_and_taint obj = Pathname.new("a") obj.freeze assert_equal(false, obj.tainted?) - assert_raise(RuntimeError) { obj.taint } + assert_raise(FrozenError) { obj.taint } obj = Pathname.new("a") obj.taint diff --git a/test/ruby/test_array.rb b/test/ruby/test_array.rb index 08100142f8..0d99629bc5 100644 --- a/test/ruby/test_array.rb +++ b/test/ruby/test_array.rb @@ -571,7 +571,7 @@ def test_concat assert_equal([4, 5, 4, 5, 4, 5], b) assert_raise(TypeError) { [0].concat(:foo) } - assert_raise(RuntimeError) { [0].freeze.concat(:foo) } + assert_raise(FrozenError) { [0].freeze.concat(:foo) } end def test_count @@ -1260,10 +1260,10 @@ def test_replace fa = a.dup.freeze assert_nothing_raised(RuntimeError) { a.replace(a) } - assert_raise(RuntimeError) { fa.replace(fa) } + assert_raise(FrozenError) { fa.replace(fa) } assert_raise(ArgumentError) { fa.replace() } assert_raise(TypeError) { a.replace(42) } - assert_raise(RuntimeError) { fa.replace(42) } + assert_raise(FrozenError) { fa.replace(42) } end def test_reverse @@ -1491,7 +1491,7 @@ def test_sort_bang_with_freeze o2 = o1.clone ary << o1 << o2 orig = ary.dup - assert_raise(RuntimeError, "frozen during comparison") {ary.sort!} + assert_raise(FrozenError, "frozen during comparison") {ary.sort!} assert_equal(orig, ary, "must not be modified once frozen") end @@ -1723,7 +1723,7 @@ def test_uniq! f = a.dup.freeze assert_raise(ArgumentError) { a.uniq!(1) } assert_raise(ArgumentError) { f.uniq!(1) } - assert_raise(RuntimeError) { f.uniq! } + assert_raise(FrozenError) { f.uniq! } assert_nothing_raised do a = [ {c: "b"}, {c: "r"}, {c: "w"}, {c: "g"}, {c: "g"} ] @@ -1769,7 +1769,7 @@ def test_uniq_bang_with_block def test_uniq_bang_with_freeze ary = [1,2] orig = ary.dup - assert_raise(RuntimeError, "frozen during comparison") { + assert_raise(FrozenError, "frozen during comparison") { ary.uniq! {|v| ary.freeze; 1} } assert_equal(orig, ary, "must not be modified once frozen") @@ -2019,7 +2019,7 @@ def test_aset_error assert_raise(ArgumentError) { [0][0, 0, 0] = 0 } assert_raise(ArgumentError) { [0].freeze[0, 0, 0] = 0 } assert_raise(TypeError) { [0][:foo] = 0 } - assert_raise(RuntimeError) { [0].freeze[:foo] = 0 } + assert_raise(FrozenError) { [0].freeze[:foo] = 0 } end def test_first2 @@ -2044,8 +2044,8 @@ def test_shift2 end def test_unshift_error - assert_raise(RuntimeError) { [].freeze.unshift('cat') } - assert_raise(RuntimeError) { [].freeze.unshift() } + assert_raise(FrozenError) { [].freeze.unshift('cat') } + assert_raise(FrozenError) { [].freeze.unshift() } end def test_aref @@ -2104,7 +2104,7 @@ def test_insert assert_raise(ArgumentError) { a.insert } assert_equal([0, 1, 2], a.insert(-1, 2)) assert_equal([0, 1, 3, 2], a.insert(-2, 3)) - assert_raise(RuntimeError) { [0].freeze.insert(0)} + assert_raise(FrozenError) { [0].freeze.insert(0)} assert_raise(ArgumentError) { [0].freeze.insert } end @@ -2285,8 +2285,8 @@ def test_flatten_error assert_raise(ArgumentError) { a.flatten!(1, 2) } assert_raise(TypeError) { a.flatten!(:foo) } assert_raise(ArgumentError) { f.flatten!(1, 2) } - assert_raise(RuntimeError) { f.flatten! } - assert_raise(RuntimeError) { f.flatten!(:foo) } + assert_raise(FrozenError) { f.flatten! } + assert_raise(FrozenError) { f.flatten!(:foo) } end def test_shuffle @@ -2621,7 +2621,7 @@ def test_rotate! assert_equal([], a.rotate!(13)) assert_equal([], a.rotate!(-13)) a = [].freeze - assert_raise_with_message(RuntimeError, /can\'t modify frozen/) {a.rotate!} + assert_raise_with_message(FrozenError, /can\'t modify frozen/) {a.rotate!} a = [1,2,3] assert_raise(ArgumentError) { a.rotate!(1, 1) } end diff --git a/test/ruby/test_class.rb b/test/ruby/test_class.rb index 072729d4e5..4289b84702 100644 --- a/test/ruby/test_class.rb +++ b/test/ruby/test_class.rb @@ -427,14 +427,14 @@ def test_singleton_class_of_frozen_object obj = Object.new c = obj.singleton_class obj.freeze - assert_raise_with_message(RuntimeError, /frozen object/) { + assert_raise_with_message(FrozenError, /frozen object/) { c.class_eval {def f; end} } end def test_singleton_class_message c = Class.new.freeze - assert_raise_with_message(RuntimeError, /frozen Class/) { + assert_raise_with_message(FrozenError, /frozen Class/) { def c.f; end } end diff --git a/test/ruby/test_enumerator.rb b/test/ruby/test_enumerator.rb index bd6b7fa669..cacf98d200 100644 --- a/test/ruby/test_enumerator.rb +++ b/test/ruby/test_enumerator.rb @@ -79,7 +79,7 @@ def test_initialize enum = @obj.to_enum assert_raise(NoMethodError) { enum.each {} } enum.freeze - assert_raise(RuntimeError) { + assert_raise(FrozenError) { capture_io do # warning: Enumerator.new without a block is deprecated; use Object#to_enum enum.__send__(:initialize, @obj, :foo) @@ -440,7 +440,7 @@ def test_generator assert_equal([1, 2, 3], a) g.freeze - assert_raise(RuntimeError) { + assert_raise(FrozenError) { g.__send__ :initialize, proc { |y| y << 4 << 5 } } diff --git a/test/ruby/test_eval.rb b/test/ruby/test_eval.rb index fc08d61ef5..0242be2714 100644 --- a/test/ruby/test_eval.rb +++ b/test/ruby/test_eval.rb @@ -506,7 +506,7 @@ def test_eval_location_fstring def test_fstring_instance_eval bug = "[ruby-core:78116] [Bug #12930]".freeze assert_same bug, (bug.instance_eval {self}) - assert_raise(RuntimeError) { + assert_raise(FrozenError) { bug.instance_eval {@ivar = true} } end diff --git a/test/ruby/test_hash.rb b/test/ruby/test_hash.rb index 040d25e144..7f4066d141 100644 --- a/test/ruby/test_hash.rb +++ b/test/ruby/test_hash.rb @@ -965,8 +965,8 @@ def test_replace2 assert_raise(TypeError) { h2.replace(1) } h2.freeze assert_raise(ArgumentError) { h2.replace() } - assert_raise(RuntimeError) { h2.replace(h1) } - assert_raise(RuntimeError) { h2.replace(42) } + assert_raise(FrozenError) { h2.replace(h1) } + assert_raise(FrozenError) { h2.replace(42) } end def test_size2 diff --git a/test/ruby/test_lazy_enumerator.rb b/test/ruby/test_lazy_enumerator.rb index 792a95bb7e..8705f97cfd 100644 --- a/test/ruby/test_lazy_enumerator.rb +++ b/test/ruby/test_lazy_enumerator.rb @@ -25,7 +25,7 @@ def test_initialize a = [1, 2, 3].lazy a.freeze - assert_raise(RuntimeError) { + assert_raise(FrozenError) { a.__send__ :initialize, [4, 5], &->(y, *v) { y << yield(*v) } } end diff --git a/test/ruby/test_marshal.rb b/test/ruby/test_marshal.rb index bfc3f6df25..744e1d91f5 100644 --- a/test/ruby/test_marshal.rb +++ b/test/ruby/test_marshal.rb @@ -568,7 +568,7 @@ def test_marshal_dump_ivar s.instance_variable_set(:@t, 42) t = Bug8276.new(s) s = Marshal.dump(t) - assert_raise(RuntimeError) {Marshal.load(s)} + assert_raise(FrozenError) {Marshal.load(s)} end def test_marshal_load_ivar diff --git a/test/ruby/test_module.rb b/test/ruby/test_module.rb index d1c5d53822..d4c95b18e1 100644 --- a/test/ruby/test_module.rb +++ b/test/ruby/test_module.rb @@ -636,15 +636,15 @@ def self.baz; end def bar; end end m.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do m.module_eval do def foo; end end end - assert_raise(RuntimeError) do + assert_raise(FrozenError) do m.__send__ :private, :bar end - assert_raise(RuntimeError) do + assert_raise(FrozenError) do m.private_class_method :baz end end @@ -949,7 +949,7 @@ def test_singleton_constants def test_frozen_module m = Module.new m.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do m.instance_eval { undef_method(:foo) } end end @@ -957,7 +957,7 @@ def test_frozen_module def test_frozen_class c = Class.new c.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do c.instance_eval { undef_method(:foo) } end end @@ -967,7 +967,7 @@ def test_frozen_singleton_class o = klass.new c = class << o; self; end c.freeze - assert_raise_with_message(RuntimeError, /frozen/) do + assert_raise_with_message(FrozenError, /frozen/) do c.instance_eval { undef_method(:foo) } end klass.class_eval do @@ -2061,17 +2061,17 @@ def test_frozen_visibility bug11532 = '[ruby-core:70828] [Bug #11532]' c = Class.new {const_set(:A, 1)}.freeze - assert_raise_with_message(RuntimeError, /frozen class/, bug11532) { + assert_raise_with_message(FrozenError, /frozen class/, bug11532) { c.class_eval {private_constant :A} } c = Class.new {const_set(:A, 1); private_constant :A}.freeze - assert_raise_with_message(RuntimeError, /frozen class/, bug11532) { + assert_raise_with_message(FrozenError, /frozen class/, bug11532) { c.class_eval {public_constant :A} } c = Class.new {const_set(:A, 1)}.freeze - assert_raise_with_message(RuntimeError, /frozen class/, bug11532) { + assert_raise_with_message(FrozenError, /frozen class/, bug11532) { c.class_eval {deprecate_constant :A} } end diff --git a/test/ruby/test_object.rb b/test/ruby/test_object.rb index 7c1b98f76e..53ecaa3566 100644 --- a/test/ruby/test_object.rb +++ b/test/ruby/test_object.rb @@ -85,12 +85,12 @@ def test_kind_of def test_taint_frozen_obj o = Object.new o.freeze - assert_raise(RuntimeError) { o.taint } + assert_raise(FrozenError) { o.taint } o = Object.new o.taint o.freeze - assert_raise(RuntimeError) { o.untaint } + assert_raise(FrozenError) { o.untaint } end def test_freeze_immediate @@ -109,7 +109,7 @@ def test_frozen_error_message attr_accessor :foo } obj = klass.new.freeze - assert_raise_with_message(RuntimeError, /#{name}/) { + assert_raise_with_message(FrozenError, /#{name}/) { obj.foo = 1 } end @@ -385,7 +385,7 @@ def object_id; 1; end def test_remove_method c = Class.new c.freeze - assert_raise(RuntimeError) do + assert_raise(FrozenError) do c.instance_eval { remove_method(:foo) } end @@ -793,7 +793,7 @@ class ToS\u{3042} class<