Types in Java

💡 原文中文,约19400字,阅读约需47分钟。
📝

内容提要

本文介绍如何使用CodeQL分析Java代码中的数据类型,包括通过Type类及其子类表示类型、利用RefType建模类层次结构,并演示两个查询示例:查找有问题的数组向下转型(排除Collection.toArray的误报),以及查找Collection.contains中元素类型与参数类型不兼容的调用(排除类型变量、自动装箱和null的误报)。

🔎

延伸解读

类型层次建模的实用价值

文章展示了如何利用CodeQL的RefType类及其谓词(如getASupertype、getASubtype)来建模Java类型层次。这种能力使得开发者能够编写查询,自动检测与类型相关的潜在问题,例如数组向下转型和集合contains方法的不兼容调用。通过精确的层次遍历,可以避免手工检查的繁琐和遗漏,提升代码分析的效率和准确性。

误报排除的典型策略

在编写CodeQL查询时,误报是常见挑战。文章通过两个示例展示了排除误报的方法:针对Collection.toArray的调用,利用方法覆盖和实例化关系识别安全场景;针对contains检查,排除类型变量、自动装箱和null类型。这些策略体现了在静态分析中平衡召回率和精确率的思路,对实际应用具有参考价值。

从示例到实际应用的启示

文章中的查询示例不仅展示了CodeQL的API用法,还揭示了真实代码中常见的类型问题,如Zookeeper中的contains误用。通过将查询应用于实际项目,开发者可以发现潜在缺陷并修复。这种基于代码库的自动化分析,有助于在开发早期识别类型相关的逻辑错误,提升代码质量。

Q&A

CodeQL如何表示Java中的数据类型?

CodeQL通过Type类及其子类来表示Java类型。PrimitiveType表示基本类型(如boolean、int),RefType及其子类表示引用类型(类、接口、数组等),包括Java标准库和非库代码中的类型。

在CodeQL中如何查找一个类的所有祖先类型?

可以使用RefType的getASupertype+()谓词,通过传递闭包获取所有祖先类型。例如,查询`select B.getASupertype+()`会返回B的所有直接和间接超类型。

为什么数组向下转型会导致运行时异常?

因为即使每个数组元素都可以向下转型,但将数组本身向下转型为更具体的数组类型时,如果实际对象不是该类型,就会抛出ClassCastException。例如,将Object[]强制转换为String[],如果实际数组是Object[],则运行时异常。

如何用CodeQL查找有问题的数组向下转型?

可以编写查询,寻找满足以下条件的CastExpr:源类型和目标类型都是数组,且源数组的元素类型是目标数组元素类型的超类型。查询会标记这些潜在的向下转型。

在数组向下转型查询中,如何排除Collection.toArray的误报?

通过定义CollectionToArray和CollectionToArrayCall类,识别对Collection.toArray的调用,并在查询中排除这些调用。因为toArray返回Object[],但实际运行时不会出错。

如何用CodeQL查找Collection.contains中元素类型不兼容的调用?

编写查询,找到Collection.contains的调用,获取集合元素类型和参数类型,检查它们是否有共同子类型。如果没有,则标记为不兼容。

在contains不匹配查询中,如何处理类型变量、自动装箱和null的误报?

通过排除TypeVariable实例、检查集合元素类型不是参数类型的装箱类型(如Integer和int),以及排除null类型(<nulltype>)来减少误报。

🏷️

标签

➡️

继续阅读