xengineer’s diary

結果、メモ的な内容になっています。

ruby code readingメモ

概要

ちょっと知りたいことがあって、ふとrubyのコードをみたのでメモ。

ちなみに初めてみるのと、そんなに舐め回すようにみたわけじゃないので、

間違ってるかもしれないです。

ただ、メモっておかないと、次みるとき忘れちゃうので・・・

なぜそんなのをみることになったか。

railsのコードの中でよく出てくる、ClassMethodsモジュールって何かね?

っていうのを調べてて、こんな記事に辿り着いたため。

Rails4 - Rails の module ClassMethods がやっている事 - Qiita

記事中に、include文の中で、append_featuresが呼ばれるんだよね、って書いてあったので、

どれどれ、どこなのかな、と思ってみてみることにしました。

詳細

なにはともあれ、rubyのソースをcloneします。

$ cd <WORKDIR>
$ git clone https://github.com/ruby/ruby.git

rubyっていうディレクトリができるので、入って、lsしてみる。

$ cd ruby
$ ls
BSDL             README.md      compile.c     dmydln.c       eval_jump.c      include       man         parse.y          regerror.c     sparc.c           thread_pthread.h  vm.c             vm_trace.c
CONTRIBUTING.md  aclocal.m4     complex.c     dmyenc.c       ext              inits.c       marshal.c   prelude.rb       regexec.c      spec              thread_sync.c     vm_args.c        vsnprintf.c
COPYING          addr2line.c    configure.in  dmyext.c       file.c           insns.def     math.c      probes.d         regint.h       sprintf.c         thread_win32.c    vm_backtrace.c   win32
COPYING.ja       addr2line.h    constant.h    doc            gc.c             internal.h    method.h    probes_helper.h  regparse.c     st.c              thread_win32.h    vm_core.h
ChangeLog        array.c        cont.c        enc            gc.h             io.c          miniinit.c  proc.c           regparse.h     strftime.c        time.c            vm_debug.h
GPL              benchmark      coverage      encindex.h     gem_prelude.rb   iseq.c        misc        process.c        regsyntax.c    string.c          timev.h           vm_dump.c
KNOWNBUGS.rb     bignum.c       cygwin        encoding.c     gems             iseq.h        missing     random.c         ruby.c         struct.c          tool              vm_eval.c
LEGAL            bin            debug.c       enum.c         golf_prelude.rb  lex.c.blt     nacl        range.c          ruby_atomic.h  symbol.c          transcode.c       vm_exec.c
Makefile.in      bootstraptest  defs          enumerator.c   goruby.c         lib           node.c      rational.c       safe.c         symbol.h          transcode_data.h  vm_exec.h
NEWS             ccan           dir.c         error.c        hash.c           load.c        node.h      re.c             sample         template          util.c            vm_insnhelper.c
README.EXT       class.c        dln.c         eval.c         ia64.s           loadpath.c    numeric.c   regcomp.c        signal.c       test              variable.c        vm_insnhelper.h
README.EXT.ja    common.mk      dln.h         eval_error.c   id_table.c       localeinit.c  object.c    regenc.c         siphash.c      thread.c          version.c         vm_method.c
README.ja.md     compar.c       dln_find.c    eval_intern.h  id_table.h       main.c        pack.c      regenc.h         siphash.h      thread_pthread.c  version.h         vm_opts.h

おお、直下に沢山。

Cとか読むの10年ぶりくらいですけど大丈夫でしょうか。

遠い記憶を掘り起こして・・・Cはまずmain.cだ!!!

$ cat main.c
/**********************************************************************

  main.c -

  $Author$
  created at: Fri Aug 19 13:19:58 JST 1994

  Copyright (C) 1993-2007 Yukihiro Matsumoto

**********************************************************************/

#undef RUBY_EXPORT
#include "ruby.h"
#include "vm_debug.h"
#ifdef HAVE_LOCALE_H
#include <locale.h>
#endif
#ifdef RUBY_DEBUG_ENV
#include <stdlib.h>
#endif

int
main(int argc, char **argv)
{
#ifdef RUBY_DEBUG_ENV
    ruby_set_debug_option(getenv("RUBY_DEBUG"));
#endif
#ifdef HAVE_LOCALE_H
    setlocale(LC_CTYPE, "");
#endif

    ruby_sysinit(&argc, &argv);
    {
        RUBY_INIT_STACK;
        ruby_init();
        return ruby_run_node(ruby_options(argc, argv));
    }
}

色々書いてあるけど、

ruby_init();
return ruby_run_node(ruby_options(argc, argv));

が次かな。ここから色々grep

$ grep -E 'ruby_init' *.c | grep -v 'ruby_init_'
debug.c:    SET_WHEN("gc_stress", *ruby_initial_gc_stress_ptr, Qtrue);
eval.c:ruby_init(void)
gc.c:#define ruby_initial_gc_stress     gc_params.gc_stress
gc.c:VALUE *ruby_initial_gc_stress_ptr = &ruby_initial_gc_stress;
gc.c:    gc_stress_set(objspace, ruby_initial_gc_stress);
loadpath.c:const char ruby_initial_load_paths[] =
main.c: ruby_init();
miniinit.c:const char ruby_initial_load_paths[] = "";
ruby.c:    const char *paths = ruby_initial_load_paths;

ファイル名的に怪しいのは、eval.cである。で、開いてみたところ、

起動時のキーになりそうな関数が沢山。

int
ruby_setup(void)

void
ruby_init(void)

void *
ruby_options(int argc, char **argv)

static void
ruby_finalize_0(void)

などなど

