如何获取传递给eval方法的块的返回值?

时间:2014-07-14 10:19:08

标签: ruby eval return-value yield

我想实现类似沙盒的东西

  • eval given string
  • 使用eval
  • 在相同的上下文中执行给定的块
  • 返回阻止
  • 的结果

沙箱的目的是检查内容 - 函数,变量,e.t.c。 - 易受攻击的代码。

这是我的规范

it 'returns return value of given block' do
  value = Sandbox.secure_eval('hoge = ["hoge", "fuga"]') do
    hoge[0]
  end
  expect(value).to eq('hoge')
end

,这是我对沙箱的实现

require 'timeout'
module Sandbox
  def self.secure_eval(code, timeout: 5, safe_level: 2)
    raise ArgumentError, 'please set call back by block' unless block_given?

    proc = Proc.new do
      Timeout::timeout timeout do
        $SAFE = safe_level
        eval code do
          yield
        end
      end
    end

    proc.call
  end
end

但#secure_eval返回eval的结果,在这种情况下[" hoge"," fuga"],并且无法捕获块的返回值。

我该怎么做?

1 个答案:

答案 0 :(得分:4)

您可以使用yield将eval的结果返回到块。你只需要yield这个值;因此我将您的yield更改为yield eval code。在您提供给Sandbox.secure_eval的块中,您必须将此结果绑定到块变量。 secure_eval的结果将是块的结果,就像您想要的那样。

proc = Proc.new do
  Timeout::timeout timeout do
    $SAFE = safe_level
    yield eval code # <= This line changed
  end
end

Sandbox.secure_eval('hoge = ["hoge", "fuga"]') { |hoge| hoge[0] }
# => "hoge"

Sandbox.secure_eval('2 ** 4') { |result| result - 5 }
# => 11

回应你的评论;事实证明,在Kernel#Binding的帮助下,我们可以让它或多或少地像你想要的那样工作。感觉就像一个黑客,所以谨慎使用它。

我使用Binding来评估代码,该代码可以访问所有已定义的变量。另外,我为Binding类定义了一个method_missing,这样我们就可以更容易地访问变量了。没有它,您需要eval('varname')而不仅仅是varname。根据提到的猴子补丁解决方案的@hakcho的评论并不理想,我现在使用仅在本地改变Binding行为的改进(即method_missing实现)。

我在您的方法中添加了一个明确的block参数,我将其用于instance_eval而不是yield。然后我们可以直接在块中访问变量。

require 'timeout'

module Sandbox
  refine Binding do
    def method_missing(meth, *args, &block)
      self.eval(meth.to_s)
    end
  end

  def self.secure_eval(code, timeout: 5, safe_level: 2, &block)
    raise ArgumentError, 'please set call back by block' unless block_given?

    proc = Proc.new do
      Timeout::timeout timeout do
        $SAFE = safe_level
        binding = binding()
        binding.eval(code)
        binding.instance_eval(&block)
      end
    end

    proc.call
  end
end

using Sandbox # Activate the refinement so we can use x, y, z directly
Sandbox.secure_eval('x = [1,2,3]; y = 0; z = { key: "Hello!" }') do
   x[1]    # => 2
   y       # => 0
   z[:key] # => "Hello!"
end