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 +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 [](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
|