RubyCocoa の dangling pointer 問題を直した

LimeChat for OSX には、かなり低い確率で解放済みのメモリにアクセスしてしまう問題があって、ずっと原因がわからずに困っていた。

今日、たまたま RHG を読み直していたら、あっ、これだっ!!!という項目を見つけた。

http://i.loveruby.net/ja/rhg/book/gc.html

GC対策のvolatile

スタック上のVALUEはGCが面倒を見てくれると書いた。
それならば ローカル変数としてVALUEを置いておけば
そのVALUEは確実にマークされるはずである。
しかし現実には最適化の影響で変数が消えてしまうことがある。
例えば次のような場合は消える可能性がある。

  VALUE str;
  str = rb_str_new2("...");
  printf("%s\n", RSTRING(str)->ptr);

このコードではstr自体にアクセスしていないので、
コンパイラによっては str->ptrだけメモリに残して
strは消してしまうことがある。
そうすると strが回収されて落ちる。
こういう時は仕方がないので、

  volatile VALUE str;

とする。

つまり、ruby の C 拡張で GC に回収されないためには、VALUE がスタックに積まれているか、VALUE がルートからたどれる必要があるらしい。
実際に、RubyCocoa のコードを grep しながらよく調べてみるといくつか危ない箇所があったので、volatile をつけておいた。
送られてきたクラッシュレポートのスタックトレースから判断すると、十中八九これが原因のようだ。

他にも、ruby の文字列から C ポインタを取り出すのに STR2CSTR() を使っている箇所があって、上と同じ問題を引き起こす可能性があるので、全部 StringValuePtr() に書き換えてコミット。(RubyCocoa r2165)

char* を取り出す場合、version 1.6 以前では「STR2CSTR()」というマクロを使っていましたが、これは to_str() による暗黙の型変換結果が GC される可能性があるため、version 1.7 以降では obsolete となり、代わりに StringValue() と StringValuePtr() を使う事を推奨しています。
(ruby の README.EXT.ja より)

この問題以外に、C レベルでクラッシュしたというレポートはまったく来ていないので、RubyCocoa はこれでかなり安定するはず。何はともあれ、RubyCocoa 1.0 までに見つけられてよかったと思う。