概要

テストやちょっとしたツールを作りたいときに内部変数・関数にアクセスしたい時がありますが、 そんなときに便利なのがvmモジュールです。しかしvmではvarの変数は取得できるものの、letconstの変数は取得できません。 そこで色々試行錯誤して作ったツールについて書きます。

vmを用いた内部関数の取得

vm.runInNewContextではコードを実行時にglobalオブジェクトをセットすることができます。vm.runInThisContextは自動的に現在のglobalオブジェクトが使われます。

この関数は実行時にglobalスコープ上のvarをglobalオブジェクトに代入することで外からのアクセスを可能にしてくれますが、letconstは変換されません。

sample.js

const test1 = test1;
let test2 = test2;
var test3 = test3;

function test(arg1, arg2, arg3) {
  const sum = arg1 + arg2 + arg3;
  let num1 = 1;
  var num2 = 2;
  num3 = 3;
  return sum + num1 + num2 + num3;
}

exec.js

const vm = require(vm);
const fs = require(fs);
const path = require(path);

const filepath = path.resolve(__dirname, sample.js);
const file = fs.readFileSync(filepath, utf8);

const context = {};
vm.runInNewContext(file, context);
console.log(context);
/* 
 * { test3: test3, 
 *   test: [Function: test] } // let, constが取得できない
 */

vm-agent

今回作ったツールはlet, constが取得できるのと、関数を自動実行して値を取得できるツールを作りました。 また関数の実行も可能です。

const fs = require(fs);
const path = require(path);
const { Agent } = require(vm-agent);

const filepath = path.resolve(__dirname, sample.js);
const file = fs.readFileSync(filepath, utf8);

const result1 = new Agent(file)
  .run()
  .getInnerVariable();

console.log(result1);
/* 
 * { test1: test1,
 *   test2: test2,
 *   test3: test3,
 *   test: [Function: test] }
 */

const result2 = new Agent(result1.test)
  .setArguments(4, 5, 6)
  .run()
  .getInnerVariable();

console.log(result2);
/*
 * { arg1: 4, 
 *   arg2: 5, 
 *   arg3: 6, 
 *   sum: 15, 
 *   num1: 1, 
 *   num2: 2, 
 *   num3: 3 }
 */

vm-agent/example at master · suguru03/vm-agent · GitHub

実装内容

実装内容はesprimaを用いてlet, constvarに変換し、escodegenを用いてコードを再構築し実行するというものです。constが上書きできてしまったりパフォーマンスが低下したりしますが、テスト用なのでいったん良しとします。

また関数の実行については、関数の引数を解析し変数に変換することでvmモジュールでのアクセスを可能にしています。

ソース

  • Nodeでプライベートな(exportsされてない)メソッドのテスト - ぶれすとつーる
  • Executing JavaScript | Node.js v7.4.0 Documentation
  • GitHub - suguru03/vm-agent: vm-agent is able to get all inner variables