Skip to content
Snippets Groups Projects
Verified Commit 52f3388b authored by Yorick Peterse's avatar Yorick Peterse
Browse files

Fixed getting classes from Tracepoint objects

Apparently when using RBASIC_CLASS() sometimes Ruby returns something
that _looks_ like a VALUE but isn't. This results in Ruby sometimes
treating it as a Fixnum of sorts while in other cases it just segfaults.

To work around this we're now using rb_tracearg_defined_class() to get
an object class, which seems to be the correct way of doing it (based on
digging through the MRI source code).

One small nuisance is that we now also get singleton classes which we
generally don't care about (since there's always only one of them). This
means we'll need to filter these out whenever we generate a Ruby Hash
containing the counts per class. Of course MRI doesn't provide a sane
API for this (that doesn't involve calling Ruby methods) so we instead
use the same logic as MRI's implementation of Class#singleton_class?
uses.
parent 8bdda6cb
No related branches found
No related tags found
No related merge requests found
Pipeline #
Loading
Loading
@@ -6,6 +6,9 @@ VALUE state_const;
 
ID id_enabled;
 
#define IS_SINGLETON(klass) \
RB_TYPE_P(klass, T_CLASS) && FL_TEST(klass, FL_SINGLETON)
/**
* Called whenever a new Ruby object is allocated.
*/
Loading
Loading
@@ -16,14 +19,19 @@ void newobj_callback(VALUE tracepoint, void* data) {
 
AllocationState *state = allocation_state_get_struct(state_const);
 
VALUE obj = rb_tracearg_object(trace_arg);
VALUE klass = RBASIC_CLASS(obj);
VALUE klass = rb_tracearg_defined_class(trace_arg);
 
/* These aren't actually allocated so there's no point in tracking them. */
if ( klass == Qtrue || klass == Qfalse || klass == Qnil ) {
return;
}
 
// We don't care about sigleton classes since only one of them exists at a
// time. The logic here is stolen from MRI's implementation of
// Class#singleton_class? as MRI sadly provides no public C function for
// this method.
if ( IS_SINGLETON(klass) ) return;
st_lookup(state->object_counts, (st_data_t) klass, &count);
st_insert(state->object_counts, (st_data_t) klass, count + 1);
}
Loading
Loading
@@ -40,8 +48,9 @@ void freeobj_callback(VALUE tracepoint, void* data) {
rb_trace_arg_t *trace_arg = rb_tracearg_from_tracepoint(tracepoint);
AllocationState *state = allocation_state_get_struct(state_const);
 
VALUE obj = rb_tracearg_object(trace_arg);
VALUE klass = RBASIC_CLASS(obj);
VALUE klass = rb_tracearg_defined_class(trace_arg);
if ( IS_SINGLETON(klass) ) return;
 
if ( st_lookup(state->object_counts, (st_data_t) klass, &count) ) {
if ( count > 0 && (count - 1) > 0 ) {
Loading
Loading
@@ -60,9 +69,7 @@ void freeobj_callback(VALUE tracepoint, void* data) {
static int each_count(st_data_t key, st_data_t value, st_data_t hash_ptr) {
VALUE vkey = (VALUE) key;
 
if ( rb_obj_is_kind_of(key, rb_cClass) == Qtrue ) {
rb_hash_aset((VALUE) hash_ptr, vkey, INT2NUM(value));
}
rb_hash_aset((VALUE) hash_ptr, vkey, INT2NUM(value));
 
return ST_CONTINUE;
}
Loading
Loading
@@ -73,6 +80,8 @@ static int each_count(st_data_t key, st_data_t value, st_data_t hash_ptr) {
* The returned Hash contains its own copy of the statistics, any further object
* allocations/frees will not modify said Hash.
*
* This method ignores singleton classes.
*
* call-seq:
* Allocations.to_hash -> Hash
*/
Loading
Loading
Loading
Loading
@@ -4,7 +4,7 @@
#include "liballocations.h"
 
typedef struct {
st_table *object_counts
st_table *object_counts;
} AllocationState;
 
extern void allocation_state_reset_counts(AllocationState*);
Loading
Loading
Loading
Loading
@@ -42,14 +42,23 @@ describe Allocations do
end
 
describe '.to_hash' do
it 'returns a Hash containing object counts' do
before do
described_class.start
end
 
foo = 'foo'
it 'returns a Hash containing object counts' do
hash = described_class.to_hash
 
expect(hash).to be_an_instance_of(Hash)
expect(hash[String] >= 1).to eq(true)
expect(hash.keys).not_to be_empty
expect(hash.values[0] > 0).to eq(true)
end
it 'does not include singleton classes in the returned Hash' do
klass = Class.new
hash = described_class.to_hash
expect(hash.keys).not_to include(klass.singleton_class)
end
end
end
  • James Lopez @jameslopez ·
    Developer

    awesome stuff and explanation @yorickpeterse !

    If I get it correctly, we don't need the rb_obj_is_kind_of(key, rb_cClass) == Qtrue check as we are now using rb_tracearg_defined_class() that always gets the right object?

0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment