Overflow-prone comparisons in Java

💡 原文中文,约8700字,阅读约需21分钟。
📝

内容提要

本文介绍如何使用CodeQL编写查询,检测Java循环中因整数溢出导致非终止的比较。通过示例说明int与long比较可能溢出,并逐步构建查询:先查找int与long的小于表达式,再限定循环条件并排除常量,最后泛化为比较窄类型与宽类型的溢出风险,涵盖<、<=、>、>=等运算符。

🔎

延伸解读

溢出导致循环不终止的机制

文章指出,当循环变量为int而比较对象为long时,若long值超过int最大值(2^31-1),循环可能永不终止。因为int溢出后会回绕到最小值(-2^31),导致循环条件始终成立。理解这一机制有助于识别类似风险,不仅限于int和long,任何窄类型与宽类型的比较都可能存在隐患。

CodeQL查询的构建思路

文章展示了如何逐步构建CodeQL查询:先定位特定类型比较(如int与long的<),再限定为循环条件中的表达式,并排除常量操作数以减少误报。最后通过定义类型宽度谓词和抽象类,将查询泛化到所有窄宽类型比较及多种运算符(<、<=、>、>=)。这种模块化方法便于扩展和维护。

实际应用中的注意事项

查询仅标记潜在风险,并非所有此类比较都是错误。例如,若循环体内有break或return,或循环变量可能被修改,则可能不会导致非终止。此外,常量操作数被排除,因为常量比较通常可静态确定结果。使用该查询时,需结合上下文人工审查,确认是否真正存在溢出风险。

Q&A

什么是Java中的整数溢出?

整数溢出是指当数值超过其类型所能表示的最大值时,会回绕到该类型的最小值。例如,int类型的最大值是2^31-1,超过后回绕到-2^31。

为什么在循环中比较int和long可能导致死循环?

如果long变量l大于int的最大值,循环变量i从0递增到int最大值后溢出变为负数,永远无法达到l,导致循环不终止。

如何使用CodeQL检测Java循环中可能因溢出导致非终止的比较?

可以编写CodeQL查询,查找循环条件中窄类型(如int)与宽类型(如long)的比较,并排除常量操作数。具体步骤包括:先查找int与long的小于表达式,再限定为循环条件,最后泛化为所有窄宽类型比较。

CodeQL中如何判断一个表达式是循环条件的一部分?

使用LoopStmt类的getCondition()方法获取循环条件,并通过getAChildExpr*()谓词检查表达式是否嵌套在条件中。

CodeQL查询中如何过滤掉常量操作数的比较?

使用not expr.getAnOperand().isCompileTimeConstant(),其中getAnOperand()返回任一操作数,isCompileTimeConstant()判断是否为编译时常量,取反后表示两个操作数都不是常量。

CodeQL中如何定义类型宽度并泛化溢出比较?

定义width谓词返回byte、short、char、int、long的位数(8、16、16、32、64),然后创建OverflowProneComparison抽象类,其子类LTOverflowProneComparison和GTOverflowProneComparison分别匹配<、<=和>、>=,并检查窄端宽度小于宽端宽度。

🏷️

标签

➡️

继续阅读