Ruby 一行代码整垮 irb

ugoa · January 16, 2013 · Last by YangZX replied at January 18, 2013 · 7484 hits

def method_missing; evil; end

将以上代码复制到 irb 中运行…… 聪明的你,是否悟到了其中的奥妙?lol

循环调用,堆栈溢出?

def self.method_missing; evil; end;

即可

你的那种在 1.8 时是没问题的,在 1.9 会崩掉

#3 楼 @jasl 我只在 1.9 上试过的。

#1 楼 @zlx_star 99% 正确,我完善一点细节啊:

如果调用了一个对象上不存在的方法,Ruby 最终会执行每一个对象都会有的method_missing方法,由它抛出NoMethodError异常,。

def method_missing; evil; end这一行代码重写并立即执行了 irb 默认 main 对象的method_missing方法,奥妙就在与这个重写的方法里我又胡乱编造了一个不存在的方法 evil(),irb 在执行 method_missing 时找不到这个 evil(),所以它只好再回去调用 method_missing,结果又遇到 evil()……以此循环往复,陷入无限死结中,最终结果就是堆栈溢出,irb 崩溃。

@ugoa 你说的不对 你可以在 irb 下发现(1.9.3)

1.9.3-p327 :002 > def method_missing;end
 => nil
1.9.3-p327 :003 > xx
(irb):2:in `method_missing': wrong number of arguments (1 for 0) (ArgumentError)
    from /Users/jasl/.rvm/scripts/irbrc.rb:32:in `initialize'
    from /Users/jasl/.rvm/scripts/irbrc.rb:32:in `open'
    from /Users/jasl/.rvm/scripts/irbrc.rb:32:in `block in <top (required)>'
(irb):2:in `method_missing': wrong number of arguments (1 for 0) (ArgumentError)
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/ruby-lex.rb:800:in `identify_identifier'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/ruby-lex.rb:731:in `block in lex_int2'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/slex.rb:236:in `call'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/slex.rb:236:in `match_io'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/slex.rb:75:in `match'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/ruby-lex.rb:286:in `token'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/ruby-lex.rb:262:in `lex'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/ruby-lex.rb:233:in `block (2 levels) in each_top_level_statement'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `loop'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/ruby-lex.rb:229:in `block in each_top_level_statement'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/ruby-lex.rb:228:in `catch'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb/ruby-lex.rb:228:in `each_top_level_statement'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb.rb:155:in `eval_input'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb.rb:70:in `block in start'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb.rb:69:in `catch'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/lib/ruby/1.9.1/irb.rb:69:in `start'
    from /Users/jasl/.rvm/rubies/ruby-1.9.3-p327/bin/irb:16:in `<main>'

在 pry 下发现

[1] pry(main)> def method_missing;end
(pry) output error: #<ArgumentError: wrong number of arguments (1 for 0)>
[2] pry(main)> xxx
CodeRay::Scanners::Scanner::ScanError:

***ERROR in scanner.rb:200:in `rescue in tokenize': wrong number of arguments (1 for 0) (after 0 tokens)

tokens:


current line: 1  column: 1  pos: 0
matched: nil  state: "Error in CodeRay::Scanners::Ruby#scan_tokens, initial state was: :initial"
bol? = true,  eos? = false

surrounding code:
nil  ~~  "xxx"


***ERROR***

