java 非法转义符报错,从一个 JSON.parse 错误深入研究 JavaScript 的转义字符

原标题:从一个 JSON.parse 错误深入研究 JavaScript 的转义字符

JSON.parse将一个 JSON 字符串转换为 Java 对象。

JSON.parse('{"hello":"world"}')

以上代码输出:

{

hello:"world"

}

是一个 Java 对象,但是仔细观察会发现, "world"变成了 "world"。

那么我们继续运行如下代码:

JSON.parse('{"hello":"world"}')

出抛出异常:

VM376:1UncaughtSyntaxError:Unexpectedtoken winJSON at position11

at JSON.parse()

at:1:6

Unexpectedtoken w。

好奇心不死,继续试,3 个反斜杠:

JSON.parse('{"hello":"world"}')

结果是:

VM16590:1UncaughtSyntaxError:Unexpectedtoken winJSON at position11

at JSON.parse()

at:1:6

继续,4 个反斜杠:

JSON.parse('{"hello":"world"}')

结果正常:

{

hello:"world"

}

1 个,"world"

2 个,Error

3 个,Error

4 个,"world"

5 个,"world"

6 个,Error

7 个,Error

8 个,"world"

。。。

我们换个思路,把 JSON.parse去掉,只输出 Java 字符串:

>'hello'

"hello"

>'hello'

"hello"

>'hello'

"hello"

>'hello'

"hello"

>'hello'

"hello"

问题大概找到了。

把上面的规则带入到之前的 JSON.parse代码,问题就解决了。

我们看看 JSON 的字符串解析规则:

java 非法转义符报错,从一个 JSON.parse 错误深入研究 JavaScript 的转义字符_第1张图片

根据这个规则,我们解析一下 "hello",第 1 个字符是反斜杠( ),所以在引号后面走最下面的分支(红线标注):

java 非法转义符报错,从一个 JSON.parse 错误深入研究 JavaScript 的转义字符_第2张图片

第 2 个字符是 h,但是反斜杠后面只有 9 条路,这个不属于任何一条路,所以这个是个非法字符。

不只是 JSON,在很多语言中都会抛出类似 Error:(7,27)Illegalescape:'h'的错误。

但是不知道为什么 Java 偏偏可以解析这个非法转义字符,而解决方式也很暴力:直接忽略。

在 es 规范我没有找到具体的章节。去看看 V8 是怎么解析的吧。

引擎读取 Java 源码后首先进行词法分析,文件 /src/parsing/scanner.cc 的功能是读取源码并解析(当前最新版 6.4.286)。

找到 Scanner::Scan()函数关键代码:

case'"':

case''':

token=ScanString();

break;

是一个很长的 switch语句:如果遇到双引号( ")、单引号( ')则调用 ScanString()函数。

简单解释下:以上代码是 C++ 代码,在 C++ 中单引号是字符,双引号是字符串。所以表示字符时,双引号不需要转义,但是单引号需要转义;而表示字符串时,正好相反。此处的 C++ 转义并不是我们今天要研究的转义。

在 ScanString()函数中我们也只看重点代码:

while(c0_!=quote&&c0_!=kEndOfInput&&!IsLineTerminator(c0_)){

uc32 c=c0_;

Advance();

if(c==''){

if(c0_==kEndOfInput||!ScanEscape()){

returnToken::ILLEGAL;

}

}else{

AddLiteralChar(c);

}

}

if(c0_!=quote)returnToken::ILLEGAL;

literal.Complete();

如果已经到了末尾,或者下 1 个字符是不能转义的字符,则返回 Token::ILLEGAL。那么我们看看 ScanEscape是不是返回了 false呢?

template

boolScanner::ScanEscape(){

uc32 c=c0_;

Advance();

// Skip escaped newlines.

if(!in_template_literal&&c0_!=kEndOfInput&&IsLineTerminator(c)){

// Allow escaped CR+LF newlines in multiline string literals.

if(IsCarriageReturn(c)&&IsLineFeed(c0_))Advance();

returntrue;

}

switch(c){

case''':// fall through

case'"':// fall through

case'':break;

case'b':c='b';break;

case'f':c='f';break;

case'n':c='n';break;

case'r':c='r';break;

case't':c='t';break;

case'u':{

c=ScanUnicodeEscape();

if(c<0)returnfalse;

break;

}

case'v':

c='v';

break;

case'x':{

c=ScanHexNumber(2);

if(c<0)returnfalse;

break;

}

case'0':// Fall through.

case'1':// fall through

case'2':// fall through

case'3':// fall through

case'4':// fall through

case'5':// fall through

case'6':// fall through

case'7':

c=ScanOctalEscape(c,2);

break;

}

// Other escaped characters are interpreted as their non-escaped version.

AddLiteralChar(c);

returntrue;

}

这个函数只有 2 处返回了 false。

1、如果转义字符后面是 u, u后面不是 Unicode 字符时,返回 false

2、如果转义字符后面是 x, x后面不是十六进制数字时,返回 false

也就是说: 'u'、 'uhello'、 'u1'、 'x'、 'xx'都抛出异常。

UncaughtSyntaxError:InvalidUnicodeescape sequence

UncaughtSyntaxError:Invalidhexadecimal escape sequence

而其它非转义字符,都直接执行了后面的代码:

AddLiteralChar(c);

returntrue;

前面的注释也说明了这一点:

// Other escaped characters are interpreted as their non-escaped version.

其他转义字符被解释为对应的非转义版本。

综上,问题的根源就是 Java 和 JSON 对转义字符的处理方式不同,导致了难以发现的 bug。JSON 遇到不能转义的字符直接抛出异常,而 Java 遇到不能转义的字符直接解释为对应的非转义版本。

公众号回复 V8查看更多 V8 专题文章。返回搜狐,查看更多

责任编辑:

你可能感兴趣的:(java,非法转义符报错)