Ruby Hacking Guide
Chapter 15: Methods
In this chapter, I’ll talk about method searching and invoking.
Searching methods
Terminology
In this chapter, both method calls and method definitions are discussed, and there will appear really various “arguments”. Therefore, to make it not confusing, let’s strictly define terms here:
m(a) # a is a "normal argument" m(*list) # list is an "array argument" m(&block) # block is a "block argument" def m(a) # a is a "normal parameter" def m(a=nil) # a is an "option parameter", nil is "it default value". def m(*rest) # rest is a "rest parameter" def m(&block) # block is a "block parameter"
In short, they are all “arguments” when passing and “parameters” when receiving, and each adjective is attached according to its type.
However, among the above things, the “block arguments” and the “block parameters” will be discussed in the next chapter.
Investigation
obj.method(7,8)
NODE_CALL nd_mid = 9049 (method) nd_recv: NODE_VCALL nd_mid = 9617 (obj) nd_args: NODE_ARRAY [ 0: NODE_LIT nd_lit = 7:Fixnum 1: NODE_LIT nd_lit = 8:Fixnum ]
The node for a method call is `NODE_CALL`. The `nd_args` holds the arguments as a list of `NODE_ARRAY`.
Additionally, as the nodes for method calls, there are also `NODE_FCALL` and `NODE_VCALL`. `NODE_FCALL` is for the “`method(args)`” form, `NODE_VCALL` corresponds to method calls in the “`method`” form that is the same form as the local variables. `FCALL` and `VCALL` could actually be integrated into one, but because there’s no need to prepare arguments when it is `VCALL`, they are separated from each other only in order to save both times and memories for it.
Now, let’s look at the handler of `NODE_CALL` in `rb_eval()`.
2745 case NODE_CALL: 2746 { 2747 VALUE recv; 2748 int argc; VALUE *argv; /* used in SETUP_ARGS */ 2749 TMP_PROTECT; 2750 2751 BEGIN_CALLARGS; 2752 recv = rb_eval(self, node->nd_recv); 2753 SETUP_ARGS(node->nd_args); 2754 END_CALLARGS; 2755 2756 SET_CURRENT_SOURCE(); 2757 result = rb_call(CLASS_OF(recv),recv,node->nd_mid,argc,argv,0); 2758 } 2759 break; (eval.c)
The problems are probably the three macros, `BEGIN_CALLARGS SETUP_ARGS() END_CALLARGS`. It seems that `rb_eval()` is to evaluate the receiver and `rb_call()` is to invoke the method, we can roughly imagine that the evaluation of the arguments might be done in the three macros, but what is actually done? `BEGIN_CALLARGS` and `END_CALLARGS` are difficult to understand before talking about the iterators, so they are explained in the next chapter “Block”. Here, let’s investigate only about `SETUP_ARGS()`.
`SETUP_ARGS()`
`SETUP_ARGS()` is the macro to evaluate the arguments of a method. Inside of this macro, as the comment in the original program says, the variables named `argc` and `argv` are used, so they must be defined in advance. And because it uses `TMP_ALLOC()`, it must use `TMP_PROTECT` in advance. Therefore, something like the following is a boilerplate:
int argc; VALUE *argv; /* used in SETUP_ARGS */ TMP_PROTECT; SETUP_ARGS(args_node);
`args_node` is (the node represents) the arguments of the method, turn it into an array of the values obtained by evaluating it, and store it in `argv`. Let’s look at it:
1780 #define SETUP_ARGS(anode) do {\ 1781 NODE *n = anode;\ 1782 if (!n) {\ no arguments 1783 argc = 0;\ 1784 argv = 0;\ 1785 }\ 1786 else if (nd_type(n) == NODE_ARRAY) {\ only normal arguments 1787 argc=n->nd_alen;\ 1788 if (argc > 0) {\ arguments present 1789 int i;\ 1790 n = anode;\ 1791 argv = TMP_ALLOC(argc);\ 1792 for (i=0;i<argc;i++) {\ 1793 argv[i] = rb_eval(self,n->nd_head);\ 1794 n=n->nd_next;\ 1795 }\ 1796 }\ 1797 else {\ no arguments 1798 argc = 0;\ 1799 argv = 0;\ 1800 }\ 1801 }\ 1802 else {\ both or one of an array argument 1803 VALUE args = rb_eval(self,n);\ and a block argument 1804 if (TYPE(args) != T_ARRAY)\ 1805 args = rb_ary_to_ary(args);\ 1806 argc = RARRAY(args)->len;\ 1807 argv = ALLOCA_N(VALUE, argc);\ 1808 MEMCPY(argv, RARRAY(args)->ptr, VALUE, argc);\ 1809 }\ 1810 } while (0) (eval.c)
This is a bit long, but since it clearly branches in three ways, not so terrible actually. The meaning of each branch is written as comments.
We don’t have to care about the case with no arguments, the two rest branches are doing similar things. Roughly speaking, what they are doing consists of three steps:
- allocate a space to store the arguments
- evaluate the expressions of the arguments
- copy the value into the variable space
If I write in the code (and tidy up a little), it becomes as follows.
/***** else if clause、argc!=0 *****/ int i; n = anode; argv = TMP_ALLOC(argc); /* 1 */ for (i = 0; i < argc; i++) { argv[i] = rb_eval(self, n->nd_head); /* 2,3 */ n = n->nd_next; } /***** else clause *****/ VALUE args = rb_eval(self, n); /* 2 */ if (TYPE(args) != T_ARRAY) args = rb_ary_to_ary(args); argc = RARRAY(args)->len; argv = ALLOCA_N(VALUE, argc); /* 1 */ MEMCPY(argv, RARRAY(args)->ptr, VALUE, argc); /* 3 */
`TMP_ALLOC()` is used in the `else if` side, but `ALLOCA_N()`, which is ordinary `alloca()`, is used in the `else` side. Why? Isn’t it dangerous in the `C_ALLOCA` environment because `alloca()` is equivalent to `malloc()` ?
The point is that “in the `else` side the values of arguments are also stored in `args`”. If I illustrate, it would look like Figure 1.
If at least one `VALUE` is on the stack, others can be successively marked through it. This kind of `VALUE` plays a role to tie up the other `VALUE`s to the stack like an anchor. Namely, it becomes “`anchor VALUE`”. In the `else` side, `args` is the anchor `VALUE`.
For your information, “anchor `VALUE`” is the word just coined now.
`rb_call()`
`SETUP_ARGS()` is relatively off the track. Let’s go back to the main line. The function to invoke a method, it is `rb_call()`. In the original there’re codes like raising exceptions when it could not find anything, as usual I’ll skip all of them.
static VALUE rb_call(klass, recv, mid, argc, argv, scope) VALUE klass, recv; ID mid; int argc; const VALUE *argv; int scope; { NODE *body; int noex; ID id = mid; struct cache_entry *ent; /* search over method cache */ ent = cache + EXPR1(klass, mid); if (ent->mid == mid && ent->klass == klass) { /* cache hit */ klass = ent->origin; id = ent->mid0; noex = ent->noex; body = ent->method; } else { /* cache miss, searching step-by-step */ body = rb_get_method_body(&klass, &id, &noex); } /* ... check the visibility ... */ return rb_call0(klass, recv, mid, id, argc, argv, body, noex & NOEX_UNDEF); }
The basic way of searching methods was discussed in chapter 2: “Object”. It is following its superclasses and searching `m_tbl`. This is done by `search_method()`.
The principle is certainly this, but when it comes to the phase to execute actually, if it searches by looking up its hash many times for each method call, its speed would be too slow. To improve this, in `ruby`, once a method is called, it will be cached. If a method is called once, it’s often immediately called again. This is known as an experiential fact and this cache records the high hit rate.
What is looking up the cache is the first half of `rb_call()`. Only with
ent = cache + EXPR1(klass, mid);
this line, the cache is searched. We’ll examine its mechanism in detail later.
When any cache was not hit, the next `rb_get_method_body()` searches the class tree step-by-step and caches the result at the same time. Figure 2 shows the entire flow of searching.
Method Cache
Next, let’s examine the structure of the method cache in detail.
180 #define CACHE_SIZE 0x800 181 #define CACHE_MASK 0x7ff 182 #define EXPR1(c,m) ((((c)>>3)^(m))&CACHE_MASK) 183 184 struct cache_entry { /* method hash table. */ 185 ID mid; /* method's id */ 186 ID mid0; /* method's original id */ 187 VALUE klass; /* receiver's class */ 188 VALUE origin; /* where method defined */ 189 NODE *method; 190 int noex; 191 }; 192 193 static struct cache_entry cache[CACHE_SIZE]; (eval.c)
If I describe the mechanism shortly, it is a hash table. I mentioned that the principle of the hash table is to convert a table search to an indexing of an array. Three things are necessary to accomplish: an array to store the data, a key, and a hash function.
First, the array here is an array of `struct cache_entry`. And the method is uniquely determined by only the class and the method name, so these two become the key of the hash calculation. The rest is done by creating a hash function to generate the index (`0×000` ~ `0×7ff`) of the cache array form the key. It is `EXPR1. (Figure 3)
However, `EXPR1()` is not a perfect hash function or anything, so a different method can generate the same index coincidentally. But because this is nothing more than a cache, conflicts do not cause a problem. It just slows its performance down a little.
The effect of Method Cache
By the way, how much effective is the method cache in actuality? We could not be convinced just by being said “it is known as …”. Let’s measure by ourselves.
Type | Program | Hit Rate |
---|---|---|
generating LALR parser | racc ruby.y | 99.9% |
generating a mail thread | a mailer | 99.1% |
generating a document | rd2html rubyrefm.rd | 97.8% |
Surprisingly, in all of the three experiments the hit rate is more than 95%. This is awesome. Apparently, the effect of “it is know as …” is outstanding.
Invocation
`rb_call0()`
There have been many things and finally we arrived at the method invoking. However, this `rb_call0()` is huge. As it’s more than 200 lines, it would come to 5,6 pages. If the whole part is laid out here, it would be disastrous. Let’s look at it by dividing into small portions. Starting with the outline:
4482 static VALUE 4483 rb_call0(klass, recv, id, oid, argc, argv, body, nosuper) 4484 VALUE klass, recv; 4485 ID id; 4486 ID oid; 4487 int argc; /* OK */ 4488 VALUE *argv; /* OK */ 4489 NODE *body; /* OK */ 4490 int nosuper; 4491 { 4492 NODE *b2; /* OK */ 4493 volatile VALUE result = Qnil; 4494 int itr; 4495 static int tick; 4496 TMP_PROTECT; 4497 4498 switch (ruby_iter->iter) { 4499 case ITER_PRE: 4500 itr = ITER_CUR; 4501 break; 4502 case ITER_CUR: 4503 default: 4504 itr = ITER_NOT; 4505 break; 4506 } 4507 4508 if ((++tick & 0xff) == 0) { 4509 CHECK_INTS; /* better than nothing */ 4510 stack_check(); 4511 } 4512 PUSH_ITER(itr); 4513 PUSH_FRAME(); 4514 4515 ruby_frame->last_func = id; 4516 ruby_frame->orig_func = oid; 4517 ruby_frame->last_class = nosuper?0:klass; 4518 ruby_frame->self = recv; 4519 ruby_frame->argc = argc; 4520 ruby_frame->argv = argv; 4521 4522 switch (nd_type(body)) { /* ... main process ... */ 4698 4699 default: 4700 rb_bug("unknown node type %d", nd_type(body)); 4701 break; 4702 } 4703 POP_FRAME(); 4704 POP_ITER(); 4705 return result; 4706 } (eval.c)
First, an `ITER` is pushed and whether or not the method is an iterator is finally fixed. As its value is used by the `PUSH_FRAME()` which comes immediately after it, `PUSH_ITER()` needs to appear beforehand. `PUSH_FRAME()` will be discussed soon.
And if I first describe about the “… main process …” part, it branches based on the following node types and each branch does its invoking process.
`NODE_CFUNC` | methods defined in C |
`NODE_IVAR` | `attr_reader` |
`NODE_ATTRSET` | `attr_writer` |
`NODE_SUPER` | `super` |
`NODE_ZSUPER` | `super` without arguments |
`NODE_DMETHOD` | invoke `UnboundMethod` |
`NODE_BMETHOD` | invoke `Method` |
`NODE_SCOPE` | methods defined in Ruby |
Some of the above nodes are not explained in this book but not so important and could be ignored. The important things are only `NODE_CFUNC`, `NODE_SCOPE` and `NODE_ZSUPER`.
`PUSH_FRAME()`
536 #define PUSH_FRAME() do { \ 537 struct FRAME _frame; \ 538 _frame.prev = ruby_frame; \ 539 _frame.tmp = 0; \ 540 _frame.node = ruby_current_node; \ 541 _frame.iter = ruby_iter->iter; \ 542 _frame.cbase = ruby_frame->cbase; \ 543 _frame.argc = 0; \ 544 _frame.argv = 0; \ 545 _frame.flags = FRAME_ALLOCA; \ 546 ruby_frame = &_frame 548 #define POP_FRAME() \ 549 ruby_current_node = _frame.node; \ 550 ruby_frame = _frame.prev; \ 551 } while (0) (eval.c)
First, we’d like to make sure the entire `FRAME` is allocated on the stack. This is identical to `module_setup()`. The rest is basically just doing ordinary initializations.
If I add one more description, the flag `FRAME_ALLOCA` indicates the allocation method of the `FRAME`. `FRAME_ALLOCA` obviously indicates “it is on the stack”.
`rb_call0()` – `NODE_CFUNC`
A lot of things are written in this part of the original code, but most of them are related to `trace_func` and substantive code is only the following line:
case NODE_CFUNC: result = call_cfunc(body->nd_cfnc, recv, len, argc, argv); break;
Then, as for `call_cfunc()` …
4394 static VALUE 4395 call_cfunc(func, recv, len, argc, argv) 4396 VALUE (*func)(); 4397 VALUE recv; 4398 int len, argc; 4399 VALUE *argv; 4400 { 4401 if (len >= 0 && argc != len) { 4402 rb_raise(rb_eArgError, "wrong number of arguments(%d for %d)", 4403 argc, len); 4404 } 4405 4406 switch (len) { 4407 case -2: 4408 return (*func)(recv, rb_ary_new4(argc, argv)); 4409 break; 4410 case -1: 4411 return (*func)(argc, argv, recv); 4412 break; 4413 case 0: 4414 return (*func)(recv); 4415 break; 4416 case 1: 4417 return (*func)(recv, argv[0]); 4418 break; 4419 case 2: 4420 return (*func)(recv, argv[0], argv[1]); 4421 break; : : 4475 default: 4476 rb_raise(rb_eArgError, "too many arguments(%d)", len); 4477 break; 4478 } 4479 return Qnil; /* not reached */ 4480 } (eval.c)
As shown above, it branches based on the argument count. The maximum argument count is 15.
Note that neither `SCOPE` or `VARS` is pushed when it is `NODE_CFUNC`. It makes sense because a method defined in C does not use Ruby’s local variables. But it simultaneously means that if the “current” local variables are accessed by `C`, they are actually the local variables of the previous `FRAME`. And in some places, say, `rb_svar` (`eval.c`), it is actually done.
`rb_call0()` – `NODE_SCOPE`
`NODE_SCOPE` is to invoke a method defined in Ruby. This part forms the foundation of Ruby.
4568 case NODE_SCOPE: 4569 { 4570 int state; 4571 VALUE *local_vars; /* OK */ 4572 NODE *saved_cref = 0; 4573 4574 PUSH_SCOPE(); 4575 /* (A)forward CREF */ 4576 if (body->nd_rval) { 4577 saved_cref = ruby_cref; 4578 ruby_cref = (NODE*)body->nd_rval; 4579 ruby_frame->cbase = body->nd_rval; 4580 } /* (B)initialize ruby_scope->local_vars */ 4581 if (body->nd_tbl) { 4582 local_vars = TMP_ALLOC(body->nd_tbl[0]+1); 4583 *local_vars++ = (VALUE)body; 4584 rb_mem_clear(local_vars, body->nd_tbl[0]); 4585 ruby_scope->local_tbl = body->nd_tbl; 4586 ruby_scope->local_vars = local_vars; 4587 } 4588 else { 4589 local_vars = ruby_scope->local_vars = 0; 4590 ruby_scope->local_tbl = 0; 4591 } 4592 b2 = body = body->nd_next; 4593 4594 PUSH_VARS(); 4595 PUSH_TAG(PROT_FUNC); 4596 4597 if ((state = EXEC_TAG()) == 0) { 4598 NODE *node = 0; 4599 int i; /* ……(C)assign the arguments to the local variables …… */ 4666 if (trace_func) { 4667 call_trace_func("call", b2, recv, id, klass); 4668 } 4669 ruby_last_node = b2; /* (D)method body */ 4670 result = rb_eval(recv, body); 4671 } 4672 else if (state == TAG_RETURN) { /* back via return */ 4673 result = prot_tag->retval; 4674 state = 0; 4675 } 4676 POP_TAG(); 4677 POP_VARS(); 4678 POP_SCOPE(); 4679 ruby_cref = saved_cref; 4680 if (trace_func) { 4681 call_trace_func("return", ruby_last_node, recv, id, klass); 4682 } 4683 switch (state) { 4684 case 0: 4685 break; 4686 4687 case TAG_RETRY: 4688 if (rb_block_given_p()) { 4689 JUMP_TAG(state); 4690 } 4691 /* fall through */ 4692 default: 4693 jump_tag_but_local_jump(state); 4694 break; 4695 } 4696 } 4697 break; (eval.c)
(A) `CREF` forwarding, which was described at the section of constants in the previous chapter. In other words, `cbase` is transplanted to `FRAME` from the method entry.
(B) The content here is completely identical to what is done at `module_setup()`. An array is allocated at `local_vars` of `SCOPE`. With this and `PUSH_SCOPE()` and `PUSH_VARS()`, the local variable scope creation is completed. After this, one can execute `rb_eval()` in the exactly same environment as the interior of the method.
(C) This sets the received arguments to the parameter variables. The parameter variables are in essence identical to the local variables. Things such as the number of arguments are specified by `NODE_ARGS`, all it has to do is setting one by one. Details will be explained soon. And,
(D) this executes the method body. Obviously, the receiver (`recv`) becomes `self`. In other words, it becomes the first argument of `rb_eval()`. After all, the method is completely invoked.
Set Parameters
Then, we’ll examine the totally skipped part, which sets parameters. But before that, I’d like you to first check the syntax tree of the method again.
% ruby -rnodedump -e 'def m(a) nil end' NODE_SCOPE nd_rval = (null) nd_tbl = 3 [ _ ~ a ] nd_next: NODE_BLOCK nd_head: NODE_ARGS nd_cnt = 1 nd_rest = -1 nd_opt = (null) nd_next: NODE_BLOCK nd_head: NODE_NEWLINE nd_file = "-e" nd_nth = 1 nd_next: NODE_NIL nd_next = (null)
`NODE_ARGS` is the node to specify the parameters of a method. I aggressively dumped several things, and it seemed its members are used as follows:
`nd_cnt` | the number of the normal parameters |
`nd_rest` | the variable `ID` of the `rest` parameter. `-1` if the `rest` parameter is missing |
`nd_opt` | holds the syntax tree to represent the default values of the option parameters. a list of `NODE_BLOCK` |
If one has this amount of the information, the local variable `ID` for each parameter variable can be uniquely determined. First, I mentioned that 0 and 1 are always `$` and `$~`. In 2 and later, the necessary number of ordinary parameters are in line. The number of option parameters can be determined by the length of `NODEBLOCK`. Again next to them, the rest-parameter comes.
For example, if you write a definition as below,
def m(a, b, c = nil, *rest) lvar1 = nil end
local variable IDs are assigned as follows.
0 1 2 3 4 5 6 $_ $~ a b c rest lvar1
Are you still with me? Taking this into considerations, let’s look at the code.
4601 if (nd_type(body) == NODE_ARGS) { /* no body */ 4602 node = body; /* NODE_ARGS */ 4603 body = 0; /* the method body */ 4604 } 4605 else if (nd_type(body) == NODE_BLOCK) { /* has body */ 4606 node = body->nd_head; /* NODE_ARGS */ 4607 body = body->nd_next; /* the method body */ 4608 } 4609 if (node) { /* have somewhat parameters */ 4610 if (nd_type(node) != NODE_ARGS) { 4611 rb_bug("no argument-node"); 4612 } 4613 4614 i = node->nd_cnt; 4615 if (i > argc) { 4616 rb_raise(rb_eArgError, "wrong number of arguments(%d for %d)", 4617 argc, i); 4618 } 4619 if (node->nd_rest == -1) { /* no rest parameter */ /* counting the number of parameters */ 4620 int opt = i; /* the number of parameters (i is nd_cnt) */ 4621 NODE *optnode = node->nd_opt; 4622 4623 while (optnode) { 4624 opt++; 4625 optnode = optnode->nd_next; 4626 } 4627 if (opt < argc) { 4628 rb_raise(rb_eArgError, 4629 "wrong number of arguments(%d for %d)", argc, opt); 4630 } /* assigning at the second time in rb_call0 */ 4631 ruby_frame->argc = opt; 4632 ruby_frame->argv = local_vars+2; 4633 } 4634 4635 if (local_vars) { /* has parameters */ 4636 if (i > 0) { /* has normal parameters */ 4637 /* +2 to skip the spaces for $_ and $~ */ 4638 MEMCPY(local_vars+2, argv, VALUE, i); 4639 } 4640 argv += i; argc -= i; 4641 if (node->nd_opt) { /* has option parameters */ 4642 NODE *opt = node->nd_opt; 4643 4644 while (opt && argc) { 4645 assign(recv, opt->nd_head, *argv, 1); 4646 argv++; argc--; 4647 opt = opt->nd_next; 4648 } 4649 if (opt) { 4650 rb_eval(recv, opt); 4651 } 4652 } 4653 local_vars = ruby_scope->local_vars; 4654 if (node->nd_rest >= 0) { /* has rest parameter */ 4655 VALUE v; 4656 /* make an array of the remainning parameters and assign it to a variable */ 4657 if (argc > 0) 4658 v = rb_ary_new4(argc,argv); 4659 else 4660 v = rb_ary_new2(0); 4661 ruby_scope->local_vars[node->nd_rest] = v; 4662 } 4663 } 4664 } (eval.c)
Since comments are added more than before, you might be able to understand what it is doing by following step-by-step.
One thing I’d like to mention is about `argc` and `argv` of `ruby_frame`. It seems to be updated only when any rest-parameter does not exist, why is it only when any rest-parameter does not exist?
This point can be understood by thinking about the purpose of `argc` and `argv`. These members actually exist for `super` without arguments. It means the following form:
super
This `super` has a behavior to directly pass the parameters of the currently executing method. To enable to pass at the moment, the arguments are saved in `ruby_frame→argv`.
Going back to the previous story here, if there’s a rest-parameter, passing the original parameters list somehow seems more convenient. If there’s not, the one after option parameters are assigned seems better.
def m(a, b, *rest) super # probably 5, 6, 7, 8 should be passed end m(5, 6, 7, 8) def m(a, b = 6) super # probably 5, 6 should be passed end m(5)
This is a question of which is better as a specification rather than “it must be”. If a method has a rest-parameter, it supposed to also have a rest-parameter at superclass. Thus, if the value after processed is passed, there’s the high possibility of being inconvenient.
Now, I’ve said various things, but the story of method invocation is all done. The rest is, as the ending of this chapter, looking at the implementation of `super` which is just discussed.
`super`
What corresponds to `super` are `NODE_SUPER` and `NODE_ZSUPER`. `NODE_SUPER` is ordinary `super`, and `NODE_ZSUPER` is `super` without arguments.
2780 case NODE_SUPER: 2781 case NODE_ZSUPER: 2782 { 2783 int argc; VALUE *argv; /* used in SETUP_ARGS */ 2784 TMP_PROTECT; 2785 /*(A)case when super is forbidden */ 2786 if (ruby_frame->last_class == 0) { 2787 if (ruby_frame->orig_func) { 2788 rb_name_error(ruby_frame->last_func, 2789 "superclass method `%s' disabled", 2790 rb_id2name(ruby_frame->orig_func)); 2791 } 2792 else { 2793 rb_raise(rb_eNoMethodError, "super called outside of method"); 2794 } 2795 } /*(B)setup or evaluate parameters */ 2796 if (nd_type(node) == NODE_ZSUPER) { 2797 argc = ruby_frame->argc; 2798 argv = ruby_frame->argv; 2799 } 2800 else { 2801 BEGIN_CALLARGS; 2802 SETUP_ARGS(node->nd_args); 2803 END_CALLARGS; 2804 } 2805 /*(C)yet mysterious PUSH_ITER() */ 2806 PUSH_ITER(ruby_iter->iter?ITER_PRE:ITER_NOT); 2807 SET_CURRENT_SOURCE(); 2808 result = rb_call(RCLASS(ruby_frame->last_class)->super, 2809 ruby_frame->self, ruby_frame->orig_func, 2810 argc, argv, 3); 2811 POP_ITER(); 2812 } 2813 break; (eval.c)
For `super` without arguments, I said that `ruby_frame→argv` is directly used as arguments, this is directly shown at (B).
(C) just before calling `rb_call()`, doing `PUSH_ITER()`. This is also what cannot be explained in detail, but in this way the block passed to the current method can be handed over to the next method (meaning, the method of superclass that is going to be called).
And finally, (A) when `ruby_frame→last_class` is 0, calling `super` seems forbidden.
Since the error message says “`must be enabled by rb_enable_super()`”,
it seems it becomes callable by calling `rb_enable_super()`.
((errata: The error message “`must be enabled by rb_enable_super()`” exists not
in this list but in `rb_call_super()`.))
Why?
First, If we investigate in what kind of situation `last_class` becomes 0, it seems that it is while executing the method whose substance is defined in C (`NODE_CFUNC`). Moreover, it is the same when doing `alias` or replacing such method.
I’ve understood until there, but even though reading source codes, I couldn’t understand the subsequents of them. Because I couldn’t, I searched “`rb_enable_super`” over the `ruby`’s mailing list archives and found it. According to that mail, the situation looks like as follows:
For example, there’s a method named `String.new`. Of course, this is a method to create a string. `String.new` creates a struct of `T_STRING`. Therefore, you can expect that the receiver is always of `T_STRING` when writing an instance methods of `String`.
Then, `super` of `String.new` is `Object.new`. `Object.new` create a struct of `T_OBJECT`. What happens if `String.new` is replaced by new definition and `super` is called?
def String.new super end
As a consequence, an object whose struct is of `T_OBJECT` but whose class is `String` is created. However, a method of `String` is written with expectation of a struct of `T_STRING`, so naturally it downs.
How can we avoid this? The answer is to forbid to call any method expecting a struct of a different struct type. But the information of “expecting struct type” is not attached to method, and also not to class. For example, if there’s a way to obtain `T_STRING` from `String` class, it can be checked before calling, but currently we can’t do such thing. Therefore, as the second-best plan, “`super` from methods defined in C is forbidden” is defined. In this way, if the layer of methods at C level is precisely created, it cannot be got down at least. And, when the case is “It’s absolutely safe, so allow `super`”, `super` can be enabled by calling `rb_enable_super()`.
In short, the heart of the problem is miss match of struct types. This is the same as the problem that occurs at the allocation framework.
Then, how to solve this is to solve the root of the problem that “the class does not know the struct-type of the instance”. But, in order to resolve this, at least new API is necessary, and if doing more deeply, compatibility will be lost. Therefore, for the time being, the final solution has not decided yet.