Ruby1.9/例外処理を読む
をテンプレートにして作成
[
トップ
] [
新規
|
一覧
|
単語検索
|
最終更新
|
ヘルプ
]
開始行:
#contents
*はじめに [#y59fb9de]
今回はRuby1.9の例外処理を以下の手順で読解したいと思います。
-スクリプトからNODEツリーへの変換
-NODEツリーからYARVコードへの変換
-YARVコードの実行
*サンプルスクリプト [#g9cedf27]
例外処理だけに注目するということでサンプルはとても作為的...
def exc_func
begin
raise "Exception"
ensure
puts "ensure in exc_func"
end
end
begin
exc_func
rescue
puts $!.backtrace
ensure
puts "ensure in top"
end
見るのは以下の項目です。
-実行コンテキストからの脱出
-ensureの実行
-例外の補足
-バックトレースの生成
ちなみに、実行結果は以下のようになります。
$ ruby exc.rb
ensure in exc_func
exc.rb:3:in `raise'
exc.rb:3:in `exc_func'
exc.rb:10:in `<main>'
ensure in top
ん?Ruby1.8と違いますね。Ruby1.8だと以下のようになります...
$ ruby exc.rb
ensure in exc_func
exc.rb:3:in `exc_func'
exc.rb:10
ensure in top
*NODEツリーへの変換 [#r3bb4ff5]
いろいろな要素がどういうNODEツリーに変換されるかは[[スク...
**primary(keyword_begin) [#a6acb9c7]
beginが見つかるとprimary規則のkeyword_beginがひっかかりま...
nd_type = NODE_BEGIN
u1.value = 0
u2.value = bodystmt
u3.value = 0
**bodystmt [#xc95ae28]
次に、beginの中身とrescue以下がbodystmt規則にひっかかりま...
NODE_ENSURE
NODE_RESCUE
compstmt
opt_rescue
0(opt_else)
opt_ensure
**opt_rescue [#ac5487d4]
opt_rescue規則です。補足する例外クラスは指定しておらず他...
nd_type = NODE_RESBODY
u1.value = 0(opt_rescue)
u2.value = compstmt
u3.value = 0(exc_list)
**opt_ensure [#i690aa3e]
opt_ensure規則ではensureに続くcompstmtがそのままNODEにな...
**変換結果 [#x097df2d]
というわけで変換結果です。
&ref(exc.node.txt);
*YARVコードへの変換 [#m0e9b8a3]
それでは次にNODEからYARVコードへの変換です。全体的な流れ...
**NODE_ENSURE [#r19dd141]
NODE_BEGINは子ノードをCOMPILE_マクロにかけるだけです。で...
まず、ensureの本体がNEW_CHILD_ISEQVALマクロにかけられてい...
local_table = ID("#$!")
local_table_size = 1
local_size = 1
ん・・・、$!ではなくて#$!ですか。まあそのうち意味がわかる...
NEW_CHILD_ISEQVALマクロから帰ってくるといろいろ情報が設定...
次に、開始ラベルの追加、本体(rescue含む)のコンパイル、...
次にensureの本体がコンパイルされています。さっきやったじ...
その後、ensureの終わり部分にラベルを追加しています。この...
最後にADD_CATCH_ENTRYマクロを利用して例外が発生したときの...
**NODE_RESCUE [#o0fca20d]
次にensureの本体NODE_RESCUEを見てみましょう。まずrescueの...
NEW_CHILD_ISEQVALマクロから帰ってくると開始ラベルの追加、...
最後にADD_CATCH_ENTRYマクロを利用して例外が発生したときの...
**NODE_RESBODY [#b517dbc8]
最後にrescue本体のNODE_RESBODYを見てみましょう。
まず、発生された例外が指定された例外かチェックし、そうな...
次にrescueの後ろのラベルへのjumpを追加しています。例外が...
次に例外が指定したものの場合のjump先ラベルを追加し、rescu...
最後に例外が指定したもの以外の場合のjump先ラベルを追加し...
**コンパイル結果 [#k99c832f]
というわけでコンパイル結果です。&ref(exc.yarv.txt);
*YARVコードの実行 [#d486381e]
それでは例外が発生した場合の処理の流れを見てみましょう。...
**rb_f_raise(eval.c) [#hc5c0081]
raiseの処理関数はrb_f_raise関数です。引数に応じて例外オブ...
**rb_longjmp(eval.c) [#rb9134c7]
rb_longjmp関数はいろいろやってますが、引数で渡された例外...
**make_backtrace(eval.c) [#o150a92a]
rb_longjmp関数の途中で例外オブジェクトにバックトレースが...
make_backtrace関数は引数levを-1としてbacktrace関数を呼ん...
次にvm_backtrace関数に移ります。まず一番上のフレームを計...
cfp→|現在実行しているフレーム情報
...
top_of_cfp→|rb_vm_set_finish_env関数で積んだフレー...
|th_init2関数で積んだフレーム情報
stack+stack_size→|
その後、vm_backtrace_each関数にてバックトレースが生成され...
**JUMP_TAG(eval_intern.h) [#sa2fcb55]
JUMP_TAGマクロの定義は以下のようになっています。
#define TH_JUMP_TAG(th, st) do { \
ruby_longjmp(th->tag->buf,(st)); \
} while (0)
#define JUMP_TAG(st) TH_JUMP_TAG(GET_THREAD(), st)
というわけでlongjmpしています。対応するsetjmp(EXEC_TAG)は...
vm_eval_body(rb_thread_t *th)
{
int state;
VALUE result, err;
VALUE initial = 0;
TH_PUSH_TAG(th);
if ((state = EXEC_TAG()) == 0) {
vm_loop_start:
result = vm_eval(th, initial);
**vm_eval_body(vm.c) [#ve86222a]
というわけでvm_eval_body関数に飛んできてelseが実行されま...
:label(0) # start
putnil
putobject("Exception")
epc→send(:raise, 1, 0, VM_CALL_FCALL_BIT, 0)
:label(1) # end
putnil
putobject("ensure in exc_func")
send(:puts, 1, 0, VM_CALL_FCALL_BIT, 0)
pop
:label(2) # cont
leave
次にiseqのcatch_tableからepcに対応するensureのiseqが引き...
/* enter catch scope */
GetISeqPtr(catch_iseqval, catch_iseq);
cfp->sp = cfp->bp + cont_sp;
cfp->pc = cfp->iseq->iseq_encoded + cont_pc;
cfp->sp[0] = err;
vm_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;
というわけでensureはブロックと同じ形式で実行されるようで...
lfp→|GC_GUARDED_PTR(0)
...
|例外オブジェクト
dfp→|GC_GUARDED_PTR(cfp->dfp)
sp,bp→|
**throw [#s798c0d5]
ensureのiseqの最後の命令はthrow(0)です。コメントにも書い...
throwの一つ前でgetdynamic(1, 0)が実行されているのでスタッ...
vm_throw関数はいろいろ((throwはbreakの実装などにも使われ...
戻ってきてTHROW_EXCEPTIONマクロ、コンパイルオプションによ...
#define THROW_EXCEPTION(exc) return (VALUE)(exc)
と展開されるとします。
**vm_eval_body再び [#w9738dd5]
さてというわけでまたvm_eval_body関数に戻ってきました。た...
result = vm_eval(th, initial);
if ((state = th->state) != 0) {
err = result;
th->state = 0;
goto exception_handler;
}
現在のiseq(ensureのiseq)には例外が発生したpcに対応するも...
:label(0) # start
...
:label(1) # end
...
:label(2) # cont
pc→leave
というわけでexc_funcメソッドのフレームでも例外に反応する...
:label(6) # start(NODE_RESCUE)
putnil
pc→send(:exc_func, 0, 0, VM_CALL_VCALL_BIT, 0)
:label(7) # end(NODE_RESCUE)
nop
:label(8) # cont(NODE_RESCUE)
:label(4) # end(NODE_ENSURE)
putnil
putobject("ensure in top")
send(:puts, 1, 0, VM_CALL_FCALL_BIT, 0)
というわけでrescueのiseqがひっかかりました。
rescueのiseqを実行していくとleaveが実行されるのでrescueの...
:label(6) # start(NODE_RESCUE)
putnil
send(:exc_func, 0, 0, VM_CALL_VCALL_BIT, 0)
:label(7) # end(NODE_RESCUE)
nop
:label(8) # cont(NODE_RESCUE)
:label(4) # end(NODE_ENSURE)
pc→putnil
putobject("ensure in top")
send(:puts, 1, 0, VM_CALL_FCALL_BIT, 0)
なのでensureの部分が実行されます。このensureは例外処理と...
*おわりに [#v5fdefe1]
今回はRuby1.9の例外処理を見てきました。わかったこととして...
-例外が起きたpcからどのiseqを実行するかを引き当てる
-例外処理はフレームを割り当てブロック的に実行される
-ensureは例外処理として実行されるものと通常の命令として実...
ふうむ、今回はかなり難解でした。それではみなさんもよいコ...
終了行:
#contents
*はじめに [#y59fb9de]
今回はRuby1.9の例外処理を以下の手順で読解したいと思います。
-スクリプトからNODEツリーへの変換
-NODEツリーからYARVコードへの変換
-YARVコードの実行
*サンプルスクリプト [#g9cedf27]
例外処理だけに注目するということでサンプルはとても作為的...
def exc_func
begin
raise "Exception"
ensure
puts "ensure in exc_func"
end
end
begin
exc_func
rescue
puts $!.backtrace
ensure
puts "ensure in top"
end
見るのは以下の項目です。
-実行コンテキストからの脱出
-ensureの実行
-例外の補足
-バックトレースの生成
ちなみに、実行結果は以下のようになります。
$ ruby exc.rb
ensure in exc_func
exc.rb:3:in `raise'
exc.rb:3:in `exc_func'
exc.rb:10:in `<main>'
ensure in top
ん?Ruby1.8と違いますね。Ruby1.8だと以下のようになります...
$ ruby exc.rb
ensure in exc_func
exc.rb:3:in `exc_func'
exc.rb:10
ensure in top
*NODEツリーへの変換 [#r3bb4ff5]
いろいろな要素がどういうNODEツリーに変換されるかは[[スク...
**primary(keyword_begin) [#a6acb9c7]
beginが見つかるとprimary規則のkeyword_beginがひっかかりま...
nd_type = NODE_BEGIN
u1.value = 0
u2.value = bodystmt
u3.value = 0
**bodystmt [#xc95ae28]
次に、beginの中身とrescue以下がbodystmt規則にひっかかりま...
NODE_ENSURE
NODE_RESCUE
compstmt
opt_rescue
0(opt_else)
opt_ensure
**opt_rescue [#ac5487d4]
opt_rescue規則です。補足する例外クラスは指定しておらず他...
nd_type = NODE_RESBODY
u1.value = 0(opt_rescue)
u2.value = compstmt
u3.value = 0(exc_list)
**opt_ensure [#i690aa3e]
opt_ensure規則ではensureに続くcompstmtがそのままNODEにな...
**変換結果 [#x097df2d]
というわけで変換結果です。
&ref(exc.node.txt);
*YARVコードへの変換 [#m0e9b8a3]
それでは次にNODEからYARVコードへの変換です。全体的な流れ...
**NODE_ENSURE [#r19dd141]
NODE_BEGINは子ノードをCOMPILE_マクロにかけるだけです。で...
まず、ensureの本体がNEW_CHILD_ISEQVALマクロにかけられてい...
local_table = ID("#$!")
local_table_size = 1
local_size = 1
ん・・・、$!ではなくて#$!ですか。まあそのうち意味がわかる...
NEW_CHILD_ISEQVALマクロから帰ってくるといろいろ情報が設定...
次に、開始ラベルの追加、本体(rescue含む)のコンパイル、...
次にensureの本体がコンパイルされています。さっきやったじ...
その後、ensureの終わり部分にラベルを追加しています。この...
最後にADD_CATCH_ENTRYマクロを利用して例外が発生したときの...
**NODE_RESCUE [#o0fca20d]
次にensureの本体NODE_RESCUEを見てみましょう。まずrescueの...
NEW_CHILD_ISEQVALマクロから帰ってくると開始ラベルの追加、...
最後にADD_CATCH_ENTRYマクロを利用して例外が発生したときの...
**NODE_RESBODY [#b517dbc8]
最後にrescue本体のNODE_RESBODYを見てみましょう。
まず、発生された例外が指定された例外かチェックし、そうな...
次にrescueの後ろのラベルへのjumpを追加しています。例外が...
次に例外が指定したものの場合のjump先ラベルを追加し、rescu...
最後に例外が指定したもの以外の場合のjump先ラベルを追加し...
**コンパイル結果 [#k99c832f]
というわけでコンパイル結果です。&ref(exc.yarv.txt);
*YARVコードの実行 [#d486381e]
それでは例外が発生した場合の処理の流れを見てみましょう。...
**rb_f_raise(eval.c) [#hc5c0081]
raiseの処理関数はrb_f_raise関数です。引数に応じて例外オブ...
**rb_longjmp(eval.c) [#rb9134c7]
rb_longjmp関数はいろいろやってますが、引数で渡された例外...
**make_backtrace(eval.c) [#o150a92a]
rb_longjmp関数の途中で例外オブジェクトにバックトレースが...
make_backtrace関数は引数levを-1としてbacktrace関数を呼ん...
次にvm_backtrace関数に移ります。まず一番上のフレームを計...
cfp→|現在実行しているフレーム情報
...
top_of_cfp→|rb_vm_set_finish_env関数で積んだフレー...
|th_init2関数で積んだフレーム情報
stack+stack_size→|
その後、vm_backtrace_each関数にてバックトレースが生成され...
**JUMP_TAG(eval_intern.h) [#sa2fcb55]
JUMP_TAGマクロの定義は以下のようになっています。
#define TH_JUMP_TAG(th, st) do { \
ruby_longjmp(th->tag->buf,(st)); \
} while (0)
#define JUMP_TAG(st) TH_JUMP_TAG(GET_THREAD(), st)
というわけでlongjmpしています。対応するsetjmp(EXEC_TAG)は...
vm_eval_body(rb_thread_t *th)
{
int state;
VALUE result, err;
VALUE initial = 0;
TH_PUSH_TAG(th);
if ((state = EXEC_TAG()) == 0) {
vm_loop_start:
result = vm_eval(th, initial);
**vm_eval_body(vm.c) [#ve86222a]
というわけでvm_eval_body関数に飛んできてelseが実行されま...
:label(0) # start
putnil
putobject("Exception")
epc→send(:raise, 1, 0, VM_CALL_FCALL_BIT, 0)
:label(1) # end
putnil
putobject("ensure in exc_func")
send(:puts, 1, 0, VM_CALL_FCALL_BIT, 0)
pop
:label(2) # cont
leave
次にiseqのcatch_tableからepcに対応するensureのiseqが引き...
/* enter catch scope */
GetISeqPtr(catch_iseqval, catch_iseq);
cfp->sp = cfp->bp + cont_sp;
cfp->pc = cfp->iseq->iseq_encoded + cont_pc;
cfp->sp[0] = err;
vm_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;
というわけでensureはブロックと同じ形式で実行されるようで...
lfp→|GC_GUARDED_PTR(0)
...
|例外オブジェクト
dfp→|GC_GUARDED_PTR(cfp->dfp)
sp,bp→|
**throw [#s798c0d5]
ensureのiseqの最後の命令はthrow(0)です。コメントにも書い...
throwの一つ前でgetdynamic(1, 0)が実行されているのでスタッ...
vm_throw関数はいろいろ((throwはbreakの実装などにも使われ...
戻ってきてTHROW_EXCEPTIONマクロ、コンパイルオプションによ...
#define THROW_EXCEPTION(exc) return (VALUE)(exc)
と展開されるとします。
**vm_eval_body再び [#w9738dd5]
さてというわけでまたvm_eval_body関数に戻ってきました。た...
result = vm_eval(th, initial);
if ((state = th->state) != 0) {
err = result;
th->state = 0;
goto exception_handler;
}
現在のiseq(ensureのiseq)には例外が発生したpcに対応するも...
:label(0) # start
...
:label(1) # end
...
:label(2) # cont
pc→leave
というわけでexc_funcメソッドのフレームでも例外に反応する...
:label(6) # start(NODE_RESCUE)
putnil
pc→send(:exc_func, 0, 0, VM_CALL_VCALL_BIT, 0)
:label(7) # end(NODE_RESCUE)
nop
:label(8) # cont(NODE_RESCUE)
:label(4) # end(NODE_ENSURE)
putnil
putobject("ensure in top")
send(:puts, 1, 0, VM_CALL_FCALL_BIT, 0)
というわけでrescueのiseqがひっかかりました。
rescueのiseqを実行していくとleaveが実行されるのでrescueの...
:label(6) # start(NODE_RESCUE)
putnil
send(:exc_func, 0, 0, VM_CALL_VCALL_BIT, 0)
:label(7) # end(NODE_RESCUE)
nop
:label(8) # cont(NODE_RESCUE)
:label(4) # end(NODE_ENSURE)
pc→putnil
putobject("ensure in top")
send(:puts, 1, 0, VM_CALL_FCALL_BIT, 0)
なのでensureの部分が実行されます。このensureは例外処理と...
*おわりに [#v5fdefe1]
今回はRuby1.9の例外処理を見てきました。わかったこととして...
-例外が起きたpcからどのiseqを実行するかを引き当てる
-例外処理はフレームを割り当てブロック的に実行される
-ensureは例外処理として実行されるものと通常の命令として実...
ふうむ、今回はかなり難解でした。それではみなさんもよいコ...
ページ名: