Ruby  3.1.0dev(2021-09-10revisionb76ad15ed0da636161de0243c547ee1e6fc95681)
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 
18 static int current_mode;
19 static VALUE me2counter = Qnil;
20 
21 /*
22  * call-seq:
23  * Coverage.start => nil
24  *
25  * Enables coverage measurement.
26  */
27 static VALUE
28 rb_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")))))
46  mode |= COVERAGE_TARGET_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"))))) {
52  if (mode & COVERAGE_TARGET_LINES)
53  rb_raise(rb_eRuntimeError, "cannot enable lines and oneshot_lines simultaneously");
54  mode |= COVERAGE_TARGET_LINES;
56  }
57  }
58 
59  if (mode & COVERAGE_TARGET_METHODS) {
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;
71  if (mode == 0) mode = COVERAGE_TARGET_LINES;
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 
88 static int
89 branch_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 
104 static int
105 branch_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 
123 static VALUE
124 branch_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 
138 static int
139 method_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 
204 static int
205 coverage_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 */
212  VALUE lines = rb_ary_dup(RARRAY_AREF(coverage, COVERAGE_INDEX_LINES));
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  */
255 static VALUE
256 rb_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 
275 static int
276 clear_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  */
290 static VALUE
291 rb_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  */
329 static VALUE
330 rb_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  */
479 void
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 }
rb_obj_hide
VALUE rb_obj_hide(VALUE obj)
Make the object invisible from Ruby code.
Definition: object.c:93
hash.h
Internal header for Hash.
rb_ident_hash_new
VALUE rb_ident_hash_new(void)
Definition: hash.c:4402
rb_hash_new
VALUE rb_hash_new(void)
Definition: hash.c:1526
rb_warn
void rb_warn(const char *fmt,...)
Definition: error.c:414
LONG2FIX
#define LONG2FIX
Definition: long.h:49
gc.h
rb_clear_coverages
void rb_clear_coverages(void)
Definition: thread.c:4729
rb_intern
ID rb_intern(const char *)
Definition: symbol.c:784
imemo_ment
@ imemo_ment
Definition: imemo.h:41
rb_global_variable
void rb_global_variable(VALUE *var)
An alias for rb_gc_register_address().
Definition: gc.c:8842
branch_coverage_result_builder::id
int id
Definition: coverage.c:82
rb_get_coverages
VALUE rb_get_coverages(void)
Definition: thread.c:5757
rb_hash_aref
VALUE rb_hash_aref(VALUE hash, VALUE key)
Definition: hash.c:2071
branch_coverage_result_builder::result
VALUE result
Definition: coverage.c:83
rb_reset_coverages
void rb_reset_coverages(void)
Definition: thread.c:5784
INT2FIX
#define INT2FIX
Definition: long.h:48
rb_define_module
VALUE rb_define_module(const char *name)
Definition: class.c:887
FIX2LONG
#define FIX2LONG
Definition: long.h:46
argv
char ** argv
Definition: ruby.c:243
COVERAGE_TARGET_LINES
#define COVERAGE_TARGET_LINES
Definition: thread.h:20
FIXNUM_MAX
#define FIXNUM_MAX
Definition: fixnum.h:26
branch_coverage_result_builder
Definition: coverage.c:80
rb_method_entry_struct::owner
VALUE owner
Definition: method.h:59
rb_set_coverages
void rb_set_coverages(VALUE coverages, int mode, VALUE me2counter)
Definition: thread.c:5769
rb_resolve_me_location
const rb_method_entry_t * rb_resolve_me_location(const rb_method_entry_t *, VALUE[5])
Definition: thread.c:5678
thread.h
Internal header for Thread.
T_ICLASS
#define T_ICLASS
Definition: value_type.h:66
branch_coverage_result_builder::children
VALUE children
Definition: coverage.c:84
rb_raise
void rb_raise(VALUE exc, const char *fmt,...)
Definition: error.c:3022
NIL_P
#define NIL_P
Definition: special_consts.h:46
COVERAGE_INDEX_LINES
#define COVERAGE_INDEX_LINES
Definition: thread.h:18
T_HASH
#define T_HASH
Definition: value_type.h:65
Qfalse
#define Qfalse
Definition: special_consts.h:50
RHASH_TBL_RAW
#define RHASH_TBL_RAW(h)
Definition: hash.h:118
imemo_type
imemo_type
Definition: imemo.h:34
COVERAGE_INDEX_BRANCHES
#define COVERAGE_INDEX_BRANCHES
Definition: thread.h:19
rb_hash_lookup
VALUE rb_hash_lookup(VALUE hash, VALUE key)
Definition: hash.c:2097
ruby.h
Qnil
#define Qnil
Definition: special_consts.h:51
vm_core.h
st_data_t
unsigned long st_data_t
Definition: st.h:22
rb_eRuntimeError
VALUE rb_eRuntimeError
Definition: error.c:1091
branch_coverage_result_builder::counters
VALUE counters
Definition: coverage.c:85
COVERAGE_TARGET_METHODS
#define COVERAGE_TARGET_METHODS
Definition: thread.h:22
rb_hash_foreach
void rb_hash_foreach(VALUE hash, rb_foreach_func *func, VALUE farg)
Definition: hash.c:1481
rb_scan_args
int rb_scan_args(int argc, const VALUE *argv, const char *fmt,...)
Definition: class.c:2347
RTEST
#define RTEST
Definition: special_consts.h:42
key
key
Definition: openssl_missing.h:145
Qtrue
#define Qtrue
Definition: special_consts.h:52
COVERAGE_TARGET_BRANCHES
#define COVERAGE_TARGET_BRANCHES
Definition: thread.h:21
Init_coverage
void Init_coverage(void)
Definition: coverage.c:480
OBJ_WB_UNPROTECT
#define OBJ_WB_UNPROTECT
Definition: rgengc.h:120
st_foreach
int st_foreach(st_table *tab, st_foreach_callback_func *func, st_data_t arg)
Definition: st.c:1574
sanitizers.h
Internal header for ASAN / MSAN / etc.
VALUE
unsigned long VALUE
Definition: value.h:38
rb_objspace_each_objects
void rb_objspace_each_objects(each_obj_callback *callback, void *data)
Definition: gc.c:3633
rb_bug
void rb_bug(const char *fmt,...)
Definition: error.c:796
ST_CONTINUE
@ ST_CONTINUE
Definition: st.h:99
rb_convert_type
VALUE rb_convert_type(VALUE, int, const char *, const char *)
Converts an object into another type.
Definition: object.c:2939
rb_hash_aset
VALUE rb_hash_aset(VALUE hash, VALUE key, VALUE val)
Definition: hash.c:2892
argc
int argc
Definition: ruby.c:242
POSFIXABLE
#define POSFIXABLE
Definition: fixnum.h:29
RARRAY_AREF
#define RARRAY_AREF(a, i)
Definition: missing.h:201
rb_ary_freeze
VALUE rb_ary_freeze(VALUE ary)
Definition: array.c:679
rb_method_definition_struct::original_id
ID original_id
Definition: method.h:188
COVERAGE_TARGET_ONESHOT_LINES
#define COVERAGE_TARGET_ONESHOT_LINES
Definition: thread.h:23
rb_ary_new_from_args
#define rb_ary_new_from_args(...)
Definition: internal.h:65
rb_method_entry_struct
Definition: method.h:54
ID2SYM
#define ID2SYM
Definition: symbol.h:44
rb_ary_dup
VALUE rb_ary_dup(VALUE ary)
Definition: array.c:2675
rb_define_module_function
#define rb_define_module_function(klass, mid, func, arity)
Defines klass#mid and makes it a module function.
Definition: cxxanyargs.hpp:674
rb_method_entry_struct::def
struct rb_method_definition_struct *const def
Definition: method.h:57
T_IMEMO
#define T_IMEMO
Definition: value_type.h:67
rb_hash_freeze
VALUE rb_hash_freeze(VALUE hash)
Definition: hash.c:84