最低限の Smalltalk デバッガ入門(Squeak システム向け)
Smalltalk ではスタックフレームも「コンテキスト」と呼ばれるオブジェクトです。ちなみに、実行中のコンテキストに容易にアクセスできるようにわざわざ thisContext という擬変数まで用意されています。Smalltalk には予約語は全部でたった6つしかない(他は self、super、nil、true、false)うちのひとつを使うわけですから、これはある意味、破格の扱いです。
こうした背景もあってか、はたまた私の単純な思い込みでか、Smalltalk のデバッガは「コンテキスト(や、その連なりによって表現されるコールスタック)をブラウズするためのツール」という性格が強いように感じられます。クラス用ブラウザがクラスブラウザ、インスタンス用がインスペクタなら、デバッガはコンテキスト向けに特別に用意された“第三のブラウザ”といったところでしょうか。
▼デバッガの起動
デバッガを起動するにはまずノーティファイアを呼び出します。ちなみに、Smalltalk ではデバッガを使うにあたって、モードの切り替えや特別なビルド操作のようなものは必要ありません(もっとも Edit-Build-Debug といった開発サイクル自体が Smalltalk にはないわけですが…)。
ノーティファイアは、割り込み操作(alt/cmd + ピリオド)や例外時、また、任意のオブジェクトへの「halt」メッセージの送信(通常は、ブレイクポイントを設置したい式のレシーバの直後に halt と挿入しておけばよいでしょう。モダンな Smalltalk 処理系と違い、Squeak Smalltalk ではブレイクポイントをハンドコードして指定する必要があります)により画面に現われるやや小さめのウインドウで、三つのボタンとコールスタックの一覧からなります。
デバッガは、このノーティファイア上段右端の Debug ボタン、もしくは、下に列挙されたコンテキストから任意のものをクリックすることで起動できます。
たとえばワークスペースなどで次式を do it(alt/cmd + d)してみてください。ノーティファイアが現われたら UndefinedObject>>DoIt をクリックします。
(1 halt to: 3 + 4) do: [:each | each factorial]
ちなみにノーティファイアにある他のボタンは、Proceed ボタンが処理の続行で、Abandon ボタンは処理の中断に使います(後者はノーティファイアをウインドウのクローズボタンをクリックして閉じるのと一緒)。
コンテキストのクリックと同時にノーティファイアが閉じ、入れ替わりにデバッガが起動します。
ノーティファイアやデバッガにおいて(いや。これらに限らずシステム内において…)コンテキストは原則として「レシーバのクラス名(実際にメソッドが属するクラス名)>>実行中のメソッド名」の形式で表記されます。ブロックでは、若干表記が変わります([] in …)が、その扱われ方はほとんど変わりません。
また、ここでの例のように、組み込みのエディタなどで入力・選択した式やスクリプトを do it や print it でインタラクティブに評価した場合には、評価した式やスクリプトを本体に持つ #DoIt という名前のメソッドの実行として、通常のメソッド呼び出しと同様に扱われます。
これは、評価のための操作と同時に、その文脈での self のクラス(たいていは self は nil なのでそのクラスの UndefinedObject)に #DoIt という名前のメソッドとして定義され(そう。Smalltalk も Ruby 同様オープンクラスで、普段からこの機能を大いに活用しています)、self へのメッセージ「DoIt」の送信で呼び出されたあと、自動的に削除される …のだと解釈するとよいでしょう(実際にもそれに近いことをしています)。
▼ステップ実行
上のコンテキスト一覧からブラウズしたいコンテキストをクリックして選ぶと(あるいはデバッガ起動時にあらかじめノーティファイア上で選んでおくと)、そのコンテキストで実行中のメソッドのソースコードが中央の枠内に表示されます。
このとき、次に実行されるポイントが選択された状態になっていますが、単純にテキストが選択状態にあるだけなので、ちょっとしたマウスやキーボードの操作で簡単に解除されてしまいます。そんなときは、右端の Where ボタンで元の選択状態に戻すことが可能です。
ステップ実行操作のためのボタンとして、中央付近に並んだ Into、Over、Through の三種類があります。
Over ボタンは単純にワンステップの実行のみを行ないます。
Into ボタンは、次に新たに作られるコンテキストにブラウズ対象を切り替えます。
Through ボタンの動きは Over ボタンとよく似ていますが、do: [...] のようなブロック付きメソッドの呼び出しの際に、これを Over のときのようにワンステップにまとめてしまわずに、引数のブロック内の処理もステップ実行してくれます。
そのメソッドに潜りたいときは Into を使い、その必要がなければ Through で。ただしループをいちいち走査する必要がないときは Through の代わりに Over を…というように使い分けるとよいでしょう。
いずれのボタンも、メソッド中の最後の式まで実行を終えると同時に、ブラウズ対象を、呼び出し元のコンテキストに切り替えます。これまでブラウズしていたコンテキストは通常の実行時と同様、原則として破棄されるので、もうそこには戻れません。
Restart ボタンを押すと、ブラウズ中のコンテキストの実行ポイントをメソッドの最初の式に戻せます。
ただしこのとき、self や関連オブジェクト(self のインスタンス変数に代入されたオブジェクトたち)の状態はリセットされないので注意しないといけません。self の状態も戻さないといけない場合(self が状態を持つオブジェクトの場合たいていはそうですが…)は、次項のデバッガ備え付けのインスペクタで self の状態を編集するか、そのオブジェクトを生成しているもっとも前のコンテキストに戻ってそこからやり直す(そのコンテキストをブラウズした状態で Restart し、あらためて Into で目的のコンテキストまで戻ってくる)必要があります。
コールスタックの深いところにあるはずのコンテキストにアクセスしたいのに、なんらかの理由で表示されていないようなときは、Full Stack ボタンを押すと、それらをリストの下の方に追加できることがあります。
Proceed ボタンは(ノーティファイアのそれと同様に)デバッガを閉じて処理を続行します。
▼そのコンテキストにおける self のインスタンス変数とテンポラリ変数のインスペクタ
デバッガの最下部の左右にあるのは、そのコンテキストの self(メッセージを受けて該当メソッドをコールしたオブジェクト。レシーバ)とそのコンテキスト自身(thisContext)のインスペクタです。
左の self のインスペクタでは、通常のインスペクタ同様に、self が指し示すオブジェクトと、それが保持するインスタンス変数の一覧(all inst vars)、および、各インスタンス変数の個別のブラウズが可能です。
同様に、右の thisContext のインスペクタでは、thisContext と、それが保持するテンポラリ変数の一覧(all temp vars)、および、各テンポラリ変数の個別のブラウズが可能です。
通常のインスペクタ同様に、向かって左側のリスト枠内をクリックすると内容が右側に表示でき、その状態で Smalltalk 式をタイプして入力してから accept(alt/cmd + s)すれば、選択したインスタンス変数、あるいは、テンポラリ変数の内容を変更することもできます。
▼実行中メソッドのソースコードの編集と実行の継続
デバッガ中央のソースコード表示枠は、クラスブラウザと同等のソースコード編集機能も備えています。よって、表示されている実行中のメソッドを修正して accept(alt/cmd + s)すれば、コールスタックはそのままにしてメソッドだけ差し替えて処理を続行させることが可能です。Java 1.4 や Xcode 2.1 が登場したころに話題になった Fix and Continue とか 、Microsoft では Edit and Continue とかいわれるものですね。
図では factorial 送信時に Into したあと、そこで組み込みの #factorial を元の再帰呼び出し版から #inject:into: を使ったループ版に書き換えて、実行を継続する様子を示しています。
既存のメソッドに加えた変更はクラスブラウザを用いた通常のメソッド編集時と同様に永続化されることに注意してください。たとえばデバッグのために処理を追加、あるいはコメントアウトで一部無効化した場合でも、これらの変更はデバッガを終了したあとも保持され、自動的に取り消されることはありません。自分で元に戻す必要があります。
ただし、#DoIt なコードの場合は例外で、通常のメソッド同様に変更は可能ですが、エディタなどで選択して評価した元のテキストまでは変更は及びません(たとえば、最初の例にある式で、デバッガ内で 1 halt の halt を削って accept してみると分かります)。
手を加えたメソッドを元に戻すには、バージョンブラウザ(デバッガの上のコールスタック表示枠内の第二ボタンメニューから versions を選ぶか alt/cmd + v をタイプして呼び出せる)を使うのが便利です。