Techioz Blog

Ruby C API の例外処理

概要

次のようにCを介してRubyスクリプトを実行しています。

#include <ruby.h>

int main(void) {
  ruby_init();

  int status;
  rb_load_protect(rb_str_new2("./test.rb"), 0, &status);

  if (status) {
    VALUE rbError = rb_funcall(rb_gv_get("$!"), rb_intern("message"), 0);
    printf("%s\n", StringValueCStr(rbError));
  }

  ruby_finalize();
  return status;
}

Ruby スクリプト:

1.hello

今実行すると、次の出力が得られます。

undefined method `hello' for 1:Integer

これは予想通りです。 次に、このメソッドを定義します。

VALUE hello(VALUE self) {
  return Qnil;
}

// just after ruby_init()
rb_define_method(rb_cInteger, "hello", hello, 0);

今実行すると、次のようになります

ruby: [BUG] Segmentation fault at 0x00000000000000b0
/* ... */

ここでエラーを処理するにはどうすればよいでしょうか?この場合は nil ですが、理由はわかりません。

解決策

ステータスを初期化するだけでよいと思います。

int status = 0;

rb_load_protect は、例外がある場合はステータスを設定しますが、例外がない場合はステータスを 0 に設定しないように見えますが、それに &status を渡すことで、コンパイラからの初期化された変数の警告を抑制しています。

現時点で起こっていることは、ステータスが何らかの (ゼロではない) ガベージとして開始され、例外がない限りゼロに設定されないため、if ブロックは常に実行されます。ただし、例外がない場合は nil なので、メッセージを呼び出すとハンドルされない例外が発生し、クラッシュが発生します。