Swift Digest
Blog | Swift.org Blog

Swift 5.9 のデバッグ改善

Debugging Improvements in Swift 5.9

このダイジェストはClaude Opus 4.7 / 4.8によって生成されたものです(License)。原文はこちら

この記事の要点

p / po の高速化と永続結果変数の抑制

LLDB には、変数を調べる p と、オブジェクトの debugDescription プロパティを呼び出す po という短縮コマンドがあります。これらはもともと、やや重量級の expressionp)と expression -Opo)のエイリアスでした。Swift 5.9 では、ppo は新しい dwim-print コマンドのエイリアスに定義し直されました。

dwim-print は、最もユーザーにとって扱いやすい方法で値を表示します。”DWIM” は “Do What I Mean”(言いたいことを汲み取る)の頭字語です。具体的には、変数を表示するとき、dwim-print は重い式評価器ではなく frame variablev)と同じ実装を使うため、表示が高速になります。

高速になっただけでなく、p$R0 のような 永続結果変数(persistent result variable)を作らなくなりました。これらはデバッグ中に使われないことが多いうえに、オーバーヘッドを生み、保持しているオブジェクトを retain し続けるため、プログラムの実行に予期しない副作用を与えることがありました。

ときどき永続結果が欲しい場合は、p の代わりに expression(あるいは expr のような一意な接頭辞)を直接使えます。常に永続結果を有効にしたいなら、LLDB のエイリアス機能を使って ~/.lldbinit に次を書いておけます。

command unalias p
command alias p dwim-print --persistent-result on --

dwim-printpo にも新しい機能を与えます。po は、生のアドレスを渡すだけでも Swift のオブジェクトを表示できるようになりました。po <オブジェクトのアドレス> を実行すると、LLDB に組み込まれた Swift コンパイラが内部的に unsafeBitCast(<オブジェクトのアドレス>, to: AnyObject.self) を評価し、期待どおりの結果を生成します。

Swift 5.9 より前:

(lldb) po 0x00006000025c43d0
(Int) 105553155867600

Swift 5.9 では:

(lldb) po 0x00006000025c43d0
<MyApp.AppDelegate: 0x6000025c43d0>

式の中でジェネリック型パラメータを参照する

LLDB は、式評価の中でジェネリック型パラメータを参照できるようになりました。たとえば次のコードで考えます。

func use<T>(_ t: T) {
    print(t) // break here
}

use(5)
use("Hello!")

use で止まった状態で po T.self を実行すると、1 回目の呼び出し経由なら Int、2 回目なら String と表示されます。

ジェネリックの具体的な型を表示できるだけでなく、これを使って具体的な型を条件にすることもできます。たとえば上のブレークポイントに T.self == String.self という条件を付けると、変数 tString のときだけ use で止まるようになります(この最後の例は Swift 5.9 ツールチェインの nightly ビルドでのみ動作します)。

より精密な lexical scope 情報

Swift コンパイラは、デバッグ情報により正確な lexical scope を出力するようになりました。これにより、デバッガが異なる変数をより正しく区別できます。たとえば次の例には x という名前の変数が複数あります。

func f(x: AnyObject?) {
  // 関数パラメータ x: AnyObject?
  guard let x else {}
  // ローカル変数 x: AnyObject。これは関数引数の x をシャドーする
  ...
}

Swift 5.9 では、コンパイラがより正確な ASTScope 情報を使って lexical scope の階層をデバッグ情報に生成するようになり、その結果デバッガの挙動にいくつかの変化があります。

次の例では、ローカル変数 agetInt() の呼び出し時点ではまだスコープに入っておらず、代入されて初めて参照できるようになります。以前のバージョンのコンパイラが生成したデバッグ情報では、getInt() の呼び出し時点で a の中身として未初期化メモリを表示してしまうことがありました。Swift 5.9 では、変数 a は初期化された後にだけ見えるようになります。

  1 func getInt() -> Int { return 42 }
  2
  3 func f() {
  4     let a = getInt()
                ^
  5     print(a)
  6 }

(lldb) p a
error: <EXPR>:3:1: error: cannot find 'a' in scope

(lldb) n
  3 func f() {
  4     let a = getInt()
  5     print(a)
        ^
  6 }

(lldb) p a
42

デバッグについて参加するには

Swift のデバッグや LLDB についてさらに学んだり、フィードバックを送ったり、ツール自体にコントリビュートしたりしたい場合は、Swift 開発フォーラムの LLDB セクション に参加できます。

関連リンク