Skip to content

Commit

Permalink
Add Fiddle::Closure.create and Fiddle::Closure.free
Browse files Browse the repository at this point in the history
GitHub: fix GH-102

It's for freeing a closure explicitly.

We can't use Fiddle::Closure before we fork the process. If we do it,
the process may be crashed with SELinux.

See #102 (comment)
for details.

Reported by Vít Ondruch. Thanks!!!
  • Loading branch information
kou committed Sep 14, 2022
1 parent 060eef7 commit a0ccc6b
Show file tree
Hide file tree
Showing 3 changed files with 131 additions and 47 deletions.
55 changes: 48 additions & 7 deletions ext/fiddle/closure.c
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,17 @@ allocate(VALUE klass)
return i;
}

static fiddle_closure *
get_raw(VALUE self)
{
fiddle_closure *closure;
TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure);
if (!closure) {
rb_raise(rb_eArgError, "already freed: %+"PRIsVALUE, self);
}
return closure;
}

static VALUE
initialize(int rbargc, VALUE argv[], VALUE self)
{
Expand Down Expand Up @@ -295,14 +306,28 @@ initialize(int rbargc, VALUE argv[], VALUE self)
static VALUE
to_i(VALUE self)
{
fiddle_closure * cl;
void *code;

TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, cl);
fiddle_closure *closure = get_raw(self);
return PTR2NUM(closure->code);
}

code = cl->code;
static VALUE
closure_free(VALUE self)
{
fiddle_closure *closure;
TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure);
if (closure) {
dealloc(closure);
RTYPEDDATA_DATA(self) = NULL;
}
return RUBY_Qnil;
}

return PTR2NUM(code);
static VALUE
closure_freed_p(VALUE self)
{
fiddle_closure *closure;
TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure);
return closure ? RUBY_Qfalse : RUBY_Qtrue;
}

void
Expand Down Expand Up @@ -355,8 +380,24 @@ Init_fiddle_closure(void)
/*
* Document-method: to_i
*
* Returns the memory address for this closure
* Returns the memory address for this closure.
*/
rb_define_method(cFiddleClosure, "to_i", to_i, 0);

/*
* Document-method: free
*
* Free this closure explicitly. You can't use this closure anymore.
*
* If this closure is already freed, this does nothing.
*/
rb_define_method(cFiddleClosure, "free", closure_free, 0);

/*
* Document-method: freed?
*
* Whether this closure was freed explicitly.
*/
rb_define_method(cFiddleClosure, "freed?", closure_freed_p, 0);
}
/* vim: set noet sw=4 sts=4 */
25 changes: 25 additions & 0 deletions lib/fiddle/closure.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,31 @@
# frozen_string_literal: true
module Fiddle
class Closure
class << self
# Create a new closure. If a block is given, the created closure
# is automatically freed after the given block is executed.
#
# The all given arguments are passed to Fiddle::Closure.new. So
# using this method without block equals to Fiddle::Closure.new.
#
# == Example
#
# Fiddle::Closure.create(TYPE_INT, [TYPE_INT]) do |closure|
# # closure is freed automatically when this block is finished.
# end
def create(*args)
if block_given?
closure = new(*args)
begin
yield(closure)
ensure
closure.free
end
else
new(*args)
end
end
end

# the C type of the return of the FFI closure
attr_reader :ctype
Expand Down
98 changes: 58 additions & 40 deletions test/fiddle/test_closure.rb
Original file line number Diff line number Diff line change
Expand Up @@ -29,37 +29,40 @@ def test_argument_errors
end

def test_type_symbol
closure = Closure.new(:int, [:void])
assert_equal([
TYPE_INT,
[TYPE_VOID],
],
[
closure.instance_variable_get(:@ctype),
closure.instance_variable_get(:@args),
])
Closure.create(:int, [:void]) do |closure|
assert_equal([
TYPE_INT,
[TYPE_VOID],
],
[
closure.instance_variable_get(:@ctype),
closure.instance_variable_get(:@args),
])
end
end

def test_call
closure = Class.new(Closure) {
closure_class = Class.new(Closure) do
def call
10
end
}.new(TYPE_INT, [])

func = Function.new(closure, [], TYPE_INT)
assert_equal 10, func.call
end
closure_class.create(TYPE_INT, []) do |closure|
func = Function.new(closure, [], TYPE_INT)
assert_equal 10, func.call
end
end

def test_returner
closure = Class.new(Closure) {
closure_class = Class.new(Closure) do
def call thing
thing
end
}.new(TYPE_INT, [TYPE_INT])

func = Function.new(closure, [TYPE_INT], TYPE_INT)
assert_equal 10, func.call(10)
end
closure_class.create(TYPE_INT, [TYPE_INT]) do |closure|
func = Function.new(closure, [TYPE_INT], TYPE_INT)
assert_equal 10, func.call(10)
end
end

def test_const_string
Expand All @@ -69,18 +72,35 @@ def call(string)
@return_string
end
end
closure = closure_class.new(:const_string, [:const_string])
closure_class.create(:const_string, [:const_string]) do |closure|
func = Function.new(closure, [:const_string], :const_string)
assert_equal("Hello! World!", func.call("World!"))
end
end

func = Function.new(closure, [:const_string], :const_string)
assert_equal("Hello! World!", func.call("World!"))
def test_free
Closure.create(:int, [:void]) do |closure|
assert do
not closure.freed?
end
closure.free
assert do
closure.freed?
end
closure.free
end
end

def test_block_caller
cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one|
one
end
func = Function.new(cb, [TYPE_INT], TYPE_INT)
assert_equal 11, func.call(11)
begin
func = Function.new(cb, [TYPE_INT], TYPE_INT)
assert_equal 11, func.call(11)
ensure
cb.free
end
end

def test_memsize
Expand All @@ -97,23 +117,21 @@ def test_memsize
define_method("test_conversion_#{n.downcase}") do
arg = nil

clos = Class.new(Closure) do
closure_class = Class.new(Closure) do
define_method(:call) {|x| arg = x}
end.new(t, [t])

v = ~(~0 << (8*s))

arg = nil
assert_equal(v, clos.call(v))
assert_equal(arg, v, n)

arg = nil
func = Function.new(clos, [t], t)
assert_equal(v, func.call(v))
assert_equal(arg, v, n)
ensure
clos = nil
func = nil
end
closure_class.create(t, [t]) do |closure|
v = ~(~0 << (8*s))

arg = nil
assert_equal(v, closure.call(v))
assert_equal(arg, v, n)

arg = nil
func = Function.new(closure, [t], t)
assert_equal(v, func.call(v))
assert_equal(arg, v, n)
end
end
end
end
Expand Down

0 comments on commit a0ccc6b

Please sign in to comment.