Forum: Ruby How I can create/extract a variable/hash into the current binding in Ruby?

Posted by Ramon de C Valle (Guest)
on 2012-12-12 10:28
Attachment: smime.p7s (4,36 KB)
(Received via mailing list)
Hi,

How I can create/extract a variable/hash into the current binding in
Ruby? For instance, the following results in a NameError:

  class Hash
    def extract(b)
      self.each do |key, value|
        bind = b.eval <<-END
          #{key} = nil
          proc { |value| #{key} = value }
        END
        bind.call(value)
      end
    end
  end

  hash = {:a => 1}
  hash.extract(binding)
  puts a

Noteworthy mentioning, each call to Kernel#binding returns a different
Binding object instance, which makes me think that I'm not really
changing the binding of the caller of Hash#extract. For instance, the
following works:

  class Hash
    def extract(b)
      self.each do |key, value|
        bind = b.eval <<-END
          #{key} = nil
          proc { |value| #{key} = value }
        END
        bind.call(value)
      end
    end
  end

  hash = {:a => 1}
  b = binding
  hash.extract(b)
  eval('puts a', b)


Thanks,
Posted by Jan E. (jacques1)
on 2012-12-12 11:23
Hi,

I'm no expert on this stuff, but I think it's a problem of the Ruby
interpreter determining variables while it still parses the code:

eval 'x = 1'
puts x

This doesn't work, because the parser doesn't "see" the eval stuff. It
doesn't find an assignment for "x", so it assumes it's a method (which
leads to a NameError).

If, however, you set "x" to an arbitrary value beforehands, it correctly
regognizes "x" as a variable:

x = nil
eval 'x = 1'
puts x

Apart from the technical stuff: I don't find it a good idea to just dump
variables into a binding, because it's just too much "magic". It makes
it hard to keep track of which variable has which value. And I can't
think of any good reason to do that when you're programming
object-oriented.

Did you get the idea from PHP? Because that language is notorious for
dumping values into all kinds of contexts, which has lead and still
leads to gigantic security problems.
Posted by 7stud -- (7stud)
on 2012-12-12 11:25
Ramon de C Valle wrote in post #1088790:
> Hi,
>
> the following results in a NameError:
>
>   hash = {:a => 1}
>
>   puts a

Yep.
Posted by Ramon de C Valle (rcvalle)
on 2012-12-12 11:29
Attachment: smime.p7s (4,36 KB)
(Received via mailing list)
Hi,

On 12/12/2012 08:23 AM, Jan E. wrote:
> leads to a NameError).
> it hard to keep track of which variable has which value. And I can't
> think of any good reason to do that when you're programming
> object-oriented.
>
> Did you get the idea from PHP? Because that language is notorious for
> dumping values into all kinds of contexts, which has lead and still
> leads to gigantic security problems.
For my particular case, I just want to avoid a giant list of assignments
for creating local variables within a method while retrieving
information from a series of nested FFI Structs.

>

Thanks,
Posted by Jan E. (jacques1)
on 2012-12-12 11:38
As to the solution:

If you want to keep this approach, I think you'll have no other choice 
than to also wrap the variable uses in an "eval" call. Because whenever 
the parser finds one of those dynamically created variables, it won't 
recognize it as a variable and throw a NameError.
Posted by Arlen Cuss (unnali)
on 2012-12-12 11:57
(Received via mailing list)
To expand on Jan's comments, note the difference in disassemblies:

>> puts RubyVM::InstructionSequence.disasm lambda { eval 'x = 1'; puts x }
== disasm: <RubyVM::InstructionSequence:block in irb_binding@(irb)>=====
== catch table
| catch type: redo   st: 0000 ed: 0028 sp: 0000 cont: 0000
| catch type: next   st: 0000 ed: 0028 sp: 0000 cont: 0028
|------------------------------------------------------------------------
0000 trace            1                                               ( 
3)
0002 putself
0003 putstring        "x = 1"
0005 send             :eval, 1, nil, 8, <ic:0>
0011 pop
0012 trace            1
0014 putself
0015 putself
0016 send             :x, 0, nil, 24, <ic:1>
0022 send             :puts, 1, nil, 8, <ic:2>
0028 leave
=> nil
>> puts RubyVM::InstructionSequence.disasm lambda { x = nil; eval 'x = 1';
puts x }
== disasm: <RubyVM::InstructionSequence:block in irb_binding@(irb)>=====
== catch table
| catch type: redo   st: 0000 ed: 0030 sp: 0000 cont: 0000
| catch type: next   st: 0000 ed: 0030 sp: 0000 cont: 0030
|------------------------------------------------------------------------
local table (size: 2, argc: 0 [opts: 0, rest: -1, post: 0, block: -1] 
s1)
[ 2] x
0000 trace            1                                               ( 
4)
0002 putnil
0003 setdynamic       x, 0
0006 trace            1
0008 putself
0009 putstring        "x = 1"
0011 send             :eval, 1, nil, 8, <ic:0>
0017 pop
0018 trace            1
0020 putself
0021 getdynamic       x, 0
0024 send             :puts, 1, nil, 8, <ic:1>
0030 leave
=> nil

In the first instance, the parser decides that, having seen no local x, 
it
must be a function call, and so "0016 send :x, 0, nil, 24, <ic:1>" is
emitted.

In the second, having already seen an x ("0003 setdynamic x, 0"), it 
knows
that it is referring to the local var, so it emits "0021 getdynamic x, 
0"
instead of a call.

Unfortunately, there's no in between where it could refer to either a 
local
or function, because the compiler needs to know all locally-referable
variables in order to emit the instructions; "setdynamic x, 0" is 
actually
"setdynamic 2, 0" (note the 'local table' before the instructions in the
second disassembly, and the total lack of same in the first one), which 
is
why local variables are so fast; they're just direct offsets from the 
local
frame 
pointer<http://lifegoo.pluskid.org/upload/doc/yarv/yarv_is...
("environment
pointer" as of June
2012<http://comments.gmane.org/gmane.comp.lang.ruby.cvs/39182>
).

So, in short, VM constraints are responsible for this being impossible. 
You
could either just say 'a = b = c = d = … = nil' before your hash.extract 
to
make them findable, or find a possibly "more Ruby" way. :)

