mirror of
https://github.com/ruby/ruby.git
synced 2022-11-09 12:17:21 -05:00
Add FrozenError#receiver
Similar to NameError#receiver, this returns the object on which the modification was attempted. This is useful as it can pinpoint exactly what is frozen. In many cases when a FrozenError is raised, you cannot determine from the context which object is frozen that you attempted to modify. Users of the current rb_error_frozen C function will have to switch to using rb_error_frozen_object or the new rb_frozen_error_raise in order to set the receiver of the FrozenError. To allow the receiver to be set from Ruby, support an optional second argument to FrozenError#initialize. Implements [Feature #15751]
This commit is contained in:
parent
897901283c
commit
39eadca76b
7 changed files with 121 additions and 7 deletions
9
NEWS
9
NEWS
|
@ -66,6 +66,15 @@ Enumerator::
|
||||||
can be directly passed to another method as a block
|
can be directly passed to another method as a block
|
||||||
argument. [Feature #15618]
|
argument. [Feature #15618]
|
||||||
|
|
||||||
|
FrozenError::
|
||||||
|
|
||||||
|
New method::
|
||||||
|
|
||||||
|
* Added FrozenError#receiver to return the frozen object that
|
||||||
|
modification was attempted on. To set this object when raising
|
||||||
|
FrozenError in Ruby code, pass it as the second argument to
|
||||||
|
FrozenError.new.
|
||||||
|
|
||||||
Integer::
|
Integer::
|
||||||
|
|
||||||
Modified method::
|
Modified method::
|
||||||
|
|
47
error.c
47
error.c
|
@ -1396,6 +1396,32 @@ exit_success_p(VALUE exc)
|
||||||
return Qfalse;
|
return Qfalse;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/*
|
||||||
|
* call-seq:
|
||||||
|
* FrozenError.new(msg=nil, receiver=nil) -> name_error
|
||||||
|
*
|
||||||
|
* Construct a new FrozenError exception. If given the <i>receiver</i>
|
||||||
|
* parameter may subsequently be examined using the FrozenError#receiver
|
||||||
|
* method.
|
||||||
|
*
|
||||||
|
* a = [].freeze
|
||||||
|
* raise FrozenError.new("can't modify frozen array", a)
|
||||||
|
*/
|
||||||
|
|
||||||
|
static VALUE
|
||||||
|
frozen_err_initialize(int argc, VALUE *argv, VALUE self)
|
||||||
|
{
|
||||||
|
VALUE mesg, recv;
|
||||||
|
|
||||||
|
argc = rb_scan_args(argc, argv, "02", &mesg, &recv);
|
||||||
|
if (argc > 1) {
|
||||||
|
argc--;
|
||||||
|
rb_ivar_set(self, id_recv, recv);
|
||||||
|
}
|
||||||
|
rb_call_super(argc, argv);
|
||||||
|
return self;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
rb_name_error(ID id, const char *fmt, ...)
|
rb_name_error(ID id, const char *fmt, ...)
|
||||||
{
|
{
|
||||||
|
@ -2483,6 +2509,8 @@ Init_Exception(void)
|
||||||
|
|
||||||
rb_eRuntimeError = rb_define_class("RuntimeError", rb_eStandardError);
|
rb_eRuntimeError = rb_define_class("RuntimeError", rb_eStandardError);
|
||||||
rb_eFrozenError = rb_define_class("FrozenError", rb_eRuntimeError);
|
rb_eFrozenError = rb_define_class("FrozenError", rb_eRuntimeError);
|
||||||
|
rb_define_method(rb_eFrozenError, "initialize", frozen_err_initialize, -1);
|
||||||
|
rb_define_method(rb_eFrozenError, "receiver", name_err_receiver, 0);
|
||||||
rb_eSecurityError = rb_define_class("SecurityError", rb_eException);
|
rb_eSecurityError = rb_define_class("SecurityError", rb_eException);
|
||||||
rb_eNoMemError = rb_define_class("NoMemoryError", rb_eException);
|
rb_eNoMemError = rb_define_class("NoMemoryError", rb_eException);
|
||||||
rb_eEncodingError = rb_define_class("EncodingError", rb_eStandardError);
|
rb_eEncodingError = rb_define_class("EncodingError", rb_eStandardError);
|
||||||
|
@ -2845,6 +2873,20 @@ rb_error_frozen(const char *what)
|
||||||
rb_raise(rb_eFrozenError, "can't modify frozen %s", what);
|
rb_raise(rb_eFrozenError, "can't modify frozen %s", what);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
rb_frozen_error_raise(VALUE frozen_obj, const char *fmt, ...)
|
||||||
|
{
|
||||||
|
va_list args;
|
||||||
|
VALUE exc, mesg;
|
||||||
|
|
||||||
|
va_start(args, fmt);
|
||||||
|
mesg = rb_vsprintf(fmt, args);
|
||||||
|
va_end(args);
|
||||||
|
exc = rb_exc_new3(rb_eFrozenError, mesg);
|
||||||
|
rb_ivar_set(exc, id_recv, frozen_obj);
|
||||||
|
rb_exc_raise(exc);
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
rb_error_frozen_object(VALUE frozen_obj)
|
rb_error_frozen_object(VALUE frozen_obj)
|
||||||
{
|
{
|
||||||
|
@ -2855,11 +2897,12 @@ rb_error_frozen_object(VALUE frozen_obj)
|
||||||
VALUE path = rb_ary_entry(debug_info, 0);
|
VALUE path = rb_ary_entry(debug_info, 0);
|
||||||
VALUE line = rb_ary_entry(debug_info, 1);
|
VALUE line = rb_ary_entry(debug_info, 1);
|
||||||
|
|
||||||
rb_raise(rb_eFrozenError, "can't modify frozen %"PRIsVALUE", created at %"PRIsVALUE":%"PRIsVALUE,
|
rb_frozen_error_raise(frozen_obj,
|
||||||
|
"can't modify frozen %"PRIsVALUE", created at %"PRIsVALUE":%"PRIsVALUE,
|
||||||
CLASS_OF(frozen_obj), path, line);
|
CLASS_OF(frozen_obj), path, line);
|
||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
rb_raise(rb_eFrozenError, "can't modify frozen %"PRIsVALUE,
|
rb_frozen_error_raise(frozen_obj, "can't modify frozen %"PRIsVALUE,
|
||||||
CLASS_OF(frozen_obj));
|
CLASS_OF(frozen_obj));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
2
eval.c
2
eval.c
|
@ -454,7 +454,7 @@ rb_class_modify_check(VALUE klass)
|
||||||
goto noclass;
|
goto noclass;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
rb_error_frozen(desc);
|
rb_frozen_error_raise(klass, "can't modify frozen %s", desc);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -278,6 +278,7 @@ PRINTF_ARGS(NORETURN(void rb_loaderror(const char*, ...)), 1, 2);
|
||||||
PRINTF_ARGS(NORETURN(void rb_loaderror_with_path(VALUE path, const char*, ...)), 2, 3);
|
PRINTF_ARGS(NORETURN(void rb_loaderror_with_path(VALUE path, const char*, ...)), 2, 3);
|
||||||
PRINTF_ARGS(NORETURN(void rb_name_error(ID, const char*, ...)), 2, 3);
|
PRINTF_ARGS(NORETURN(void rb_name_error(ID, const char*, ...)), 2, 3);
|
||||||
PRINTF_ARGS(NORETURN(void rb_name_error_str(VALUE, const char*, ...)), 2, 3);
|
PRINTF_ARGS(NORETURN(void rb_name_error_str(VALUE, const char*, ...)), 2, 3);
|
||||||
|
PRINTF_ARGS(NORETURN(void rb_frozen_error_raise(VALUE, const char*, ...)), 2, 3);
|
||||||
NORETURN(void rb_invalid_str(const char*, const char*));
|
NORETURN(void rb_invalid_str(const char*, const char*));
|
||||||
NORETURN(void rb_error_frozen(const char*));
|
NORETURN(void rb_error_frozen(const char*));
|
||||||
NORETURN(void rb_error_frozen_object(VALUE));
|
NORETURN(void rb_error_frozen_object(VALUE));
|
||||||
|
|
34
spec/ruby/core/exception/frozen_error_spec.rb
Normal file
34
spec/ruby/core/exception/frozen_error_spec.rb
Normal file
|
@ -0,0 +1,34 @@
|
||||||
|
require_relative '../../spec_helper'
|
||||||
|
|
||||||
|
describe "FrozenError" do
|
||||||
|
ruby_version_is "2.5" do
|
||||||
|
it "is a subclass of RuntimeError" do
|
||||||
|
RuntimeError.should be_ancestor_of(FrozenError)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "FrozenError.new" do
|
||||||
|
ruby_version_is "2.7" do
|
||||||
|
it "should take optional receiver argument" do
|
||||||
|
o = Object.new
|
||||||
|
FrozenError.new("msg", o).receiver.should equal(o)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe "FrozenError#receiver" do
|
||||||
|
ruby_version_is "2.7" do
|
||||||
|
it "should return frozen object that modification was attempted on" do
|
||||||
|
o = Object.new.freeze
|
||||||
|
begin
|
||||||
|
def o.x; end
|
||||||
|
rescue => e
|
||||||
|
e.should be_kind_of(FrozenError)
|
||||||
|
e.receiver.should equal(o)
|
||||||
|
else
|
||||||
|
raise
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -853,6 +853,33 @@ end.join
|
||||||
alias inspect pretty_inspect
|
alias inspect pretty_inspect
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def test_frozen_error_receiver
|
||||||
|
obj = Object.new.freeze
|
||||||
|
(obj.foo = 1) rescue (e = $!)
|
||||||
|
assert_same(obj, e.receiver)
|
||||||
|
obj.singleton_class.const_set(:A, 2) rescue (e = $!)
|
||||||
|
assert_same(obj.singleton_class, e.receiver)
|
||||||
|
end
|
||||||
|
|
||||||
|
def test_frozen_error_initialize
|
||||||
|
obj = Object.new
|
||||||
|
exc = FrozenError.new("bar", obj)
|
||||||
|
assert_equal("bar", exc.message)
|
||||||
|
assert_same(obj, exc.receiver)
|
||||||
|
|
||||||
|
exc = FrozenError.new("bar")
|
||||||
|
assert_equal("bar", exc.message)
|
||||||
|
assert_raise_with_message(ArgumentError, "no receiver is available") {
|
||||||
|
exc.receiver
|
||||||
|
}
|
||||||
|
|
||||||
|
exc = FrozenError.new
|
||||||
|
assert_equal("FrozenError", exc.message)
|
||||||
|
assert_raise_with_message(ArgumentError, "no receiver is available") {
|
||||||
|
exc.receiver
|
||||||
|
}
|
||||||
|
end
|
||||||
|
|
||||||
def test_name_error_new_default
|
def test_name_error_new_default
|
||||||
error = NameError.new
|
error = NameError.new
|
||||||
assert_equal("NameError", error.message)
|
assert_equal("NameError", error.message)
|
||||||
|
|
4
thread.c
4
thread.c
|
@ -3325,7 +3325,7 @@ VALUE
|
||||||
rb_thread_local_aset(VALUE thread, ID id, VALUE val)
|
rb_thread_local_aset(VALUE thread, ID id, VALUE val)
|
||||||
{
|
{
|
||||||
if (OBJ_FROZEN(thread)) {
|
if (OBJ_FROZEN(thread)) {
|
||||||
rb_error_frozen("thread locals");
|
rb_frozen_error_raise(thread, "can't modify frozen thread locals");
|
||||||
}
|
}
|
||||||
|
|
||||||
return threadptr_local_aset(rb_thread_ptr(thread), id, val);
|
return threadptr_local_aset(rb_thread_ptr(thread), id, val);
|
||||||
|
@ -3402,7 +3402,7 @@ rb_thread_variable_set(VALUE thread, VALUE id, VALUE val)
|
||||||
VALUE locals;
|
VALUE locals;
|
||||||
|
|
||||||
if (OBJ_FROZEN(thread)) {
|
if (OBJ_FROZEN(thread)) {
|
||||||
rb_error_frozen("thread locals");
|
rb_frozen_error_raise(thread, "can't modify frozen thread locals");
|
||||||
}
|
}
|
||||||
|
|
||||||
locals = rb_ivar_get(thread, id_locals);
|
locals = rb_ivar_get(thread, id_locals);
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue