Ruby 3.0.5p211 (2022-11-24 revision ba5cf0f7c52d4d35cc6a173c89eda98ceffa2dcf)
coverage.c
Go to the documentation of this file.
1/************************************************
2
3 coverage.c -
4
5 $Author: $
6
7 Copyright (c) 2008 Yusuke Endoh
8
9************************************************/
10
11#include "gc.h"
12#include "internal/hash.h"
13#include "internal/thread.h"
14#include "internal/sanitizers.h"
15#include "ruby.h"
16#include "vm_core.h"
17
18static int current_mode;
19static VALUE me2counter = Qnil;
20
21/*
22 * call-seq:
23 * Coverage.start => nil
24 *
25 * Enables coverage measurement.
26 */
27static VALUE
28rb_coverage_start(int argc, VALUE *argv, VALUE klass)
29{
30 VALUE coverages, opt;
31 int mode;
32
33 rb_scan_args(argc, argv, "01", &opt);
34
35 if (argc == 0) {
36 mode = 0; /* compatible mode */
37 }
38 else if (opt == ID2SYM(rb_intern("all"))) {
40 }
41 else {
42 mode = 0;
43 opt = rb_convert_type(opt, T_HASH, "Hash", "to_hash");
44
45 if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("lines")))))
47 if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("branches")))))
49 if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("methods")))))
51 if (RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("oneshot_lines"))))) {
53 rb_raise(rb_eRuntimeError, "cannot enable lines and oneshot_lines simultaneously");
56 }
57 }
58
60 me2counter = rb_ident_hash_new();
61 }
62 else {
63 me2counter = Qnil;
64 }
65
66 coverages = rb_get_coverages();
67 if (!RTEST(coverages)) {
68 coverages = rb_hash_new();
69 rb_obj_hide(coverages);
70 current_mode = mode;
72 rb_set_coverages(coverages, mode, me2counter);
73 }
74 else if (current_mode != mode) {
75 rb_raise(rb_eRuntimeError, "cannot change the measuring target during coverage measurement");
76 }
77 return Qnil;
78}
79
81{
82 int id;
86};
87
88static int
89branch_coverage_ii(VALUE _key, VALUE branch, VALUE v)
90{
92
93 VALUE target_label = RARRAY_AREF(branch, 0);
94 VALUE target_first_lineno = RARRAY_AREF(branch, 1);
95 VALUE target_first_column = RARRAY_AREF(branch, 2);
96 VALUE target_last_lineno = RARRAY_AREF(branch, 3);
97 VALUE target_last_column = RARRAY_AREF(branch, 4);
98 long counter_idx = FIX2LONG(RARRAY_AREF(branch, 5));
99 rb_hash_aset(b->children, rb_ary_new_from_args(6, target_label, LONG2FIX(b->id++), target_first_lineno, target_first_column, target_last_lineno, target_last_column), RARRAY_AREF(b->counters, counter_idx));
100
101 return ST_CONTINUE;
102}
103
104static int
105branch_coverage_i(VALUE _key, VALUE branch_base, VALUE v)
106{
108
109 VALUE base_type = RARRAY_AREF(branch_base, 0);
110 VALUE base_first_lineno = RARRAY_AREF(branch_base, 1);
111 VALUE base_first_column = RARRAY_AREF(branch_base, 2);
112 VALUE base_last_lineno = RARRAY_AREF(branch_base, 3);
113 VALUE base_last_column = RARRAY_AREF(branch_base, 4);
114 VALUE branches = RARRAY_AREF(branch_base, 5);
116 rb_hash_aset(b->result, rb_ary_new_from_args(6, base_type, LONG2FIX(b->id++), base_first_lineno, base_first_column, base_last_lineno, base_last_column), children);
117 b->children = children;
118 rb_hash_foreach(branches, branch_coverage_ii, v);
119
120 return ST_CONTINUE;
121}
122
123static VALUE
124branch_coverage(VALUE branches)
125{
126 VALUE structure = RARRAY_AREF(branches, 0);
127
129 b.id = 0;
130 b.result = rb_hash_new();
131 b.counters = RARRAY_AREF(branches, 1);
132
133 rb_hash_foreach(structure, branch_coverage_i, (VALUE)&b);
134
135 return b.result;
136}
137
138static int
139method_coverage_i(void *vstart, void *vend, size_t stride, void *data)
140{
141 /*
142 * ObjectSpace.each_object(Module){|mod|
143 * mod.instance_methods.each{|mid|
144 * m = mod.instance_method(mid)
145 * if loc = m.source_location
146 * p [m.name, loc, $g_method_cov_counts[m]]
147 * end
148 * }
149 * }
150 */
151 VALUE ncoverages = *(VALUE*)data, v;
152
153 for (v = (VALUE)vstart; v != (VALUE)vend; v += stride) {
154 void *poisoned = asan_poisoned_object_p(v);
155 asan_unpoison_object(v, false);
156
157 if (RB_TYPE_P(v, T_IMEMO) && imemo_type(v) == imemo_ment) {
158 const rb_method_entry_t *me = (rb_method_entry_t *) v;
159 VALUE path, first_lineno, first_column, last_lineno, last_column;
160 VALUE data[5], ncoverage, methods;
161 VALUE methods_id = ID2SYM(rb_intern("methods"));
162 VALUE klass;
163 const rb_method_entry_t *me2 = rb_resolve_me_location(me, data);
164 if (me != me2) continue;
165 klass = me->owner;
166 if (RB_TYPE_P(klass, T_ICLASS)) {
167 rb_bug("T_ICLASS");
168 }
169 path = data[0];
170 first_lineno = data[1];
171 first_column = data[2];
172 last_lineno = data[3];
173 last_column = data[4];
174 if (FIX2LONG(first_lineno) <= 0) continue;
175 ncoverage = rb_hash_aref(ncoverages, path);
176 if (NIL_P(ncoverage)) continue;
177 methods = rb_hash_aref(ncoverage, methods_id);
178
179 {
180 VALUE method_id = ID2SYM(me->def->original_id);
181 VALUE rcount = rb_hash_aref(me2counter, (VALUE) me);
182 VALUE key = rb_ary_new_from_args(6, klass, method_id, first_lineno, first_column, last_lineno, last_column);
183 VALUE rcount2 = rb_hash_aref(methods, key);
184
185 if (NIL_P(rcount)) rcount = LONG2FIX(0);
186 if (NIL_P(rcount2)) rcount2 = LONG2FIX(0);
187 if (!POSFIXABLE(FIX2LONG(rcount) + FIX2LONG(rcount2))) {
188 rcount = LONG2FIX(FIXNUM_MAX);
189 }
190 else {
191 rcount = LONG2FIX(FIX2LONG(rcount) + FIX2LONG(rcount2));
192 }
193 rb_hash_aset(methods, key, rcount);
194 }
195 }
196
197 if (poisoned) {
198 asan_poison_object(v);
199 }
200 }
201 return 0;
202}
203
204static int
205coverage_peek_result_i(st_data_t key, st_data_t val, st_data_t h)
206{
207 VALUE path = (VALUE)key;
208 VALUE coverage = (VALUE)val;
209 VALUE coverages = (VALUE)h;
210 if (current_mode == 0) {
211 /* compatible mode */
213 rb_ary_freeze(lines);
214 coverage = lines;
215 }
216 else {
217 VALUE h = rb_hash_new();
218
219 if (current_mode & COVERAGE_TARGET_LINES) {
220 VALUE lines = RARRAY_AREF(coverage, COVERAGE_INDEX_LINES);
221 const char *kw = (current_mode & COVERAGE_TARGET_ONESHOT_LINES) ? "oneshot_lines" : "lines";
222 lines = rb_ary_dup(lines);
223 rb_ary_freeze(lines);
224 rb_hash_aset(h, ID2SYM(rb_intern(kw)), lines);
225 }
226
227 if (current_mode & COVERAGE_TARGET_BRANCHES) {
228 VALUE branches = RARRAY_AREF(coverage, COVERAGE_INDEX_BRANCHES);
229 rb_hash_aset(h, ID2SYM(rb_intern("branches")), branch_coverage(branches));
230 }
231
232 if (current_mode & COVERAGE_TARGET_METHODS) {
233 rb_hash_aset(h, ID2SYM(rb_intern("methods")), rb_hash_new());
234 }
235
236 coverage = h;
237 }
238
239 rb_hash_aset(coverages, path, coverage);
240 return ST_CONTINUE;
241}
242
243/*
244 * call-seq:
245 * Coverage.peek_result => hash
246 *
247 * Returns a hash that contains filename as key and coverage array as value.
248 * This is the same as `Coverage.result(stop: false, clear: false)`.
249 *
250 * {
251 * "file.rb" => [1, 2, nil],
252 * ...
253 * }
254 */
255static VALUE
256rb_coverage_peek_result(VALUE klass)
257{
258 VALUE coverages = rb_get_coverages();
259 VALUE ncoverages = rb_hash_new();
260 if (!RTEST(coverages)) {
261 rb_raise(rb_eRuntimeError, "coverage measurement is not enabled");
262 }
263 OBJ_WB_UNPROTECT(coverages);
264 st_foreach(RHASH_TBL_RAW(coverages), coverage_peek_result_i, ncoverages);
265
266 if (current_mode & COVERAGE_TARGET_METHODS) {
267 rb_objspace_each_objects(method_coverage_i, &ncoverages);
268 }
269
270 rb_hash_freeze(ncoverages);
271 return ncoverages;
272}
273
274
275static int
276clear_me2counter_i(VALUE key, VALUE value, VALUE unused)
277{
278 rb_hash_aset(me2counter, key, INT2FIX(0));
279 return ST_CONTINUE;
280}
281
282/*
283 * call-seq:
284 * Coverage.result(stop: true, clear: true) => hash
285 *
286 * Returns a hash that contains filename as key and coverage array as value.
287 * If +clear+ is true, it clears the counters to zero.
288 * If +stop+ is true, it disables coverage measurement.
289 */
290static VALUE
291rb_coverage_result(int argc, VALUE *argv, VALUE klass)
292{
293 VALUE ncoverages;
294 VALUE opt;
295 int stop = 1, clear = 1;
296
297 rb_scan_args(argc, argv, "01", &opt);
298
299 if (argc == 1) {
300 opt = rb_convert_type(opt, T_HASH, "Hash", "to_hash");
301 stop = RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("stop"))));
302 clear = RTEST(rb_hash_lookup(opt, ID2SYM(rb_intern("clear"))));
303 }
304
305 ncoverages = rb_coverage_peek_result(klass);
306 if (stop && !clear) {
307 rb_warn("stop implies clear");
308 clear = 1;
309 }
310 if (clear) {
312 if (!NIL_P(me2counter)) rb_hash_foreach(me2counter, clear_me2counter_i, Qnil);
313 }
314 if (stop) {
316 me2counter = Qnil;
317 }
318 return ncoverages;
319}
320
321
322/*
323 * call-seq:
324 * Coverage.running? => bool
325 *
326 * Returns true if coverage stats are currently being collected (after
327 * Coverage.start call, but before Coverage.result call)
328 */
329static VALUE
330rb_coverage_running(VALUE klass)
331{
332 VALUE coverages = rb_get_coverages();
333 return RTEST(coverages) ? Qtrue : Qfalse;
334}
335
336/* Coverage provides coverage measurement feature for Ruby.
337 * This feature is experimental, so these APIs may be changed in future.
338 *
339 * = Usage
340 *
341 * 1. require "coverage"
342 * 2. do Coverage.start
343 * 3. require or load Ruby source file
344 * 4. Coverage.result will return a hash that contains filename as key and
345 * coverage array as value. A coverage array gives, for each line, the
346 * number of line execution by the interpreter. A +nil+ value means
347 * coverage is disabled for this line (lines like +else+ and +end+).
348 *
349 * = Examples
350 *
351 * [foo.rb]
352 * s = 0
353 * 10.times do |x|
354 * s += x
355 * end
356 *
357 * if s == 45
358 * p :ok
359 * else
360 * p :ng
361 * end
362 * [EOF]
363 *
364 * require "coverage"
365 * Coverage.start
366 * require "foo.rb"
367 * p Coverage.result #=> {"foo.rb"=>[1, 1, 10, nil, nil, 1, 1, nil, 0, nil]}
368 *
369 * == Lines Coverage
370 *
371 * If a coverage mode is not explicitly specified when starting coverage, lines
372 * coverage is what will run. It reports the number of line executions for each
373 * line.
374 *
375 * require "coverage"
376 * Coverage.start(lines: true)
377 * require "foo.rb"
378 * p Coverage.result #=> {"foo.rb"=>{:lines=>[1, 1, 10, nil, nil, 1, 1, nil, 0, nil]}}
379 *
380 * The value of the lines coverage result is an array containing how many times
381 * each line was executed. Order in this array is important. For example, the
382 * first item in this array, at index 0, reports how many times line 1 of this
383 * file was executed while coverage was run (which, in this example, is one
384 * time).
385 *
386 * A +nil+ value means coverage is disabled for this line (lines like +else+
387 * and +end+).
388 *
389 * == Oneshot Lines Coverage
390 *
391 * Oneshot lines coverage tracks and reports on the executed lines while
392 * coverage is running. It will not report how many times a line was executed,
393 * only that it was executed.
394 *
395 * require "coverage"
396 * Coverage.start(oneshot_lines: true)
397 * require "foo.rb"
398 * p Coverage.result #=> {"foo.rb"=>{:oneshot_lines=>[1, 2, 3, 6, 7]}}
399 *
400 * The value of the oneshot lines coverage result is an array containing the
401 * line numbers that were executed.
402 *
403 * == Branches Coverage
404 *
405 * Branches coverage reports how many times each branch within each conditional
406 * was executed.
407 *
408 * require "coverage"
409 * Coverage.start(branches: true)
410 * require "foo.rb"
411 * p Coverage.result #=> {"foo.rb"=>{:branches=>{[:if, 0, 6, 0, 10, 3]=>{[:then, 1, 7, 2, 7, 7]=>1, [:else, 2, 9, 2, 9, 7]=>0}}}}
412 *
413 * Each entry within the branches hash is a conditional, the value of which is
414 * another hash where each entry is a branch in that conditional. The values
415 * are the number of times the method was executed, and the keys are identifying
416 * information about the branch.
417 *
418 * The information that makes up each key identifying branches or conditionals
419 * is the following, from left to right:
420 *
421 * 1. A label for the type of branch or conditional.
422 * 2. A unique identifier.
423 * 3. The starting line number it appears on in the file.
424 * 4. The starting column number it appears on in the file.
425 * 5. The ending line number it appears on in the file.
426 * 6. The ending column number it appears on in the file.
427 *
428 * == Methods Coverage
429 *
430 * Methods coverage reports how many times each method was executed.
431 *
432 * [foo_method.rb]
433 * class Greeter
434 * def greet
435 * "welcome!"
436 * end
437 * end
438 *
439 * def hello
440 * "Hi"
441 * end
442 *
443 * hello()
444 * Greeter.new.greet()
445 * [EOF]
446 *
447 * require "coverage"
448 * Coverage.start(methods: true)
449 * require "foo_method.rb"
450 * p Coverage.result #=> {"foo_method.rb"=>{:methods=>{[Object, :hello, 7, 0, 9, 3]=>1, [Greeter, :greet, 2, 2, 4, 5]=>1}}}
451 *
452 * Each entry within the methods hash represents a method. The values in this
453 * hash are the number of times the method was executed, and the keys are
454 * identifying information about the method.
455 *
456 * The information that makes up each key identifying a method is the following,
457 * from left to right:
458 *
459 * 1. The class.
460 * 2. The method name.
461 * 3. The starting line number the method appears on in the file.
462 * 4. The starting column number the method appears on in the file.
463 * 5. The ending line number the method appears on in the file.
464 * 6. The ending column number the method appears on in the file.
465 *
466 * == All Coverage Modes
467 *
468 * You can also run all modes of coverage simultaneously with this shortcut.
469 * Note that running all coverage modes does not run both lines and oneshot
470 * lines. Those modes cannot be run simultaneously. Lines coverage is run in
471 * this case, because you can still use it to determine whether or not a line
472 * was executed.
473 *
474 * require "coverage"
475 * Coverage.start(:all)
476 * require "foo.rb"
477 * p Coverage.result #=> {"foo.rb"=>{:lines=>[1, 1, 10, nil, nil, 1, 1, nil, 0, nil], :branches=>{[:if, 0, 6, 0, 10, 3]=>{[:then, 1, 7, 2, 7, 7]=>1, [:else, 2, 9, 2, 9, 7]=>0}}, :methods=>{}}}
478 */
479void
481{
482 VALUE rb_mCoverage = rb_define_module("Coverage");
483 rb_define_module_function(rb_mCoverage, "start", rb_coverage_start, -1);
484 rb_define_module_function(rb_mCoverage, "result", rb_coverage_result, -1);
485 rb_define_module_function(rb_mCoverage, "peek_result", rb_coverage_peek_result, 0);
486 rb_define_module_function(rb_mCoverage, "running?", rb_coverage_running, 0);
487 rb_global_variable(&me2counter);
488}
VALUE rb_ary_dup(VALUE ary)
Definition: array.c:2666
VALUE rb_ary_freeze(VALUE ary)
Definition: array.c:674
void Init_coverage(void)
Definition: coverage.c:480
#define rb_define_module_function(klass, mid, func, arity)
Defines klass#mid and makes it a module function.
Definition: cxxanyargs.hpp:672
void rb_objspace_each_objects(each_obj_callback *callback, void *data)
Definition: gc.c:3285
VALUE rb_define_module(const char *name)
Definition: class.c:871
int rb_scan_args(int argc, const VALUE *argv, const char *fmt,...)
Definition: class.c:2296
void rb_raise(VALUE exc, const char *fmt,...)
Definition: error.c:2917
void rb_bug(const char *fmt,...)
Definition: error.c:768
VALUE rb_ident_hash_new(void)
Definition: hash.c:4443
VALUE rb_eRuntimeError
Definition: error.c:1055
void rb_warn(const char *fmt,...)
Definition: error.c:408
VALUE rb_convert_type(VALUE, int, const char *, const char *)
Converts an object into another type.
Definition: object.c:2930
VALUE rb_obj_hide(VALUE obj)
Make the object invisible from Ruby code.
Definition: object.c:92
void rb_hash_foreach(VALUE hash, rb_foreach_func *func, VALUE farg)
Definition: hash.c:1498
VALUE rb_hash_aref(VALUE hash, VALUE key)
Definition: hash.c:2046
VALUE rb_hash_freeze(VALUE hash)
Definition: hash.c:101
VALUE rb_hash_aset(VALUE hash, VALUE key, VALUE val)
Definition: hash.c:2901
VALUE rb_hash_lookup(VALUE hash, VALUE key)
Definition: hash.c:2072
VALUE rb_hash_new(void)
Definition: hash.c:1538
imemo_type
Definition: imemo.h:34
@ imemo_ment
Definition: imemo.h:41
#define FIXNUM_MAX
Definition: fixnum.h:26
#define POSFIXABLE
Definition: fixnum.h:29
#define ID2SYM
Definition: symbol.h:44
ID rb_intern(const char *)
Definition: symbol.c:785
Internal header for GC.
Internal header for Hash.
#define RHASH_TBL_RAW(h)
Definition: hash.h:118
Internal header for Thread.
VALUE rb_get_coverages(void)
Definition: thread.c:5773
#define COVERAGE_TARGET_METHODS
Definition: thread.h:22
#define COVERAGE_TARGET_BRANCHES
Definition: thread.h:21
#define COVERAGE_INDEX_BRANCHES
Definition: thread.h:19
#define COVERAGE_TARGET_ONESHOT_LINES
Definition: thread.h:23
#define COVERAGE_TARGET_LINES
Definition: thread.h:20
#define COVERAGE_INDEX_LINES
Definition: thread.h:18
#define rb_ary_new_from_args(...)
Definition: internal.h:65
const char int mode
Definition: ioapi.h:137
#define INT2FIX
Definition: long.h:48
#define LONG2FIX
Definition: long.h:49
#define FIX2LONG
Definition: long.h:46
const rb_method_entry_t * rb_resolve_me_location(const rb_method_entry_t *, VALUE[5])
Definition: thread.c:5694
#define RARRAY_AREF(a, i)
Definition: psych_emitter.c:7
#define OBJ_WB_UNPROTECT
Definition: rgengc.h:120
int argc
Definition: ruby.c:240
char ** argv
Definition: ruby.c:241
Internal header for ASAN / MSAN / etc.
#define Qtrue
#define RTEST
#define Qnil
#define Qfalse
#define NIL_P
@ ST_CONTINUE
Definition: st.h:99
unsigned long st_data_t
Definition: st.h:22
#define st_foreach
Definition: st.h:142
Definition: method.h:54
struct rb_method_definition_struct *const def
Definition: method.h:57
VALUE owner
Definition: method.h:59
void rb_clear_coverages(void)
Definition: thread.c:4739
void rb_set_coverages(VALUE coverages, int mode, VALUE me2counter)
Definition: thread.c:5785
void rb_reset_coverages(void)
Definition: thread.c:5800
unsigned long VALUE
Definition: value.h:38
#define T_IMEMO
Definition: value_type.h:66
#define T_ICLASS
Definition: value_type.h:65
#define T_HASH
Definition: value_type.h:64