Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 23 additions & 3 deletions ext/fiddle/pointer.c
Original file line number Diff line number Diff line change
Expand Up @@ -193,14 +193,34 @@ rb_fiddle_ptr_initialize(int argc, VALUE argv[], VALUE self)

/*
* call-seq:
*
* Fiddle::Pointer.malloc(size, freefunc = nil) => fiddle pointer instance
*
* == Examples
*
* # Relying on the garbage collector - may lead to unlimited memory allocated before freeing any, but safe
* pointer = Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE)
* ...
*
* # Manual freeing
* pointer = Fiddle::Pointer.malloc(size)
* begin
* ...
* ensure
* Fiddle.free pointer
* end
*
* # No free function and no call to free - the native memory will leak if the pointer is garbage collected
* pointer = Fiddle::Pointer.malloc(size)
* ...
*
* Allocate +size+ bytes of memory and associate it with an optional
* +freefunc+ that will be called when the pointer is garbage collected.
*
* +freefunc+ must be an address pointing to a function or an instance of
* Fiddle::Function
* +Fiddle::Function+. Using +freefunc+ may lead to unlimited memory being
* allocated before any is freed as the native memory the pointer references
* does not contribute to triggering the Ruby garbage collector. Consider
* manually freeing the memory as illustrated above. You cannot combine
* the techniques as this may lead to a double-free.
*/
static VALUE
rb_fiddle_ptr_s_malloc(int argc, VALUE argv[], VALUE klass)
Expand Down
11 changes: 8 additions & 3 deletions lib/fiddle/struct.rb
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,12 @@ module CStructBuilder
#
# MyStruct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members)
#
# obj = MyStruct.allocate
# obj = MyStruct.malloc
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think allocate was ever correct?

# begin
# ...
# ensure
# Fiddle.free obj.to_ptr
# end
#
def create(klass, types, members)
new_class = Class.new(klass){
Expand Down Expand Up @@ -112,7 +117,7 @@ class CStructEntity < Fiddle::Pointer

# Allocates a C struct with the +types+ provided.
#
# When the instance is garbage collected, the C function +func+ is called.
# See Fiddle::Pointer.malloc for memory management issues.
def CStructEntity.malloc(types, func = nil)
addr = Fiddle.malloc(CStructEntity.size(types))
CStructEntity.new(addr, types, func)
Expand Down Expand Up @@ -267,7 +272,7 @@ class CUnionEntity < CStructEntity

# Allocates a C union the +types+ provided.
#
# When the instance is garbage collected, the C function +func+ is called.
# See Fiddle::Pointer.malloc for memory management issues.
def CUnionEntity.malloc(types, func=nil)
addr = Fiddle.malloc(CUnionEntity.size(types))
CUnionEntity.new(addr, types, func)
Expand Down
12 changes: 6 additions & 6 deletions test/fiddle/test_c_struct_entry.rb
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ def test_class_size_with_count
end

def test_set_ctypes
union = CStructEntity.malloc [TYPE_INT, TYPE_LONG]
union = CStructEntity.malloc [TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE
union.assign_names %w[int long]

# this test is roundabout because the stored ctypes are not accessible
Expand All @@ -55,20 +55,20 @@ def test_set_ctypes
end

def test_aref_pointer_array
team = CStructEntity.malloc([[TYPE_VOIDP, 2]])
team = CStructEntity.malloc([[TYPE_VOIDP, 2]], Fiddle::RUBY_FREE)
team.assign_names(["names"])
alice = Fiddle::Pointer.malloc(6)
alice = Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE)
alice[0, 6] = "Alice\0"
bob = Fiddle::Pointer.malloc(4)
bob = Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE)
bob[0, 4] = "Bob\0"
team["names"] = [alice, bob]
assert_equal(["Alice", "Bob"], team["names"].map(&:to_s))
end

def test_aref_pointer
user = CStructEntity.malloc([TYPE_VOIDP])
user = CStructEntity.malloc([TYPE_VOIDP], Fiddle::RUBY_FREE)
user.assign_names(["name"])
alice = Fiddle::Pointer.malloc(6)
alice = Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE)
alice[0, 6] = "Alice\0"
user["name"] = alice
assert_equal("Alice", user["name"].to_s)
Expand Down
2 changes: 1 addition & 1 deletion test/fiddle/test_c_union_entity.rb
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ def test_class_size_with_count
end

def test_set_ctypes
union = CUnionEntity.malloc [TYPE_INT, TYPE_LONG]
union = CUnionEntity.malloc [TYPE_INT, TYPE_LONG], Fiddle::RUBY_FREE
union.assign_names %w[int long]

