バグとり その2

最適化のON/OFFのどちらでも起こる。
ASSERT の活用を開始したら。5分で。RETURN 命令で死んでいることが分かる。

やっとバグを psyntax を通さずに再現できた。

(display
 ((call/cc
   (lambda (outerk)
     (lambda ()
       (with-exception-handler
        (lambda (c)
          (
           (outerk
            (lambda () #t))))
        (lambda () (raise #f)))
          )))))

with-exception-handler と call/cc でキャプチャした継続でスタックが壊れたか?
もう少し掘り下げて最小再現状況を作ってみた。

(display
(call/cc
 (lambda (cont)
   (with-exception-handler
     (lambda (exception)
       (cont 3))
     (lambda () (raise #f))))))

これぞ最小。よしそろそろデバッグ方針を決めよう。

デバッグ方針を考える

Code Completeデバッグに関連するところを読み直す。

  • デバッグでコードを読むことは<コードの品質を評価する>チャンス。
  • 自己流デバッグ術の発明に頼らない
    • <憶測>で探すな
    • <問題>を理解しろ
    • <真っ先に思いついた>方法で修正するな
  • 科学的なデバッグ
    • <エラーを確実に再現させる>
    • エラーの原因を特定する。
    • 欠陥を再現するデータを集める
    • データを分析し欠陥に対する仮説を立てる
    • 欠陥を立証・反証する方法としてテストとコードのどちらが適切か判断
    • 立証・反証
    • 欠陥を修正する
    • 同様のエラーを探す
  • 焦って問題を解決することは時間をもっとも無駄にする行為の一つ

ふむ。読み返して良かった。


仮説:with-exception-handler の中で継続を restore するとスタックがずれる
立証・反証の方法:それぞれの状況で期待されるスタックの状態を考え、ASSERTを挿入する?


コードを読み直すと call/cc でキャプチャした継続への復帰でスタックポインタが書き換わるのに、それを with-exception-handler が上書きしていることが判明。
問題の根本は callClosure 系の関数がレジスタを勝手に保存・復帰する点だということかな。
いや違う with-exception-handler の仕様をきちんと把握できていないことが問題。そのせいで現在の実装が正しいか確信がもてない。

今後の方針

  1. with-exception-handler の仕様の把握。
    • 一切のごまかし無く完全に理解できるまで時間を投資する
  2. Gauche の with-exception-handler 周りの実装 call/cc と dynamic-wind あたりを熟読する
    • あわせて日本語のドキュメントを探す
  3. ypsilon も見てみよう。