summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSutou Kouhei <[email protected]>2022-09-14 12:59:13 +0900
committerHiroshi SHIBATA <[email protected]>2022-10-07 15:18:51 +0900
commit255e617bc38f714c943c932e7df2b709313fd6bf (patch)
tree7c29170acba0ebce434cce22c0bfe34774ac7117
parent191b91f47a1557b7178072e2a607105d5449e2ef (diff)
[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
-rw-r--r--ext/fiddle/closure.c55
-rw-r--r--ext/fiddle/lib/fiddle/closure.rb25
-rw-r--r--test/fiddle/test_closure.rb98
3 files changed, 131 insertions, 47 deletions
diff --git a/ext/fiddle/closure.c b/ext/fiddle/closure.c
index 2d340297d5..694ef90b7f 100644
--- a/ext/fiddle/closure.c
+++ b/ext/fiddle/closure.c
@@ -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;
-
- 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
@@ -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 */
diff --git a/ext/fiddle/lib/fiddle/closure.rb b/ext/fiddle/lib/fiddle/closure.rb
index c865a63c20..7e0077ea52 100644
--- a/ext/fiddle/lib/fiddle/closure.rb
+++ b/ext/fiddle/lib/fiddle/closure.rb
@@ -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
diff --git a/test/fiddle/test_closure.rb b/test/fiddle/test_closure.rb
index 1a7c41fd95..d248b9cbdd 100644
--- a/test/fiddle/test_closure.rb
+++ b/test/fiddle/test_closure.rb
@@ -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])
-
- 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