# this test is roundabout because the stored ctypes are not accessible
Expand Down
103 changes: 70 additions & 33 deletions test/fiddle/test_import.rb
Original file line number Diff line number Diff line change
Expand Up @@ -57,35 +57,56 @@ def test_ensure_call_dlload
def test_struct_memory_access()
# check memory operations performed directly on struct
my_struct = Fiddle::Importer.struct(['int id']).malloc
my_struct[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT
assert_equal 0x01010101, my_struct.id

my_struct.id = 0
assert_equal "\x00".b * Fiddle::SIZEOF_INT, my_struct[0, Fiddle::SIZEOF_INT]
begin
my_struct[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT
assert_equal 0x01010101, my_struct.id

my_struct.id = 0
assert_equal "\x00".b * Fiddle::SIZEOF_INT, my_struct[0, Fiddle::SIZEOF_INT]
ensure
Fiddle.free my_struct.to_ptr
end
end

def test_struct_ptr_array_subscript_multiarg()
# check memory operations performed on struct#to_ptr
struct = Fiddle::Importer.struct([ 'int x' ]).malloc
ptr = struct.to_ptr
begin
ptr = struct.to_ptr

struct.x = 0x02020202
assert_equal("\x02".b * Fiddle::SIZEOF_INT, ptr[0, Fiddle::SIZEOF_INT])
struct.x = 0x02020202
assert_equal("\x02".b * Fiddle::SIZEOF_INT, ptr[0, Fiddle::SIZEOF_INT])

ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT
assert_equal 0x01010101, struct.x
ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT
assert_equal 0x01010101, struct.x
ensure
Fiddle.free struct.to_ptr
end
end

def test_malloc()
s1 = LIBC::Timeval.malloc()
s2 = LIBC::Timeval.malloc()
refute_equal(s1.to_ptr.to_i, s2.to_ptr.to_i)
begin
s2 = LIBC::Timeval.malloc()
begin
refute_equal(s1.to_ptr.to_i, s2.to_ptr.to_i)
ensure
Fiddle.free s2.to_ptr
end
ensure
Fiddle.free s1.to_ptr
end
end

def test_sizeof()
assert_equal(SIZEOF_VOIDP, LIBC.sizeof("FILE*"))
assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct))
assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(LIBC::MyStruct.malloc()))
my_struct = LIBC::MyStruct.malloc()
begin
assert_equal(LIBC::MyStruct.size(), LIBC.sizeof(my_struct))
ensure
Fiddle.free my_struct.to_ptr
end
assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG)
end

Expand Down Expand Up @@ -131,35 +152,51 @@ def test_value()

def test_struct_array_assignment()
instance = Fiddle::Importer.struct(["unsigned int stages[3]"]).malloc
instance.stages[0] = 1024
instance.stages[1] = 10
instance.stages[2] = 100
assert_equal 1024, instance.stages[0]
assert_equal 10, instance.stages[1]
assert_equal 100, instance.stages[2]
assert_equal [1024, 10, 100].pack(Fiddle::PackInfo::PACK_MAP[-Fiddle::TYPE_INT] * 3),
instance.to_ptr[0, 3 * Fiddle::SIZEOF_INT]
assert_raise(IndexError) { instance.stages[-1] = 5 }
assert_raise(IndexError) { instance.stages[3] = 5 }
begin
instance.stages[0] = 1024
instance.stages[1] = 10
instance.stages[2] = 100
assert_equal 1024, instance.stages[0]
assert_equal 10, instance.stages[1]
assert_equal 100, instance.stages[2]
assert_equal [1024, 10, 100].pack(Fiddle::PackInfo::PACK_MAP[-Fiddle::TYPE_INT] * 3),
instance.to_ptr[0, 3 * Fiddle::SIZEOF_INT]
assert_raise(IndexError) { instance.stages[-1] = 5 }
assert_raise(IndexError) { instance.stages[3] = 5 }
ensure
Fiddle.free instance.to_ptr
end
end

def test_struct()
s = LIBC::MyStruct.malloc()
s.num = [0,1,2,3,4]
s.c = ?a.ord
s.buff = "012345\377"
assert_equal([0,1,2,3,4], s.num)
assert_equal(?a.ord, s.c)
assert_equal([?0.ord,?1.ord,?2.ord,?3.ord,?4.ord,?5.ord,?\377.ord], s.buff)
begin
s.num = [0,1,2,3,4]
s.c = ?a.ord
s.buff = "012345\377"
assert_equal([0,1,2,3,4], s.num)
assert_equal(?a.ord, s.c)
assert_equal([?0.ord,?1.ord,?2.ord,?3.ord,?4.ord,?5.ord,?\377.ord], s.buff)
ensure
Fiddle.free s.to_ptr
end
end

def test_gettimeofday()
if( defined?(LIBC.gettimeofday) )
timeval = LIBC::Timeval.malloc()
timezone = LIBC::Timezone.malloc()
LIBC.gettimeofday(timeval, timezone)
cur = Time.now()
assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i)
begin
timezone = LIBC::Timezone.malloc()
begin
LIBC.gettimeofday(timeval, timezone)
ensure
Fiddle.free timezone.to_ptr
end
cur = Time.now()
assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i)
ensure
Fiddle.free timeval.to_ptr
end
end
end

Expand Down
24 changes: 17 additions & 7 deletions test/fiddle/test_pointer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def test_to_ptr_string
end

def test_to_ptr_io
buf = Pointer.malloc(10)
buf = Pointer.malloc(10, Fiddle::RUBY_FREE)
File.open(__FILE__, 'r') do |f|
ptr = Pointer.to_ptr f
fread = Function.new(@libc['fread'],
Expand Down Expand Up @@ -145,7 +145,11 @@ def test_to_value

def test_free
ptr = Pointer.malloc(4)
assert_nil ptr.free
begin
assert_nil ptr.free
ensure
Fiddle.free ptr
end
end

def test_free=
Expand Down Expand Up @@ -173,15 +177,21 @@ def test_null?

def test_size
ptr = Pointer.malloc(4)
assert_equal 4, ptr.size
Fiddle.free ptr.to_i
begin
assert_equal 4, ptr.size
ensure
Fiddle.free ptr
end
end

def test_size=
ptr = Pointer.malloc(4)
ptr.size = 10
assert_equal 10, ptr.size
Fiddle.free ptr.to_i
begin
ptr.size = 10
assert_equal 10, ptr.size
ensure
Fiddle.free ptr
end
end

def test_aref_aset
Expand Down