00001
00002
00003
00004
00005
00006
00007
00008
00009
00010
00011
00012
00013
00014
00015 #include "ruby/ruby.h"
00016 #include "ruby/debug.h"
00017 #include "ruby/encoding.h"
00018 #include "ruby/io.h"
00019 #include "gc.h"
00020 #include "node.h"
00021 #include "vm_core.h"
00022 #include "objspace.h"
00023 #include "internal.h"
00024
00025 static VALUE sym_output, sym_stdout, sym_string, sym_file;
00026
00027 struct dump_config {
00028 VALUE type;
00029 FILE *stream;
00030 VALUE string;
00031 int roots;
00032 const char *root_category;
00033 VALUE cur_obj;
00034 VALUE cur_obj_klass;
00035 size_t cur_obj_references;
00036 };
00037
00038 static void
00039 dump_append(struct dump_config *dc, const char *format, ...)
00040 {
00041 va_list vl;
00042 va_start(vl, format);
00043
00044 if (dc->stream) {
00045 vfprintf(dc->stream, format, vl);
00046 fflush(dc->stream);
00047 }
00048 else if (dc->string)
00049 rb_str_vcatf(dc->string, format, vl);
00050
00051 va_end(vl);
00052 }
00053
00054 static void
00055 dump_append_string_value(struct dump_config *dc, VALUE obj)
00056 {
00057 int i;
00058 char c, *value;
00059
00060 dump_append(dc, "\"");
00061 for (i = 0, value = RSTRING_PTR(obj); i < RSTRING_LEN(obj); i++) {
00062 switch ((c = value[i])) {
00063 case '\\':
00064 case '"':
00065 dump_append(dc, "\\%c", c);
00066 break;
00067 case '\0':
00068 dump_append(dc, "\\u0000");
00069 break;
00070 case '\b':
00071 dump_append(dc, "\\b");
00072 break;
00073 case '\t':
00074 dump_append(dc, "\\t");
00075 break;
00076 case '\f':
00077 dump_append(dc, "\\f");
00078 break;
00079 case '\n':
00080 dump_append(dc, "\\n");
00081 break;
00082 case '\r':
00083 dump_append(dc, "\\r");
00084 break;
00085 default:
00086 if (c <= 0x1f)
00087 dump_append(dc, "\\u%04d", c);
00088 else
00089 dump_append(dc, "%c", c);
00090 }
00091 }
00092 dump_append(dc, "\"");
00093 }
00094
00095 static inline const char *
00096 obj_type(VALUE obj)
00097 {
00098 switch (BUILTIN_TYPE(obj)) {
00099 #define CASE_TYPE(type) case T_##type: return #type; break
00100 CASE_TYPE(NONE);
00101 CASE_TYPE(NIL);
00102 CASE_TYPE(OBJECT);
00103 CASE_TYPE(CLASS);
00104 CASE_TYPE(ICLASS);
00105 CASE_TYPE(MODULE);
00106 CASE_TYPE(FLOAT);
00107 CASE_TYPE(STRING);
00108 CASE_TYPE(REGEXP);
00109 CASE_TYPE(ARRAY);
00110 CASE_TYPE(HASH);
00111 CASE_TYPE(STRUCT);
00112 CASE_TYPE(BIGNUM);
00113 CASE_TYPE(FILE);
00114 CASE_TYPE(FIXNUM);
00115 CASE_TYPE(TRUE);
00116 CASE_TYPE(FALSE);
00117 CASE_TYPE(DATA);
00118 CASE_TYPE(MATCH);
00119 CASE_TYPE(SYMBOL);
00120 CASE_TYPE(RATIONAL);
00121 CASE_TYPE(COMPLEX);
00122 CASE_TYPE(UNDEF);
00123 CASE_TYPE(NODE);
00124 CASE_TYPE(ZOMBIE);
00125 #undef CASE_TYPE
00126 }
00127 return "UNKNOWN";
00128 }
00129
00130 static void
00131 reachable_object_i(VALUE ref, void *data)
00132 {
00133 struct dump_config *dc = (struct dump_config *)data;
00134
00135 if (dc->cur_obj_klass == ref)
00136 return;
00137
00138 if (dc->cur_obj_references == 0)
00139 dump_append(dc, ", \"references\":[\"%p\"", (void *)ref);
00140 else
00141 dump_append(dc, ", \"%p\"", (void *)ref);
00142
00143 dc->cur_obj_references++;
00144 }
00145
00146 static void
00147 dump_object(VALUE obj, struct dump_config *dc)
00148 {
00149 size_t memsize;
00150 struct allocation_info *ainfo;
00151 rb_io_t *fptr;
00152 ID flags[RB_OBJ_GC_FLAGS_MAX];
00153 size_t n, i;
00154
00155 if (SPECIAL_CONST_P(obj)) {
00156 dump_append(dc, "{}");
00157 return;
00158 }
00159
00160 dc->cur_obj = obj;
00161 dc->cur_obj_references = 0;
00162 dc->cur_obj_klass = BUILTIN_TYPE(obj) == T_NODE ? 0 : RBASIC_CLASS(obj);
00163
00164 if (dc->cur_obj == dc->string)
00165 return;
00166
00167 dump_append(dc, "{\"address\":\"%p\", \"type\":\"%s\"", (void *)obj, obj_type(obj));
00168
00169 if (dc->cur_obj_klass)
00170 dump_append(dc, ", \"class\":\"%p\"", (void *)dc->cur_obj_klass);
00171 if (rb_obj_frozen_p(obj))
00172 dump_append(dc, ", \"frozen\":true");
00173
00174 switch (BUILTIN_TYPE(obj)) {
00175 case T_NODE:
00176 dump_append(dc, ", \"node_type\":\"%s\"", ruby_node_name(nd_type(obj)));
00177 break;
00178
00179 case T_STRING:
00180 if (STR_EMBED_P(obj))
00181 dump_append(dc, ", \"embedded\":true");
00182 if (STR_ASSOC_P(obj))
00183 dump_append(dc, ", \"associated\":true");
00184 if (is_broken_string(obj))
00185 dump_append(dc, ", \"broken\":true");
00186 if (FL_TEST(obj, RSTRING_FSTR))
00187 dump_append(dc, ", \"fstring\":true");
00188 if (STR_SHARED_P(obj))
00189 dump_append(dc, ", \"shared\":true");
00190 else {
00191 dump_append(dc, ", \"bytesize\":%ld", RSTRING_LEN(obj));
00192 if (!STR_EMBED_P(obj) && !STR_NOCAPA_P(obj) && (long)rb_str_capacity(obj) != RSTRING_LEN(obj))
00193 dump_append(dc, ", \"capacity\":%ld", rb_str_capacity(obj));
00194
00195 if (is_ascii_string(obj)) {
00196 dump_append(dc, ", \"value\":");
00197 dump_append_string_value(dc, obj);
00198 }
00199 }
00200
00201 if (!ENCODING_IS_ASCII8BIT(obj))
00202 dump_append(dc, ", \"encoding\":\"%s\"", rb_enc_name(rb_enc_from_index(ENCODING_GET(obj))));
00203 break;
00204
00205 case T_HASH:
00206 dump_append(dc, ", \"size\":%ld", RHASH_SIZE(obj));
00207 if (FL_TEST(obj, HASH_PROC_DEFAULT))
00208 dump_append(dc, ", \"default\":\"%p\"", (void *)RHASH_IFNONE(obj));
00209 break;
00210
00211 case T_ARRAY:
00212 dump_append(dc, ", \"length\":%ld", RARRAY_LEN(obj));
00213 if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, ELTS_SHARED))
00214 dump_append(dc, ", \"shared\":true");
00215 if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, RARRAY_EMBED_FLAG))
00216 dump_append(dc, ", \"embedded\":true");
00217 break;
00218
00219 case T_CLASS:
00220 case T_MODULE:
00221 if (dc->cur_obj_klass)
00222 dump_append(dc, ", \"name\":\"%s\"", rb_class2name(obj));
00223 break;
00224
00225 case T_DATA:
00226 if (RTYPEDDATA_P(obj))
00227 dump_append(dc, ", \"struct\":\"%s\"", RTYPEDDATA_TYPE(obj)->wrap_struct_name);
00228 break;
00229
00230 case T_FLOAT:
00231 dump_append(dc, ", \"value\":\"%g\"", RFLOAT_VALUE(obj));
00232 break;
00233
00234 case T_OBJECT:
00235 dump_append(dc, ", \"ivars\":%ld", ROBJECT_NUMIV(obj));
00236 break;
00237
00238 case T_FILE:
00239 fptr = RFILE(obj)->fptr;
00240 if (fptr)
00241 dump_append(dc, ", \"fd\":%d", fptr->fd);
00242 break;
00243
00244 case T_ZOMBIE:
00245 dump_append(dc, "}\n");
00246 return;
00247 }
00248
00249 rb_objspace_reachable_objects_from(obj, reachable_object_i, dc);
00250 if (dc->cur_obj_references > 0)
00251 dump_append(dc, "]");
00252
00253 if ((ainfo = objspace_lookup_allocation_info(obj))) {
00254 dump_append(dc, ", \"file\":\"%s\", \"line\":%lu", ainfo->path, ainfo->line);
00255 if (RTEST(ainfo->mid))
00256 dump_append(dc, ", \"method\":\"%s\"", rb_id2name(SYM2ID(ainfo->mid)));
00257 dump_append(dc, ", \"generation\":%"PRIuSIZE, ainfo->generation);
00258 }
00259
00260 if ((memsize = rb_obj_memsize_of(obj)) > 0)
00261 dump_append(dc, ", \"memsize\":%"PRIuSIZE, memsize);
00262
00263 if ((n = rb_obj_gc_flags(obj, flags, sizeof(flags))) > 0) {
00264 dump_append(dc, ", \"flags\":{");
00265 for (i=0; i<n; i++) {
00266 dump_append(dc, "\"%s\":true", rb_id2name(flags[i]));
00267 if (i != n-1) dump_append(dc, ", ");
00268 }
00269 dump_append(dc, "}");
00270 }
00271
00272 dump_append(dc, "}\n");
00273 }
00274
00275 static int
00276 heap_i(void *vstart, void *vend, size_t stride, void *data)
00277 {
00278 VALUE v = (VALUE)vstart;
00279 for (; v != (VALUE)vend; v += stride) {
00280 if (RBASIC(v)->flags)
00281 dump_object(v, data);
00282 }
00283 return 0;
00284 }
00285
00286 static void
00287 root_obj_i(const char *category, VALUE obj, void *data)
00288 {
00289 struct dump_config *dc = (struct dump_config *)data;
00290
00291 if (dc->root_category != NULL && category != dc->root_category)
00292 dump_append(dc, "]}\n");
00293 if (dc->root_category == NULL || category != dc->root_category)
00294 dump_append(dc, "{\"type\":\"ROOT\", \"root\":\"%s\", \"references\":[\"%p\"", category, (void *)obj);
00295 else
00296 dump_append(dc, ", \"%p\"", (void *)obj);
00297
00298 dc->root_category = category;
00299 dc->roots++;
00300 }
00301
00302 static VALUE
00303 dump_output(struct dump_config *dc, VALUE opts, VALUE output, const char *filename)
00304 {
00305 VALUE tmp;
00306
00307 if (RTEST(opts))
00308 output = rb_hash_aref(opts, sym_output);
00309
00310 if (output == sym_stdout) {
00311 dc->stream = stdout;
00312 dc->string = Qnil;
00313 }
00314 else if (output == sym_file) {
00315 rb_io_t *fptr;
00316 rb_require("tempfile");
00317 tmp = rb_assoc_new(rb_str_new_cstr(filename), rb_str_new_cstr(".json"));
00318 tmp = rb_funcallv(rb_path2class("Tempfile"), rb_intern("create"), 1, &tmp);
00319 io:
00320 dc->string = rb_io_get_write_io(tmp);
00321 rb_io_flush(dc->string);
00322 GetOpenFile(dc->string, fptr);
00323 dc->stream = rb_io_stdio_file(fptr);
00324 }
00325 else if (output == sym_string) {
00326 dc->string = rb_str_new_cstr("");
00327 }
00328 else if (!NIL_P(tmp = rb_io_check_io(output))) {
00329 output = sym_file;
00330 goto io;
00331 }
00332 else {
00333 rb_raise(rb_eArgError, "wrong output option: %"PRIsVALUE, output);
00334 }
00335 return output;
00336 }
00337
00338 static VALUE
00339 dump_result(struct dump_config *dc, VALUE output)
00340 {
00341 if (output == sym_string) {
00342 return dc->string;
00343 }
00344 else if (output == sym_file) {
00345 rb_io_flush(dc->string);
00346 return dc->string;
00347 }
00348 else {
00349 return Qnil;
00350 }
00351 }
00352
00353
00354
00355
00356
00357
00358
00359
00360
00361
00362
00363
00364
00365
00366
00367 static VALUE
00368 objspace_dump(int argc, VALUE *argv, VALUE os)
00369 {
00370 static const char filename[] = "rubyobj";
00371 VALUE obj = Qnil, opts = Qnil, output;
00372 struct dump_config dc = {0,};
00373
00374 rb_scan_args(argc, argv, "1:", &obj, &opts);
00375
00376 output = dump_output(&dc, opts, sym_string, filename);
00377
00378 dump_object(obj, &dc);
00379
00380 return dump_result(&dc, output);
00381 }
00382
00383
00384
00385
00386
00387
00388
00389
00390
00391
00392
00393
00394
00395
00396
00397
00398
00399 static VALUE
00400 objspace_dump_all(int argc, VALUE *argv, VALUE os)
00401 {
00402 static const char filename[] = "rubyheap";
00403 VALUE opts = Qnil, output;
00404 struct dump_config dc = {0,};
00405
00406 rb_scan_args(argc, argv, "0:", &opts);
00407
00408 output = dump_output(&dc, opts, sym_file, filename);
00409
00410
00411 rb_objspace_reachable_objects_from_root(root_obj_i, &dc);
00412 if (dc.roots) dump_append(&dc, "]}\n");
00413
00414
00415 rb_objspace_each_objects(heap_i, &dc);
00416
00417 return dump_result(&dc, output);
00418 }
00419
00420 void
00421 Init_objspace_dump(VALUE rb_mObjSpace)
00422 {
00423 #if 0
00424 rb_mObjSpace = rb_define_module("ObjectSpace");
00425 #endif
00426
00427 rb_define_module_function(rb_mObjSpace, "dump", objspace_dump, -1);
00428 rb_define_module_function(rb_mObjSpace, "dump_all", objspace_dump_all, -1);
00429
00430 sym_output = ID2SYM(rb_intern("output"));
00431 sym_stdout = ID2SYM(rb_intern("stdout"));
00432 sym_string = ID2SYM(rb_intern("string"));
00433 sym_file = ID2SYM(rb_intern("file"));
00434
00435
00436 rb_obj_gc_flags(rb_mObjSpace, 0, 0);
00437 }
00438