Bug #5283
Updated by nobu (Nobuyoshi Nakada) about 12 years ago
=begin 芝と申します。 クラスオブジェクトを clone したときの挙動について質問させてください。 + # サンプルコード String.class_eval do def self.singleton_method_added(mid) puts("singleton_method_added: self = #{self}, mid = #{mid}") end end puts("start clone") StrClone = String.clone puts("finish clone") + # サンプルコードの実行結果 singleton_method_added: self = String, mid = singleton_method_added start clone singleton_method_added: self = String, mid = try_convert singleton_method_added: self = String, mid = singleton_method_added finish clone 上記のサンプルコードを実行すると、String クラスを clone し、メソッドテー ブルを StrClone にコピーしているときに、String クラスに対して singleton_method_added が呼び出され、実行結果のように出力されます。 質問なんですが、クラスオブジェクトの clone 時に clone 元のクラスオブジェ クトの singleton_method_added が呼び出されるのは仕様なのでしょうか。 上記のサンプルコードの場合、StrClone の singleton_method_added が呼び出 されるか、もしくは singleton_method_added が呼び出されないことを期待して singleton_method_added を使用していました。 上記のサンプルコードで String クラスの singleton_method_added が呼び出さ れる原因は、rb_singleton_class_clone(class.c) にあります。 rb_singleton_class_clone では、以下のように特異クラスのインスタンス変数 をコピーするのですが、このとき、"__attached__" というインスタンス変数を コピーしています。 VALUE rb_singleton_class_clone(VALUE obj) { ... if (RCLASS_IV_TBL(klass)) { /* インスタンス変数のコピー */ RCLASS_IV_TBL(clone) = st_copy(RCLASS_IV_TBL(klass)); } ... RCLASS_M_TBL(clone) = st_init_numtable(); data.tbl = RCLASS_M_TBL(clone); data.klass = (VALUE)clone; /* メソッドテーブルのコピー、method_added の呼び出し */ st_foreach(RCLASS_M_TBL(klass), clone_method, (st_data_t)&data); ... } 今回の例の場合、この、"__attached__" は、クラスのコピー元である String クラスを指しています。singleton_method_added を呼び出す CALL_METHOD_HOOK では、以下のように、レシーバを特異クラスの "__attached__" によって決定す るので、メソッドテーブルをコピーするときに呼び出す singleton_method_added のレシーバが、String になっています。 #define CALL_METHOD_HOOK(klass, hook, mid) do { \ const VALUE arg = ID2SYM(mid); \ VALUE recv_class = (klass); \ ID hook_id = (hook); \ if (FL_TEST((klass), FL_SINGLETON)) { \ recv_class = rb_ivar_get((klass), attached);\ hook_id = singleton_##hook; \ } \ rb_funcall2(recv_class, hook_id, 1, &arg); \ } while (0) このようになるのが仕様として定まっているのかどうかが分からないのですが、 singleton_method_added のレシーバをクローン先のクラスに変更するようにし たパッチを作成したので、本メールの末尾に張っておきます。 rb_obj_clone での RBASIC(clone)->klass = rb_singleton_class_clone_and_attach(obj, clone); の位置をずらしていいのかどうかが不安なんですが、参考にしていただければ幸 いです。 以上、よろしくお願いいたします。 + # パッチ Index: object.c =================================================================== --- object.c (revision 33202) +++ object.c (working copy) @@ -268,6 +268,7 @@ * the class. */ +VALUE rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach); VALUE rb_obj_clone(VALUE obj) { @@ -277,9 +278,9 @@ rb_raise(rb_eTypeError, "can't clone %s", rb_obj_classname(obj)); } clone = rb_obj_alloc(rb_obj_class(obj)); - RBASIC(clone)->klass = rb_singleton_class_clone(obj); RBASIC(clone)->flags = (RBASIC(obj)->flags | FL_TEST(clone, FL_TAINT) | FL_TEST(clone, FL_UNTRUSTED)) & ~(FL_FREEZE|FL_FINALIZE|FL_MARK); init_copy(clone, obj); + RBASIC(clone)->klass = rb_singleton_class_clone_and_attach(obj, clone); rb_funcall(clone, id_init_clone, 1, obj); RBASIC(clone)->flags |= RBASIC(obj)->flags & FL_FREEZE; Index: class.c =================================================================== --- class.c (revision 33202) +++ class.c (working copy) @@ -219,9 +219,17 @@ return rb_mod_init_copy(clone, orig); } +VALUE rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach); + VALUE rb_singleton_class_clone(VALUE obj) { + return rb_singleton_class_clone_and_attach(obj, Qundef); +} + +VALUE +rb_singleton_class_clone_and_attach(VALUE obj, VALUE attach) +{ VALUE klass = RBASIC(obj)->klass; if (!FL_TEST(klass, FL_SINGLETON)) @@ -246,6 +254,10 @@ RCLASS_CONST_TBL(clone) = st_init_numtable(); st_foreach(RCLASS_CONST_TBL(klass), clone_const_i, (st_data_t)RCLASS_CONST_TBL(clone)); } + if (attach != Qundef) { + rb_singleton_class_attached(clone, attach); + } RCLASS_M_TBL(clone) = st_init_numtable(); data.tbl = RCLASS_M_TBL(clone); data.klass = (VALUE)clone; =end