Last active
March 1, 2018 14:08
-
-
Save shreeve/f1b735f2abb8f874d8fa to your computer and use it in GitHub Desktop.
binding_of_caller, pure FFI version (no compiler needed)
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
#!/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