【发布时间】:2011-03-27 13:26:03
【问题描述】:
我正在寻找示例,说明为什么在 ruby 中扩展基类不是一个好主意。我需要向一些人展示为什么它是一种需要小心使用的武器。
有什么恐怖故事可以分享吗?
【问题讨论】:
标签: ruby monkeypatching
我正在寻找示例,说明为什么在 ruby 中扩展基类不是一个好主意。我需要向一些人展示为什么它是一种需要小心使用的武器。
有什么恐怖故事可以分享吗?
【问题讨论】:
标签: ruby monkeypatching
大约 2.5 年前在鲁比纽斯有一个非常著名的 monkey-patching going horribly wrong 示例。
这个案例的有趣之处在于,违规代码和受害者都非常明显且非常不寻常。通常,冒犯者是某个 PHP 脚本小子编写的一段代码,他在 1337 元编程 h4X0r Skillz 上喝醉了。并且失败模式是一个简单的ArgumentError异常,因为原始方法和monkeypatch有不同的arity。
但是,在这种情况下,攻击者是 stdlib (mathn) 中的一个库,故障模式是 Rubinius VM 完全崩溃。
那么,发生了什么?好吧,mathn monkey 修补了 Fixnum 类并更改了 Fixnum 算术的工作方式。特别是,它改变了几个核心方法的 results 和 types。例如:
r = 4/3 # => 1
r.class # => Fixnum
require 'mathn'
r = 4/3 # => (4/3)
r.class # => Rational
问题当然是在 Rubinius 中,整个 Ruby 编译器、整个 Ruby 内核、Ruby 核心库的大部分、Rubinius VM 的某些部分以及 Rubinius 基础设施的其他部分,都是用 Ruby 编写的。当然,所有这些都使用Fixnum 算术。
Hash 类是用 Ruby 编写的,它使用Fixnum 算法来计算哈希桶的大小,计算哈希函数等等。 Array 是用 Ruby 编写的,需要计算元素大小和数组长度。 FFI 库是用 Ruby 编写的,需要计算内存地址(!)和结构大小。 Rubinius 的许多部分假设他们可以做一些Fixnum 算术,然后将结果作为指针或int 传递给某个C 函数。
由于 Ruby 不支持任何类型的选择器命名空间或类装箱或类似的(尽管 Ruby 2.0 计划使用类似的东西),只要一些随机用户代码需要 mathn 库,所有这些部分只是壮观地爆炸,因为突然之间,Fixnum 操作的结果不再是Fixnum(它与机器int 基本相同,可以这样传递),而是Rational (这是一个成熟的 Ruby 对象)。
基本上,会发生什么,一些代码会require 'mathn'(或者您将其输入到 IRb 中),然后 VM 会立即死掉。
在这种情况下,解决方案是编译器的安全数学插件:当编译器检测到它正在编译内核或 Rubinius 的其他核心部分时,它会自动重写对 @987654340 的调用@method 调用这些方法的私有不可变副本。 [注意:我认为在当前版本的 Rubinius 中,问题以不同的方式解决。]
【讨论】:
The Trifecta of FAIL; or, how to patch Rails 2.0 for Ruby 1.8.7 有一个 Rails 示例(这是一个经过严格审查的大型项目)导致问题,因为他们猴子修补了 String 以添加方法 chars。
【讨论】:
一个明显的缺陷是名称冲突 - 如果两个或多个包为行为不同的方法选择相同的名称。
【讨论】: