JavaScript 死循环检测方案:AST 注入与沙箱执行

作者:沉舟时间:2026-10-23 21:19:07浏览:134

本文介绍了一种在 JavaScript 中检测用户代码死循环的方案。通过 iframe 的 sandbox 属性建立安全执行环境,利用 Babel 解析 AST 并在 for/while 循环中注入计数器逻辑,当循环次数超过阈值时抛出错误终止执行,从而避免浏览器主线程阻塞。

前言与问题背景

前言

在执行用户提交的代码时,如何有效防止死循环导致程序挂起?针对这一常见问题,我们进行了相关技术探索与尝试。

前言与问题背景

基于 iframe 的沙箱执行环境

第一步是引入 iframe 机制。

出于安全考量,必须构建一个沙箱环境来运行 JavaScript 代码,以隔离其对主页面的影响。利用 iframe 的 sandbox 属性可以禁用弹窗等危险操作,这提供了必要的安全保障。

关于 iframe 的源地址,虽然 Blob URL 是一种选择,但它会继承当前网页的 origin。若用户执行包含恶意代码片段,可能引发安全风险。因此,最终决定采用 data URI 来指定地址。

需要明确的是,iframe 内的代码执行依然运行在同一个主线程中。

即便代码在 iframe 内部发生死循环,浏览器主线程仍会卡死,因为 iframe 与父页面共享同一执行线程。这意味着无法通过在父页面设置超时检测来解决问题,因为死循环发生时,父页面的检测代码根本无法获得执行机会。

Web Worker 方案可行,但存在 DOM API 限制。

若仅处理纯 JavaScript 逻辑,Web Worker 或许适用。然而,由于实际需求涉及 DOM API,Web Worker 因不支持直接操作 DOM 而被排除。

AST 注入检测逻辑

看来只能对用户代码进行修改。

假设大多数死循环由 while 或 for 循环引起,若能在每次循环迭代中插入检测代码,或许能依据特定条件提前终止循环。

例如以下代码场景:


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

若引入 __detectInfiniteLoop() 方法并在 while 循环内部调用,当循环执行达到 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 的三个核心包。

  1. @babel/parser:用于将代码解析为 AST。
  2. @babel/traverse:用于遍历并搜索 for/while 循环节点。
  3. @babel/generator:用于生成插入检测代码后的最终代码。

首先,将用户代码解析为 AST 结构。


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

接着,准备需要插入的检测代码。

插入代码分为两部分:第一部分是函数定义,可直接作为字符串插入代码头部;第二部分是函数调用,需解析为 AST 节点以便插入到原代码结构中。


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 循环节点的位置,并插入检测器的调用逻辑。


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

前言与问题背景

效果符合预期。

方案总结

总结。

该方法并非完美无缺,但已满足当前需求。用户可根据具体场景进行相应调整。

到此这篇关于如何检测JavaScript中的死循环的文章就介绍到这了,更多相关检测JavaScript死循环内容请搜索本站以前的文章或继续浏览下面的相关文章希望大家以后多多支持本站!

作者最新文章