diff options
Diffstat (limited to 'ext/objspace')
| -rw-r--r-- | ext/objspace/depend | 571 | ||||
| -rw-r--r-- | ext/objspace/extconf.rb | 4 | ||||
| -rw-r--r-- | ext/objspace/lib/objspace.rb | 91 | ||||
| -rw-r--r-- | ext/objspace/lib/objspace/trace.rb | 45 | ||||
| -rw-r--r-- | ext/objspace/object_tracing.c | 579 | ||||
| -rw-r--r-- | ext/objspace/objspace.c | 1005 | ||||
| -rw-r--r-- | ext/objspace/objspace.h | 20 | ||||
| -rw-r--r-- | ext/objspace/objspace_dump.c | 657 |
8 files changed, 2972 insertions, 0 deletions
diff --git a/ext/objspace/depend b/ext/objspace/depend new file mode 100644 index 0000000000..1248308397 --- /dev/null +++ b/ext/objspace/depend @@ -0,0 +1,571 @@ +# AUTOGENERATED DEPENDENCIES START +object_tracing.o: $(RUBY_EXTCONF_H) +object_tracing.o: $(arch_hdrdir)/ruby/config.h +object_tracing.o: $(hdrdir)/ruby/assert.h +object_tracing.o: $(hdrdir)/ruby/backward.h +object_tracing.o: $(hdrdir)/ruby/backward/2/assume.h +object_tracing.o: $(hdrdir)/ruby/backward/2/attributes.h +object_tracing.o: $(hdrdir)/ruby/backward/2/bool.h +object_tracing.o: $(hdrdir)/ruby/backward/2/inttypes.h +object_tracing.o: $(hdrdir)/ruby/backward/2/limits.h +object_tracing.o: $(hdrdir)/ruby/backward/2/long_long.h +object_tracing.o: $(hdrdir)/ruby/backward/2/stdalign.h +object_tracing.o: $(hdrdir)/ruby/backward/2/stdarg.h +object_tracing.o: $(hdrdir)/ruby/debug.h +object_tracing.o: $(hdrdir)/ruby/defines.h +object_tracing.o: $(hdrdir)/ruby/intern.h +object_tracing.o: $(hdrdir)/ruby/internal/anyargs.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/char.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/double.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/int.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/long.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/short.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +object_tracing.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +object_tracing.o: $(hdrdir)/ruby/internal/assume.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/artificial.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/cold.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/const.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/constexpr.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/deprecated.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/error.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/forceinline.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/format.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/noalias.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/noexcept.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/noinline.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/nonnull.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/noreturn.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/pure.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/restrict.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/warning.h +object_tracing.o: $(hdrdir)/ruby/internal/attr/weakref.h +object_tracing.o: $(hdrdir)/ruby/internal/cast.h +object_tracing.o: $(hdrdir)/ruby/internal/compiler_is.h +object_tracing.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +object_tracing.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +object_tracing.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +object_tracing.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +object_tracing.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +object_tracing.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +object_tracing.o: $(hdrdir)/ruby/internal/compiler_since.h +object_tracing.o: $(hdrdir)/ruby/internal/config.h +object_tracing.o: $(hdrdir)/ruby/internal/constant_p.h +object_tracing.o: $(hdrdir)/ruby/internal/core.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rarray.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rbasic.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rbignum.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rclass.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rdata.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rfile.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rhash.h +object_tracing.o: $(hdrdir)/ruby/internal/core/robject.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rregexp.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rstring.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rstruct.h +object_tracing.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +object_tracing.o: $(hdrdir)/ruby/internal/ctype.h +object_tracing.o: $(hdrdir)/ruby/internal/dllexport.h +object_tracing.o: $(hdrdir)/ruby/internal/dosish.h +object_tracing.o: $(hdrdir)/ruby/internal/error.h +object_tracing.o: $(hdrdir)/ruby/internal/eval.h +object_tracing.o: $(hdrdir)/ruby/internal/event.h +object_tracing.o: $(hdrdir)/ruby/internal/fl_type.h +object_tracing.o: $(hdrdir)/ruby/internal/gc.h +object_tracing.o: $(hdrdir)/ruby/internal/glob.h +object_tracing.o: $(hdrdir)/ruby/internal/globals.h +object_tracing.o: $(hdrdir)/ruby/internal/has/attribute.h +object_tracing.o: $(hdrdir)/ruby/internal/has/builtin.h +object_tracing.o: $(hdrdir)/ruby/internal/has/c_attribute.h +object_tracing.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +object_tracing.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +object_tracing.o: $(hdrdir)/ruby/internal/has/extension.h +object_tracing.o: $(hdrdir)/ruby/internal/has/feature.h +object_tracing.o: $(hdrdir)/ruby/internal/has/warning.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/array.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/bignum.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/class.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/compar.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/complex.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/cont.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/dir.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/enum.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/enumerator.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/error.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/eval.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/file.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/gc.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/hash.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/io.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/load.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/marshal.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/numeric.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/object.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/parse.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/proc.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/process.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/random.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/range.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/rational.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/re.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/ruby.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/select.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/signal.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/sprintf.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/string.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/struct.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/thread.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/time.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/variable.h +object_tracing.o: $(hdrdir)/ruby/internal/intern/vm.h +object_tracing.o: $(hdrdir)/ruby/internal/interpreter.h +object_tracing.o: $(hdrdir)/ruby/internal/iterator.h +object_tracing.o: $(hdrdir)/ruby/internal/memory.h +object_tracing.o: $(hdrdir)/ruby/internal/method.h +object_tracing.o: $(hdrdir)/ruby/internal/module.h +object_tracing.o: $(hdrdir)/ruby/internal/newobj.h +object_tracing.o: $(hdrdir)/ruby/internal/rgengc.h +object_tracing.o: $(hdrdir)/ruby/internal/scan_args.h +object_tracing.o: $(hdrdir)/ruby/internal/special_consts.h +object_tracing.o: $(hdrdir)/ruby/internal/static_assert.h +object_tracing.o: $(hdrdir)/ruby/internal/stdalign.h +object_tracing.o: $(hdrdir)/ruby/internal/stdbool.h +object_tracing.o: $(hdrdir)/ruby/internal/symbol.h +object_tracing.o: $(hdrdir)/ruby/internal/value.h +object_tracing.o: $(hdrdir)/ruby/internal/value_type.h +object_tracing.o: $(hdrdir)/ruby/internal/variable.h +object_tracing.o: $(hdrdir)/ruby/internal/warning_push.h +object_tracing.o: $(hdrdir)/ruby/internal/xmalloc.h +object_tracing.o: $(hdrdir)/ruby/missing.h +object_tracing.o: $(hdrdir)/ruby/ruby.h +object_tracing.o: $(hdrdir)/ruby/st.h +object_tracing.o: $(hdrdir)/ruby/subst.h +object_tracing.o: $(top_srcdir)/internal.h +object_tracing.o: object_tracing.c +object_tracing.o: objspace.h +objspace.o: $(RUBY_EXTCONF_H) +objspace.o: $(arch_hdrdir)/ruby/config.h +objspace.o: $(hdrdir)/ruby/assert.h +objspace.o: $(hdrdir)/ruby/atomic.h +objspace.o: $(hdrdir)/ruby/backward.h +objspace.o: $(hdrdir)/ruby/backward/2/assume.h +objspace.o: $(hdrdir)/ruby/backward/2/attributes.h +objspace.o: $(hdrdir)/ruby/backward/2/bool.h +objspace.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h +objspace.o: $(hdrdir)/ruby/backward/2/inttypes.h +objspace.o: $(hdrdir)/ruby/backward/2/limits.h +objspace.o: $(hdrdir)/ruby/backward/2/long_long.h +objspace.o: $(hdrdir)/ruby/backward/2/stdalign.h +objspace.o: $(hdrdir)/ruby/backward/2/stdarg.h +objspace.o: $(hdrdir)/ruby/defines.h +objspace.o: $(hdrdir)/ruby/encoding.h +objspace.o: $(hdrdir)/ruby/intern.h +objspace.o: $(hdrdir)/ruby/internal/anyargs.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/char.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/double.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/int.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/long.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/short.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +objspace.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +objspace.o: $(hdrdir)/ruby/internal/assume.h +objspace.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +objspace.o: $(hdrdir)/ruby/internal/attr/artificial.h +objspace.o: $(hdrdir)/ruby/internal/attr/cold.h +objspace.o: $(hdrdir)/ruby/internal/attr/const.h +objspace.o: $(hdrdir)/ruby/internal/attr/constexpr.h +objspace.o: $(hdrdir)/ruby/internal/attr/deprecated.h +objspace.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +objspace.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +objspace.o: $(hdrdir)/ruby/internal/attr/error.h +objspace.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +objspace.o: $(hdrdir)/ruby/internal/attr/forceinline.h +objspace.o: $(hdrdir)/ruby/internal/attr/format.h +objspace.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +objspace.o: $(hdrdir)/ruby/internal/attr/noalias.h +objspace.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +objspace.o: $(hdrdir)/ruby/internal/attr/noexcept.h +objspace.o: $(hdrdir)/ruby/internal/attr/noinline.h +objspace.o: $(hdrdir)/ruby/internal/attr/nonnull.h +objspace.o: $(hdrdir)/ruby/internal/attr/noreturn.h +objspace.o: $(hdrdir)/ruby/internal/attr/pure.h +objspace.o: $(hdrdir)/ruby/internal/attr/restrict.h +objspace.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +objspace.o: $(hdrdir)/ruby/internal/attr/warning.h +objspace.o: $(hdrdir)/ruby/internal/attr/weakref.h +objspace.o: $(hdrdir)/ruby/internal/cast.h +objspace.o: $(hdrdir)/ruby/internal/compiler_is.h +objspace.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +objspace.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +objspace.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +objspace.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +objspace.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +objspace.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +objspace.o: $(hdrdir)/ruby/internal/compiler_since.h +objspace.o: $(hdrdir)/ruby/internal/config.h +objspace.o: $(hdrdir)/ruby/internal/constant_p.h +objspace.o: $(hdrdir)/ruby/internal/core.h +objspace.o: $(hdrdir)/ruby/internal/core/rarray.h +objspace.o: $(hdrdir)/ruby/internal/core/rbasic.h +objspace.o: $(hdrdir)/ruby/internal/core/rbignum.h +objspace.o: $(hdrdir)/ruby/internal/core/rclass.h +objspace.o: $(hdrdir)/ruby/internal/core/rdata.h +objspace.o: $(hdrdir)/ruby/internal/core/rfile.h +objspace.o: $(hdrdir)/ruby/internal/core/rhash.h +objspace.o: $(hdrdir)/ruby/internal/core/rmatch.h +objspace.o: $(hdrdir)/ruby/internal/core/robject.h +objspace.o: $(hdrdir)/ruby/internal/core/rregexp.h +objspace.o: $(hdrdir)/ruby/internal/core/rstring.h +objspace.o: $(hdrdir)/ruby/internal/core/rstruct.h +objspace.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +objspace.o: $(hdrdir)/ruby/internal/ctype.h +objspace.o: $(hdrdir)/ruby/internal/dllexport.h +objspace.o: $(hdrdir)/ruby/internal/dosish.h +objspace.o: $(hdrdir)/ruby/internal/encoding/coderange.h +objspace.o: $(hdrdir)/ruby/internal/encoding/ctype.h +objspace.o: $(hdrdir)/ruby/internal/encoding/encoding.h +objspace.o: $(hdrdir)/ruby/internal/encoding/pathname.h +objspace.o: $(hdrdir)/ruby/internal/encoding/re.h +objspace.o: $(hdrdir)/ruby/internal/encoding/sprintf.h +objspace.o: $(hdrdir)/ruby/internal/encoding/string.h +objspace.o: $(hdrdir)/ruby/internal/encoding/symbol.h +objspace.o: $(hdrdir)/ruby/internal/encoding/transcode.h +objspace.o: $(hdrdir)/ruby/internal/error.h +objspace.o: $(hdrdir)/ruby/internal/eval.h +objspace.o: $(hdrdir)/ruby/internal/event.h +objspace.o: $(hdrdir)/ruby/internal/fl_type.h +objspace.o: $(hdrdir)/ruby/internal/gc.h +objspace.o: $(hdrdir)/ruby/internal/glob.h +objspace.o: $(hdrdir)/ruby/internal/globals.h +objspace.o: $(hdrdir)/ruby/internal/has/attribute.h +objspace.o: $(hdrdir)/ruby/internal/has/builtin.h +objspace.o: $(hdrdir)/ruby/internal/has/c_attribute.h +objspace.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +objspace.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +objspace.o: $(hdrdir)/ruby/internal/has/extension.h +objspace.o: $(hdrdir)/ruby/internal/has/feature.h +objspace.o: $(hdrdir)/ruby/internal/has/warning.h +objspace.o: $(hdrdir)/ruby/internal/intern/array.h +objspace.o: $(hdrdir)/ruby/internal/intern/bignum.h +objspace.o: $(hdrdir)/ruby/internal/intern/class.h +objspace.o: $(hdrdir)/ruby/internal/intern/compar.h +objspace.o: $(hdrdir)/ruby/internal/intern/complex.h +objspace.o: $(hdrdir)/ruby/internal/intern/cont.h +objspace.o: $(hdrdir)/ruby/internal/intern/dir.h +objspace.o: $(hdrdir)/ruby/internal/intern/enum.h +objspace.o: $(hdrdir)/ruby/internal/intern/enumerator.h +objspace.o: $(hdrdir)/ruby/internal/intern/error.h +objspace.o: $(hdrdir)/ruby/internal/intern/eval.h +objspace.o: $(hdrdir)/ruby/internal/intern/file.h +objspace.o: $(hdrdir)/ruby/internal/intern/gc.h +objspace.o: $(hdrdir)/ruby/internal/intern/hash.h +objspace.o: $(hdrdir)/ruby/internal/intern/io.h +objspace.o: $(hdrdir)/ruby/internal/intern/load.h +objspace.o: $(hdrdir)/ruby/internal/intern/marshal.h +objspace.o: $(hdrdir)/ruby/internal/intern/numeric.h +objspace.o: $(hdrdir)/ruby/internal/intern/object.h +objspace.o: $(hdrdir)/ruby/internal/intern/parse.h +objspace.o: $(hdrdir)/ruby/internal/intern/proc.h +objspace.o: $(hdrdir)/ruby/internal/intern/process.h +objspace.o: $(hdrdir)/ruby/internal/intern/random.h +objspace.o: $(hdrdir)/ruby/internal/intern/range.h +objspace.o: $(hdrdir)/ruby/internal/intern/rational.h +objspace.o: $(hdrdir)/ruby/internal/intern/re.h +objspace.o: $(hdrdir)/ruby/internal/intern/ruby.h +objspace.o: $(hdrdir)/ruby/internal/intern/select.h +objspace.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +objspace.o: $(hdrdir)/ruby/internal/intern/signal.h +objspace.o: $(hdrdir)/ruby/internal/intern/sprintf.h +objspace.o: $(hdrdir)/ruby/internal/intern/string.h +objspace.o: $(hdrdir)/ruby/internal/intern/struct.h +objspace.o: $(hdrdir)/ruby/internal/intern/thread.h +objspace.o: $(hdrdir)/ruby/internal/intern/time.h +objspace.o: $(hdrdir)/ruby/internal/intern/variable.h +objspace.o: $(hdrdir)/ruby/internal/intern/vm.h +objspace.o: $(hdrdir)/ruby/internal/interpreter.h +objspace.o: $(hdrdir)/ruby/internal/iterator.h +objspace.o: $(hdrdir)/ruby/internal/memory.h +objspace.o: $(hdrdir)/ruby/internal/method.h +objspace.o: $(hdrdir)/ruby/internal/module.h +objspace.o: $(hdrdir)/ruby/internal/newobj.h +objspace.o: $(hdrdir)/ruby/internal/rgengc.h +objspace.o: $(hdrdir)/ruby/internal/scan_args.h +objspace.o: $(hdrdir)/ruby/internal/special_consts.h +objspace.o: $(hdrdir)/ruby/internal/static_assert.h +objspace.o: $(hdrdir)/ruby/internal/stdalign.h +objspace.o: $(hdrdir)/ruby/internal/stdbool.h +objspace.o: $(hdrdir)/ruby/internal/symbol.h +objspace.o: $(hdrdir)/ruby/internal/value.h +objspace.o: $(hdrdir)/ruby/internal/value_type.h +objspace.o: $(hdrdir)/ruby/internal/variable.h +objspace.o: $(hdrdir)/ruby/internal/warning_push.h +objspace.o: $(hdrdir)/ruby/internal/xmalloc.h +objspace.o: $(hdrdir)/ruby/io.h +objspace.o: $(hdrdir)/ruby/missing.h +objspace.o: $(hdrdir)/ruby/onigmo.h +objspace.o: $(hdrdir)/ruby/oniguruma.h +objspace.o: $(hdrdir)/ruby/re.h +objspace.o: $(hdrdir)/ruby/regex.h +objspace.o: $(hdrdir)/ruby/ruby.h +objspace.o: $(hdrdir)/ruby/st.h +objspace.o: $(hdrdir)/ruby/subst.h +objspace.o: $(hdrdir)/ruby/thread_native.h +objspace.o: $(top_srcdir)/ccan/check_type/check_type.h +objspace.o: $(top_srcdir)/ccan/container_of/container_of.h +objspace.o: $(top_srcdir)/ccan/list/list.h +objspace.o: $(top_srcdir)/ccan/str/str.h +objspace.o: $(top_srcdir)/darray.h +objspace.o: $(top_srcdir)/gc.h +objspace.o: $(top_srcdir)/id_table.h +objspace.o: $(top_srcdir)/internal.h +objspace.o: $(top_srcdir)/internal/array.h +objspace.o: $(top_srcdir)/internal/class.h +objspace.o: $(top_srcdir)/internal/compilers.h +objspace.o: $(top_srcdir)/internal/gc.h +objspace.o: $(top_srcdir)/internal/hash.h +objspace.o: $(top_srcdir)/internal/imemo.h +objspace.o: $(top_srcdir)/internal/sanitizers.h +objspace.o: $(top_srcdir)/internal/serial.h +objspace.o: $(top_srcdir)/internal/static_assert.h +objspace.o: $(top_srcdir)/internal/vm.h +objspace.o: $(top_srcdir)/internal/warnings.h +objspace.o: $(top_srcdir)/method.h +objspace.o: $(top_srcdir)/node.h +objspace.o: $(top_srcdir)/ruby_assert.h +objspace.o: $(top_srcdir)/ruby_atomic.h +objspace.o: $(top_srcdir)/symbol.h +objspace.o: $(top_srcdir)/thread_pthread.h +objspace.o: $(top_srcdir)/vm_core.h +objspace.o: $(top_srcdir)/vm_opts.h +objspace.o: objspace.c +objspace.o: {$(VPATH)}id.h +objspace_dump.o: $(RUBY_EXTCONF_H) +objspace_dump.o: $(arch_hdrdir)/ruby/config.h +objspace_dump.o: $(hdrdir)/ruby/assert.h +objspace_dump.o: $(hdrdir)/ruby/atomic.h +objspace_dump.o: $(hdrdir)/ruby/backward.h +objspace_dump.o: $(hdrdir)/ruby/backward/2/assume.h +objspace_dump.o: $(hdrdir)/ruby/backward/2/attributes.h +objspace_dump.o: $(hdrdir)/ruby/backward/2/bool.h +objspace_dump.o: $(hdrdir)/ruby/backward/2/gcc_version_since.h +objspace_dump.o: $(hdrdir)/ruby/backward/2/inttypes.h +objspace_dump.o: $(hdrdir)/ruby/backward/2/limits.h +objspace_dump.o: $(hdrdir)/ruby/backward/2/long_long.h +objspace_dump.o: $(hdrdir)/ruby/backward/2/stdalign.h +objspace_dump.o: $(hdrdir)/ruby/backward/2/stdarg.h +objspace_dump.o: $(hdrdir)/ruby/debug.h +objspace_dump.o: $(hdrdir)/ruby/defines.h +objspace_dump.o: $(hdrdir)/ruby/encoding.h +objspace_dump.o: $(hdrdir)/ruby/intern.h +objspace_dump.o: $(hdrdir)/ruby/internal/anyargs.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/char.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/double.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/fixnum.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/gid_t.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/int.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/intptr_t.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/long.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/long_long.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/mode_t.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/off_t.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/pid_t.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/short.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/size_t.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/st_data_t.h +objspace_dump.o: $(hdrdir)/ruby/internal/arithmetic/uid_t.h +objspace_dump.o: $(hdrdir)/ruby/internal/assume.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/alloc_size.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/artificial.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/cold.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/const.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/constexpr.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/deprecated.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/diagnose_if.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/enum_extensibility.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/error.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/flag_enum.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/forceinline.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/format.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/maybe_unused.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/noalias.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/nodiscard.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/noexcept.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/noinline.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/nonnull.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/noreturn.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/pure.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/restrict.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/returns_nonnull.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/warning.h +objspace_dump.o: $(hdrdir)/ruby/internal/attr/weakref.h +objspace_dump.o: $(hdrdir)/ruby/internal/cast.h +objspace_dump.o: $(hdrdir)/ruby/internal/compiler_is.h +objspace_dump.o: $(hdrdir)/ruby/internal/compiler_is/apple.h +objspace_dump.o: $(hdrdir)/ruby/internal/compiler_is/clang.h +objspace_dump.o: $(hdrdir)/ruby/internal/compiler_is/gcc.h +objspace_dump.o: $(hdrdir)/ruby/internal/compiler_is/intel.h +objspace_dump.o: $(hdrdir)/ruby/internal/compiler_is/msvc.h +objspace_dump.o: $(hdrdir)/ruby/internal/compiler_is/sunpro.h +objspace_dump.o: $(hdrdir)/ruby/internal/compiler_since.h +objspace_dump.o: $(hdrdir)/ruby/internal/config.h +objspace_dump.o: $(hdrdir)/ruby/internal/constant_p.h +objspace_dump.o: $(hdrdir)/ruby/internal/core.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rarray.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rbasic.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rbignum.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rclass.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rdata.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rfile.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rhash.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/robject.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rregexp.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rstring.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rstruct.h +objspace_dump.o: $(hdrdir)/ruby/internal/core/rtypeddata.h +objspace_dump.o: $(hdrdir)/ruby/internal/ctype.h +objspace_dump.o: $(hdrdir)/ruby/internal/dllexport.h +objspace_dump.o: $(hdrdir)/ruby/internal/dosish.h +objspace_dump.o: $(hdrdir)/ruby/internal/encoding/coderange.h +objspace_dump.o: $(hdrdir)/ruby/internal/encoding/ctype.h +objspace_dump.o: $(hdrdir)/ruby/internal/encoding/encoding.h +objspace_dump.o: $(hdrdir)/ruby/internal/encoding/pathname.h +objspace_dump.o: $(hdrdir)/ruby/internal/encoding/re.h +objspace_dump.o: $(hdrdir)/ruby/internal/encoding/sprintf.h +objspace_dump.o: $(hdrdir)/ruby/internal/encoding/string.h +objspace_dump.o: $(hdrdir)/ruby/internal/encoding/symbol.h +objspace_dump.o: $(hdrdir)/ruby/internal/encoding/transcode.h +objspace_dump.o: $(hdrdir)/ruby/internal/error.h +objspace_dump.o: $(hdrdir)/ruby/internal/eval.h +objspace_dump.o: $(hdrdir)/ruby/internal/event.h +objspace_dump.o: $(hdrdir)/ruby/internal/fl_type.h +objspace_dump.o: $(hdrdir)/ruby/internal/gc.h +objspace_dump.o: $(hdrdir)/ruby/internal/glob.h +objspace_dump.o: $(hdrdir)/ruby/internal/globals.h +objspace_dump.o: $(hdrdir)/ruby/internal/has/attribute.h +objspace_dump.o: $(hdrdir)/ruby/internal/has/builtin.h +objspace_dump.o: $(hdrdir)/ruby/internal/has/c_attribute.h +objspace_dump.o: $(hdrdir)/ruby/internal/has/cpp_attribute.h +objspace_dump.o: $(hdrdir)/ruby/internal/has/declspec_attribute.h +objspace_dump.o: $(hdrdir)/ruby/internal/has/extension.h +objspace_dump.o: $(hdrdir)/ruby/internal/has/feature.h +objspace_dump.o: $(hdrdir)/ruby/internal/has/warning.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/array.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/bignum.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/class.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/compar.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/complex.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/cont.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/dir.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/enum.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/enumerator.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/error.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/eval.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/file.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/gc.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/hash.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/io.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/load.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/marshal.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/numeric.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/object.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/parse.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/proc.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/process.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/random.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/range.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/rational.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/re.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/ruby.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/select.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/select/largesize.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/signal.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/sprintf.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/string.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/struct.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/thread.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/time.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/variable.h +objspace_dump.o: $(hdrdir)/ruby/internal/intern/vm.h +objspace_dump.o: $(hdrdir)/ruby/internal/interpreter.h +objspace_dump.o: $(hdrdir)/ruby/internal/iterator.h +objspace_dump.o: $(hdrdir)/ruby/internal/memory.h +objspace_dump.o: $(hdrdir)/ruby/internal/method.h +objspace_dump.o: $(hdrdir)/ruby/internal/module.h +objspace_dump.o: $(hdrdir)/ruby/internal/newobj.h +objspace_dump.o: $(hdrdir)/ruby/internal/rgengc.h +objspace_dump.o: $(hdrdir)/ruby/internal/scan_args.h +objspace_dump.o: $(hdrdir)/ruby/internal/special_consts.h +objspace_dump.o: $(hdrdir)/ruby/internal/static_assert.h +objspace_dump.o: $(hdrdir)/ruby/internal/stdalign.h +objspace_dump.o: $(hdrdir)/ruby/internal/stdbool.h +objspace_dump.o: $(hdrdir)/ruby/internal/symbol.h +objspace_dump.o: $(hdrdir)/ruby/internal/value.h +objspace_dump.o: $(hdrdir)/ruby/internal/value_type.h +objspace_dump.o: $(hdrdir)/ruby/internal/variable.h +objspace_dump.o: $(hdrdir)/ruby/internal/warning_push.h +objspace_dump.o: $(hdrdir)/ruby/internal/xmalloc.h +objspace_dump.o: $(hdrdir)/ruby/io.h +objspace_dump.o: $(hdrdir)/ruby/missing.h +objspace_dump.o: $(hdrdir)/ruby/onigmo.h +objspace_dump.o: $(hdrdir)/ruby/oniguruma.h +objspace_dump.o: $(hdrdir)/ruby/ruby.h +objspace_dump.o: $(hdrdir)/ruby/st.h +objspace_dump.o: $(hdrdir)/ruby/subst.h +objspace_dump.o: $(hdrdir)/ruby/thread_native.h +objspace_dump.o: $(hdrdir)/ruby/util.h +objspace_dump.o: $(top_srcdir)/ccan/check_type/check_type.h +objspace_dump.o: $(top_srcdir)/ccan/container_of/container_of.h +objspace_dump.o: $(top_srcdir)/ccan/list/list.h +objspace_dump.o: $(top_srcdir)/ccan/str/str.h +objspace_dump.o: $(top_srcdir)/darray.h +objspace_dump.o: $(top_srcdir)/gc.h +objspace_dump.o: $(top_srcdir)/internal.h +objspace_dump.o: $(top_srcdir)/internal/array.h +objspace_dump.o: $(top_srcdir)/internal/compilers.h +objspace_dump.o: $(top_srcdir)/internal/gc.h +objspace_dump.o: $(top_srcdir)/internal/hash.h +objspace_dump.o: $(top_srcdir)/internal/imemo.h +objspace_dump.o: $(top_srcdir)/internal/sanitizers.h +objspace_dump.o: $(top_srcdir)/internal/serial.h +objspace_dump.o: $(top_srcdir)/internal/static_assert.h +objspace_dump.o: $(top_srcdir)/internal/string.h +objspace_dump.o: $(top_srcdir)/internal/vm.h +objspace_dump.o: $(top_srcdir)/internal/warnings.h +objspace_dump.o: $(top_srcdir)/method.h +objspace_dump.o: $(top_srcdir)/node.h +objspace_dump.o: $(top_srcdir)/ruby_assert.h +objspace_dump.o: $(top_srcdir)/ruby_atomic.h +objspace_dump.o: $(top_srcdir)/thread_pthread.h +objspace_dump.o: $(top_srcdir)/vm_core.h +objspace_dump.o: $(top_srcdir)/vm_opts.h +objspace_dump.o: objspace.h +objspace_dump.o: objspace_dump.c +objspace_dump.o: {$(VPATH)}id.h +# AUTOGENERATED DEPENDENCIES END diff --git a/ext/objspace/extconf.rb b/ext/objspace/extconf.rb new file mode 100644 index 0000000000..d0c2dbd02b --- /dev/null +++ b/ext/objspace/extconf.rb @@ -0,0 +1,4 @@ +# frozen_string_literal: true +$INCFLAGS << " -I$(topdir) -I$(top_srcdir)" +$VPATH << '$(topdir)' << '$(top_srcdir)' # for id.h. +create_makefile('objspace') diff --git a/ext/objspace/lib/objspace.rb b/ext/objspace/lib/objspace.rb new file mode 100644 index 0000000000..0298b0646c --- /dev/null +++ b/ext/objspace/lib/objspace.rb @@ -0,0 +1,91 @@ +# frozen_string_literal: true + +require 'objspace.so' + +module ObjectSpace + class << self + private :_dump + private :_dump_all + end + + module_function + + # call-seq: + # ObjectSpace.dump(obj[, output: :string]) # => "{ ... }" + # ObjectSpace.dump(obj, output: :file) # => #<File:/tmp/rubyobj20131125-88733-1xkfmpv.json> + # ObjectSpace.dump(obj, output: :stdout) # => nil + # + # Dump the contents of a ruby object as JSON. + # + # This method is only expected to work with C Ruby. + # This is an experimental method and is subject to change. + # In particular, the function signature and output format are + # not guaranteed to be compatible in future versions of ruby. + def dump(obj, output: :string) + out = case output + when :file, nil + require 'tempfile' + Tempfile.create(%w(rubyobj .json)) + when :stdout + STDOUT + when :string + +'' + when IO + output + else + raise ArgumentError, "wrong output option: #{output.inspect}" + end + + ret = _dump(obj, out) + return nil if output == :stdout + ret + end + + + # call-seq: + # ObjectSpace.dump_all([output: :file]) # => #<File:/tmp/rubyheap20131125-88469-laoj3v.json> + # ObjectSpace.dump_all(output: :stdout) # => nil + # ObjectSpace.dump_all(output: :string) # => "{...}\n{...}\n..." + # ObjectSpace.dump_all(output: + # File.open('heap.json','w')) # => #<File:heap.json> + # ObjectSpace.dump_all(output: :string, + # since: 42) # => "{...}\n{...}\n..." + # + # Dump the contents of the ruby heap as JSON. + # + # _since_ must be a non-negative integer or +nil+. + # + # If _since_ is a positive integer, only objects of that generation and + # newer generations are dumped. The current generation can be accessed using + # GC::count. + # + # Objects that were allocated without object allocation tracing enabled + # are ignored. See ::trace_object_allocations for more information and + # examples. + # + # If _since_ is omitted or is +nil+, all objects are dumped. + # + # This method is only expected to work with C Ruby. + # This is an experimental method and is subject to change. + # In particular, the function signature and output format are + # not guaranteed to be compatible in future versions of ruby. + def dump_all(output: :file, full: false, since: nil) + out = case output + when :file, nil + require 'tempfile' + Tempfile.create(%w(rubyheap .json)) + when :stdout + STDOUT + when :string + +'' + when IO + output + else + raise ArgumentError, "wrong output option: #{output.inspect}" + end + + ret = _dump_all(out, full, since) + return nil if output == :stdout + ret + end +end diff --git a/ext/objspace/lib/objspace/trace.rb b/ext/objspace/lib/objspace/trace.rb new file mode 100644 index 0000000000..c23f5a9d52 --- /dev/null +++ b/ext/objspace/lib/objspace/trace.rb @@ -0,0 +1,45 @@ +# This is a simple tool to enable the object allocation tracer. +# When you have an object of unknown provenance, you can use this +# to investigate where the object in question is created. +# +# = Important notice +# +# This is only for debugging purpose. Do not use this in production. +# Require'ing this file immediately starts tracing the object allocation, +# which brings a large performance overhead. +# +# = Usage +# +# 1. Add `require "objspace/trace"` into your code (or add `-robjspace/trace` into the command line) +# 2. `p obj` will show the allocation site of `obj` +# +# Note: This redefines `Kernel#p` method, but not `Object#inspect`. +# +# = Examples +# +# 1: require "objspace/trace" +# 2: +# 3: obj = "str" +# 4: +# 5: p obj #=> "str" @ test.rb:3 + +require 'objspace.so' + +module Kernel + remove_method :p + define_method(:p) do |*objs| + objs.each do |obj| + file = ObjectSpace.allocation_sourcefile(obj) + line = ObjectSpace.allocation_sourceline(obj) + if file + puts "#{ obj.inspect } @ #{ file }:#{ line }" + else + puts obj.inspect + end + end + end +end + +ObjectSpace.trace_object_allocations_start + +warn "objspace/trace is enabled" diff --git a/ext/objspace/object_tracing.c b/ext/objspace/object_tracing.c new file mode 100644 index 0000000000..66d6baa491 --- /dev/null +++ b/ext/objspace/object_tracing.c @@ -0,0 +1,579 @@ +/********************************************************************** + + object_tracing.c - Object Tracing mechanism/ObjectSpace extender for MRI. + + $Author$ + created at: Mon May 27 16:27:44 2013 + + NOTE: This extension library is not expected to exist except C Ruby. + NOTE: This feature is an example usage of internal event tracing APIs. + + All the files in this distribution are covered under the Ruby's + license (see the file COPYING). + +**********************************************************************/ + +#include "internal.h" +#include "ruby/debug.h" +#include "objspace.h" + +struct traceobj_arg { + int running; + int keep_remains; + VALUE newobj_trace; + VALUE freeobj_trace; + st_table *object_table; /* obj (VALUE) -> allocation_info */ + st_table *str_table; /* cstr -> refcount */ + struct traceobj_arg *prev_traceobj_arg; +}; + +static const char * +make_unique_str(st_table *tbl, const char *str, long len) +{ + if (!str) { + return NULL; + } + else { + st_data_t n; + char *result; + + if (st_lookup(tbl, (st_data_t)str, &n)) { + st_insert(tbl, (st_data_t)str, n+1); + st_get_key(tbl, (st_data_t)str, &n); + result = (char *)n; + } + else { + result = (char *)ruby_xmalloc(len+1); + strncpy(result, str, len); + result[len] = 0; + st_add_direct(tbl, (st_data_t)result, 1); + } + return result; + } +} + +static void +delete_unique_str(st_table *tbl, const char *str) +{ + if (str) { + st_data_t n; + + st_lookup(tbl, (st_data_t)str, &n); + if (n == 1) { + n = (st_data_t)str; + st_delete(tbl, &n, 0); + ruby_xfree((char *)n); + } + else { + st_insert(tbl, (st_data_t)str, n-1); + } + } +} + +static void +newobj_i(VALUE tpval, void *data) +{ + struct traceobj_arg *arg = (struct traceobj_arg *)data; + rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval); + VALUE obj = rb_tracearg_object(tparg); + VALUE path = rb_tracearg_path(tparg); + VALUE line = rb_tracearg_lineno(tparg); + VALUE mid = rb_tracearg_method_id(tparg); + VALUE klass = rb_tracearg_defined_class(tparg); + struct allocation_info *info; + const char *path_cstr = RTEST(path) ? make_unique_str(arg->str_table, RSTRING_PTR(path), RSTRING_LEN(path)) : 0; + VALUE class_path = (RTEST(klass) && !OBJ_FROZEN(klass)) ? rb_class_path_cached(klass) : Qnil; + const char *class_path_cstr = RTEST(class_path) ? make_unique_str(arg->str_table, RSTRING_PTR(class_path), RSTRING_LEN(class_path)) : 0; + st_data_t v; + + if (st_lookup(arg->object_table, (st_data_t)obj, &v)) { + info = (struct allocation_info *)v; + if (arg->keep_remains) { + if (info->living) { + /* do nothing. there is possibility to keep living if FREEOBJ events while suppressing tracing */ + } + } + /* reuse info */ + delete_unique_str(arg->str_table, info->path); + delete_unique_str(arg->str_table, info->class_path); + } + else { + info = (struct allocation_info *)ruby_xmalloc(sizeof(struct allocation_info)); + } + info->living = 1; + info->flags = RBASIC(obj)->flags; + info->klass = RBASIC_CLASS(obj); + + info->path = path_cstr; + info->line = NUM2INT(line); + info->mid = mid; + info->class_path = class_path_cstr; + info->generation = rb_gc_count(); + st_insert(arg->object_table, (st_data_t)obj, (st_data_t)info); +} + +static void +freeobj_i(VALUE tpval, void *data) +{ + struct traceobj_arg *arg = (struct traceobj_arg *)data; + rb_trace_arg_t *tparg = rb_tracearg_from_tracepoint(tpval); + st_data_t obj = (st_data_t)rb_tracearg_object(tparg); + st_data_t v; + struct allocation_info *info; + + if (arg->keep_remains) { + if (st_lookup(arg->object_table, obj, &v)) { + info = (struct allocation_info *)v; + info->living = 0; + } + } + else { + if (st_delete(arg->object_table, &obj, &v)) { + info = (struct allocation_info *)v; + delete_unique_str(arg->str_table, info->path); + delete_unique_str(arg->str_table, info->class_path); + ruby_xfree(info); + } + } +} + +static int +free_keys_i(st_data_t key, st_data_t value, st_data_t data) +{ + ruby_xfree((void *)key); + return ST_CONTINUE; +} + +static int +free_values_i(st_data_t key, st_data_t value, st_data_t data) +{ + ruby_xfree((void *)value); + return ST_CONTINUE; +} + +static void +allocation_info_tracer_mark(void *ptr) +{ + struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr; + rb_gc_mark(trace_arg->newobj_trace); + rb_gc_mark(trace_arg->freeobj_trace); +} + +static void +allocation_info_tracer_free(void *ptr) +{ + struct traceobj_arg *arg = (struct traceobj_arg *)ptr; + /* clear tables */ + st_foreach(arg->object_table, free_values_i, 0); + st_free_table(arg->object_table); + st_foreach(arg->str_table, free_keys_i, 0); + st_free_table(arg->str_table); + xfree(arg); +} + +static size_t +allocation_info_tracer_memsize(const void *ptr) +{ + size_t size; + struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr; + size = sizeof(*trace_arg); + size += st_memsize(trace_arg->object_table); + size += st_memsize(trace_arg->str_table); + return size; +} + +static int +hash_foreach_should_replace_key(st_data_t key, st_data_t value, st_data_t argp, int error) +{ + VALUE allocated_object; + + allocated_object = (VALUE)value; + if (allocated_object != rb_gc_location(allocated_object)) { + return ST_REPLACE; + } + + return ST_CONTINUE; +} + +static int +hash_replace_key(st_data_t *key, st_data_t *value, st_data_t argp, int existing) +{ + *key = rb_gc_location((VALUE)*key); + + return ST_CONTINUE; +} + +static void +allocation_info_tracer_compact(void *ptr) +{ + struct traceobj_arg *trace_arg = (struct traceobj_arg *)ptr; + + if (trace_arg->object_table && + st_foreach_with_replace(trace_arg->object_table, hash_foreach_should_replace_key, hash_replace_key, 0)) { + rb_raise(rb_eRuntimeError, "hash modified during iteration"); + } +} + +static const rb_data_type_t allocation_info_tracer_type = { + "ObjectTracing/allocation_info_tracer", + { + allocation_info_tracer_mark, + allocation_info_tracer_free, /* Never called because global */ + allocation_info_tracer_memsize, + allocation_info_tracer_compact, + }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY +}; + +static VALUE traceobj_arg; +static struct traceobj_arg *tmp_trace_arg; /* TODO: Do not use global variables */ +static int tmp_keep_remains; /* TODO: Do not use global variables */ + +static struct traceobj_arg * +get_traceobj_arg(void) +{ + if (tmp_trace_arg == 0) { + VALUE obj = TypedData_Make_Struct(rb_cObject, struct traceobj_arg, &allocation_info_tracer_type, tmp_trace_arg); + traceobj_arg = obj; + rb_gc_register_mark_object(traceobj_arg); + tmp_trace_arg->running = 0; + tmp_trace_arg->keep_remains = tmp_keep_remains; + tmp_trace_arg->newobj_trace = 0; + tmp_trace_arg->freeobj_trace = 0; + tmp_trace_arg->object_table = st_init_numtable(); + tmp_trace_arg->str_table = st_init_strtable(); + } + return tmp_trace_arg; +} + +/* + * call-seq: trace_object_allocations_start + * + * Starts tracing object allocations. + * + */ +static VALUE +trace_object_allocations_start(VALUE self) +{ + struct traceobj_arg *arg = get_traceobj_arg(); + + if (arg->running++ > 0) { + /* do nothing */ + } + else { + if (arg->newobj_trace == 0) { + arg->newobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_NEWOBJ, newobj_i, arg); + arg->freeobj_trace = rb_tracepoint_new(0, RUBY_INTERNAL_EVENT_FREEOBJ, freeobj_i, arg); + } + rb_tracepoint_enable(arg->newobj_trace); + rb_tracepoint_enable(arg->freeobj_trace); + } + + return Qnil; +} + +/* + * call-seq: trace_object_allocations_stop + * + * Stop tracing object allocations. + * + * Note that if ::trace_object_allocations_start is called n-times, then + * tracing will stop after calling ::trace_object_allocations_stop n-times. + * + */ +static VALUE +trace_object_allocations_stop(VALUE self) +{ + struct traceobj_arg *arg = get_traceobj_arg(); + + if (arg->running > 0) { + arg->running--; + } + + if (arg->running == 0) { + if (arg->newobj_trace != 0) { + rb_tracepoint_disable(arg->newobj_trace); + } + if (arg->freeobj_trace != 0) { + rb_tracepoint_disable(arg->freeobj_trace); + } + } + + return Qnil; +} + +/* + * call-seq: trace_object_allocations_clear + * + * Clear recorded tracing information. + * + */ +static VALUE +trace_object_allocations_clear(VALUE self) +{ + struct traceobj_arg *arg = get_traceobj_arg(); + + /* clear tables */ + st_foreach(arg->object_table, free_values_i, 0); + st_clear(arg->object_table); + st_foreach(arg->str_table, free_keys_i, 0); + st_clear(arg->str_table); + + /* do not touch TracePoints */ + + return Qnil; +} + +/* + * call-seq: trace_object_allocations { block } + * + * Starts tracing object allocations from the ObjectSpace extension module. + * + * For example: + * + * require 'objspace' + * + * class C + * include ObjectSpace + * + * def foo + * trace_object_allocations do + * obj = Object.new + * p "#{allocation_sourcefile(obj)}:#{allocation_sourceline(obj)}" + * end + * end + * end + * + * C.new.foo #=> "objtrace.rb:8" + * + * This example has included the ObjectSpace module to make it easier to read, + * but you can also use the ::trace_object_allocations notation (recommended). + * + * Note that this feature introduces a huge performance decrease and huge + * memory consumption. + */ +static VALUE +trace_object_allocations(VALUE self) +{ + trace_object_allocations_start(self); + return rb_ensure(rb_yield, Qnil, trace_object_allocations_stop, self); +} + +int rb_bug_reporter_add(void (*func)(FILE *, void *), void *data); +static int object_allocations_reporter_registered = 0; + +static int +object_allocations_reporter_i(st_data_t key, st_data_t val, st_data_t ptr) +{ + FILE *out = (FILE *)ptr; + VALUE obj = (VALUE)key; + struct allocation_info *info = (struct allocation_info *)val; + + fprintf(out, "-- %p (%s F: %p, ", (void *)obj, info->living ? "live" : "dead", (void *)info->flags); + if (info->class_path) fprintf(out, "C: %s", info->class_path); + else fprintf(out, "C: %p", (void *)info->klass); + fprintf(out, "@%s:%lu", info->path ? info->path : "", info->line); + if (!NIL_P(info->mid)) { + VALUE m = rb_sym2str(info->mid); + fprintf(out, " (%s)", RSTRING_PTR(m)); + } + fprintf(out, ")\n"); + + return ST_CONTINUE; +} + +static void +object_allocations_reporter(FILE *out, void *ptr) +{ + fprintf(out, "== object_allocations_reporter: START\n"); + if (tmp_trace_arg) { + st_foreach(tmp_trace_arg->object_table, object_allocations_reporter_i, (st_data_t)out); + } + fprintf(out, "== object_allocations_reporter: END\n"); +} + +static VALUE +trace_object_allocations_debug_start(VALUE self) +{ + tmp_keep_remains = 1; + if (object_allocations_reporter_registered == 0) { + object_allocations_reporter_registered = 1; + rb_bug_reporter_add(object_allocations_reporter, 0); + } + + return trace_object_allocations_start(self); +} + +static struct allocation_info * +lookup_allocation_info(VALUE obj) +{ + if (tmp_trace_arg) { + st_data_t info; + if (st_lookup(tmp_trace_arg->object_table, obj, &info)) { + return (struct allocation_info *)info; + } + } + return NULL; +} + +struct allocation_info * +objspace_lookup_allocation_info(VALUE obj) +{ + return lookup_allocation_info(obj); +} + +/* + * call-seq: allocation_sourcefile(object) -> string + * + * Returns the source file origin from the given +object+. + * + * See ::trace_object_allocations for more information and examples. + */ +static VALUE +allocation_sourcefile(VALUE self, VALUE obj) +{ + struct allocation_info *info = lookup_allocation_info(obj); + + if (info && info->path) { + return rb_str_new2(info->path); + } + else { + return Qnil; + } +} + +/* + * call-seq: allocation_sourceline(object) -> integer + * + * Returns the original line from source for from the given +object+. + * + * See ::trace_object_allocations for more information and examples. + */ +static VALUE +allocation_sourceline(VALUE self, VALUE obj) +{ + struct allocation_info *info = lookup_allocation_info(obj); + + if (info) { + return INT2FIX(info->line); + } + else { + return Qnil; + } +} + +/* + * call-seq: allocation_class_path(object) -> string + * + * Returns the class for the given +object+. + * + * class A + * def foo + * ObjectSpace::trace_object_allocations do + * obj = Object.new + * p "#{ObjectSpace::allocation_class_path(obj)}" + * end + * end + * end + * + * A.new.foo #=> "Class" + * + * See ::trace_object_allocations for more information and examples. + */ +static VALUE +allocation_class_path(VALUE self, VALUE obj) +{ + struct allocation_info *info = lookup_allocation_info(obj); + + if (info && info->class_path) { + return rb_str_new2(info->class_path); + } + else { + return Qnil; + } +} + +/* + * call-seq: allocation_method_id(object) -> string + * + * Returns the method identifier for the given +object+. + * + * class A + * include ObjectSpace + * + * def foo + * trace_object_allocations do + * obj = Object.new + * p "#{allocation_class_path(obj)}##{allocation_method_id(obj)}" + * end + * end + * end + * + * A.new.foo #=> "Class#new" + * + * See ::trace_object_allocations for more information and examples. + */ +static VALUE +allocation_method_id(VALUE self, VALUE obj) +{ + struct allocation_info *info = lookup_allocation_info(obj); + if (info) { + return info->mid; + } + else { + return Qnil; + } +} + +/* + * call-seq: allocation_generation(object) -> integer or nil + * + * Returns garbage collector generation for the given +object+. + * + * class B + * include ObjectSpace + * + * def foo + * trace_object_allocations do + * obj = Object.new + * p "Generation is #{allocation_generation(obj)}" + * end + * end + * end + * + * B.new.foo #=> "Generation is 3" + * + * See ::trace_object_allocations for more information and examples. + */ +static VALUE +allocation_generation(VALUE self, VALUE obj) +{ + struct allocation_info *info = lookup_allocation_info(obj); + if (info) { + return SIZET2NUM(info->generation); + } + else { + return Qnil; + } +} + +void +Init_object_tracing(VALUE rb_mObjSpace) +{ +#if 0 + rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */ +#endif + + rb_define_module_function(rb_mObjSpace, "trace_object_allocations", trace_object_allocations, 0); + rb_define_module_function(rb_mObjSpace, "trace_object_allocations_start", trace_object_allocations_start, 0); + rb_define_module_function(rb_mObjSpace, "trace_object_allocations_stop", trace_object_allocations_stop, 0); + rb_define_module_function(rb_mObjSpace, "trace_object_allocations_clear", trace_object_allocations_clear, 0); + + rb_define_module_function(rb_mObjSpace, "trace_object_allocations_debug_start", trace_object_allocations_debug_start, 0); + + rb_define_module_function(rb_mObjSpace, "allocation_sourcefile", allocation_sourcefile, 1); + rb_define_module_function(rb_mObjSpace, "allocation_sourceline", allocation_sourceline, 1); + rb_define_module_function(rb_mObjSpace, "allocation_class_path", allocation_class_path, 1); + rb_define_module_function(rb_mObjSpace, "allocation_method_id", allocation_method_id, 1); + rb_define_module_function(rb_mObjSpace, "allocation_generation", allocation_generation, 1); +} diff --git a/ext/objspace/objspace.c b/ext/objspace/objspace.c new file mode 100644 index 0000000000..ad5bbe7d0c --- /dev/null +++ b/ext/objspace/objspace.c @@ -0,0 +1,1005 @@ +/********************************************************************** + + objspace.c - ObjectSpace extender for MRI. + + $Author$ + created at: Wed Jun 17 07:39:17 2009 + + NOTE: This extension library is only expected to exist with C Ruby. + + All the files in this distribution are covered under the Ruby's + license (see the file COPYING). + +**********************************************************************/ + +#include "gc.h" +#include "internal.h" +#include "internal/class.h" +#include "internal/compilers.h" +#include "internal/hash.h" +#include "internal/imemo.h" +#include "internal/sanitizers.h" +#include "node.h" +#include "ruby/io.h" +#include "ruby/re.h" +#include "ruby/st.h" +#include "symbol.h" + +#undef rb_funcall + +#include "ruby/ruby.h" + +/* + * call-seq: + * ObjectSpace.memsize_of(obj) -> Integer + * + * Return consuming memory size of obj in bytes. + * + * Note that the return size is incomplete. You need to deal with this + * information as only a *HINT*. Especially, the size of +T_DATA+ may not be + * correct. + * + * This method is only expected to work with C Ruby. + * + * From Ruby 2.2, memsize_of(obj) returns a memory size includes + * sizeof(RVALUE). + */ + +static VALUE +memsize_of_m(VALUE self, VALUE obj) +{ + return SIZET2NUM(rb_obj_memsize_of(obj)); +} + +struct total_data { + size_t total; + VALUE klass; +}; + +static void +total_i(VALUE v, void *ptr) +{ + struct total_data *data = (struct total_data *)ptr; + + switch (BUILTIN_TYPE(v)) { + case T_NONE: + case T_IMEMO: + case T_ICLASS: + case T_NODE: + case T_ZOMBIE: + return; + default: + if (data->klass == 0 || rb_obj_is_kind_of(v, data->klass)) { + data->total += rb_obj_memsize_of(v); + } + } +} + +typedef void (*each_obj_with_flags)(VALUE, void*); + +struct obj_itr { + each_obj_with_flags cb; + void *data; +}; + +static int +heap_iter(void *vstart, void *vend, size_t stride, void *ptr) +{ + struct obj_itr * ctx = (struct obj_itr *)ptr; + VALUE v; + + for (v = (VALUE)vstart; v != (VALUE)vend; v += stride) { + void *poisoned = asan_poisoned_object_p(v); + asan_unpoison_object(v, false); + + if (RBASIC(v)->flags) { + (*ctx->cb)(v, ctx->data); + } + + if (poisoned) { + asan_poison_object(v); + } + } + + return 0; +} + +static void +each_object_with_flags(each_obj_with_flags cb, void *ctx) +{ + struct obj_itr data; + data.cb = cb; + data.data = ctx; + rb_objspace_each_objects(heap_iter, &data); +} + +/* + * call-seq: + * ObjectSpace.memsize_of_all([klass]) -> Integer + * + * Return consuming memory size of all living objects in bytes. + * + * If +klass+ (should be Class object) is given, return the total memory size + * of instances of the given class. + * + * Note that the returned size is incomplete. You need to deal with this + * information as only a *HINT*. Especially, the size of +T_DATA+ may not be + * correct. + * + * Note that this method does *NOT* return total malloc'ed memory size. + * + * This method can be defined by the following Ruby code: + * + * def memsize_of_all klass = false + * total = 0 + * ObjectSpace.each_object{|e| + * total += ObjectSpace.memsize_of(e) if klass == false || e.kind_of?(klass) + * } + * total + * end + * + * This method is only expected to work with C Ruby. + */ + +static VALUE +memsize_of_all_m(int argc, VALUE *argv, VALUE self) +{ + struct total_data data = {0, 0}; + + if (argc > 0) { + rb_scan_args(argc, argv, "01", &data.klass); + } + + each_object_with_flags(total_i, &data); + return SIZET2NUM(data.total); +} + +static int +set_zero_i(st_data_t key, st_data_t val, st_data_t arg) +{ + VALUE k = (VALUE)key; + VALUE hash = (VALUE)arg; + rb_hash_aset(hash, k, INT2FIX(0)); + return ST_CONTINUE; +} + +static VALUE +setup_hash(int argc, VALUE *argv) +{ + VALUE hash; + + if (rb_scan_args(argc, argv, "01", &hash) == 1) { + if (!RB_TYPE_P(hash, T_HASH)) + rb_raise(rb_eTypeError, "non-hash given"); + } + + if (hash == Qnil) { + hash = rb_hash_new(); + } + else if (!RHASH_EMPTY_P(hash)) { + /* WB: no new reference */ + st_foreach(RHASH_TBL_RAW(hash), set_zero_i, hash); + } + + return hash; +} + +static void +cos_i(VALUE v, void *data) +{ + size_t *counts = (size_t *)data; + counts[BUILTIN_TYPE(v)] += rb_obj_memsize_of(v); +} + +static VALUE +type2sym(enum ruby_value_type i) +{ + VALUE type; + switch (i) { +#define CASE_TYPE(t) case t: type = ID2SYM(rb_intern(#t)); break; + CASE_TYPE(T_NONE); + CASE_TYPE(T_OBJECT); + CASE_TYPE(T_CLASS); + CASE_TYPE(T_MODULE); + CASE_TYPE(T_FLOAT); + CASE_TYPE(T_STRING); + CASE_TYPE(T_REGEXP); + CASE_TYPE(T_ARRAY); + CASE_TYPE(T_HASH); + CASE_TYPE(T_STRUCT); + CASE_TYPE(T_BIGNUM); + CASE_TYPE(T_FILE); + CASE_TYPE(T_DATA); + CASE_TYPE(T_MATCH); + CASE_TYPE(T_COMPLEX); + CASE_TYPE(T_RATIONAL); + CASE_TYPE(T_NIL); + CASE_TYPE(T_TRUE); + CASE_TYPE(T_FALSE); + CASE_TYPE(T_SYMBOL); + CASE_TYPE(T_FIXNUM); + CASE_TYPE(T_UNDEF); + CASE_TYPE(T_IMEMO); + CASE_TYPE(T_NODE); + CASE_TYPE(T_ICLASS); + CASE_TYPE(T_MOVED); + CASE_TYPE(T_ZOMBIE); +#undef CASE_TYPE + default: rb_bug("type2sym: unknown type (%d)", i); + } + return type; +} + +/* + * call-seq: + * ObjectSpace.count_objects_size([result_hash]) -> hash + * + * Counts objects size (in bytes) for each type. + * + * Note that this information is incomplete. You need to deal with + * this information as only a *HINT*. Especially, total size of + * T_DATA may be wrong. + * + * It returns a hash as: + * {:TOTAL=>1461154, :T_CLASS=>158280, :T_MODULE=>20672, :T_STRING=>527249, ...} + * + * If the optional argument, result_hash, is given, + * it is overwritten and returned. + * This is intended to avoid probe effect. + * + * The contents of the returned hash is implementation defined. + * It may be changed in future. + * + * This method is only expected to work with C Ruby. + */ + +static VALUE +count_objects_size(int argc, VALUE *argv, VALUE os) +{ + size_t counts[T_MASK+1]; + size_t total = 0; + enum ruby_value_type i; + VALUE hash = setup_hash(argc, argv); + + for (i = 0; i <= T_MASK; i++) { + counts[i] = 0; + } + + each_object_with_flags(cos_i, &counts[0]); + + for (i = 0; i <= T_MASK; i++) { + if (counts[i]) { + VALUE type = type2sym(i); + total += counts[i]; + rb_hash_aset(hash, type, SIZET2NUM(counts[i])); + } + } + rb_hash_aset(hash, ID2SYM(rb_intern("TOTAL")), SIZET2NUM(total)); + return hash; +} + +struct dynamic_symbol_counts { + size_t mortal; + size_t immortal; +}; + +static void +cs_i(VALUE v, void *n) +{ + struct dynamic_symbol_counts *counts = (struct dynamic_symbol_counts *)n; + + if (BUILTIN_TYPE(v) == T_SYMBOL) { + ID id = RSYMBOL(v)->id; + if ((id & ~ID_SCOPE_MASK) == 0) { + counts->mortal++; + } + else { + counts->immortal++; + } + } +} + +size_t rb_sym_immortal_count(void); + +/* + * call-seq: + * ObjectSpace.count_symbols([result_hash]) -> hash + * + * Counts symbols for each Symbol type. + * + * This method is only for MRI developers interested in performance and memory + * usage of Ruby programs. + * + * If the optional argument, result_hash, is given, it is overwritten and + * returned. This is intended to avoid probe effect. + * + * Note: + * The contents of the returned hash is implementation defined. + * It may be changed in future. + * + * This method is only expected to work with C Ruby. + * + * On this version of MRI, they have 3 types of Symbols (and 1 total counts). + * + * * mortal_dynamic_symbol: GC target symbols (collected by GC) + * * immortal_dynamic_symbol: Immortal symbols promoted from dynamic symbols (do not collected by GC) + * * immortal_static_symbol: Immortal symbols (do not collected by GC) + * * immortal_symbol: total immortal symbols (immortal_dynamic_symbol+immortal_static_symbol) + */ + +static VALUE +count_symbols(int argc, VALUE *argv, VALUE os) +{ + struct dynamic_symbol_counts dynamic_counts = {0, 0}; + VALUE hash = setup_hash(argc, argv); + + size_t immortal_symbols = rb_sym_immortal_count(); + each_object_with_flags(cs_i, &dynamic_counts); + + rb_hash_aset(hash, ID2SYM(rb_intern("mortal_dynamic_symbol")), SIZET2NUM(dynamic_counts.mortal)); + rb_hash_aset(hash, ID2SYM(rb_intern("immortal_dynamic_symbol")), SIZET2NUM(dynamic_counts.immortal)); + rb_hash_aset(hash, ID2SYM(rb_intern("immortal_static_symbol")), SIZET2NUM(immortal_symbols - dynamic_counts.immortal)); + rb_hash_aset(hash, ID2SYM(rb_intern("immortal_symbol")), SIZET2NUM(immortal_symbols)); + + return hash; +} + +static void +cn_i(VALUE v, void *n) +{ + size_t *nodes = (size_t *)n; + + if (BUILTIN_TYPE(v) == T_NODE) { + size_t s = nd_type((NODE *)v); + nodes[s]++; + } +} + +/* + * call-seq: + * ObjectSpace.count_nodes([result_hash]) -> hash + * + * Counts nodes for each node type. + * + * This method is only for MRI developers interested in performance and memory + * usage of Ruby programs. + * + * It returns a hash as: + * + * {:NODE_METHOD=>2027, :NODE_FBODY=>1927, :NODE_CFUNC=>1798, ...} + * + * If the optional argument, result_hash, is given, it is overwritten and + * returned. This is intended to avoid probe effect. + * + * Note: + * The contents of the returned hash is implementation defined. + * It may be changed in future. + * + * This method is only expected to work with C Ruby. + */ + +static VALUE +count_nodes(int argc, VALUE *argv, VALUE os) +{ + size_t nodes[NODE_LAST+1]; + enum node_type i; + VALUE hash = setup_hash(argc, argv); + + for (i = 0; i <= NODE_LAST; i++) { + nodes[i] = 0; + } + + each_object_with_flags(cn_i, &nodes[0]); + + for (i=0; i<NODE_LAST; i++) { + if (nodes[i] != 0) { + VALUE node; + switch (i) { +#define COUNT_NODE(n) case n: node = ID2SYM(rb_intern(#n)); goto set + COUNT_NODE(NODE_SCOPE); + COUNT_NODE(NODE_BLOCK); + COUNT_NODE(NODE_IF); + COUNT_NODE(NODE_UNLESS); + COUNT_NODE(NODE_CASE); + COUNT_NODE(NODE_CASE2); + COUNT_NODE(NODE_CASE3); + COUNT_NODE(NODE_WHEN); + COUNT_NODE(NODE_IN); + COUNT_NODE(NODE_WHILE); + COUNT_NODE(NODE_UNTIL); + COUNT_NODE(NODE_ITER); + COUNT_NODE(NODE_FOR); + COUNT_NODE(NODE_FOR_MASGN); + COUNT_NODE(NODE_BREAK); + COUNT_NODE(NODE_NEXT); + COUNT_NODE(NODE_REDO); + COUNT_NODE(NODE_RETRY); + COUNT_NODE(NODE_BEGIN); + COUNT_NODE(NODE_RESCUE); + COUNT_NODE(NODE_RESBODY); + COUNT_NODE(NODE_ENSURE); + COUNT_NODE(NODE_AND); + COUNT_NODE(NODE_OR); + COUNT_NODE(NODE_MASGN); + COUNT_NODE(NODE_LASGN); + COUNT_NODE(NODE_DASGN); + COUNT_NODE(NODE_GASGN); + COUNT_NODE(NODE_IASGN); + COUNT_NODE(NODE_CDECL); + COUNT_NODE(NODE_CVASGN); + COUNT_NODE(NODE_OP_ASGN1); + COUNT_NODE(NODE_OP_ASGN2); + COUNT_NODE(NODE_OP_ASGN_AND); + COUNT_NODE(NODE_OP_ASGN_OR); + COUNT_NODE(NODE_OP_CDECL); + COUNT_NODE(NODE_CALL); + COUNT_NODE(NODE_OPCALL); + COUNT_NODE(NODE_FCALL); + COUNT_NODE(NODE_VCALL); + COUNT_NODE(NODE_QCALL); + COUNT_NODE(NODE_SUPER); + COUNT_NODE(NODE_ZSUPER); + COUNT_NODE(NODE_LIST); + COUNT_NODE(NODE_ZLIST); + COUNT_NODE(NODE_VALUES); + COUNT_NODE(NODE_HASH); + COUNT_NODE(NODE_RETURN); + COUNT_NODE(NODE_YIELD); + COUNT_NODE(NODE_LVAR); + COUNT_NODE(NODE_DVAR); + COUNT_NODE(NODE_GVAR); + COUNT_NODE(NODE_IVAR); + COUNT_NODE(NODE_CONST); + COUNT_NODE(NODE_CVAR); + COUNT_NODE(NODE_NTH_REF); + COUNT_NODE(NODE_BACK_REF); + COUNT_NODE(NODE_MATCH); + COUNT_NODE(NODE_MATCH2); + COUNT_NODE(NODE_MATCH3); + COUNT_NODE(NODE_LIT); + COUNT_NODE(NODE_STR); + COUNT_NODE(NODE_DSTR); + COUNT_NODE(NODE_XSTR); + COUNT_NODE(NODE_DXSTR); + COUNT_NODE(NODE_EVSTR); + COUNT_NODE(NODE_DREGX); + COUNT_NODE(NODE_ONCE); + COUNT_NODE(NODE_ARGS); + COUNT_NODE(NODE_ARGS_AUX); + COUNT_NODE(NODE_OPT_ARG); + COUNT_NODE(NODE_KW_ARG); + COUNT_NODE(NODE_POSTARG); + COUNT_NODE(NODE_ARGSCAT); + COUNT_NODE(NODE_ARGSPUSH); + COUNT_NODE(NODE_SPLAT); + COUNT_NODE(NODE_BLOCK_PASS); + COUNT_NODE(NODE_DEFN); + COUNT_NODE(NODE_DEFS); + COUNT_NODE(NODE_ALIAS); + COUNT_NODE(NODE_VALIAS); + COUNT_NODE(NODE_UNDEF); + COUNT_NODE(NODE_CLASS); + COUNT_NODE(NODE_MODULE); + COUNT_NODE(NODE_SCLASS); + COUNT_NODE(NODE_COLON2); + COUNT_NODE(NODE_COLON3); + COUNT_NODE(NODE_DOT2); + COUNT_NODE(NODE_DOT3); + COUNT_NODE(NODE_FLIP2); + COUNT_NODE(NODE_FLIP3); + COUNT_NODE(NODE_SELF); + COUNT_NODE(NODE_NIL); + COUNT_NODE(NODE_TRUE); + COUNT_NODE(NODE_FALSE); + COUNT_NODE(NODE_ERRINFO); + COUNT_NODE(NODE_DEFINED); + COUNT_NODE(NODE_POSTEXE); + COUNT_NODE(NODE_DSYM); + COUNT_NODE(NODE_ATTRASGN); + COUNT_NODE(NODE_LAMBDA); + COUNT_NODE(NODE_ARYPTN); + COUNT_NODE(NODE_FNDPTN); + COUNT_NODE(NODE_HSHPTN); +#undef COUNT_NODE + case NODE_LAST: break; + } + UNREACHABLE; + set: + rb_hash_aset(hash, node, SIZET2NUM(nodes[i])); + } + } + return hash; +} + +static void +cto_i(VALUE v, void *data) +{ + VALUE hash = (VALUE)data; + + if (BUILTIN_TYPE(v) == T_DATA) { + VALUE counter; + VALUE key = RBASIC(v)->klass; + + if (key == 0) { + const char *name = rb_objspace_data_type_name(v); + if (name == 0) name = "unknown"; + key = ID2SYM(rb_intern(name)); + } + + counter = rb_hash_aref(hash, key); + if (NIL_P(counter)) { + counter = INT2FIX(1); + } + else { + counter = INT2FIX(FIX2INT(counter) + 1); + } + + rb_hash_aset(hash, key, counter); + } +} + +/* + * call-seq: + * ObjectSpace.count_tdata_objects([result_hash]) -> hash + * + * Counts objects for each +T_DATA+ type. + * + * This method is only for MRI developers interested in performance and memory + * usage of Ruby programs. + * + * It returns a hash as: + * + * {RubyVM::InstructionSequence=>504, :parser=>5, :barrier=>6, + * :mutex=>6, Proc=>60, RubyVM::Env=>57, Mutex=>1, Encoding=>99, + * ThreadGroup=>1, Binding=>1, Thread=>1, RubyVM=>1, :iseq=>1, + * Random=>1, ARGF.class=>1, Data=>1, :autoload=>3, Time=>2} + * # T_DATA objects existing at startup on r32276. + * + * If the optional argument, result_hash, is given, it is overwritten and + * returned. This is intended to avoid probe effect. + * + * The contents of the returned hash is implementation specific and may change + * in the future. + * + * In this version, keys are Class object or Symbol object. + * + * If object is kind of normal (accessible) object, the key is Class object. + * If object is not a kind of normal (internal) object, the key is symbol + * name, registered by rb_data_type_struct. + * + * This method is only expected to work with C Ruby. + */ + +static VALUE +count_tdata_objects(int argc, VALUE *argv, VALUE self) +{ + VALUE hash = setup_hash(argc, argv); + each_object_with_flags(cto_i, (void *)hash); + return hash; +} + +static ID imemo_type_ids[IMEMO_MASK+1]; + +static void +count_imemo_objects_i(VALUE v, void *data) +{ + VALUE hash = (VALUE)data; + + if (BUILTIN_TYPE(v) == T_IMEMO) { + VALUE counter; + VALUE key = ID2SYM(imemo_type_ids[imemo_type(v)]); + + counter = rb_hash_aref(hash, key); + + if (NIL_P(counter)) { + counter = INT2FIX(1); + } + else { + counter = INT2FIX(FIX2INT(counter) + 1); + } + + rb_hash_aset(hash, key, counter); + } +} + +/* + * call-seq: + * ObjectSpace.count_imemo_objects([result_hash]) -> hash + * + * Counts objects for each +T_IMEMO+ type. + * + * This method is only for MRI developers interested in performance and memory + * usage of Ruby programs. + * + * It returns a hash as: + * + * {:imemo_ifunc=>8, + * :imemo_svar=>7, + * :imemo_cref=>509, + * :imemo_memo=>1, + * :imemo_throw_data=>1} + * + * If the optional argument, result_hash, is given, it is overwritten and + * returned. This is intended to avoid probe effect. + * + * The contents of the returned hash is implementation specific and may change + * in the future. + * + * In this version, keys are symbol objects. + * + * This method is only expected to work with C Ruby. + */ + +static VALUE +count_imemo_objects(int argc, VALUE *argv, VALUE self) +{ + VALUE hash = setup_hash(argc, argv); + + if (imemo_type_ids[0] == 0) { + imemo_type_ids[0] = rb_intern("imemo_env"); + imemo_type_ids[1] = rb_intern("imemo_cref"); + imemo_type_ids[2] = rb_intern("imemo_svar"); + imemo_type_ids[3] = rb_intern("imemo_throw_data"); + imemo_type_ids[4] = rb_intern("imemo_ifunc"); + imemo_type_ids[5] = rb_intern("imemo_memo"); + imemo_type_ids[6] = rb_intern("imemo_ment"); + imemo_type_ids[7] = rb_intern("imemo_iseq"); + imemo_type_ids[8] = rb_intern("imemo_tmpbuf"); + imemo_type_ids[9] = rb_intern("imemo_ast"); + imemo_type_ids[10] = rb_intern("imemo_parser_strterm"); + imemo_type_ids[11] = rb_intern("imemo_callinfo"); + imemo_type_ids[12] = rb_intern("imemo_callcache"); + imemo_type_ids[13] = rb_intern("imemo_constcache"); + } + + each_object_with_flags(count_imemo_objects_i, (void *)hash); + + return hash; +} + +static void +iow_mark(void *ptr) +{ + rb_gc_mark((VALUE)ptr); +} + +static size_t +iow_size(const void *ptr) +{ + VALUE obj = (VALUE)ptr; + return rb_obj_memsize_of(obj); +} + +static const rb_data_type_t iow_data_type = { + "ObjectSpace::InternalObjectWrapper", + {iow_mark, 0, iow_size,}, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY +}; + +static VALUE rb_cInternalObjectWrapper; + +static VALUE +iow_newobj(VALUE obj) +{ + return TypedData_Wrap_Struct(rb_cInternalObjectWrapper, &iow_data_type, (void *)obj); +} + +/* Returns the type of the internal object. */ +static VALUE +iow_type(VALUE self) +{ + VALUE obj = (VALUE)DATA_PTR(self); + return type2sym(BUILTIN_TYPE(obj)); +} + +/* See Object#inspect. */ +static VALUE +iow_inspect(VALUE self) +{ + VALUE obj = (VALUE)DATA_PTR(self); + VALUE type = type2sym(BUILTIN_TYPE(obj)); + + return rb_sprintf("#<InternalObject:%p %"PRIsVALUE">", (void *)obj, rb_sym2str(type)); +} + +/* Returns the Object#object_id of the internal object. */ +static VALUE +iow_internal_object_id(VALUE self) +{ + VALUE obj = (VALUE)DATA_PTR(self); + return rb_obj_id(obj); +} + +struct rof_data { + VALUE refs; + VALUE internals; +}; + +static void +reachable_object_from_i(VALUE obj, void *data_ptr) +{ + struct rof_data *data = (struct rof_data *)data_ptr; + VALUE key = obj; + VALUE val = obj; + + if (rb_objspace_markable_object_p(obj)) { + if (rb_objspace_internal_object_p(obj)) { + val = iow_newobj(obj); + rb_ary_push(data->internals, val); + } + rb_hash_aset(data->refs, key, val); + } +} + +static int +collect_values(st_data_t key, st_data_t value, st_data_t data) +{ + VALUE ary = (VALUE)data; + rb_ary_push(ary, (VALUE)value); + return ST_CONTINUE; +} + +/* + * call-seq: + * ObjectSpace.reachable_objects_from(obj) -> array or nil + * + * [MRI specific feature] Return all reachable objects from `obj'. + * + * This method returns all reachable objects from `obj'. + * + * If `obj' has two or more references to the same object `x', then returned + * array only includes one `x' object. + * + * If `obj' is a non-markable (non-heap management) object such as true, + * false, nil, symbols and Fixnums (and Flonum) then it simply returns nil. + * + * If `obj' has references to an internal object, then it returns instances of + * ObjectSpace::InternalObjectWrapper class. This object contains a reference + * to an internal object and you can check the type of internal object with + * `type' method. + * + * If `obj' is instance of ObjectSpace::InternalObjectWrapper class, then this + * method returns all reachable object from an internal object, which is + * pointed by `obj'. + * + * With this method, you can find memory leaks. + * + * This method is only expected to work except with C Ruby. + * + * Example: + * ObjectSpace.reachable_objects_from(['a', 'b', 'c']) + * #=> [Array, 'a', 'b', 'c'] + * + * ObjectSpace.reachable_objects_from(['a', 'a', 'a']) + * #=> [Array, 'a', 'a', 'a'] # all 'a' strings have different object id + * + * ObjectSpace.reachable_objects_from([v = 'a', v, v]) + * #=> [Array, 'a'] + * + * ObjectSpace.reachable_objects_from(1) + * #=> nil # 1 is not markable (heap managed) object + * + */ + +static VALUE +reachable_objects_from(VALUE self, VALUE obj) +{ + if (rb_objspace_markable_object_p(obj)) { + struct rof_data data; + + if (rb_typeddata_is_kind_of(obj, &iow_data_type)) { + obj = (VALUE)DATA_PTR(obj); + } + + data.refs = rb_ident_hash_new(); + data.internals = rb_ary_new(); + + rb_objspace_reachable_objects_from(obj, reachable_object_from_i, &data); + + return rb_funcall(data.refs, rb_intern("values"), 0); + } + else { + return Qnil; + } +} + +struct rofr_data { + VALUE categories; + const char *last_category; + VALUE last_category_str; + VALUE last_category_objects; +}; + +static void +reachable_object_from_root_i(const char *category, VALUE obj, void *ptr) +{ + struct rofr_data *data = (struct rofr_data *)ptr; + VALUE category_str; + VALUE category_objects; + + if (category == data->last_category) { + category_str = data->last_category_str; + category_objects = data->last_category_objects; + } + else { + data->last_category = category; + category_str = data->last_category_str = rb_str_new2(category); + category_objects = data->last_category_objects = rb_ident_hash_new(); + if (!NIL_P(rb_hash_lookup(data->categories, category_str))) { + rb_bug("reachable_object_from_root_i: category should insert at once"); + } + rb_hash_aset(data->categories, category_str, category_objects); + } + + if (rb_objspace_markable_object_p(obj) && + obj != data->categories && + obj != data->last_category_objects) { + if (rb_objspace_internal_object_p(obj)) { + obj = iow_newobj(obj); + } + rb_hash_aset(category_objects, obj, obj); + } +} + +static int +collect_values_of_values(VALUE category, VALUE category_objects, VALUE categories) +{ + VALUE ary = rb_ary_new(); + rb_hash_foreach(category_objects, collect_values, ary); + rb_hash_aset(categories, category, ary); + return ST_CONTINUE; +} + +/* + * call-seq: + * ObjectSpace.reachable_objects_from_root -> hash + * + * [MRI specific feature] Return all reachable objects from root. + */ +static VALUE +reachable_objects_from_root(VALUE self) +{ + struct rofr_data data; + VALUE hash = data.categories = rb_ident_hash_new(); + data.last_category = 0; + + rb_objspace_reachable_objects_from_root(reachable_object_from_root_i, &data); + rb_hash_foreach(hash, collect_values_of_values, hash); + + return hash; +} + +static VALUE +wrap_klass_iow(VALUE klass) +{ + if (!RTEST(klass)) { + return Qnil; + } + else if (RB_TYPE_P(klass, T_ICLASS) || + CLASS_OF(klass) == Qfalse /* hidden object */) { + return iow_newobj(klass); + } + else { + return klass; + } +} + +/* + * call-seq: + * ObjectSpace.internal_class_of(obj) -> Class or Module + * + * [MRI specific feature] Return internal class of obj. + * obj can be an instance of InternalObjectWrapper. + * + * Note that you should not use this method in your application. + */ +static VALUE +objspace_internal_class_of(VALUE self, VALUE obj) +{ + VALUE klass; + + if (rb_typeddata_is_kind_of(obj, &iow_data_type)) { + obj = (VALUE)DATA_PTR(obj); + } + + if (RB_TYPE_P(obj, T_IMEMO)) { + return Qnil; + } + else { + klass = CLASS_OF(obj); + return wrap_klass_iow(klass); + } +} + +/* + * call-seq: + * ObjectSpace.internal_super_of(cls) -> Class or Module + * + * [MRI specific feature] Return internal super class of cls (Class or Module). + * obj can be an instance of InternalObjectWrapper. + * + * Note that you should not use this method in your application. + */ +static VALUE +objspace_internal_super_of(VALUE self, VALUE obj) +{ + VALUE super; + + if (rb_typeddata_is_kind_of(obj, &iow_data_type)) { + obj = (VALUE)DATA_PTR(obj); + } + + switch (OBJ_BUILTIN_TYPE(obj)) { + case T_MODULE: + case T_CLASS: + case T_ICLASS: + super = RCLASS_SUPER(obj); + break; + default: + rb_raise(rb_eArgError, "class or module is expected"); + } + + return wrap_klass_iow(super); +} + +void Init_object_tracing(VALUE rb_mObjSpace); +void Init_objspace_dump(VALUE rb_mObjSpace); + +/* + * Document-module: ObjectSpace + * + * The objspace library extends the ObjectSpace module and adds several + * methods to get internal statistic information about + * object/memory management. + * + * You need to <code>require 'objspace'</code> to use this extension module. + * + * Generally, you *SHOULD NOT* use this library if you do not know + * about the MRI implementation. Mainly, this library is for (memory) + * profiler developers and MRI developers who need to know about MRI + * memory usage. + */ + +void +Init_objspace(void) +{ +#undef rb_intern + VALUE rb_mObjSpace; +#if 0 + rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */ +#endif + rb_mObjSpace = rb_const_get(rb_cObject, rb_intern("ObjectSpace")); + + rb_define_module_function(rb_mObjSpace, "memsize_of", memsize_of_m, 1); + rb_define_module_function(rb_mObjSpace, "memsize_of_all", memsize_of_all_m, -1); + + rb_define_module_function(rb_mObjSpace, "count_objects_size", count_objects_size, -1); + rb_define_module_function(rb_mObjSpace, "count_symbols", count_symbols, -1); + rb_define_module_function(rb_mObjSpace, "count_nodes", count_nodes, -1); + rb_define_module_function(rb_mObjSpace, "count_tdata_objects", count_tdata_objects, -1); + rb_define_module_function(rb_mObjSpace, "count_imemo_objects", count_imemo_objects, -1); + + rb_define_module_function(rb_mObjSpace, "reachable_objects_from", reachable_objects_from, 1); + rb_define_module_function(rb_mObjSpace, "reachable_objects_from_root", reachable_objects_from_root, 0); + + rb_define_module_function(rb_mObjSpace, "internal_class_of", objspace_internal_class_of, 1); + rb_define_module_function(rb_mObjSpace, "internal_super_of", objspace_internal_super_of, 1); + + /* + * This class is used as a return value from + * ObjectSpace::reachable_objects_from. + * + * When ObjectSpace::reachable_objects_from returns an object with + * references to an internal object, an instance of this class is returned. + * + * You can use the #type method to check the type of the internal object. + */ + rb_cInternalObjectWrapper = rb_define_class_under(rb_mObjSpace, "InternalObjectWrapper", rb_cObject); + rb_undef_alloc_func(rb_cInternalObjectWrapper); + rb_define_method(rb_cInternalObjectWrapper, "type", iow_type, 0); + rb_define_method(rb_cInternalObjectWrapper, "inspect", iow_inspect, 0); + rb_define_method(rb_cInternalObjectWrapper, "internal_object_id", iow_internal_object_id, 0); + + Init_object_tracing(rb_mObjSpace); + Init_objspace_dump(rb_mObjSpace); +} diff --git a/ext/objspace/objspace.h b/ext/objspace/objspace.h new file mode 100644 index 0000000000..95b84d6c1e --- /dev/null +++ b/ext/objspace/objspace.h @@ -0,0 +1,20 @@ +#ifndef OBJSPACE_H +#define OBJSPACE_H 1 + +/* object_tracing.c */ +struct allocation_info { + /* all of information don't need marking. */ + int living; + VALUE flags; + VALUE klass; + + /* allocation info */ + const char *path; + unsigned long line; + const char *class_path; + VALUE mid; + size_t generation; +}; +struct allocation_info *objspace_lookup_allocation_info(VALUE obj); + +#endif diff --git a/ext/objspace/objspace_dump.c b/ext/objspace/objspace_dump.c new file mode 100644 index 0000000000..47081754c9 --- /dev/null +++ b/ext/objspace/objspace_dump.c @@ -0,0 +1,657 @@ +/********************************************************************** + + objspace_dump.c - Heap dumping ObjectSpace extender for MRI. + + $Author$ + created at: Sat Oct 11 10:11:00 2013 + + NOTE: This extension library is not expected to exist except C Ruby. + + All the files in this distribution are covered under the Ruby's + license (see the file COPYING). + +**********************************************************************/ + +#include "gc.h" +#include "internal.h" +#include "internal/hash.h" +#include "internal/string.h" +#include "internal/sanitizers.h" +#include "node.h" +#include "objspace.h" +#include "ruby/debug.h" +#include "ruby/util.h" +#include "ruby/io.h" +#include "vm_core.h" + +RUBY_EXTERN const char ruby_hexdigits[]; + +#define BUFFER_CAPACITY 4096 + +struct dump_config { + VALUE type; + VALUE stream; + VALUE string; + const char *root_category; + VALUE cur_obj; + VALUE cur_obj_klass; + size_t cur_obj_references; + unsigned int roots: 1; + unsigned int full_heap: 1; + unsigned int partial_dump; + size_t since; + unsigned long buffer_len; + char buffer[BUFFER_CAPACITY]; +}; + +static void +dump_flush(struct dump_config *dc) +{ + if (dc->buffer_len) { + if (dc->stream) { + size_t written = rb_io_bufwrite(dc->stream, dc->buffer, dc->buffer_len); + if (written < dc->buffer_len) { + MEMMOVE(dc->buffer, dc->buffer + written, char, dc->buffer_len - written); + dc->buffer_len -= written; + return; + } + } + else if (dc->string) { + rb_str_cat(dc->string, dc->buffer, dc->buffer_len); + } + dc->buffer_len = 0; + } +} + +static inline void +buffer_ensure_capa(struct dump_config *dc, unsigned long requested) +{ + RUBY_ASSERT(requested <= BUFFER_CAPACITY); + if (requested + dc->buffer_len >= BUFFER_CAPACITY) { + dump_flush(dc); + if (requested + dc->buffer_len >= BUFFER_CAPACITY) { + rb_raise(rb_eIOError, "full buffer"); + } + } +} + +static void buffer_append(struct dump_config *dc, const char *cstr, unsigned long len) +{ + if (LIKELY(len > 0)) { + buffer_ensure_capa(dc, len); + MEMCPY(dc->buffer + dc->buffer_len, cstr, char, len); + dc->buffer_len += len; + } +} + +# define dump_append(dc, str) buffer_append(dc, (str), (long)strlen(str)) + +static void +dump_append_ld(struct dump_config *dc, const long number) +{ + 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); + dc->buffer_len += required; +} + +static void +dump_append_lu(struct dump_config *dc, const unsigned long number) +{ + 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); + dc->buffer_len += required; +} + +static void +dump_append_g(struct dump_config *dc, const double number) +{ + unsigned long capa_left = BUFFER_CAPACITY - dc->buffer_len; + unsigned long required = snprintf(dc->buffer + dc->buffer_len, capa_left, "%#g", number); + + if (required >= capa_left) { + buffer_ensure_capa(dc, required); + capa_left = BUFFER_CAPACITY - dc->buffer_len; + snprintf(dc->buffer + dc->buffer_len, capa_left, "%#g", number); + } + dc->buffer_len += required; +} + +static void +dump_append_d(struct dump_config *dc, const int number) +{ + 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); + dc->buffer_len += required; +} + +static void +dump_append_sizet(struct dump_config *dc, const size_t number) +{ + 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); + dc->buffer_len += required; +} + +static void +dump_append_c(struct dump_config *dc, unsigned char c) +{ + if (c <= 0x1f) { + const unsigned int width = (sizeof(c) * CHAR_BIT / 4) + 5; + buffer_ensure_capa(dc, width); + unsigned long required = snprintf(dc->buffer + dc->buffer_len, width, "\\u00%02x", c); + RUBY_ASSERT(required <= width); + dc->buffer_len += required; + } + else { + buffer_ensure_capa(dc, 1); + dc->buffer[dc->buffer_len] = c; + dc->buffer_len++; + } +} + +static void +dump_append_ref(struct dump_config *dc, VALUE ref) +{ + RUBY_ASSERT(ref > 0); + + char buffer[((sizeof(VALUE) * CHAR_BIT + 3) / 4) + 4]; + char *buffer_start, *buffer_end; + + buffer_start = buffer_end = &buffer[sizeof(buffer)]; + *--buffer_start = '"'; + while (ref) { + *--buffer_start = ruby_hexdigits[ref & 0xF]; + ref >>= 4; + } + *--buffer_start = 'x'; + *--buffer_start = '0'; + *--buffer_start = '"'; + buffer_append(dc, buffer_start, buffer_end - buffer_start); +} + +static void +dump_append_string_value(struct dump_config *dc, VALUE obj) +{ + long i; + char c; + const char *value; + + dump_append(dc, "\""); + for (i = 0, value = RSTRING_PTR(obj); i < RSTRING_LEN(obj); i++) { + switch ((c = value[i])) { + case '\\': + dump_append(dc, "\\\\"); + break; + case '"': + dump_append(dc, "\\\""); + break; + case '\0': + dump_append(dc, "\\u0000"); + break; + case '\b': + dump_append(dc, "\\b"); + break; + case '\t': + dump_append(dc, "\\t"); + break; + case '\f': + dump_append(dc, "\\f"); + break; + case '\n': + dump_append(dc, "\\n"); + break; + case '\r': + dump_append(dc, "\\r"); + break; + case '\177': + dump_append(dc, "\\u007f"); + break; + default: + dump_append_c(dc, c); + } + } + dump_append(dc, "\""); +} + +static void +dump_append_symbol_value(struct dump_config *dc, VALUE obj) +{ + dump_append(dc, "{\"type\":\"SYMBOL\", \"value\":"); + dump_append_string_value(dc, rb_sym2str(obj)); + dump_append(dc, "}"); +} + +static inline const char * +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); +#undef CASE_TYPE + default: break; + } + return "UNKNOWN"; +} + +static void +dump_append_special_const(struct dump_config *dc, VALUE value) +{ + if (value == Qtrue) { + dump_append(dc, "true"); + } + else if (value == Qfalse) { + dump_append(dc, "false"); + } + else if (value == Qnil) { + dump_append(dc, "null"); + } + else if (FIXNUM_P(value)) { + dump_append_ld(dc, FIX2LONG(value)); + } + else if (FLONUM_P(value)) { + dump_append_g(dc, RFLOAT_VALUE(value)); + } + else if (SYMBOL_P(value)) { + dump_append_symbol_value(dc, value); + } + else { + dump_append(dc, "{}"); + } +} + +static void +reachable_object_i(VALUE ref, void *data) +{ + struct dump_config *dc = (struct dump_config *)data; + + if (dc->cur_obj_klass == ref) + return; + + if (dc->cur_obj_references == 0) { + dump_append(dc, ", \"references\":["); + dump_append_ref(dc, ref); + } + else { + dump_append(dc, ", "); + dump_append_ref(dc, ref); + } + + dc->cur_obj_references++; +} + +static void +dump_append_string_content(struct dump_config *dc, VALUE obj) +{ + dump_append(dc, ", \"bytesize\":"); + dump_append_ld(dc, RSTRING_LEN(obj)); + if (!STR_EMBED_P(obj) && !STR_SHARED_P(obj) && (long)rb_str_capacity(obj) != RSTRING_LEN(obj)) { + dump_append(dc, ", \"capacity\":"); + dump_append_sizet(dc, rb_str_capacity(obj)); + } + + if (is_ascii_string(obj)) { + dump_append(dc, ", \"value\":"); + dump_append_string_value(dc, obj); + } +} + +static void +dump_object(VALUE obj, struct dump_config *dc) +{ + size_t memsize; + struct allocation_info *ainfo = objspace_lookup_allocation_info(obj); + rb_io_t *fptr; + ID flags[RB_OBJ_GC_FLAGS_MAX]; + size_t n, i; + + if (SPECIAL_CONST_P(obj)) { + dump_append_special_const(dc, obj); + return; + } + + dc->cur_obj = obj; + dc->cur_obj_references = 0; + dc->cur_obj_klass = BUILTIN_TYPE(obj) == T_NODE ? 0 : RBASIC_CLASS(obj); + + if (dc->partial_dump && (!ainfo || ainfo->generation < dc->since)) { + return; + } + + if (dc->cur_obj == dc->string) + return; + + dump_append(dc, "{\"address\":"); + dump_append_ref(dc, obj); + + dump_append(dc, ", \"type\":\""); + dump_append(dc, obj_type(obj)); + dump_append(dc, "\""); + + if (dc->cur_obj_klass) { + dump_append(dc, ", \"class\":"); + dump_append_ref(dc, dc->cur_obj_klass); + } + if (rb_obj_frozen_p(obj)) + dump_append(dc, ", \"frozen\":true"); + + switch (BUILTIN_TYPE(obj)) { + case T_NONE: + dump_append(dc, "}\n"); + return; + + case T_IMEMO: + dump_append(dc, ", \"imemo_type\":\""); + dump_append(dc, rb_imemo_name(imemo_type(obj))); + dump_append(dc, "\""); + break; + + case T_SYMBOL: + dump_append_string_content(dc, rb_sym2str(obj)); + break; + + 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 (STR_SHARED_P(obj)) + dump_append(dc, ", \"shared\":true"); + else + dump_append_string_content(dc, obj); + + if (!ENCODING_IS_ASCII8BIT(obj)) { + dump_append(dc, ", \"encoding\":\""); + dump_append(dc, rb_enc_name(rb_enc_from_index(ENCODING_GET(obj)))); + dump_append(dc, "\""); + } + break; + + case T_HASH: + dump_append(dc, ", \"size\":"); + dump_append_sizet(dc, (size_t)RHASH_SIZE(obj)); + if (FL_TEST(obj, RHASH_PROC_DEFAULT)) { + dump_append(dc, ", \"default\":"); + dump_append_ref(dc, RHASH_IFNONE(obj)); + } + break; + + case T_ARRAY: + dump_append(dc, ", \"length\":"); + dump_append_ld(dc, RARRAY_LEN(obj)); + if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, ELTS_SHARED)) + dump_append(dc, ", \"shared\":true"); + if (RARRAY_LEN(obj) > 0 && FL_TEST(obj, RARRAY_EMBED_FLAG)) + dump_append(dc, ", \"embedded\":true"); + break; + + case T_ICLASS: + if (rb_class_get_superclass(obj)) { + dump_append(dc, ", \"superclass\":"); + dump_append_ref(dc, rb_class_get_superclass(obj)); + } + break; + + case T_CLASS: + case T_MODULE: + if (rb_class_get_superclass(obj)) { + dump_append(dc, ", \"superclass\":"); + dump_append_ref(dc, rb_class_get_superclass(obj)); + } + + if (dc->cur_obj_klass) { + VALUE mod_name = rb_mod_name(obj); + if (!NIL_P(mod_name)) { + dump_append(dc, ", \"name\":\""); + dump_append(dc, RSTRING_PTR(mod_name)); + dump_append(dc, "\""); + } else { + VALUE real_mod_name = rb_mod_name(rb_class_real(obj)); + if (RTEST(real_mod_name)) { + dump_append(dc, ", \"real_class_name\":\""); + dump_append(dc, RSTRING_PTR(real_mod_name)); + dump_append(dc, "\""); + } + } + + if (FL_TEST(obj, FL_SINGLETON)) { + dump_append(dc, ", \"singleton\":true"); + } + } + break; + + case T_DATA: + if (RTYPEDDATA_P(obj)) { + dump_append(dc, ", \"struct\":\""); + dump_append(dc, RTYPEDDATA_TYPE(obj)->wrap_struct_name); + dump_append(dc, "\""); + } + break; + + case T_FLOAT: + dump_append(dc, ", \"value\":\""); + dump_append_g(dc, RFLOAT_VALUE(obj)); + dump_append(dc, "\""); + break; + + case T_OBJECT: + dump_append(dc, ", \"ivars\":"); + dump_append_lu(dc, ROBJECT_NUMIV(obj)); + break; + + case T_FILE: + fptr = RFILE(obj)->fptr; + if (fptr) { + dump_append(dc, ", \"fd\":"); + dump_append_d(dc, fptr->fd); + } + break; + + case T_ZOMBIE: + dump_append(dc, "}\n"); + return; + + default: + break; + } + + rb_objspace_reachable_objects_from(obj, reachable_object_i, dc); + if (dc->cur_obj_references > 0) + dump_append(dc, "]"); + + if (ainfo) { + if (ainfo->path) { + dump_append(dc, ", \"file\":\""); + dump_append(dc, ainfo->path); + dump_append(dc, "\""); + } + if (ainfo->line) { + dump_append(dc, ", \"line\":"); + dump_append_lu(dc, ainfo->line); + } + if (RTEST(ainfo->mid)) { + VALUE m = rb_sym2str(ainfo->mid); + dump_append(dc, ", \"method\":"); + dump_append_string_value(dc, m); + } + dump_append(dc, ", \"generation\":"); + dump_append_sizet(dc, ainfo->generation); + } + + if ((memsize = rb_obj_memsize_of(obj)) > 0) { + dump_append(dc, ", \"memsize\":"); + dump_append_sizet(dc, memsize); + } + + if ((n = rb_obj_gc_flags(obj, flags, sizeof(flags))) > 0) { + dump_append(dc, ", \"flags\":{"); + for (i=0; i<n; i++) { + dump_append(dc, "\""); + dump_append(dc, rb_id2name(flags[i])); + dump_append(dc, "\":true"); + if (i != n-1) dump_append(dc, ", "); + } + dump_append(dc, "}"); + } + + dump_append(dc, "}\n"); +} + +static int +heap_i(void *vstart, void *vend, size_t stride, void *data) +{ + struct dump_config *dc = (struct dump_config *)data; + VALUE v = (VALUE)vstart; + for (; v != (VALUE)vend; v += stride) { + void *ptr = asan_poisoned_object_p(v); + asan_unpoison_object(v, false); + + if (dc->full_heap || RBASIC(v)->flags) + dump_object(v, dc); + + if (ptr) { + asan_poison_object(v); + } + } + return 0; +} + +static void +root_obj_i(const char *category, VALUE obj, void *data) +{ + struct dump_config *dc = (struct dump_config *)data; + + if (dc->root_category != NULL && category != dc->root_category) + dump_append(dc, "]}\n"); + if (dc->root_category == NULL || category != dc->root_category) { + dump_append(dc, "{\"type\":\"ROOT\", \"root\":\""); + dump_append(dc, category); + dump_append(dc, "\", \"references\":["); + dump_append_ref(dc, obj); + } + else { + dump_append(dc, ", "); + dump_append_ref(dc, obj); + } + + dc->root_category = category; + dc->roots = 1; +} + +static void +dump_output(struct dump_config *dc, VALUE output, VALUE full, VALUE since) +{ + + dc->full_heap = 0; + dc->buffer_len = 0; + + if (TYPE(output) == T_STRING) { + dc->stream = Qfalse; + dc->string = output; + } else { + dc->stream = output; + dc->string = Qfalse; + } + + if (full == Qtrue) { + dc->full_heap = 1; + } + + if (RTEST(since)) { + dc->partial_dump = 1; + dc->since = NUM2SIZET(since); + } else { + dc->partial_dump = 0; + } +} + +static VALUE +dump_result(struct dump_config *dc) +{ + dump_flush(dc); + + if (dc->string) { + return dc->string; + } else { + rb_io_flush(dc->stream); + return dc->stream; + } +} + +static VALUE +objspace_dump(VALUE os, VALUE obj, VALUE output) +{ + struct dump_config dc = {0,}; + dump_output(&dc, output, Qnil, Qnil); + + dump_object(obj, &dc); + + return dump_result(&dc); +} + +static VALUE +objspace_dump_all(VALUE os, VALUE output, VALUE full, VALUE since) +{ + struct dump_config dc = {0,}; + dump_output(&dc, output, full, since); + + if (!dc.partial_dump || dc.since == 0) { + /* dump roots */ + rb_objspace_reachable_objects_from_root(root_obj_i, &dc); + if (dc.roots) dump_append(&dc, "]}\n"); + } + + /* dump all objects */ + rb_objspace_each_objects(heap_i, &dc); + + return dump_result(&dc); +} + +void +Init_objspace_dump(VALUE rb_mObjSpace) +{ +#undef rb_intern +#if 0 + rb_mObjSpace = rb_define_module("ObjectSpace"); /* let rdoc know */ +#endif + + rb_define_module_function(rb_mObjSpace, "_dump", objspace_dump, 2); + rb_define_module_function(rb_mObjSpace, "_dump_all", objspace_dump_all, 3); + + /* force create static IDs */ + rb_obj_gc_flags(rb_mObjSpace, 0, 0); +} |
