クラスメソッドからクラス変数を参照 - クラス外で定義されたクラスメソッドの場合 (ruby 1.9) [その1]
今回は、クラス外で定義されたクラスメソッドからクラス変数を参照する場合の内部動作を調べてみる。Ruby のコードは以下のとおり。
class A @@a = "Hello" end def A.test_out puts @@a end A.test_out
前回やったのと同じ要領で、puts @@a に対応する YARV コード getclassvariable にブレークポイントを設定して、プログラムを実行する。
$ gdb ../ruby-trunk GNU gdb Red Hat Linux (6.3.0.0-1.21rh) Copyright 2004 Free Software Foundation, Inc. GDB is free software, covered by the GNU General Public License, and you are welcome to change it and/or distribute copies of it under certain conditions. Type "show copying" to see the conditions. There is absolutely no warranty for GDB. Type "show warranty" for details. This GDB was configured as "i386-redhat-linux-gnu"...Using host libthread_db library "/lib/libthread_db.so.1". (gdb) b insns.def:218 Breakpoint 1 at 0x80e29d0: file insns.def, line 218. (gdb) r test_out.rb Starting program: /home/g-squid/projects/ruby-trunk/ruby-trunk test_out.rb Reading symbols from shared object read from target memory...done. Loaded system supplied DSO at 0x8e0000 [Thread debugging using libthread_db enabled] [New Thread -1208920384 (LWP 19753)] [New Thread -1208923216 (LWP 19756)] [Switching to Thread -1208920384 (LWP 19753)] Breakpoint 1, th_eval (th=0x8f602e0, initial=0) at insns.def:218 218 VALUE klass = eval_get_cvar_base(th, GET_ISEQ()); (gdb)
つづけて、1行実行して klass の値を得る。
(gdb) n test_out.rb:6: warning: class variable access from toplevel 219 val = rb_cvar_get(klass, id); (gdb)
warning が出てしまった。返ってきたクラスが何なのか調べてみよう。
(gdb) p/x *(struct RClass*)klass $3 = {basic = {flags = 0x3, klass = 0xb7eafbfc}, iv_tbl = 0x8562780, m_tbl = 0x8562708, super = 0xb7eaf9a4} (gdb) p rb_class2name(klass) $4 = 0xb7e70e3c "Object" (gdb)
flags の値が 0x3 なので、特異クラスではない普通のクラスだ。クラス名を調べてみると Object だった。Object クラスが返ってきたらしい。
前回調べたときは、ここでクラス A が返っていた。クラス変数はクラス A のインスタンス変数テーブルに格納されていたので、ここで Object クラスが返ってくるのは、何かおかしい。
Object クラスには目的のクラス変数 @@a は定義されてないはずだ。デバッガで次の関数 rb_cvar_get() の内部を追って確認してみよう。rb_cvar_get() の定義は以下のようになっている。前回同様、マクロ CVAR_LOOKUP は展開してある。
VALUE rb_cvar_get(VALUE klass, ID id) { VALUE value, tmp; tmp = klass; do { if (RCLASS(klass)->iv_tbl && st_lookup(RCLASS(klass)->iv_tbl,id,(&value))) { return (value); } if (FL_TEST(klass, FL_SINGLETON) ) { VALUE obj = rb_iv_get(klass, "__attached__"); switch (TYPE(obj)) { case T_MODULE: case T_CLASS: klass = obj; break; default: klass = RCLASS(klass)->super; break; } } else { klass = RCLASS(klass)->super; } while (klass) { if (RCLASS(klass)->iv_tbl && st_lookup(RCLASS(klass)->iv_tbl,id,(&value))) { return (value); } klass = RCLASS(klass)->super; } } while(0); rb_name_error(id,"uninitialized class variable %s in %s", rb_id2name(id), rb_class2name(tmp)); return Qnil; /* not reached */ } (variable.c r12003改)
デバッガでこのコードを追っていくと、すべての st_lookup() に失敗して、最終的に rb_name_error() の行に到達するのが分かる。
途中の FL_TEST(klass, FL_SINGLETON) の行は、klass の特異クラスフラグが立っているかをチェックしている。この時点での klass は、最初に引数として渡された Object クラスだから、特異クラスでないことは既に確認してある。したがって、この if 文は else の方が実行される。つまり、このコードは Object クラスのスーパークラスを順々に遡って @@a が定義されているかどうかを調べているということになる。
クラス A が Object のスーパークラスであることはまずありえないので、Object とそのスーパークラスのどれにも @@a がないというのは理解できる。
となると、エラーの原因はやはり、eval_get_cvar_base() がクラス A でなく Object を返していることらしい。
eval_get_cvar_base() を実行したときに表示された警告のメッセージは「トップレベルからのクラス変数アクセス」と言っていた。Ruby でトップレベルというと、クラスやモジュール定義の内部ではない「地」の部分だ。
クラス外で定義されたクラス関数で参照されているから、この警告が出たのだろうか?eval_get_cvar_base() の中身を調べてみよう。
EVALBODY_HELPER_FUNCTION VALUE eval_get_cvar_base(rb_thread_t *th, rb_iseq_t *iseq) { NODE *cref = get_cref(iseq, th->cfp->lfp); VALUE klass = Qnil; if (cref) { klass = cref->nd_clss; if (!cref->nd_next) { rb_warn("class variable access from toplevel"); } } if (NIL_P(klass)) { rb_raise(rb_eTypeError, "no class variables available"); } return klass; } (vm.c r12003)
さっそく問題の警告メッセージ "class variable access from toplevel" が現れた。get_cref() で返った cref の nd_next が NULL のときに、このメッセージが表示されるらしい。
get_cref() と cref については以前触れた。要するに、get_cref() は iseq で表わされる命令列が属するクラスネストの一番下の cref を返す。cref はリンクリストになっていて、nd_next はクラスネストの上位の cref を指す。この辺りのデータ構造の説明は RHG 第14章 ruby_cref が詳しい。
ともかく、nd_next が NULL ということは、それが最上位の cref ということになる。get_cref() はネストの最下位の cref を返すのだから、ここでは一つしかクラスがないことになる。
一つしかないクラスネスト(つまりネストしていない)、しかもそのクラスが Object というのはどういうことか。
警告メッセージから推して、クラスネストのトップレベルのクラスが Object であることが想像できるが、これはあくまでも想像だ。真実を知るためには、この cref に Object クラスが代入される現場(コード)を直接押さえるのが確実だ。
どこから Object クラスがやってきて、どういう経緯で cref にセットされたかが分かれば、Object クラスが cref で果たしている役割も分かる。
この調査は長くなるのでまた次回に。