nazonoRubyist RubyでJavaScriptのためにC このページをアンテナに追加 RSSフィード

2006-06-28

例外処理 12:06 例外処理 - nazonoRubyist RubyでJavaScriptのためにC を含むブックマーク はてなブックマーク - 例外処理 - nazonoRubyist RubyでJavaScriptのためにC 例外処理 - nazonoRubyist RubyでJavaScriptのためにC のブックマークコメント

さて、昨日のコードであるが。

まず驚くべきは、コールバック関数引数-2 argv[-2] で関数オブジェクトが取り出せる!って何だよこの仕様オブジェクト指向では一般的なの? PerlJavaScript::SpiderMonkeyソースで使用されているのを知って初めて知ったのだが…-1に何が入っているかは調べていません。

で、取り出した関数オブジェクトから、事前に仕込んでおいたプロパティーを取り出して、そこから procオブジェクトを取り出している。あ、このへんエラー処理してないな。

その後、それらを実行するわけだが、普通proc を実行してしまうと、proc 内で例外が投げられたときにセグメンテーション違反で落ちてしまう(ということで数日悩んだ)。対策としては、rb_protect 内で、procを呼び出す。rb_protect で呼び出される関数は、引数を一個しか受け取らないので*1、args の最後にprocpushしている。

rb_smjs_ruby_proc_callerの内部はとくにおかしなところはなく

// protect 内で proc を呼ぶ。args の最後に proc が入っている
static VALUE
rb_smjs_ruby_proc_caller(VALUE args){
  // Procを実行
  VALUE proc = rb_ary_pop( args );
  return rb_apply(proc, rb_intern("call"), args);
}

こんなかんじ。

さて、これで終わってしまうと、Rubyproc内で投げられた例外は、すべて消失してしまう。投げられた例外を再びRuby世界に戻すためには rb_jump_tag( status ) を呼び出すことが必要だ。しかし、それを実行するのは、この「JavaScriptエンジンから呼び出されたC関数」の中であってはいけない。ではどこか、というと、RubyからJavaScriptエンジンに制御を移した場所でないといけない。

世界はこうなっているわけだ。

Ruby >> (例外が)越えられない壁=JavaScript >> Ruby

現在のところ、eval にしか RubyJavaScript の境界面はなく、rb_smjs_value_function_callback にしか JavaScriptRuby の境界面はない。つまり、正しい解決方法は rb_smjs_value_function_callback で発生するRuby例外はJavaScript例外にラップして JavaScript世界に投げ、evalJavaScript例外をキャッチしてRuby例外にしてRuby世界に投げる、という実装だろう。つまりそれを実装しているのが rb_smjs_raise_js である。

// 最後に発生したエラーの情報
struct sSMJS_Error{
  int status;
}gSMJS_LastError;

JSBool
rb_smjs_raise_js( JSContext *cx, int status ){
  SMJS_LastError.status = status;
  JSObject* exp = JS_NewObject( cx, NULL, NULL, NULL );
  JS_SetPendingException( cx, OBJECT_TO_JSVAL(exp) );
  return JS_FALSE;
}

えへへ、だが眠かったので、例外をグローバル変数に保存しておくようにしちゃった。これでスレッド・アンセーフになったわけだが、気にせずいこう。この辺は JavaScript内でクラスが実装できるようになってから変更することにする。

で、対応する JS_EvaluateScript を呼んでいる部分は

  JSBool ok = JS_EvaluateScript( cs->cx, cs->globalObj, source, strlen(source),
      filename, lineno, &value);
  if (!ok){
    rb_jump_tag( gSMJS_LastError.stat );
  }

こんなかんじ。まあ、このままだと「Ruby例外以外の例外で終了した場合」にまずいわけだが、その辺は実際の実装を参考に。

*1:実はポインタなので適当にだますことは可能なのだが

トラックバック - http://rubyist.g.hatena.ne.jp/nazoking/20060628