Project

General

Profile

Bug #5283

Updated by nobu (Nobuyoshi Nakada) over 9 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 

Back