Skip to content

Instantly share code, notes, and snippets.

@shreeve
Last active March 1, 2018 14:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save shreeve/f1b735f2abb8f874d8fa to your computer and use it in GitHub Desktop.
Save shreeve/f1b735f2abb8f874d8fa to your computer and use it in GitHub Desktop.
binding_of_caller, pure FFI version (no compiler needed)
#!/usr/bin/env ruby
# =============================================================================
# Quick proof-of-concept of an FFI-based binding_of_caller (no compiling!)
#
# Author: Steve Shreeve <steve.shreeve@gmail.com>
# Basis: binding_of_caller and ffi gems
# Date: 28 May 2015
#
# Legal: MIT License
# =============================================================================
# NOTE: FFI requires a dynamic library, it cannot be a static library
#
# To install a shared libruby using ruby-install, use this:
#
# ruby-install ruby 2.2.2 -- --enable-shared --disable-install-doc
#
# To nuke everything if you're using ruby-install, use this:
#
# rm -rf ~/.gem/ruby/2.2.2 ~/.rubies/ruby-2.2.2 ~/src/ruby-2.2.2
require 'ffi'
module FFI
module Binding
# Not sure the best way to refer to the dynamic library for ruby
LIB_PATH ||= "#{ENV['HOME']}/.rubies/ruby-2.2.2/lib/libruby.2.2.0.dylib"
extend FFI::Library
ffi_lib LIB_PATH
class Value
class << self
extend FFI::Library
ffi_lib LIB_PATH
attach_function :rb_obj_id , [:pointer], :pointer
attach_function :rb_num2long, [:pointer], :long
def native_type
FFI::Type::POINTER
end
def from_native(value, ctx=nil)
id =
if value.address & 0x0f == 0xe
value.address >> 8
else
rb_num2long rb_obj_id value
end
ObjectSpace._id2ref id
end
def to_native(obj, ctx=nil)
id = obj.__id__
case obj
when Symbol then FFI::Pointer.new id << 8 | 0xe
when Fixnum, FalseClass, TrueClass, NilClass then FFI::Pointer.new id
else FFI::Pointer.new id << 1
end
end
end
end
typedef FFI::Type::Mapped.new(Value), :value
attach_function :rb_debug_inspector_open, [callback([:pointer, :pointer], :pointer), :pointer], :value
class Context < FFI::Struct
layout :thread , :pointer,
:frame , :pointer,
:backtrace , :pointer,
:contexts , :pointer,
:backtrace_size, :long
end
Contexts = -> (ptr, _) { Context.new(ptr)[:contexts] }
class << self
def callers
rb_debug_inspector_open(Contexts, nil).drop(3).map {|ary| ary[2]}
end
end
end
end
class ::Binding
def of_caller(n)
c = ::FFI::Binding.callers
n < c.size or raise "No such frame, gone beyond end of stack!"
c[n]
end
end
# ==[ Show that it works... ]==
outer = 10
class Z
def z
u = 10
A.new.a
end
end
class A
def a
y = 10
B.new.b
end
end
class B
def b
x = 10
puts binding.of_caller(0).eval('local_variables')
puts binding.of_caller(1).eval('local_variables')
puts binding.of_caller(2).eval('local_variables')
puts binding.of_caller(3).eval('local_variables')
# puts binding.of_caller(400).eval('local_variables') # should throw an Exception
end
end
Z.new.z
__END__
# output:
# => x
# => y
# => u
# => outer
# Exception
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment