Working with source locations
内容提要
本文介绍如何利用CodeQL查询Java代码中运算符周围空白与优先级矛盾的可疑嵌套表达式。通过位置API计算空白量,筛选内部运算符空白多于外部的情况,并排除关联运算符和不对称空白以减少误报。示例展示了发现真实bug的过程,如`capacity + capacity>>1`实际解析为`(capacity+capacity)>>1`,与预期不符。
延伸解读
空白与优先级:代码可读性的隐患
本文通过CodeQL查询揭示了Java中运算符空白与优先级不一致可能导致的误解。例如,`capacity + capacity>>1`实际解析为`(capacity+capacity)>>1`,与预期不符。这种模式虽不一定是bug,但会降低代码可读性,增加维护成本。开发者应留意此类写法,必要时使用括号明确意图。
CodeQL位置API的应用
CodeQL通过位置API(如`getStartColumn`、`getEndColumn`)推断源码中的空白,从而检测潜在问题。本文展示了如何利用这些API计算运算符周围空白量,并过滤误报。这体现了静态分析工具在代码审查中的价值,能辅助发现人工易忽略的细节。
误报的排除策略
查询在初步实现后会产生误报,如不对称空白和关联运算符。通过要求空白总数为偶数,并排除相同关联运算符的嵌套,有效减少了误报。这提示我们在设计静态分析规则时,需结合语言特性和常见模式,平衡检出率与精确度。
Q&A
CodeQL如何检测Java代码中运算符周围空白与优先级矛盾的可疑表达式?
CodeQL通过位置API获取表达式中操作符的列位置,计算操作符周围的空白字符数,然后筛选出内部表达式操作符空白多于外部表达式操作符的情况,从而发现可能因优先级误解导致的错误。
在CodeQL中,如何计算一个二元表达式操作符周围的空白数量?
首先确保表达式位于单行,然后获取左操作数的结束列(lcol)和右操作数的起始列(rcol),计算 rcol - (lcol+1) 得到操作符及周围空白的总字符数,再减去操作符本身的长度(通过 getOp().length()-2 得到),即可得到空白数量。
为什么在检测时使用 getAChildExpr 而不是 getAnOperand?
因为 getAnOperand 会剥离表达式周围的括号,而如果内层表达式有括号,说明程序员可能有意为之,不应标记为可疑。使用 getAChildExpr 可以保留括号信息,避免误报。
CodeQL查询中如何减少误报?
通过两个改进:一是要求操作符前后的空白数量对称(即总空白数为偶数),二是排除内外操作符相同且为关联运算符的情况(如加法、乘法、位运算、逻辑与或),因为这些运算符的嵌套顺序不影响结果。
文章中的示例代码 `capacity + capacity>>1` 实际解析结果是什么?
由于 + 的优先级高于 >>,该表达式被解析为 (capacity + capacity) >> 1,即 capacity(除非发生溢出),而不是作者预期的 1.5 倍 capacity。
CodeQL查询在Hadoop HBase中发现了什么可疑代码?
发现了 `KEY_SLAVE = tmp[ i+1 % 2 ];`,空白暗示程序员意图在0和1之间切换 i,但实际解析为 i + (1%2) 即 i+1,导致 i 只是递增而非切换。