ed-precompiled_bindex 0.8.1

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.
Files changed (38) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +15 -0
  3. data/.travis.yml +18 -0
  4. data/CONTRIBUTING.md +15 -0
  5. data/Gemfile +10 -0
  6. data/LICENSE.txt +22 -0
  7. data/README.md +56 -0
  8. data/Rakefile +37 -0
  9. data/bindex.gemspec +27 -0
  10. data/ext/skiptrace/com/gsamokovarov/skiptrace/BindingBuilder.java +13 -0
  11. data/ext/skiptrace/com/gsamokovarov/skiptrace/CurrentBindingsIterator.java +54 -0
  12. data/ext/skiptrace/com/gsamokovarov/skiptrace/JRubyIntegration.java +49 -0
  13. data/ext/skiptrace/com/gsamokovarov/skiptrace/RubyBindingsCollector.java +34 -0
  14. data/ext/skiptrace/com/gsamokovarov/skiptrace/SetExceptionBindingsEventHook.java +24 -0
  15. data/ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInterfaceException.java +14 -0
  16. data/ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInternals.java +54 -0
  17. data/ext/skiptrace/cruby.c +80 -0
  18. data/ext/skiptrace/extconf.rb +15 -0
  19. data/lib/bindex.rb +4 -0
  20. data/lib/skiptrace/binding_ext.rb +5 -0
  21. data/lib/skiptrace/binding_locations.rb +34 -0
  22. data/lib/skiptrace/exception_ext.rb +5 -0
  23. data/lib/skiptrace/internal/jruby.rb +7 -0
  24. data/lib/skiptrace/internal/jruby_internals.jar +0 -0
  25. data/lib/skiptrace/internal/rubinius.rb +66 -0
  26. data/lib/skiptrace/location.rb +34 -0
  27. data/lib/skiptrace/version.rb +3 -0
  28. data/lib/skiptrace.rb +19 -0
  29. data/test/fixtures/basic_nested_fixture.rb +17 -0
  30. data/test/fixtures/custom_error_fixture.rb +11 -0
  31. data/test/fixtures/eval_nested_fixture.rb +17 -0
  32. data/test/fixtures/flat_fixture.rb +9 -0
  33. data/test/fixtures/reraised_fixture.rb +23 -0
  34. data/test/skiptrace/current_bindings_test.rb +11 -0
  35. data/test/skiptrace/exception_test.rb +67 -0
  36. data/test/skiptrace/location_test.rb +18 -0
  37. data/test/test_helper.rb +19 -0
  38. metadata +141 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: c2dad15030466ddbaa2ef4febddc0388db5d4c0a13584c1595fd3bad76bcbed8
