Bug #5274

[ruby-dev:44460] Class の clone と Class の生成を組み合わせたときのバグ

Added by Kenta Murata over 2 years ago. Updated over 2 years ago.

[ruby-dev:44461]
Status:Closed
Priority:High
Assignee:Yukihiro Matsumoto
Category:core
Target version:1.9.3
ruby -v:ruby 1.9.3dev (2011-09-04 revision 33179) Backport:

Description

をチケット化します。

芝と申します。

次のサンプルコードを実行すると segv を吐きます。
確認した Ruby 処理系は ruby 1.9.3dev (2011-09-04 revision 33179)
[i686-linux] です。

#サンプルコード(#
StrClone = String.clone
Class.new(StrClone)
Marshal.dump(StrClone.new("a"))

segv を吐く原因は、StrClone.new("a") で壊れたオブジェクト(インスタンス変
数の数がえらいことになってるオブジェクト)を生成しているためです。
Class.new(StrClone) を実行した時点で StrClone の特異クラス(以降
と書きます)が変更され、メソッドの検索結果が変わってしまってい
ます。このため、StrClone インスタンスのアロケータが TSTRING のものか
ら、T
OBJECT のものとなり、StrClone.new で T_OBJECT のオブジェクトが割り
当てられます。

しかし、StrClone のメソッドテーブルは変更されていないため、割り当てたオ
ブジェクトに対しては文字列のイニシャライザが呼び出されます。このせいで、
T_OBJECT のオブジェクトに対して文字列の初期化処理が走ってしまい、壊れた
オブジェクトが作られます。サンプルコードでは、この、壊れたオブジェクトを
Marshal.dump で吐こうとしたときに、インスタンス変数のダンプをしようとし
て、segv を吐きます。

class オブジェクトの clone や、特異クラスの処理は複雑なので理解しきれて
いませんが、今回の場合、StrClone の特異クラスが変更されてしまうのが問題
に見えます。StrClone の特異メソッドが変更されてしまうことの原因は、以下
のように、make_metaclass(class.c) にあります。

#define ENSUREEIGENCLASS(klass) \
(rb
ivarget(METACLASSOF(klass), idattached) == (klass) ?
METACLASS
OF(klass) : make_metaclass(klass))

static inline VALUE
make_metaclass(VALUE klass)
{
....

super = RCLASSSUPER(klass);
while (RB
TYPEP(super, TICLASS)) super = RCLASSSUPER(super);
RCLASS
SUPER(metaclass) = super ? ENSUREEIGENCLASS(super) : rbcClass;

....

return metaclass;
}

ここの ENSUREEIGENCLASS というマクロは、klass が StrClone だった場合に
rb
ivarget(METACLASSOF(StrClone), idattached) == (StrClone)
の判定が偽になり、make
metaclass(StrClone) が実行されます。
この make_metaclass(StrClone) で、StrClone の特異クラスが変更されます。

これが何故起こるかというと、 の "attached" というインスタ
ンス変数が、StrClone を指していないためです。 の
"attached" は、rbsingletonclassclone(class.c)の以下の処理によっ
て、自分自身を指しています。この部分を少しかえて、 の
"
attached_" が StrClone を指すようにすれば、StrClone の特異クラスが変
更されてしまうという点については解決できます。

VALUE
rbsingletonclass_clone(VALUE obj)
{
VALUE klass = RBASIC(obj)->klass;

...

   VALUE clone = class_alloc((RBASIC(klass)->flags & ~(FL_MARK)), 0);

   if (BUILTIN_TYPE(obj) == T_CLASS) {
       RBASIC(clone)->klass = (VALUE)clone; /* <= ココを通る(1) */
   }
   else {
       RBASIC(clone)->klass = rb_singleton_class_clone(klass);
   }

   rb_singleton_class_attached(RBASIC(clone)->klass, (VALUE)clone);
   /* (1) を通った場合、RBASIC(clone)->klass = clone */
   /* clone の "__attached__" は clone 自身を指す */

...
}

本メールの末尾に、rbsingletonclassclone などの "attached" の処理
に対するパッチを添付します。特異クラスが常に "
attached_" に何かしらを
指している必要があるのなら、class.c に対するパッチは無視してください。

特異クラスの "attached" が自分自身を指していないとまずいケースがある
かどうかが分からないので、このパッチが有効かどうかは分かりませんが、再現
環境でサンプルコードを無事実行し、test-all にも通過することは確認できま
した。

参考にしていただければ幸いです。
よろしくお願いいたします。

パッチ

Index: object.c

--- object.c (revision 33182)
+++ object.c (working copy)
@@ -272,12 +272,17 @@
rbobjclone(VALUE obj)
{
VALUE clone;
+ VALUE singleton;

if (rb_special_const_p(obj)) {
    rb_raise(rb_eTypeError, "can't clone %s", rb_obj_classname(obj));
}
clone = rb_obj_alloc(rb_obj_class(obj));
  • RBASIC(clone)->klass = rbsingletonclass_clone(obj);
  • singleton = rbsingletonclass_clone(obj);
  • RBASIC(clone)->klass = singleton;
  • if (FLTEST(singleton, FLSINGLETON)) {
  • rbsingletonclass_attached(singleton, clone);
  • } RBASIC(clone)->flags = (RBASIC(obj)->flags | FLTEST(clone, FLTAINT) | FLTEST(clone, FLUNTRUSTED)) & ~(FLFREEZE|FLFINALIZE|FLMARK); initcopy(clone, obj); rbfuncall(clone, idinitclone, 1, obj); Index: class.c =================================================================== --- class.c (revision 33182) +++ class.c (working copy) @@ -230,12 +230,15 @@ struct clonemethoddata data; /* copy singleton(unnamed) class */ VALUE clone = classalloc((RBASIC(klass)->flags & ~(FL_MARK)), 0);
  •   int attach;
    

    if (BUILTINTYPE(obj) == TCLASS) {
    RBASIC(clone)->klass = (VALUE)clone;

  •       attach = 0;
    

    }
    else {
    RBASIC(clone)->klass = rbsingletonclass_clone(klass);

  •       attach = 1;
    

    }

    RCLASSSUPER(clone) = RCLASSSUPER(klass);
    @@ -251,7 +254,9 @@
    data.klass = (VALUE)clone;
    stforeach(RCLASSMTBL(klass), clonemethod,
    (stdatat)&data);

  •   rb_singleton_class_attached(RBASIC(clone)->klass, (VALUE)clone);
    
  •   if (attach) {
    
  •     rb_singleton_class_attached(RBASIC(clone)->klass, (VALUE)clone);
    
  •   }
    

    FLSET(clone, FLSINGLETON);
    return (VALUE)clone;
    }


Related issues

Related to ruby-trunk - Bug #4378: 1.9.2でClassオブジェクトのcloneの特異メソッドが消える Closed 02/07/2011

Associated revisions

Revision 33292
Added by Nobuyoshi Nakada over 2 years ago

  • object.c (rbobjclone): singleton class should be attached singleton object to. a patch by Satoshi Shiba at . [Bug #5274]

History

#1 Updated by Nobuyoshi Nakada over 2 years ago

  • Status changed from Assigned to Closed
  • % Done changed from 0 to 100

This issue was solved with changeset r33292.
Kenta, thank you for reporting this issue.
Your contribution to Ruby is greatly appreciated.
May Ruby be with you.


  • object.c (rbobjclone): singleton class should be attached singleton object to. a patch by Satoshi Shiba at . [Bug #5274]

Also available in: Atom PDF