JavaScript 转义字符JSON parse错误研究

目录
  • JSON 字符串转换为 JavaScript 对象
  • 找到 Scanner::Scan() 函数关键代码:
  • ScanString() 函数

JSON 字符串转换为 JavaScript 对象

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

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

以上代码输出:

{
  hello: "world"
}

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

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

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

出抛出异常:

VM376:1 Uncaught SyntaxError: Unexpected token w in JSON at position 11
    at JSON.parse (<anonymous>)
    at <anonymous>:1:6

Unexpected token w。

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

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

结果是:

VM16590:1 Uncaught SyntaxError: Unexpected token w in JSON at position 11
    at JSON.parse (<anonymous>)
    at <anonymous>: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 去掉,只输出 JavaScript 字符串:

> 'hello'
"hello"
> '\hello'
"hello"
> '\\hello'
"\hello"
> '\\\hello'
"\hello"
> '\\\\hello'
"\\hello"

问题大概找到了。

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

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

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

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

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

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

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

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

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

case '"':
case '\'':
  token = ScanString();
break;

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

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

ScanString() 函数

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

while (c0_ != quote && c0_ != kEndOfInput && !IsLineTerminator(c0_)) {
  uc32 c = c0_;
  Advance();
  if (c == '\\') {
    if (c0_ == kEndOfInput || !ScanEscape<false, false>()) {
      return Token::ILLEGAL;
    }
  } else {
    AddLiteralChar(c);
  }
}
if (c0_ != quote) return Token::ILLEGAL;
literal.Complete();

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

template <bool capture_raw, bool in_template_literal>
bool Scanner::ScanEscape() {
  uc32 c = c0_;
  Advance<capture_raw>();
  // 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<capture_raw>();
    return true;
  }
  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<capture_raw>();
      if (c < 0) return false;
      break;
    }
    case 'v':
      c = '\v';
      break;
    case 'x': {
      c = ScanHexNumber<capture_raw>(2);
      if (c < 0) return false;
      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<capture_raw>(c, 2);
      break;
  }
  // Other escaped characters are interpreted as their non-escaped version.
  AddLiteralChar(c);
  return true;
}

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

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

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

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

Uncaught SyntaxError: Invalid Unicode escape sequence

Uncaught SyntaxError: Invalid hexadecimal escape sequence

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

AddLiteralChar(c);
return true;

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

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

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

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

以上就是JavaScript 转义字符JSON parse错误研究的详细内容,更多关于JavaScript JSON parse错误的资料请关注我们其它相关文章!

(0)

