[ruby/fiddle] Improve documentation on how to correctly free memory and free memory in tests (#33)

https://github.com/ruby/fiddle/commit/e59cfd708a
This commit is contained in:
Chris Seaton 2020-05-19 00:12:47 +01:00 committed by Nobuyoshi Nakada
parent 24b615e82e
commit 3015a7aae7
6 changed files with 124 additions and 52 deletions

View File

@ -73,7 +73,12 @@ module Fiddle
# #
# MyStruct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members) # MyStruct = Fiddle::CStructBuilder.create(Fiddle::CUnion, types, members)
# #
# obj = MyStruct.allocate # obj = MyStruct.malloc
# begin
# ...
# ensure
# Fiddle.free obj.to_ptr
# end
# #
def create(klass, types, members) def create(klass, types, members)
new_class = Class.new(klass){ new_class = Class.new(klass){
@ -112,7 +117,7 @@ module Fiddle
# Allocates a C struct with the +types+ provided. # 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) def CStructEntity.malloc(types, func = nil)
addr = Fiddle.malloc(CStructEntity.size(types)) addr = Fiddle.malloc(CStructEntity.size(types))
CStructEntity.new(addr, types, func) CStructEntity.new(addr, types, func)
@ -267,7 +272,7 @@ module Fiddle
# Allocates a C union the +types+ provided. # 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) def CUnionEntity.malloc(types, func=nil)
addr = Fiddle.malloc(CUnionEntity.size(types)) addr = Fiddle.malloc(CUnionEntity.size(types))
CUnionEntity.new(addr, types, func) CUnionEntity.new(addr, types, func)

View File

@ -193,14 +193,34 @@ rb_fiddle_ptr_initialize(int argc, VALUE argv[], VALUE self)
/* /*
* call-seq: * call-seq:
*
* Fiddle::Pointer.malloc(size, freefunc = nil) => fiddle pointer instance * 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 * Allocate +size+ bytes of memory and associate it with an optional
* +freefunc+ that will be called when the pointer is garbage collected. * +freefunc+ that will be called when the pointer is garbage collected.
*
* +freefunc+ must be an address pointing to a function or an instance of * +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 static VALUE
rb_fiddle_ptr_s_malloc(int argc, VALUE argv[], VALUE klass) rb_fiddle_ptr_s_malloc(int argc, VALUE argv[], VALUE klass)

View File

@ -43,7 +43,7 @@ module Fiddle
end end
def test_set_ctypes 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] union.assign_names %w[int long]
# this test is roundabout because the stored ctypes are not accessible # this test is roundabout because the stored ctypes are not accessible
@ -55,20 +55,20 @@ module Fiddle
end end
def test_aref_pointer_array def test_aref_pointer_array
team = CStructEntity.malloc([[TYPE_VOIDP, 2]]) team = CStructEntity.malloc([[TYPE_VOIDP, 2]], Fiddle::RUBY_FREE)
team.assign_names(["names"]) team.assign_names(["names"])
alice = Fiddle::Pointer.malloc(6) alice = Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE)
alice[0, 6] = "Alice\0" alice[0, 6] = "Alice\0"
bob = Fiddle::Pointer.malloc(4) bob = Fiddle::Pointer.malloc(4, Fiddle::RUBY_FREE)
bob[0, 4] = "Bob\0" bob[0, 4] = "Bob\0"
team["names"] = [alice, bob] team["names"] = [alice, bob]
assert_equal(["Alice", "Bob"], team["names"].map(&:to_s)) assert_equal(["Alice", "Bob"], team["names"].map(&:to_s))
end end
def test_aref_pointer def test_aref_pointer
user = CStructEntity.malloc([TYPE_VOIDP]) user = CStructEntity.malloc([TYPE_VOIDP], Fiddle::RUBY_FREE)
user.assign_names(["name"]) user.assign_names(["name"])
alice = Fiddle::Pointer.malloc(6) alice = Fiddle::Pointer.malloc(6, Fiddle::RUBY_FREE)
alice[0, 6] = "Alice\0" alice[0, 6] = "Alice\0"
user["name"] = alice user["name"] = alice
assert_equal("Alice", user["name"].to_s) assert_equal("Alice", user["name"].to_s)

View File

@ -21,7 +21,7 @@ module Fiddle
end end
def test_set_ctypes 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] union.assign_names %w[int long]
# this test is roundabout because the stored ctypes are not accessible # this test is roundabout because the stored ctypes are not accessible

View File

@ -57,16 +57,21 @@ module Fiddle
def test_struct_memory_access() def test_struct_memory_access()
# check memory operations performed directly on struct # check memory operations performed directly on struct
my_struct = Fiddle::Importer.struct(['int id']).malloc my_struct = Fiddle::Importer.struct(['int id']).malloc
begin
my_struct[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT my_struct[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT
assert_equal 0x01010101, my_struct.id assert_equal 0x01010101, my_struct.id
my_struct.id = 0 my_struct.id = 0
assert_equal "\x00".b * Fiddle::SIZEOF_INT, my_struct[0, Fiddle::SIZEOF_INT] assert_equal "\x00".b * Fiddle::SIZEOF_INT, my_struct[0, Fiddle::SIZEOF_INT]
ensure
Fiddle.free my_struct.to_ptr
end
end end
def test_struct_ptr_array_subscript_multiarg() def test_struct_ptr_array_subscript_multiarg()
# check memory operations performed on struct#to_ptr # check memory operations performed on struct#to_ptr
struct = Fiddle::Importer.struct([ 'int x' ]).malloc struct = Fiddle::Importer.struct([ 'int x' ]).malloc
begin
ptr = struct.to_ptr ptr = struct.to_ptr
struct.x = 0x02020202 struct.x = 0x02020202
@ -74,18 +79,34 @@ module Fiddle
ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT ptr[0, Fiddle::SIZEOF_INT] = "\x01".b * Fiddle::SIZEOF_INT
assert_equal 0x01010101, struct.x assert_equal 0x01010101, struct.x
ensure
Fiddle.free struct.to_ptr
end
end end
def test_malloc() def test_malloc()
s1 = LIBC::Timeval.malloc() s1 = LIBC::Timeval.malloc()
begin
s2 = LIBC::Timeval.malloc() s2 = LIBC::Timeval.malloc()
begin
refute_equal(s1.to_ptr.to_i, s2.to_ptr.to_i) 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 end
def test_sizeof() def test_sizeof()
assert_equal(SIZEOF_VOIDP, LIBC.sizeof("FILE*")) 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))
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) assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG)
end end
@ -131,6 +152,7 @@ module Fiddle
def test_struct_array_assignment() def test_struct_array_assignment()
instance = Fiddle::Importer.struct(["unsigned int stages[3]"]).malloc instance = Fiddle::Importer.struct(["unsigned int stages[3]"]).malloc
begin
instance.stages[0] = 1024 instance.stages[0] = 1024
instance.stages[1] = 10 instance.stages[1] = 10
instance.stages[2] = 100 instance.stages[2] = 100
@ -141,25 +163,40 @@ module Fiddle
instance.to_ptr[0, 3 * Fiddle::SIZEOF_INT] instance.to_ptr[0, 3 * Fiddle::SIZEOF_INT]
assert_raise(IndexError) { instance.stages[-1] = 5 } assert_raise(IndexError) { instance.stages[-1] = 5 }
assert_raise(IndexError) { instance.stages[3] = 5 } assert_raise(IndexError) { instance.stages[3] = 5 }
ensure
Fiddle.free instance.to_ptr
end
end end
def test_struct() def test_struct()
s = LIBC::MyStruct.malloc() s = LIBC::MyStruct.malloc()
begin
s.num = [0,1,2,3,4] s.num = [0,1,2,3,4]
s.c = ?a.ord s.c = ?a.ord
s.buff = "012345\377" s.buff = "012345\377"
assert_equal([0,1,2,3,4], s.num) assert_equal([0,1,2,3,4], s.num)
assert_equal(?a.ord, s.c) assert_equal(?a.ord, s.c)
assert_equal([?0.ord,?1.ord,?2.ord,?3.ord,?4.ord,?5.ord,?\377.ord], s.buff) 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 end
def test_gettimeofday() def test_gettimeofday()
if( defined?(LIBC.gettimeofday) ) if( defined?(LIBC.gettimeofday) )
timeval = LIBC::Timeval.malloc() timeval = LIBC::Timeval.malloc()
begin
timezone = LIBC::Timezone.malloc() timezone = LIBC::Timezone.malloc()
begin
LIBC.gettimeofday(timeval, timezone) LIBC.gettimeofday(timeval, timezone)
ensure
Fiddle.free timezone.to_ptr
end
cur = Time.now() cur = Time.now()
assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i) assert(cur.to_i - 2 <= timeval.tv_sec && timeval.tv_sec <= cur.to_i)
ensure
Fiddle.free timeval.to_ptr
end
end end
end end

View File

@ -84,7 +84,7 @@ module Fiddle
end end
def test_to_ptr_io def test_to_ptr_io
buf = Pointer.malloc(10) buf = Pointer.malloc(10, Fiddle::RUBY_FREE)
File.open(__FILE__, 'r') do |f| File.open(__FILE__, 'r') do |f|
ptr = Pointer.to_ptr f ptr = Pointer.to_ptr f
fread = Function.new(@libc['fread'], fread = Function.new(@libc['fread'],
@ -145,7 +145,11 @@ module Fiddle
def test_free def test_free
ptr = Pointer.malloc(4) ptr = Pointer.malloc(4)
begin
assert_nil ptr.free assert_nil ptr.free
ensure
Fiddle.free ptr
end
end end
def test_free= def test_free=
@ -173,15 +177,21 @@ module Fiddle
def test_size def test_size
ptr = Pointer.malloc(4) ptr = Pointer.malloc(4)
begin
assert_equal 4, ptr.size assert_equal 4, ptr.size
Fiddle.free ptr.to_i ensure
Fiddle.free ptr
end
end end
def test_size= def test_size=
ptr = Pointer.malloc(4) ptr = Pointer.malloc(4)
begin
ptr.size = 10 ptr.size = 10
assert_equal 10, ptr.size assert_equal 10, ptr.size
Fiddle.free ptr.to_i ensure
Fiddle.free ptr
end
end end
def test_aref_aset def test_aref_aset