On Tue, Oct 25, 2011 at 12:17 AM, Joshua Ballanco <jballanc / gmail.com> wrote:
> I've had an alternate idea to accomplish something like a compromise, but
> I'm still a ways away from having anything like a patch.
> The idea is this: make Proc#freeze snapshot all bindings.
> Essentially, telling a Proc to freeze would cause all bindings in the proc
> to be evaluated and replaced with their value at that moment. These values,
> themselves, would then be frozen, and references to the original code blocks
> could be released. Requiring an extra method call to accomplish this is
> slightly inconvenient, but it would keep Ruby 2.0 "backwards compatible" (in
> quotes because really? does anybody sensible actually abuse proc bindings
> like this?). The hope would be that eventually everyone would get into the
> habit of freezing their procs and eventually this could become the default
> behavior.
> I have to admit I haven't had a chance to completely work through all the
> details of this idea yet. I will say that my ultimate hope is that the
> following two methods would be equivalent with the same performance in Ruby
> 2.0:
>
> def traditional
>  do_stuff...
> end
> define_method :meta do
>  do_stuff...
> end.freeze

Freezing procs would be useful for optimizing the proc/block itself,
but it would do nothing for the surrounding method.

If we could freeze a proc or binding, such that none of its enclosing
state would be mutable (e.g. local variables), then we could "flatten"
it into a single array of closed-over variables or eliminate the
closure entirely if no surrounding state were accessed. The latter
case is common for define_method:

a = whatever
define_method :blah do
  # some code that never accesses a
end

Currently, unless we do a lot of inspection, it's difficult or
impossible to make the "blah" method be as overhead-free as a normal
method body. If we knew that the block could be frozen (and arguably,
define_method should do this by *default* since it's a threading
nightmare to have methods that share local variable scopes) we could
turn the block body directly into a method body.

However, the case I want to fix is more sinister. Another example:

def foo
  a = please_dont_change_my_value
  yummy do
    # whatever code
  end
end
...
def yummy(&block)
  eval <<-EOS, block
    # hahaha! I can view and change your local variables!
    a = my_evil_new_value
  EOS
end

This is bad. A method I call should NEVER be allowed to see my
method's local variables unless I explicitly make them available.

And it's not just local variables, either. Instance variables, class
variables, constants, $~ and $_, and basically any state that
surrounds the block is exposed to the called code, which can then do
anything with it you could do in the method body itself. Icky.

How about this lovely item:

system ~/projects/jruby $ jirb
irb(main):001:0> def foo
irb(main):002:1> blah {}
irb(main):003:1> end
=> nil
irb(main):004:0> def blah(&block)
irb(main):005:1> eval 'def foo; puts "hello"; end', block
irb(main):006:1> end
=> nil
irb(main):007:0> foo
=> nil
irb(main):008:0> foo
hello
=> nil

Lovely, yes?

- Charlie