Skip to content

Instantly share code, notes, and snippets.

Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save JoshCheek/67c992f01c826da7d148816e8dad4b83 to your computer and use it in GitHub Desktop.
Save JoshCheek/67c992f01c826da7d148816e8dad4b83 to your computer and use it in GitHub Desktop.
A bunch of ways to get a pristine binding for an object (uh.... why isn't `Binding.new obj` a thing?!?!)
def get_binding_via_implicit_block
yield.instance_eval { binding }
end
b = get_binding_via_implicit_block { 111 }
b.receiver # => 111
b.local_variables # => []
def get_binding_via_ivar
# in reality, you wouldn't do this on main, but didn't feel like spamming the gist with ceremony
@obj2.instance_eval { binding }
end
@obj2 = 222
b = get_binding_via_ivar
b.receiver # => 222
b.local_variables # => []
def get_binding_via_method
# again, you wouldn't actually do this on main
obj3.instance_eval { binding }
end
singleton_class.class_eval { attr_accessor :obj3 }
self.obj3 = 333
b = get_binding_via_method
b.receiver # => 333
b.local_variables # => []
# First one is the most obvious way to do this, but it could break if some
# questionable code redefined instance_eval, so the second one is included
# b/c it should work in that situation. I chose `instance_eval`, b/c it's
# on BasicObject, so it can be bound to anything
def get_binding_via_a_procs_binding1(obj)
obj.method(:instance_eval).to_proc.binding
end
def get_binding_via_a_procs_binding2(obj)
BasicObject.instance_method(:instance_eval).bind(obj).to_proc.binding
end
b = get_binding_via_a_procs_binding1 444.1
b.receiver # => 444.1
b.local_variables # => []
b = get_binding_via_a_procs_binding2 444.2
b.receiver # => 444.2
b.local_variables # => []
def get_binding_via_threadlocal_var
Thread.current[:obj_to_bind].instance_eval { binding }
end
Thread.current[:obj_to_bind] = 555
b = get_binding_via_threadlocal_var
b.receiver # => 555
b.local_variables # => []
# apparently refinements are *really* lexically scoped,
# Ruby would not let me define one in a block, so I had to use the keywords
module GetBindingViaRefinedMonkeyPatch
refine BasicObject do
define_method(:get_binding) { binding }
end
using self
define_singleton_method(:call) { |obj| obj.get_binding }
end
b = GetBindingViaRefinedMonkeyPatch.(666)
b.receiver # => 666
b.local_variables # => []
def get_binding_via_exception_virtual_variable(obj)
raise "fn args are for suckahz"
rescue
$! # => #<RuntimeError: fn args are for suckahz>
$!.instance_variable_set :@obj, obj
helper7
end
def helper7
$!.instance_variable_get(:@obj).instance_eval { binding }
end
b = get_binding_via_exception_virtual_variable 777
b.receiver # => 777
b.local_variables # => []
# got the idea for this from https://github.com/seattlerb/change_class
# oh, and if this gist segfaults when you run it, change to MRI 2.5.0 on a 64 bit machine,
# or comment out this example :P
def get_bound_by_the_dark_arts(obj)
require 'fiddle'
obj_address = [obj.object_id].pack('q')
binding_with_no_locals8.tap do |b|
rdata_ptr = Fiddle::Pointer[b.object_id*2]
ptr = (rdata_ptr+32).ptr
# b/c of the way C structs work, and b/c we know how we created the binding,
# this is a pointer to: `rb_binding_t`, `struct rb_block`, and `struct rb_captured_block`
# the first 8 bytes of `struct rb_captured_block` is the pointer to `self`
# so we can write our obj's address into that bit of memory
b.receiver # => main
ptr[0, 8] = obj_address
b.receiver # => 888
end
end
def binding_with_no_locals8
binding
end
b = get_bound_by_the_dark_arts 888
b.receiver # => 888
b.local_variables # => []
def get_binding_via_monkey_unpatch(obj)
# in reality, you'd only do this once, when your lib code gets loaded
# and then you'd save it off in a var somewhere and just do the last line
BasicObject.class_eval do
def get_binding
binding
end
end
get_binding = BasicObject.instance_method(:get_binding)
BasicObject.remove_method :get_binding
get_binding.bind(obj).call
end
b = get_binding_via_monkey_unpatch 999
b.receiver # => 999
b.local_variables # => []
# Okay, if you make a throwaway class like this, then this isn't too problematic,
# but don't do it repeatedly, with the same class. It's not threadsafe, and
# whenever I remember that class variables exist, I remember that they shouldn't,
# b/c they're inherently buggy, so don't do this in general 😝
def get_binding_via_class_variables(obj)
Class.new do
@@obj = obj
def self.get_binding
@@obj.instance_eval { binding }
end
end.get_binding
end
b = get_binding_via_class_variables 101010
b.receiver # => 101010
b.local_variables # => []
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment