专栏首页BFE.dev前端刷题日记如何检测JavaScript中的死循环?
原创

如何检测JavaScript中的死循环?

如果我们需要执行用户写的代码,如和避免死循环?我们最近遇到了这个问题,因为写错代码很常见,所以我们进行了一下尝试。

首先我们需要使用iframe

这主要是安全考虑,我们需要一个sandbox环境来执行JavaScript,避免影响到整体。iframe的sandbox属性可以用来禁止弹窗等等,非常有用。

地址可以选择Blob url,不过blob url会持有当前web page的origin,如果用户拷贝一些乱七八糟的代码不小心执行的话,会有安全问题。所以最终决定用data URI。

iframe的执行仍然在同一个thread

iframe中执行了代码,发生死循环的时候,浏览器还是死掉了,因为iframe和parent还是在同一个thread。也就是说,我们无法在parent中进行timeout检测,因为检测代码在死循环发生时永远不会被执行

Web Worker可行但不支持DOM API

如果是纯粹的JavaScript代码,或许用web worker可以,但是我们需要DOM API,所以Web Worker也不在考虑范围之中。

看来只能修改用户代码了

假设大多数死循环都是由while/for引起的,如果我们能插入一些代码并在每一次循环中进行检测,我们也许就可以根据某些条件提前终止循环。

比如这样的代码

function abc() {
  while (true) {
    console.log(Date.now())
  }
}

如果我们插入一个 __detectInfiniteLoop() 方法,并在while loop里面调用的话,就可以在loop 10000次的时候报错终止执行。

let __count = 0
const __detectInfiniteLoop = () => {
  if (__count > 10000) {
    throw new Error('Infinite Loop detected')
  }
  __count += 1
}

function abc() {
  while (true) {
    console.log(Date.now())
    __detectInfiniteLoop()
  }
}

操作AST在合适位置插入代码

通过字符串匹配来编辑代码细节太复杂容易出错,我们可以用编辑AST的方式,实际上非常简单。

用到babel的3个package。

  1. @babel/parser - parse 代码为AST
  2. @babel/traverse - 搜索 for/while loop
  3. @babel/generator - 生成插入后的代码

首先 parse用户的代码为AST

import { parse } from '@babel/parser'
const ast = parse(code)

然后我们准备一下需要插入的代码。

代码有两部分,第一部分是function定义,实际上可以在头部插入,所以字符串就够了。第二部分是function的调用,这部分需要插入到AST中,所以也需要parse一下。

const prefix = `
  let __count = 0
  const __detectInfiniteLoop = () => {
    if (__count > 10000) {
      throw new Error('Infinite Loop detected')
    }
    __count += 1
  }
`

const detector = parse(`__detectInfiniteLoop()`)

接下来就找到 while/for/do..while 的位置,然后插入detector的调用。

import traverse from '@babel/traverse'
traverse(ast, {
  ForStatement: function (path) {
    path.node.body.body.push(...detector.program.body)
  },
  WhileStatement: function (path) {
    path.node.body.body.push(...detector.program.body)
  },
  DoWhileStatement: function (path) {
    path.node.body.body.push(...detector.program.body)
  }
})

AST修改好了,最后一步就是生成最终的代码,然后放到iframe中执行。

import generate from '@babel/generator'
const newCode = prefix + generate(ast).code

如愿以偿!撒花!

最后

这个方法不是完美的,不过满足了我们自己的需求。你可以根据需要进行一下调整。

如果有更好的办法,欢迎评论告诉我们。

希望本文能帮助到你,下次再见!

原创声明,本文系作者授权云+社区发表,未经许可,不得转载。

如有侵权,请联系 yunjia_community@tencent.com 删除。

我来说两句

0 条评论
登录 后参与评论

相关文章

  • BFE.dev前端刷题#108. 用队列(Queue)实现栈(Stack)

    如果我们要pop4的话,因为这是一个队列,我们只能把1 dequeue掉。所以为了要得到4,我们必须要把其余的1,2,3给dequeue掉。dequeue掉的元...

    JSer
  • BFE.dev前端刷题#9. 解密消息 (Facebook面试题)

    无法前进的时候,经过的字符就就是隐藏信息。比如上面的二维数组的话,隐藏消息是IROCLED

    JSer
  • BFE.dev前端刷题#32. 实现`Promise.all()`

    fulfill的data需要存在一个数组里,但是promise的fulfill时机未知,先后顺序不定,所以不能push,而是利用index来放置数据到正确的位置...

    JSer
  • 软件的未来是无代码化

    人称T客
  • 究竟怎样写代码才算是好代码

    今天让我们来谈谈代码吧。代码重要吗?当然,代码就是设计(Jack W.Reeves, 1992);代码是最有价值的交付物。我们需要好代码吗?在给“好代码”下个定...

    小程故事多
  • 从教女友写代码中学到的

    从教女友写代码中学到的,教人写代码在一定程度上是硬件问题 本文由 伯乐在线 - 奇风余谷 翻译自 Shu Uesugi。译文链接见文末“阅读原文”。 从今年四月...

    Crossin先生
  • 30分钟轻松搞定代码瘦身

    导语 当一个新的产品想要复用一个旧的产品的逻辑的时候,是直接把全盘的代码copy过去就可以了吗?站在功能的角度当然没问题,但是这对于新产品是相当臃肿的,因为一些...

    腾讯移动品质中心TMQ
  • 编程新手入门踩过的25个“坑”,你犯过其中哪些错误?

    大数据文摘
  • 一步步教你编写不可维护的 PHP 代码

    随着失业率越来越高,很多人意识到保全自己的工作是多么的重要。那么,什么是保住自己工作,并让自己无可替代的好方法呢?一个很简单的事实是只要你的代码没有人能够维护,...

    猿哥
  • 欲火焚身,心静则凉--只靠冲动是不能长久地

    抱歉,今天我标题党了,Orz... 解释一下标题的意思, 1、“欲火焚身”,看前端开发工程师的工资都二三十k,激动了,想像着自己也拿二三十k的月薪,会是个什么情...

    web前端教室

扫码关注云+社区

领取腾讯云代金券