diff options
Diffstat (limited to 'ext/objspace/objspace_dump.c')
-rw-r--r-- | ext/objspace/objspace_dump.c | 331 |
1 files changed, 270 insertions, 61 deletions
diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c index c3619a9656..bb479b91c5 100644 --- a/ext/objspace/objspace_dump.c +++ b/ext/objspace/objspace_dump.c @@ -12,16 +12,23 @@ **********************************************************************/ -#include "gc.h" +#include "id_table.h" #include "internal.h" +#include "internal/array.h" +#include "internal/class.h" +#include "internal/gc.h" #include "internal/hash.h" +#include "internal/io.h" #include "internal/string.h" #include "internal/sanitizers.h" +#include "symbol.h" +#include "shape.h" #include "node.h" #include "objspace.h" #include "ruby/debug.h" #include "ruby/util.h" #include "ruby/io.h" +#include "vm_callinfo.h" #include "vm_core.h" RUBY_EXTERN const char ruby_hexdigits[]; @@ -35,11 +42,13 @@ struct dump_config { const char *root_category; VALUE cur_obj; VALUE cur_obj_klass; + size_t cur_page_slot_size; size_t cur_obj_references; unsigned int roots: 1; unsigned int full_heap: 1; unsigned int partial_dump; size_t since; + size_t shapes_since; unsigned long buffer_len; char buffer[BUFFER_CAPACITY]; }; @@ -75,7 +84,8 @@ buffer_ensure_capa(struct dump_config *dc, unsigned long requested) } } -static void buffer_append(struct dump_config *dc, const char *cstr, unsigned long len) +static void +buffer_append(struct dump_config *dc, const char *cstr, unsigned long len) { if (LIKELY(len > 0)) { buffer_ensure_capa(dc, len); @@ -89,7 +99,7 @@ static void buffer_append(struct dump_config *dc, const char *cstr, unsigned lon static void dump_append_ld(struct dump_config *dc, const long number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%ld", number); RUBY_ASSERT(required <= width); @@ -99,7 +109,7 @@ dump_append_ld(struct dump_config *dc, const long number) static void dump_append_lu(struct dump_config *dc, const unsigned long number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%lu", number); RUBY_ASSERT(required <= width); @@ -123,7 +133,7 @@ dump_append_g(struct dump_config *dc, const double number) static void dump_append_d(struct dump_config *dc, const int number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT - 1) + 2; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%d", number); RUBY_ASSERT(required <= width); @@ -133,7 +143,7 @@ dump_append_d(struct dump_config *dc, const int number) static void dump_append_sizet(struct dump_config *dc, const size_t number) { - const int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; + const unsigned int width = DECIMAL_SIZE_OF_BITS(sizeof(number) * CHAR_BIT) + 1; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "%"PRIuSIZE, number); RUBY_ASSERT(required <= width); @@ -141,10 +151,10 @@ dump_append_sizet(struct dump_config *dc, const size_t number) } static void -dump_append_c(struct dump_config *dc, char c) +dump_append_c(struct dump_config *dc, unsigned char c) { if (c <= 0x1f) { - const int width = (sizeof(c) * CHAR_BIT / 4) + 5; + const unsigned int width = rb_strlen_lit("\\u0000") + 1; buffer_ensure_capa(dc, width); unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "\\u00%02x", c); RUBY_ASSERT(required <= width); @@ -158,11 +168,9 @@ dump_append_c(struct dump_config *dc, char c) } static void -dump_append_ref(struct dump_config *dc, VALUE ref) +dump_append_ptr(struct dump_config *dc, VALUE ref) { - RUBY_ASSERT(ref > 0); - - char buffer[((sizeof(VALUE) * CHAR_BIT + 3) / 4) + 4]; + char buffer[roomof(sizeof(VALUE) * CHAR_BIT, 4) + rb_strlen_lit("\"0x\"")]; char *buffer_start, *buffer_end; buffer_start = buffer_end = &buffer[sizeof(buffer)]; @@ -178,6 +186,14 @@ dump_append_ref(struct dump_config *dc, VALUE ref) } static void +dump_append_ref(struct dump_config *dc, VALUE ref) +{ + RUBY_ASSERT(ref > 0); + dump_append_ptr(dc, ref); +} + + +static void dump_append_string_value(struct dump_config *dc, VALUE obj) { long i; @@ -234,32 +250,32 @@ obj_type(VALUE obj) { switch (BUILTIN_TYPE(obj)) { #define CASE_TYPE(type) case T_##type: return #type - CASE_TYPE(NONE); - CASE_TYPE(NIL); - CASE_TYPE(OBJECT); - CASE_TYPE(CLASS); - CASE_TYPE(ICLASS); - CASE_TYPE(MODULE); - CASE_TYPE(FLOAT); - CASE_TYPE(STRING); - CASE_TYPE(REGEXP); - CASE_TYPE(ARRAY); - CASE_TYPE(HASH); - CASE_TYPE(STRUCT); - CASE_TYPE(BIGNUM); - CASE_TYPE(FILE); - CASE_TYPE(FIXNUM); - CASE_TYPE(TRUE); - CASE_TYPE(FALSE); - CASE_TYPE(DATA); - CASE_TYPE(MATCH); - CASE_TYPE(SYMBOL); - CASE_TYPE(RATIONAL); - CASE_TYPE(COMPLEX); - CASE_TYPE(IMEMO); - CASE_TYPE(UNDEF); - CASE_TYPE(NODE); - CASE_TYPE(ZOMBIE); + CASE_TYPE(NONE); + CASE_TYPE(NIL); + CASE_TYPE(OBJECT); + CASE_TYPE(CLASS); + CASE_TYPE(ICLASS); + CASE_TYPE(MODULE); + CASE_TYPE(FLOAT); + CASE_TYPE(STRING); + CASE_TYPE(REGEXP); + CASE_TYPE(ARRAY); + CASE_TYPE(HASH); + CASE_TYPE(STRUCT); + CASE_TYPE(BIGNUM); + CASE_TYPE(FILE); + CASE_TYPE(FIXNUM); + CASE_TYPE(TRUE); + CASE_TYPE(FALSE); + CASE_TYPE(DATA); + CASE_TYPE(MATCH); + CASE_TYPE(SYMBOL); + CASE_TYPE(RATIONAL); + CASE_TYPE(COMPLEX); + CASE_TYPE(IMEMO); + CASE_TYPE(UNDEF); + CASE_TYPE(NODE); + CASE_TYPE(ZOMBIE); #undef CASE_TYPE default: break; } @@ -312,6 +328,17 @@ reachable_object_i(VALUE ref, void *data) dc->cur_obj_references++; } +static bool +dump_string_ascii_only(const char *str, long size) +{ + for (long i = 0; i < size; i++) { + if (str[i] & 0x80) { + return false; + } + } + return true; +} + static void dump_append_string_content(struct dump_config *dc, VALUE obj) { @@ -322,12 +349,35 @@ dump_append_string_content(struct dump_config *dc, VALUE obj) dump_append_sizet(dc, rb_str_capacity(obj)); } - if (is_ascii_string(obj)) { - dump_append(dc, ", \"value\":"); - dump_append_string_value(dc, obj); + if (RSTRING_LEN(obj) && rb_enc_asciicompat(rb_enc_from_index(ENCODING_GET(obj)))) { + int cr = ENC_CODERANGE(obj); + if (cr == RUBY_ENC_CODERANGE_UNKNOWN) { + if (dump_string_ascii_only(RSTRING_PTR(obj), RSTRING_LEN(obj))) { + cr = RUBY_ENC_CODERANGE_7BIT; + } + } + if (cr == RUBY_ENC_CODERANGE_7BIT) { + dump_append(dc, ", \"value\":"); + dump_append_string_value(dc, obj); + } + } +} + +static inline void +dump_append_id(struct dump_config *dc, ID id) +{ + VALUE str = rb_sym2str(ID2SYM(id)); + if (RTEST(str)) { + dump_append_string_value(dc, str); + } + else { + dump_append(dc, "\"ID_INTERNAL("); + dump_append_sizet(dc, rb_id_to_serial(id)); + dump_append(dc, ")\""); } } + static void dump_object(VALUE obj, struct dump_config *dc) { @@ -336,6 +386,7 @@ dump_object(VALUE obj, struct dump_config *dc) rb_io_t *fptr; ID flags[RB_OBJ_GC_FLAGS_MAX]; size_t n, i; + ID mid; if (SPECIAL_CONST_P(obj)) { dump_append_special_const(dc, obj); @@ -344,7 +395,11 @@ dump_object(VALUE obj, struct dump_config *dc) dc->cur_obj = obj; dc->cur_obj_references = 0; - dc->cur_obj_klass = BUILTIN_TYPE(obj) == T_NODE ? 0 : RBASIC_CLASS(obj); + if (BUILTIN_TYPE(obj) == T_NODE || BUILTIN_TYPE(obj) == T_IMEMO) { + dc->cur_obj_klass = 0; + } else { + dc->cur_obj_klass = RBASIC_CLASS(obj); + } if (dc->partial_dump && (!ainfo || ainfo->generation < dc->since)) { return; @@ -360,6 +415,13 @@ dump_object(VALUE obj, struct dump_config *dc) dump_append(dc, obj_type(obj)); dump_append(dc, "\""); + size_t shape_id = rb_shape_get_shape_id(obj); + dump_append(dc, ", \"shape_id\":"); + dump_append_sizet(dc, shape_id); + + dump_append(dc, ", \"slot_size\":"); + dump_append_sizet(dc, dc->cur_page_slot_size); + if (dc->cur_obj_klass) { dump_append(dc, ", \"class\":"); dump_append_ref(dc, dc->cur_obj_klass); @@ -376,6 +438,33 @@ dump_object(VALUE obj, struct dump_config *dc) dump_append(dc, ", \"imemo_type\":\""); dump_append(dc, rb_imemo_name(imemo_type(obj))); dump_append(dc, "\""); + + switch (imemo_type(obj)) { + case imemo_callinfo: + mid = vm_ci_mid((const struct rb_callinfo *)obj); + if (mid != 0) { + dump_append(dc, ", \"mid\":"); + dump_append_id(dc, mid); + } + break; + + case imemo_callcache: + mid = vm_cc_cme((const struct rb_callcache *)obj)->called_id; + if (mid != 0) { + dump_append(dc, ", \"called_id\":"); + dump_append_id(dc, mid); + + VALUE klass = ((const struct rb_callcache *)obj)->klass; + if (klass != 0) { + dump_append(dc, ", \"receiver_class\":"); + dump_append_ref(dc, klass); + } + } + break; + + default: + break; + } break; case T_SYMBOL: @@ -385,10 +474,10 @@ dump_object(VALUE obj, struct dump_config *dc) case T_STRING: if (STR_EMBED_P(obj)) dump_append(dc, ", \"embedded\":true"); - if (is_broken_string(obj)) - dump_append(dc, ", \"broken\":true"); if (FL_TEST(obj, RSTRING_FSTR)) dump_append(dc, ", \"fstring\":true"); + if (CHILLED_STRING_P(obj)) + dump_append(dc, ", \"chilled\":true"); if (STR_SHARED_P(obj)) dump_append(dc, ", \"shared\":true"); else @@ -399,6 +488,27 @@ dump_object(VALUE obj, struct dump_config *dc) dump_append(dc, rb_enc_name(rb_enc_from_index(ENCODING_GET(obj)))); dump_append(dc, "\""); } + + dump_append(dc, ", \"coderange\":\""); + switch (RB_ENC_CODERANGE(obj)) { + case RUBY_ENC_CODERANGE_UNKNOWN: + dump_append(dc, "unknown"); + break; + case RUBY_ENC_CODERANGE_7BIT: + dump_append(dc, "7bit"); + break; + case RUBY_ENC_CODERANGE_VALID: + dump_append(dc, "valid"); + break; + case RUBY_ENC_CODERANGE_BROKEN: + dump_append(dc, "broken"); + break; + } + dump_append(dc, "\""); + + if (RB_ENC_CODERANGE(obj) == RUBY_ENC_CODERANGE_BROKEN) + dump_append(dc, ", \"broken\":true"); + break; case T_HASH: @@ -413,9 +523,9 @@ dump_object(VALUE obj, struct dump_config *dc) case T_ARRAY: dump_append(dc, ", \"length\":"); dump_append_ld(dc, RARRAY_LEN(obj)); - if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, ELTS_SHARED)) + if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, RARRAY_SHARED_FLAG)) dump_append(dc, ", \"shared\":true"); - if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, RARRAY_EMBED_FLAG)) + if (FL_TEST(obj, RARRAY_EMBED_FLAG)) dump_append(dc, ", \"embedded\":true"); break; @@ -427,6 +537,9 @@ dump_object(VALUE obj, struct dump_config *dc) break; case T_CLASS: + dump_append(dc, ", \"variation_count\":"); + dump_append_d(dc, RCLASS_EXT(obj)->variation_count); + case T_MODULE: if (rb_class_get_superclass(obj)) { dump_append(dc, ", \"superclass\":"); @@ -439,7 +552,8 @@ dump_object(VALUE obj, struct dump_config *dc) dump_append(dc, ", \"name\":\""); dump_append(dc, RSTRING_PTR(mod_name)); dump_append(dc, "\""); - } else { + } + else { VALUE real_mod_name = rb_mod_name(rb_class_real(obj)); if (RTEST(real_mod_name)) { dump_append(dc, ", \"real_class_name\":\""); @@ -448,7 +562,7 @@ dump_object(VALUE obj, struct dump_config *dc) } } - if (FL_TEST(obj, FL_SINGLETON)) { + if (RCLASS_SINGLETON_P(obj)) { dump_append(dc, ", \"singleton\":true"); } } @@ -469,8 +583,15 @@ dump_object(VALUE obj, struct dump_config *dc) break; case T_OBJECT: + if (FL_TEST(obj, ROBJECT_EMBED)) { + dump_append(dc, ", \"embedded\":true"); + } + dump_append(dc, ", \"ivars\":"); - dump_append_lu(dc, ROBJECT_NUMIV(obj)); + dump_append_lu(dc, ROBJECT_IV_COUNT(obj)); + if (rb_shape_obj_too_complex(obj)) { + dump_append(dc, ", \"too_complex_shape\":true"); + } break; case T_FILE: @@ -482,8 +603,8 @@ dump_object(VALUE obj, struct dump_config *dc) break; case T_ZOMBIE: - dump_append(dc, "}\n"); - return; + dump_append(dc, "}\n"); + return; default: break; @@ -539,9 +660,10 @@ heap_i(void *vstart, void *vend, size_t stride, void *data) for (; v != (VALUE)vend; v += stride) { void *ptr = asan_poisoned_object_p(v); asan_unpoison_object(v, false); + dc->cur_page_slot_size = stride; - if (dc->full_heap || RBASIC(v)->flags) - dump_object(v, dc); + if (dc->full_heap || RBASIC(v)->flags) + dump_object(v, dc); if (ptr) { asan_poison_object(v); @@ -573,7 +695,7 @@ root_obj_i(const char *category, VALUE obj, void *data) } static void -dump_output(struct dump_config *dc, VALUE output, VALUE full, VALUE since) +dump_output(struct dump_config *dc, VALUE output, VALUE full, VALUE since, VALUE shapes) { dc->full_heap = 0; @@ -582,7 +704,8 @@ dump_output(struct dump_config *dc, VALUE output, VALUE full, VALUE since) if (TYPE(output) == T_STRING) { dc->stream = Qfalse; dc->string = output; - } else { + } + else { dc->stream = output; dc->string = Qfalse; } @@ -594,9 +717,12 @@ dump_output(struct dump_config *dc, VALUE output, VALUE full, VALUE since) if (RTEST(since)) { dc->partial_dump = 1; dc->since = NUM2SIZET(since); - } else { + } + else { dc->partial_dump = 0; } + + dc->shapes_since = RTEST(shapes) ? NUM2SIZET(shapes) : 0; } static VALUE @@ -606,28 +732,93 @@ dump_result(struct dump_config *dc) if (dc->string) { return dc->string; - } else { + } + else { rb_io_flush(dc->stream); return dc->stream; } } +/* :nodoc: */ static VALUE objspace_dump(VALUE os, VALUE obj, VALUE output) { struct dump_config dc = {0,}; - dump_output(&dc, output, Qnil, Qnil); + if (!RB_SPECIAL_CONST_P(obj)) { + dc.cur_page_slot_size = rb_gc_obj_slot_size(obj); + } + + dump_output(&dc, output, Qnil, Qnil, Qnil); dump_object(obj, &dc); return dump_result(&dc); } +static void +shape_i(rb_shape_t *shape, void *data) +{ + struct dump_config *dc = (struct dump_config *)data; + + size_t shape_id = rb_shape_id(shape); + if (shape_id < dc->shapes_since) { + return; + } + + dump_append(dc, "{\"address\":"); + dump_append_ref(dc, (VALUE)shape); + + dump_append(dc, ", \"type\":\"SHAPE\", \"id\":"); + dump_append_sizet(dc, shape_id); + + if (shape->type != SHAPE_ROOT) { + dump_append(dc, ", \"parent_id\":"); + dump_append_lu(dc, shape->parent_id); + } + + dump_append(dc, ", \"depth\":"); + dump_append_sizet(dc, rb_shape_depth(shape)); + + dump_append(dc, ", \"shape_type\":"); + switch((enum shape_type)shape->type) { + case SHAPE_ROOT: + dump_append(dc, "\"ROOT\""); + break; + case SHAPE_IVAR: + dump_append(dc, "\"IVAR\""); + + dump_append(dc, ",\"edge_name\":"); + dump_append_id(dc, shape->edge_name); + + break; + case SHAPE_FROZEN: + dump_append(dc, "\"FROZEN\""); + break; + case SHAPE_T_OBJECT: + dump_append(dc, "\"T_OBJECT\""); + break; + case SHAPE_OBJ_TOO_COMPLEX: + dump_append(dc, "\"OBJ_TOO_COMPLEX\""); + break; + default: + rb_bug("[objspace] unexpected shape type"); + } + + dump_append(dc, ", \"edges\":"); + dump_append_sizet(dc, rb_shape_edges_count(shape)); + + dump_append(dc, ", \"memsize\":"); + dump_append_sizet(dc, rb_shape_memsize(shape)); + + dump_append(dc, "}\n"); +} + +/* :nodoc: */ static VALUE -objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since) +objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since, VALUE shapes) { struct dump_config dc = {0,}; - dump_output(&dc, output, full, since); + dump_output(&dc, output, full, since, shapes); if (!dc.partial_dump || dc.since == 0) { /* dump roots */ @@ -635,12 +826,29 @@ objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since) if (dc.roots) dump_append(&dc, "]}\n"); } + if (RTEST(shapes)) { + rb_shape_each_shape(shape_i, &dc); + } + /* dump all objects */ rb_objspace_each_objects(heap_i, &dc); return dump_result(&dc); } +/* :nodoc: */ +static VALUE +objspace_dump_shapes(VALUE os, VALUE output, VALUE shapes) +{ + struct dump_config dc = {0,}; + dump_output(&dc, output, Qfalse, Qnil, shapes); + + if (RTEST(shapes)) { + rb_shape_each_shape(shape_i, &dc); + } + return dump_result(&dc); +} + void Init_objspace_dump(VALUE rb_mObjSpace) { @@ -650,7 +858,8 @@ Init_objspace_dump(VALUE rb_mObjSpace) #endif rb_define_module_function(rb_mObjSpace, "_dump", objspace_dump, 2); - rb_define_module_function(rb_mObjSpace, "_dump_all", objspace_dump_all, 3); + rb_define_module_function(rb_mObjSpace, "_dump_all", objspace_dump_all, 4); + rb_define_module_function(rb_mObjSpace, "_dump_shapes", objspace_dump_shapes, 2); /* force create static IDs */ rb_obj_gc_flags(rb_mObjSpace, 0, 0); |