skiptrace 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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