データフロー分析を使用したIDEA Ultimateコードのバキューム

IntelliJ IDEAには、Javaコードの数千の​​検査が含まれています。 それらのほとんどは高度な正規表現として機能します。特定のパターンに従って、タイプミスのように見える、冗長、い、またはゆっくりと動作する可能性のあるプログラムフラグメントを探します。 しかし、まったく異なる種類の検査があります。 それはやや奇妙な名前を持っています:「一定の条件と例外」。 実際、いわゆる「シンボリック実行」を使用して、Javaメソッドでデータフロー分析を実行します。 この分析により、疑わしい事実が明らかになる場合があります。 そのような事実の例を次に示します。





これらの事実は必ずしも間違いを示しているわけではありません。 たとえば、コードを明確にするために、意図的に真の条件を追加したい場合があります。



 if (obj == null) { … } else if (obj != null) { // : 'obj != null'   … }
      
      





ただし、多くの場合、この検査からの警告はタイプミスを示しているか、コードの作成者が自分が書いたものを完全に理解していないことを示唆しています。 そして最も重要なことは、この検査により、パターンに含まれていない非常に奇妙なエラーを検出できることです。 IDEの作成者である私たちでさえ、「一定の条件と例外」検査が機能するコードを見て驚かされることがあります。



長年にわたり、検査は常に改善されています。 以前は、主にヌルとタイプの不一致に関する問題の原因でした。 その後、Optionalの値の存在の分析、整数範囲の分析、配列の境界の分析がその中に現れました。 以前のメジャーリリース2017.3では、整数範囲の分析を改善し、Stream APIおよびオプションチェーンの基本的なサポートを追加しました。



私たちが最初に試みる検査の改善はすべて、IDEA Ultimateコードにあります。 これは、2〜10年間に作成された数万のクラスを含む主要なプロジェクトです。 さまざまなレベルの知識とスタイルを持つ少なくとも100人の開発者がコードを手にしました。 したがって、「一定の条件と例外」を追い払い、古代のクラスの新しいバグを発見することは常に興味深いです。 次のリリースで改善が行われた後、新しい検査で見つかったものを見てみましょう。



2018.1で最も興味深い分析の改善点の1つは、変数間の関係の追跡です。 以前は、検査は変数の等式と不等式のみを追跡していました。 たとえば、条件if(a == b && a != b)



常にfalseとマークされました。 整数範囲の監視を開始したとき、 if(a > 0 && a <= 0)



ような条件で新しい警告が表示されましa



は、 a



の値が範囲[1..MAX_VALUE]



にある場合は左側、範囲[MIN_VALUE..0]



、およびこれらの範囲は重複しません。 ただし、ここでは範囲if(a > b && a <= b)



に関する警告はありません。これは、ここでは範囲について何も知らないためです。 現在、どちらの値が不明であっても、どちらの値が大きいかを監視しているため、2018.1ではこの条件も強調表示されます。 このような些細なエラーはプログラマ自身が気付く可能性が高いようですが、データストリームの分析がパターンを探しているだけではないことを忘れないでください。



まず、彼は反対のケースも簡単に見つけます。 そして、このようなエラーは、SQLテーブルを処理するためのコードに実際に現れました。



idx&lt; myAncestorRefs.length || idx&gt; = myAncestorRefs.length



はい、メソッドは常にtrueの条件で始まり、 idx



変数の値がmyAncestorRefs



配列の長さより短いか、それ以上であるため、常に空のリストを返します。 次の10行のコードは実行されません。 明らかにタイプミスです: idx < myAncestorRefs.length



ではなくidx < 0







ところで、配列の長さについて。 この機能が、配列の長さに関する既存のチェックと友達になったことは、嬉しい驚きでした。 Groovyプラグインで見つかったこのようなエラーを見つけることは想定していませんでした。



if(initializers.length&lt; i)return initializers [i];



さて、配列の要素を読み取るとき、検査はすでに、指定された場所のi



が常にinitializers



の要素の数よりも大きいことを知っていinitializers



。 したがって、条件が満たされた場合、 ArrayIndexOutOfBoundsException



例外ArrayIndexOutOfBoundsException



避けられません。 実際、それは決して達成されていないと思います。そうでなければ、例外に気づいたでしょう。 もちろん、条件は間違って逆さまにされました。



配列の境界に関連する別の問題が、キャッチブロックを処理する1つのJava検査のコードで見つかりました。



while(catchSections [index]!= catchSection&amp;&amp; index&lt; catchSections.length)



ご覧のとおり、この変数を使用して配列要素にアクセスした後、 index



値の境界がチェックされます。 条件をfalseにすることはできませんArrayIndexOutOfBoundsException



場合、 ArrayIndexOutOfBoundsException



このコードを既に残していArrayIndexOutOfBoundsException







新しいバージョンでは、配列初期化子から値を追跡する場合があります。 これにより、次のような冗長コードを検出できました。



boolean [] result = new boolean [] {false}; &lt; ...&gt; if(結果[0])



むかしむかし、シングルトンのresult



配列はcommand



ラムダ内で使用され、エディターをアクティブ化する必要性を捉えました。 時間が経つにつれて、エディターのアクティブ化は不要になりましたが、1つの既存の検査では、条件が過剰であることに気付きませんでした。 今それを見る。



現在、名前が「is」で始まる引数のないブールメソッドは、同じ修飾子で2回呼び出された場合に同じ結果を返すと考えられます。 はい、それは発見的であり、必ずしも真実ではありません。 ただし、コードで誤検知が発生する場合は、考えてみてください。 これはおそらく、IDEだけでなく同僚にとってもコードが理解しにくいことを示しています。 しかし、このようなヒューリスティックにより、実際のバグを見つけることができます。 たとえば、CSSセレクターの処理中にこのようなコードが発見されました。



localSelectorElement =(CssSimpleSelector)localElement; remoteSelectorElement =(CssSimpleSelector)localSelectorElement; if(localSelectorElement.isUniversalSelector()!= remoteSelectorElement.isUniversalSelector())



修飾子はここでは異なりますが、上記と同じ意味が割り当てられています。 したがって、アナライザーはisUniversalSelector



が同じ結果を返すと想定します。 そしてそれは本当に! ここでの入力ミスは条件ではなく、上の行です。もちろん、 remoteSelectorElement = (CssSimpleSelector)remoteSelectorElements[j]



があるはずです。



また、2018.1では、検査により新しい警告が発行されます。 変数に値が割り当てられ、常にこの場所で常に同じ値が設定されている場合は、それを強調表示します。 繰り返しますが、これは常に間違いではありませんが、割り当ては明らかに冗長です。 この警告とともに真のエラーも見つかりました。 たとえば、これはHTML形式をチェックする単体テストのフラグメントです。



int indentSize = htmlIndentOptions.INDENT_SIZE = 2;



問題は、警告の3行上にあります。最後に「 = 2



」があってはなりません。 結果として、インデントのサイズはテスト後、期待どおりに復元されません。



また、この警告により、フィールドまたは配列要素の初期化チェーンの重複が明らかになりました。



デフォルト[CONSTANT_Int] = ints; ...デフォルト[CONSTANT_Int] = ints;



おそらく、この行は単純に冗長ですが、おそらく他の何かが意図されていました。 どういうわけか、これは明らかに間違いです。



this



処理のわずかな改善により、タブ付きコントロールを処理するコードのエラーが明らかになりました。



while(コンポーネント!=これ||コンポーネント!= null)



左側が真の場合、右側はチェックされません。 左側がfalseの場合、 component



this



であり、 this



間違いなくnullでcomponent



ません。つまり、 component



nullでcomponent



ません。 したがって、条件全体が常に真です。 ||



を使用する際の非常によくある間違い 代わりに&&



またはその逆。



最後に、Stream APIの処理を改善しました。 特に、不完全なチェーンで動作するようになりました(つまり、端末操作で終了しません)。 この改善によりコードのエラーは明らかになりませんでしたが、いくつかの冗長なチェックが見つかりました。 たとえば、このようなメソッドは、コードカバレッジ結果の処理で使用できます。



mapToObj(idx-&gt; Pair.create(...))。filter((x)-&gt; x!= null&amp;&amp; ...);興味深い、そして盲人はこの記事を読んでいますか?私はすべての写真に代替テキストを書いていますが、これから何か利益がありますか?



Pair.create



メソッドの場合、 Pair.create



注釈が自動的に出力されるため、条件x != null



冗長です。 インスペクションは、 x



が前のラムダが返した値と同じであることを認識しています。



「一定の条件と例外」検査では、驚くほど多くの場合、長年にわたって大規模プロジェクトに隠れていたエラーが見つかります。 検証されていない新しいコードを記述するときにさらに役立ちます。 最初は、この検査からのいくつかの警告は迷惑になりますが、時間の経過とともにそれらと調和して生きる方法を理解します。



既に2018.1 EAPプログラムを開始しているため、今すぐ新しい機能を試すことができます。 この検査は絶えず改善されており、誤検出はなくなり、有用な検査が追加されています。 まだ改善できる多くのアイデアがありますので、ご期待ください。



All Articles