ORC は、OSX ツールチェーン上の C++ の 1 つの定義ルールの違反を見つけるためのツールです。
ORC は ELF をもじった DWARF をもじったものです。 ORC は頭字語です。 O はODR を表しますが、皮肉なことに、 RとC は複数の (おそらく矛盾する) 単語を表します。
C++ 標準そのものを含め、One Definition Rule (ODR) については多くの記事が書かれています。ルールの要点は、プログラム内でシンボルを定義する場合、そのシンボルは 1 回だけ定義できるということです。一部のシンボルにはこの規則の例外が認められており、複数回定義することが許可されています。ただし、これらのシンボルは同一のトークン シーケンスで定義されている必要があります。
一部のコンパイラ設定はトークン シーケンスにも影響を与える可能性があることに注意してください。たとえば、RTTI を有効または無効にすると、シンボル (この場合はクラスの vtable) の定義が変更される可能性があります。
上記のルールに違反するシンボルはすべて ODR 違反 (ODRV) です。場合によっては、リンカが重複したシンボル定義を捕捉し、警告またはエラーを発行することがあります。ただし、規格では、そのためにリンカーは必要ないと規定されています。アンディ G はそれを次のように説明しています。
パフォーマンス上の理由から、C++ 標準では、テンプレートに関して 1 つの定義ルールに違反した場合、その動作は単純に未定義であると規定されています。リンカーは気にしないため、このルールの違反は黙って行われます。ソース
非テンプレート ODRV も可能ですが、リンカーも同様にそれらについて沈黙する可能性があります。
ODRV は通常、シンボルを構築したコンパイル単位に応じてバイナリ レイアウトが異なるシンボルがあることを意味します。ただし、ルールにより、リンカが複数の定義に遭遇した場合、それらのいずれかを自由に選択し、それをシンボルのバイナリ レイアウトとして使用できます。選択したレイアウトがコンパイル単位内のシンボルの内部バイナリ レイアウトと一致しない場合、動作は未定義です。
多くの場合、このようなシナリオではデバッガーは役に立ちません。また、プログラム全体に対して 1 つのシンボル定義を使用することになるため、ODRV をデバッグしようとすると、デバッガによって不正なデータが提供されたり、ファイル内の正しくないと思われる場所が指定されたりする可能性があります。最終的に、デバッガーは嘘をついているように見えますが、根本的な問題が何であるかについては何も手がかりを与えません。
すべてのバグと同様、ODRV の修正には時間がかかるのに、なぜテスト済み (そしておそらく動作している) コード内の ODR 違反を修正する必要があるのでしょうか?
ORC は次のことを実行するツールです。
ツールにバグがない限り、ORC は誤検知を生成しません。報告するものはすべて ODRV です。
現時点では、ORC は 1 つの定義ルールの違反の可能性をすべて検出しているわけではありません。私たちは、時間の経過とともに、捕捉できる内容を拡大し、改善していきたいと考えています。それまでは、ORC は貴重なチェックではありますが、クリーン スキャンによってプログラムに ODRV がないことが保証されるわけではないことを意味します。
ORC は以下を見つけることができます:
vtable に関する注意: ORC は、異なるスロットにある仮想メソッドを検出します。 (これはひどい破損プログラムです。) この時点では、ODR に違反している重複クラスの「スーパーセット」である仮想メソッドを持つクラスは検出されません。
主要な ORC ソースに加えて、ツールがキャッチする必要がある ODRV を含む一連のサンプル アプリケーションを提供するよう努めています。
ORC はもともと macOS 上で考案されました。現在の実装はそこに焦点を当てていますが、そのツールチェーンに制約される必要はありません。
ORC は cmake によって管理され、CMake が管理するプロジェクトの一般的なビルド規則を使用してビルドされます。
mkdir build
cd build
cmake -GXcode ..
テスト目的で ORC が統合されるサンプル アプリケーションがいくつかあります。これらは、Xcode のターゲット ポップアップから選択できます。
ORC は、プロファイリング ツールとして Tracy を使用しており、デフォルトで有効になっています。 Tracy を無効にするには、cmake コマンド ラインを次のように指定します。
cmake .. -GXcode -DTRACY_ENABLE=OFF
プロファイリングが無効になっている場合でも、Tracy 依存関係は必要です (ランタイムからコンパイルされます)。このオプションはキャッシュされるため、明示的にOFF
またはON
にする必要があることに注意してください。オプションを指定せずにコマンド ライン呼び出しを再実行すると、以前の値が使用されます。
ORC はコマンド ラインから直接呼び出すことも、リンカー ステップでツール チェーンに挿入することもできます。出力は変更されません。それは単にワークフローの利便性の問題です。
このモードは、リンカー コマンドとその引数があり、実際のビルドとは別に ODRV を検索したい場合に便利です。
設定ファイル (以下を参照)
'forward_to_linker' = false
'standalone_mode' = false
XCode からのld
コマンド ライン引数が必要です。 Xcode でビルドし (リンクできない場合、ORC は役に立ちません)、リンク コマンドをコピーして、ORC 呼び出しの後に貼り付けます。次のようなもの:
/path/to/orc /Applications/Xcode.app/Contents/Developer/Toolchains/XcodeDefault.xctoolchain/usr/bin/clang++ -target ... Debug/lem_mac
(これは膨大なコマンドラインなので、ここでは省略します。)
ORC が実行され、ODR 違反がコンソールに記録されます。
ORC が処理するライブラリ ファイルのリストがある場合は、それも実行できます。
設定ファイル (以下を参照)
'forward_to_linker' = false
'standalone_mode' = true
このモードでは、ライブラリ ファイルのリストを ORC に渡して処理するだけです。
設定ファイル (以下を参照)
'forward_to_linker' = true
'standalone_mode' = false
Xcode ビルド プロジェクト内で ORC を使用するには、次の変数を ORC スクリプトへの完全修飾パスでオーバーライドします。
"LIBTOOL": "/absolute/path/to/orc",
"LDTOOL": "/absolute/path/to/orc",
"ALTERNATE_LINKER": "/absolute/path/to/orc",
これらの設定を行うと、Xcode はプロジェクト ビルドのlibtool
とld
フェーズの両方のツールとして ORC を使用する必要があります。 forward_to_linker
設定により、ORC は適切なリンク ツールを呼び出してバイナリ ファイルを生成します。それが完了すると、ORC はスキャンを開始します。
他の設定の中でも特に、ORC は、ODRV が検出されたときに終了するか、単に警告するように構成できます。
ORC は現在のディレクトリを上に移動し、次のいずれかの名前の設定ファイルを探します。
.orc-config
、または_orc-config
見つかった場合、多くのスイッチで ORC のロジックを制御できます。例については、リポジトリの_orc_config
を参照してください。 ORC は.orc-config
優先するため、元の_orc_config
をコピーして.orc-config
の値をローカルに変更するのは簡単です。
例えば:
error: ODRV (structure:byte_size); conflict in `object`
compilation unit: a.o:
definition location: /Volumes/src/orc/extras/struct0/src/a.cpp:3
calling_convention: pass by value; 5 (0x5)
name: object
byte_size: 4 (0x4)
compilation unit: main.o:
definition location: /Volumes/src/orc/extras/struct0/src/main.cpp:3
calling_convention: pass by value; 5 (0x5)
name: object
byte_size: 1 (0x1)
structure:byte_size
は ODRV カテゴリとして知られており、このエラーがどのような種類の違反を表すかを正確に説明します。次に、衝突している 2 つのコンパイル単位が、衝突の原因となった DWARF 情報とともに出力されます。
struct object { ... }
In ao:
とIn main.o
には、一致しない 2 つのオブジェクト ファイルまたはアーカイブがあります。 ODR は、これらのアーカイブをコンパイルする際のコンパイル設定または #define 設定の不一致によって発生する可能性があります。 byte_size
エラーの原因となる実際の値です。
definition location: /Volumes/src/orc/extras/struct0/src/a.cpp:3
オブジェクトが宣言された行とファイル。つまり、この例ではa.cpp
の 3 行目です。
ORC のバージョンが同じで入力が同じ場合、ORC は常に同じ出力を書き込みます。 「同じ」とはバイトが同一であり、diff ツールでは違いが表示されません。
高度にマルチスレッド化されたアプリケーションでは、一貫した出力を達成する (そしておそらく維持する) ことは驚くほど困難です。
ただし、これは ORC の異なるバージョンには適用されないことに注意してください。 ORC を変更すると、ほぼ確実に出力が変更されます。
また、入力ファイルの「小さな」変更が ORC 出力の「小さな」変更を保証するという保証もありません。この動作は望ましいものであり、将来的には改善される可能性があります。
orc_test
) ORC がキャッチすべきものをキャッチしていることを確認するために、単体テスト アプリケーションが提供されています。 orc_test
既知のソースからオブジェクト ファイルを生成して既知の ODR 違反を生成する小型の「ビルド システム」を導入します。次に、ORC コマンド ライン ツールと同じエンジンを使用してオブジェクト ファイルを処理し、結果を予想される ODRV レポート リストと比較します。
バッテリー内のすべての単体テストは個別であり、以下が含まれます。
odrv_test.toml
、テストのパラメータを記述する高レベル TOML ファイル一般に、1 つのテストで 1 つの ODR 違反が検出されるはずですが、これはすべてのケースで可能であるとは限りません。
これらのファイルは標準の C++ ソース ファイルです。それらの量とサイズは非常に小さく、意図した ODRV を引き起こすのに必要なだけ大きくなければなりません。
odrv_test.toml
ファイル設定ファイルは、どのソースをコンパイルする必要があるか、どのコンパイル フラグをテストに使用する必要があるか、生成されたオブジェクト ファイルをリンクした結果としてシステムがどの ODRV を監視する必要があるかをテスト アプリケーションに記述します。 ) 一緒に。
テスト ソースは[[source]]
ディレクティブで指定します。
[[ source ]]
path = " one.cpp "
obj = " one "
flags = [
" -Dfoo=1 "
]
path
フィールドには、 odrv_test.toml
を基準としたファイルへの相対パスが記述されます。これは唯一の必須フィールドです。
obj
フィールドは、作成される (一時的な) オブジェクト ファイルの名前を指定します。この名前を省略した場合は、擬似ランダムな名前が使用されます。
flags
フィールドは、このコンパイル単位に特に使用されるコンパイル フラグを指定します。このフィールドを使用すると、同じソース ファイルを異なるコンパイル フラグで再利用して ODRV を引き出すことができます。
ODRV は[[odrv]]
ディレクティブで指定します。
[[ odrv ]]
category = " subprogram:vtable_elem_location "
linkage_name = " _ZNK6object3apiEv "
category
フィールドには、テスト アプリが検出する必要がある特定のタイプの ODR 違反が記述されます。
linkage_name
フィールドには、ODRV を引き起こした特定のシンボルが記述されます。現在は使用されていませんが、テスト アプリが成熟するにつれて強制される予定です。
次のフラグは現在使用されていないか、単体テスト アプリが成熟し続けるにつれて大幅に変更される予定です。
[compile_flags]
: 単体テストのすべてのソース ファイルに適用される一連のコンパイル フラグ。
[orc_test_flags]
: このテストのためにテスト アプリに渡す一連のランタイム設定。
[orc_flags]
: このテストのために ORC エンジンに渡す一連のランタイム設定。