Bug #14847
closed`clone` can generate strange objects
Description
Object#clone(obj)
を呼び出すと、
- (1)
rb_obj_alloc(rb_obj_class(obj));
で空のobj
を作り、 - (2)
RBasic(orig)->flags
を(できるだけ)引き継ぎ - (3) 特異クラス情報を引き継ぎ
- (4) インスタンス変数を引き継ぎ
- (5) taint 情報を引き継ぎ
- (6)
initialize_copy
を呼んで初期化
という感じで処理をするのですが、(2) の箇所で FL_USER*
がそのまま引き継がれます。
そして、initialize_copy
が呼ばれたとき、適切に初期化をしないと、FL_USER*
がおかしなことになります。
下記はどれも遠藤さんが見つけてくれた例です。
class Array; def initialize_copy(*); end; end # 何もしない
x = [1,2,3].clone; p x #=> [false, false, false]
EMBED_LEN だけがコピーされ、配列の中身はコピーされない(0 初期化のまま)なので false が充填された配列になる。
class Array; def initialize_copy(*); end; end
x = [1,2,3,4,5,6,7][1..-2].clone
x.push(1,1,1,1,1)
#=> [BUG] Segmentation fault
ELTS_SHARED がコピーされるが、shared root への参照がコピーされないので SEGV する
class Hash; def initialize_copy(*); end; end
h = {}; h.default_proc = proc { }
h = h.clone
h[1] #=> undefined method `default_proc=' for {}:Hash (NoMethodError)
HASH_PROC_DEFAULT (== FL_USER2) だけがコピーされ、default_proc は nil のまま。
initialize_copy
が責務を果たしていない、というのはその通りですが、そもそも FL_USER*
をここでコピーするのは必要なんでしょうか。
initialize_copy
側で、FL_USER*
を適切に設定するべきではないでしょうか。
少なくとも、SEGV は回避しなければいけないかと思います。
歴史的経緯を知らないので、要るんだって話かもしれませんが、とりあえず現状はなんかやばそうな気がします。
Updated by matz (Yukihiro Matsumoto) over 6 years ago
まつもと ゆきひろです
おそらくは FL_FREEZE などいくつかのフラグだけ特別にコピーするべきなんだと思います。
最近 mruby でも同様のレポートが来て、あちらでは FREEZE だけコピーすることにしました。
CRuby ではもうちょっとコピーする必要がありそうです。
Updated by ko1 (Koichi Sasada) over 6 years ago
- Status changed from Open to Closed
Applied in changeset trunk|r63912.
Don't copy FL_USER* on Kernel#clone. [Bug #14847]
- object.c (mutable_obj_clone):
Kernel#clone
should not copy
FL_USER* flags because they are copied unexpectedly.
Unexpected copy will break internal data consistency.