Ruby 2.7.7p221 (2022-11-24 revision 168ec2b1e5ad0e4688e963d9de019557c78feed9)
objspace_dump.c
Go to the documentation of this file.
1/**********************************************************************
2
3 objspace_dump.c - Heap dumping ObjectSpace extender for MRI.
4
5 $Author$
6 created at: Sat Oct 11 10:11:00 2013
7
8 NOTE: This extension library is not expected to exist except C Ruby.
9
10 All the files in this distribution are covered under the Ruby's
11 license (see the file COPYING).
12
13**********************************************************************/
14
15#include "ruby/io.h"
16#include "internal.h"
17#include "ruby/debug.h"
18#include "gc.h"
19#include "node.h"
20#include "vm_core.h"
21#include "objspace.h"
22
23static VALUE sym_output, sym_stdout, sym_string, sym_file;
24static VALUE sym_full;
25
30 const char *root_category;
34 unsigned int roots: 1;
35 unsigned int full_heap: 1;
36};
37
38PRINTF_ARGS(static void dump_append(struct dump_config *, const char *, ...), 2, 3);
39static void
40dump_append(struct dump_config *dc, const char *format, ...)
41{
42 va_list vl;
43 va_start(vl, format);
44
45 if (dc->stream) {
46 vfprintf(dc->stream, format, vl);
47 }
48 else if (dc->string)
49 rb_str_vcatf(dc->string, format, vl);
50
51 va_end(vl);
52}
53
54static void
55dump_append_string_value(struct dump_config *dc, VALUE obj)
56{
57 long i;
58 char c;
59 const char *value;
60
61 dump_append(dc, "\"");
62 for (i = 0, value = RSTRING_PTR(obj); i < RSTRING_LEN(obj); i++) {
63 switch ((c = value[i])) {
64 case '\\':
65 case '"':
66 dump_append(dc, "\\%c", c);
67 break;
68 case '\0':
69 dump_append(dc, "\\u0000");
70 break;
71 case '\b':
72 dump_append(dc, "\\b");
73 break;
74 case '\t':
75 dump_append(dc, "\\t");
76 break;
77 case '\f':
78 dump_append(dc, "\\f");
79 break;
80 case '\n':
81 dump_append(dc, "\\n");
82 break;
83 case '\r':
84 dump_append(dc, "\\r");
85 break;
86 default:
87 if (c <= 0x1f)
88 dump_append(dc, "\\u%04d", c);
89 else
90 dump_append(dc, "%c", c);
91 }
92 }
93 dump_append(dc, "\"");
94}
95
96static void
97dump_append_symbol_value(struct dump_config *dc, VALUE obj)
98{
99 dump_append(dc, "{\"type\":\"SYMBOL\", \"value\":");
100 dump_append_string_value(dc, rb_sym2str(obj));
101 dump_append(dc, "}");
102}
103
104static inline const char *
105obj_type(VALUE obj)
106{
107 switch (BUILTIN_TYPE(obj)) {
108#define CASE_TYPE(type) case T_##type: return #type
109 CASE_TYPE(NONE);
110 CASE_TYPE(NIL);
111 CASE_TYPE(OBJECT);
112 CASE_TYPE(CLASS);
113 CASE_TYPE(ICLASS);
114 CASE_TYPE(MODULE);
115 CASE_TYPE(FLOAT);
116 CASE_TYPE(STRING);
117 CASE_TYPE(REGEXP);
118 CASE_TYPE(ARRAY);
119 CASE_TYPE(HASH);
120 CASE_TYPE(STRUCT);
121 CASE_TYPE(BIGNUM);
123 CASE_TYPE(FIXNUM);
126 CASE_TYPE(DATA);
128 CASE_TYPE(SYMBOL);
129 CASE_TYPE(RATIONAL);
130 CASE_TYPE(COMPLEX);
131 CASE_TYPE(IMEMO);
134 CASE_TYPE(ZOMBIE);
135#undef CASE_TYPE
136 }
137 return "UNKNOWN";
138}
139
140static void
141dump_append_special_const(struct dump_config *dc, VALUE value)
142{
143 if (value == Qtrue) {
144 dump_append(dc, "true");
145 }
146 else if (value == Qfalse) {
147 dump_append(dc, "false");
148 }
149 else if (value == Qnil) {
150 dump_append(dc, "null");
151 }
152 else if (FIXNUM_P(value)) {
153 dump_append(dc, "%ld", FIX2LONG(value));
154 }
155 else if (FLONUM_P(value)) {
156 dump_append(dc, "%#g", RFLOAT_VALUE(value));
157 }
158 else if (SYMBOL_P(value)) {
159 dump_append_symbol_value(dc, value);
160 }
161 else {
162 dump_append(dc, "{}");
163 }
164}
165
166static void
167reachable_object_i(VALUE ref, void *data)
168{
169 struct dump_config *dc = (struct dump_config *)data;
170
171 if (dc->cur_obj_klass == ref)
172 return;
173
174 if (dc->cur_obj_references == 0)
175 dump_append(dc, ", \"references\":[\"%#"PRIxVALUE"\"", ref);
176 else
177 dump_append(dc, ", \"%#"PRIxVALUE"\"", ref);
178
179 dc->cur_obj_references++;
180}
181
182static void
183dump_append_string_content(struct dump_config *dc, VALUE obj)
184{
185 dump_append(dc, ", \"bytesize\":%ld", RSTRING_LEN(obj));
187 dump_append(dc, ", \"capacity\":%"PRIuSIZE, rb_str_capacity(obj));
188
189 if (is_ascii_string(obj)) {
190 dump_append(dc, ", \"value\":");
191 dump_append_string_value(dc, obj);
192 }
193}
194
195static const char *
196imemo_name(int imemo)
197{
198 switch(imemo) {
199#define TYPE_STR(t) case(imemo_##t): return #t
200 TYPE_STR(env);
201 TYPE_STR(cref);
202 TYPE_STR(svar);
203 TYPE_STR(throw_data);
204 TYPE_STR(ifunc);
205 TYPE_STR(memo);
206 TYPE_STR(ment);
207 TYPE_STR(iseq);
208 TYPE_STR(tmpbuf);
209 TYPE_STR(ast);
210 TYPE_STR(parser_strterm);
211 default:
212 return "unknown";
213#undef TYPE_STR
214 }
215}
216
217static void
218dump_object(VALUE obj, struct dump_config *dc)
219{
220 size_t memsize;
221 struct allocation_info *ainfo;
222 rb_io_t *fptr;
224 size_t n, i;
225
226 if (SPECIAL_CONST_P(obj)) {
227 dump_append_special_const(dc, obj);
228 return;
229 }
230
231 dc->cur_obj = obj;
232 dc->cur_obj_references = 0;
234
235 if (dc->cur_obj == dc->string)
236 return;
237
238 dump_append(dc, "{\"address\":\"%#"PRIxVALUE"\", \"type\":\"%s\"", obj, obj_type(obj));
239
240 if (dc->cur_obj_klass)
241 dump_append(dc, ", \"class\":\"%#"PRIxVALUE"\"", dc->cur_obj_klass);
242 if (rb_obj_frozen_p(obj))
243 dump_append(dc, ", \"frozen\":true");
244
245 switch (BUILTIN_TYPE(obj)) {
246 case T_NONE:
247 dump_append(dc, "}\n");
248 return;
249
250 case T_IMEMO:
251 dump_append(dc, ", \"imemo_type\":\"%s\"", imemo_name(imemo_type(obj)));
252 break;
253
254 case T_SYMBOL:
255 dump_append_string_content(dc, rb_sym2str(obj));
256 break;
257
258 case T_STRING:
259 if (STR_EMBED_P(obj))
260 dump_append(dc, ", \"embedded\":true");
262 dump_append(dc, ", \"broken\":true");
264 dump_append(dc, ", \"fstring\":true");
265 if (STR_SHARED_P(obj))
266 dump_append(dc, ", \"shared\":true");
267 else
268 dump_append_string_content(dc, obj);
269
271 dump_append(dc, ", \"encoding\":\"%s\"", rb_enc_name(rb_enc_from_index(ENCODING_GET(obj))));
272 break;
273
274 case T_HASH:
275 dump_append(dc, ", \"size\":%"PRIuSIZE, (size_t)RHASH_SIZE(obj));
277 dump_append(dc, ", \"default\":\"%#"PRIxVALUE"\"", RHASH_IFNONE(obj));
278 break;
279
280 case T_ARRAY:
281 dump_append(dc, ", \"length\":%ld", RARRAY_LEN(obj));
282 if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, ELTS_SHARED))
283 dump_append(dc, ", \"shared\":true");
285 dump_append(dc, ", \"embedded\":true");
286 break;
287
288 case T_CLASS:
289 case T_MODULE:
290 if (dc->cur_obj_klass)
291 dump_append(dc, ", \"name\":\"%s\"", rb_class2name(obj));
292 break;
293
294 case T_DATA:
295 if (RTYPEDDATA_P(obj))
296 dump_append(dc, ", \"struct\":\"%s\"", RTYPEDDATA_TYPE(obj)->wrap_struct_name);
297 break;
298
299 case T_FLOAT:
300 dump_append(dc, ", \"value\":\"%g\"", RFLOAT_VALUE(obj));
301 break;
302
303 case T_OBJECT:
304 dump_append(dc, ", \"ivars\":%u", ROBJECT_NUMIV(obj));
305 break;
306
307 case T_FILE:
308 fptr = RFILE(obj)->fptr;
309 if (fptr)
310 dump_append(dc, ", \"fd\":%d", fptr->fd);
311 break;
312
313 case T_ZOMBIE:
314 dump_append(dc, "}\n");
315 return;
316 }
317
318 rb_objspace_reachable_objects_from(obj, reachable_object_i, dc);
319 if (dc->cur_obj_references > 0)
320 dump_append(dc, "]");
321
322 if ((ainfo = objspace_lookup_allocation_info(obj))) {
323 dump_append(dc, ", \"file\":\"%s\", \"line\":%lu", ainfo->path, ainfo->line);
324 if (RTEST(ainfo->mid)) {
325 VALUE m = rb_sym2str(ainfo->mid);
326 dump_append(dc, ", \"method\":\"%s\"", RSTRING_PTR(m));
327 }
328 dump_append(dc, ", \"generation\":%"PRIuSIZE, ainfo->generation);
329 }
330
331 if ((memsize = rb_obj_memsize_of(obj)) > 0)
332 dump_append(dc, ", \"memsize\":%"PRIuSIZE, memsize);
333
334 if ((n = rb_obj_gc_flags(obj, flags, sizeof(flags))) > 0) {
335 dump_append(dc, ", \"flags\":{");
336 for (i=0; i<n; i++) {
337 dump_append(dc, "\"%s\":true", rb_id2name(flags[i]));
338 if (i != n-1) dump_append(dc, ", ");
339 }
340 dump_append(dc, "}");
341 }
342
343 dump_append(dc, "}\n");
344}
345
346static int
347heap_i(void *vstart, void *vend, size_t stride, void *data)
348{
349 struct dump_config *dc = (struct dump_config *)data;
350 VALUE v = (VALUE)vstart;
351 for (; v != (VALUE)vend; v += stride) {
352 if (dc->full_heap || RBASIC(v)->flags)
353 dump_object(v, dc);
354 }
355 return 0;
356}
357
358static void
359root_obj_i(const char *category, VALUE obj, void *data)
360{
361 struct dump_config *dc = (struct dump_config *)data;
362
363 if (dc->root_category != NULL && category != dc->root_category)
364 dump_append(dc, "]}\n");
365 if (dc->root_category == NULL || category != dc->root_category)
366 dump_append(dc, "{\"type\":\"ROOT\", \"root\":\"%s\", \"references\":[\"%#"PRIxVALUE"\"", category, obj);
367 else
368 dump_append(dc, ", \"%#"PRIxVALUE"\"", obj);
369
370 dc->root_category = category;
371 dc->roots = 1;
372}
373
374static VALUE
375dump_output(struct dump_config *dc, VALUE opts, VALUE output, const char *filename)
376{
377 VALUE tmp;
378
379 dc->full_heap = 0;
380
381 if (RTEST(opts)) {
382 output = rb_hash_aref(opts, sym_output);
383
384 if (Qtrue == rb_hash_lookup2(opts, sym_full, Qfalse))
385 dc->full_heap = 1;
386 }
387
388 if (output == sym_stdout) {
389 dc->stream = stdout;
390 dc->string = Qnil;
391 }
392 else if (output == sym_file) {
393 rb_io_t *fptr;
394 rb_require("tempfile");
395 tmp = rb_assoc_new(rb_str_new_cstr(filename), rb_str_new_cstr(".json"));
396 tmp = rb_funcallv(rb_path2class("Tempfile"), rb_intern("create"), 1, &tmp);
397 io:
398 dc->string = rb_io_get_write_io(tmp);
399 rb_io_flush(dc->string);
400 GetOpenFile(dc->string, fptr);
401 dc->stream = rb_io_stdio_file(fptr);
402 }
403 else if (output == sym_string) {
404 dc->string = rb_str_new_cstr("");
405 }
406 else if (!NIL_P(tmp = rb_io_check_io(output))) {
407 output = sym_file;
408 goto io;
409 }
410 else {
411 rb_raise(rb_eArgError, "wrong output option: %"PRIsVALUE, output);
412 }
413 return output;
414}
415
416static VALUE
417dump_result(struct dump_config *dc, VALUE output)
418{
419 if (output == sym_string) {
420 return rb_str_resurrect(dc->string);
421 }
422 else if (output == sym_file) {
423 rb_io_flush(dc->string);
424 return dc->string;
425 }
426 else {
427 return Qnil;
428 }
429}
430
431/*
432 * call-seq:
433 * ObjectSpace.dump(obj[, output: :string]) # => "{ ... }"
434 * ObjectSpace.dump(obj, output: :file) # => #<File:/tmp/rubyobj20131125-88733-1xkfmpv.json>
435 * ObjectSpace.dump(obj, output: :stdout) # => nil
436 *
437 * Dump the contents of a ruby object as JSON.
438 *
439 * This method is only expected to work with C Ruby.
440 * This is an experimental method and is subject to change.
441 * In particular, the function signature and output format are
442 * not guaranteed to be compatible in future versions of ruby.
443 */
444
445static VALUE
446objspace_dump(int argc, VALUE *argv, VALUE os)
447{
448 static const char filename[] = "rubyobj";
449 VALUE obj = Qnil, opts = Qnil, output;
450 struct dump_config dc = {0,};
451
452 rb_scan_args(argc, argv, "1:", &obj, &opts);
453
454 output = dump_output(&dc, opts, sym_string, filename);
455
456 dump_object(obj, &dc);
457
458 return dump_result(&dc, output);
459}
460
461/*
462 * call-seq:
463 * ObjectSpace.dump_all([output: :file]) # => #<File:/tmp/rubyheap20131125-88469-laoj3v.json>
464 * ObjectSpace.dump_all(output: :stdout) # => nil
465 * ObjectSpace.dump_all(output: :string) # => "{...}\n{...}\n..."
466 * ObjectSpace.dump_all(output:
467 * File.open('heap.json','w')) # => #<File:heap.json>
468 *
469 * Dump the contents of the ruby heap as JSON.
470 *
471 * This method is only expected to work with C Ruby.
472 * This is an experimental method and is subject to change.
473 * In particular, the function signature and output format are
474 * not guaranteed to be compatible in future versions of ruby.
475 */
476
477static VALUE
478objspace_dump_all(int argc, VALUE *argv, VALUE os)
479{
480 static const char filename[] = "rubyheap";
481 VALUE opts = Qnil, output;
482 struct dump_config dc = {0,};
483
484 rb_scan_args(argc, argv, "0:", &opts);
485
486 output = dump_output(&dc, opts, sym_file, filename);
487
488 /* dump roots */
490 if (dc.roots) dump_append(&dc, "]}\n");
491
492 /* dump all objects */
493 rb_objspace_each_objects(heap_i, &dc);
494
495 return dump_result(&dc, output);
496}
497
498void
500{
501#undef rb_intern
502#if 0
503 rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */
504#endif
505
506 rb_define_module_function(rb_mObjSpace, "dump", objspace_dump, -1);
507 rb_define_module_function(rb_mObjSpace, "dump_all", objspace_dump_all, -1);
508
509 sym_output = ID2SYM(rb_intern("output"));
510 sym_stdout = ID2SYM(rb_intern("stdout"));
511 sym_string = ID2SYM(rb_intern("string"));
512 sym_file = ID2SYM(rb_intern("file"));
513 sym_full = ID2SYM(rb_intern("full"));
514
515 /* force create static IDs */
516 rb_obj_gc_flags(rb_mObjSpace, 0, 0);
517}
union @73::@75 imemo
rb_encoding * rb_enc_from_index(int index)
Definition: encoding.c:609
#define rb_enc_name(enc)
Definition: encoding.h:177
#define ENCODING_GET(obj)
Definition: encoding.h:62
#define ENCODING_IS_ASCII8BIT(obj)
Definition: encoding.h:63
#define MATCH(s)
VALUE rb_define_module(const char *)
Definition: class.c:785
@ RARRAY_EMBED_FLAG
Definition: ruby.h:1029
@ RSTRING_FSTR
Definition: ruby.h:983
void rb_raise(VALUE exc, const char *fmt,...)
Definition: error.c:2671
VALUE rb_eArgError
Definition: error.c:925
VALUE rb_obj_frozen_p(VALUE obj)
Determines if the object is frozen.
Definition: object.c:1099
FILE * rb_io_stdio_file(rb_io_t *fptr)
Definition: io.c:8036
#define GetOpenFile(obj, fp)
Definition: io.h:127
VALUE rb_io_check_io(VALUE io)
Definition: io.c:739
VALUE rb_io_get_write_io(VALUE io)
Definition: io.c:745
struct allocation_info * objspace_lookup_allocation_info(VALUE obj)
#define CASE_TYPE(type)
PRINTF_ARGS(static void dump_append(struct dump_config *, const char *,...), 2, 3)
void Init_objspace_dump(VALUE rb_mObjSpace)
#define TYPE_STR(t)
#define RARRAY_LEN(a)
#define is_ascii_string(str)
#define NULL
#define STR_EMBED_P(str)
#define rb_funcallv(recv, mid, argc, argv)
#define T_FILE
#define stdout
#define RSTRING_LEN(str)
void rb_objspace_each_objects(int(*callback)(void *start, void *end, size_t stride, void *data), void *data)
void rb_objspace_reachable_objects_from_root(void(func)(const char *category, VALUE, void *), void *data)
Definition: gc.c:9509
#define RTEST(v)
#define FL_TEST(x, f)
#define RBASIC(obj)
#define T_STRING
VALUE rb_assoc_new(VALUE, VALUE)
Definition: array.c:896
VALUE rb_hash_aref(VALUE, VALUE)
Definition: hash.c:2037
#define RHASH_IFNONE(h)
VALUE rb_path2class(const char *)
Definition: variable.c:268
#define PRIuSIZE
size_t rb_obj_memsize_of(VALUE)
Definition: gc.c:3950
int int int int int int vfprintf(FILE *__restrict__, const char *__restrict__, __gnuc_va_list) __attribute__((__format__(__printf__
const VALUE VALUE obj
#define T_FLOAT
#define RSTRING_PTR(str)
#define T_IMEMO
VALUE VALUE rb_str_vcatf(VALUE, const char *, va_list)
Definition: sprintf.c:1210
size_t rb_obj_gc_flags(VALUE, ID[], size_t)
#define STR_SHARED_P(s)
#define NIL_P(v)
#define ID2SYM(x)
const char * rb_id2name(ID)
Definition: symbol.c:801
#define ELTS_SHARED
const char size_t n
#define T_DATA
const char const char *typedef unsigned long VALUE
#define T_NONE
#define T_NODE
VALUE rb_sym2str(VALUE)
Definition: symbol.c:784
VALUE rb_hash_lookup2(VALUE, VALUE, VALUE)
Definition: hash.c:2050
VALUE rb_str_resurrect(VALUE str)
Definition: string.c:1522
#define RB_OBJ_GC_FLAGS_MAX
#define T_MODULE
uint32_t i
void rb_objspace_reachable_objects_from(VALUE obj, void(func)(VALUE, void *), void *data)
Definition: gc.c:9481
VALUE rb_io_flush(VALUE)
Definition: io.c:1903
void rb_define_module_function(VALUE, const char *, VALUE(*)(), int)
#define va_end(v)
#define FLONUM_P(x)
#define T_HASH
__gnuc_va_list va_list
#define is_broken_string(str)
#define PRIsVALUE
int VALUE v
#define rb_scan_args(argc, argvp, fmt,...)
#define rb_intern(str)
VALUE rb_require(const char *)
Definition: load.c:1161
#define va_start(v, l)
#define PRIxVALUE
const rb_iseq_t * iseq
#define RFLOAT_VALUE(v)
#define RTYPEDDATA_TYPE(v)
#define TRUE
#define FALSE
#define RHASH_SIZE(h)
#define Qtrue
#define T_ZOMBIE
#define Qnil
#define Qfalse
#define T_ARRAY
#define T_OBJECT
#define RTYPEDDATA_P(v)
#define SPECIAL_CONST_P(x)
#define RFILE(obj)
#define T_SYMBOL
const VALUE * argv
#define SYMBOL_P(x)
#define FIXNUM_P(f)
#define T_CLASS
#define RBASIC_CLASS(obj)
unsigned long ID
#define FIX2LONG(x)
#define BUILTIN_TYPE(x)
#define rb_str_new_cstr(str)
const char * rb_class2name(VALUE)
Definition: variable.c:280
#define ROBJECT_NUMIV(o)
unsigned long VALUE
Definition: ruby.h:102
size_t rb_str_capacity(VALUE str)
Definition: string.c:712
VALUE flags
Definition: objspace.h:8
unsigned long line
Definition: objspace.h:13
const char * path
Definition: objspace.h:12
size_t generation
Definition: objspace.h:16
unsigned int roots
Definition: objspace_dump.c:34
size_t cur_obj_references
Definition: objspace_dump.c:33
unsigned int full_heap
Definition: objspace_dump.c:35
VALUE cur_obj_klass
Definition: objspace_dump.c:32
FILE * stream
Definition: objspace_dump.c:28
const char * root_category
Definition: objspace_dump.c:30
Definition: io.h:66
int fd
Definition: io.h:68
#define UNDEF
#define env