from (pry):1:in `method_missing'

这个应该是和 ruby 的词法分析或者对象模型有关,具体的没深入研究过

@jasl @ugoa 这个元编程书上有讲,method_missing 要慎用,而且最好是自己要捕获的函数名有一定的规律可循或者有个范围,用正则或者数组来检测是否是你想捕获的方法,不是的话就 super 给原始的 method_missing 实现来处理. jasl 碰到的错误是因为重载 method_missing 没写对参数,method_missing 应该这么写

def method_missing(name,*args)
   super unless 符合你的判断条件
end

@sailtsao 嗯,这样可以了,但是重载一般函数是不需要形参一致的吧?method_missing 是特殊么?

#6 楼 @jasl 大哥你漏了一个重要的地方,在 method_missing 里面我还有一个自己编造的evil()方法啊(你可以自己随便编造一个),这个方法才是引发无限递归的罪魁祸首啊,具体我上面已经解释的很清楚了。

#7 楼 @sailtsao 我就是看了 Ruby 元编程才想到这个例子的,你说的完全正确。

@ugoa 你重现一下看看抛得是什么异常。。。是ArgumentError你的 eval 根本就没执行到,如果栈溢出是抛SystemStackError

这只是简单的 SystemStackError 没出什么大错误啊

#11 楼 @iBachue #10 楼 @jasl 可能我用的是 Windows 上的 irb 1.9.3(工作环境没有 Linux),结果是它直接闪退了,没有看到抛出的异常。没有在 Linux 上测试,所以结果可能没 Windows 这么夸张,我回头再在 Linux 试下。

我想突出的重点是 Ruby 方法执行的一个小细节,即 method_missing。大家明白就好了,@sailtsao 对参数的解释我以前也没注意到,学习了,感谢。

這也說明了誤用 method_missing 可以做成難以除錯的錯誤

#8 楼 @jasl 重载没问题。。但找不到方法至少会把方法名传入 method_missing,但是你这 method_missing 没参数。。

#12 楼 @ugoa 呵呵 method_missing 这个东西很多人都知道啊 很多其他脚本语言都有类似功能呢 关于方法的选择的细节 Ruby 里有很多呢

@jjym 没参数怎么了?函数体里用不到 name 自然不需要参数嘛

@jjym @sailtsao 而且这个已经不是 ruby 的规矩了,不同的解释器对这句话的执行结果也是有差异的,可以在 pry、mri1.8、mri1.9 上试试定义def method_missing; end def method_missing(name, *args); end 等等,结果是不同的

@luikore 大神有想法么?

def test
  puts 'hello'
end
test "hello"

之前版本我没测试,我这里是 1.9.3,不需要参数的函数如果你传参进去的话是会报错的
ruby test.rb /Users/sail/test.rb:1:in test': wrong number of arguments (1 for 0) (ArgumentError) from /Users/sail/test.rb:4:in'
irb ArgumentError: wrong number of arguments (1 for 0) from (irb):1:in test' from (irb):5 from /usr/local/bin/irb:12:in'
虽然你的 method_missing 可能不需要这些参数,不代表你不就不用写了
ruby 是灵活,而像 c++ 等强编译的语言你重载某个函数时,你声明的函数必须要和你重载的函数完全保持一致,特别是 c++ 用到 c 的东西的时候,两种语言编译器生成的函数标识符很多地方不一样的,就算名字参数你都照着抄了都不行,必须加上 extern "C"才能让 C++ 编译器了解这些导入的 C 的库的函数名字,呵呵,扯远了...

#8 楼 @jasl 虽然不需要形参一致,但是至少要数目一致,否则 ruby 解释器会报参数数目不对的错误的 ArgumentError: wrong number of arguments

@sailtsao 当然不是

[jasl@localhost:~]$ pry
[1] pry(main)> def x(name);end
=> nil
[2] pry(main)> def x;end
=> nil
[3] pry(main)> x
=> nil

@sailtsaohttp://stackoverflow.com/questions/9491462/why-do-i-get-stack-level-too-deep-from-method-missing-in-irb-1-9-3 Defining it as a top-level method replaces BasicObject#method_missing, which probably affected some irb internals like Phrogz said. 这是一个 irb 内部的设计问题

#21 楼 @jasl 你的例子明显不对啊,一个原本需要 name 的函数用新的不需要参数的覆盖,到此为止没错,但是你下面调用不该是 x 而是 x("abcd"),因为你调用一个不存在的函数的时候,ruby 解释器会把你调用的信息转换成 funcname,[parameters...] 的形式调用 method_missing

@jasl 没什么想法...参数个数不匹配不是发生在定义方法时,而是发生在调用方法时,所以你这个不加参数的method_missing可以定义,但调不进去


(跑题,很多人都知道的), 一行代码整垮 linux (fork bomb):

:(){:|:&};:

mac 跑这个就不会挂...

@sailtsao 我用无形参的函数覆写有形参的 为啥要调用有形参的? 如果你说的情况会调用 method_missing,那么

[1] pry(main)> def self.method_missing(*args)
[1] pry(main)*   puts args.join(' ')
[1] pry(main)* end
=> nil
[2] pry(main)> xx oo
oo
xx
=> nil
[3] pry(main)> def x(name);end
=> nil
[4] pry(main)> def x;end
=> nil
[5] pry(main)> x('abc')
ArgumentError: wrong number of arguments (1 for 0)
from (pry):6:in `x'

如何解释?这里明显没有触发 method_missing

#25 楼 @jasl 我们的理解出现了偏差,我说的形参是特指 method_missing 的调用,你的例子与 method_missing 无关,我那句解释只针对你 16 楼说的 method_missing 不需要 name 参数
还有,ruby 的 method_missing 是根据函数名触发的,与参数有无无关,这里本来就不应该触发,因为你的 x 函数已经定义了
我的解释特指你 2 楼的 method_missing 的实现是错误的,应该至少包含 name 参数,否则解释器调用 method_missing 就报错了

@jasl 用这个解释挺累的,不如用 skype 了,如果你用的话就加我吧,在我发的帖子里面有一个 skype 的联系方法,你可以加我,我拖你到群里慢慢聊,我那句 x("abcd") 是告诉你的 ruby 解释器是这么调用 method_missing 的,这里面的 abcd 就是你调用的不存在的函数名

@sailtsao - - 哪个。。。

@luikore 不知道你又没看过 wat 那个视频,同样的代码在 1.8 下正常,在 1.9 下就会发生问题, 里的解释是会触发 irb 内部的问题,所以搞的很困惑

#30 楼 @jasl irb 不是什么魔法,它也是 ruby 实现的,最简单的 irb 就像这样:

# simple irb

prompt = '[0] '
binding = Object.new.send :binding

loop do
  print prompt
  prompt.succ!
  begin
    res = binding.eval gets
  rescue SystemExit
    exit
  rescue Exception
    puts $!.message
    puts $!.backtrace
  end
  puts "=> #{res.inspect}"
end

irb 里还有各种各样的功能,例如按上箭头可以把以前输入过的东西重现出来,那就需要引入 readline, 或者色彩高亮显示方法源代码,那就是 pry 里用 coderay 着色的处理。而这些功能要调用方法,或者要触发 method_missing 都不奇怪,irb 还在不断改进中,1.8 和 1.9 的实现自然很不相同,引入了不同的库,行为也会不一样...

#25 楼 @jasl 打个比方。。可以模拟一下。。

def a
   "hello"
end

send :a,"a"

和这道理差不多。。另外 1.8 是没注意

irb 的 REPL 当前类是 Object

You need to Sign in before reply, if you don't have an account, please Sign up first.