Java 虚拟机生态最尴尬的地方,不是语言太少,而是语言太多。Kotlin、Java、Groovy、Scala 看起来都能“互通有无”,可一旦编译器在类型、空值、泛型和继承规则上理解不一致,程序就可能在边界处悄悄翻车。
这篇论文盯上的就是这个“隐形地雷区”。它没有继续把目光只放在单语言编译测试上,而是直接把差分测试推进到跨语言 JVM 编译场景,最后在五种主流编译器里确认了 32 个 Bug,算是把“语言边界不是装饰线”这件事讲得很实在。
CrossLangFuzzer 的核心思路并不玄乎:先用一个统一的中间表示把跨语言程序搭起来,再把它翻译成 Kotlin、Java、Groovy 或 Scala 代码,最后让多个编译器互相“对答案”。谁的行为和别人不一致,谁就先进入重点排查名单。
这套流水线分成四步:生成器先造出结构合法的跨语言程序,变异器再故意把程序“搅一搅”,打印器把抽象表示变成具体源代码,运行器负责调用不同编译器做正常测试和差分测试。发现问题后,IR 还会进入约简器,像做减法一样把触发 Bug 的程序压缩到最小,方便开发者定位。
这里最聪明的地方在于,生成阶段强调“合法”,变异阶段允许“故意添乱”。前者负责保证测试样本有足够语义密度,后者负责扩大覆盖面,专门去碰那些编译器最容易犯迷糊的地方,比如泛型、空值、覆写解析和平台类型。
CrossLangFuzzer 的地基是统一 IR。它借鉴了 Kotlin 编译器后端的内部表示,但并不是简单照搬,而是把程序、类、继承关系、类型参数和成员函数整理成一棵可遍历、可变换、可约简的树。
这棵树支持五类关键类型:普通类型、参数化类型、可空类型、平台类型和类型参数。说人话就是,编译器最爱在这些地方“装糊涂”,所以测试工具也必须把这些坑位单独点名,不然 Bug 很容易漏网。
图1:Kotlin 编译器因原始类型参数拒绝了一个有效的 Java 程序
图1 这个例子很典型:Java 里看着合法的继承关系,到了 Kotlin 这里却可能因为原始类型和泛型语义的对齐问题被误判为错误。论文拿它当动机案例,目的不是炫技,而是提醒读者:跨语言编译的麻烦,往往不是“编不过”,而是“编译器以为自己很懂,其实没懂”。
在变异策略上,论文设计了 7 种操作,覆盖泛型子类型、空值性、覆写解析和语言重分配四个方向。它们的共同目标很明确:不是随机撒网,而是专挑 JVM 多语言生态里最容易出语义分歧的点下手。
表1:CrossLangFuzzer 的变异操作符
这些变异器的价值不在于“多”,而在于“准”。比如泛型相关变异会逼编译器处理替换和变型问题,空值性变异会逼它处理跨语言的空安全语义,覆写变异则会把方法解析和链接规则拖到台前,谁偷懒谁暴露。
论文最硬的部分,当然还是结果。CrossLangFuzzer 在最新版本的五个 JVM 编译器上找到了 32 个确认 Bug,分布在 Kotlin、Groovy、Scala 2、Scala 3 和 Java 相关链路中,说明跨语言边界上的问题并没有因为生态成熟就自动消失。
表2:CrossLangFuzzer 发现的 Bug 总览
更关键的是,这些问题不是“实验室里跑不通”的假阳性,而是已经被各自编译器团队验证过的真实缺陷。Groovy 的 4 个问题已经全部修复,Kotlin 也有 1 个已修,剩下的仍在等待处理。对测试工具来说,这种结果比单纯报一堆数字更有分量。
从工程视角看,这篇工作真正的价值不是“又发现了几个 Bug”,而是把跨语言 JVM 编译测试从概念变成了可执行流水线。对于做编译器、静态分析、语言工具链的人来说,这类方法的意义非常直接:边界问题终于有了系统化的抓手。
为什么这篇工作不是普通的编译器 fuzzing?普通 fuzzing 多半盯着单语言前端,CrossLangFuzzer 盯的是“语言之间怎么互相绊脚”。它测试的不是某门语言本身,而是多语言互操作时的语义一致性,这才是 JVM 生态真正麻烦的地方。
它最值得借鉴的工程思路是什么?先抽象出统一 IR,再把生成、变异、打印、运行、约简串成闭环,这个思路很干净。好处是测试逻辑和具体语法解耦,后续要扩语言、扩变异、扩编译器,成本都不会太离谱。
这类方法能不能直接落地到别的语言生态?能借鉴,但不能照抄。统一 IR、差分测试、约简器这三件套很通用,真正难的是把目标语言的语义坑位摸清楚,否则工具再漂亮,也只是“会跑的摆设”。
如果你还有哪些想要了解的,欢迎在评论区留言或者讨论~
论文创新性分数:★★★★☆把差分测试从单语言推进到跨语言 JVM 编译,这是很实在的增量创新,不是换个名字继续跑老套路。
实验合理度:★★★★☆五个编译器、32 个确认 Bug、还能给出修复状态,实验链条完整,可信度不错。
学术研究价值:★★★★☆它补上了跨语言编译测试这一块明显空白,对编译器测试方向有明确补位价值。
稳定性:★★★★☆有统一 IR、约简器和人工确认闭环,工具链不是“跑一次就散架”的那种。
适应性以及泛化能力:★★★☆☆思路能迁移到别的多语言生态,但具体变异点仍然强依赖目标语言语义。
硬件需求及成本:★★★★☆主要成本是编译器运行和回归验证,不算重型模型那种烧钱玩法,工程上可接受。
复现难度:★★★☆☆开源了代码和 Docker,复现门槛不高,但配置多个 JDK 和编译器版本还是有点折腾。
产品化成熟度:★★★☆☆更像高质量研究工具,离“拿来即用的工业平台”还有一段工程打磨距离。
可能的问题:变异器虽然针对性强,但对更复杂的跨语言语义组合覆盖仍有限;另外,论文主要证明“能找到 Bug”,对长期回归监控和大规模持续集成场景的成本评估还不够细。
跨语言编译的“隐形地雷”:一个小例子引爆Kotlin编译器错误
JVM 生态最麻烦的地方,不是某一种语言太难,而是几种语言“看起来都能互通”,实际却各有各的脾气。Java、Kotlin、Groovy、Scala 放在同一个项目里,代码能编过去不代表语义真的对齐。最容易出事的,就是类型系统、空值处理、泛型继承和方法覆写这些细节。平时看着风平浪静,一到编译器边界就可能突然翻车。
图1:Kotlin 编译器因原始类型参数拒绝了一个有效的 Java 程序
图里的例子很典型:Java 代码本身是合法的,但 Kotlin 编译器在处理跨语言继承关系时,却因为对原始类型和泛型语义的理解不一致,把一个本来应该通过的程序拒掉了。更扎心的是,这种问题不是“写错代码”的锅,而是编译器自己在语言边界上没对齐。
论文把这个问题看得很准:单语言测试再强,也很难覆盖多语言交互时的语义缝隙。真正麻烦的 Bug,往往不是“某门语言内部逻辑错了”,而是“几门语言在交界处各说各话”。CrossLangFuzzer 要做的,就是专门去抓这种边界错配。
CrossLangFuzzer:首创跨语言JVM编译器差分测试框架
CrossLangFuzzer 的定位很直接:它是一个面向跨语言 JVM 编译的差分测试框架。这里的“差分测试”可以先理解成一句大白话——同一份程序,让多个编译器或者多个版本去编,看看谁的行为不一样。只要行为出现分歧,就有可能藏着 Bug。它不是靠“编译能不能过”这一条线索,而是靠“大家是不是都做出了同样的判断”来抓问题。
这篇工作的特别之处在于,它不是简单地把单语言 fuzzing 套到 JVM 上,而是直接把测试对象推进到跨语言场景。也就是说,测试样本本身就带着 Kotlin、Java、Groovy、Scala 之间的语义混搭,专门去触发语言边界处的编译器失误。这个方向很实用,因为真实项目里恰恰就是这种混搭最常见。
论文还给出了一个很重要的工程事实:CrossLangFuzzer 不是“跑一把试试”的玩具,而是已经能在最新版本的五个 JVM 编译器上挖出 32 个确认 Bug。这里面包括 Kotlin、Groovy、Scala 2、Scala 3 和 Java 相关链路的问题。换句话说,这不是在旧版本上捡漏,而是在现役编译器里找真问题。
从整体流程看,CrossLangFuzzer 分成四个环节:先生成结构合法的跨语言程序,再通过变异器故意“搅乱”测试样本,然后把统一表示打印成具体源代码,最后交给编译器做正常测试和差分测试。发现异常后,还会进入约简阶段,把触发 Bug 的程序压缩到最小,方便开发者定位。
这套设计的聪明之处在于,它把“合法生成”和“故意捣乱”分开了。生成器负责保证样本有语义密度,变异器负责扩大覆盖面。前者像是先把棋盘摆好,后者像是故意把几颗棋子挪乱,看看编译器会不会当场露馅。思路不花哨,但很有效。
核心设计揭秘:统一IR与7种变异操作符
CrossLangFuzzer 的地基是一个统一中间表示,简称 IR,英文全称是 Intermediate Representation,中文一般叫“中间表示”。这里的 IR 不是编译器术语里的摆设,而是整个测试系统的共同语言:生成、变异、约简、打印都围着它转。
论文借鉴了 Kotlin 编译器后端的 IR 思想,但并不是直接依赖 Kotlin 编译器来造测试,而是自己实现了一套结构抽象。这样做的好处很明显:测试逻辑不绑死在某门语言的语法上,后续要把同一个样本翻译到 Kotlin、Java、Groovy 或 Scala,只需要在打印阶段做映射即可。
IR 的结构也很清晰:顶层是程序节点,下面挂多个类声明,每个类带有目标语言标签、继承链、类型参数和成员函数。说白了,这棵树把“这个类属于哪门语言、继承谁、实现谁、有哪些方法”都显式记下来,后面不管怎么变异,都不会把程序关系搞丢。
更关键的是类型系统。CrossLangFuzzer 的 IR 支持五类类型:普通类型、参数化类型、可空类型、平台类型和类型参数。这里面最值得注意的是 平台类型,它对应 Kotlin 里常见的带感叹号类型,比如 String!。这种类型在跨语言边界上很常见,因为 Java 的空值注解信息不总是完整,Kotlin 就需要用平台类型来表示“这个东西可能空,也可能不空”。而这恰恰是编译器最容易犯迷糊的地方。
论文还提到两种遍历机制:一个是自顶向下的访问器,用来收集和校验结构信息;另一个是原地变换器,用来替换子树。前者更像“体检”,后者更像“外科手术”。生成器和约简器依赖体检维持合法性,变异器则负责做手术,把程序改出各种边界状态。
表1:CrossLangFuzzer 的变异操作符
表1 列出了 7 种变异操作符,虽然名字看着长,但本质上都在围绕四类跨语言分歧下手。第一类是泛型子类型相关变异,比如改父类泛型参数、改方法参数里的嵌套泛型、改类型参数上界;第二类是空值性相关变异,比如把参数从非空改成可空,或者改上界是否允许空;第三类是覆写解析相关变异,直接把被覆写的方法体去掉,看看编译器还能不能正确认亲;第四类是语言位置重分配,把同样的结构放到不同语言组合里重新测试。
这些变异不是为了“随机”,而是为了“精准”。泛型、空值和覆写,本来就是 JVM 多语言互操作的高危区。编译器在这里稍微理解偏一点,就可能把合法程序拒掉,或者把非法程序放过去。CrossLangFuzzer 不是在撒网,而是在往雷区里精准插旗。
图2:CrossLangFuzzer 的变异策略示意
论文还强调,多个变异器可以串联使用,而且变异是按概率加权选择的。这意味着测试样本不会只停留在单一缺陷模式上,而是能逐步叠加复杂度。对编译器来说,这种“层层加压”的方式更容易逼出边界错误,因为真实项目里的问题通常也不是单点孤立出现,而是多种语义因素一起搅出来的。
这里还有一个工程上很加分的细节:当发现 Bug 后,系统会把测试程序序列化回统一 IR,再用基于 DDMin 的约简器做减法。DDMin 的英文全称是 Delta Debugging Minimization,中文可以理解成“增量调试最小化”。它的作用就是不断删掉无关部分,只保留最小触发条件。对编译器开发者来说,这一步非常重要,因为谁都不想面对一大坨几百行的复现代码。
实战成果:32个确认错误,Groovy全部修复
结果部分是这篇论文最有说服力的地方。CrossLangFuzzer 在五个 JVM 编译器上共发现 32 个确认 Bug,而且都经过了对应开发团队验证。这个“确认”很关键,因为编译器测试最怕的就是噪声太多:看起来像 Bug,实际上只是输入非法或者测试样本本身不够严谨。论文能把结果落到“已确认”而不是“疑似”,说明整个测试闭环是站得住的。
表2:CrossLangFuzzer 发现的 Bug 总览
从表2可以看到,这 32 个 Bug 分布并不平均:Kotlin 占了 15 个,Groovy 4 个,Scala 3 有 7 个,Scala 2 有 2 个,Java 相关链路也有 4 个。这个分布本身就说明一个事实:跨语言编译不是“谁语言新谁更容易出事”,而是只要边界处理不严,老语言新语言都可能中招。
更值得注意的是修复状态。Groovy 的 4 个问题已经全部修复,Kotlin 也有 1 个修掉了,剩下的 14 个 Kotlin 问题仍处于确认待修阶段。对于一个测试工具来说,这种结果含金量不低,因为它说明发现的问题不是“学术上有趣”,而是“工程上可落地修复”的真实缺陷。
论文在结果里还传递了一个很清晰的信号:现役 JVM 编译器并没有因为生态成熟就自动免疫跨语言错误。恰恰相反,语言互操作越复杂,越容易在类型转换、继承推断、空值传播这些地方积累“隐形债务”。CrossLangFuzzer 的价值,就是把这些债务翻出来,让它们变成可见、可修、可回归的测试案例。
看到这种结果,比较合理的反应不是“哇,太神了”,而是认真点头。因为这类工作真正难的地方,从来不是把工具跑起来,而是让它找到的每个问题都经得起开发者验证。32 个确认 Bug,说明这套框架不是在编译器门口敲门,而是真的进屋翻出了问题。
未来展望:LLM助力,让模糊测试更智能
论文最后给了一个很自然的延伸方向:因为生成和变异都围绕抽象 IR 展开,所以这套系统很适合和大模型结合。比如,LLM 可以直接读写序列化后的 IR,或者根据错误日志来建议下一步该变异哪里。这里的 LLM 指的是 Large Language Model,也就是大语言模型。
这个方向并不玄。传统 fuzzing 的问题在于,变异虽然快,但“往哪儿变”常常靠经验;而 LLM 擅长从结构化信息里总结模式,正好可以补上这块。比如某类错误老是出现在泛型参数和可空类型的组合上,大模型就可以帮助优先生成这种组合,而不是靠随机碰运气。这样一来,测试效率和命中率都有机会再往上推一截。
不过,别急着把它想成“加个大模型就无敌”。跨语言编译测试最怕的还是语义约束失控。LLM 可以帮忙生成候选样本,但最后能不能落到“结构合法、语义有价值、结果可复现”,还是得靠 IR、约简器和编译器回归验证这套硬骨头。大模型更像加速器,不是替代品。
从更大的视角看,这篇论文的启发也很明确:未来多语言软件会越来越常见,编译器测试也不能还停留在“单语言自嗨”阶段。只要语言边界还在,只要类型系统和空值语义还不完全统一,跨语言差分测试就有继续挖矿的空间。对编译器团队来说,这类工具会越来越像基础设施,而不是锦上添花。
龙迷三问
这篇论文到底解决了什么问题?它解决的是“跨语言 JVM 编译器怎么测”的问题。以前很多测试只盯单语言编译,而这篇工作专门把测试推进到 Kotlin、Java、Groovy、Scala 的交界处,去抓语言互操作时的编译器错误。
IR、平台类型、DDMin 这些词分别是什么意思?IR 是中间表示,是统一的数据结构;平台类型是 Kotlin 里对跨语言边界上“空值信息不完全明确”的类型表示,比如 String!;DDMin 是一种最小化调试方法,用来把触发 Bug 的程序尽量缩小,方便定位问题。
这套方法为什么能找到那么多 Bug?因为它不是随机乱测,而是专挑泛型、空值、覆写和语言组合这些高风险区域下手。再加上差分测试和约简闭环,既能发现不一致,又能把问题压缩到可验证的最小案例,所以结果更容易被开发者确认。
如果你还有哪些想要了解的,欢迎在评论区留言或者讨论~
龙哥点评
论文创新性分数:★★★★☆把差分测试推进到跨语言 JVM 编译边界,这个切口很准,不是换皮式增量。
实验合理度:★★★★☆五个编译器、32 个确认 Bug、还有修复状态,证据链比较完整。
学术研究价值:★★★★☆补上了跨语言编译测试的空白,对编译器测试和语言互操作研究都有价值。
稳定性:★★★★☆有统一 IR、约简器和人工确认闭环,工具链比较扎实,不是一次性 demo。
适应性以及泛化能力:★★★☆☆思路可迁移,但具体变异点还是强依赖目标语言语义,不能无脑照搬。
硬件需求及成本:★★★★☆主要是编译器运行和回归验证,成本不算高,工程上可接受。
复现难度:★★★☆☆代码和 Docker 都给了,但多 JDK、多编译器环境配置还是有点折腾。
产品化成熟度:★★★☆☆更像高质量研究工具,离大规模工业持续集成平台还有距离。
可能的问题:变异策略对更复杂语义组合的覆盖仍有限,长期回归监控和规模化部署成本还需要进一步评估。
Xiaotian Ma, Qiong Feng, Yongqiang Tian, Wei Song, and Peng Liang. CrossLangFuzzer: Differential Testing of Cross-Language JVM Compilers. arXiv:2606.28132v1, 2026.
CrossLangFuzzer 开源代码:https://github.com/XYZboom/CrossLangFuzzer
项目演示视频:https://youtu.be/XBG6dUO0Adk
跨语言编译最怕“表面兼容、暗地翻车”。想继续围观编译器、模糊测试、LLM 和系统论文的硬核拆解,欢迎加入龙哥读论文星球和微信群,少走弯路,多看门道。

欢迎加入龙哥读论文粉丝群,
扫描下方二维码或者添加龙哥助手微信号加群:kangjinlonghelper。
一定要备注:研究方向+地点+学校/公司+昵称(如 图像处理+上海+清华+龙哥),根据格式备注,可更快被通过且邀请进群。

