有没有办法创建一个完全空的
Binding
对象以与 eval
一起使用?
根据文档,只有
Kernel#binding
方法可以创建新的绑定。我尝试过这样的事情:
empty = binding
但是,该绑定在其局部变量中包含
empty
本身,以及稍后在代码中分配的相同范围内的任何其他局部变量。
我发现常量
TOPLEVEL_BINDING
是一个空绑定,足以满足我的直接目的。但可能并不总是如此。
有什么办法可以创建一个全新的、完全空的
Binding
?
一个简单的方法是编写一个只调用
binding
的方法:
def empty_binding
binding
end
然后:
b = empty_binding
b.local_variables
# [ ]
该绑定仍将具有
self
并可以访问该 self
可用的任何实例变量。你可以用一些诡计来限制它:
module Empty
def self.binding
super
end
end
b = Empty.binding
b.eval('puts local_variables.inspect')
# [ ]
b.eval('puts instance_variables.inspect')
# [ ]
b.eval('puts self.inspect')
# Empty
什么有效取决于目标是什么。没有局部变量的绑定非常简单,如果不破解 Ruby 本身,什么都没有的绑定可能是不可能的(尽管
BasicObject
可能比模块更接近于空)。
如果这就是你所追求的,这些东西都不会给你一个安全
eval
的监狱。
之前接受的答案的问题在于仍然存在共享上下文:
module Empty
def self.binding
super
end
end
binding = Empty.binding
binding.eval("@foo = 'bar'")
binding.eval("puts instance_variables.inspect")
# => [:@foo]
binding_too = Empty.binding
binding_too.eval("puts instance_variables.inspect")
# => [:@foo]
Ruby 3.0 引入了 Ractor。它类似于 Javascript 的 Web Workers,并提供了一种并行执行 Ruby 代码的方法(每个 ractor 都有自己的 GVL/GIL)。此功能的一个有趣的副作用是,除了传递给它的状态之外,它无法共享任何状态,因此 Ruby 3.0 有效地引入了一个“上下文无关”块,该块仅在您创建 Ractor 时可用。该块不捕获任何外部上下文(与 Ruby 中的所有其他块不同)。
您可以(ab)使用此功能生成绑定并立即返回:
binding = Ractor.new { self.binding }.take
binding.eval("@foo = 'bar'")
binding.eval("puts instance_variables.inspect")
# => [:@foo]
binding_too = Ractor.new { self.binding }.take
binding_too.eval("puts instance_variables.inspect")
# => []
尽管如此,它仍然不是完全隔离,因为您可能需要使用不同的进程。修改环境变量之类的事情仍然会影响绑定之外的事情:
binding = Ractor.new { self.binding }.take
binding.eval("ENV['LOL'] = 'yolo'")
binding.eval("puts ENV['LOL']")
# => "yolo"
binding_too = Ractor.new { self.binding }.take
binding_too.eval("puts ENV['LOL']")
# => "yolo"
puts ENV['LOL']
# => "yolo"