Java 和 Kotlin 中易溢出比较¶
您可以使用 CodeQL 检查 Java/Kotlin 代码中比较操作符的两侧是否容易发生溢出。
注意
CodeQL 对 Kotlin 的分析目前处于测试阶段。在测试阶段,对 Kotlin 代码的分析以及相关文档将不如其他语言全面。
关于本文¶
在本教程文章中,您将编写一个查询,用于查找循环中整数和长整数之间的比较操作,这些操作可能会因溢出而导致循环不终止。
首先,请考虑以下代码片段
void foo(long l) {
for(int i=0; i<l; i++) {
// do something
}
}
如果 l
大于 231- 1(类型 int
的最大正值),则此循环将永远不会终止:i
将从零开始,一直增加到 231- 1,这仍然小于 l
。当它再次增加时,将发生算术溢出,i
将变为 -231,这也小于 l
!最终,i
将再次达到零,循环重复。
更多关于溢出
所有原始数值类型都有一个最大值,超过该值,它们将环绕到其可能的最小值(称为“溢出”)。对于
int
,这个最大值是 231- 1。类型long
可以容纳更大的值,最大值为 263- 1。在本例中,这意味着l
可以取一个大于类型int
的最大值的值;i
将永远无法达到这个值,而是溢出并返回到一个较低的值。
我们将开发一个查询,以查找可能表现出此类行为的代码。我们将使用几个标准库类来表示语句和函数。有关完整列表,请参阅“用于处理 Java 程序的抽象语法树类。”
初始查询¶
我们将从编写一个查询开始,该查询查找左操作数类型为 int
且右操作数类型为 long
的小于表达式(CodeQL 类 LTExpr
)。
import java
from LTExpr expr
where expr.getLeftOperand().getType().hasName("int") and
expr.getRightOperand().getType().hasName("long")
select expr
此查询通常会在大多数代码库中找到结果。
请注意,我们使用谓词 getType
(适用于 Expr
的所有子类)来确定操作数的类型。类型反过来定义谓词 hasName
,该谓词允许我们识别原始类型 int
和 long
。就目前而言,此查询将找到所有比较 int
和 long
的小于表达式,但实际上我们只对循环条件的一部分比较感兴趣。此外,我们想要过滤掉任何一个操作数为常量的情况,因为这种情况不太可能是真正的错误。修改后的查询如下所示
import java
from LTExpr expr
where expr.getLeftOperand().getType().hasName("int") and
expr.getRightOperand().getType().hasName("long") and
exists(LoopStmt l | l.getCondition().getAChildExpr*() = expr) and
not expr.getAnOperand().isCompileTimeConstant()
select expr
请注意,找到的结果更少了。
类 LoopStmt
是所有循环的通用超类,包括特别是在我们上面的示例中使用的 for
循环。虽然不同类型的循环有不同的语法,但它们都具有循环条件,可以通过谓词 getCondition
访问。我们使用应用于 getAChildExpr
谓词的自反传递闭包运算符 *
来表达 expr
应该嵌套在循环条件内的要求。特别是,它可以是循环条件本身。
where
子句中的最后一个连词利用了以下事实:谓词 可以返回多个值(实际上它们是关系)。特别是,getAnOperand
可以返回 expr
的任何一个操作数,因此 expr.getAnOperand().isCompileTimeConstant()
在至少一个操作数为常量时成立。否定此条件意味着查询将只找到没有一个操作数为常量的表达式。
泛化查询¶
当然,int
和 long
之间的比较并不是唯一有问题的案例:任何在较小端类型宽度小于较大端类型宽度的小于比较都可能有问题,而小于等于、大于和大于等于比较与小于比较一样有问题。
为了比较类型的范围,我们定义了一个谓词,该谓词返回给定整数类型的宽度(以位为单位)
int width(PrimitiveType pt) {
(pt.hasName("byte") and result=8) or
(pt.hasName("short") and result=16) or
(pt.hasName("char") and result=16) or
(pt.hasName("int") and result=32) or
(pt.hasName("long") and result=64)
}
现在,我们想要泛化我们的查询,使其适用于任何比较,其中比较较小端类型的宽度小于比较较大端类型的宽度。我们把这种比较称为易溢出比较,并引入一个抽象类来对其进行建模
abstract class OverflowProneComparison extends ComparisonExpr {
Expr getLesserOperand() { none() }
Expr getGreaterOperand() { none() }
}
此类的两个具体子类:一个用于 <=
或 <
比较,另一个用于 >=
或 >
比较。在这两种情况下,我们都以这样一种方式实现构造函数,使其只匹配我们想要的表达式
class LTOverflowProneComparison extends OverflowProneComparison {
LTOverflowProneComparison() {
(this instanceof LEExpr or this instanceof LTExpr) and
width(this.getLeftOperand().getType()) < width(this.getRightOperand().getType())
}
}
class GTOverflowProneComparison extends OverflowProneComparison {
GTOverflowProneComparison() {
(this instanceof GEExpr or this instanceof GTExpr) and
width(this.getRightOperand().getType()) < width(this.getLeftOperand().getType())
}
}
现在,我们将查询改写为使用这些新类
import java
// Return the width (in bits) of a given integral type
int width(PrimitiveType pt) {
(pt.hasName("byte") and result=8) or
(pt.hasName("short") and result=16) or
(pt.hasName("char") and result=16) or
(pt.hasName("int") and result=32) or
(pt.hasName("long") and result=64)
}
// Find any comparison where the width of the type on the smaller end of
// the comparison is less than the width of the type on the greater end
abstract class OverflowProneComparison extends ComparisonExpr {
Expr getLesserOperand() { none() }
Expr getGreaterOperand() { none() }
}
// Return `<=` and `<` comparisons
class LTOverflowProneComparison extends OverflowProneComparison {
LTOverflowProneComparison() {
(this instanceof LEExpr or this instanceof LTExpr) and
width(this.getLeftOperand().getType()) < width(this.getRightOperand().getType())
}
}
// Return `>=` and `>` comparisons
class GTOverflowProneComparison extends OverflowProneComparison {
GTOverflowProneComparison() {
(this instanceof GEExpr or this instanceof GTExpr) and
width(this.getRightOperand().getType()) < width(this.getLeftOperand().getType())
}
}
from OverflowProneComparison expr
where exists(LoopStmt l | l.getCondition().getAChildExpr*() = expr) and
not expr.getAnOperand().isCompileTimeConstant()
select expr