
「静的コードアナライザーについての意見は残念です」という記事を書いたので、私たちは声を上げて落ち着いてトピックを手放す予定でした。しかし、意外にも、この記事は嵐の反応を引き起こしました。残念ながら、議論はうまくいかなかったので、今度は状況のビジョンを説明するためにもう一度試みます。
逸話-アナロジー
それで、それはすべて「静的コードアナライザーについての意見は残念です」という記事から始まりました。それはいくつかのリソースで活発に議論され始めました、そしてこの議論は次の古い逸話に非常に似ています。
シベリアの過酷な木こり用に日本のチェーンソーを購入しました。1対1のストーリー。人々はコードを見ました:
材木ジャックは輪になってそれをテストすることにしました。
彼らは彼女を連れてきて、彼女に木を与えました。
「ジッパー」と日本人は見た。
「ああ、ファック...」-ランバージャックは言った。
彼らは彼女にもっと太い木を滑らせた。「Vzh-g-zhik!」-のこぎりは言った。
「わあ、ファック!」-木こりは言った。
彼らは厚い杉を彼女に滑り込ませた。「VZH-ZH-ZH-ZH-ZH-ZH-ZHIK !!!」-のこぎりは言った。
「わあ、ファック!!」-木こりは言った。
彼らは彼女に鉄片を滑り込ませた。"亀裂!" -のこぎりは言った。
「うん、ファック!!!」-過酷なシベリアの木こりが非難した!そして彼らは斧で森を切るために去りました...
if (A[0] == 0)
{
X = Y;
if (A[0] == 0)
....
}
そして、彼らはそれが正当化されるかもしれない状況を発明し始めました、それはPVS-Studioアナライザーの警告が偽陽性であることを意味します。以下から生じる2つのチェック間のメモリの変化についての推論がコースに入りました。
- 並列ストリームの作業。
- シグナル/割り込みハンドラー;
- 変数Xは要素A [0]への参照です。
- DMA操作の実行などのハードウェア。
- 等
そして、分析者が理解できるすべての状況ではないことを議論したので、彼らは軸で森を切り刻むために去りました。つまり、彼らは自分たちの仕事で静的コードアナライザーを使用し続けることができない理由を見つけました。
状況に対する私たちのビジョン
このアプローチは逆効果です。不完全なツールが役立つ可能性があり、その使用は経済的に実行可能です。
はい、静的アナライザーは誤検知を生成します。そして、それについては何もできません。しかし、この不幸は非常に誇張されています。実際には、静的分析装置を構成することができ、偽陽性と抑制して作業するための様々な方法で使用される(参照1、2、3、4)。さらに、ここで「誤検知は私たちの敵ですが、それでもあなたの友達かもしれません」という記事を思い出すのが適切です。
しかし、これでも主なことではありません。エキゾチックなコードの特殊なケースを検討するのは意味がありません!アナライザーを複雑なコードと混同できますか?はい、できます。ただし、そのようなケースの1つとして、何百もの有用なアナライザートリガーがあります。多くの間違いは、非常に早い段階で見つけて修正することができます。また、1つまたは2つの誤検知を安全に抑制でき、それらに注意を払う必要がなくなります。
そして再びPVS-Studioは正しい
ここで記事を完成させることができます。ただし、前のセクションを合理的な考慮事項ではなく、PVS-Studioツールの弱点と欠点を隠そうとする人もいます。だからあなたは続けなければなりません。変数宣言を含む
具体的なコンパイル済みコードについて考えてみます。
void SetSynchronizeVar(int *);
int foo()
{
int flag = 0;
SetSynchronizeVar(&flag);
int X, Y = 1;
if (flag == 0)
{
X = Y;
if (flag == 0)
return 1;
}
return 2;
}
PVS-Studioアナライザーは合理的に警告を発行します:V547式 'フラグ== 0'は常に真です。
そして、アナライザーは絶対に正しいです。誰かが変数が別のスレッドやシグナルハンドラーなどで変更される可能性があると怒鳴り始めた場合、その人は単にCおよびC ++言語を理解していません。そのように書くことはできません。
最適化の目的で、コンパイラには2番目のチェックを破棄する権利があり、完全に正しいものになります。言語の観点から、変数は変更できません。その背景の変化は、未定義の動作にすぎません。
チェックを所定の位置に維持するには、変数を揮発性として宣言する必要があります。
void SetSynchronizeVar(volatile int *);
int foo()
{
volatile int flag = 0;
SetSynchronizeVar(&flag);
....
}
PVS-Studioアナライザーはこれを認識しており、そのようなコードに対して警告を発行しなくなりました。
ここで、最初の記事で説明した内容に戻ります。問題はない。しかし、なぜアナライザーが警告を発する権利を持っているのかという批判や誤解があります。
最も細心の注意を払った読者への注意
一部の読者は、最初の記事の合成例に戻る可能性があります。
char get();
int foo(char *p, bool arg)
{
if (p[1] == 1)
{
if (arg)
p[0] = get();
if (p[1] == 1) // Warning
return 1;
}
// ....
return 3;
}
そして揮発性を追加します:
char get();
int foo(volatile char *p, bool arg)
{
if (p[1] == 1)
{
if (arg)
p[0] = get();
if (p[1] == 1) // Warning :-(
return 1;
}
// ....
return 3;
}
その後も、アナライザーが警告を発行していると言っても過言ではありません。V547式 'p [1] == 1'は常に真です。
やったー、アナライザーがまだ間違っていることがついに示された:)。これは誤検知です。
ご覧のとおり、欠陥は隠されていません。配列要素のデータストリームを解析するときに、この不運な揮発性物質が失われました。欠陥はすでに発見され、修正されています。この修正は、アナライザーの次のバージョンで利用できるようになります。誤検知はありません。
このバグが以前に特定されなかったのはなぜですか?実際、これも非現実的なコードであり、実際のプロジェクトには見られないためです。実際、多くのオープンソースプロジェクトをチェックしましたが、これまでそのようなコードに出くわしたことはありません。
コードが非現実的なのはなぜですか?まず、実際には、2つのチェックの間に何らかのタイミングまたは遅延機能があります。第二に、絶対に必要でない限り、彼らの正しい心の誰も、揮発性要素からなる配列を作成しません。このようなアレイを使用すると、パフォーマンスが大幅に低下します。
まとめましょう。パーサーが失敗する例を作成するのは簡単です。しかし、実用的な観点からは、特定された欠陥は、コード分析の品質や検出された実際のエラーの数に実質的に影響を与えません。結局のところ、実際のアプリケーションのコードは、分析者と人が同時に理解できるコードであり、リバスやパズルではありません。コードがパズルの場合、アナライザーの時間はありません:)。
清聴ありがとうございました。

追加のリンク
- 静的コードアナライザーをレガシープロジェクトに実装し、チームの意欲を削ぐ方法。
- 追加の診断設定。
- EFLコアライブラリの例によるPVS-Studioアナライザの特性、誤検知の10〜15%。
- バグを探すのではなく、静的分析をプロセスに注入します。

この記事を英語を話す聴衆と共有したい場合は、翻訳リンクを使用してください:AndreyKarpov。パート2:静的アナライザーに関する意見の混乱。