WWDC Quick Look 💓 By SwiftGGTeam
Detect bugs early with the static analyzer

Detect bugs early with the static analyzer

元の動画を見る

ハイライト

Xcode 13 の静的アナライザーは、アプリを実行せずに Objective-C、C、および C++ コードをスキャンし、null 戻り値、アサーションの副作用、無限ループ、冗長条件、および C++ の移動/転送の使用エラーを早期に検出できます。

主要内容

Swift と Objective-C が混在するアプリの場合、一般的な問題のすべてをコンパイラーで回避できるわけではありません。

講演の例は、太陽系閲覧アプリです。プロジェクトに Objective-C メソッドがありますpositionAtDateの場合、メソッド宣言は null 以外 (空でない) 値を返します。 Swift コードがそれを呼び出します。

問題は、ある特定のswitchデフォルトのブランチは初期化されていませんposition。メソッドは最終的に戻りますnil。コンパイルは成功する可能性がありますが、テストではこのパスがカバーされない可能性があります。 Swift の実行中にこれを取得しますnil、動作は予測できません。

以前は、この種のバグを見つけるには、開発者は通常、クラッシュ レポートを待ち、ログを追加し、ユーザー パスを再現してから、コードに戻ってどのブランチが抜けていたかを推測する必要がありました。パスが稀であるほど、トラブルシューティングは遅くなります。

Xcode の Static Analyzer は、このステップを開発段階に前進させます。アプリを実行するのではなく、ソースコードを分析するだけです。制御フローとデータ フローに沿って変数の状態を推定し、まれなパスで論理エラーを見つけます。

Xcode 13 は検査の範囲を拡大し続けています。副作用、無限ループ、アサーション内の冗長な分岐条件、および C++ を検出できます。moveそしてforward使用上のエラー。

「問題を見つける」から「道筋を説明する」へ

静的アナライザーの価値は、警告を報告することだけではありません。

サンプル プロジェクトでは、Xcode の問題ナビゲーターによってイベントのリストが展開されます。表示された最後のイベントpositionはいnil。見上げる、regularPositionAtDate呼ばれなかった。検索を続けると、受信オブジェクトはデフォルトのブランチから開始されます。nil

開発者が取得できるのは、どのブランチに入り、どのオブジェクトが残るかという、読み取り可能なパスです。nil、どの呼び出しがスキップされたか、そして最終的に戻ってきた理由nil

修正も簡単です。default同じロジックを使用して分岐と球状 (球状) 分岐を実行し、分析を再実行します。

日々の開発にチェックを入れる

静的解析は手動で実行することも、ビルドに組み込むこともできます。

手動の方法は [Product] > [Analyze] で、ショートカットは Command-Shift-B です。 Xcode は、現在アクティブなスキーム内のすべてのターゲットのソース ファイルを分析します。

ビルドするたびに確認したい場合は、ビルド設定の「ビルド中分析」をオンにします。 Xcode は、インクリメンタル ビルドのように変更されたファイルのみを分析します。

プロジェクトが大きい場合は、浅いモード(浅い分析モード)に変更することもできます。これは高速ですが、複数の関数にまたがる問題を回避します。セキュリティに敏感なコードの場合、セキュリティ上の問題 (セキュリティ上の問題) 関連のチェックを個別にオンにすることができます。

詳細

静的アナライザーを実行します (01:21)

Apple が提供するエントリは Xcode メニューにあります。プロジェクトを開いた状態で、[Product] > [Analyze] を選択するか、Command+Shift+B を押します。

Xcode
└── Product
    └── Analyze    Command-Shift-B

キーポイント:

  • ProductXcodeのビルド関連メニューです。
  • Analyze静的アナライザーが開始されます。
  • Command-Shift-Bこれらは同じ操作のショートカット キーです。
  • 分析の範囲は、現在アクティブなスキーム内のすべてのターゲットのソース ファイルです。

このステップはビルドと似ていますが、目的が異なります。ビルドではコードがコンパイルされるかどうかがチェックされ、静的アナライザーではコード パスに潜在的なバグがないかチェックされます。

分析レポートのイベント チェーンを読んでください (02:41)

アナライザーが問題を見つけたら、問題ナビゲーターでレポートを展開できます。レポートには、バグの原因となった一連のイベントがリストされ、エディターでこれらのイベントに矢印が付けられます。