4
+ data.tar.gz: 580698c6ad91d39e0c542615a208d33230d92a083db9b2c613abbded01794544
5
+ SHA512:
6
+ metadata.gz: 174a03db11f91ee12f742d44b09fc28f608d22dc659d315cbeeb03f99c82c13523de62f4a3a031b85fed3769e733940a3b12102908c7398e872c28820a2a6a60
7
+ data.tar.gz: a542158bb41607ce71def8964df9404c2fc1dddb3213286842ac98b03a03ea50f10acec6e87c4f319c4975c53f893009fa052a9e1be22a35c0f9011f3cee10fe
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,18 @@
1
+ language: ruby
2
+
3
+ rvm:
4
+ - ruby-2.5
5
+ - ruby-2.6
6
+ - ruby-head
7
+
8
+ allow_failures:
9
+ - rvm: ruby-head
10
+ - rvm: jruby-head
11
+
12
+ env:
13
+ global:
14
+ - JRUBY_OPTS=--dev
15
+
16
+ before_install: gem install bundler
17
+
18
+ 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
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,56 @@
1
+ # Skiptrace [![Build Status](https://travis-ci.org/gsamokovarov/skiptrace.svg?branch=master)](https://travis-ci.org/gsamokovarov/skiptrace)
2
+
3
+ When Ruby raises an exception, it leaves a backtrace to help you figure out
4
+ where the exception originated in. Skiptrace gives you the bindings as well.
5
+ This can help you introspect the state of the Ruby program when the exception
6
+ 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
+ #### Exception#binding_locations
22
+
23
+ Returns an array of `Skiptrace::Location` objects that are like [`Thread::Backtrace::Location`](https://ruby-doc.org/core-2.6.3/Thread/Backtrace/Location.html)
24
+ but also carry a `Binding` object for that frame through the `#binding` method.
25
+
26
+ #### Skiptrace.current_bindings
27
+
28
+ Returns all of the current Ruby execution state bindings. The first one is the
29
+ current one, the second is the caller one, the third is the caller of the
30
+ caller one and so on.
31
+
32
+ ## Support
33
+
34
+ ### CRuby
35
+
36
+ CRuby 2.5.0 and above is supported.
37
+
38
+ ### JRuby
39
+
40
+ To get the best support, run JRuby in interpreted mode.
41
+
42
+ ```bash
43
+ export JRUBY_OPTS=--dev
44
+ ```
45
+
46
+ Only JRuby 9k is supported.
47
+
48
+ ### Rubinius
49
+
50
+ Internal errors like `ZeroDevisionError` aren't caught.
51
+
52
+ ## Credits
53
+
54
+ Thanks to John Mair for his work on binding_of_caller, which is a huge
55
+ inspiration. Thanks to Charlie Somerville for better_errors where the idea
56
+ comes from. Thanks to Koichi Sasada for the debug inspector API in CRuby.
data/Rakefile ADDED
@@ -0,0 +1,37 @@
1
+ require 'rake/testtask'
2
+ require "rake/clean"
3
+
4
+ CLOBBER.include "pkg"
5
+
6
+ require "bundler/gem_tasks"
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/internal'
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/internal'
30
+ ext.source_version = '1.8'
31
+ ext.target_version = '1.8'
32
+ end
33
+
34
+ task default: [:clean, :compile, :test]
35
+ else
36
+ task default: [:test]
37
+ 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 = "ed-precompiled_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/internal/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
+ class Binding
2
+ def source_location
3
+ eval '[__FILE__, __LINE__.to_i]'
4
+ end unless method_defined?(:source_location)
5
+ end
@@ -0,0 +1,34 @@
1
+ module Skiptrace
2
+ class BindingLocations < BasicObject
3
+ def initialize(locations, bindings)
4
+ @locations = locations
5
+ @bindings = bindings
6
+ @cached_locations = {}
7
+ end
8
+
9
+ private
10
+
11
+ def cached_location(location)
12
+ @cached_locations[location.to_s] ||= Location.new(location, guess_binding_around(location))
13
+ end
14
+
15
+ def guess_binding_around(location)
16
+ location && @bindings.find do |binding|
17
+ binding.source_location == [location.path, location.lineno]
18
+ end
19
+ end
20
+
21
+ def method_missing(name, *args, &block)
22
+ case maybe_location = @locations.public_send(name, *args, &block)
23
+ when ::Thread::Backtrace::Location
24
+ cached_location(maybe_location)
25
+ else
26
+ maybe_location
27
+ end
28
+ end
29
+
30
+ def respond_to_missing?(name, include_all = false)
31
+ @locations.respond_to?(name, include_all)
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,5 @@
1
+ class Exception
2
+ def binding_locations
3
+ @binding_locations ||= Skiptrace::BindingLocations.new(backtrace_locations, bindings)
4
+ end
5
+ end
@@ -0,0 +1,7 @@
1
+ require 'skiptrace/internal/jruby_internals'
2
+
3
+ module Skiptrace
4
+ java_import com.gsamokovarov.skiptrace.JRubyIntegration
5
+
6
+ JRubyIntegration.setup(JRuby.runtime)
7
+ end
@@ -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,34 @@
1
+ module Skiptrace
2
+ class Location
3
+ attr_reader :binding
4
+
5
+ def initialize(location, binding)
6
+ @location = location
7
+ @binding = binding
8
+ end
9
+
10
+ def absolute_path
11
+ @location.absolute_path
12
+ end
13
+
14
+ def base_label
15
+ @location.base_label
16
+ end
17
+
18
+ def inspect
19
+ @location.inspect
20
+ end
21
+
22
+ def label
23
+ @location.label
24
+ end
25
+
26
+ def lineno
27
+ @location.lineno
28
+ end
29
+
30
+ def to_s
31
+ @location.to_s
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,3 @@
1
+ module Skiptrace
2
+ VERSION = "0.8.1"
3
+ end
data/lib/skiptrace.rb ADDED
@@ -0,0 +1,19 @@
1
+ case RUBY_ENGINE
2
+ when 'rbx'
3
+ require 'skiptrace/internal/rubinius'
4
+ when 'jruby'
5
+ require 'skiptrace/internal/jruby'
6
+ when 'ruby'
7
+ begin
8
+ ruby_version = /(\d+\.\d+)/.match(::RUBY_VERSION)
9
+ require "skiptrace/internal/#{ruby_version}/cruby"
10
+ rescue LoadError
11
+ require 'skiptrace/internal/cruby'
12
+ end
13
+ end
14
+
15
+ require 'skiptrace/location'
16
+ require 'skiptrace/binding_locations'
17
+ require 'skiptrace/binding_ext'
18
+ require 'skiptrace/exception_ext'
19
+ require 'skiptrace/version'
@@ -0,0 +1,17 @@
1
+ module Skiptrace
2
+ module BasicNestedFixture
3
+ extend self
4
+
5
+ def call
6
+ raise_an_error
7
+ rescue => exc
8
+ exc
9
+ end
10
+
11
+ private
12
+
13
+ def raise_an_error
14
+ raise
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,11 @@
1
+ module Skiptrace
2
+ module CustomErrorFixture
3
+ Error = Class.new(StandardError)
4
+
5
+ def self.call
6
+ raise Error
7
+ rescue => exc
8
+ exc
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,17 @@
1
+ module Skiptrace
2
+ module EvalNestedFixture
3
+ extend self
4
+
5
+ def call
6
+ tap { raise_an_error_in_eval }
7
+ rescue => exc
8
+ exc
9
+ end
10
+
11
+ private
12
+
13
+ def raise_an_error_in_eval
14
+ eval 'raise', binding, __FILE__, __LINE__
15
+ end
16
+ end
17
+ end
@@ -0,0 +1,9 @@
1
+ module Skiptrace
2
+ module FlatFixture
3
+ def self.call
4
+ raise
5
+ rescue => exc
6
+ exc
7
+ end
8
+ end
9
+ end
@@ -0,0 +1,23 @@
1
+ module Skiptrace
2
+ module ReraisedFixture
3
+ extend self
4
+
5
+ def call
6
+ reraise_an_error
7
+ rescue => exc
8
+ exc
9
+ end
10
+
11
+ private
12
+
13
+ def raise_an_error_in_eval
14
+ method_that_raises
15
+ rescue => exc
16
+ raise exc
17
+ end
18
+
19
+ def method_that_raises
20
+ raise
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,11 @@
1
+ require 'test_helper'
2
+
3
+ module Skiptrace
4
+ class CurrentBindingsTest < Test
5
+ test 'first binding returned is the current one' do
6
+ _, lineno = Skiptrace.current_bindings.first.source_location
7
+
8
+ assert_equal __LINE__ - 2, lineno
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,67 @@
1
+ require 'test_helper'
2
+
3
+ module Skiptrace
4
+ class ExceptionTest < Test
5
+ test 'bindings returns all the bindings of where the error originated' do
6
+ exc = FlatFixture.()
7
+
8
+ assert_equal 4, exc.bindings.first.source_location.last
9
+ end
10
+
11
+ test 'bindings returns all the bindings of where a custom error originate' do
12
+ exc = CustomErrorFixture.()
13
+
14
+ assert_equal 6, exc.bindings.first.source_location.last
15
+ end
16
+
17
+ test 'bindings goes down the stack' do
18
+ exc = BasicNestedFixture.()
19
+
20
+ assert_equal 14, exc.bindings.first.source_location.last
21
+ end
22
+
23
+ test 'bindings inside of an eval' do
24
+ exc = EvalNestedFixture.()
25
+
26
+ assert_equal 14, exc.bindings.first.source_location.last
27
+ end
28
+
29
+ test "re-raising doesn't lose bindings information" do
30
+ exc = ReraisedFixture.()
31
+
32
+ assert_equal 6, exc.bindings.first.source_location.last
33
+ end
34
+
35
+ test 'bindings is empty when exception is still not raised' do
36
+ exc = RuntimeError.new
37
+
38
+ assert_equal [], exc.bindings
39
+ end
40
+
41
+ test 'bindings is empty when set backtrace is badly called' do
42
+ exc = RuntimeError.new
43
+
44
+ # Exception#set_backtrace expects a string or array of strings. If the
45
+ # input isn't like this it will raise a TypeError.
46
+ assert_raises(TypeError) do
47
+ exc.set_backtrace([nil])
48
+ end
49
+
50
+ assert_equal [], exc.bindings
51
+ end
52
+
53
+ test 'binding_locations maps closely to backtrace_locations' do
54
+ exc = FlatFixture.()
55
+
56
+ exc.binding_locations.first.tap do |location|
57
+ assert_equal 4, location.lineno
58
+ assert_equal exc, location.binding.eval('exc')
59
+ end
60
+
61
+ exc.binding_locations[1].tap do |location|
62
+ assert_equal 54, location.lineno
63
+ assert_equal exc, location.binding.eval('exc')
64
+ end
65
+ end
66
+ end
67
+ end
@@ -0,0 +1,18 @@
1
+ require 'test_helper'
2
+
3
+ module Skiptrace
4
+ class LocationTest < Test
5
+ test 'behaves like Thread::Backtrace::Location' do
6
+ native_location = caller_locations.first
7
+ location = Skiptrace::Location.new(native_location, binding)
8
+
9
+ assert_equal native_location.absolute_path, location.absolute_path
10
+ assert_equal native_location.base_label, location.base_label
11
+ assert_equal native_location.inspect, location.inspect
12
+ assert_equal native_location.label, location.label
13
+ assert_equal native_location.to_s, location.to_s
14
+
15
+ assert_equal [__FILE__, __LINE__ - 8], location.binding.source_location
16
+ end
17
+ end
18
+ end
@@ -0,0 +1,19 @@
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
+ module Skiptrace
14
+ class Test < Minitest::Test
15
+ def self.test(name, &block)
16
+ define_method("test_#{name}", &block)
17
+ end
18
+ end
19
+ end
metadata ADDED
@@ -0,0 +1,141 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: ed-precompiled_bindex
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.8.1
5
+ platform: ruby
6
+ authors:
7
+ - Genadi Samokovarov
8
+ bindir: bin
9
+ cert_chain: []
10
+ date: 1980-01-02 00:00:00.000000000 Z
11
+ dependencies:
12
+ - !ruby/object:Gem::Dependency
13
+ name: minitest
14
+ requirement: !ruby/object:Gem::Requirement
15
+ requirements:
16
+ - - "~>"
17
+ - !ruby/object:Gem::Version
18
+ version: '5.4'
19
+ type: :development
20
+ prerelease: false
21
+ version_requirements: !ruby/object:Gem::Requirement
22
+ requirements:
23
+ - - "~>"
24
+ - !ruby/object:Gem::Version
25
+ version: '5.4'
26
+ - !ruby/object:Gem::Dependency
27
+ name: bundler
28
+ requirement: !ruby/object:Gem::Requirement
29
+ requirements:
30
+ - - ">="
31
+ - !ruby/object:Gem::Version
32
+ version: '0'
33
+ type: :development
34
+ prerelease: false
35
+ version_requirements: !ruby/object:Gem::Requirement
36
+ requirements:
37
+ - - ">="
38
+ - !ruby/object:Gem::Version
39
+ version: '0'
40
+ - !ruby/object:Gem::Dependency
41
+ name: rake
42
+ requirement: !ruby/object:Gem::Requirement
43
+ requirements:
44
+ - - ">="
45
+ - !ruby/object:Gem::Version
46
+ version: '0'
47
+ type: :development
48
+ prerelease: false
49
+ version_requirements: !ruby/object:Gem::Requirement
50
+ requirements:
51
+ - - ">="
52
+ - !ruby/object:Gem::Version
53
+ version: '0'
54
+ - !ruby/object:Gem::Dependency
55
+ name: rake-compiler
56
+ requirement: !ruby/object:Gem::Requirement
57
+ requirements:
58
+ - - ">="
59
+ - !ruby/object:Gem::Version
60
+ version: '0'
61
+ type: :development
62
+ prerelease: false
63
+ version_requirements: !ruby/object:Gem::Requirement
64
+ requirements:
65
+ - - ">="
66
+ - !ruby/object:Gem::Version
67
+ version: '0'
68
+ email:
69
+ - gsamokovarov@gmail.com
70
+ executables: []
71
+ extensions:
72
+ - ext/skiptrace/extconf.rb
73
+ extra_rdoc_files: []
74
+ files:
75
+ - ".gitignore"
76
+ - ".travis.yml"
77
+ - CONTRIBUTING.md
78
+ - Gemfile
79
+ - LICENSE.txt
80
+ - README.md
81
+ - Rakefile
82
+ - bindex.gemspec
83
+ - ext/skiptrace/com/gsamokovarov/skiptrace/BindingBuilder.java
84
+ - ext/skiptrace/com/gsamokovarov/skiptrace/CurrentBindingsIterator.java
85
+ - ext/skiptrace/com/gsamokovarov/skiptrace/JRubyIntegration.java
86
+ - ext/skiptrace/com/gsamokovarov/skiptrace/RubyBindingsCollector.java
87
+ - ext/skiptrace/com/gsamokovarov/skiptrace/SetExceptionBindingsEventHook.java
88
+ - ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInterfaceException.java
89
+ - ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInternals.java
90
+ - ext/skiptrace/cruby.c
91
+ - ext/skiptrace/extconf.rb
92
+ - lib/bindex.rb
93
+ - lib/skiptrace.rb
94
+ - lib/skiptrace/binding_ext.rb
95
+ - lib/skiptrace/binding_locations.rb
96
+ - lib/skiptrace/exception_ext.rb
97
+ - lib/skiptrace/internal/jruby.rb
98
+ - lib/skiptrace/internal/jruby_internals.jar
99
+ - lib/skiptrace/internal/rubinius.rb
100
+ - lib/skiptrace/location.rb
101
+ - lib/skiptrace/version.rb
102
+ - test/fixtures/basic_nested_fixture.rb
103
+ - test/fixtures/custom_error_fixture.rb
104
+ - test/fixtures/eval_nested_fixture.rb
105
+ - test/fixtures/flat_fixture.rb
106
+ - test/fixtures/reraised_fixture.rb
107
+ - test/skiptrace/current_bindings_test.rb
108
+ - test/skiptrace/exception_test.rb
109
+ - test/skiptrace/location_test.rb
110
+ - test/test_helper.rb
111
+ homepage: https://github.com/gsamokovarov/bindex
112
+ licenses:
113
+ - MIT
114
+ metadata: {}
115
+ rdoc_options: []
116
+ require_paths:
117
+ - lib
118
+ required_ruby_version: !ruby/object:Gem::Requirement
119
+ requirements:
120
+ - - ">="
121
+ - !ruby/object:Gem::Version
122
+ version: 2.0.0
123
+ required_rubygems_version: !ruby/object:Gem::Requirement
124
+ requirements:
125
+ - - ">="
126
+ - !ruby/object:Gem::Version
127
+ version: '0'
128
+ requirements: []
129
+ rubygems_version: 3.6.7
130
+ specification_version: 4
131
+ summary: Bindings for your Ruby exceptions
132
+ test_files:
133
+ - test/fixtures/basic_nested_fixture.rb
134
+ - test/fixtures/custom_error_fixture.rb
135
+ - test/fixtures/eval_nested_fixture.rb
136
+ - test/fixtures/flat_fixture.rb
137
+ - test/fixtures/reraised_fixture.rb
138
+ - test/skiptrace/current_bindings_test.rb
139
+ - test/skiptrace/exception_test.rb
140
+ - test/skiptrace/location_test.rb
141
+ - test/test_helper.rb