ffi / ffi

Ruby FFI
https://github.com/ffi/ffi/wiki
BSD 3-Clause "New" or "Revised" License
2.08k stars 333 forks source link

Garbage collection with Struct containing inline Struct #520

Open lionelperrin opened 8 years ago

lionelperrin commented 8 years ago

This issue is a sibling of #517.

Consider the following C struct

struct A {
  char* value;
};
struct TestStruct {
  int a;
  struct A substruct;
};

FFI looks like

class A < FFI::Struct
  layout :value, :pointer
end
class TestStruct < FFI::Struct
  layout :a, :int,
    :substruct, A
end

It appears that the operator [] for FFI::Struct returns a new instance of struct when accessing a substructure.

def allocate
  t = TestStruct.new
  a = t[:substruct] # instance of A created on the fly
  a[:value] = MemoryBuffer.from_string('hello') # pointer will be kept by the `a` struct 
  # unfortunately, t[:substruct][:value] will be valid only if a reference to `a` is kept
  t
end

t = allocate
GC.start # cause underlying temporary substruct to be lost
t[:substruct] # created on the fly at the right memory location
t[:substruct][:value] # a pointer to memory location which has been freed.
lionelperrin commented 8 years ago

Here follows a possible workaround:

module FFI
 class Struct
    alias :orig_array_aset :[]=
    alias :orig_array_aref :[]
    def sub_structs
      @sub_structs ||= {}
    end
    def []=(index, value)
      sub_structs[index] = value if value.is_a? Struct
      orig_array_aset(index, value)
    end
    def [](index)
      v = orig_array_aref(index)
      if v.is_a? Struct
        vv = sub_structs[index]
        # always return the same instance of the structure
        # if the underlying pointer hasn't changed
        return vv if vv && vv.to_ptr == v.to_ptr
        # update sub_structs if the pointer has changed
        sub_structs[index] = v
      end
      v
    end
  end
end