インコヒーレントなインストゥルメンテーションとは?

このブログは「What is incoherent instrumentation?」を翻訳・一部加筆したものです。

はじめに

コードカバレッジツールCocoには、共通の原因を持つ2つの関連した現象があります:

  • CoverageBrowserやHTMLレポートでは、ファイル名を数字の接頭辞で区別して、複数のファイルを見ることができます。例えば、"header.h"という名前のファイルが、"header.h#1"や"header.h#2"、あるいはそれ以上のバージョンで表示されます。

  • コンパイル中、Cocoは次のように始まる警告を表示します。 

Warning (Squish Coco): Instrumentation of source file 'myproject/header.h' is different. 

この行の後には、問題の原因をより詳しく説明する警告行がいくつか続きます。

どちらもインコヒーレントなインストゥルメンテーションの例で、ファイルが2回以上コンパイルされるが、その方法は異なることに起因します。

この記事では、これが何を意味するのか、どのようにして起こるのか、そしてどのように防ぐことができるのかを説明します。

警告[warning]は何を意味するのか? 

この警告の最終的な原因は、C/C++プリプロセッサにあります。これはコンパイラのコンポーネントで、機械語への翻訳が始まる前に、コンパイルの最初にソースコードの一部を置き換えます。CやC++のコードは、しばしば複数の異なるプラットフォーム上で実行される必要があるため、これは必要なものです。プリプロセッサを使えば、ソースコードにプラットフォーム固有の定数を持たせ、コンパイル時にのみその値を設定したり、同じコードでも異なるプラットフォーム用に複数のバージョンを持たせたり、コンパイル時にのみ適切なものを選択したりすることができます。

 

#ifndef HEADER_H 

#define HEADER_H 

static inline char letter() 

{ 

#ifdef A 

    return 'a'; 

#else 

    return 'b'; 

#endif 

} 

#endif 

header.hというヘッダーファイルには、プリプロセッサマクロAが定義されているかどうかによって動作が異なる関数があります。

A diagram of a diagram

Description automatically generated

header.hファイルは、a.cppとb.cppという2つの異なるソース・ファイルにインクルードされています。これらのファイルを異なるコマンド・ライン・オプションでコンパイルし、後で共通のメイン・プログラムにマージすると、インストゥルメンテーションがインコヒーレントになります。

 

意図的であろうとなかろうと、同じプログラム内で同じソース・ファイルを2度、異なるプリプロセッサ・オプションでコンパイルすることさえ可能です。その結果、通常は異なるインストルメンテーションを必要とする2つの異なるサブプログラムが生成されます。例えば、元のファイルが "header.h "であれば、"header.h#1 "と "header.h #2 "と呼ばれるファイルが生成されます。これらの番号付けされたファイル名は、次の様にCoverageBrowserやHTMLレポートに表示されます。

 

header.hファイルはCoverageBrowserに2回表示されます 

警告はいつ表示されるのか? 

ファイルをコンパイルするのは一度だけでよく、したがってこの種の問題はめったに起こらないであろうと思うかもしれません。しかし、同じファイルが何度もコンパイルされることは、現実的なプロジェクトでは必ず起こります。その主な原因は、ヘッダー・ファイルにあります。ヘッダー・ファイルは、複数の異なるソース・ファイルにインクルードされることを意図しており、例えばインライン関数の中にインストルメンテーション可能なコードが含まれていることがあります。そのため、あるヘッダー・ファイルが異なるプリプロセッサ・オプションで異なるコンパイラを実行したときに2度読み込まれると、異なるインストルメンテーションが行われる可能性があります。

普通のソース・ファイル(末尾が".cpp"のもの)が2度コンパイルされることはもっと稀ですが、そういうこともあります。マルチプラットフォーム・コンパイルとは別に、ファイルが異なる実行ファイルで使われ、それぞれ別々にコンパイルされることもあります。ほとんどの場合、これはファイルをライブラリに入れることで回避できますが、異なるパラメータで2回コンパイルすることで、意図したとおりになることもあります。

なぜプロジェクトでインコヒーレントなインストゥルメンテーションが行われるのか?

大規模なプロジェクトでは、ビルド・プロセスの一部に異なるコマンドライン・オプションが存在することが簡単に起こり得ます。あるいは、IDEを使用している場合、コンパイルオプションの多くを独自に設定し、すべての異なるソースファイルのコマンドラインオプションの概要を提供していないかもしれません。プロジェクトが十分に大きければ、CMakeのような自動ビルドシステムでも同じことが言えるかもしれません。