相关推荐

  • JavaScript中直接写undefined的用法剖析

    目录 介绍 原因 解决 结语 介绍 众所周知,在JavaScript 语言中,一个没有被赋值的变量会有个默认值 undefined ,而 undefined 作为全局对象的一个属性经常会用作一些赋值返回,逻辑判断等业务场景中.可本期要说的是,尽量不要直接去拼写 undefined 了,因为那是不太严谨的行为,可能出现很多意外情况. 原因 我们经常在判断某个变量是否等于 undefined ,如下: function test(value) { if (value === undefined) {

  • JS前端错误监控捕获以及上报方法详解

    目录 前端错误捕获方法 不同场景错误处理方式 错误信息上报 ajax进行上报 image上报 sendBeacon 前端错误捕获方法 前端捕获错误的方法: try..catch:捕获的异常必须是线程执行进入到try...catch且try...catch未执行完的时候抛出来. 语法异常在语法检查阶段就报错了,线程尚未进入try...catch代码块,所以无法捕获到异常. try { a. }catch(e) { console.log('catch error:', e) } 不能捕获setTi

  • vue项目前端错误收集之sentry教程详解

    sentry简介 Sentry 是一个开源的错误追踪工具,可以帮助开发人员实时监控和修复系统中的错误.其专注于错误监控以及提取一切事后处理所需的信息;支持几乎所有主流开发语言( JS/Java/Python/php )和平台, 并提供了web来展示输出错误. sentry官网: https://sentry.io/ sentry安装 sentry 是一个开源的工具,可以自行搭建. 官方支持两种安装和运行 Sentry 服务器的方法, Docker 和 Python .推荐使用 Docker .

  • Laravel Validator自定义错误返回提示消息并在前端展示

    前言 Laravel Validator 默认返回的是英文的提示消息,而大多数情况我们需要自定义错误返回提示消息,本文将介绍一下如何自定义错误消息,并在前端展示. 自定义错误消息 别怪我太直接,代码奉上 $messages = [ 'phone.unique' => '重复的电话号码', 'required' => '请将信息填写完整', ]; $this->validate($request, [ 'phone' => 'required|unique:table_name',

  • Node.js在child_process域和错误冒泡及捕获实践

    目录 child_process options.detached cipher.update 错误的冒泡和捕获 child_process child_进程模块提供派生子进程的功能.它与popen(3)相似但不相同.此函数主要由[child_process.spown()]函数提供: const { spawn } = require('child_process'); const ls = spawn('ls', ['-lh', '/usr']); ls.stdout.on('data',

  • 前端项目中的Vue、React错误监听

    目录 一. Vue 错误监听 window.onerror errorCaptured 生命周期 errorHandler 异步错误 答案 扩展 二.React 错误监听 ErrorBoundary dev 和 build 事件报错 异步错误 答案 扩展 一. Vue 错误监听 题目: 如何统一监听 Vue 组件报错? 分析: 真实项目需要闭环,即考虑各个方面,除了基本的功能外,还要考虑性能优化.报错.统计等. 而个人项目.课程项目一般以实现功能为主,不会考虑这么全面.所以,没有实际工作经验的同

  • JavaScript 转义字符JSON parse错误研究

    目录 JSON 字符串转换为 JavaScript 对象 找到 Scanner::Scan() 函数关键代码: ScanString() 函数 JSON 字符串转换为 JavaScript 对象 JSON.parse 将一个 JSON 字符串转换为 JavaScript 对象. JSON.parse('{"hello":"\world"}') 以上代码输出: {  hello: "world"} 是一个 JavaScript 对象,但是仔细观察会

  • javascript中JSON.parse()与eval()解析json的区别

    本文实例讲述了javascript中JSON.parse()与eval()解析json的区别.分享给大家供大家参考,具体如下: JSON(JavaScript Object Notation)是一种轻量级的数据格式,采用完全独立于语言的文本格式,是理想的数据交换格式.同时,JSON是Javascript原生格式,这意味着在javascript中处理JSON数据 基本格式: varjsonData='{"data1":"Hello,","data2"

  • JavaScript 中 JSON.parse 函数 和 JSON.stringify 函数

    1. JSON.parse 函数: 使用 JSON.parse 可将 JSON 字符串转换成对象. <!doctype html> <html> <head> <meta charset="UTF-8"> <title>Document</title> <script type="text/javascript"> var jsontext = '{"Name":

  • 转义字符(\)对JavaScript中JSON.parse的影响概述

    按照ECMA262第五版中的解释,JSON是一个提供了stringify和parse方法的内置对象,前者用于将js对象转化为符合json标准的字符串,后者将符合json标准的字符串转化为js对象.json标准参考<a href="http://json.org/" target="_blank">json.org</a>.(其实将符合json标准的字符串转化为js对象可以用eval,但是eval性能相对差且存在安全隐患(会执行json字符串中

  • JSON在Javascript中的使用(eval和JSON.parse的区别)详细解析

    JSON 和XML比较 json的长度和xml比起来更加短小 json读取速度更快 json可以使用JavaScript的内置方法直接进行解析,转化成javascript对象,非常方便. 在Javascript使用eval将接送转化为json对象 var jsonData = '{"persons":{"name":"成龙","age":58},{"name":"吴京","ag

  • JavaScript对象与JSON格式的转换及JSON.stringify和JSON.parse的使用方法

    目录 JSON处理 JSON.stringify stringify的限制 排除和替换 映射函数 格式化使用的空格数量 自定义toJSON方法 JSON.parse 使用reviver 总结 JSON处理 JSON(JavaScript Object Notation)是JavaScript表达值和对象的通用数据格式,其本质就是符合一定规范的字符串.由于JSON的优良特性,非常容易和其他语言进行数据交换,尤其在前后端交互方面.即使我们前端使用JavaScript,后端使用Java/PHP/Pyt

  • 详解JSON.parse和JSON.stringify用法

    JSON格式,(简写JavaScript Object Notation),是一种用于数据交换的文本格式,书写简单.基于JavaScript原生语法,能够序列化对象.数组.数值.字符串.布尔值和 null. 在ES5中,增加了一个JSON对象,专门用来处理JSON格式的数据.JSON是一个对象,但只有两个方法:parse 和 stringify,不能作为构造函数,也无属性. typeof JSON === 'object' JSON.parse JSON.parse() 用来解析JSON字符串,

  • javascript中json基础知识详解

    大致介绍 JSON(JavaScript Object Notation  JavaScript对象表示法),JSON是一种数据格式,不是一种编程语言.虽然它的名字中有JavaScript但是它却不属于JavaScript,就像Java和JavaScript的关系一样.而且,并不是只有JavaScript才使用它,毕竟 JSON 只是一种数据格式.很多编程语言都有针对 JSON 的解析器和序列化器. JSON是由Douglas Crockford在2001年提出,为了取代XML 语法 JSON的

  • JavaScript解析JSON格式数据的方法示例

    本文实例讲述了JavaScript解析JSON格式数据的方法.分享给大家供大家参考,具体如下: 1.使用JavaScript提供的eval()函数 function JsonText1() { var strJSON = "{'Name':'Kevin','Age':'23'}"; //得到的JSON var obj = eval("(" + strJSON + ")"); //转换后的JSON对象 alert(obj.Name); } 2.使用

  • JavaScript解析JSON数据示例

    本文实例讲述了JavaScript解析JSON数据.分享给大家供大家参考,具体如下: JSON数据是一种常用的数据格式,解析方式也比较简单,特别是由于JavaScript原生就支持JSON,所以JavaScript能够更好的解析JSON. <!DOCTYPE html> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8&quo

随机推荐