relevance-rcov 0.8.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,331 @@
1
+
2
+ #include <ruby.h>
3
+ #include <env.h>
4
+ #include <node.h>
5
+ #include <st.h>
6
+ #include <stdlib.h>
7
+ #include <assert.h>
8
+
9
+ #define COVERAGE_DEBUG_EVENTS 0
10
+
11
+ #define RCOVRT_VERSION_MAJOR 2
12
+ #define RCOVRT_VERSION_MINOR 0
13
+ #define RCOVRT_VERSION_REV 0
14
+
15
+ static VALUE mRcov;
16
+ static VALUE mRCOV__;
17
+ static VALUE oSCRIPT_LINES__;
18
+ static ID id_cover;
19
+ static st_table* coverinfo = 0;
20
+ static char coverage_hook_set_p;
21
+
22
+ struct cov_array {
23
+ unsigned int len;
24
+ unsigned int *ptr;
25
+ };
26
+
27
+ static struct cov_array *cached_array = 0;
28
+ static char *cached_file = 0;
29
+
30
+
31
+ /*
32
+ *
33
+ * coverage hook and associated functions
34
+ *
35
+ * */
36
+
37
+ static struct cov_array *
38
+ coverage_increase_counter_uncached(char *sourcefile, unsigned int sourceline,
39
+ char mark_only)
40
+ {
41
+ struct cov_array *carray = NULL;
42
+
43
+ if(sourcefile == NULL) {
44
+ /* "can't happen", just ignore and avoid segfault */
45
+ return NULL;
46
+ } else if(!st_lookup(coverinfo, (st_data_t)sourcefile, (st_data_t*)&carray)) {
47
+ VALUE arr;
48
+
49
+ arr = rb_hash_aref(oSCRIPT_LINES__, rb_str_new2(sourcefile));
50
+ if(NIL_P(arr))
51
+ return 0;
52
+ rb_check_type(arr, T_ARRAY);
53
+ carray = calloc(1, sizeof(struct cov_array));
54
+ carray->ptr = calloc(RARRAY(arr)->len, sizeof(unsigned int));
55
+ carray->len = RARRAY(arr)->len;
56
+ st_insert(coverinfo, (st_data_t)strdup(sourcefile),
57
+ (st_data_t) carray);
58
+ } else {
59
+ /* recovered carray, sanity check */
60
+ assert(carray && "failed to create valid carray");
61
+ }
62
+
63
+ if(mark_only) {
64
+ if(!carray->ptr[sourceline])
65
+ carray->ptr[sourceline] = 1;
66
+ } else {
67
+ if (carray && carray->len > sourceline) {
68
+ carray->ptr[sourceline]++;
69
+ }
70
+ }
71
+
72
+ return carray;
73
+ }
74
+
75
+
76
+ static void
77
+ coverage_mark_caller()
78
+ {
79
+ struct FRAME *frame = ruby_frame;
80
+ NODE *n;
81
+
82
+ if (frame->last_func == ID_ALLOCATOR) {
83
+ frame = frame->prev;
84
+ }
85
+ for (; frame && (n = frame->node); frame = frame->prev) {
86
+ if (frame->prev && frame->prev->last_func) {
87
+ if (frame->prev->node == n) {
88
+ if (frame->prev->last_func == frame->last_func) continue;
89
+ }
90
+ coverage_increase_counter_uncached(n->nd_file, nd_line(n) - 1, 1);
91
+ }
92
+ else {
93
+ coverage_increase_counter_uncached(n->nd_file, nd_line(n) - 1, 1);
94
+ }
95
+ break;
96
+ }
97
+ }
98
+
99
+
100
+ static void
101
+ coverage_increase_counter_cached(char *sourcefile, int sourceline)
102
+ {
103
+ if(cached_file == sourcefile && cached_array && cached_array->len > sourceline) {
104
+ cached_array->ptr[sourceline]++;
105
+ return;
106
+ }
107
+ cached_file = sourcefile;
108
+ cached_array = coverage_increase_counter_uncached(sourcefile, sourceline, 0);
109
+ }
110
+
111
+
112
+ static void
113
+ coverage_event_coverage_hook(rb_event_t event, NODE *node, VALUE self,
114
+ ID mid, VALUE klass)
115
+ {
116
+ char *sourcefile;
117
+ unsigned int sourceline;
118
+ static unsigned int in_hook = 0;
119
+
120
+ if(in_hook) {
121
+ return;
122
+ }
123
+
124
+ in_hook++;
125
+
126
+ #if COVERAGE_DEBUG_EVENTS
127
+ do {
128
+ int status;
129
+ VALUE old_exception;
130
+ old_exception = rb_gv_get("$!");
131
+ rb_protect(rb_inspect, klass, &status);
132
+ if(!status) {
133
+ printf("EVENT: %d %s %s %s %d\n", event,
134
+ klass ? RSTRING(rb_inspect(klass))->ptr : "",
135
+ mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
136
+ : "unknown",
137
+ node ? node->nd_file : "", node ? nd_line(node) : 0);
138
+ } else {
139
+ printf("EVENT: %d %s %s %d\n", event,
140
+ mid ? (mid == ID_ALLOCATOR ? "ID_ALLOCATOR" : rb_id2name(mid))
141
+ : "unknown",
142
+ node ? node->nd_file : "", node ? nd_line(node) : 0);
143
+ }
144
+ rb_gv_set("$!", old_exception);
145
+ } while (0);
146
+ #endif
147
+
148
+ if(event & RUBY_EVENT_C_CALL) {
149
+ coverage_mark_caller();
150
+ }
151
+ if(event & (RUBY_EVENT_C_CALL | RUBY_EVENT_C_RETURN | RUBY_EVENT_CLASS)) {
152
+ in_hook--;
153
+ return;
154
+ }
155
+
156
+ if(node == NULL) {
157
+ in_hook--;
158
+ return;
159
+ }
160
+
161
+ sourcefile = node->nd_file;
162
+ sourceline = nd_line(node) - 1;
163
+
164
+ coverage_increase_counter_cached(sourcefile, sourceline);
165
+ if(event & RUBY_EVENT_CALL)
166
+ coverage_mark_caller();
167
+ in_hook--;
168
+ }
169
+
170
+
171
+ static VALUE
172
+ cov_install_coverage_hook(VALUE self)
173
+ {
174
+ if(!coverage_hook_set_p) {
175
+ if(!coverinfo)
176
+ coverinfo = st_init_strtable();
177
+ coverage_hook_set_p = 1;
178
+ /* TODO: allow C_CALL too, since it's supported already
179
+ * the overhead is around ~30%, tested on typo */
180
+ rb_add_event_hook(coverage_event_coverage_hook,
181
+ RUBY_EVENT_ALL & ~RUBY_EVENT_C_CALL &
182
+ ~RUBY_EVENT_C_RETURN & ~RUBY_EVENT_CLASS);
183
+
184
+ return Qtrue;
185
+ }
186
+ else
187
+ return Qfalse;
188
+ }
189
+
190
+
191
+ static int
192
+ populate_cover(st_data_t key, st_data_t value, st_data_t cover)
193
+ {
194
+ VALUE rcover;
195
+ VALUE rkey;
196
+ VALUE rval;
197
+ struct cov_array *carray;
198
+ unsigned int i;
199
+
200
+ rcover = (VALUE)cover;
201
+ carray = (struct cov_array *) value;
202
+ rkey = rb_str_new2((char*) key);
203
+ rval = rb_ary_new2(carray->len);
204
+ for(i = 0; i < carray->len; i++)
205
+ RARRAY(rval)->ptr[i] = UINT2NUM(carray->ptr[i]);
206
+ RARRAY(rval)->len = carray->len;
207
+
208
+ rb_hash_aset(rcover, rkey, rval);
209
+
210
+ return ST_CONTINUE;
211
+ }
212
+
213
+
214
+ static int
215
+ free_table(st_data_t key, st_data_t value, st_data_t ignored)
216
+ {
217
+ struct cov_array *carray;
218
+
219
+ carray = (struct cov_array *) value;
220
+ free((char *)key);
221
+ free(carray->ptr);
222
+ free(carray);
223
+
224
+ return ST_CONTINUE;
225
+ }
226
+
227
+
228
+ static VALUE
229
+ cov_remove_coverage_hook(VALUE self)
230
+ {
231
+ if(!coverage_hook_set_p)
232
+ return Qfalse;
233
+ else {
234
+ rb_remove_event_hook(coverage_event_coverage_hook);
235
+ coverage_hook_set_p = 0;
236
+ return Qtrue;
237
+ }
238
+ }
239
+
240
+
241
+ static VALUE
242
+ cov_generate_coverage_info(VALUE self)
243
+ {
244
+ VALUE cover;
245
+
246
+ if(rb_const_defined_at(mRCOV__, id_cover)) {
247
+ rb_mod_remove_const(mRCOV__, ID2SYM(id_cover));
248
+ }
249
+
250
+ cover = rb_hash_new();
251
+ if(coverinfo)
252
+ st_foreach(coverinfo, populate_cover, cover);
253
+ rb_define_const(mRCOV__, "COVER", cover);
254
+
255
+ return cover;
256
+ }
257
+
258
+
259
+ static VALUE
260
+ cov_reset_coverage(VALUE self)
261
+ {
262
+ if(coverage_hook_set_p) {
263
+ rb_raise(rb_eRuntimeError,
264
+ "Cannot reset the coverage info in the middle of a traced run.");
265
+ return Qnil;
266
+ }
267
+
268
+ cached_array = 0;
269
+ cached_file = 0;
270
+ st_foreach(coverinfo, free_table, Qnil);
271
+ st_free_table(coverinfo);
272
+ coverinfo = 0;
273
+
274
+ return Qnil;
275
+ }
276
+
277
+
278
+ static VALUE
279
+ cov_ABI(VALUE self)
280
+ {
281
+ VALUE ret;
282
+
283
+ ret = rb_ary_new();
284
+ rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MAJOR));
285
+ rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_MINOR));
286
+ rb_ary_push(ret, INT2FIX(RCOVRT_VERSION_REV));
287
+
288
+ return ret;
289
+ }
290
+
291
+
292
+ void
293
+ Init_rcovrt()
294
+ {
295
+ ID id_rcov = rb_intern("Rcov");
296
+ ID id_coverage__ = rb_intern("RCOV__");
297
+ ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
298
+
299
+ id_cover = rb_intern("COVER");
300
+
301
+ if(rb_const_defined(rb_cObject, id_rcov))
302
+ mRcov = rb_const_get(rb_cObject, id_rcov);
303
+ else
304
+ mRcov = rb_define_module("Rcov");
305
+
306
+ if(rb_const_defined(mRcov, id_coverage__))
307
+ mRCOV__ = rb_const_get_at(mRcov, id_coverage__);
308
+ else
309
+ mRCOV__ = rb_define_module_under(mRcov, "RCOV__");
310
+
311
+ if(rb_const_defined(rb_cObject, id_script_lines__))
312
+ oSCRIPT_LINES__ = rb_const_get(rb_cObject, rb_intern("SCRIPT_LINES__"));
313
+ else {
314
+ oSCRIPT_LINES__ = rb_hash_new();
315
+ rb_const_set(rb_cObject, id_script_lines__, oSCRIPT_LINES__);
316
+ }
317
+
318
+ coverage_hook_set_p = 0;
319
+
320
+ rb_define_singleton_method(mRCOV__, "install_coverage_hook",
321
+ cov_install_coverage_hook, 0);
322
+ rb_define_singleton_method(mRCOV__, "remove_coverage_hook",
323
+ cov_remove_coverage_hook, 0);
324
+ rb_define_singleton_method(mRCOV__, "generate_coverage_info",
325
+ cov_generate_coverage_info, 0);
326
+ rb_define_singleton_method(mRCOV__, "reset_coverage", cov_reset_coverage, 0);
327
+ rb_define_singleton_method(mRCOV__, "ABI", cov_ABI, 0);
328
+
329
+ Init_rcov_callsite();
330
+ }
331
+ /* vim: set sw=8 expandtab: */
@@ -0,0 +1,258 @@
1
+ #include <ruby.h>
2
+ #include <ruby/st.h>
3
+ #include <stdlib.h>
4
+
5
+ static char callsite_hook_set_p;
6
+
7
+ typedef struct {
8
+ const char *sourcefile;
9
+ unsigned int sourceline;
10
+ VALUE curr_meth;
11
+ } type_def_site;
12
+ static VALUE caller_info = 0;
13
+ static VALUE method_def_site_info = 0;
14
+
15
+ static int caller_stack_len = 1;
16
+
17
+ /*
18
+ *
19
+ * callsite hook and associated functions
20
+ *
21
+ * */
22
+
23
+ static VALUE
24
+ record_callsite_info(VALUE args)
25
+ {
26
+ VALUE caller_ary;
27
+ VALUE curr_meth;
28
+ VALUE count_hash;
29
+ VALUE count;
30
+ VALUE *pargs = (VALUE *)args;
31
+
32
+ caller_ary = pargs[0];
33
+ curr_meth = pargs[1];
34
+ count_hash = rb_hash_aref(caller_info, curr_meth);
35
+ if(TYPE(count_hash) != T_HASH) {
36
+ /* Qnil, anything else should be impossible unless somebody's been
37
+ * messing with ObjectSpace */
38
+ count_hash = rb_hash_new();
39
+ rb_hash_aset(caller_info, curr_meth, count_hash);
40
+ }
41
+ count = rb_hash_aref(count_hash, caller_ary);
42
+ if(count == Qnil)
43
+ count = INT2FIX(0);
44
+ count = INT2FIX(FIX2UINT(count) + 1);
45
+ rb_hash_aset(count_hash, caller_ary, count);
46
+ /* * /
47
+ printf("CALLSITE: %s -> %s %d\n", RSTRING_PTR(rb_inspect(curr_meth)),
48
+ RSTRING_PTR(rb_inspect(caller_ary)), FIX2INT(count));
49
+ / * */
50
+
51
+ return Qnil;
52
+ }
53
+
54
+
55
+ static VALUE
56
+ record_method_def_site(VALUE args)
57
+ {
58
+ type_def_site *pargs = (type_def_site *)args;
59
+ VALUE def_site_info;
60
+ // VALUE hash;
61
+
62
+ if( RTEST(rb_hash_aref(method_def_site_info, pargs->curr_meth)) )
63
+ return Qnil;
64
+ def_site_info = rb_ary_new();
65
+ rb_ary_push(def_site_info, rb_str_new2(pargs->sourcefile));
66
+ rb_ary_push(def_site_info, INT2NUM(pargs->sourceline+1));
67
+ rb_hash_aset(method_def_site_info, pargs->curr_meth, def_site_info);
68
+ /* * /
69
+ printf("DEFSITE: %s:%d for %s\n", pargs->sourcefile, pargs->sourceline+1,
70
+ RSTRING_PTR(rb_inspect(pargs->curr_meth)));
71
+ / * */
72
+
73
+ return Qnil;
74
+ }
75
+
76
+ static VALUE
77
+ callsite_custom_backtrace(int lev)
78
+ {
79
+ ID id;
80
+ VALUE klass;
81
+ VALUE klass_path;
82
+ VALUE eval_string;
83
+
84
+ rb_frame_method_id_and_class(&id, &klass);
85
+ if (id == ID_ALLOCATOR)
86
+ return Qnil;
87
+ if (klass) {
88
+ if (TYPE(klass) == T_ICLASS) {
89
+ klass = RBASIC(klass)->klass;
90
+ }
91
+ else if (FL_TEST(klass, FL_SINGLETON)) {
92
+ klass = rb_iv_get(klass, "__attached__");
93
+ }
94
+ }
95
+ // rb_sprintf("\"#<Class:%s>\"", RSTRING_PTR(klass_path))
96
+
97
+ /*
98
+ klass = class << klass; self end unless klass === eval("self", binding)
99
+ */
100
+
101
+ klass_path = rb_class_path(klass);
102
+ VALUE reciever = rb_funcall(rb_binding_new(), rb_intern("eval"), 1, rb_str_new2("self"));
103
+ if (rb_funcall(klass, rb_intern("=="), 1, reciever) == Qtrue) {
104
+ klass_path = rb_sprintf("\"#<Class:%s>\"", RSTRING_PTR(klass_path));
105
+ OBJ_FREEZE(klass_path);
106
+ }
107
+
108
+ eval_string = rb_sprintf("caller[%d, 1].map do |line|\nmd = /^([^:]*)(?::(\\d+)(?::in `(?:block in )?(.*)'))?/.match(line)\nraise \"Bad backtrace format\" unless md\n[%s, md[3] ? md[3].to_sym : nil, md[1], (md[2] || '').to_i]\nend", lev, RSTRING_PTR(klass_path));
109
+ return rb_eval_string(RSTRING_PTR(eval_string));
110
+ }
111
+
112
+ static void
113
+ coverage_event_callsite_hook(rb_event_flag_t event, VALUE node,
114
+ VALUE self, ID mid, VALUE klass)
115
+ {
116
+ VALUE caller_ary;
117
+ VALUE curr_meth;
118
+ VALUE args[2];
119
+ int status;
120
+
121
+ caller_ary = callsite_custom_backtrace(caller_stack_len);
122
+
123
+ VALUE klass_path;
124
+ curr_meth = rb_ary_new();
125
+
126
+ rb_frame_method_id_and_class(&mid, &klass);
127
+
128
+ if (mid == ID_ALLOCATOR)
129
+ return; //Qnil;
130
+ if (klass) {
131
+ if (TYPE(klass) == T_ICLASS) {
132
+ klass = RBASIC(klass)->klass;
133
+ }
134
+ else if (FL_TEST(klass, FL_SINGLETON)) {
135
+ klass = rb_iv_get(klass, "__attached__");
136
+ }
137
+ }
138
+
139
+ /*
140
+ klass = class << klass; self end unless klass === eval("self", binding)
141
+ */
142
+
143
+ klass_path = rb_class_path(klass);
144
+ VALUE reciever = rb_funcall(rb_binding_new(), rb_intern("eval"), 1, rb_str_new2("self"));
145
+ if (rb_funcall(klass, rb_intern("=="), 1, reciever) == Qtrue) {
146
+ klass_path = rb_sprintf("#<Class:%s>", RSTRING_PTR(klass_path));
147
+ OBJ_FREEZE(klass_path);
148
+ }
149
+
150
+ rb_ary_push(curr_meth, klass_path);
151
+ rb_ary_push(curr_meth, ID2SYM(mid));
152
+
153
+ args[0] = caller_ary;
154
+ args[1] = curr_meth;
155
+ rb_protect(record_callsite_info, (VALUE)args, &status);
156
+
157
+ if(!status) {
158
+ type_def_site args;
159
+
160
+ args.sourcefile = rb_sourcefile();
161
+ args.sourceline = rb_sourceline();
162
+ args.curr_meth = curr_meth;
163
+ rb_protect(record_method_def_site, (VALUE)&args, NULL);
164
+ }
165
+ if(status)
166
+ rb_gv_set("$!", Qnil);
167
+ }
168
+
169
+
170
+ static VALUE
171
+ cov_install_callsite_hook(VALUE self)
172
+ {
173
+ if(!callsite_hook_set_p) {
174
+ if(TYPE(caller_info) != T_HASH)
175
+ caller_info = rb_hash_new();
176
+ callsite_hook_set_p = 1;
177
+ VALUE something = 0;
178
+ rb_add_event_hook(coverage_event_callsite_hook,
179
+ RUBY_EVENT_CALL, something);
180
+ return Qtrue;
181
+ } else
182
+ return Qfalse;
183
+ }
184
+
185
+
186
+ static VALUE
187
+ cov_remove_callsite_hook(VALUE self)
188
+ {
189
+ if(!callsite_hook_set_p)
190
+ return Qfalse;
191
+ else {
192
+ rb_remove_event_hook(coverage_event_callsite_hook);
193
+ callsite_hook_set_p = 0;
194
+ return Qtrue;
195
+ }
196
+ }
197
+
198
+
199
+ static VALUE
200
+ cov_generate_callsite_info(VALUE self)
201
+ {
202
+ VALUE ret;
203
+
204
+ ret = rb_ary_new();
205
+ rb_ary_push(ret, caller_info);
206
+ rb_ary_push(ret, method_def_site_info);
207
+ return ret;
208
+ }
209
+
210
+
211
+ static VALUE
212
+ cov_reset_callsite(VALUE self)
213
+ {
214
+ if(callsite_hook_set_p) {
215
+ rb_raise(rb_eRuntimeError,
216
+ "Cannot reset the callsite info in the middle of a traced run.");
217
+ return Qnil;
218
+ }
219
+
220
+ caller_info = rb_hash_new();
221
+ method_def_site_info = rb_hash_new();
222
+ return Qnil;
223
+ }
224
+
225
+ void
226
+ Init_rcov_callsite()
227
+ {
228
+ VALUE mRcov;
229
+ VALUE mRCOV__;
230
+ ID id_rcov = rb_intern("Rcov");
231
+ ID id_coverage__ = rb_intern("RCOV__");
232
+ // ID id_script_lines__ = rb_intern("SCRIPT_LINES__");
233
+
234
+ if(rb_const_defined(rb_cObject, id_rcov))
235
+ mRcov = rb_const_get(rb_cObject, id_rcov);
236
+ else
237
+ mRcov = rb_define_module("Rcov");
238
+
239
+ if(rb_const_defined(mRcov, id_coverage__))
240
+ mRCOV__ = rb_const_get_at(mRcov, id_coverage__);
241
+ else
242
+ mRCOV__ = rb_define_module_under(mRcov, "RCOV__");
243
+
244
+ callsite_hook_set_p = 0;
245
+ caller_info = rb_hash_new();
246
+ method_def_site_info = rb_hash_new();
247
+ rb_gc_register_address(&caller_info);
248
+ rb_gc_register_address(&method_def_site_info);
249
+
250
+ rb_define_singleton_method(mRCOV__, "install_callsite_hook",
251
+ cov_install_callsite_hook, 0);
252
+ rb_define_singleton_method(mRCOV__, "remove_callsite_hook",
253
+ cov_remove_callsite_hook, 0);
254
+ rb_define_singleton_method(mRCOV__, "generate_callsite_info",
255
+ cov_generate_callsite_info, 0);
256
+ rb_define_singleton_method(mRCOV__, "reset_callsite", cov_reset_callsite, 0);
257
+ }
258
+ /* vim: set sw=8 expandtab: */