博客

Java 开发避坑指南:位运算符与逻辑运算符的区别

February 7, 2021
Alan Richardson
双手在笔记本电脑上敲击代码,屏幕上显示着“通过 Sensei 和 Secure Code Warrior 掌握安全编码”的文字及相关标识。

Java 陷阱 - 位运算符与布尔运算符

> “Java 陷阱” - 指一种容易在无意中写出的常见错误模式。

一个在 Java 中很容易掉进去的简单陷阱是:将位运算符误用为布尔比较运算符。

例如,简单的手误可能导致你本想写“&&”时却写成了“&”。

我们在审查代码时通常会遵循以下启发式规则:

> 在条件语句中使用“&”或“|”通常并非本意。

在本篇博文中,我们将探讨这一启发式规则,并找出识别和修复此类编码问题的方法。


问题出在哪里?位运算在布尔值上运行正常


在布尔值上使用位运算符是完全合法的,因此 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,这会导致我的代码出现 NullPointerException,因为即使 args 为 null,我们也会执行 args.length 的检查,这是因为两个布尔条件都必须进行求值。


布尔运算符的短路求值


当使用 && 时,例如:

if (args != null && args.length() > 23) {
    System.out.println(args);
}


一旦我们确定 args != null 的求值结果为 false,条件表达式的求值就会停止。

我们无需对右侧进行求值。

无论右侧条件的结果如何,布尔表达式的最终值都将为 false。


但这在生产代码中绝不会发生


这是一个很容易犯的错误,而且静态分析工具并不总能检测出来。

我使用了以下 Google Dork 来查看是否能找到该模式的公开示例:

filetype:java if "!=null & "
此搜索结果中包含来自 Android RootWindowContainer 的一段代码:
isDocument = intent != null & intent.isDocument()


这类代码可能会通过代码审查,因为我们经常在赋值语句中使用位运算符来屏蔽值。但在这种情况下,其结果与上述 if 语句示例相同。如果 intent 为 null,则会抛出 NullPointerException。

我们之所以经常能侥幸避开这个问题,是因为我们习惯于防御性编程并编写了冗余代码。在大多数使用场景中,对 != null 的检查很可能就是多余的。

这是程序员在生产代码中常犯的一个错误。

我不确定搜索结果的时效性如何,但在我进行搜索时,返回的结果中包含了来自 Google、Amazon、Apache 以及我本人的代码。

我最近的一个开源项目收到了一份 pull request,正是为了修复这个错误。

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: "将位与运算符替换为逻辑与运算符"
    actions:
     :- rewrite:
          to: "{{#sed}}s/&/&&/g,{{{ . }}}{{/sed}}"


结语

以上涵盖了位运算符最常见的误用情况,即本意是使用布尔运算符却误用了位运算符。

当然,这种情况也可能出现在其他场景中,例如赋值语句。但在编写配方时,我们必须尽量避免误报,否则配方会被忽略或禁用。我们构建配方是为了匹配最常见的情况。随着 Sensei 的不断演进,我们很可能会增加搜索功能的精确度,以覆盖更多的匹配条件。

以目前的形式,该配方可以识别出许多实际应用中的用例,而且最重要的是,它能识别出我项目中报告的那个问题。

注:不少代码战士为本示例和配方评审做出了贡献——Charlie Eriksen、Matthieu Calie、Robin Claerhaut、Brysen Ackx、Nathan Desmet 和 Downey Robersscheuten。感谢你们的帮助。


---


你可以通过 IntelliJ 的“Preferences \ Plugins”(Mac)或“Settings \ Plugins”(Windows)安装 Sensei,然后搜索“sensei secure code”即可。

我们已将这些博文(包括本文)的大量源代码和配方上传至 Secure Code Warrior GitHub 账号下的 `sensei-blog-examples` 仓库中。

https://github.com/securecodewarrior/sensei-blog-examples

了解更多关于 Sensei 的信息


标语

在 AI 驱动的开发成果发布前实现有效治理

评估 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年网络安全意识月:理解人工智能安全对企业防御至关重要

目前,大多数顶级人工智能公司已公开呼吁全球放缓人工智能的发展速度,以便安全防护措施能够跟上步伐。然而事实是,严重的安全问题已经显现。随着人工智能实施带来的经济成本和风险不断累积,无数安全领导者和高管团队正处于紧迫的境地。

了解更多
博客
筛选标签
这是 div 块中的一段文本。

赋能要素 8:打造项目品牌

成功的安全编码项目不仅需要优质的内容。赋能要素 8 展示了如何通过品牌化将参与过程转变为一种令人向往、以地位为驱动的体验。

了解更多
博客
筛选标签
这是 div 块中的一段文本。

每一位员工现在都处于 AI 网络安全的最前线

企业技术格局正以一种鲜有人预料到的速度发生剧变,直到为时已晚。我们已正式跨越了从人工编写代码和基础辅助编程,进入“智能体开发生命周期”(ADLC)的时代。虽然自主 AI 智能体有望在多个职能领域实现前所未有的效率,但它们也带来了全新的安全和监管风险。

了解更多

在产品发布前确保 AI 驱动的开发安全

洞察开发风险,执行安全策略,并在整个软件开发生命周期中预防漏洞。

预约演示
未找到相关项目。