Babel 去控制流平坦化之while-switch

需求

控制流平坦化,大致就是把自上而下执行的代码通过控制的方式(比如while-switch,for-switch)打乱布局顺序

在不改变代码功能的情况下,给分析代码增加难度

具体解释、图解什么的请搜索引擎走起

下面是代码样例(encode.js)

var array = '4|3|8|5|4|0|2|3'.split('|'), index = 0;

while (true) {
    switch (array[index++]) {
        case '0':
            console.log('This is case 0');
            continue;
        case '1':
            console.log('This is case 1');
            continue;
        case '2':
            console.log('This is case 2');
            continue;
        case '3':
            console.log('This is case 3');
            continue;
        case '4':
            console.log('This is case 4');
            continue;
        case '5':
            console.log('This is case 5');
            continue;
        case '6':
            console.log('This is case 6');
            continue;
        case '7':
            console.log('This is case 7');
            continue;
        case '8':
            console.log('This is case 8');
            continue;
        case '9':
            console.log('This is case 9');
            continue;
        default:
            console.log('This is case [default], exit loop.');
    }
    break;
}

处理后代码(decode.js)

var array = '4|3|8|5|4|0|2|3'.split('|'),
    index = 0;
console.log('This is case 4');
console.log('This is case 3');
console.log('This is case 8');
console.log('This is case 5');
console.log('This is case 4');
console.log('This is case 0');
console.log('This is case 2');
console.log('This is case 3');
console.log('This is case [default], exit loop.');

思路

还原前的代码执行逻辑大致是:

编写 babel 插件

废话不多说,完整插件代码

// decrypt.js
const fs = require('fs');
var util = require('util');
const parser = require('@babel/parser');
const traverse = require('@babel/traverse').default;
const types = require('@babel/types');
const generator = require('@babel/generator').default;

// 程序启动时间
var time_start = new Date().getTime()
// 读取文件
process.argv.length > 2 ? encode_file = process.argv[2] : encode_file = 'encode.js';
process.argv.length > 3 ? decode_file = process.argv[3] : decode_file = 'decode.js';

let jscode = fs.readFileSync(encode_file, { encoding: 'utf-8' });
console.log(util.format('Reading the file [%s] is complete.', encode_file))
// 转换为 ast 树
let ast = parser.parse(jscode);

const visitor =
{
  WhileStatement(path) {
    let { body } = path.node;
    let switch_statement = body.body[0];
    if (!types.isSwitchStatement(switch_statement)) {
      return;
    }
    let { discriminant, cases } = switch_statement;
    // 进一步进行特征判断
    if (!types.isMemberExpression(discriminant) || !types.isUpdateExpression(discriminant.property)) {
      return;
    }

    // 找到 array 是哪定义的,并且使用 path.evaluate() 方法获取其最终值
    let { confident, value } = path.scope.getBinding(discriminant.object.name).path.get('init').evaluate();

    if (!confident) {
      return;
    }
    let array = value, case_map = {}, tmp_array = [];

    for (let c of cases) {
      let { consequent, test } = c;
      let test_value;
      if (test) {
        test_value = test.value;
      }
      else {
        test_value = 'default_case';
      }
      let statement_array = [];
      for (let i of consequent) {
        if (types.isContinueStatement(i)) {
          continue;
        }
        statement_array.push(i);
      }
      case_map[test_value] = statement_array;
    }
    for (let i of array) {
      tmp_array = tmp_array.concat(case_map[i])
    }
    if (case_map.hasOwnProperty('default_case')) {
        tmp_array = tmp_array.concat(case_map['default_case'])
    }
    path.replaceWithMultiple(tmp_array);
    // 手动更新 scope ,防止影响下个插件使用
    path.scope.crawl();
  }
}

//调用插件,处理待处理 js ast 树
traverse(ast, visitor);
console.log('AST traverse completed.')

// 生成处理后的 js
let { code } = generator(ast);
console.log('AST generator completed.')
fs.writeFile(decode_file, code, (err) => { });
console.log(util.format('The javascript code in [%s] has been processed.', encode_file))
console.log(util.format('The processing result has been saved to [%s].', decode_file))
// 程序结束时间
var time_end = new Date().getTime()
console.log(util.format('The program runs to completion, time-consuming: %s s', (time_end - time_start) / 1000))

推荐阅读

参考

Table of Contents