关于ES5.1与ES6对于不规范的八进制数字表示的问题

背景

最近组里这学期在做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:http://www.ecma-international.org/ecma-262/5.1

ES6.0:http://www.ecma-international.org/ecma-262/6.0

我们已知,相较于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标准,也算是有所收获。

在此将此过程记录下来,希望能对其他人有所启发。文章写的仓促,有很多不完善和不清楚的地方,欢迎批评指正!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值