講演の中核となるパスは、次の最小限の Objective-C の例に整理できます。これは、草案の事実にそのまま対応しています。positionAtDateステートメントは null 以外を返しますが、デフォルトのブランチではposition保つnil

typedef NS_ENUM(NSInteger, BodyShape) {
    BodyShapeSpherical,
    BodyShapeIrregular,
    BodyShapeUnknown,
};

- (nonnull Position *)positionAtDate:(NSDate *)date {
    Position *position = nil;

    switch (self.shape) {
        case BodyShapeSpherical:
            position = [self regularPositionAtDate:date];
            break;
        case BodyShapeIrregular:
            position = [self irregularPositionAtDate:date];
            break;
        default:
            break;
    }

    return position;
}

キーポイント:

  • BodyShapeUnknown最初の 2 つによって選択されていないことを示しますcaseカバーされた形状。
  • position初期値はnil
  • BodyShapeSpherical支店から電話がかかりますregularPositionAtDate:初期化position
  • BodyShapeIrregularブランチは別のメソッドを呼び出して初期化しますposition
  • default直接分岐するbreakpositionまだnil
  • メソッドの戻り値の型は次のようにマークされます。nonnull、戻るnilそれはこの協定を破ることになります。

修正はデフォルトのブランチも初期化することですposition。講演で採用された修正は、デフォルトのケースを球状のケースと同じにすることでした。

- (nonnull Position *)positionAtDate:(NSDate *)date {
    Position *position = nil;

    switch (self.shape) {
        case BodyShapeSpherical:
        default:
            position = [self regularPositionAtDate:date];
            break;
        case BodyShapeIrregular:
            position = [self irregularPositionAtDate:date];
            break;
    }

    return position;
}

キーポイント:

  • defaultそしてBodyShapeSpherical同じロジックを共有します。
  • デフォルトブランチに入るとき、regularPositionAtDate:呼ばれます。
  • positionデフォルトの分岐に沿って留まらなくなりましたnil
  • 修正後、同じツールを使用して分析を再実行し、問題が解消されていることを確認します。

アサーションの副作用をチェックする (04:49)

Xcode 13 では、アサーションの副作用チェックが追加されています。逐語的なドラフトの例では、天体配列を横断し、衛星を含むオブジェクトの数を数え、その後、次のように使用します。NSAssert検証の数は惑星の数を超えません。

間違った書き方は、カウントの更新をアサーション式に入れることです。

NSUInteger objectsWithMoons = 0;

for (AstronomicalObject *object in objects) {
    if (object.moonCount > 0) {
        NSAssert(++objectsWithMoons <= planetCount,
                 @"Objects with moons must not exceed planets");
    }
}

キーポイント:

  • objectsWithMoons衛星が記録されたオブジェクトの数。
  • for天体の配列をループします。
  • object.moonCount > 0現在のオブジェクトに衛星があることを示します。
  • ++objectsWithMoonsカウンタが変更されますが、これは副作用です。
  • この副作用は次の場合に発生します。NSAssert表現の中で。
  • リリース ビルドではアサーションが無効になり、カウンターの更新が失われる可能性があります。

修正は、副作用をアサーションの外側に移動することです。

NSUInteger objectsWithMoons = 0;

for (AstronomicalObject *object in objects) {
    if (object.moonCount > 0) {
        objectsWithMoons += 1;
        NSAssert(objectsWithMoons <= planetCount,
                 @"Objects with moons must not exceed planets");
    }
}

キーポイント:

  • objectsWithMoons += 1独立した実行。アサーションが有効かどうかには依存しません。
  • NSAssert条件のみがチェックされます。
  • カウンターの更新は、デバッグ ビルドとリリース ビルドの間で一貫しています。 -同じ種類のチェックが C および C++ にも適用されますassert

無限ループをチェックする (06:02)

Xcode 13 では無限ループも検出できます。逐語的な原稿の例は、2 次元グリッドを埋めるときに、誤って内側のループが追加されてしまうというものです。value、実際のループカウンターをインクリメントせずにcolumn

int value = 0;

for (int row = 0; row < rowCount; row++) {
    for (int column = 0; column < columnCount; value++) {
        grid[row][column] = value;
    }
}

