pax_global_header00006660000000000000000000000064137723732230014523gustar00rootroot0000000000000052 comment=7edf4b494a8af981dda8a3d18cd8ff8fd400df32 binding_of_caller-1.0.0/000077500000000000000000000000001377237322300151415ustar00rootroot00000000000000binding_of_caller-1.0.0/.gemtest000066400000000000000000000000001377237322300166000ustar00rootroot00000000000000binding_of_caller-1.0.0/.github/000077500000000000000000000000001377237322300165015ustar00rootroot00000000000000binding_of_caller-1.0.0/.github/workflows/000077500000000000000000000000001377237322300205365ustar00rootroot00000000000000binding_of_caller-1.0.0/.github/workflows/test.yml000066400000000000000000000047341377237322300222500ustar00rootroot00000000000000name: Test on: push: branches: [ master ] schedule: - cron: '0 0 11,25 * *' # roughly every two weeks to run on new Ruby versions pull_request: branches: [ master ] workflow_dispatch: jobs: test: name: "Unit" runs-on: ubuntu-latest strategy: fail-fast: false matrix: ruby: - "2.1" - "2.2" - "2.3" - "2.4" - "2.5" - "2.6" - "2.7" - "3.0" steps: - uses: actions/checkout@v2 - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ matrix.ruby }} bundler-cache: true - name: Test run: bundle exec rspec -f doc system: name: "System" runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: os: - macos-latest - ubuntu-latest - windows-latest ruby: - "2" - "3.0" - "jruby" - "truffleruby" exclude: # Windows releases of jruby and truffleruby have issues. Skip them for now. - { ruby: "jruby", os: "windows-latest" } - { ruby: "truffleruby", os: "windows-latest" } steps: - uses: actions/checkout@v2 - name: Determine ruby version name id: ruby_version run: | if [[ $OS == 'windows-latest' && $RUBY == '3.0' ]]; then # Windows doesn't have 3.0, so run head there but nowhere else. echo "::set-output name=release::head" else echo "::set-output name=release::$RUBY" fi shell: bash env: OS: ${{ matrix.os }} RUBY: ${{ matrix.ruby }} - name: Set up Ruby uses: ruby/setup-ruby@v1 with: ruby-version: ${{ steps.ruby_version.outputs.release }} bundler-cache: true - name: Install gem run: bundle exec rake install - name: Create directory for gem test run: mkdir -p tmp/gem-test - name: Create test Gemfile run: echo "gem 'binding_of_caller'" > Gemfile working-directory: ./tmp/gem-test - name: Test gem load run: bundle exec ruby -e "require 'binding_of_caller'" - name: Test gem functionality if: ${{ matrix.ruby != 'jruby' && matrix.ruby != 'truffleruby' }} run: bundle exec ruby -e "require 'binding_of_caller'; binding.of_caller(0).eval('var = :hello')" env: JRUBY_OPTS: "--dev" # This will support JRuby once the gem is updated to support JRuby 9.x binding_of_caller-1.0.0/.gitignore000066400000000000000000000001771377237322300171360ustar00rootroot00000000000000Gemfile.lock /.bundle/ /.yardoc /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ # rspec failure tracking .rspec_status binding_of_caller-1.0.0/.yardopts000066400000000000000000000000231377237322300170020ustar00rootroot00000000000000--markup markdown binding_of_caller-1.0.0/Gemfile000066400000000000000000000001761377237322300164400ustar00rootroot00000000000000source "https://rubygems.org" # Specify your gem's dependencies in binding_of_caller.gemspec gemspec gem "rake" gem "rspec" binding_of_caller-1.0.0/LICENSE000066400000000000000000000021531377237322300161470ustar00rootroot00000000000000License ------- (The MIT License) Copyright (c) 2011 John Mair (banisterfiend) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. binding_of_caller-1.0.0/README.md000066400000000000000000000070211377237322300164200ustar00rootroot00000000000000[![Build Status](https://github.com/banister/binding_of_caller/workflows/Test/badge.svg?branch=master&event=push)](https://github.com/banister/binding_of_caller/actions?query=branch%3Amaster) [![Gem Version](https://img.shields.io/gem/v/binding_of_caller.svg)](https://rubygems.org/gems/binding_of_caller) binding_of_caller ================= (C) John Mair (banisterfiend) 2012 _Retrieve the binding of a method's caller in MRI (>= 2.0.0) and RBX (Rubinius)_ The `binding_of_caller` gem provides the `Binding#of_caller` method. Using `binding_of_caller` we can grab bindings from higher up the call stack and evaluate code in that context. Allows access to bindings arbitrarily far up the call stack, not limited to just the immediate caller. **Recommended for use only in debugging situations. Do not use this in production apps.** **Works in MRI Ruby (>= 2.0) and RBX (Rubinius)** * Install the [gem](https://rubygems.org/gems/binding_of_caller): `gem install binding_of_caller` * See the [source code](http://github.com/banister/binding_of_caller) Example: Modifying a local inside the caller of a caller -------- ```ruby def a var = 10 b puts var end def b c end def c binding.of_caller(2).eval('var = :hello') end a() # OUTPUT # => hello ``` Spinoff project ------- This project is a spinoff from the [Pry REPL project.](http://pry.github.com) Features and limitations ------------------------- * Works in MRI (>= 2.0.0) and RBX (Rubinius) * For MRI 1.9.x, use version "~> 0.8" of the gem, which included support for MRI before 2.0. * Does not work in 1.8.7, but there is a well known (continuation-based) hack to get a `Binding#of_caller` there. * There is experimental support for jruby 1.7.x, but it only works in interpreted mode (i.e. use the option `-Djruby.compile.mode=OFF` or append `compile.mode=OFF` to your `.jrubyrc`) Development ----------- After checking out the repo, run `bin/setup` to install dependencies. Then, run `rake spec` to run the tests. You can also run `bin/console` for an interactive prompt that will allow you to experiment. To install this gem onto your local machine, run `bundle exec rake install`. To release a new version, update the version number in `version.rb`, and then run `bundle exec rake release`, which will create a git tag for the version, push git commits and tags, and push the `.gem` file to [rubygems.org](https://rubygems.org). Contact ------- Problems or questions contact me at [github](http://github.com/banister) License ------- (The MIT License) Copyright (c) 2012 (John Mair) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the 'Software'), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED 'AS IS', WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. binding_of_caller-1.0.0/Rakefile000066400000000000000000000001651377237322300166100ustar00rootroot00000000000000require "bundler/gem_tasks" require "rspec/core/rake_task" RSpec::Core::RakeTask.new(:spec) task :default => :spec binding_of_caller-1.0.0/bin/000077500000000000000000000000001377237322300157115ustar00rootroot00000000000000binding_of_caller-1.0.0/bin/console000077500000000000000000000005401377237322300173000ustar00rootroot00000000000000#!/usr/bin/env ruby require "bundler/setup" require "binding_of_caller" # You can add fixtures and/or initialization code here to make experimenting # with your gem easier. You can also use a different console, if you like. # (If you use this, don't forget to add pry to your Gemfile!) # require "pry" # Pry.start require "irb" IRB.start(__FILE__) binding_of_caller-1.0.0/bin/setup000077500000000000000000000002031377237322300167720ustar00rootroot00000000000000#!/usr/bin/env bash set -euo pipefail IFS=$'\n\t' set -vx bundle install # Do any other automated setup that you need to do here binding_of_caller-1.0.0/binding_of_caller.gemspec000066400000000000000000000026241377237322300221320ustar00rootroot00000000000000require_relative 'lib/binding_of_caller/version' Gem::Specification.new do |spec| spec.name = "binding_of_caller" spec.version = BindingOfCaller::VERSION spec.authors = ["John Mair (banisterfiend)"] spec.email = ["jrmair@gmail.com"] spec.summary = %q{Retrieve the binding of a method's caller, or further up the stack.} spec.description = <<-TXT Provides the Binding#of_caller method. Using binding_of_caller we can grab bindings from higher up the call stack and evaluate code in that context. Allows access to bindings arbitrarily far up the call stack, not limited to just the immediate caller. Recommended for use only in debugging situations. Do not use this in production apps. TXT spec.homepage = "https://github.com/banister/binding_of_caller" spec.license = "MIT" spec.required_ruby_version = Gem::Requirement.new(">= 2.0.0") spec.metadata = { "changelog_uri" => "https://github.com/banister/binding_of_caller/releases", } # Specify which files should be added to the gem when it is released. # The `git ls-files -z` loads the files in the RubyGem that have been added into git. spec.files = Dir.chdir(File.expand_path('..', __FILE__)) do `git ls-files -z`.split("\x0").reject { |f| f.match(%r{^(test|spec|examples|bin)/}) } end spec.require_paths = ["lib"] spec.add_dependency "debug_inspector", ">= 0.0.1" end binding_of_caller-1.0.0/examples/000077500000000000000000000000001377237322300167575ustar00rootroot00000000000000binding_of_caller-1.0.0/examples/benchmark.rb000066400000000000000000000020201377237322300212300ustar00rootroot00000000000000require 'benchmark' unless Object.const_defined? :BindingOfCaller $:.unshift File.expand_path '../../lib', __FILE__ require 'binding_of_caller' require 'binding_of_caller/version' end n = 250000 Benchmark.bm(10) do |x| x.report("#of_caller") do 1.upto(n) do 1.times do 1.times do binding.of_caller(2) binding.of_caller(1) end end end end x.report("#frame_count") do 1.upto(n) do 1.times do 1.times do binding.frame_count end end end end x.report("#callers") do 1.upto(n) do 1.times do 1.times do binding.callers end end end end x.report("#frame_description") do 1.upto(n) do 1.times do 1.times do binding.of_caller(1).frame_description end end end end x.report("#frame_type") do 1.upto(n) do 1.times do 1.times do binding.of_caller(1).frame_type end end end end end binding_of_caller-1.0.0/examples/example.rb000066400000000000000000000012201377237322300207320ustar00rootroot00000000000000unless Object.const_defined? :BindingOfCaller $:.unshift File.expand_path '../../lib', __FILE__ require 'binding_of_caller' require 'binding_of_caller/version' end 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') end end Z.new.z # output: # => x # => y # => u # => outer # Exception binding_of_caller-1.0.0/lib/000077500000000000000000000000001377237322300157075ustar00rootroot00000000000000binding_of_caller-1.0.0/lib/binding_of_caller.rb000066400000000000000000000006011377237322300216510ustar00rootroot00000000000000require "binding_of_caller/version" if defined?(RUBY_ENGINE) && RUBY_ENGINE == "ruby" if RUBY_VERSION =~ /^[23]/ require 'binding_of_caller/mri' else puts "This version of binding_of_caller doesn't support this version of Ruby" end elsif defined?(Rubinius) require 'binding_of_caller/rubinius' elsif defined?(JRuby) require 'binding_of_caller/jruby_interpreted' end binding_of_caller-1.0.0/lib/binding_of_caller/000077500000000000000000000000001377237322300213275ustar00rootroot00000000000000binding_of_caller-1.0.0/lib/binding_of_caller/jruby_interpreted.rb000066400000000000000000000047201377237322300254170ustar00rootroot00000000000000module BindingOfCaller class JRubyBindingHolder java_import org.jruby.RubyBinding def initialize(binding) @binding = binding end def eval(code, file = nil, line = nil) b = JRuby.dereference(RubyBinding.new(JRuby.runtime, Binding, @binding)) if (file == nil) Kernel.eval code, b else Kernel.eval code, b, file, line end end def frame_type case when block? :block when eval? :eval when top? :top else :method end end def frame_description "#{block_desc}#{method_desc}" end private def block? @binding.getDynamicScope().getStaticScope().isBlockScope() end def eval? @binding.getFrame().getKlazz().nil? && @binding.getLine() != 0 end def top? @binding.getFrame().getKlazz().nil? && @binding.getLine() == 0 end def block_desc if frame_type == :block "block in " end end def method_desc @binding.getFrame().getName() || "
" end end module BindingExtensions def of_caller(index = 1) index += 1 # always omit this frame JRuby.runtime.current_context.binding_of_caller(index) end def callers ary = [] n = 2 while binding = of_caller(n) ary << binding n += 1 end ary end def frame_count callers.count - 1 end def frame_type nil end def frame_description nil end end end class org::jruby::runtime::ThreadContext java_import org.jruby.runtime.Binding java_import org.jruby.RubyInstanceConfig::CompileMode field_accessor :frameStack, :frameIndex, :scopeStack, :scopeIndex, :backtrace, :backtraceIndex def binding_of_caller(index) unless JRuby.runtime.instance_config.compile_mode == CompileMode::OFF raise RuntimeError, "caller binding only supported in interpreter" end index += 1 # always omit this frame return nil if index > frameIndex frame = frameStack[frameIndex - index] return binding_of_caller(index - 1) if index > scopeIndex scope = scopeStack[scopeIndex - index] element = backtrace[backtraceIndex - index] binding = Binding.new(frame, scope.static_scope.module, scope, element.clone) BindingOfCaller::JRubyBindingHolder.new(binding) end end class ::Binding include BindingOfCaller::BindingExtensions endbinding_of_caller-1.0.0/lib/binding_of_caller/mri.rb000066400000000000000000000025651377237322300224530ustar00rootroot00000000000000require 'debug_inspector' module BindingOfCaller module BindingExtensions # Retrieve the binding of the nth caller of the current frame. # @return [Binding] def of_caller(n) c = callers.drop(1) if n > (c.size - 1) raise "No such frame, gone beyond end of stack!" else c[n] end end # Return bindings for all caller frames. # @return [Array] def callers ary = [] RubyVM::DebugInspector.open do |dc| locs = dc.backtrace_locations locs.size.times do |i| b = dc.frame_binding(i) if b b.instance_variable_set(:@iseq, dc.frame_iseq(i)) ary << b end end end ary.drop(1) end # Number of parent frames available at the point of call. # @return [Fixnum] def frame_count callers.size - 1 end # The type of the frame. # @return [Symbol] def frame_type return nil if !@iseq # apparently the 9th element of the iseq array holds the frame type # ...not sure how reliable this is. @frame_type ||= @iseq.to_a[9] end # The description of the frame. # @return [String] def frame_description return nil if !@iseq @frame_description ||= @iseq.label end end end class ::Binding include BindingOfCaller::BindingExtensions end binding_of_caller-1.0.0/lib/binding_of_caller/rubinius.rb000066400000000000000000000033151377237322300235160ustar00rootroot00000000000000module BindingOfCaller module BindingExtensions # Retrieve the binding of the nth caller of the current frame. # @return [Binding] def of_caller(n) location = Rubinius::VM.backtrace(1 + n, true).first raise RuntimeError, "Invalid frame, gone beyond end of stack!" if location.nil? setup_binding_from_location(location) end # The description of the frame. # @return [String] def frame_description @frame_description end # Return bindings for all caller frames. # @return [Array] def callers Rubinius::VM.backtrace(1, true).map &(method(:setup_binding_from_location). to_proc) end # Number of parent frames available at the point of call. # @return [Fixnum] def frame_count Rubinius::VM.backtrace(1).count end # The type of the frame. # @return [Symbol] def frame_type if compiled_code.for_module_body? :class elsif compiled_code.for_eval? :eval elsif compiled_code.is_block? :block else :method end end protected def setup_binding_from_location(location) binding = Binding.setup location.variables, location.variables.method, location.constant_scope, location.variables.self, location binding.instance_variable_set :@frame_description, location.describe.gsub("{ } in", "block in") binding end end end class ::Binding include BindingOfCaller::BindingExtensions extend BindingOfCaller::BindingExtensions end binding_of_caller-1.0.0/lib/binding_of_caller/version.rb000066400000000000000000000000571377237322300233430ustar00rootroot00000000000000module BindingOfCaller VERSION = "1.0.0" end binding_of_caller-1.0.0/spec/000077500000000000000000000000001377237322300160735ustar00rootroot00000000000000binding_of_caller-1.0.0/spec/binding_of_caller_spec.rb000066400000000000000000000075051377237322300230610ustar00rootroot00000000000000require 'spec_helper' puts "Testing binding_of_caller version #{BindingOfCaller::VERSION}..." puts "Ruby version: #{RUBY_VERSION}" RSpec.describe BindingOfCaller do describe "#of_caller" do it "fetches the immediate caller's binding when 0 is passed" do o = Object.new def o.a var = 1 binding.of_caller(0).eval('var') end expect(o. a).to eq 1 end it "fetches the parent of caller's binding when 1 is passed" do o = Object.new def o.a var = 1 b end def o.b binding.of_caller(1).eval('var') end expect(o.a).to eq 1 end it "modifies locals in the parent of caller's binding" do o = Object.new def o.a var = 1 b var end def o.b binding.of_caller(1).eval('var = 20') end expect(o.a).to eq 20 end it "raises an exception when retrieving an out-of-band binding" do o = Object.new def o.a binding.of_caller(100) end expect { o.a }.to raise_error(RuntimeError) end end describe "#callers" do before do @o = Object.new end it 'returns the first non-internal binding when using callers.first' do def @o.meth x = :a_local [binding.callers.first, binding.of_caller(0)] end b1, b2 = @o.meth expect(b1.eval("x")).to eq :a_local expect(b2.eval("x")).to eq :a_local end end describe "#frame_count" do it 'equals the binding callers.count' do expect(binding.frame_count).to eq binding.callers.count end end describe "#frame_descripton" do it 'can be called on ordinary binding without raising' do expect { binding.frame_description }.not_to raise_error end describe "when inside a block" do before { @binding = proc { binding.of_caller(0) }.call } it 'describes a block frame' do expect(@binding.frame_description).to match /block/ end end describe "when inside an instance method" do before do o = Object.new def o.horsey_malone; binding.of_caller(0); end @binding = o.horsey_malone; end it 'describes a method frame with the method name' do expect(@binding.frame_description).to match /horsey_malone/ end end describe "when inside a class definition" do before do class HorseyMalone @binding = binding.of_caller(0) def self.binding; @binding; end end @binding = HorseyMalone.binding end it 'describes a class frame' do expect(@binding.frame_description).to match /class/i Object.remove_const(:HorseyMalone) end end end describe "#frame_type" do it 'can be called on ordinary binding without raising' do expect { binding.frame_type }.not_to raise_error end describe "when inside a class definition" do before do class HorseyMalone @binding = binding.of_caller(0) def self.binding; @binding; end end @binding = HorseyMalone.binding end it 'returns :class' do expect(@binding.frame_type).to eq :class end end describe "when evaluated" do before { @binding = eval("binding.of_caller(0)") } it 'returns :eval' do expect(@binding.frame_type).to eq :eval end end describe "when inside a block" do before { @binding = proc { binding.of_caller(0) }.call } it 'returns :block' do expect(@binding.frame_type).to eq :block end end describe "when inside an instance method" do before do o = Object.new def o.a; binding.of_caller(0); end @binding = o.a; end it 'returns :method' do expect(@binding.frame_type).to eq :method end end end end binding_of_caller-1.0.0/spec/spec_helper.rb000066400000000000000000000004701377237322300207120ustar00rootroot00000000000000require "bundler/setup" require "binding_of_caller" class Module public :remove_const end RSpec.configure do |config| # Enable flags like --only-failures and --next-failure config.example_status_persistence_file_path = ".rspec_status" config.expect_with :rspec do |c| c.syntax = :expect end end