GUIアプリケーション等にmrubyを組み込む時、スクリプトの実行時間に制限を掛けたい場合が少なからずあると思います。スクリプトが走っているスレッドを外部から強制終了する...わけにもいきませんので、インタプリタ側から処理を中断できるようにする必要があります。
簡易的なものですが、これを書いている時点でのmrubyのHEADは fa4ae7dd4d
では、mruby本体には手を加えなくてもデバッグ用のVMフックを利用することで、このような制限を実装できます。
準備
mrbconf.h を編集して、ENABLE_DEBUGのコメントアウトを外します。
この状態でmrubyをビルドすると、struct mrb_state
のメンバにcode_fetch_hook
という関数ポインタが追加されます。
/* -DENABLE_XXXX to enable following features */
#define ENABLE_DEBUG /* hooks for debugger */
サンプル
mrb->code_fetch_hookにセットした関数が、バイトコードが実行されるごとに呼び出されます。
適当にリミットを設け、vm_hook
の呼び出しが指定した回数を超えたときに例外を投げて強制的に停止させます。
#include <stdio.h>
#include <mruby.h>
#include <mruby/compile.h>
int vm_step_count = 0;
int vm_step_limit = 0;
void
vm_hook(struct mrb_state* mrb, struct mrb_irep *irep, mrb_code *pc, mrb_value *regs)
{
if (vm_step_limit > 0) {
if (vm_step_count > vm_step_limit) {
vm_step_count = vm_step_limit = 0;
mrb_raise(mrb, E_RUNTIME_ERROR, "step limit exceeded");
}
vm_step_count++;
}
}
mrb_value
mrb_limited_load_string(mrb_state *mrb, const char *source, int limit)
{
vm_step_count = 0;
vm_step_limit = limit;
return mrb_load_string(mrb, source);
}
int main(void)
{
mrb_state *mrb = mrb_open();
mrb->code_fetch_hook = vm_hook;
mrb_limited_load_string(mrb, "loop do; end", 3000);
mrb_close(mrb);
}
実際には、実時間での実行時間で制限したほうが使い勝手は良いと思います。
当然のことながら、C言語で実装されている関数内で時間がかかっている場合には対処できません。