背景
最近组里这学期在做JS引擎的差分测试工作。所谓差分测试,就是用多个JS引擎执行同一份测试用例,然后找到执行结果与其他引擎存在差异的某引擎,这样就很可能是该引擎存在bug。
当然,这种差异的情况也有可能是由于不同引擎所支持的ES标准不同所导致的的,所以在测出差异后,我们需要对该用例进行人工分析,以确定其真的是引擎bug,还是只是由于不同标准所引起的差异。
下面将记录我对于一个差异用例的人工分析的过程。
前提
该用例的功能是打印 038 这个八进制数(在JS中0开头表示八进制数,但是注意其中出现了非八进制数:8)。然而,当时较高版本的V8/spiderMonkey/chakra/javascriptcore/nashorn/rhino这些引擎打印的结果都是38,只有jerryscript报了错误:
Script Error: SyntaxError: Invilid number.
显然,其他引擎同时出bug的可能性微乎其微,很大可能是Jerryscript出了问题。而这个问题,到底是Jerryscript真的存在这个bug呢,还是由于Jerryscript不支持ES6(其他引擎均支持ES6),而导致的问题呢?
我内心第一感觉是:应该是ES6对于这种情况有新标准,规定了如何处理 ;而ES5.1没有,所以基于ES5.1标准的jerry就报错了。然而,我也不能肯定,毕竟bug分析的工作可不能仅凭直觉,还是要讲道理的。接下来就需要对ES5.1和ES6的八进制数值的标准进行分析。
分析
首先,贴出ES5.1和ES6的网页版的标准:
我们已知,相较于ES5.1,ES6.0新增了对于二进制和八进制字面量的定义方式,通过前缀0b和0o来分别表示二进制和八进制。比如:
0b111110111 === 503 0o767 === 503
然而这一点好像和我们这个用例没有关系,我们这个用例还是用老方法:以0为前缀来表达八进制数。所以还得继续看标准。
ES标准中涉及这一块主要包含两部分内容,一部分是定义的规则(也就是对数值字面量的语法规范,原文中为“Syntax”);另一部分是对于数字字面量的取值(即一个数值字面量的值应该如何计算,原文中的“Semantics”)。我们先看Syntax,在确定数值规则类型之后,再看Semantic部分,来计算其值。
对于Syntax:
Syntax中又分为两部分内容,规则和规则对应的取值范围,比如ES5.1中:
规则:
这里总共有3类大规则,不同规则类别中存在嵌套。
取值:
(这里只拿ES5.1中的十进制和八进制相关的来说)
注意,在ES标准的附录部分,还存在“拓展模式”,其是对于标准模式的一些补充,而且
只在非严格模式下生效!
不要漏看这部分拓展内容。比如下面这部分则是ES5.1中的拓展内容。
我将两种标准关于Numeric Literals(数字字面量)的语法规则分别进行了总结,如下图所示。
(这里插入规则图)
在看了很久之后,我才真正懂了ES标准对于Numeric Literal的规范是怎么一回事。首先要明确,Syntax中定义了一个个规则,不同规则间存在嵌套,同一规则间存在递归。当有一个新的数字字面量时,JS引擎需要将这一个个规则去套这个数字,看数字符合哪一种。在一层层的关系确定了之后,再根据Semantic的规则,来计算该数字到底代表着什么值。
来看一个例子: 在JS代码中,我们需要打印 038 这个数字:
print(038) // 或者 console.log(038)
对于038这一数字字面量,JS引擎的解释规则是,从右往左,依次寻找符合要求的规则,多次查找,知道最后不能再分了为止。
下面是详细解释:
1. 在ES5.1的标准下,最后一位8属于 DecimalDigit类型,而整体不包含小数点和Exp符号,在规则及其拓展中根本找不到这种情况,所以jerryscript就报错了。
2. 在ES6.0的标准下:
① 最后一位8属于NonOctalDigit类别,而之前的部分(03)则可以归类于LegacyOctalLikeDecimalIntegerLiteral类别,于是,我们将038的类别做了第一次分类:其类别是红框中的部分。
随后,引擎应当查找Semantic中对应的计算值的规则,如下红框所示:
其意思是,这种类别的取值公式为:LegacyOctalLikeDecimalIntegerLiteral的取值 乘以10,再加上NonOctalDigit的值。对于NonOctalDigit的值,我们直接由其Semantic规则:
可知,其值直接就是8。接下来,我们需要对LegacyOctalLikeDecimalIntegerLiteral这部分(即03)继续分类和计算值。
② 对于03,满足其要求的规则为:
知道了规则,之后就还是去Semantic中找该规则对应的计算值的方法。如下:
由此可知,0对应于规则中的0,3对应于规则中的OctalDigit。所以03的取值就是OctalDigit所对应的值。注意,这里并不是直接等于其字面量3了,这里的OctalDigit也是一种类型,也需要查阅其计算值的方法。如下:
这样,我们才能确定地说,03对应的值就是3!
③ 根据①中的公式,038最终的取值为 03的值(3)乘以10,再加上8的取值(8),得到结果38.
(要注意这一点,并不是说038直接去掉0就得到了值,实际上JS引擎还是要经过一步步的计算才得到的值)。
结论
通过上述分析过程,我确定了:Jerryscript不能正确处理打印 038 这个数字字面量的原因,是因为ES5.1中并未对这种情况有所规定,所以并非Jerryscript自身的bug。而其余引擎,由于ES6中存在对应的处理规则,所以能够正确地处理并打印这个量。
综上,这个用例并未真正触发引擎bug。话虽如此,但我能借此机会深入了解了一下ES标准,也算是有所收获。
在此将此过程记录下来,希望能对其他人有所启发。文章写的仓促,有很多不完善和不清楚的地方,欢迎批评指正!