如何实现Simple JS子集分析器?验证JS代码是否符合限定功能
实现Simple JS子集分析器的步骤
要做一个基于允许列表的JS子集检查器,核心思路是通过AST(抽象语法树)分析代码结构,逐一验证每个语法节点和API调用是否符合规则。下面是具体实现步骤:
1. 明确允许的语法与API范围
先把规则落地成清晰的列表,避免模糊:
允许的语法
- 变量定义与修改:
var/let/const声明、变量赋值 - 函数相关:普通函数声明、函数表达式、箭头函数(非async)
- 基础语句:条件判断(
if/else)、循环(for/while)、return语句 - 基础表达式:字面量(数字、字符串、布尔值)、标识符、二元/一元运算
禁止的内容
- 语法层面:
async/await、生成器函数、with语句、try/catch(按需调整) - API层面:
eval、Function构造函数、网络操作(fetch/XMLHttpRequest)、定时器(setTimeout/setInterval,按需调整)
2. 选择AST解析工具
用成熟的JS解析器生成AST,推荐Acorn或Esprima,都是轻量、易集成的工具,直接通过npm安装即可。
3. 编写AST遍历与检查逻辑
解析代码生成AST后,遍历每个节点,对照允许列表做校验:
- 检查节点类型:如果节点类型不在允许列表里,直接标记错误
- 检查异步语法:如果函数(含箭头函数)带有
async标记,或者遇到await表达式,直接禁止 - 检查API调用:遍历所有函数调用节点,判断是否调用了禁止的API(包括全局函数和对象方法,比如
window.fetch) - 检查危险构造:比如
new Function()这种动态代码生成的方式,直接禁止
示例代码
下面是用Acorn+acorn-walk实现的极简检查器:
# 先安装依赖 npm install acorn acorn-walk
const acorn = require('acorn'); const walk = require('acorn-walk'); // 允许的AST节点类型集合 const ALLOWED_NODE_TYPES = new Set([ 'Program', 'BlockStatement', 'VariableDeclaration', 'VariableDeclarator', 'AssignmentExpression', 'FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression', 'Literal', 'Identifier', 'BinaryExpression', 'UnaryExpression', 'IfStatement', 'ForStatement', 'WhileStatement', 'ReturnStatement', 'ExpressionStatement' ]); // 禁止的全局API/构造函数 const FORBIDDEN_APIS = new Set([ 'eval', 'fetch', 'XMLHttpRequest', 'Function', 'setTimeout', 'setInterval' ]); function checkSimpleJS(code) { let ast; // 先做语法校验 try { ast = acorn.parse(code, { ecmaVersion: 2020 }); } catch (err) { return { valid: false, errors: [`代码语法错误: ${err.message}`] }; } const errors = []; // 遍历AST节点 walk.simple(ast, { enter(node) { // 检查节点类型是否合法 if (!ALLOWED_NODE_TYPES.has(node.type)) { errors.push(`非法语法: ${node.type}`); } // 检查是否是async函数 if (['FunctionDeclaration', 'FunctionExpression', 'ArrowFunctionExpression'].includes(node.type) && node.async) { errors.push('禁止使用async函数'); } }, // 检查函数调用 CallExpression(node) { // 直接调用全局函数,比如 eval() if (node.callee.type === 'Identifier' && FORBIDDEN_APIS.has(node.callee.name)) { errors.push(`禁止调用API: ${node.callee.name}`); } // 调用对象方法,比如 window.fetch() if (node.callee.type === 'MemberExpression') { const propName = node.callee.property.name; if (FORBIDDEN_APIS.has(propName)) { errors.push(`禁止调用API: ${propName}`); } } }, // 专门检查await表达式 AwaitExpression() { errors.push('禁止使用await'); }, // 检查new构造函数,比如 new XMLHttpRequest() NewExpression(node) { if (node.callee.type === 'Identifier' && FORBIDDEN_APIS.has(node.callee.name)) { errors.push(`禁止使用构造函数: ${node.callee.name}`); } } }); return { valid: errors.length === 0, errors: errors }; } // 测试用例 const validCode = ` let count = 0; const max = 10; function increment() { count += 1; return count; } const double = num => num * 2; while (count < max) { count = double(increment()); } `; const invalidCode = ` async function loadData() { const resp = await fetch('/data'); const result = eval(resp.text()); } `; console.log('合法代码检查结果:', checkSimpleJS(validCode)); console.log('非法代码检查结果:', checkSimpleJS(invalidCode));
4. 优化与扩展
- 可以给错误信息加上行号和列号,Acorn生成的AST节点包含
loc属性,能定位代码位置 - 如果需要支持更多基础语法(比如
switch语句),直接把对应的节点类型加到允许列表里 - 可以扩展API检查,比如禁止访问
window对象的危险属性,或者自定义全局变量的使用
内容的提问来源于stack exchange,提问作者pir
相关产品推荐
相关产品推荐

