mrubyのコードをCで実行すると、エラーが発生することがありますが、mrb_load_string
などで普通に実行しても、エラー内容やバックトレースは表示されません。
そこで、Cでエラー内容とバックトレースを取得する方法を調べてみました。
どんなエラーが起こったのか表示されない!!
mrb_load_string(mrb,
"def foo\n"
" xxx(0)\n"
"end\n"
"foo");
欲しい情報
foo.rb:1: undefined method 'xxx' for main (NoMethodError)
foo.rb:2:in Object.foo
foo.rb:4
方法
文字列を実行して、エラーとバックトレースを取得するコードはこのようになりました。
#include <stdio.h>
#include <mruby.h>
#include <mruby/compile.h>
#include <mruby/string.h>
// extern "C" (C++の場合必要)
mrb_value mrb_get_backtrace(mrb_state *mrb, mrb_value self);
...
mrb_state *mrb = mrb_open();
// コンテキストを生成、ファイル名を設定
mrbc_context *context = mrbc_context_new(mrb);
mrbc_filename(mrb, context, "foo.rb");
// 一時オブジェクトがつくられるのでGC arenaを保存
int ai = mrb_gc_arena_save(mrb);
mrb_load_string_cxt(mrb,
"def foo\n"
" xxx(0)\n"
"end\n"
"foo",
context);
mrb_value exc = mrb_obj_value(mrb->exc);
// エラー内容を取得
mrb_value backtrace = mrb_get_backtrace(mrb, exc);
puts(mrb_str_to_cstr(mrb, mrb_inspect(mrb, backtrace)));
// バックトレースを取得
mrb_value inspect = mrb_inspect(mrb, exc);
puts(mrb_str_to_cstr(mrb, inspect));
// 例外をクリア
mrb->exc = 0;
mrb_gc_arena_restore(mrb, ai);
mrbc_context_free(mrb, context);
mrb_close(mrb);
["foo.rb:2:in Object.foo", "foo.rb:4"]
foo.rb:1: undefined method 'xxx' for main (NoMethodError)
ポイント
コンテキストを用意する
文字列をロードする際は、mrb_load_string_cxt
関数を使って、コンテキストを同時に渡す必要があります。
ファイル名は、mrbc_filename
関数でコンテキストに設定します。
コンテキストを渡さないと、バックトレースが取得できません。
例外オブジェクト
例外オブジェクトはmrb->exc
で参照できます。
inspectでエラー内容を取得
例外オブジェクトをinspectすることで例のようなエラー文字列が取得できます。
バックトレースの取得
mrb_get_backtrace
が、例外オブジェクトからバックトレース(StringのArray)を取得する関数です。
ヘッダにはないので自分でプロトタイプ宣言を用意する必要があるようです。
バックトレースは例外発生後すぐに取得する必要がある
バックトレースを取得する前にinspectを行う処理を書いたところ、
バックトレースが取得できなくなることに気がついたので、issueを送ってみました。
mrb_get_backtrace
はmrb_stateからバックトレースを取得するため、他の処理を行ってしまうと
それが上書きされてしまうしたがって、バックトレースは例外発生後すぐに取得する必要がある
とのことでした。
例外はクリアする
mrb->exc
に0を設定することによって、例外をクリアする必要があります。
mrb->exc
が残っていると、次にスクリプトを実行するときに失敗してしまいます。
GC arena の save / restore
例外オブジェクト、処理中にCから生成したオブジェクトは、そのままでは GC arena に登録されて GC から保護されてしまい、いつまでも回収されません。
そこで、GC arena を save / restore して、GC の対象にさせる必要があります。