キーポイント: ・外部循環用row行を反復処理します。

  • 内部ループの条件依存関係column < columnCount
  • 内側のループ本体の記述grid[row][column]
  • 更新式はvalue++
  • columnこのままでは、常にループ条件が成立する可能性があります。
  • アナライザーはこのタイプのエラーを報告し、どの変数が期待どおりに更新されなかったかを説明します。

修正は段階的に行うことですcolumn

int value = 0;

for (int row = 0; row < rowCount; row++) {
    for (int column = 0; column < columnCount; column++) {
        grid[row][column] = value;
        value++;
    }
}

キーポイント:

  • column++内側のループを終了条件に向かって進めます。
  • grid[row][column]現在の行と列を引き続き書き込みに使用します。
  • value++ループ本体に移動して、次に書き込まれる値を生成します。
  • 制御変数とビジネス変数が分離され、人やツールがエラーを見つけやすくなります。

ビルドで自動的に実行 (06:47)

Xcode を使用すると、プロファイラーをビルド プロセスにプラグインできます。ビルド設定に移動しますanalysis関連する設定を検索し、「ビルド中の分析」を開きます。

Build Settings
└── Search: analysis
    └── Analyze During 'Build' = Yes

キーポイント:

  • Build Settingsプロジェクトのビルド設定エントリです。
  • 検索analysis静的解析関連のオプションのみを表示できます。
  • Analyze During 'Build'オンにすると、ビルドごとに分析がトリガーされます。
  • 分析プロセスは増分ビルドに似ており、変更されたファイルのみが処理されます。
  • ビルド時間に敏感なプロジェクトの場合は、浅いモードを選択できます。

単一ファイルのみを検査する場合は、「製品」>「アクションの実行」メニューから「単一ファイル分析」を選択できます。

Xcode
└── Product
    └── Perform Action
        └── Analyze <Current File>

キーポイント:

  • 単一ファイル分析は、実装ファイルが変更されたばかりの場合の迅速な検査に適しています。
  • ヘッダー ファイルを変更する場合に特に便利です。
  • このヘッダー ファイルをインポートするすべてのファイルの再解析を回避します。

重要ポイント

  • 内容: Objective-C と Swift のハイブリッド境界に対して null 以外の戻り値検査を実行します。 実行する価値がある理由: 講演中のバグは Objective-C で発生しましたnonnullメソッドの戻り値nil、Swift 呼び出し後にランタイム例外が発生する場合があります。 開始方法: Swift が呼び出す Objective-C API から開始し、[製品] > [分析] を実行し、空の戻り値と空の受信オブジェクトを持つレポートに優先順位を付けます。

  • 対処方法: アサーションをクリーンアップして、「状態の確認のみを行い、状態は変更しない」ようにします。 実行する価値がある理由: Xcode 13 で発見できることNSAssert、C assert、C++ assertこれらの副作用は、リリース ビルドでは解消される可能性があります。 開始方法: 検索NSAssertそしてassert、バンドル++、代入、メモリ書き込み、その他の操作はアサーションの前の行に移動されます。

  • 内容: 複雑なループの静的解析チェックを追加します。 実行する価値がある理由: 2 次元グリッド、ページ トラバーサル、およびステート マシン ループでは、制御変数とビジネス変数が混在することがよくあります。 Xcode 13 は、ループ変数が進行しないことによって引き起こされる無限ループを見つけることができます。 開始方法: ループ ロジックを変更した後、現在のファイルに対して [プロダクト] > [アクションの実行] の単一ファイル分析を実行し、最初に無限ループ レポートを処理します。

  • 内容: コア モジュールのビルド時に分析を自動的に実行します。 実行する価値がある理由: 静的分析は、まれなパスをカバーするためのテストを必要とせず、セキュリティの問題、ロジックの問題、API の誤用の早期検出に適しています。 開始方法: ビルド設定で検索しますanalysis、「ビルド中の分析」をオンにし、モジュールを押してディープ モードとシャロー モードのどちらを使用するかを決定します。

  • 対処方法: セキュリティに敏感なコードに対する特別なセキュリティ チェックをオンにします。 実行する価値がある理由: 講演では、アナライザーがセキュリティ問題をチェックでき、ログイン、支払い、ローカル ファイル、ネットワーク入力などのコードの処理に適していることが明確に述べられました。 開始方法: ビルド設定でセキュリティ関連のアナライザー チェックを選択的に有効にし、ノイズの多いチェックを個別にオフにします。

関連セッション

コメント

GitHub Issues · utterances