skiptrace 0.6.0
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +15 -0
- data/.travis.yml +27 -0
- data/CONTRIBUTING.md +15 -0
- data/Gemfile +10 -0
- data/LICENSE.txt +22 -0
- data/README.md +51 -0
- data/Rakefile +35 -0
- data/bindex.gemspec +27 -0
- data/ext/skiptrace/com/gsamokovarov/skiptrace/BindingBuilder.java +13 -0
- data/ext/skiptrace/com/gsamokovarov/skiptrace/CurrentBindingsIterator.java +54 -0
- data/ext/skiptrace/com/gsamokovarov/skiptrace/JRubyIntegration.java +49 -0
- data/ext/skiptrace/com/gsamokovarov/skiptrace/RubyBindingsCollector.java +34 -0
- data/ext/skiptrace/com/gsamokovarov/skiptrace/SetExceptionBindingsEventHook.java +24 -0
- data/ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInterfaceException.java +14 -0
- data/ext/skiptrace/com/gsamokovarov/skiptrace/ThreadContextInternals.java +54 -0
- data/ext/skiptrace/cruby.c +80 -0
- data/ext/skiptrace/extconf.rb +15 -0
- data/lib/bindex.rb +4 -0
- data/lib/skiptrace/jruby.rb +5 -0
- data/lib/skiptrace/jruby_internals.jar +0 -0
- data/lib/skiptrace/rubinius.rb +66 -0
- data/lib/skiptrace/version.rb +3 -0
- data/lib/skiptrace.rb +11 -0
- data/skiptrace.gemspec +27 -0
- data/test/current_bindings_test.rb +7 -0
- data/test/exception_test.rb +51 -0
- data/test/fixtures/basic_nested_fixture.rb +13 -0
- data/test/fixtures/custom_error_fixture.rb +9 -0
- data/test/fixtures/eval_nested_fixture.rb +13 -0
- data/test/fixtures/flat_fixture.rb +7 -0
- data/test/fixtures/reraised_fixture.rb +19 -0
- data/test/test_helper.rb +17 -0
- metadata +140 -0
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
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
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
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
|
data/lib/skiptrace.rb
ADDED
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,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
|
data/test/test_helper.rb
ADDED
@@ -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
|