call_stack 0.1.0.0-mswin32
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.
- 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: */
|