V8 のヒープの中身を覗き見る

参照がなくなったはずのオブジェクトがなかなか回収されないのでどうしたもんかと思い、昨日今日ととりあえずヒープの中に分け入っています。
GC 管理下のヒープの中身を覗くとか正直あまりやりたくはないのですが、表向きには参照がなくなっているはずなのでだれの責任かつきとめないとなんとなく気持ちが悪いわけでして…。(実装間違っているのかなあ、と不安。)


v8::internal のコードをいろいろおっかけて、すこしはヒープまわりの実装の概要が把握できてきました。
v8 では内部データ構造は v8::internal::Object* として表されており、この値を「ハンドル」(v8::internal::Handle)を用いて間接アクセスします。API レベルで公開されているハンドルとは v8::Utils::OpenHandle(), v8::Utils::ToLocal() などで内外のハンドルと変換できます。(src/api.cc が参考になります。)


オブジェクトのダンプには v8::internal::Object::Print() が使えそうです。たとえばこんな感じでコールバックを書けます。
ちなみに同じようなことをやる %DebugPrint というネイティブ関数(src/runtime.cc)があります。V8 の初期化時に --allow-natives-syntax スイッチを指定することで使えるようになります。リリースビルドでは v8::internal::Object::Print() がないので v8::internal::Object::ShortPrint() で代用することになります。

Handle<Value> Dump(const Arguments& args)
{
  namespace i = v8::internal;
  if (args.Length() > 0) {
    i::Handle<i::Object> obj = Utils::OpenHandle(*args[0]);
    obj->PrintLn();
  }
  return Undefined();
}


このほか v8::internal を使えるようになると、使い方を誤ると容易にクラッシュする下記のような恐ろしい関数も実装できます。
しかし、書いておいてなんですが、こんなものは使わないに越したことはありませんのでご注意。。。

// オブジェクトのアドレスを整数に変換します。
Handle<Value> ToAddress(const Arguments& args)
{
  namespace i = v8::internal;
  if (args.Length() < 1) {
    return Undefined();
  }

  i::Handle<i::Object> obj = Utils::OpenHandle(*args[0]);
  return Uint32::New(reinterpret_cast<uint32_t>(*obj));
}

// 整数をオブジェクトのアドレスと解釈してオブジェクトを返します。
// *WARNING* こちらは間違えるとものすごく危険!
Handle<Value> FromAddress(const Arguments& args)
{
  namespace i = v8::internal;
  if (args.Length() < 1) {
    return Undefined();
  }

  i::Handle<i::Object> obj(reinterpret_cast<i::Object*>(args[0]->Uint32Value()));
  return Utils::ToLocal(obj);
}