ruby_init()でなにやってるかというと・・・

書いてあるとおり、

Calls ruby_setup() and check error. Prints errors and calls exit(3) if an error occurred.

です。てコメントに書いてあるけど、EXIT_FAILUREは、defineで 1 になってる気がする。まぁいい。

/* Calls ruby_setup() and check error.
 *
 * Prints errors and calls exit(3) if an error occurred.
 */
void
ruby_init(void)
{
    int state = ruby_setup();
    if (state) {
        if (RTEST(ruby_debug))
          error_print();
      exit(EXIT_FAILURE);
    }
}

ruby_setup()は・・・Ruby VMとビルトインライブラリの初期化、と。

Initializes the Ruby VM and builtin libraries.

/* Initializes the Ruby VM and builtin libraries.
 * @retval 0 if succeeded.
 * @retval non-zero an error occurred.
 */
int
ruby_setup(void)
{
    int state;

    if (GET_VM())
      return 0;

    ruby_init_stack((void *)&state);
    Init_BareVM();
    Init_heap();
    Init_vm_objects();

    PUSH_TAG();
    if ((state = EXEC_TAG()) == 0) {
      rb_call_inits();
      ruby_prog_init();
      GET_VM()->running = 1;
    }
    POP_TAG();

    return state;
}

ここから、さっと追ってみて気になったのは、rb_call_inits()くらいかな。

inits.cに定義されている。

$ cat inits.c
/**********************************************************************

  inits.c -

  $Author$
  created at: Tue Dec 28 16:01:58 JST 1993

  Copyright (C) 1993-2007 Yukihiro Matsumoto

**********************************************************************/

#include "internal.h"

#define CALL(n) {void Init_##n(void); Init_##n();}

void
rb_call_inits(void)
{
    CALL(Method);
    CALL(RandomSeed);
    CALL(sym);
    CALL(var_tables);
    CALL(Object);
    CALL(top_self);
    CALL(Encoding);
    CALL(Comparable);
    CALL(Enumerable);
    CALL(String);
    CALL(Exception);
    CALL(eval);
    CALL(safe);
    CALL(jump);
    CALL(Numeric);
    CALL(Bignum);
    CALL(syserr);
    CALL(Array);
    CALL(Hash);
    CALL(Struct);
    CALL(Regexp);
    CALL(pack);
    CALL(transcode);
    CALL(marshal);
    CALL(Range);
    CALL(IO);
    CALL(Dir);
    CALL(Time);
    CALL(Random);
    CALL(signal);
    CALL(load);
    CALL(Proc);
    CALL(Binding);
    CALL(Math);
    CALL(GC);
    CALL(Enumerator);
    CALL(VM);
    CALL(ISeq);
    CALL(Thread);
    CALL(process);
    CALL(Cont);
    CALL(Rational);
    CALL(Complex);
    CALL(version);
    CALL(vm_trace);
}
#undef CALL

ひたすら、Init_XXXXっていうのを呼び出す関数のようで。

その中の、Init_evalってのをみてみると、

Init_eval(void)
{
    ..........
    ...中略...
    ..........
    rb_define_method(rb_cModule, "include", rb_mod_include, -1);
    rb_define_method(rb_cModule, "prepend", rb_mod_prepend, -1);

    rb_define_private_method(rb_cModule, "append_features", rb_mod_append_features, 1);
    rb_define_private_method(rb_cModule, "extend_object", rb_mod_extend_object, 1);
    rb_define_private_method(rb_cModule, "prepend_features", rb_mod_prepend_features, 1);
    rb_define_private_method(rb_cModule, "refine", rb_mod_refine, 1);
    rb_define_private_method(rb_cModule, "using", mod_using, 1);
    ..........
    ...中略...
    ..........
}

・・・・append_featuresがいる。includeも。

rb_define_private_methodは、名前からして、"append_features"って言ったら、rb_mod_append_featuresってことだよ。

なんだろねきっと。

そうすると、rb_mod_includeと、rb_mod_prependを調べると何かわかりそう。

$ grep rb_mod_include *.c
class.c:rb_mod_included_modules(VALUE mod)
class.c:rb_mod_include_p(VALUE mod, VALUE mod2)
eval.c:rb_mod_include(int argc, VALUE *argv, VALUE module)
eval.c: return rb_mod_include(argc, argv, th->top_wrapper);
eval.c:    return rb_mod_include(argc, argv, rb_cObject);
eval.c:    rb_define_method(rb_cModule, "include", rb_mod_include, -1);
object.c:    rb_define_method(rb_cModule, "included_modules", rb_mod_included_modules, 0); /* in class.c */
object.c:    rb_define_method(rb_cModule, "include?", rb_mod_include_p, 1); /* in class.c */

またeval.cかな。

static VALUE
rb_mod_include(int argc, VALUE *argv, VALUE module)
{
    int i;
    ID id_append_features, id_included;

    CONST_ID(id_append_features, "append_features");
    CONST_ID(id_included, "included");

    for (i = 0; i < argc; i++)
      Check_Type(argv[i], T_MODULE);
    while (argc--) {
      rb_funcall(argv[argc], id_append_features, 1, module);
      rb_funcall(argv[argc], id_included, 1, module);
    }
    return module;
}

というわけで、includeされたらきっとこれが呼ばれるのかな。

んで、紆余曲折あって、

      rb_funcall(argv[argc], id_append_features, 1, module);
      rb_funcall(argv[argc], id_included, 1, module);

きっとこれで、append_featuresが呼ばれるのだろう。

これ以上みると夜になりそうなので、今日はここでおしまい。