Japanese

Rubyの#13053を読んでみる(3)

  • このエントリーをはてなブックマークに追加

前々回 [http://www.koooge.com/entry/2016/12/22/013733]、 前回 [http://www.koooge.com/entry/2016/12/26/005958]でlen[0]、len[1]が明らかになったので、 select_bang_ensureをみていきます。

修正前コード static VALUE select_bang_ensure(VALUE a) { volatile struct select_bang_arg *arg = (void *)a; VALUE ary = arg->ary; long len = RARRAY_LEN(ary); long i1 = arg->len[0], i2 = arg->len[1];

if (i2 < i1) {
if (i1 < len) {
    RARRAY_PTR_USE(ary, ptr, {
        MEMMOVE(ptr + i2, ptr + i1, VALUE, len - i1);
    });
}
ARY_SET_LEN(ary, len - i1 + i2);
}
return ary;

}

さてlen, i1, i2について考えます。i1 = arg->len[0]は元々のArrayの長さで、i2 = arg->len[1] がselect!後のArrayの長さとわかっています。 最後にlenですが、引数として受け取ったarg->aryの長さとなります。しかしこれ、呼び出し元のrb_ary_select_bang を軽く読んでみても何が入るか良くわからず。。

#13503のケースを考える arg->aryに何が渡されているかパッとはわからないので、 立ち返って[https://bugs.ruby-lang.org/issues/13053]のスニペットを考えます。

ary = [1,2,3,4,5] ary.select! { |i| ary.clear if i==5; false } p ary #=> [] p ary.size #=> -5

今までの結果から、i1=5、 i2=0。これに、ARRAY_SET_LEN(ary, len - i1 + i2) #=> -5 なので、lenは0です。多分どこかでselect_bang_ensureに渡されるargs->lenの値が狂っていそうです。

修正後コード ここで#13053の修正方法を見てみます。

static VALUE select_bang_ensure(VALUE a) { volatile struct select_bang_arg *arg = (void *)a; VALUE ary = arg->ary; long len = RARRAY_LEN(ary); long i1 = arg->len[0], i2 = arg->len[1];

if (i2 < len && i2 < i1) {
long tail = 0;
if (i1 < len) {
    tail = len - i1;
    RARRAY_PTR_USE(ary, ptr, {
	    MEMMOVE(ptr + i2, ptr + i1, VALUE, tail);
	});
}
ARY_SET_LEN(ary, i2 + tail);
}
return ary;

}

このコードだとlen=0のときif (i2 < len && i2 < i1) { で偽となるので、p ary.size #=> 0となりそうですね。

チケットのコメントを見ると下記の記載があります。

Shrinking the Array from the block invoked by Array#select! or Array#reject! causes the Array to be a negative number size. Ensure that the resulting Array won’t be smaller than 0.

超訳:“lenを0未満にはしないようにした”

なんだか本対処じゃなくってフェールセーフ処理っぽいですね。 つまりargs->lenが0となっている根本原因は他にありそうです。

まとめ

  • rubyのissue #13053と、共にArray#select!の処理を追ってきた * rb_ary_select_bangがArray#select!の入口で、そこからselect_bang_iとselect_bang_ensure を呼ぶ
    • select_bang_iは、select!後のArrayを生成
    • select_bang_ensureは、select!後のArray.lengthを設定(#13053はここにフェールセーフ処理を追加した)

今後 rubyのissueの雰囲気はつかめたので、 args->aryに0が渡されている元を追ってみたいと思います。

スポンサードリンク [asin:4873113679:detail]