Project

General

Profile

Bug #10969

Updated by nobu (Nobuyoshi Nakada) almost 9 years ago

While working on the Rails project, specifically this issue https://github.com/rails/rails/issues/19297 I discovered that `public_send` can raise a `NameError` instead of a `NoMethodError`. 

 Following is a minimal reproduction scenario to trigger the bug. A more detailed example can be found in this Gist: https://gist.github.com/senny/9864a138defa322ed807 

 ~~~ruby ~~~ 
 class Person 
   def implicit_assignment 
     nope rescue nil 
     public_send "nope=" 
   end 

   def method_missing(*args) 
     super 
   end 
 end 

 a = Person.new 
 a.implicit_assignment 
 # test.rb:13:in `method_missing': undefined local variable or method `nope=' for #<Person:0x007f91d1052ef8> (NameError) 
 #  	 from test.rb:4:in `public_send' 
 #  	 from test.rb:4:in `implicit_assignment' 
 #  	 from test.rb:24:in `<main>' 
 ~~~ 

 ### What a found out during debugging: 

 I am not a C programmer and have very little experience in that field. While debugging the issue I could make some observations what's going on. 

 The error is being raised in `raise_method_missing` (https://github.com/ruby/ruby/blob/7790f37efdd8dd42a0a43c3206f6afdd43f8e86a/vm_eval.c#L704-L706): 

 ~~~c ~~~ 
 else if (last_call_status & NOEX_VCALL) { 
   format = "undefined local variable or method `%s' for %s"; 
   exc = rb_eNameError; 
 ~~~ 

 `last_call_status` is stored on the current thread (https://github.com/ruby/ruby/blob/7790f37efdd8dd42a0a43c3206f6afdd43f8e86a/vm_eval.c#L655): 

 The thread struct is modified in `vm_call_method_missing` (https://github.com/ruby/ruby/blob/7790f37efdd8dd42a0a43c3206f6afdd43f8e86a/vm_insnhelper.c#L1668): 

 ~~~c ~~~ 
 th->method_missing_reason = ci->aux.missing_reason; 
 ~~~ 

 Now the problem is, that the call to `public_send` with the method name containing an `=` sign, does not modify the thread struct. This means that it still contains the value assigned from the previous call. That's what `nope rescue nil` in the reproduction is used for. It assigns `NOEX_VCALL` to that struct.

Back