別の原因として考えられるのは、デバッグビルドとリリースビルドが混在していることです。例えば、典型的なリリースビルドでは何もコンパイルされないASSERTマクロに影響があります。また、デバッグビルドとリリースビルドの間で変更する場合、システムヘッダーファイルのコンパイルに目立たない変更があるかもしれません。

しかし、特にプロジェクトが非常に大きく、使用する外部ライブラリのビルド要件に準拠しなければならない場合など、プロジェクトの部分ごとに異なるコンパイラ設定をしなければならない理由もあります。

インコヒーレントなインストゥルメントがもたらすデメリットとは?

ファイルが複数回コンパイルされ、異なるインストゥルメンテーションが行われた場合、Cocoはこれらのインストゥルメンテーションを異なるファイルとして扱います。これは、ファイルの各バージョンを別々にカバーする必要があることを意味し、完全なカバレッジに到達することが難しくなります。

ヘッダーファイルの各バージョンのコードは、プログラムコードのサブセットでのみ使用されます。したがって、あるバージョンのヘッダーファイルをフルカバレッジするためには、この特定のバージョンのヘッダーファイルを使用するコンパイル済みコードのサブセットを正確に実行するテストを書く必要があります。そのようなテストを書くのは、もしインコヒーレントがコンパイラのオプションの「ランダムな」違いによって引き起こされるのであれば、難しいでしょう。しかし、異なるバージョンのヘッダーファイルが異なるサブモジュールに属していたり、異なるプラットフォームで実行されるコードに属していたりする場合は、特に問題はありません。

インスツルメンテーションされたヘッダーファイルがインコヒーレントであることは、少なくともそれが予期せぬインコヒーレントである場合は、コードに問題がある可能性を示しています。ヘッダーファイルは、プログラムの異なる部分間のインターフェイスを提供し、そのようなファイルがコード内の異なる場所で異なって翻訳された場合、それらの間の通信は、後で見つけることが困難な方法で失敗する可能性があります。

インコヒーレントなインストルメントを防ぐためにはどうすればいいのか? 

Cocoには、不一致を見つけるための方法がいくつか用意されています。まず、インスツルメンテーションされたビルドのコンパイラー出力から、同じファイルの2つのインスツルメンテーションが不一致のままマージされたときに発生する警告を探すことができます。また、同じファイルの異なるビルドのコマンドラインオプションも報告されます。これによって、インコヒーレントの原因となったビルドスクリプトの一部を見つけて修正することができるかもしれません。実際のビルド・スクリプトを変更するには、使用されている特定のビルド・システムに関する知識が必要ですが、Cocoは一貫性のないインストルメンテーションを引き起こした可能性のある、一貫性のないコンパイル・フラグに関する情報を提供してくれます。

完全に首尾一貫したビルドを実現するために、Cocoのコマンドラインオプション -cs-warnings=error を設定することもできます。これにより、Cocoが警告を発した場合にビルドが失敗し、問題が発生したときに自動的に通知されます。ただし、他の警告が発生してもビルドが失敗するため、デメリットとして考えられます。

もうひとつの方法は、よりローカルなアプローチで、各ファイルについて、非干渉性が問題かどうかを個別に判断することです。この場合、CoverageBrowser を使って、インストゥルメントが非干渉なファイルを検出することができます。これらのファイルは、"Sources" ウィンドウのファイルリストに番号の付いたサフィックスで表示されます。ファイル名をクリックすると、ビルドのコマンドラインパラメータが "Explanation" ウィンドウに表示されます。

 

header.h#1ファイルのコンパイラー・オプション

コード品質への活用 

インコヒーレントなインストゥルメンテーションは、プログラムが複数のプラットフォームでビルドされている場合など、予想されるケースもあるが、コードに問題があることを示すケースもあります。ヘッダーファイルが2つの異なる方法で読み込まれ、関数を呼び出すコードと関数のコードが正しく相互作用できないことを意味する場合もあります。また、不必要な数のテストを行わなければ完全なコードカバレッジに到達できないことを意味する場合もあります。いずれにせよ、定期的に不一致をチェックすることは、コードの品質向上に役立ちます。

コードカバレッジ分析ツールCocoを利用したい方は、ぜひQtの無料トライアルを開始してみてください!また、Qtについてご質問あり場合は、お気軽にお問い合わせください。


Blog Topics:

Comments