Javaの落とし穴 - ビット演算子と論理演算子の違い
Javaの落とし穴 - ビット演算子と論理演算子
「Javaの落とし穴」とは、うっかり実装してしまいがちな、よくあるミスのパターンを指します。
Javaで非常に陥りやすい単純な落とし穴として、論理比較演算子の代わりにビット演算子を使ってしまうというものがあります。
例えば、本来「&&」と書くべきところを、単純な打ち間違いで「&」と書いてしまうようなケースです。
コードレビューの際によく用いられるヒューリスティック(経験則)は以下の通りです。
「条件文の中で『&』や『|』が使われている場合、それは意図したものではない可能性が高い」
本記事では、この経験則を掘り下げ、このコーディング上の問題を特定して修正する方法を探ります。
何が問題なのか?ビット演算子はBoolean型でも動作する
Boolean型に対してビット演算子を使用することはJavaの仕様上完全に有効であるため、構文エラーにはなりません。
JUnitテストを作成してビット論理和(|)とビット論理積(&)の真理値表を確認すると、ビット演算子の出力結果が真理値表と一致することがわかります。このことから、ビット演算子の使用は問題ないように思えるかもしれません。
AND(論理積)の真理値表

@Test
void bitwiseOperatorsAndTruthTable(){
Assertions.assertEquals(true, true & true);
Assertions.assertEquals(false, true & false);
Assertions.assertEquals(false, false & true);
Assertions.assertEquals(false, false & false);
}
テストはパスします。これはJavaとして完全に有効なコードです。
OR(論理和)の真理値表

