skiptrace 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: b096ce5bb5b6dc3eaaa85da829ed81f679769d250832d69200cd436ede980f8e
4
+ data.tar.gz: 225309587a7ae71e880a4c0de379a4e7d648ada4d8174f780499c386102345dc
5
+ SHA512:
6
+ metadata.gz: e343d3d59722bd66bf413607751fb8a24c3b7bcb1279191204bf2d626b06c16f9f275cc888a60979b2f6f0d676f3d245eb2feb06d6d063910d35a5fb74887b9d
7
+ data.tar.gz: 241c4e6944ca59877981f910f53c0fbf601640d54fb56fcc5c51da5a8ec4037cdae147dbc93bd59597b0fc6ed189d922bb93f66b38a21a9011ac5a33638234f8
data/.gitignore ADDED
@@ -0,0 +1,15 @@
1
+ /.bundle/
2
+ /.yardoc
3
+ /Gemfile.lock
4
+ /_yardoc/
5
+ /coverage/
6
+ /doc/
7
+ /pkg/
8
+ /spec/reports/
9
+ /tmp/
10
+ *.bundle
11
+ *.so
12
+ *.o
13
+ *.a
14
+ .ycm_extra_conf.py*
15
+ mkmf.log
data/.travis.yml ADDED
@@ -0,0 +1,27 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - ruby-2.0.0-p648
5
+ - ruby-2.1.10
6
+ - ruby-2.1.0
7
+ - ruby-2.2.6
8
+ - ruby-2.3.3
9
+ - ruby-2.4.0
10
+ - ruby-head
11
+
12
+ - jruby-9.1.8.0
13
+ - jruby-head
14
+
15
+ allow_failures:
16
+ - rvm: ruby-head
17
+ - rvm: jruby-head
18
+
19
+ env:
20
+ global:
21
+ - JRUBY_OPTS=--dev
22
+
23
+ before_install: gem install bundler
24
+
25
+ sudo: false
26
+
27
+ cache: bundler
data/CONTRIBUTING.md ADDED
@@ -0,0 +1,15 @@
1
+ # Contributing
2
+
3
+ 1. Fork it ( https://github.com/gsamokovarov/skiptrace/fork )
4
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
5
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
6
+ 4. Push to the branch (`git push origin my-new-feature`)
7
+ 5. Create a new Pull Request
8
+
9
+ ## Etiquette
10
+
11
+ If you want to contribute code, which is not your own or is heavily inspired by
12
+ someone else's code, please give them a warm shoutout in the pull request (or
13
+ the commit message) and the code itself.
14
+
15
+ Of course, don't try to sneak in non MIT compatible code.
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+
3
+ gemspec name: 'skiptrace'
4
+
5
+ # Rubinius 2.2.2 travis tests complain about this one.
6
+ platforms :rbx do
7
+ gem "rubysl-mutex_m"
8
+ gem "rubysl-open3"
9
+ gem "rubysl-singleton"
10
+ end
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2014-2017 Genadi Samokovarov
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,51 @@
1
+ # Skiptrace [![Build Status](https://travis-ci.org/gsamokovarov/bindex.svg?branch=master)](https://travis-ci.org/gsamokovarov/bindex)
2
+
3
+ When Ruby raises an exception, it leaves you a backtrace to help you figure out
4
+ where did the exception originated in. Skiptrace gives you the bindings as well.
5
+ This can help you introspect the state of the Ruby program when at the point
6
+ the exception occurred.
7
+
8
+ ## Usage
9
+
10
+ **Do not** use this gem on production environments. The performance penalty isn't
11
+ worth it anywhere outside of development.
12
+
13
+ ### API
14
+
15
+ Skiptrace defines the following API:
16
+
17
+ #### Exception#bindings
18
+
19
+ Returns all the bindings up to the one in which the exception originated in.
20
+
21
+ #### Skiptrace.current_bindings
22
+
23
+ Returns all of the current Ruby execution state bindings. The first one is the
24
+ current one, the second is the caller one, the third is the caller of the
25
+ caller one and so on.
26
+
27
+ ## Support
28
+
29
+ ### CRuby
30
+
31
+ CRuby 2.0.0 and above is supported.
32
+
33
+ ### JRuby
34
+
35
+ To get the best support, run JRuby in interpreted mode.
36
+
37
+ ```bash
38
+ export JRUBY_OPTS=--dev
39
+ ```
40
+
41
+ Only JRuby 9k is supported.
42
+
43
+ ### Rubinius
44
+
45
+ Internal errors like `ZeroDevisionError` aren't caught.
46
+
47
+ ## Credits
48
+
49
+ Thanks to John Mair for his work on binding_of_caller, which is a huge
50
+ inspiration. Thanks to Charlie Somerville for better_errors where the idea
51
+ comes from. Thanks to Koichi Sasada for the debug inspector API in CRuby.
data/Rakefile ADDED
@@ -0,0 +1,35 @@
1
+ require 'rake/testtask'
2
+ require "rake/clean"
3
+
4
+ CLOBBER.include "pkg"
5
+
6
+ Bundler::GemHelper.install_tasks name: ENV.fetch('GEM_NAME', 'skiptrace')
7
+
8
+ Rake::TestTask.new do |t|
9
+ t.libs << 'test'
10
+ t.test_files = FileList['test/*_test.rb']
11
+ t.verbose = true
12
+ end
13
+
14
+ case RUBY_ENGINE
15
+ when 'ruby'
16
+ require 'rake/extensiontask'
17
+
18
+ Rake::ExtensionTask.new('skiptrace') do |ext|
19
+ ext.name = 'cruby'
20
+ ext.lib_dir = 'lib/skiptrace'
21
+ end
22
+
23
+ task default: [:clean, :compile, :test]
24
+ when 'jruby'
25
+ require 'rake/javaextensiontask'
26
+
27
+ Rake::JavaExtensionTask.new('skiptrace') do |ext|
28
+ ext.name = 'jruby_internals'
29
+ ext.lib_dir = 'lib/skiptrace'
30
+ end
31
+
32
+ task default: [:clean, :compile, :test]
33
+ else
34
+ task default: [:test]
35
+ end
data/bindex.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH << File.expand_path('../lib', __FILE__)
2
+
3
+ require 'skiptrace/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "bindex"
7
+ spec.version = Skiptrace::VERSION
8
+ spec.authors = ["Genadi Samokovarov"]
9
+ spec.email = ["gsamokovarov@gmail.com"]
10
+ spec.extensions = ["ext/skiptrace/extconf.rb"]
11
+ spec.summary = "Bindings for your Ruby exceptions"
12
+ spec.homepage = "https://github.com/gsamokovarov/bindex"
13
+ spec.license = "MIT"
14
+
15
+ spec.required_ruby_version = ">= 2.0.0"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+ spec.extensions = ["ext/skiptrace/extconf.rb"]
22
+
23
+ spec.add_development_dependency "minitest", "~> 5.4"
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rake-compiler"
27
+ end
@@ -0,0 +1,13 @@
1
+ package com.gsamokovarov.skiptrace;
2
+
3
+ import org.jruby.runtime.DynamicScope;
4
+ import org.jruby.runtime.Binding;
5
+ import org.jruby.runtime.Frame;
6
+ import org.jruby.runtime.DynamicScope;
7
+ import org.jruby.runtime.backtrace.BacktraceElement;
8
+
9
+ class BindingBuilder {
10
+ public static Binding build(Frame frame, DynamicScope scope, BacktraceElement element) {
11
+ return new Binding(frame, scope, element.method, element.filename, element.line);
12
+ }
13
+ }
@@ -0,0 +1,54 @@
1
+ package com.gsamokovarov.skiptrace;
2
+
3
+ import org.jruby.runtime.ThreadContext;
4
+ import org.jruby.runtime.DynamicScope;
5
+ import org.jruby.runtime.Binding;
6
+ import org.jruby.runtime.Frame;
7
+ import org.jruby.runtime.DynamicScope;
8
+ import org.jruby.runtime.backtrace.BacktraceElement;
9
+ import java.util.Iterator;
10
+ import java.util.NoSuchElementException;
11
+
12
+ class CurrentBindingsIterator implements Iterator<Binding> {
13
+ private Frame[] frameStack;
14
+ private int frameIndex;
15
+
16
+ private DynamicScope[] scopeStack;
17
+ private int scopeIndex;
18
+
19
+ private BacktraceElement[] backtrace;
20
+ private int backtraceIndex;
21
+
22
+ CurrentBindingsIterator(ThreadContext context) {
23
+ ThreadContextInternals contextInternals = new ThreadContextInternals(context);
24
+
25
+ this.frameStack = contextInternals.getFrameStack();
26
+ this.frameIndex = contextInternals.getFrameIndex();
27
+
28
+ this.scopeStack = contextInternals.getScopeStack();
29
+ this.scopeIndex = contextInternals.getScopeIndex();
30
+
31
+ this.backtrace = contextInternals.getBacktrace();
32
+ this.backtraceIndex = contextInternals.getBacktraceIndex();
33
+ }
34
+
35
+ public boolean hasNext() {
36
+ return frameIndex >= 0 && scopeIndex >= 0 && backtraceIndex >= 0;
37
+ }
38
+
39
+ public Binding next() {
40
+ if (!hasNext()) {
41
+ throw new NoSuchElementException();
42
+ }
43
+
44
+ Frame frame = frameStack[frameIndex--];
45
+ DynamicScope scope = scopeStack[scopeIndex--];
46
+ BacktraceElement element = backtrace[backtraceIndex--];
47
+
48
+ return BindingBuilder.build(frame, scope, element);
49
+ }
50
+
51
+ public void remove() {
52
+ throw new UnsupportedOperationException();
53
+ }
54
+ }
@@ -0,0 +1,49 @@
1
+ package com.gsamokovarov.skiptrace;
2
+
3
+ import org.jruby.Ruby;
4
+ import org.jruby.RubyArray;
5
+ import org.jruby.RubyModule;
6
+ import org.jruby.RubyClass;
7
+ import org.jruby.runtime.ThreadContext;
8
+ import org.jruby.runtime.builtin.IRubyObject;
9
+ import org.jruby.runtime.builtin.InstanceVariables;
10
+ import org.jruby.anno.JRubyMethod;
11
+
12
+ public class JRubyIntegration {
13
+ public static void setup(Ruby runtime) {
14
+ RubyModule skiptrace = runtime.defineModule("Skiptrace");
15
+ skiptrace.defineAnnotatedMethods(SkiptraceMethods.class);
16
+
17
+ RubyClass exception = runtime.getException();
18
+ exception.defineAnnotatedMethods(ExceptionExtensionMethods.class);
19
+
20
+ IRubyObject verbose = runtime.getVerbose();
21
+ try {
22
+ runtime.setVerbose(runtime.getNil());
23
+ runtime.addEventHook(new SetExceptionBindingsEventHook());
24
+ } finally {
25
+ runtime.setVerbose(verbose);
26
+ }
27
+ }
28
+
29
+ public static class SkiptraceMethods {
30
+ @JRubyMethod(name = "current_bindings", meta = true)
31
+ public static IRubyObject currentBindings(ThreadContext context, IRubyObject self) {
32
+ return RubyBindingsCollector.collectCurrentFor(context);
33
+ }
34
+ }
35
+
36
+ public static class ExceptionExtensionMethods {
37
+ @JRubyMethod
38
+ public static IRubyObject bindings(ThreadContext context, IRubyObject self) {
39
+ InstanceVariables instanceVariables = self.getInstanceVariables();
40
+
41
+ IRubyObject bindings = instanceVariables.getInstanceVariable("@bindings");
42
+ if (bindings != null && !bindings.isNil()) {
43
+ return bindings;
44
+ }
45
+
46
+ return RubyArray.newArray(context.getRuntime());
47
+ }
48
+ }
49
+ }
@@ -0,0 +1,34 @@
1
+ package com.gsamokovarov.skiptrace;
2
+
3
+ import org.jruby.runtime.ThreadContext;
4
+ import org.jruby.runtime.Binding;
5
+ import org.jruby.runtime.DynamicScope;
6
+ import org.jruby.runtime.builtin.IRubyObject;
7
+ import org.jruby.RubyBinding;
8
+ import org.jruby.RubyArray;
9
+ import org.jruby.Ruby;
10
+ import java.util.Iterator;
11
+
12
+ public class RubyBindingsCollector {
13
+ private final Ruby runtime;
14
+ private Iterator<Binding> iterator;
15
+
16
+ public static RubyArray collectCurrentFor(ThreadContext context) {
17
+ return new RubyBindingsCollector(context).collectCurrent();
18
+ }
19
+
20
+ private RubyBindingsCollector(ThreadContext context) {
21
+ this.iterator = new CurrentBindingsIterator(context);
22
+ this.runtime = context.getRuntime();
23
+ }
24
+
25
+ private RubyArray collectCurrent() {
26
+ RubyArray bindings = RubyArray.newArray(runtime);
27
+
28
+ while (iterator.hasNext()) {
29
+ bindings.append(((IRubyObject) RubyBinding.newBinding(runtime, iterator.next())));
30
+ }
31
+
32
+ return bindings;
33
+ }
34
+ }
@@ -0,0 +1,24 @@
1
+ package com.gsamokovarov.skiptrace;
2
+
3
+ import org.jruby.runtime.EventHook;
4
+ import org.jruby.runtime.RubyEvent;
5
+ import org.jruby.runtime.ThreadContext;
6
+ import org.jruby.runtime.builtin.IRubyObject;
7
+ import org.jruby.RubyArray;
8
+ import org.jruby.RubyException;
9
+
10
+ public class SetExceptionBindingsEventHook extends EventHook {
11
+ public boolean isInterestedInEvent(RubyEvent event) {
12
+ return event == RubyEvent.RAISE;
13
+ }
14
+
15
+ public void eventHandler(ThreadContext context, String eventName, String file, int line, String name, IRubyObject type) {
16
+ RubyArray bindings = RubyBindingsCollector.collectCurrentFor(context);
17
+ RubyException exception = (RubyException) context.runtime.getGlobalVariables().get("$!");
18
+
19
+ IRubyObject exceptionBindings = exception.getInstanceVariable("@bindings");
20
+ if (exceptionBindings == null || exceptionBindings.isNil()) {
21
+ exception.setInstanceVariable("@bindings", bindings);
22
+ }
23
+ }
24
+ }
@@ -0,0 +1,14 @@
1
+ package com.gsamokovarov.skiptrace;
2
+
3
+ class ThreadContextInterfaceException extends RuntimeException {
4
+ private static final String MESSAGE_TEMPLATE =
5
+ "Expected private field %s in ThreadContext is missing";
6
+
7
+ ThreadContextInterfaceException(String fieldName) {
8
+ super(String.format(MESSAGE_TEMPLATE, fieldName));
9
+ }
10
+
11
+ ThreadContextInterfaceException(String fieldName, Throwable cause) {
12
+ super(String.format(MESSAGE_TEMPLATE, fieldName), cause);
13
+ }
14
+ }
@@ -0,0 +1,54 @@
1
+ package com.gsamokovarov.skiptrace;
2
+
3
+ import java.lang.reflect.Field;
4
+ import org.jruby.runtime.ThreadContext;
5
+ import org.jruby.runtime.DynamicScope;
6
+ import org.jruby.runtime.Frame;
7
+ import org.jruby.runtime.backtrace.BacktraceElement;
8
+ import org.jruby.runtime.builtin.IRubyObject;
9
+
10
+ public class ThreadContextInternals {
11
+ private ThreadContext context;
12
+
13
+ public ThreadContextInternals(ThreadContext context) {
14
+ this.context = context;
15
+ }
16
+
17
+ public Frame[] getFrameStack() {
18
+ return (Frame[]) getPrivateField("frameStack");
19
+ }
20
+
21
+ public int getFrameIndex() {
22
+ return (Integer) getPrivateField("frameIndex");
23
+ }
24
+
25
+ public DynamicScope[] getScopeStack() {
26
+ return (DynamicScope[]) getPrivateField("scopeStack");
27
+ }
28
+
29
+ public int getScopeIndex() {
30
+ return (Integer) getPrivateField("scopeIndex");
31
+ }
32
+
33
+ public BacktraceElement[] getBacktrace() {
34
+ return (BacktraceElement[]) getPrivateField("backtrace");
35
+ }
36
+
37
+ public int getBacktraceIndex() {
38
+ return (Integer) getPrivateField("backtraceIndex");
39
+ }
40
+
41
+ private Object getPrivateField(String fieldName) {
42
+ try {
43
+ Field field = ThreadContext.class.getDeclaredField(fieldName);
44
+
45
+ field.setAccessible(true);
46
+
47
+ return field.get(context);
48
+ } catch (NoSuchFieldException exc) {
49
+ throw new ThreadContextInterfaceException(fieldName, exc);
50
+ } catch (IllegalAccessException exc) {
51
+ throw new ThreadContextInterfaceException(fieldName, exc);
52
+ }
53
+ }
54
+ }
@@ -0,0 +1,80 @@
1
+ #include <ruby.h>
2
+ #include <ruby/debug.h>
3
+
4
+ static VALUE st_mSkiptrace;
5
+ static ID id_bindings;
6
+
7
+ static VALUE
8
+ current_bindings_callback(const rb_debug_inspector_t *context, void *data)
9
+ {
10
+ VALUE locations = rb_debug_inspector_backtrace_locations(context);
11
+ VALUE binding, bindings = rb_ary_new();
12
+ long i, length = RARRAY_LEN(locations);
13
+
14
+ for (i = 0; i < length; i++) {
15
+ binding = rb_debug_inspector_frame_binding_get(context, i);
16
+
17
+ if (!NIL_P(binding)) {
18
+ rb_ary_push(bindings, binding);
19
+ }
20
+ }
21
+
22
+ return bindings;
23
+ }
24
+
25
+ static VALUE
26
+ current_bindings(void)
27
+ {
28
+ return rb_debug_inspector_open(current_bindings_callback, NULL);
29
+ }
30
+
31
+ static void
32
+ set_exception_bindings_callback(VALUE tpval, void *data)
33
+ {
34
+ rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tpval);
35
+ VALUE exception = rb_tracearg_raised_exception(trace_arg);
36
+ VALUE bindings = rb_attr_get(exception, id_bindings);
37
+
38
+ /* Set the bindings, only if they haven't been set already. This may reset
39
+ * the binding during reraise. */
40
+ if (NIL_P(bindings)) {
41
+ rb_ivar_set(exception, id_bindings, current_bindings());
42
+ }
43
+ }
44
+
45
+ static void
46
+ set_exception_bindings_on_raise(void)
47
+ {
48
+ VALUE tpval = rb_tracepoint_new(0, RUBY_EVENT_RAISE, set_exception_bindings_callback, 0);
49
+ rb_tracepoint_enable(tpval);
50
+ }
51
+
52
+ static VALUE
53
+ st_current_bindings(VALUE self)
54
+ {
55
+ return current_bindings();
56
+ }
57
+
58
+ static VALUE
59
+ st_exc_bindings(VALUE self)
60
+ {
61
+ VALUE bindings = rb_attr_get(self, id_bindings);
62
+
63
+ if (NIL_P(bindings)) {
64
+ bindings = rb_ary_new();
65
+ }
66
+
67
+ return bindings;
68
+ }
69
+
70
+ void
71
+ Init_cruby(void)
72
+ {
73
+ st_mSkiptrace = rb_define_module("Skiptrace");
74
+ id_bindings = rb_intern("bindings");
75
+
76
+ rb_define_singleton_method(st_mSkiptrace, "current_bindings", st_current_bindings, 0);
77
+ rb_define_method(rb_eException, "bindings", st_exc_bindings, 0);
78
+
79
+ set_exception_bindings_on_raise();
80
+ }
@@ -0,0 +1,15 @@
1
+ case RUBY_ENGINE
2
+ when "ruby"
3
+ require "mkmf"
4
+
5
+ $CFLAGS << " -Wall"
6
+ $CFLAGS << " -g3 -O0" if ENV["DEBUG"]
7
+
8
+ create_makefile("skiptrace/cruby")
9
+ else
10
+ IO.write(File.expand_path("../Makefile", __FILE__), <<-END)
11
+ all install static install-so install-rb: Makefile
12
+ .PHONY: all install static install-so install-rb
13
+ .PHONY: clean clean-so clean-static clean-rb
14
+ END
15
+ end
data/lib/bindex.rb ADDED
@@ -0,0 +1,4 @@
1
+ require_relative "skiptrace"
2
+
3
+ # Keep backwards compatibility with the previous name.
4
+ Bindex = Skiptrace
@@ -0,0 +1,5 @@
1
+ require 'skiptrace/jruby_internals'
2
+
3
+ java_import com.gsamokovarov.skiptrace.JRubyIntegration
4
+
5
+ JRubyIntegration.setup(JRuby.runtime)
Binary file
@@ -0,0 +1,66 @@
1
+ module Skiptrace
2
+ module Rubinius
3
+ # Filters internal Rubinius locations.
4
+ #
5
+ # There are a couple of reasons why we wanna filter out the locations.
6
+ #
7
+ # * ::Kernel.raise, is implemented in Ruby for Rubinius. We don't wanna
8
+ # have the frame for it to align with the CRuby and JRuby implementations.
9
+ #
10
+ # * For internal methods location variables can be nil. We can't create a
11
+ # bindings for them.
12
+ #
13
+ # * Bindings from the current file are considered internal and ignored.
14
+ #
15
+ # We do that all that so we can align the bindings with the backtraces
16
+ # entries.
17
+ class InternalLocationFilter
18
+ def initialize(locations)
19
+ @locations = locations
20
+ end
21
+
22
+ def filter
23
+ @locations.reject do |location|
24
+ location.file.start_with?('kernel/delta/kernel.rb') ||
25
+ location.file == __FILE__ ||
26
+ location.variables.nil?
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+
33
+ # Gets the current bindings for all available Ruby frames.
34
+ #
35
+ # Filters the internal Rubinius and Skiptrace frames.
36
+ def Skiptrace.current_bindings
37
+ locations = ::Rubinius::VM.backtrace(1, true)
38
+
39
+ Skiptrace::Rubinius::InternalLocationFilter.new(locations).filter.map do |location|
40
+ Binding.setup(
41
+ location.variables,
42
+ location.variables.method,
43
+ location.constant_scope,
44
+ location.variables.self,
45
+ location
46
+ )
47
+ end
48
+ end
49
+
50
+ ::Exception.class_eval do
51
+ def bindings
52
+ @bindings || []
53
+ end
54
+ end
55
+
56
+ ::Rubinius.singleton_class.class_eval do
57
+ raise_exception = instance_method(:raise_exception)
58
+
59
+ define_method(:raise_exception) do |exc|
60
+ if exc.bindings.empty?
61
+ exc.instance_variable_set(:@bindings, Skiptrace.current_bindings)
62
+ end
63
+
64
+ raise_exception.bind(self).call(exc)
65
+ end
66
+ end
@@ -0,0 +1,3 @@
1
+ module Skiptrace
2
+ VERSION = "0.6.0"
3
+ end
data/lib/skiptrace.rb ADDED
@@ -0,0 +1,11 @@
1
+ case RUBY_ENGINE
2
+ when 'rbx'
3
+ require 'skiptrace/rubinius'
4
+ when 'jruby'
5
+ require 'skiptrace/jruby'
6
+ when 'ruby'
7
+ require 'skiptrace/cruby'
8
+ end
9
+
10
+ require "skiptrace/version"
11
+
data/skiptrace.gemspec ADDED
@@ -0,0 +1,27 @@
1
+ $LOAD_PATH << File.expand_path('../lib', __FILE__)
2
+
3
+ require 'skiptrace/version'
4
+
5
+ Gem::Specification.new do |spec|
6
+ spec.name = "skiptrace"
7
+ spec.version = Skiptrace::VERSION
8
+ spec.authors = ["Genadi Samokovarov"]
9
+ spec.email = ["gsamokovarov@gmail.com"]
10
+ spec.extensions = ["ext/skiptrace/extconf.rb"]
11
+ spec.summary = "Bindings for your Ruby exceptions"
12
+ spec.homepage = "https://github.com/gsamokovarov/skiptrace"
13
+ spec.license = "MIT"
14
+
15
+ spec.required_ruby_version = ">= 2.0.0"
16
+
17
+ spec.files = `git ls-files -z`.split("\x0")
18
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
19
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
20
+ spec.require_paths = ["lib"]
21
+ spec.extensions = ["ext/skiptrace/extconf.rb"]
22
+
23
+ spec.add_development_dependency "minitest", "~> 5.4"
24
+ spec.add_development_dependency "bundler"
25
+ spec.add_development_dependency "rake"
26
+ spec.add_development_dependency "rake-compiler"
27
+ end
@@ -0,0 +1,7 @@
1
+ require 'test_helper'
2
+
3
+ class CurrentBindingsTest < BaseTest
4
+ test 'first binding returned is the current one' do
5
+ assert_equal __LINE__, Skiptrace.current_bindings.first.eval('__LINE__')
6
+ end
7
+ end
@@ -0,0 +1,51 @@
1
+ require 'test_helper'
2
+
3
+ class ExceptionTest < BaseTest
4
+ test 'bindings returns all the bindings of where the error originated' do
5
+ exc = FlatFixture.new.call
6
+
7
+ assert_equal 3, exc.bindings.first.eval('__LINE__')
8
+ end
9
+
10
+ test 'bindings returns all the bindings of where a custom error originate' do
11
+ exc = CustomErrorFixture.new.call
12
+
13
+ assert_equal 5, exc.bindings.first.eval('__LINE__')
14
+ end
15
+
16
+ test 'bindings goes down the_stack' do
17
+ exc = BasicNestedFixture.new.call
18
+
19
+ assert_equal 11, exc.bindings.first.eval('__LINE__')
20
+ end
21
+
22
+ test 'bindings inside_of_an_eval' do
23
+ exc = EvalNestedFixture.new.call
24
+
25
+ assert_equal 11, exc.bindings.first.eval('__LINE__')
26
+ end
27
+
28
+ test "re-raising doesn't lose bindings information" do
29
+ exc = ReraisedFixture.new.call
30
+
31
+ assert_equal 3, exc.bindings.first.eval('__LINE__')
32
+ end
33
+
34
+ test 'bindings is_empty_when_exception_is_still_not_raised' do
35
+ exc = RuntimeError.new
36
+
37
+ assert_equal [], exc.bindings
38
+ end
39
+
40
+ test 'bindings is_empty_when_set_backtrace_is_badly_called' do
41
+ exc = RuntimeError.new
42
+
43
+ # Exception#set_backtrace expects a string or array of strings. If the
44
+ # input isn't like this it will raise a TypeError.
45
+ assert_raises(TypeError) do
46
+ exc.set_backtrace([nil])
47
+ end
48
+
49
+ assert_equal [], exc.bindings
50
+ end
51
+ end
@@ -0,0 +1,13 @@
1
+ class BasicNestedFixture
2
+ def call
3
+ raise_an_error
4
+ rescue => exc
5
+ exc
6
+ end
7
+
8
+ private
9
+
10
+ def raise_an_error
11
+ raise
12
+ end
13
+ end
@@ -0,0 +1,9 @@
1
+ class CustomErrorFixture
2
+ Error = Class.new(StandardError)
3
+
4
+ def call
5
+ raise Error
6
+ rescue => exc
7
+ exc
8
+ end
9
+ end
@@ -0,0 +1,13 @@
1
+ class EvalNestedFixture
2
+ def call
3
+ tap { raise_an_error_in_eval }
4
+ rescue => exc
5
+ exc
6
+ end
7
+
8
+ private
9
+
10
+ def raise_an_error_in_eval
11
+ eval 'raise', binding, __FILE__, __LINE__
12
+ end
13
+ end
@@ -0,0 +1,7 @@
1
+ class FlatFixture
2
+ def call
3
+ raise
4
+ rescue => exc
5
+ exc
6
+ end
7
+ end
@@ -0,0 +1,19 @@
1
+ class ReraisedFixture
2
+ def call
3
+ reraise_an_error
4
+ rescue => exc
5
+ exc
6
+ end
7
+
8
+ private
9
+
10
+ def raise_an_error_in_eval
11
+ method_that_raises
12
+ rescue => exc
13
+ raise exc
14
+ end
15
+
16
+ def method_that_raises
17
+ raise
18
+ end
19
+ end
@@ -0,0 +1,17 @@
1
+ $LOAD_PATH << File.expand_path('../lib', __FILE__)
2
+
3
+ require 'minitest/autorun'
4
+ require 'skiptrace'
5
+
6
+ current_directory = File.dirname(File.expand_path(__FILE__))
7
+
8
+ # Fixtures are plain classes that respond to #call.
9
+ Dir["#{current_directory}/fixtures/**/*.rb"].each do |fixture|
10
+ require fixture
11
+ end
12
+
13
+ class BaseTest < MiniTest::Test
14
+ def self.test(name, &block)
15
+ define_method("test_#{name}", &block)
16
+ end
17
+ end
metadata ADDED
@@ -0,0 +1,140 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: skiptrace
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.6.0
5
+ platform: ruby
6
+ authors:
7
+ - Genadi Samokovarov
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2019-03-29 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: minitest
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - "~>"
18
+ - !ruby/object:Gem::Version
19
+ version: '5.4'
20
+ type: :development
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - "~>"
25
+ - !ruby/object:Gem::Version
26
+ version: '5.4'
27
+ - !ruby/object:Gem::Dependency
28
+ name: bundler
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - ">="
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :development
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - ">="
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ - !ruby/object:Gem::Dependency
42
+ name: rake
43
+ requirement: !ruby/object:Gem::Requirement
44
+ requirements:
45
+ - - ">="
46
+ - !ruby/object:Gem::Version
47
+ version: '0'
48
+ type: :development
49
+ prerelease: false
50
+ version_requirements: !ruby/object:Gem::Requirement
51
+ requirements:
52
+ - - ">="
53
+ - !ruby/object:Gem::Version
54
+ version: '0'
55
+ - !ruby/object:Gem::Dependency
56
+ name: rake-compiler
57
+ requirement: !ruby/object:Gem::Requirement
58
+ requirements:
59
+ - - ">="
60
+ - !ruby/object:Gem::Version
61
+ version: '0'
62
+ type: :development
63
+ prerelease: false
64
+ version_requirements: !ruby/object:Gem::Requirement
65
+ requirements:
66
+ - - ">="
67
+ - !ruby/object:Gem::Version
68
+ version: '0'
69
+ description:
70
+ email:
71
+ - gsamokovarov@gmail.com
72
+ executables: []
73
+ extensions:
74
+ - ext/skiptrace/extconf.rb
75
+ extra_rdoc_files: []
76
+ files:
77
+ - ".gitignore"
78
+ - ".travis.yml"
79
+ - CONTRIBUTING.md
80
+ - Gemfile
81
+ - LICENSE.txt
82
+ - README.md
83
+ - Rakefile
84
+ - bindex.gemspec
85
+ - ext/skiptrace/com/gsamokovarov/skiptrace/BindingBuilder.java
86
+ - ext/skiptrace/com/gsamokovarov/skiptrace/CurrentBindingsIterator.java
87
+ - ext/skiptrace/com/gsamokovarov/skiptrace/JRubyIntegration.java
88
+ - ext/skiptrace/com/gsamokovarov/skiptrace/RubyBindingsCollector.java
89
+ - ext/skiptrace/com/gsamokovarov/skiptrace/SetExceptionBindingsEventHook.java
90
+ - ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInterfaceException.java
91
+ - ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInternals.java
92
+ - ext/skiptrace/cruby.c
93
+ - ext/skiptrace/extconf.rb
94
+ - lib/bindex.rb
95
+ - lib/skiptrace.rb
96
+ - lib/skiptrace/jruby.rb
97
+ - lib/skiptrace/jruby_internals.jar
98
+ - lib/skiptrace/rubinius.rb
99
+ - lib/skiptrace/version.rb
100
+ - skiptrace.gemspec
101
+ - test/current_bindings_test.rb
102
+ - test/exception_test.rb
103
+ - test/fixtures/basic_nested_fixture.rb
104
+ - test/fixtures/custom_error_fixture.rb
105
+ - test/fixtures/eval_nested_fixture.rb
106
+ - test/fixtures/flat_fixture.rb
107
+ - test/fixtures/reraised_fixture.rb
108
+ - test/test_helper.rb
109
+ homepage: https://github.com/gsamokovarov/skiptrace
110
+ licenses:
111
+ - MIT
112
+ metadata: {}
113
+ post_install_message:
114
+ rdoc_options: []
115
+ require_paths:
116
+ - lib
117
+ required_ruby_version: !ruby/object:Gem::Requirement
118
+ requirements:
119
+ - - ">="
120
+ - !ruby/object:Gem::Version
121
+ version: 2.0.0
122
+ required_rubygems_version: !ruby/object:Gem::Requirement
123
+ requirements:
124
+ - - ">="
125
+ - !ruby/object:Gem::Version
126
+ version: '0'
127
+ requirements: []
128
+ rubygems_version: 3.0.3
129
+ signing_key:
130
+ specification_version: 4
131
+ summary: Bindings for your Ruby exceptions
132
+ test_files:
133
+ - test/current_bindings_test.rb
134
+ - test/exception_test.rb
135
+ - test/fixtures/basic_nested_fixture.rb
136
+ - test/fixtures/custom_error_fixture.rb
137
+ - test/fixtures/eval_nested_fixture.rb
138
+ - test/fixtures/flat_fixture.rb
139
+ - test/fixtures/reraised_fixture.rb
140
+ - test/test_helper.rb