ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Infer 的 Inefficient keySet Iterator 检测器:`INEFFICIENT_KEYSET_ITERATOR` 详解与实战

Infer 的 Inefficient keySet Iterator 检测器:`INEFFICIENT_KEYSET_ITERATOR` 详解与实战 Infer 的 Inefficient keySet Iterator 检测器INEFFICIENT_KEYSET_ITERATOR详解与实战【免费下载链接】inferA static analyzer for Java, C, C, and Objective-C项目地址: https://gitcode.com/gh_mirrors/infer/infer导读本文基于 checker-inefficient-keyset-iterator.md 展开围绕 Infer 静态分析器A static analyzer for Java, C, C, and Objective-C中的性能类检测器展开讲解它如何在 Java 代码中识别“先迭代keySet()再逐个get(key)回查值”的低效模式并给出触发命令、Issue 类型、源码级检测原理inefficientKeysetIterator.ml与测试用例以及修复方案。读完你将掌握该检测器的启用方式、匹配边界如启发式、误报控制、androidx.collection.ArrayMap支持和直接可复制的正确写法。一、Checker 概览这个检测器在查什么Inefficient keySet Iterator是 Infer 内置的性能回归类PerfRegression检测器全称Inefficient keySet Iterator。它针对的是 Java 集合遍历中一种常见但低效的写法先用map.keySet()拿到所有键的集合通过Iterator或增强 for 循环逐个迭代这些键在循环体内再调用map.get(key)用键回查一遍对应的值。每次get(key)都是一次额外的哈希查找HashMap.get相比直接遍历entrySet()拿到现成的键值对白白多付出 O(1) 的哈希计算成本。检测器把这种“迭代键、再回查值”的模式识别出来并报告INEFFICIENT_KEYSET_ITERATOR告警属于性能优化建议类Warning 级别。从 infer/src/base/Checker.ml 的定义可以看到它的元信息| InefficientKeysetIterator - { id inefficient-keyset-iterator ; kind UserFacing {title Inefficient keySet Iterator; markdown_body } ; support mk_support_func ~java:Support () ; short_documentation Check for inefficient uses of iterators that iterate on keys then lookup their values, \ instead of iterating on key-value pairs directly. ; cli_flags Some {deprecated []; show_in_help true} ; enabled_by_default true ; activates [] }几个关键点enabled_by_default true该检测器默认启用无需额外开关即可参与分析~java:Support ()目前仅支持 Java 语言cli_flags有效可通过命令行显式激活也可使用--inefficient-keyset-iterator-only只看本检测器的报告见下文测试配置Issue 类型注册在 infer/src/base/IssueType.ml 中注册为Warning级别、类别为PerfRegression性能回归。支持语言根据 checker-inefficient-keyset-iterator.md官方明确的支持矩阵如下语言是否支持C / C / ObjC否C# / .NET否Erlang否Hack否Java是Python否Rust否Swift否这一结论与源码一致检测器回调只注册在 Java 前端上见 infer/src/backend/registerCheckers.ml; { checker InefficientKeysetIterator ; callbacks [(intraprocedural InefficientKeysetIterator.checker, Java)] }也就是说它作为一个过程内intraprocedural分析运行只在单个方法体内做模式匹配。二、如何激活与使用在命令行运行 Infer 时通过分析选项激活该检测器infer run --inefficient-keyset-iterator -- javac Test.java在infer analyze阶段已有捕获产物也可以使用infer analyze --inefficient-keyset-iterator若只想聚焦该检测器、屏蔽其他告警可以组合--inefficient-keyset-iterator-only。仓库自带的端到端测试 infer/tests/codetoanalyze/java/inefficientKeysetIterator/Makefile 就是如此配置的INFER_OPTIONS --inefficient-keyset-iterator-only --debug-exceptions INFERPRINT_OPTIONS --issues-tests SOURCES $(wildcard *.java) include $(TESTS_DIR)/javac.make由于默认已启用即使不加--inefficient-keyset-iterator分析 Java 代码时它也会运行显式传入的意义在于让命令行意图更清晰或在自定义构建配置中保持一致性。输出告警的格式如下取自 issues.expcodetoanalyze/java/inefficientKeysetIterator/Test.java, Test.inefficient_loop_bad(java.util.HashMap):void, 2, INEFFICIENT_KEYSET_ITERATOR, no_bucket, WARNING, []字段依次为文件路径、函数签名、行号、Issue 类型INEFFICIENT_KEYSET_ITERATOR、bucketno_bucket、严重级别WARNING、跟踪信息。三、被报告的问题触发模式与真实案例3.1 模式一增强 for 循环 keySet()get(key)infer/documentation/issues/INEFFICIENT_KEYSET_ITERATOR.md 给出的标准触发示例void inefficient_loop_bad(HashMapString, Integer testMap) { for (String key : testMap.keySet()) { Integer value testMap.get(key); // extra look-up cost foo(key, value); } }3.2 模式二显式 Iterator while(hasNext())在测试文件 infer/tests/codetoanalyze/java/inefficientKeysetIterator/Test.java 中用显式Iterator的写法同样会被捕获void inefficient_loop_itr_bad(HashMapString, Integer testMap) { Iterator itr2 testMap.keySet().iterator(); while (itr2.hasNext()) { String key (String) itr2.next(); testMap.get(key); } }3.3 模式三中间隔了其他语句启发式检测器采用“最多回溯 4 个直接前驱节点”的启发式策略源码注释为 “Heuristic: check up to 4 direct predecessor nodes”因此即使iterator()/keySet()的调用与get(key)之间隔了几条无关语句也能跨语句匹配。测试中inefficient_loop_itr_heur_bad中间有int i 0;即被报告而相隔语句超过启发式上限的inefficient_loop_itr_heur_bad_FN中间有 4 条语句则被记为预期不报告_FN后缀即 False Negative 预期用例见 Test.java 与 issues.exp。同理inefficient_loop_itr_heur_btw_bad演示了先把keySet()存到局部变量、再在其间插入赋值语句的场景仍然被报告void inefficient_loop_itr_heur_btw_bad(HashMapString, Integer testMap) { SetString keySet testMap.keySet(); int i 0; int j 1; int l 3; Iterator itr2 keySet.iterator(); while (itr2.hasNext()) { String key (String) itr2.next(); testMap.get(key); } }3.4 模式四androidx.collection.ArrayMap同样支持检测器不仅覆盖java.util.Map还通过implements_androidx_map识别 AndroidX 的ArrayMapArrayMap继承自SimpleMap见 Test.javavoid inefficient_arraymap_loop_bad(ArrayMapString, Integer arrayMap) { for (String key : arrayMap.keySet()) { arrayMap.get(key); } }该用例同样出现在 issues.exp 第 5 行。四、检测原理源码级解析检测器主体在 infer/src/checkers/inefficientKeysetIterator.ml它工作在 Infer 的中间表示SIL上核心思路可以概括为四条线索的组合匹配找到循环利用Procdesc.Loop.get_loop_head_to_source_nodes与get_loop_head_to_loop_nodes拿到所有循环的入口节点loop head和循环体节点集合第 109-113 行确认循环基于迭代器循环头节点必须包含对hasNext()的调用且该调用所属类实现了java.util.IteratorPatternMatch.Java.implements_iterator见 第 116-121 行回溯定位iterator()与keySet()通过支配关系Dominators只沿“支配当前节点”的前驱回溯最远 4 层找到iterator()调用所属类实现Set再继续回溯找到keySet()调用所属类实现Map或androidx.collection.ArrayMap并验证迭代器变量确实来自该keySet()的返回值ir_var_opt见 第 122-127 行 与 when_dominating_preds_satisfy在循环体内寻找map.get(key)遍历循环体节点若发现对get的调用且实参 pvar 与前面keySet()所属的 map 变量一致则报告report_matching_get。底层使用的类型判定辅助函数如下第 29-30 行let implements_map tenv s PatternMatch.Java.implements_map tenv s || PatternMatch.Java.implements_androidx_map tenv s报告时Issue 的描述与修复建议文本由代码生成第 70-84 行描述Accessing a value using a key that was retrieved from a keySet iterator.建议It is more efficient to use an iterator on the entrySet of the map, avoiding the extra HashMap.get(key) lookup.从实现结构可以推断这是一个完全过程内intraprocedural的句法/模式匹配式检测器——它不依赖数据流值域而是在 SIL 指令序列上做结构化匹配因此对控制流形态较敏感这也是启发式上限存在的根本原因。它不会误报“遍历 A 的 keySet 却从 B 里 get”——因为第 4 步要求get的实参与keySet()的来源变量是同一个 pvar测试中的negative_loop_ok遍历testMap1.keySet()却testMap2.get(key)与independent_itr_loop_okgetter并非来自被迭代的 map均被正确判定为“不报告”见 Test.java。五、修复方式用entrySet()替代keySet()get()官方 Issue 文档 INEFFICIENT_KEYSET_ITERATOR.md 给出的修复建议是直接遍历entrySet()它会返回键值对省去额外的哈希回查。修复前的低效写法被报告void inefficient_loop_bad(HashMapString, Integer testMap) { for (String key : testMap.keySet()) { Integer value testMap.get(key); // extra look-up cost foo(key, value); } }修复后的高效写法不再报告void efficient_loop_ok(HashMapString, Integer testMap) { for (Map.EntryString, Integer entry : testMap.entrySet()) { String key entry.getKey(); Integer value entry.getValue(); foo(key, value); } }显式 Iterator 版本同样可以改写void efficient_loop_itr_ok(HashMapString, Integer testMap) { IteratorMap.EntryString, Integer itr1 testMap.entrySet().iterator(); while (itr1.hasNext()) { Map.EntryString, Integer entry itr1.next(); entry.getKey(); entry.getValue(); } }以上两种“高效写法”分别对应测试用例efficient_loop_ok与efficient_loop_itr_okTest.java在 issues.exp 中均无对应告警验证了修复的有效性。修复边界与注意事项仅当确实需要同时访问 key 和 value 时才值得改写如果循环体内只用 key、不碰 value那么keySet()遍历并无额外开销也不会触发告警不要机械地全量替换当get的目标 map 与迭代的 map 不是同一个对象时如negative_loop_ok、independent_itr_loop_ok所示检测器本来就不报告也无需改写Bundle等非 Map 类型不受影响测试用例from_bundle_ok表明Bundle不实现Map接口因此即使它有keySet()/get()方法也不会被误报Test.java。六、Issue 报告与更多信息被报告的 Issue 类型 ID 为INEFFICIENT_KEYSET_ITERATOR其完整条目可在 Infer 的 全部 Issue 类型索引 中查看源码定义见 infer/src/base/IssueType.ml类别PerfRegression级别Warning。常用调试与使用命令命令用途infer run --inefficient-keyset-iterator -- javac Test.java编译并分析启用该检测器infer analyze --inefficient-keyset-iterator对已捕获的产物仅执行分析infer analyze --inefficient-keyset-iterator-only只报告该检测器的问题屏蔽其他告警infer explore交互式浏览报告详情与调用位置仓库内的可复现实验入口检测器实现infer/src/checkers/inefficientKeysetIterator.ml测试样例含正例/反例/启发式边界infer/tests/codetoanalyze/java/inefficientKeysetIterator/Test.java预期输出infer/tests/codetoanalyze/java/inefficientKeysetIterator/issues.exp测试驱动 Makefileinfer/tests/codetoanalyze/java/inefficientKeysetIterator/Makefile官方 Issue 文档infer/documentation/issues/INEFFICIENT_KEYSET_ITERATOR.md说明本文中“启发式最多回溯 4 层前驱”“仅 Java 支持”“默认启用”等结论均直接来自上述源码与测试文件_FN后缀用例inefficient_loop_itr_heur_bad_FN为预期的“不应报告”边界用例体现了该检测器在误报率与覆盖率之间的工程取舍。【免费下载链接】inferA static analyzer for Java, C, C, and Objective-C项目地址: https://gitcode.com/gh_mirrors/infer/infer创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考
返回列表