— Arlen
Posted by Ramon de C Valle (rcvalle)
on 2012-12-12 12:38
Attachment: smime.p7s (4,36 KB)
(Received via mailing list)
Hi,

On 12/12/2012 08:38 AM, Jan E. wrote:
> As to the solution:
>
> If you want to keep this approach, I think you'll have no other choice
> than to also wrap the variable uses in an "eval" call. Because whenever
> the parser finds one of those dynamically created variables, it won't
> recognize it as a variable and throw a NameError.
Actually, this is what I'm currently doing now. I extract the structures
immediately after entering the method and wrap the rest into an eval.

>

Thanks,
Posted by Ramon de C Valle (rcvalle)
on 2012-12-12 12:41
Attachment: smime.p7s (4,36 KB)
(Received via mailing list)
Hi,

On 12/12/2012 08:57 AM, Arlen Christian Mart Cuss wrote:
> 0003 putstring        "x = 1"
> 1'; puts x }
> 0006 trace            1
>
> locally-referable variables in order to emit the instructions;
> hash.extract to make them findable, or find a possibly "more Ruby" way. :)
Thank you for the additional information! Any suggestions of a possible
"more Ruby" way of doing this?
Posted by Brian Candler (candlerb)
on 2012-12-12 13:13
A simpler test case:

def foo(b)
  b.eval "a = 1"
end

b = binding
foo(b)

puts a           # NameError: undefined local variable or method 'a'
b.eval "puts a"  # this is fine
puts local_variables     # shows both a and b
Posted by Robert Klemme (robert_k78)
on 2012-12-12 14:30
(Received via mailing list)
On Wed, Dec 12, 2012 at 12:40 PM, Ramon de C Valle <rcvalle@redhat.com> 
wrote:

> Thank you for the additional information! Any suggestions of a possible
> "more Ruby" way of doing this?

Keep the data in the Hash and use it there.  If you think about it you
will need eval for manipulation of the data as well because you do not
know beforehand what local variables you have, don't you?  If accesses
like hash[:a] are just inconvenient you could copy the Hash into an
OpenStruct and do hash.a which might be slightly nicer.

Generating local variables is almost always a bad idea.

Kind regards

robert
Posted by Ramon de C Valle (rcvalle)
on 2012-12-12 14:44
Attachment: smime.p7s (4,36 KB)
(Received via mailing list)
Hi,

On 12/12/2012 11:29 AM, Robert Klemme wrote:
> On Wed, Dec 12, 2012 at 12:40 PM, Ramon de C Valle <rcvalle@redhat.com> wrote:
>
>> Thank you for the additional information! Any suggestions of a possible
>> "more Ruby" way of doing this?
>
> Keep the data in the Hash and use it there.  If you think about it you
> will need eval for manipulation of the data as well because you do not
> know beforehand what local variables you have, don't you?
Yes, I know beforehand because they are predefined FFI structs. I'm just
trying to avoid things like
event[:u][:exception][:exception_record][:exception_information][0] (see
https://github.com/rcvalle/active_fuzzer/blob/mast...).


  If accesses
> --
> remember.guy do |as, often| as.you_can - without end
> http://blog.rubybestpractices.com/
>

Thanks,
Posted by Robert Klemme (robert_k78)
on 2012-12-12 16:43
(Received via mailing list)
On Wed, Dec 12, 2012 at 2:44 PM, Ramon de C Valle <rcvalle@redhat.com> 
wrote:
> On 12/12/2012 11:29 AM, Robert Klemme wrote:
>> On Wed, Dec 12, 2012 at 12:40 PM, Ramon de C Valle <rcvalle@redhat.com> wrote:
>>
>>> Thank you for the additional information! Any suggestions of a possible
>>> "more Ruby" way of doing this?
>>
>> Keep the data in the Hash and use it there.  If you think about it you
>> will need eval for manipulation of the data as well because you do not
>> know beforehand what local variables you have, don't you?
> Yes, I know beforehand because they are predefined FFI structs.

Whatever that is.

> I'm just
> trying to avoid things like
> event[:u][:exception][:exception_record][:exception_information][0] (see
> https://github.com/rcvalle/active_fuzzer/blob/mast...).

I have no idea what you are doing there but the huge amount of
repeated code looks highly suspicious.

Cheers

robert
Please log in before posting. Registration is free and takes only a minute.
Existing account (Switch to SSL-encrypted connection)
NEW: Do you have a Google/GoogleMail or Yahoo account? No registration required!
Log in with Google account | Log in with Yahoo account
No account? Register here.