th_eval_body:例外処理
改めて話を戻して、例外処理について。raise〜rescueやthrow〜catchの他にも、break/redo/next, returnなども同じ仕組みを使って実装されています。この辺りの処理は th_eval_body にまとまっています。基本的な流れとしては…
exception_handler: cont_pc = cont_sp = catch_iseqval = 0; cfp = th->cfp; // 現在のCFP epc = cfp->pc - cfp->iseq->iseq_encoded; // 例外の発生したアドレス // 現在のスコープで例外をcatchすべきかどうかチェック // (iseq->catch_tableを調べる) // catch すべきなら例外タイプに応じて処理 // そうでない場合、もう一つ上のスコープを調べる else { th->cfp++; if (th->cfp->pc != &yarv_finish_insn_seq[0]) { goto exception_handler; } else { pop_frame(th); th->errinfo = err; TH_POP_TAG2(); JUMP_TAG(state); } }
&yarv_finish_insn_seq[0] は前回読んだfinish命令が入ったスコープですね。そこまで言っても例外ハンドラが見あたらなかったら、VMレベルでもっと上にエラーを投げてます。
「現在のスコープで例外をcatchすべきかどうかチェック」は以下のような単純なforループで探索。ensureハンドラが見つかった場合、そこに処理を移します。
for (i = 0; i < cfp->iseq->catch_table_size; i++) { entry = &cfp->iseq->catch_table[i]; if (entry->start < epc && entry->end >= epc) { if (entry->type == CATCH_TYPE_ENSURE) { catch_iseqval = entry->iseq; cont_pc = entry->cont; cont_sp = entry->sp; break; } } }
ensureに処理を移すコードはこんな感じに、新しく制御フレームを作ってvm_loop_startに戻ってます。
if (catch_iseqval != 0) { /* found catch table */ rb_iseq_t *catch_iseq; /* enter catch scope */ GetISeqPtr(catch_iseqval, catch_iseq); cfp->sp = cfp->bp + cont_sp; cfp->pc = cfp->iseq->iseq_encoded + cont_pc; /* push block frame */ cfp->sp[0] = err; push_frame(th, catch_iseq, FRAME_MAGIC_BLOCK, cfp->self, (VALUE)cfp->dfp, catch_iseq->iseq_encoded, cfp->sp + 1, cfp->lfp, catch_iseq->local_size - 1); state = 0; th->errinfo = Qnil; goto vm_loop_start; }
catch_tableでCATCH_TYPE_ENSURE以外の適切なタイプにひっかかった場合、たとえばbreak中にCATCH_TYPE_BREAKの範囲に当たった場合は、単純にその位置にpcを戻せば良いので、新しいフレームを積んだりせずに(ほぼ即)goto vm_loop_startで戻ってました。