[ruby/fiddle] Add Fiddle::Closure.create and Fiddle::Closure.free

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 https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091
for details.

Reported by Vít Ondruch. Thanks!!!

https://github.com/ruby/fiddle/commit/a0ccc6bb1b
This commit is contained in:
Sutou Kouhei 2022-09-14 12:59:13 +09:00 коммит произвёл Hiroshi SHIBATA
Родитель 191b91f47a
Коммит 255e617bc3
3 изменённых файлов: 129 добавлений и 45 удалений

Просмотреть файл

@ -224,6 +224,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)
{
@ -293,14 +304,28 @@ initialize(int rbargc, VALUE argv[], VALUE self)
static VALUE
to_i(VALUE self)
{
fiddle_closure * cl;
void *code;
fiddle_closure *closure = get_raw(self);
return PTR2NUM(closure->code);
}
TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, cl);
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;
}
code = cl->code;
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
@ -353,8 +378,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 */

Просмотреть файл

@ -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

Просмотреть файл

@ -29,37 +29,40 @@ module Fiddle
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
@ -69,18 +72,35 @@ module Fiddle
@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
@ -97,23 +117,21 @@ module Fiddle
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])
end
closure_class.create(t, [t]) do |closure|
v = ~(~0 << (8*s))
v = ~(~0 << (8*s))
arg = nil
assert_equal(v, closure.call(v))
assert_equal(arg, v, n)
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
arg = nil
func = Function.new(closure, [t], t)
assert_equal(v, func.call(v))
assert_equal(arg, v, n)
end
end
end
end