ワナビーエンジニアのブログ

なんでもいいから文化的な生活を送りたい

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

前々回前回で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_iselect_bang_ensureを呼ぶ
    • select_bang_iは、select!後のArrayを生成
    • select_bang_ensureは、select!後のArray.lengthを設定(#13053はここにフェールセーフ処理を追加した)

今後

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

スポンサードリンク

初めてのRuby

初めてのRuby