Plan 9 ソースコードでの例外機構 使い方編

Plan 9ソースコードを読んでいると以下のような関数が頻繁に出てきます.

  • error()
  • waserror()
  • poperror()
  • nexterror()

これらはC言語で例外をサポートするためにPlan 9で利用される関数です.
具体的には以下のように使います

  1 int func(){
  2         if( waserror() ){
  3                 recover_from_error();
  4                 return ERROR;
  5         }
  6 
  7         // below could err
  8         if( !do_something_has_error() ){
  9                 error("failed!");
 10 
 11         }
 12 
 13         poperror();                                                                                                                                                                             
 14         return SUCCESS;
 15 }  

recover_from_error()とdo_something_has_error()はコードを書く人が挿入した任意の処理だと考えてください. recover_from_error()はたとえば取得したリソースを解放したりと言ったことを行います. do_something_has_error()は仮に処理が上手くいかなかったら0を返す関数としましょう.


このときこのfunc()はどのようなフローになるでしょうか?waserror()は通常0を返す事になっています.このため最初のif節はスルーされることになります.

そうして8行目が実行されるわけですが, 仮にここでdo_something_has_error()が何らかの処理の結果, 0(失敗)を返したとします. 当然のことながら, if節が実行されその中でerror()が実行されますね.

この時, error()が呼び出されたらなんと次の瞬間には"直前の"waserror()の行まで戻る,という流れになっているのです. そしてこの時はwaserror()は1を返すようになります.このため, 先ほどはスルーされたif節が実行される事になるのです.

逆に以下のように書くこともできます.

  1 int func(){                                                                                   
  2         if( !waserror() ){
  3                 // below could err                                                            
  4                 if( !do_something_has_error() )
  5                         error("failed!");                                                                                                                                                       
  6                 poperror();   
  7                 return SUCCESS;
  8         }                     
  9                               
 10         recover_from_error(); 
 11                        
 12         return ERROR;  
 13 }

この場合, エラーが無ければ3-7行目が実行されますが, do_something_has_error()が0を返すと,
5行目の次に2行目に飛びます. このときwaserror()は1を返すので, if節の処理は実行されずに10行目のrecover_from_error()が実行されることになります.

共にRubyで類似コードを書くと以下のようになるでしょうか

  1 def func()                                                                                                                                                                                      
  2   if( !do_someghint_has_error() )
  3     raise "failed!"
  4   end
  5   return SUCCESS;
  6 rescue
  7   recover_from_error();
  8   return ERROR;
  9 end

これらの例外機構のもっともオソロシイ使い方はnexterror(), poperror()を併用したコード上での逆戻りです.nexterror()はerror()同様に"直前の"waserror()に飛ぶ関数であり, poperror()は例外を一つ消すものだと考えてください.

これらを用いると以下のような関数がかけます.

  1 int func(){                                                                                                                                                                                     
  2         a = alloc(sizeof(a));
  3         if( waserror() ){
  4                 free(a);
  5                 nexterror();
  6         }       
  7         
  8         // do something
  9         
 10         b = alloc(sizeof(b));
 11         if( waserror() ){
 12                 free(b);
 13                 nexterror();
 14         }       
 15         
 16         c = alloc(sizeof(c));
 17         if( waserror() ){
 18                 free(c);
 19                 nexterror();
 20         }       
 21         
 22         if( !do_something_has_error() ){
 23                 error("we go back");
 24         }       
 25         
 26         poperror();
 27         poperror();
 28         poperror();
 29         return SUCCESS;
 30 }       

正常に実行されていれば, 2→10→16→22→26行目というように処理が運びます.

一方で, 22行目で先ほどと同じように"エラー"とされerror()関数が呼び出された場合, 直前のwaserror()である17行目まで戻ります. その中でnexterror()が呼ばれていますが, これはerror()関数と同じく直前のwaserror()の行までジャンプします.

このため最終的な結果として, do_something_has_error()が"エラー"を起こすと, 18行目のfree(c), 12行目のfree(b), 4行目のfree(a)が順々に呼び出されることになります.
そして最後(コード上は最初)のwaserror()を含むif文ではさらにnexterror()を呼び出しています

  3         if( waserror() ){
  4                 free(a);
  5                 nexterror();
  6         }

これはさらにその外, func()を呼び出しているような関数に対して例外を投げています.
このためたとえばfunc()を呼び出す関数do_func()は以下のようになっている必要があります.

  1 int do_func(){
  2         if( !waserror() ){
  3                 func();
  4                 poperror();
  5                 return SUCCESS;
  6         }
  7 
  8         // tell something
  9         return ERROR;                                                                                                                                                                           
 10 }