Skip to content

Commit

Permalink
Add a Fiddle::Pointer#call_free method (#36)
Browse files Browse the repository at this point in the history
* Add a Fiddle::Pointer#free! method

* Use stdbool

* Fiddle::Pointer#free! does nothing if no free function installed

* Tidy up Fiddle::Pointer specs

* Improve Fiddle::Pointer docs

* Rename Fiddle::Pointer#free! to #call_free

* Don't manually free pointers in tests for now

* free! -> call_free

* free! -> call_free

* free! -> call_free

Co-authored-by: Sutou Kouhei <kou@cozmixng.org>
  • Loading branch information
chrisseaton and kou committed Jun 3, 2020
1 parent 016e71f commit db097cb
Show file tree
Hide file tree
Showing 2 changed files with 85 additions and 12 deletions.
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

0 comments on commit db097cb

Please sign in to comment.