bindex-0.5.0/0000755000175000017500000000000013273334005011501 5ustar srudsrudbindex-0.5.0/Rakefile0000644000175000017500000000115313273334005013146 0ustar srudsrudrequire 'bundler/gem_tasks' require 'rake/testtask' Rake::TestTask.new do |t| t.libs << 'test' t.test_files = FileList['test/*_test.rb'] t.verbose = true end case RUBY_ENGINE when 'ruby' require 'rake/extensiontask' Rake::ExtensionTask.new('bindex') do |ext| ext.name = 'cruby' ext.lib_dir = 'lib/bindex' end task default: [:clean, :compile, :test] when 'jruby' require 'rake/javaextensiontask' Rake::JavaExtensionTask.new('bindex') do |ext| ext.name = 'jruby_internals' ext.lib_dir = 'lib/bindex' end task default: [:clean, :compile, :test] else task default: [:test] end bindex-0.5.0/README.md0000644000175000017500000000254113273334005012762 0ustar srudsrud# Bindex [![Build Status](https://travis-ci.org/gsamokovarov/bindex.svg?branch=master)](https://travis-ci.org/gsamokovarov/bindex) When Ruby raises an exception, it leaves you a backtrace to help you figure out where did the exception originated in. Bindex gives you the bindings as well. This can help you introspect the state of the Ruby program when at the point the exception occurred. ## Usage **Do not** use this gem on production environments. The performance penalty isn't worth it anywhere outside of development. ### API Bindex defines the following API: #### Exception#bindings Returns all the bindings up to the one in which the exception originated in. #### Bindex.current_bindings Returns all of the current Ruby execution state bindings. The first one is the current one, the second is the caller one, the third is the caller of the caller one and so on. ## Support ### CRuby CRuby 2.0.0 and above is supported. ### JRuby To get the best support, run JRuby in interpreted mode. ```bash export JRUBY_OPTS=--dev ``` Only JRuby 9k is supported. ### Rubinius Internal errors like `ZeroDevisionError` aren't caught. ## Credits Thanks to John Mair for his work on binding_of_caller, which is a huge inspiration. Thanks to Charlie Somerville for better_errors where the idea comes from. Thanks to Koichi Sasada for the debug inspector API in CRuby. bindex-0.5.0/.gitignore0000644000175000017500000000021213273334005013464 0ustar srudsrud/.bundle/ /.yardoc /Gemfile.lock /_yardoc/ /coverage/ /doc/ /pkg/ /spec/reports/ /tmp/ *.bundle *.so *.o *.a .ycm_extra_conf.py* mkmf.log bindex-0.5.0/.travis.yml0000644000175000017500000000051213273334005013610 0ustar srudsrudlanguage: ruby rvm: - ruby-2.0.0-p648 - ruby-2.1.10 - ruby-2.1.0 - ruby-2.2.6 - ruby-2.3.3 - ruby-2.4.0 - ruby-head - jruby-9.1.8.0 - jruby-head allow_failures: - rvm: ruby-head - rvm: jruby-head env: global: - JRUBY_OPTS=--dev before_install: gem install bundler sudo: false cache: bundler bindex-0.5.0/test/0000755000175000017500000000000013273334005012460 5ustar srudsrudbindex-0.5.0/test/fixtures/0000755000175000017500000000000013273334005014331 5ustar srudsrudbindex-0.5.0/test/fixtures/basic_nested_fixture.rb0000644000175000017500000000022013273334005021041 0ustar srudsrudclass BasicNestedFixture def call raise_an_error rescue => exc exc end private def raise_an_error raise end end bindex-0.5.0/test/fixtures/eval_nested_fixture.rb0000644000175000017500000000031313273334005020712 0ustar srudsrudclass EvalNestedFixture def call tap { raise_an_error_in_eval } rescue => exc exc end private def raise_an_error_in_eval eval 'raise', binding, __FILE__, __LINE__ end end bindex-0.5.0/test/fixtures/reraised_fixture.rb0000644000175000017500000000036613273334005020227 0ustar srudsrudclass ReraisedFixture def call reraise_an_error rescue => exc exc end private def raise_an_error_in_eval method_that_raises rescue => exc raise exc end def method_that_raises raise end end bindex-0.5.0/test/fixtures/flat_fixture.rb0000644000175000017500000000011113273334005017343 0ustar srudsrudclass FlatFixture def call raise rescue => exc exc end end bindex-0.5.0/test/fixtures/custom_error_fixture.rb0000644000175000017500000000017213273334005021147 0ustar srudsrudclass CustomErrorFixture Error = Class.new(StandardError) def call raise Error rescue => exc exc end end bindex-0.5.0/test/exception_test.rb0000644000175000017500000000245113273334005016044 0ustar srudsrudrequire 'test_helper' class ExceptionTest < BaseTest test 'bindings returns all the bindings of where the error originated' do exc = FlatFixture.new.call assert_equal 3, exc.bindings.first.eval('__LINE__') end test 'bindings returns all the bindings of where a custom error originate' do exc = CustomErrorFixture.new.call assert_equal 5, exc.bindings.first.eval('__LINE__') end test 'bindings goes down the_stack' do exc = BasicNestedFixture.new.call assert_equal 11, exc.bindings.first.eval('__LINE__') end test 'bindings inside_of_an_eval' do exc = EvalNestedFixture.new.call assert_equal 11, exc.bindings.first.eval('__LINE__') end test "re-raising doesn't lose bindings information" do exc = ReraisedFixture.new.call assert_equal 3, exc.bindings.first.eval('__LINE__') end test 'bindings is_empty_when_exception_is_still_not_raised' do exc = RuntimeError.new assert_equal [], exc.bindings end test 'bindings is_empty_when_set_backtrace_is_badly_called' do exc = RuntimeError.new # Exception#set_backtrace expects a string or array of strings. If the # input isn't like this it will raise a TypeError. assert_raises(TypeError) do exc.set_backtrace([nil]) end assert_equal [], exc.bindings end end bindex-0.5.0/test/current_bindings_test.rb0000644000175000017500000000030613273334005017402 0ustar srudsrudrequire 'test_helper' class CurrentBindingsTest < BaseTest test 'first binding returned is the current one' do assert_equal __LINE__, Bindex.current_bindings.first.eval('__LINE__') end end bindex-0.5.0/test/test_helper.rb0000644000175000017500000000063313273334005015325 0ustar srudsrud$LOAD_PATH << File.expand_path('../lib', __FILE__) require 'minitest/autorun' require 'bindex' current_directory = File.dirname(File.expand_path(__FILE__)) # Fixtures are plain classes that respond to #call. Dir["#{current_directory}/fixtures/**/*.rb"].each do |fixture| require fixture end class BaseTest < MiniTest::Test def self.test(name, &block) define_method("test_#{name}", &block) end end bindex-0.5.0/lib/0000755000175000017500000000000013273334005012247 5ustar srudsrudbindex-0.5.0/lib/bindex/0000755000175000017500000000000013273334005013520 5ustar srudsrudbindex-0.5.0/lib/bindex/jruby_internals.jar0000644000175000017500000001665313273334005017443 0ustar srudsrudPK=ftJ META-INF/PKPK=ftJMETA-INF/MANIFEST.MFMLK-. K-*ϳR03r.JM,IMu X*h%&*8%krrPK"DEPK=ftJ,com/gsamokovarov/bindex/BindingBuilder.classRJA}mLNjq!!"x왴̴3A~ (~Xエ vSZ €<0H0`!CmҋCBf4CYuc7W5cecR8tĎt0l]2^3gN#+=%ҶFr:lVvtw}5y-ٖEQ LdS.b&Y%!%‚g<95miM[.*enKya/-`3W=o=vswKX"?5CINB7K3̉yvtȘ{UuS> ur4S){nƾr/PK1)?tPK=ftJ5com/gsamokovarov/bindex/CurrentBindingsIterator.classU]SV=l+BK%P|(_؁)&iIdֈؒZ҇t2Gu+dCs]+CbYÝp'}g} {co/ػރ-9 CkH`7cRW  $~Rf]|u*1n2˔)ӒFFd]:Q v bc{k3\;{Jx[< ڴYevr"ڞ,J?yJ@'L3=xsS* )Yw[2"-ӨN.KeKv1=_0&${v(xRV/9ukZr}e O}@!!Yd^3XXܺQmu2265*Ъ䬛:Ʀq\ױ L7::ǾCI |ccK##0uz[YXٮᱎ'^ӺnRKv*S;v:GK-MٳO`-{qݹ#oe_"Fm 6lm$m l#;1P:8!oM.)<e1J&yA2Ch_{@,JtB%~AûG]"}-6)@2$(0TH-;/ǍӁ Ma/"rEyڝH+G_|g ^ ѓ፣$ڋsPݢ. PKza"PK=ftJ<com/gsamokovarov/bindex/JRubyIntegration$BindexMethods.classRO@B=QQN 0 /bMH&Hxmʞnn/_(l+\4&3;fQ' ]{o1"3 sGJ7eK{*{ZݾCӂSؔl_q[jrO/h'/_q24m<ټ#a>S6N8e4r+ ~C0C mio"\:Ѥ1)m"TeVUFb,:B;V ebr"7XX3>*N؝F( Ifח)'_|D&m,|Rnog޳V\weu4'F2 Gz:I+g3rmToh|E:w4}̽GIi!cPKeەPK=ftJHcom/gsamokovarov/bindex/JRubyIntegration$ExceptionExtensionMethods.classSMSA}lV@( &!Yc,K*VU@mLdVg'1^Ty ZR}wNmX')#=<҂=e/IhdHVɻ8MS΍ǰ? ץ/-<ÍONUk4utSaB]u/>¤u= 9y,l(d3hz]s+EAS 'c"MxE̮(%vLЯð>Toj:2xX:W>(6ZF:CkN$FӶAk5H0{a.UL_liOJ?7(hJHq)5$? XA6I3ұgd0 La A(l ݍى3D#cxĊOub r!z/^t3j6i'"or֐PK苹0iPK=ftJ.com/gsamokovarov/bindex/JRubyIntegration.classS[O@-." K*&@0bLĐnǵ;C?g^D}Gϴf mzΜ?x&0݋۸c`]}&6)(0I@ED]>K?bvVÍ"Nan3>?ή"PK̖9`5|/nzxJ!T~g*Fϓ'U? J 4%WlӔv8 ⠡hX p/hCt.ݐ ~fvA7g8EE|}vJ li(Kn߲]mtW6.6ziٷg5x\"ߐ[$B)(bF%>-~0Ho=Az$a2I & Q&g2)=] rgȏ/PKsPK=ftJ3com/gsamokovarov/bindex/RubyBindingsCollector.classTRA=% !D 0+B*RXod+,&԰)~X%o[ԞeCI.lwO='W)<0a\uD0ᆎ6)rS$ʚґ@0 \mT kW,HΑ%-;FGv-id={]+A2vI' eР_Et{ Nlܬ'%ej6ޠhcKZf1wS5\4P)-U䆙/S}a ]y1ǛOe[R73 GΨ0L /獳gJg) \U0{ Yy@Pݽ^47 a#<֐60ya/sZmJ0^=! { ;ݾ5MA2d7f(RSrZ oC؍yZŲZkwڴ8`|Gӟ>@u5PjBt !]yH ֋tyAhJ"utGP~ cjZZ?W I+uώ( w"1"fh.PKuV2PK=ftJ=com/gsamokovarov/bindex/ThreadContextInterfaceException.class_K@X5ݺ yaaQDUM}Xɴ63%v|Rě UXgsο{l)c¬9۰ɢ c??MGc38 >^DԾVʜn_0v;[Cq_G6'%&ou?m]P IT22ԟM?εёЄ:a~g`[uieLjX F)z@)8X_k_R;w1M}2zhyͮ_4 WBCL?PKf&PK=ftJ4com/gsamokovarov/bindex/ThreadContextInternals.classTkS@=[* 1PAb@@T⃧ 3m@0i3 3:~G9M Rgvn6wϹٻFZe 6C* FTV0AwS1⾂**Hk# D &LjhŔi3 fu|3λ^J]+9Uʺ'xn I3ԏ[?f?XeN9X*d³6yŸxAd|nn2\'Ո@!; u\ٛ1ݭ kux2d:ĪMlv8آ )Ks8D%l|˞}1k ;ܰ72g9t"ZnS2n3&K93WAC9u$dex"dt 5.4" spec.add_development_dependency "bundler" spec.add_development_dependency "rake" spec.add_development_dependency "rake-compiler" end bindex-0.5.0/CONTRIBUTING.md0000644000175000017500000000106013273334005013727 0ustar srudsrud# Contributing 1. Fork it ( https://github.com/gsamokovarov/bindex/fork ) 2. Create your feature branch (`git checkout -b my-new-feature`) 3. Commit your changes (`git commit -am 'Add some feature'`) 4. Push to the branch (`git push origin my-new-feature`) 5. Create a new Pull Request ## Etiquette If you want to contribute code, which is not your own or is heavily inspired by someone else's code, please give them a warm shoutout in the pull request (or the commit message) and the code itself. Of course, don't try to sneak in non MIT compatible code. bindex-0.5.0/ext/0000755000175000017500000000000013273334005012301 5ustar srudsrudbindex-0.5.0/ext/bindex/0000755000175000017500000000000013273334005013552 5ustar srudsrudbindex-0.5.0/ext/bindex/cruby.c0000644000175000017500000000342513273334005015046 0ustar srudsrud#include #include VALUE bx_mBindex; static ID id_bindings; static VALUE current_bindings_callback(const rb_debug_inspector_t *context, void *data) { VALUE locations = rb_debug_inspector_backtrace_locations(context); VALUE binding, bindings = rb_ary_new(); long i, length = RARRAY_LEN(locations); for (i = 0; i < length; i++) { binding = rb_debug_inspector_frame_binding_get(context, i); if (!NIL_P(binding)) { rb_ary_push(bindings, binding); } } return bindings; } VALUE current_bindings(void) { return rb_debug_inspector_open(current_bindings_callback, NULL); } static void set_exception_bindings_callback(VALUE tpval, void *data) { rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tpval); VALUE exception = rb_tracearg_raised_exception(trace_arg); VALUE bindings = rb_attr_get(exception, id_bindings); /* Set the bindings, only if they haven't been set already. This may reset * the binding during reraise. */ if (NIL_P(bindings)) { rb_ivar_set(exception, id_bindings, current_bindings()); } } void set_exception_bindings_on_raise(void) { VALUE tpval = rb_tracepoint_new(0, RUBY_EVENT_RAISE, set_exception_bindings_callback, 0); rb_tracepoint_enable(tpval); } static VALUE bx_current_bindings(VALUE self) { return current_bindings(); } static VALUE bx_exc_bindings(VALUE self) { VALUE bindings = rb_attr_get(self, id_bindings); if (NIL_P(bindings)) { bindings = rb_ary_new(); } return bindings; } void Init_cruby(void) { bx_mBindex = rb_define_module("Bindex"); id_bindings = rb_intern("bindings"); rb_define_singleton_method(bx_mBindex, "current_bindings", bx_current_bindings, 0); rb_define_method(rb_eException, "bindings", bx_exc_bindings, 0); set_exception_bindings_on_raise(); } bindex-0.5.0/ext/bindex/com/0000755000175000017500000000000013273334005014330 5ustar srudsrudbindex-0.5.0/ext/bindex/com/gsamokovarov/0000755000175000017500000000000013273334005017046 5ustar srudsrudbindex-0.5.0/ext/bindex/com/gsamokovarov/bindex/0000755000175000017500000000000013273334005020317 5ustar srudsrudbindex-0.5.0/ext/bindex/com/gsamokovarov/bindex/ThreadContextInterfaceException.java0000644000175000017500000000074513273334005027444 0ustar srudsrudpackage com.gsamokovarov.bindex; class ThreadContextInterfaceException extends RuntimeException { private static final String MESSAGE_TEMPLATE = "Expected private field %s in ThreadContext is missing"; ThreadContextInterfaceException(String fieldName) { super(String.format(MESSAGE_TEMPLATE, fieldName)); } ThreadContextInterfaceException(String fieldName, Throwable cause) { super(String.format(MESSAGE_TEMPLATE, fieldName), cause); } } bindex-0.5.0/ext/bindex/com/gsamokovarov/bindex/ThreadContextInternals.java0000644000175000017500000000302313273334005025614 0ustar srudsrudpackage com.gsamokovarov.bindex; import java.lang.reflect.Field; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.DynamicScope; import org.jruby.runtime.Frame; import org.jruby.runtime.backtrace.BacktraceElement; import org.jruby.runtime.builtin.IRubyObject; public class ThreadContextInternals { private ThreadContext context; public ThreadContextInternals(ThreadContext context) { this.context = context; } public Frame[] getFrameStack() { return (Frame[]) getPrivateField("frameStack"); } public int getFrameIndex() { return (Integer) getPrivateField("frameIndex"); } public DynamicScope[] getScopeStack() { return (DynamicScope[]) getPrivateField("scopeStack"); } public int getScopeIndex() { return (Integer) getPrivateField("scopeIndex"); } public BacktraceElement[] getBacktrace() { return (BacktraceElement[]) getPrivateField("backtrace"); } public int getBacktraceIndex() { return (Integer) getPrivateField("backtraceIndex"); } private Object getPrivateField(String fieldName) { try { Field field = ThreadContext.class.getDeclaredField(fieldName); field.setAccessible(true); return field.get(context); } catch (NoSuchFieldException exc) { throw new ThreadContextInterfaceException(fieldName, exc); } catch (IllegalAccessException exc) { throw new ThreadContextInterfaceException(fieldName, exc); } } } bindex-0.5.0/ext/bindex/com/gsamokovarov/bindex/SetExceptionBindingsEventHook.java0000644000175000017500000000170413273334005027077 0ustar srudsrudpackage com.gsamokovarov.bindex; import org.jruby.runtime.EventHook; import org.jruby.runtime.RubyEvent; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.RubyArray; import org.jruby.RubyException; public class SetExceptionBindingsEventHook extends EventHook { public boolean isInterestedInEvent(RubyEvent event) { return event == RubyEvent.RAISE; } public void eventHandler(ThreadContext context, String eventName, String file, int line, String name, IRubyObject type) { RubyArray bindings = RubyBindingsCollector.collectCurrentFor(context); RubyException exception = (RubyException) context.runtime.getGlobalVariables().get("$!"); IRubyObject exceptionBindings = exception.getInstanceVariable("@bindings"); if (exceptionBindings == null || exceptionBindings.isNil()) { exception.setInstanceVariable("@bindings", bindings); } } } bindex-0.5.0/ext/bindex/com/gsamokovarov/bindex/RubyBindingsCollector.java0000644000175000017500000000175713273334005025442 0ustar srudsrudpackage com.gsamokovarov.bindex; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.Binding; import org.jruby.runtime.DynamicScope; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.RubyBinding; import org.jruby.RubyArray; import org.jruby.Ruby; import java.util.Iterator; public class RubyBindingsCollector { private final Ruby runtime; private Iterator iterator; public static RubyArray collectCurrentFor(ThreadContext context) { return new RubyBindingsCollector(context).collectCurrent(); } private RubyBindingsCollector(ThreadContext context) { this.iterator = new CurrentBindingsIterator(context); this.runtime = context.getRuntime(); } private RubyArray collectCurrent() { RubyArray bindings = RubyArray.newArray(runtime); while (iterator.hasNext()) { bindings.append(((IRubyObject) RubyBinding.newBinding(runtime, iterator.next()))); } return bindings; } } bindex-0.5.0/ext/bindex/com/gsamokovarov/bindex/BindingBuilder.java0000644000175000017500000000067613273334005024054 0ustar srudsrudpackage com.gsamokovarov.bindex; import org.jruby.runtime.DynamicScope; import org.jruby.runtime.Binding; import org.jruby.runtime.Frame; import org.jruby.runtime.DynamicScope; import org.jruby.runtime.backtrace.BacktraceElement; class BindingBuilder { public static Binding build(Frame frame, DynamicScope scope, BacktraceElement element) { return new Binding(frame, scope, element.method, element.filename, element.line); } } bindex-0.5.0/ext/bindex/com/gsamokovarov/bindex/CurrentBindingsIterator.java0000644000175000017500000000320313273334005025772 0ustar srudsrudpackage com.gsamokovarov.bindex; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.DynamicScope; import org.jruby.runtime.Binding; import org.jruby.runtime.Frame; import org.jruby.runtime.DynamicScope; import org.jruby.runtime.backtrace.BacktraceElement; import java.util.Iterator; import java.util.NoSuchElementException; class CurrentBindingsIterator implements Iterator { private Frame[] frameStack; private int frameIndex; private DynamicScope[] scopeStack; private int scopeIndex; private BacktraceElement[] backtrace; private int backtraceIndex; CurrentBindingsIterator(ThreadContext context) { ThreadContextInternals contextInternals = new ThreadContextInternals(context); this.frameStack = contextInternals.getFrameStack(); this.frameIndex = contextInternals.getFrameIndex(); this.scopeStack = contextInternals.getScopeStack(); this.scopeIndex = contextInternals.getScopeIndex(); this.backtrace = contextInternals.getBacktrace(); this.backtraceIndex = contextInternals.getBacktraceIndex(); } public boolean hasNext() { return frameIndex >= 0 && scopeIndex >= 0 && backtraceIndex >= 0; } public Binding next() { if (!hasNext()) { throw new NoSuchElementException(); } Frame frame = frameStack[frameIndex--]; DynamicScope scope = scopeStack[scopeIndex--]; BacktraceElement element = backtrace[backtraceIndex--]; return BindingBuilder.build(frame, scope, element); } public void remove() { throw new UnsupportedOperationException(); } } bindex-0.5.0/ext/bindex/com/gsamokovarov/bindex/JRubyIntegration.java0000644000175000017500000000324313273334005024423 0ustar srudsrudpackage com.gsamokovarov.bindex; import org.jruby.Ruby; import org.jruby.RubyArray; import org.jruby.RubyModule; import org.jruby.RubyClass; import org.jruby.runtime.ThreadContext; import org.jruby.runtime.builtin.IRubyObject; import org.jruby.runtime.builtin.InstanceVariables; import org.jruby.anno.JRubyMethod; public class JRubyIntegration { public static void setup(Ruby runtime) { RubyModule bindex = runtime.defineModule("Bindex"); bindex.defineAnnotatedMethods(BindexMethods.class); RubyClass exception = runtime.getException(); exception.defineAnnotatedMethods(ExceptionExtensionMethods.class); IRubyObject verbose = runtime.getVerbose(); try { runtime.setVerbose(runtime.getNil()); runtime.addEventHook(new SetExceptionBindingsEventHook()); } finally { runtime.setVerbose(verbose); } } public static class BindexMethods { @JRubyMethod(name = "current_bindings", meta = true) public static IRubyObject currentBindings(ThreadContext context, IRubyObject self) { return RubyBindingsCollector.collectCurrentFor(context); } } public static class ExceptionExtensionMethods { @JRubyMethod public static IRubyObject bindings(ThreadContext context, IRubyObject self) { InstanceVariables instanceVariables = self.getInstanceVariables(); IRubyObject bindings = instanceVariables.getInstanceVariable("@bindings"); if (bindings != null && !bindings.isNil()) { return bindings; } return RubyArray.newArray(context.getRuntime()); } } } bindex-0.5.0/ext/bindex/extconf.rb0000644000175000017500000000057213273334005015551 0ustar srudsrudcase RUBY_ENGINE when "ruby" require "mkmf" $CFLAGS << " -Wall" $CFLAGS << " -g3 -O0" if ENV["DEBUG"] create_makefile("bindex/cruby") else IO.write(File.expand_path("../Makefile", __FILE__), <<-END) all install static install-so install-rb: Makefile .PHONY: all install static install-so install-rb .PHONY: clean clean-so clean-static clean-rb END end