@Test
void bitwiseOperatorsOrTruthTable(){
Assertions.assertEquals(true, true | true);
Assertions.assertEquals(true, true | false);
Assertions.assertEquals(true, false | true);
Assertions.assertEquals(false, false | false);
}
このテストもパスします。では、なぜ私たちは「&&」や「||」を優先して使うべきなのでしょうか?
真理値表の画像は、truth table tool(web.standfor.edu)を使用して作成しました。
問題:短絡評価(ショートサーキット)
真の問題は、ビット演算子(&, |)と論理演算子(&&, ||)の動作の違いにあります。
論理演算子は短絡演算子であり、必要な分だけを評価します。
例:
if (args != null & args.length() > 23) {
System.out.println(args);
}
上記のコードでは、ビット演算子が使用されているため、両方の条件式が評価されます。
- args != null
- args.length() > 23
この場合、argsがnullであっても両方の条件が評価されるため、args.lengthのチェックが実行され、NullPointerExceptionが発生する可能性があります。
論理演算子による短絡評価
例えば、&&を使用した場合:
if (args != null && args.length() > 23) {
System.out.println(args);
}
args != nullがfalseであると判明した時点で、条件式の評価は停止します。
右辺を評価する必要はありません。
右辺の条件がどのような結果であれ、論理式全体の最終的な値はfalseになるからです。
しかし、本番環境のコードでは決して起こらないだろう
これは非常に陥りやすいミスであり、静的解析ツールでも必ずしも検出されるとは限りません。
このパターンが公開されているコードに存在するかどうか、以下のGoogle検索(Google Dork)を使って調べてみました。
filetype:java if "!=null & "
この検索で、AndroidのRootWindowContainer内に以下のコードが見つかりました。
isDocument = intent != null & intent.isDocument()
ビット演算子は値のマスク処理などで代入文によく使われるため、このようなコードはコードレビューをすり抜けてしまう可能性があります。しかし、このケースでは前述のif文の例と同じ結果になります。もしintentがnullであれば、NullPointerExceptionが発生します。
多くの場合、私たちは防御的なコーディングを行い、冗長なコードを書くため、こうした構造でも問題なく動作してしまいます。ほとんどのユースケースにおいて、!= nullのチェックは冗長である可能性が高いでしょう。
これは、プログラマーが本番環境のコードで犯してしまう誤りです。
検索結果がどれほど最新のものかは分かりませんが、私が検索を実行した際には、Google、Amazon、Apache、そして私自身のコードを含む結果が返ってきました。
最近、私のオープンソースプロジェクトの一つに寄せられた プルリクエスト は、まさにこのエラーに対処するものでした。
if(type!=null & type.trim().length()>0){
acceptMediaTypeDefinitionsList.add(type.trim());
}
この問題を見つける方法
いくつかの静的解析ツールでサンプルコードをチェックしてみましたが、この隠れた「自爆コード」を検出できたものは一つもありませんでした。
Secure Code Warriorのチームとして、私たちはこれを検出できる比較的シンプルなSenseiレシピを作成し、レビューしました。
ビット演算子は完全に有効であり、代入などで頻繁に使用されるため、私たちは問題のあるコードを見つけるために、if文内でのビット演算子「&」の使用というユースケースに焦点を当てました。
search:
expression:
anyOf:
- in:
condition: {}
value:
caseSensitive: false
matches: ".* & .*"
これは正規表現を使用して、if文などの条件式内で「 & 」が使用されている箇所をマッチングします。
修正には再び正規表現を利用しました。今回はQuickFix内のsed関数を使用して、式内の「&」を「&&」に一括置換しています。
availableFixes:
- name: "Replace bitwise AND operator to logical AND operator"
actions:
- rewrite:
to: "{{#sed}}s/&/&&/g,{{{ . }}}{{/sed}}"
まとめ
これは、ビット演算子の最も一般的な誤用、つまり本来は論理演算子を使うべき場面での誤用をカバーしています。
代入の例など、他にもこの問題が発生し得る状況はありますが、レシピを作成する際は誤検知を避けるよう努める必要があります。さもなければ、レシピは無視されるか無効にされてしまうでしょう。私たちは最も一般的な発生パターンにマッチするようレシピを構築しています。Senseiの進化に伴い、より多くの条件をカバーできるよう、検索機能にさらなる詳細設定を追加していく予定です。
現在の形式でも、このレシピは多くの実用的なユースケースを特定できます。そして何よりも重要なことに、私のプロジェクトで報告された問題も特定可能です。
注:この例とレシピのレビューには、Charlie Eriksen、Matthieu Calie、Robin Claerhaut、Brysen Ackx、Nathan Desmet、Downey Robersscheutenといった多くのコードウォリアーが貢献してくれました。協力してくれた皆さんに感謝します。
---
SenseiはIntelliJ内の「Preferences \ Plugins」(Mac)または「Settings \ Plugins」(Windows)からインストールできます。検索窓で「sensei secure code」と入力してください。
これらのブログ記事(本記事を含む)で使用したソースコードやレシピの多くは、Secure Code WarriorのGitHubアカウントにある`sensei-blog-examples`リポジトリで公開しています。
https://github.com/securecodewarrior/sensei-blog-examples
AI主導の開発をリリース前にガバナンスする
AIを活用したリスク測定、コミット時のセキュアコーディングポリシーの強制、そしてSDLC全体を通じたセキュアなデリバリーの加速を実現します。
他のブログ記事を見る
セキュアコーディング、AIガバナンス、ソフトウェアリスク管理に関する専門的なコンテンツをご覧ください。
%252520%252520(3).avif)
Supercharged Security Awareness: How Tournaments are Inspiring Developers at Erste Group
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Suspendisse varius enim in eros elementum tristique. Duis cursus, mi quis viverra ornare, eros dolor interdum nulla, ut commodo diam libero vitae erat. Aenean faucibus nibh et justo cursus id rutrum lorem imperdiet. Nunc ut sem vitae risus tristique posuere.

Security as culture: How Blue Prism cultivates world-class secure developers
Learn how Blue Prism, the global leader in intelligent automation for the enterprise, used Secure Code Warrior's agile learning platform to create a security-first culture with their developers, achieve their business goals, and ship secure code at speed
AIを活用した開発をリリース前にセキュアに
開発者のリスクを可視化し、ポリシーを適用することで、ソフトウェア開発ライフサイクル全体における脆弱性を未然に防ぎます。



.avif)