call_stack 0.1.0.0-mswin32
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +56 -0
- data/README.en +76 -0
- data/Rakefile +95 -0
- data/THANKS +5 -0
- data/VERSION +1 -0
- data/example.rb +42 -0
- data/ext/call_stack/call_stack.c +493 -0
- data/ext/call_stack/extconf.rb +3 -0
- data/lib/binding_of_caller.rb +11 -0
- data/lib/breakpoint185.rb +8 -0
- data/lib/call_stack.so +0 -0
- data/mingw-rbconfig.rb +174 -0
- data/setup.rb +1585 -0
- metadata +75 -0
data/LICENSE
ADDED
@@ -0,0 +1,56 @@
|
|
1
|
+
call_stack is copyrighted free software by Mauricio Fernandez <mfp@acm.org>.
|
2
|
+
You can redistribute it and/or modify it under either the terms of the GPL
|
3
|
+
(see the file GPL), or the conditions below:
|
4
|
+
|
5
|
+
1. You may make and give away verbatim copies of the source form of the
|
6
|
+
software without restriction, provided that you duplicate all of the
|
7
|
+
original copyright notices and associated disclaimers.
|
8
|
+
|
9
|
+
2. You may modify your copy of the software in any way, provided that
|
10
|
+
you do at least ONE of the following:
|
11
|
+
|
12
|
+
a) place your modifications in the Public Domain or otherwise
|
13
|
+
make them Freely Available, such as by posting said
|
14
|
+
modifications to Usenet or an equivalent medium, or by allowing
|
15
|
+
the author to include your modifications in the software.
|
16
|
+
|
17
|
+
b) use the modified software only within your corporation or
|
18
|
+
organization.
|
19
|
+
|
20
|
+
c) give non-standard binaries non-standard names, with
|
21
|
+
instructions on where to get the original software distribution.
|
22
|
+
|
23
|
+
d) make other distribution arrangements with the author.
|
24
|
+
|
25
|
+
3. You may distribute the software in object code or binary form,
|
26
|
+
provided that you do at least ONE of the following:
|
27
|
+
|
28
|
+
a) distribute the binaries and library files of the software,
|
29
|
+
together with instructions (in the manual page or equivalent)
|
30
|
+
on where to get the original distribution.
|
31
|
+
|
32
|
+
b) accompany the distribution with the machine-readable source of
|
33
|
+
the software.
|
34
|
+
|
35
|
+
c) give non-standard binaries non-standard names, with
|
36
|
+
instructions on where to get the original software distribution.
|
37
|
+
|
38
|
+
d) make other distribution arrangements with the author.
|
39
|
+
|
40
|
+
4. You may modify and include the part of the software into any other
|
41
|
+
software (possibly commercial). But some files in the distribution
|
42
|
+
are not written by the author, so that they are not under these terms.
|
43
|
+
|
44
|
+
For the list of those files and their copying conditions, see the
|
45
|
+
file LEGAL.
|
46
|
+
|
47
|
+
5. The scripts and library files supplied as input to or produced as
|
48
|
+
output from the software do not automatically fall under the
|
49
|
+
copyright of the software, but belong to whomever generated them,
|
50
|
+
and may be sold commercially, and may be aggregated with this
|
51
|
+
software.
|
52
|
+
|
53
|
+
6. THIS SOFTWARE IS PROVIDED "AS IS" AND WITHOUT ANY EXPRESS OR
|
54
|
+
IMPLIED WARRANTIES, INCLUDING, WITHOUT LIMITATION, THE IMPLIED
|
55
|
+
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
56
|
+
PURPOSE.
|
data/README.en
ADDED
@@ -0,0 +1,76 @@
|
|
1
|
+
call_stack copyright (c) 2006 Mauricio Fernandez <mfp@acm.org>
|
2
|
+
Use and redistribution subject to the same terms as Ruby.
|
3
|
+
|
4
|
+
== Overview
|
5
|
+
|
6
|
+
call_stack allows you to obtain backtrace data in your Ruby code.
|
7
|
+
It is used as follows:
|
8
|
+
|
9
|
+
require 'call_stack'
|
10
|
+
|
11
|
+
call_stack_on # start recording information
|
12
|
+
|
13
|
+
#.... somewhere else
|
14
|
+
def b; foo end
|
15
|
+
def foo
|
16
|
+
backtrace = call_stack(-1)
|
17
|
+
end
|
18
|
+
|
19
|
+
b # => [[:unknown, :unknown, "-", 3, nil, nil],
|
20
|
+
[Object, :b, "-", 7, #<Binding:0xa7dbe6b8>, :Ruby],
|
21
|
+
[Object, :foo, "-", 8, #<Binding:0xa7dbe690>, :Ruby]]
|
22
|
+
|
23
|
+
call_stack_off
|
24
|
+
|
25
|
+
Kernel#call_stack returns an array of
|
26
|
+
[class, mid, filename, line, binding, language]
|
27
|
+
arrays, where language is either :Ruby, :C or nil. Older stack frames come
|
28
|
+
first, newer last.
|
29
|
+
|
30
|
+
Information from stack frames which existed before #call_stack_on was called
|
31
|
+
will be incomplete.
|
32
|
+
|
33
|
+
With no arguments, #call_stack will return an array with only one element
|
34
|
+
(with the structure described above), corresponding to the context where
|
35
|
+
#call_stack was called, e.g.
|
36
|
+
|
37
|
+
require 'call_stack'
|
38
|
+
call_stack_on
|
39
|
+
def foo; call_stack end
|
40
|
+
foo # => [[Object, :foo, "-", 4, #<Binding:0xa7d90880>, :Ruby]]
|
41
|
+
|
42
|
+
With a negative argument, #call_stack returns the whole call stack.
|
43
|
+
If a positive argument is given, as many levels as indicated will be returned;
|
44
|
+
call_stack() is equivalent to call_stack(1).
|
45
|
+
|
46
|
+
|
47
|
+
== Binding.of_caller compatibility
|
48
|
+
|
49
|
+
binding_of_caller.rb contains an implementation of Binding.of_caller built on
|
50
|
+
top of call_stack. It will make your program somewhat slower, so you might
|
51
|
+
want to write your own (it takes 3 lines of code) and postpone the call to
|
52
|
+
#call_stack_on until it's needed.
|
53
|
+
|
54
|
+
== Use with Ruby on Rails
|
55
|
+
|
56
|
+
Rails' breakpoint functionality doesn't work with 1.8.5, since it depends on
|
57
|
+
the "standard" Binding.of_caller, which won't run on Ruby > 1.8.4.
|
58
|
+
|
59
|
+
This can be overcome by using call_stack with Rails' breakpointer as follows:
|
60
|
+
ruby -rbreakpoint185 script/server
|
61
|
+
and then, in another console
|
62
|
+
script/breakpointer
|
63
|
+
|
64
|
+
== Installing
|
65
|
+
|
66
|
+
De-compress the archive and enter its top directory.
|
67
|
+
Then type:
|
68
|
+
|
69
|
+
($ su)
|
70
|
+
# ruby setup.rb
|
71
|
+
|
72
|
+
Run ruby setup.rb --help for information on the install options.
|
73
|
+
|
74
|
+
== License
|
75
|
+
|
76
|
+
call_stack is licensed under the same terms as Ruby. See LICENSE.
|
data/Rakefile
ADDED
@@ -0,0 +1,95 @@
|
|
1
|
+
|
2
|
+
# {{{ Package tasks
|
3
|
+
|
4
|
+
CALL_STACK_VERSION = File.read("VERSION").chomp
|
5
|
+
|
6
|
+
PKG_REVISION = ".0"
|
7
|
+
PKG_FILES = FileList[
|
8
|
+
"ext/call_stack/*.c",
|
9
|
+
"ext/call_stack/*.h",
|
10
|
+
"ext/call_stack/extconf.rb",
|
11
|
+
"lib/**/*.rb",
|
12
|
+
"LICENSE", "Rakefile", "README.*", "THANKS", "VERSION", "example.rb",
|
13
|
+
"mingw-rbconfig.rb", "setup.rb"
|
14
|
+
]
|
15
|
+
|
16
|
+
require 'rake/gempackagetask'
|
17
|
+
Spec = Gem::Specification.new do |s|
|
18
|
+
s.name = "call_stack"
|
19
|
+
s.version = CALL_STACK_VERSION + PKG_REVISION
|
20
|
+
s.summary = "Call stack information and Binding.of_caller/breakpoint workaround for Ruby 1.8.5"
|
21
|
+
s.description = <<EOF
|
22
|
+
call_stack can be used to obtain backtrace information in a running program.
|
23
|
+
It also contains a new Binding.of_caller implementation that works with Ruby 1.8.5.
|
24
|
+
|
25
|
+
It can be used with Rails' breakpointer as follows:
|
26
|
+
ruby -rbreakpoint185 script/server
|
27
|
+
and then, in another console
|
28
|
+
script/breakpointer
|
29
|
+
EOF
|
30
|
+
s.files = PKG_FILES.to_a
|
31
|
+
s.require_path = 'lib'
|
32
|
+
s.extensions << "ext/call_stack/extconf.rb"
|
33
|
+
s.author = "Mauricio Fernandez"
|
34
|
+
s.email = "mfp@acm.org"
|
35
|
+
s.homepage = "http://eigenclass.org/"
|
36
|
+
s.has_rdoc = true
|
37
|
+
s.extra_rdoc_files = %w[README.en]
|
38
|
+
s.rdoc_options << "--main" << "README.en" << "--title" << 'call_stack'
|
39
|
+
s.post_install_message = <<EOF
|
40
|
+
|
41
|
+
=============================================================================
|
42
|
+
|
43
|
+
call_stack provides an alternative implementation of Binding.of_caller that
|
44
|
+
works on Ruby > 1.8.4. You can use it by loading breakpoint185.rb before
|
45
|
+
running your program.
|
46
|
+
|
47
|
+
If you're using Rails, you can do
|
48
|
+
ruby -rbreakpoint185 script/server
|
49
|
+
and then, in another console
|
50
|
+
script/breakpointer
|
51
|
+
|
52
|
+
=============================================================================
|
53
|
+
|
54
|
+
EOF
|
55
|
+
end
|
56
|
+
|
57
|
+
Rake::GemPackageTask.new(Spec) do |p|
|
58
|
+
p.need_tar = true
|
59
|
+
end
|
60
|
+
|
61
|
+
# {{{ Cross-compilation and building of a binary RubyGems package for mswin32
|
62
|
+
|
63
|
+
require 'rake/clean'
|
64
|
+
|
65
|
+
WIN32_PKG_DIR = "call_stack-" + CALL_STACK_VERSION + PKG_REVISION
|
66
|
+
|
67
|
+
file "#{WIN32_PKG_DIR}" => [:package] do
|
68
|
+
sh "tar zxf pkg/#{WIN32_PKG_DIR}.tgz"
|
69
|
+
end
|
70
|
+
|
71
|
+
desc "Cross-compile the rcovrt.so extension for win32"
|
72
|
+
file "call_stack_win32" => ["#{WIN32_PKG_DIR}"] do
|
73
|
+
cp "mingw-rbconfig.rb", "#{WIN32_PKG_DIR}/ext/call_stack/rbconfig.rb"
|
74
|
+
sh "cd #{WIN32_PKG_DIR}/ext/call_stack/ && ruby -I. extconf.rb && make"
|
75
|
+
mv "#{WIN32_PKG_DIR}/ext/call_stack/call_stack.so", "#{WIN32_PKG_DIR}/lib"
|
76
|
+
end
|
77
|
+
|
78
|
+
Win32Spec = Spec.clone
|
79
|
+
Win32Spec.platform = Gem::Platform::WIN32
|
80
|
+
Win32Spec.extensions = []
|
81
|
+
Win32Spec.files += ["lib/call_stack.so"]
|
82
|
+
|
83
|
+
desc "Build the binary RubyGems package for win32"
|
84
|
+
task :rubygems_win32 => ["call_stack_win32"] do
|
85
|
+
Dir.chdir("#{WIN32_PKG_DIR}") do
|
86
|
+
Gem::Builder.new(Win32Spec).build
|
87
|
+
verbose(true) {
|
88
|
+
mv Dir["*.gem"].first, "../pkg/call_stack-#{CALL_STACK_VERSION + PKG_REVISION}-mswin32.gem"
|
89
|
+
}
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
CLEAN.include "#{WIN32_PKG_DIR}"
|
94
|
+
|
95
|
+
# vim: set sw=2 ft=ruby:
|
data/THANKS
ADDED
data/VERSION
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
0.1.0
|
data/example.rb
ADDED
@@ -0,0 +1,42 @@
|
|
1
|
+
|
2
|
+
require 'call_stack'
|
3
|
+
def a; _a = 1; b end
|
4
|
+
def b; _b = 2; c end
|
5
|
+
def c; _c = 3; d end
|
6
|
+
def d; _d = 4; send(:e) end
|
7
|
+
def e
|
8
|
+
levels = call_stack(10)
|
9
|
+
levels.each do |klass, id, file, line, binding, lang|
|
10
|
+
puts "=" * 80
|
11
|
+
p [klass, id, file, line, lang]
|
12
|
+
next unless binding
|
13
|
+
p eval("local_variables", binding).map{|x| [x, eval(x, binding)]}
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
def x
|
18
|
+
call_stack_on
|
19
|
+
a
|
20
|
+
call_stack_off
|
21
|
+
end
|
22
|
+
|
23
|
+
x
|
24
|
+
|
25
|
+
|
26
|
+
# binding of caller
|
27
|
+
require 'binding_of_caller'
|
28
|
+
|
29
|
+
def foo
|
30
|
+
a = b = 1
|
31
|
+
puts bar.inspect, a, b
|
32
|
+
end
|
33
|
+
|
34
|
+
def bar
|
35
|
+
Binding.of_caller do |binding|
|
36
|
+
ret = eval("[a,b]", binding)
|
37
|
+
eval("a, b = 2, 3", binding)
|
38
|
+
ret
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
foo
|
@@ -0,0 +1,493 @@
|
|
1
|
+
|
2
|
+
#include <ruby.h>
|
3
|
+
#include <node.h>
|
4
|
+
#include <st.h>
|
5
|
+
#include <stdlib.h>
|
6
|
+
|
7
|
+
#define CALLSITE_DEBUG 0
|
8
|
+
#define CALLSITE_DEBUG_EVENTS 0
|
9
|
+
|
10
|
+
static char callsite_hook_set_p;
|
11
|
+
|
12
|
+
typedef struct {
|
13
|
+
char *sourcefile;
|
14
|
+
unsigned int sourceline;
|
15
|
+
ID mid;
|
16
|
+
VALUE klass;
|
17
|
+
VALUE binding;
|
18
|
+
VALUE type;
|
19
|
+
} code_site_t;
|
20
|
+
static VALUE additional_gc_roots = 0;
|
21
|
+
|
22
|
+
static caller_stack_len = 1;
|
23
|
+
static ID id_unknown;
|
24
|
+
static ID id_binding;
|
25
|
+
static VALUE sym_C;
|
26
|
+
static VALUE sym_Ruby;
|
27
|
+
|
28
|
+
typedef struct {
|
29
|
+
code_site_t *start;
|
30
|
+
code_site_t *end;
|
31
|
+
code_site_t *ptr;
|
32
|
+
} callsite_stack_t;
|
33
|
+
static callsite_stack_t *callsite_stack;
|
34
|
+
static VALUE callsite_last_thread;
|
35
|
+
static st_table* callsite_stack_tbl;
|
36
|
+
static st_table* callsite_filename_tbl;
|
37
|
+
|
38
|
+
static st_table* callsite_defsite_data;
|
39
|
+
static st_table* callsite_caller_data;
|
40
|
+
|
41
|
+
static VALUE module_to_s_proc;
|
42
|
+
|
43
|
+
#define INITIAL_STACK_SIZE 8
|
44
|
+
|
45
|
+
|
46
|
+
static int
|
47
|
+
value_cmp(VALUE x, VALUE y)
|
48
|
+
{
|
49
|
+
return x != y;
|
50
|
+
}
|
51
|
+
|
52
|
+
static int
|
53
|
+
value_hash(VALUE v)
|
54
|
+
{
|
55
|
+
return v;
|
56
|
+
}
|
57
|
+
|
58
|
+
static struct st_hash_type type_value_hash = {
|
59
|
+
value_cmp,
|
60
|
+
value_hash,
|
61
|
+
#if RUBY_VERSION_CODE >= 190
|
62
|
+
st_nothing_key_free,
|
63
|
+
st_nothing_key_clone
|
64
|
+
#endif
|
65
|
+
};
|
66
|
+
|
67
|
+
|
68
|
+
typedef struct {
|
69
|
+
VALUE klass;
|
70
|
+
ID mid;
|
71
|
+
} meth_selector;
|
72
|
+
|
73
|
+
static int
|
74
|
+
meth_selector_cmp(meth_selector* x, meth_selector* y)
|
75
|
+
{
|
76
|
+
return (x->klass != y->klass || x->mid != y->mid);
|
77
|
+
}
|
78
|
+
|
79
|
+
static int
|
80
|
+
meth_selector_hash(meth_selector* x)
|
81
|
+
{
|
82
|
+
return (x->mid << 10) | (x->klass >> 2);
|
83
|
+
}
|
84
|
+
|
85
|
+
static struct st_hash_type type_meth_selector_hash = {
|
86
|
+
meth_selector_cmp,
|
87
|
+
meth_selector_hash,
|
88
|
+
#if RUBY_VERSION_CODE >= 190
|
89
|
+
HeyFIXME,
|
90
|
+
HeyFIXME
|
91
|
+
#endif
|
92
|
+
};
|
93
|
+
|
94
|
+
|
95
|
+
/*
|
96
|
+
*
|
97
|
+
* callsite hook and associated functions
|
98
|
+
*
|
99
|
+
* */
|
100
|
+
|
101
|
+
static VALUE
|
102
|
+
callsite_gen_backtrace_info(int argc, VALUE *argv, VALUE self)
|
103
|
+
{
|
104
|
+
VALUE backtrace;
|
105
|
+
VALUE level;
|
106
|
+
VALUE klass;
|
107
|
+
VALUE depth = Qnil;
|
108
|
+
unsigned int i;
|
109
|
+
code_site_t *csite;
|
110
|
+
int len;
|
111
|
+
|
112
|
+
if(!callsite_hook_set_p) {
|
113
|
+
return Qnil;
|
114
|
+
}
|
115
|
+
rb_scan_args(argc, argv, "01", &depth);
|
116
|
+
if(NIL_P(depth)) {
|
117
|
+
len = 1;
|
118
|
+
} else {
|
119
|
+
len = NUM2INT(depth);
|
120
|
+
}
|
121
|
+
if(len < 0) {
|
122
|
+
len = 0x7FFFFFFF;
|
123
|
+
}
|
124
|
+
backtrace = rb_ary_new();
|
125
|
+
for(i = 0, csite = callsite_stack->ptr-2; i < len; i++, csite--) {
|
126
|
+
if(csite < callsite_stack->start)
|
127
|
+
break;
|
128
|
+
level = rb_ary_new();
|
129
|
+
klass = csite->klass ? csite->klass : Qnil;
|
130
|
+
rb_ary_push(level, klass);
|
131
|
+
rb_ary_push(level, csite->mid ? ID2SYM(csite->mid) : Qnil);
|
132
|
+
if(csite->sourcefile) {
|
133
|
+
rb_ary_push(level, rb_str_new2(csite->sourcefile));
|
134
|
+
rb_ary_push(level, UINT2NUM(csite->sourceline));
|
135
|
+
}
|
136
|
+
else {
|
137
|
+
rb_ary_push(level, Qnil);
|
138
|
+
rb_ary_push(level, Qnil);
|
139
|
+
}
|
140
|
+
rb_ary_push(level, csite->binding);
|
141
|
+
rb_ary_push(level, csite->type);
|
142
|
+
|
143
|
+
rb_ary_unshift(backtrace, level);
|
144
|
+
}
|
145
|
+
|
146
|
+
|
147
|
+
return backtrace;
|
148
|
+
}
|
149
|
+
|
150
|
+
|
151
|
+
static callsite_stack_t *
|
152
|
+
callsite_stack_create()
|
153
|
+
{
|
154
|
+
callsite_stack_t *stack;
|
155
|
+
|
156
|
+
stack = ALLOC(callsite_stack_t);
|
157
|
+
stack->start = stack->ptr =
|
158
|
+
ALLOC_N(code_site_t, INITIAL_STACK_SIZE);
|
159
|
+
stack->end = stack->start + INITIAL_STACK_SIZE;
|
160
|
+
return stack;
|
161
|
+
}
|
162
|
+
|
163
|
+
static void
|
164
|
+
callsite_stack_free(callsite_stack_t *stack)
|
165
|
+
{
|
166
|
+
xfree(stack->start);
|
167
|
+
xfree(stack);
|
168
|
+
}
|
169
|
+
|
170
|
+
static void
|
171
|
+
callsite_stack_push(VALUE klass, ID mid,
|
172
|
+
char *filename, unsigned int line, VALUE binding,
|
173
|
+
VALUE type)
|
174
|
+
{
|
175
|
+
char *sourcefile;
|
176
|
+
|
177
|
+
if (callsite_stack->ptr == callsite_stack->end) {
|
178
|
+
code_site_t *new_start;
|
179
|
+
int len, new_capa;
|
180
|
+
|
181
|
+
len = callsite_stack->ptr - callsite_stack->start;
|
182
|
+
new_capa = (callsite_stack->end - callsite_stack->start) * 2;
|
183
|
+
new_start = ALLOC_N(code_site_t, new_capa);
|
184
|
+
memcpy(new_start, callsite_stack->start, sizeof(code_site_t) * len);
|
185
|
+
callsite_stack->start = new_start;
|
186
|
+
callsite_stack->end = new_start + new_capa;
|
187
|
+
callsite_stack->ptr = new_start + len;
|
188
|
+
}
|
189
|
+
callsite_stack->ptr->klass = klass ? klass : Qnil;
|
190
|
+
callsite_stack->ptr->mid = mid ? mid : id_unknown;
|
191
|
+
if(filename) {
|
192
|
+
/* have to copy the filename, since it could be reclaimed by Ruby */
|
193
|
+
if(!st_lookup(callsite_filename_tbl, (st_data_t)filename, (st_data_t*)&sourcefile)) {
|
194
|
+
sourcefile = strdup(filename);
|
195
|
+
st_insert(callsite_filename_tbl, (st_data_t)sourcefile, (st_data_t)sourcefile);
|
196
|
+
}
|
197
|
+
callsite_stack->ptr->sourcefile = sourcefile;
|
198
|
+
} else {
|
199
|
+
callsite_stack->ptr->sourcefile = 0;
|
200
|
+
}
|
201
|
+
callsite_stack->ptr->sourceline = line ? line : 0;
|
202
|
+
callsite_stack->ptr->binding = binding;
|
203
|
+
callsite_stack->ptr->type = type;
|
204
|
+
rb_hash_aset(additional_gc_roots, binding, binding);
|
205
|
+
|
206
|
+
callsite_stack->ptr++;
|
207
|
+
}
|
208
|
+
|
209
|
+
|
210
|
+
static VALUE
|
211
|
+
remove_gc_root(VALUE arg)
|
212
|
+
{
|
213
|
+
rb_hash_delete(additional_gc_roots, arg);
|
214
|
+
}
|
215
|
+
|
216
|
+
static VALUE
|
217
|
+
dummy(VALUE arg1, VALUE arg2)
|
218
|
+
{
|
219
|
+
return Qnil;
|
220
|
+
}
|
221
|
+
|
222
|
+
static inline code_site_t *
|
223
|
+
callsite_stack_pop(callsite_stack_t *stack)
|
224
|
+
{
|
225
|
+
if (stack->ptr == stack->start) {
|
226
|
+
return stack->ptr;
|
227
|
+
//rb_raise(rb_eException, "empty stack");
|
228
|
+
}
|
229
|
+
/* this is what we want to do, but it's not safe because rb_hash_delete
|
230
|
+
* will reuse the current block ... */
|
231
|
+
/* rb_hash_delete(additional_gc_roots, (stack->ptr-1)->binding); */
|
232
|
+
rb_iterate(remove_gc_root, (stack->ptr-1)->binding, dummy, Qnil);
|
233
|
+
return --stack->ptr;
|
234
|
+
}
|
235
|
+
|
236
|
+
|
237
|
+
static st_table *
|
238
|
+
callsite_stack_table_create()
|
239
|
+
{
|
240
|
+
return st_init_table(&type_value_hash);
|
241
|
+
}
|
242
|
+
|
243
|
+
|
244
|
+
static inline callsite_stack_t*
|
245
|
+
callsite_stack_find(VALUE thread)
|
246
|
+
{
|
247
|
+
st_data_t val;
|
248
|
+
|
249
|
+
if(st_lookup(callsite_stack_tbl, (st_data_t)thread, &val))
|
250
|
+
return (callsite_stack_t *) val;
|
251
|
+
else
|
252
|
+
return NULL;
|
253
|
+
}
|
254
|
+
|
255
|
+
|
256
|
+
static inline int
|
257
|
+
callsite_stack_table_insert(VALUE thread, callsite_stack_t *stack)
|
258
|
+
{
|
259
|
+
return st_insert(callsite_stack_tbl, (st_data_t ) thread, (st_data_t) stack);
|
260
|
+
}
|
261
|
+
|
262
|
+
|
263
|
+
static void
|
264
|
+
callsite_stack_dump()
|
265
|
+
{
|
266
|
+
code_site_t *csite;
|
267
|
+
int status;
|
268
|
+
VALUE klass_desc;
|
269
|
+
char *desc;
|
270
|
+
VALUE old_exception;
|
271
|
+
|
272
|
+
old_exception = rb_gv_get("$!");
|
273
|
+
printf("+++++++++\n");
|
274
|
+
printf("CALL STACK FOR CURRENT THREAD:\n");
|
275
|
+
for(csite = callsite_stack->start; csite < callsite_stack->ptr; csite++) {
|
276
|
+
klass_desc = rb_protect(rb_inspect, csite->klass, &status);
|
277
|
+
if(!status) {
|
278
|
+
desc = RSTRING(klass_desc)->ptr;
|
279
|
+
} else {
|
280
|
+
desc = rb_obj_classname(csite->klass);
|
281
|
+
}
|
282
|
+
printf("%30s %20s %s %d\n", desc,
|
283
|
+
csite->mid ? rb_id2name(csite->mid) : "",
|
284
|
+
csite->sourcefile ? csite->sourcefile : "unknown",
|
285
|
+
csite->sourceline);
|
286
|
+
}
|
287
|
+
printf("---------\n");
|
288
|
+
|
289
|
+
rb_gv_set("$!", old_exception);
|
290
|
+
|
291
|
+
}
|
292
|
+
|
293
|
+
/* This function stolen from Kent Sibilev's ruby-debug. */
|
294
|
+
/*
|
295
|
+
* This is a NASTY HACK. For some reasons rb_f_binding is declared
|
296
|
+
* static in eval.c
|
297
|
+
*/
|
298
|
+
static VALUE
|
299
|
+
create_binding(VALUE self)
|
300
|
+
{
|
301
|
+
typedef VALUE (*bind_func_t)(VALUE);
|
302
|
+
static bind_func_t f_binding = NULL;
|
303
|
+
|
304
|
+
if(f_binding == NULL)
|
305
|
+
{
|
306
|
+
NODE *body, *method;
|
307
|
+
st_lookup(RCLASS(rb_mKernel)->m_tbl, rb_intern("binding"), (st_data_t *)&body);
|
308
|
+
method = (NODE *)body->u2.value;
|
309
|
+
f_binding = (bind_func_t)method->u1.value;
|
310
|
+
}
|
311
|
+
return f_binding(self);
|
312
|
+
}
|
313
|
+
|
314
|
+
static void
|
315
|
+
callsite_select_callsite_stack()
|
316
|
+
{
|
317
|
+
VALUE curr_thread;
|
318
|
+
curr_thread = rb_thread_current();
|
319
|
+
if (curr_thread != callsite_last_thread) {
|
320
|
+
callsite_stack = callsite_stack_find(curr_thread);
|
321
|
+
if (!callsite_stack) {
|
322
|
+
callsite_stack = callsite_stack_create();
|
323
|
+
callsite_stack_table_insert(curr_thread, callsite_stack);
|
324
|
+
/* no need to add curr_thread to the additional root list for
|
325
|
+
* the GC: we will never it if the Thread disappears anyway,
|
326
|
+
* so no access is done to the (dead) Ruby object. */
|
327
|
+
}
|
328
|
+
callsite_last_thread = curr_thread;
|
329
|
+
}
|
330
|
+
}
|
331
|
+
|
332
|
+
|
333
|
+
static void
|
334
|
+
coverage_event_callsite_hook(rb_event_t event, NODE *node, VALUE self,
|
335
|
+
ID mid, VALUE klass)
|
336
|
+
{
|
337
|
+
VALUE curr_meth;
|
338
|
+
VALUE args[4];
|
339
|
+
VALUE binding;
|
340
|
+
int status;
|
341
|
+
static int in_hook = 0;
|
342
|
+
|
343
|
+
if(in_hook)
|
344
|
+
return;
|
345
|
+
in_hook++;
|
346
|
+
callsite_select_callsite_stack();
|
347
|
+
|
348
|
+
if(TYPE(klass) == T_ICLASS) {
|
349
|
+
klass = RBASIC(klass)->klass;
|
350
|
+
}
|
351
|
+
#if CALLSITE_DEBUG_EVENTS
|
352
|
+
do {
|
353
|
+
VALUE old_exception;
|
354
|
+
old_exception = rb_gv_get("$!");
|
355
|
+
rb_protect(rb_inspect, klass, &status);
|
356
|
+
if(!status) {
|
357
|
+
printf("EVENT: %d %s %s %s %d\n", event,
|
358
|
+
klass ? RSTRING(rb_inspect(klass))->ptr : "",
|
359
|
+
mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
|
360
|
+
: "unknown",
|
361
|
+
node ? node->nd_file : "", node ? nd_line(node) : 0);
|
362
|
+
} else {
|
363
|
+
printf("EVENT: %d %s %s %d\n", event,
|
364
|
+
mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
|
365
|
+
: "unknown",
|
366
|
+
node ? node->nd_file : "", node ? nd_line(node) : 0);
|
367
|
+
}
|
368
|
+
rb_gv_set("$!", old_exception);
|
369
|
+
} while (0);
|
370
|
+
#endif
|
371
|
+
switch(event) {
|
372
|
+
case RUBY_EVENT_CALL:
|
373
|
+
binding = create_binding(rb_mKernel);
|
374
|
+
callsite_stack_push(klass, mid, node->nd_file, nd_line(node) + 1, binding, sym_Ruby);
|
375
|
+
#if CALLSITE_DEBUG
|
376
|
+
callsite_stack_dump();
|
377
|
+
#endif
|
378
|
+
break;
|
379
|
+
case RUBY_EVENT_C_CALL:
|
380
|
+
callsite_stack_push(klass, mid, 0, 0, Qnil, sym_C);
|
381
|
+
break;
|
382
|
+
case RUBY_EVENT_RETURN:
|
383
|
+
case RUBY_EVENT_C_RETURN:
|
384
|
+
callsite_stack_pop(callsite_stack);
|
385
|
+
#if CALLSITE_DEBUG
|
386
|
+
callsite_stack_dump();
|
387
|
+
#endif
|
388
|
+
break;
|
389
|
+
}
|
390
|
+
in_hook--;
|
391
|
+
}
|
392
|
+
|
393
|
+
|
394
|
+
static void
|
395
|
+
callsite_prefill_callsite_stack()
|
396
|
+
{
|
397
|
+
VALUE backtrace;
|
398
|
+
VALUE str;
|
399
|
+
VALUE parts;
|
400
|
+
int i;
|
401
|
+
char *sourcefile;
|
402
|
+
unsigned int sourceline;
|
403
|
+
VALUE meth;
|
404
|
+
ID aref;
|
405
|
+
VALUE regex;
|
406
|
+
ID mid;
|
407
|
+
VALUE sym_unknown;
|
408
|
+
|
409
|
+
callsite_select_callsite_stack();
|
410
|
+
aref = rb_intern("[]");
|
411
|
+
regex = rb_eval_string("/in `([^']*)'/");
|
412
|
+
sym_unknown = ID2SYM(rb_intern("unknown"));
|
413
|
+
/* reset before refilling */
|
414
|
+
callsite_stack->ptr = callsite_stack->start;
|
415
|
+
|
416
|
+
backtrace = rb_funcall(rb_mKernel, rb_intern("caller"), 0);
|
417
|
+
if(TYPE(backtrace) == T_ARRAY) {
|
418
|
+
for(i = RARRAY(backtrace)->len - 1; i >= 0; i--) {
|
419
|
+
str = RARRAY(backtrace)->ptr[i];
|
420
|
+
parts = rb_str_split(str, ":");
|
421
|
+
if(RARRAY(parts)->len < 2) {
|
422
|
+
sourcefile = "unknown";
|
423
|
+
sourceline = 0;
|
424
|
+
} else {
|
425
|
+
sourcefile = RSTRING(RARRAY(parts)->ptr[0])->ptr;
|
426
|
+
sscanf(RSTRING((RARRAY(parts)->ptr[1]))->ptr,
|
427
|
+
"%d", &sourceline);
|
428
|
+
}
|
429
|
+
if( RTEST(meth = rb_funcall(str, aref, 2, regex, INT2FIX(1))) ) {
|
430
|
+
/* FIXME: check type */
|
431
|
+
mid = rb_intern(RSTRING(meth)->ptr);
|
432
|
+
} else {
|
433
|
+
mid = rb_intern("unknown");
|
434
|
+
}
|
435
|
+
callsite_stack_push(sym_unknown, mid, sourcefile, sourceline,
|
436
|
+
Qnil, Qnil);
|
437
|
+
}
|
438
|
+
|
439
|
+
}
|
440
|
+
else {
|
441
|
+
rb_raise(rb_eRuntimeError, "Your Kernel#caller is weird.");
|
442
|
+
}
|
443
|
+
}
|
444
|
+
|
445
|
+
static VALUE
|
446
|
+
cov_install_callsite_hook(VALUE self)
|
447
|
+
{
|
448
|
+
if(!callsite_hook_set_p) {
|
449
|
+
callsite_hook_set_p = 1;
|
450
|
+
/* for each thread */
|
451
|
+
callsite_prefill_callsite_stack();
|
452
|
+
rb_add_event_hook(coverage_event_callsite_hook,
|
453
|
+
RUBY_EVENT_CALL | RUBY_EVENT_C_CALL |
|
454
|
+
RUBY_EVENT_RETURN | RUBY_EVENT_C_RETURN);
|
455
|
+
|
456
|
+
return Qtrue;
|
457
|
+
} else
|
458
|
+
return Qfalse;
|
459
|
+
}
|
460
|
+
|
461
|
+
|
462
|
+
static VALUE
|
463
|
+
cov_remove_callsite_hook(VALUE self)
|
464
|
+
{
|
465
|
+
if(!callsite_hook_set_p)
|
466
|
+
return Qfalse;
|
467
|
+
else {
|
468
|
+
rb_remove_event_hook(coverage_event_callsite_hook);
|
469
|
+
callsite_hook_set_p = 0;
|
470
|
+
return Qtrue;
|
471
|
+
}
|
472
|
+
}
|
473
|
+
|
474
|
+
|
475
|
+
|
476
|
+
void
|
477
|
+
Init_call_stack()
|
478
|
+
{
|
479
|
+
id_unknown = rb_intern("unknown");
|
480
|
+
sym_C = ID2SYM(rb_intern("C"));
|
481
|
+
sym_Ruby = ID2SYM(rb_intern("Ruby"));
|
482
|
+
id_binding = rb_intern("binding");
|
483
|
+
|
484
|
+
additional_gc_roots = rb_hash_new();
|
485
|
+
rb_gc_register_address(&additional_gc_roots);
|
486
|
+
callsite_stack_tbl = st_init_table(&type_value_hash);
|
487
|
+
callsite_filename_tbl = st_init_strtable();
|
488
|
+
|
489
|
+
rb_define_global_function("call_stack_on", cov_install_callsite_hook, 0);
|
490
|
+
rb_define_global_function("call_stack_off", cov_remove_callsite_hook, 0);
|
491
|
+
rb_define_global_function("call_stack", callsite_gen_backtrace_info, -1);
|
492
|
+
}
|
493
|
+
/* vim: set sw=8 expandtab: */
|