英文:
How to Make Or Reference a Null Ruby Binding For Eval
问题
Rubocop 不喜欢以下内容;它会发出警告向eval传递绑定、__FILE__和__LINE__
:
sort_lambda = eval "->(a) { a.date }"
是的,我知道eval
存在安全问题。安全问题不在本问题的范围之内。
Ruby绑定的文档中提到:
Binding
类的对象封装了代码中某个特定位置的执行上下文,并保留了这个上下文以供将来使用。在这个上下文中可以访问的变量、方法、self
的值,以及可能在此上下文中可以访问的迭代器块都被保留下来。可以使用Kernel#binding
创建绑定对象,并可以在Kernel#eval
方法的第二个参数中传递这些绑定对象,以建立评估的环境。
创建的lambda不需要访问任何作用域中的变量。在从eval
调用的作用域中创建一个快速且简陋的绑定会像这样:
sort_lambda = eval "->(a) { a.date }", self.binding, __FILE__, __LINE__
理想情况下,应该传递一个空绑定(没有任何内容定义在其中,不包括来自self
的内容等)给这个eval
。如何做到这一点呢?
英文:
Rubocop dislikes the following; it issues Pass a binding, __FILE__ and __LINE__ to
eval.
:
sort_lambda = eval "->(a) { a.date }"
Yes, I know that eval
is a security problem. The issue of security is out of scope for this question.
The Ruby documentation on binding says:
> Objects of class Binding
encapsulate the execution context at some particular place in the code and retain this context for future use. The variables, methods, value of self
, and possibly an iterator block that can be accessed in this context are all retained. Binding objects can be created using Kernel#binding
, and are made available to the callback of Kernel#set_trace_func
and instances of TracePoint
.
> These binding objects can be passed as the second argument of the Kernel#eval method
, establishing an environment for the evaluation.
The lambda being created does not need to access any variables in any scopes.
A quick and dirty binding to the scope where the eval
is invoked from would look like this:
sort_lambda = eval "->(a) { a.date }", self.binding, __FILE__, __LINE__
Ideally, a null binding (a binding without anything defined in it, nothing from self
, etc.) should be passed to this eval
instead.
How could this be done?
答案1
得分: 3
不完全是,但你可以近似实现它。
在我继续之前,我知道你已经说过这个,但我想强调给以后阅读这个问题的读者。我下面所描述的并不是一个沙盒。这不会保护你免受恶意用户的攻击。如果你将用户输入传递给 eval
,它仍然可以使用我下面展示的绑定来造成很大的损害。在尝试在生产环境中使用之前,请咨询网络安全专家。
好的,现在说到正题。在Ruby中,你不能真正拥有一个空的绑定。Binding
类在某种程度上是编译时的魔法。虽然该类只公开了获取本地变量的方法,但它也捕获了在那个时间点处于作用域内的所有常量名(包括类名),以及当前的接收对象 self
以及所有可以从执行点调用的 self
上的方法。一个空绑定的问题在于,Ruby 有时候很像 Smalltalk。一切都存在于被称为 "对象" 的形而上理念的世界中,没有 Ruby 代码可以真正隔离运行。
事实上,试图这样做只是在设置障碍和笨拙的目标。认为你可以阻止我访问 BasicObject
吗?如果我在 Ruby 中有任何对象 a
,那么 a.class.ancestors.last
就是 BasicObject
。使用这种技术,我们可以通过简单拥有该类的一个实例或子类来获取任何全局类。一旦我们有了类,我们就有了模块,一旦我们有了模块,我们就有了 Kernel
,此时我们就拥有了大部分 Ruby 内置功能。
同样,self
总是存在。你无法摆脱它。它是 Ruby 对象系统的一个基本部分,即使在你认为它不存在的情况下也存在(参见我的早些时候的问题)。Ruby 中的每个方法或代码块都有一个接收对象,所以你最多只能尝试将接收对象限制为尽可能小的对象。你可能认为希望 self
是 BasicObject
,但有趣的是,实际上也没有办法做到这一点,因为只有在作用域中包含 Kernel
时,你才能 获取 绑定,而 BasicObject
并不包括 Kernel
。因此,至少你将获得所有的 Kernel
。你可能可以通过某种方式省略一些 BasicObject
子类,其中包括 Kernel
,从而避免其他 Object
方法,但这可能会在以后引起混乱。
所有这些都是为了强调,一个假设的空绑定只会稍微增加获取所有全局名称的复杂性,而不是不可能。这就是为什么它不存在的原因。
话虽如此,如果你的目标是消除本地变量并尝试,你可以通过在模块内创建一个绑定来轻松实现这一点。
module F
module_function def get_binding
binding
end
end
sort_lambda = eval "->(a) { a.date }", F.get_binding
这个绑定永远不会有本地变量,它可以访问的方法和常量仅限于 Kernel
中或全局作用域中可用的那些。这已经接近于"null",在我们所称之为 Ruby 的复杂类型和名称的联合体中是最接近的。
英文:
Not exactly, but you can approximate it.
Before I go further, I know you've already said this, but I want to emphasize it for future readers of this question as well. What I'm describing below is NOT a sandbox. This will NOT protect you from malicious users. If you pass user input to eval
, it can still do a lot of damage with the binding I show you below. Consult a cybersecurity expert before trying this in production.
Great, with that out of the way, let's move on. You can't really have an empty binding in Ruby. The Binding
class is sort of compile-time magic. Although the class proper only exposes a way to get local variables, it also captures any constant names (including class names) that are in scope at the time, as well as the current receiver object self
and all methods on self
that can be invoked from the point of execution. The problem with an empty binding is that Ruby is a lot like Smalltalk sometimes. Everything exists in one big world of Platonic ideals called "objects", and no Ruby code can truly run in isolation.
In fact, trying to do so is really just putting up obstacles and awkward goalposts. Think you can block me from accessing BasicObject
? If I have literally any object a
in Ruby, then a.class.ancestors.last
is BasicObject
. Using this technique, we can get any global class by simply having an instance of that class or a subclass. Once we have classes, we have modules, and once we have modules we have Kernel
, and at that point we have most of the Ruby built-in functionality.
Likewise, self
always exists. You can't get rid of it. It's a fundamental part of the Ruby object system, and it exists even in situations where you don't think it does (see this question of mine from awhile back, for instance). Every method or block of code in Ruby has a receiver, so the most you can do is try to limit the receiver to be as small an object as possible. One might think you want self
to be BasicObject
, but amusingly there's not really a way to do that either, since you can only get a binding if Kernel
is in scope, and BasicObject
doesn't include Kernel
. So at minimum, you're getting all of Kernel
. You might be able to skimp by somehow and use some subclass of BasicObject
that includes Kernel
, thereby avoiding other Object
methods, but that's likely to cause confusion down the road too.
All of this is to emphasize that a hypothetical null binding would really only make it slightly more complicated to get all of the global names, not impossible. And that's why it doesn't exist.
That being said, if your goal is to eliminate local variables and to try, you can get that easily by creating a binding inside of a module.
module F
module_function def get_binding
binding
end
end
sort_lambda = eval "->(a) { a.date }", F.get_binding
This binding will never have local variables, and the methods and constants it has access to are limited to those available in Kernel
or at the global scope. That's about as close to "null" as you're going to get in the complex nexus of interconnected types and names we call Ruby.
答案2
得分: 3
虽然我最初将这条评论留在了[@Silvio Mayolo](https://stackoverflow.com/users/2288659/silvio-mayolo)的答案上,他的答案写得非常好,但似乎把它作为一个答案发布更为恰当。
虽然该答案中包含的大部分内容都是正确的,但我们可以通过`BasicObject`继承略微接近“空绑定”:
```ruby
class NullBinding < BasicObject
def get_binding
::Kernel
.instance_method(:binding)
.bind(self)
.call
end
end
这个绑定上下文在 Ruby 中尽可能地限制了上下文。
在这个上下文中,你将无法仅通过名称引用常量:
eval 'Class', NullBinding.new.get_binding
#=> NameError
也就是说,你仍然可以引用顶层作用域:
eval '::Class', NullBinding.new.get_binding
#=> Class
在这个绑定上下文中,直接可用的方法仅限于BasicObject可用的实例方法。例如:
eval "puts 'name'", NullBinding.new.get_binding
#=> NoMethodError
同样,你可以访问顶层作用域,所以:
eval "::Kernel.puts 'name'", NullBinding.new.get_binding
# name
#=> nil
<details>
<summary>英文:</summary>
While I originally left this as a comment on [@Silvio Mayolo](https://stackoverflow.com/users/2288659/silvio-mayolo)'s answer, which is very well written, it seems germane to post it as an answer instead.
While most of what is contained within that answer is correct we can get slightly closer to a *"Null Binding"* through `BasicObject` inheritance:
class NullBinding < BasicObject
def get_binding
::Kernel
.instance_method(:binding)
.bind(self)
.call
end
end
This binding context has as limited a context as possible in ruby.
Using this context you will be unable to reference constants solely by name:
eval 'Class', NullBinding.new.get_binding
#=> NameError
That being said you can still reference the TOP_LEVEL scope so
eval '::Class', NullBinding.new.get_binding
#=> Class
The methods *directly* available in this binding context are limited only to the instance methods available to [BasicObject](https://ruby-doc.org/core-3.1.1/BasicObject.html). By way of Example:
eval "puts 'name'", NullBinding.new.get_binding
#=> NoMethodError
Again with the caveat that you can access TOP_LEVEL scope so:
eval "::Kernel.puts 'name'", NullBinding.new.get_binding
name
#=> nil
</details>
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论