Last active
June 3, 2018 05:32
-
-
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?!?!)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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