ブログ

Javaの落とし穴 - ビット演算子と論理演算子の違い

February 7, 2021
アラン・リチャードソン
ノートパソコンでコードを入力している手元の様子。テキストには「SenseiとSecure Code Warriorでセキュアコーディングを習得する」と記され、ロゴが表示されています。

Javaの落とし穴 - ビット演算子と論理演算子

「Javaの落とし穴」とは、うっかり実装してしまいがちな、よくあるミスのパターンを指します。

Javaで非常に陥りやすい単純な落とし穴として、論理比較演算子の代わりにビット演算子を使ってしまうというものがあります。

例えば、本来「&&」と書くべきところを、単純な打ち間違いで「&」と書いてしまうようなケースです。

コードレビューの際によく用いられるヒューリスティック(経験則)は以下の通りです。

「条件文の中で『&』や『|』が使われている場合、それは意図したものではない可能性が高い」

本記事では、この経験則を掘り下げ、このコーディング上の問題を特定して修正する方法を探ります。


何が問題なのか?ビット演算子はBoolean型でも動作する


Boolean型に対してビット演算子を使用することはJavaの仕様上完全に有効であるため、構文エラーにはなりません。

JUnitテストを作成してビット論理和(|)とビット論理積(&)の真理値表を確認すると、ビット演算子の出力結果が真理値表と一致することがわかります。このことから、ビット演算子の使用は問題ないように思えるかもしれません。

AND(論理積)の真理値表

Three columns on with a, one with b, and the last with (a^b)


@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(論理和)の真理値表


Three columns on with a, one with b, and the last with (a v b)


   @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

Senseiについて詳しく見る


タグライン

AI主導の開発をリリース前にガバナンスする

AIを活用したリスク測定、コミット時のセキュアコーディングポリシーの強制、そしてSDLC全体を通じたセキュアなデリバリーの加速を実現します。

デモを予約する
リソースライブラリ

他のブログ記事を見る

セキュアコーディング、AIガバナンス、ソフトウェアリスク管理に関する専門的なコンテンツをご覧ください。

すべて表示
導入事例
フィルターラベル
これはdivブロック内のテキストです。

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.

詳細はこちら
導入事例
フィルターラベル
これはdivブロック内のテキストです。

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

詳細はこちら
導入事例
フィルターラベル
これはdivブロック内のテキストです。

セキュリティ文化の統一:Sageはいかにしてチャンピオンプログラムを構築したか

Sageがどのようにして柔軟かつ関係性を重視したアプローチでセキュリティを強化し、200名以上のセキュリティチャンピオンを育成して、目に見えるリスク低減を実現したのかをご紹介します。

詳細はこちら
ブログ
フィルターラベル
これはdivブロック内のテキストです。

2026年サイバーセキュリティ意識向上月間:企業防衛に不可欠なAIセキュリティの理解

主要なAI企業の多くが、安全対策が追いつくまでの間、AI開発を世界的に減速させるよう公に呼びかける事態となっています。しかし現実には、深刻なセキュリティ問題はすでに顕在化しています。AI導入に伴う金銭的コストやリスクが増大し続ける中、多くのセキュリティリーダーや経営陣は差し迫った状況に追い込まれています。

詳細はこちら
ブログ
フィルターラベル
これはdivブロック内のテキストです。

イネーブラー8:プログラムのブランディング

セキュアコーディングプログラムを成功させるには、優れたコンテンツ以上のものが必要です。イネーブラー8では、ブランディングによって参加を「憧れのステータスを得る体験」へと変える方法を解説します。

詳細はこちら
ブログ
フィルターラベル
これはdivブロック内のテキストです。

すべての従業員が今、AIサイバーセキュリティの最前線に立っている

エンタープライズテクノロジーの状況は、手遅れになるまで誰も予測できなかったほどの速さで変化しています。私たちは今、人間が書いたコードと基本的なコパイロット支援の時代から、エージェント型開発ライフサイクル(ADLC)の時代へと正式に足を踏み入れました。自律型AIエージェントは、複数の機能にわたってかつてない効率性をもたらす一方で、全く新しい種類のセキュリティリスクや規制リスクも引き起こしています。

詳細はこちら

AIを活用した開発をリリース前にセキュアに

開発者のリスクを可視化し、ポリシーを適用することで、ソフトウェア開発ライフサイクル全体における脆弱性を未然に防ぎます。

デモを予約する
該当する項目が見つかりません。