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
70 changes: 58 additions & 12 deletions ext/fiddle/pointer.c
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
* $Id$
*/

#include <stdbool.h>
#include <ruby/ruby.h>
#include <ruby/io.h>
#include <ctype.h>
Expand All @@ -24,6 +25,7 @@ struct ptr_data {
void *ptr;
long size;
freefunc_t free;
bool freed;
VALUE wrap[2];
};

Expand Down Expand Up @@ -57,14 +59,19 @@ fiddle_ptr_mark(void *ptr)
}

static void
fiddle_ptr_free(void *ptr)
fiddle_ptr_free_ptr(void *ptr)
{
struct ptr_data *data = ptr;
if (data->ptr) {
if (data->free) {
(*(data->free))(data->ptr);
}
if (data->ptr && data->free && !data->freed) {
data->freed = true;
(*(data->free))(data->ptr);
}
}

static void
fiddle_ptr_free(void *ptr)
{
fiddle_ptr_free_ptr(ptr);
xfree(ptr);
}

Expand All @@ -89,6 +96,7 @@ rb_fiddle_ptr_new2(VALUE klass, void *ptr, long size, freefunc_t func)
val = TypedData_Make_Struct(klass, struct ptr_data, &fiddle_ptr_data_type, data);
data->ptr = ptr;
data->free = func;
data->freed = false;
data->size = size;

return val;
Expand Down Expand Up @@ -140,6 +148,7 @@ rb_fiddle_ptr_s_allocate(VALUE klass)
data->ptr = 0;
data->size = 0;
data->free = 0;
data->freed = false;

return obj;
}
Expand Down Expand Up @@ -197,11 +206,16 @@ rb_fiddle_ptr_initialize(int argc, VALUE argv[], VALUE self)
*
* == Examples
*
* # Manually freeing but relying on the garbage collector otherwise
* pointer = Fiddle::Pointer.malloc(size, Fiddle::RUBY_FREE)
* ...
* pointer.call_free
*
* # 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
* # Only manually freeing
* pointer = Fiddle::Pointer.malloc(size)
* begin
* ...
Expand All @@ -214,13 +228,15 @@ rb_fiddle_ptr_initialize(int argc, VALUE argv[], VALUE self)
* ...
*
* 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,
* if not already manually called via *call_free*.
*
* +freefunc+ must be an address pointing to a function or an instance of
* +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.
* +Fiddle::Function+. Using +freefunc+ without using +call_free+ 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
* can combine the techniques as +freefunc+ will not be called twice.
*/
static VALUE
rb_fiddle_ptr_s_malloc(int argc, VALUE argv[], VALUE klass)
Expand Down Expand Up @@ -370,6 +386,34 @@ rb_fiddle_ptr_free_get(VALUE self)
return rb_fiddle_new_function(address, arg_types, ret_type);
}

/*
* call-seq: call_free => nil
*
* Call the free function for this pointer. Calling more than once will do
* nothing. Does nothing if there is no free function attached.
*/
static VALUE
rb_fiddle_ptr_call_free(VALUE self)
{
struct ptr_data *pdata;
TypedData_Get_Struct(self, struct ptr_data, &fiddle_ptr_data_type, pdata);
fiddle_ptr_free_ptr(pdata);
return Qnil;
}

/*
* call-seq: freed? => bool
*
* Returns if the free function for this pointer has been called.
*/
static VALUE
rb_fiddle_ptr_freed_p(VALUE self)
{
struct ptr_data *pdata;
TypedData_Get_Struct(self, struct ptr_data, &fiddle_ptr_data_type, pdata);
return pdata->freed ? Qtrue : Qfalse;
}

/*
* call-seq:
*
Expand Down Expand Up @@ -711,6 +755,8 @@ Init_fiddle_pointer(void)
rb_define_method(rb_cPointer, "initialize", rb_fiddle_ptr_initialize, -1);
rb_define_method(rb_cPointer, "free=", rb_fiddle_ptr_free_set, 1);
rb_define_method(rb_cPointer, "free", rb_fiddle_ptr_free_get, 0);
rb_define_method(rb_cPointer, "call_free", rb_fiddle_ptr_call_free, 0);
rb_define_method(rb_cPointer, "freed?", rb_fiddle_ptr_freed_p, 0);
rb_define_method(rb_cPointer, "to_i", rb_fiddle_ptr_to_i, 0);
rb_define_method(rb_cPointer, "to_int", rb_fiddle_ptr_to_i, 0);
rb_define_method(rb_cPointer, "to_value", rb_fiddle_ptr_to_value, 0);
Expand Down
27 changes: 27 additions & 0 deletions test/fiddle/test_pointer.rb
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,33 @@ def test_free=
assert_equal free.ptr, ptr.free.ptr
end

def test_free_with_func
ptr = Pointer.malloc(4, Fiddle::RUBY_FREE)
refute ptr.freed?
ptr.call_free
assert ptr.freed?
ptr.call_free # you can safely run it again
assert ptr.freed?
GC.start # you can safely run the GC routine
assert ptr.freed?
end

def test_free_with_no_func
ptr = Pointer.malloc(4)
refute ptr.freed?
ptr.call_free
refute ptr.freed?
ptr.call_free # you can safely run it again
refute ptr.freed?
end

def test_freed?
ptr = Pointer.malloc(4, Fiddle::RUBY_FREE)
refute ptr.freed?
ptr.call_free
assert ptr.freed?
end

def test_null?
ptr = Pointer.new(0)
assert ptr.null?
Expand Down