Skip to content

Commit a0ccc6b

Browse files
committed
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 #102 (comment) for details. Reported by Vít Ondruch. Thanks!!!
1 parent 060eef7 commit a0ccc6b

File tree

3 files changed

+131
-47
lines changed

3 files changed

+131
-47
lines changed

ext/fiddle/closure.c

Lines changed: 48 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,17 @@ allocate(VALUE klass)
226226
return i;
227227
}
228228

229+
static fiddle_closure *
230+
get_raw(VALUE self)
231+
{
232+
fiddle_closure *closure;
233+
TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure);
234+
if (!closure) {
235+
rb_raise(rb_eArgError, "already freed: %+"PRIsVALUE, self);
236+
}
237+
return closure;
238+
}
239+
229240
static VALUE
230241
initialize(int rbargc, VALUE argv[], VALUE self)
231242
{
@@ -295,14 +306,28 @@ initialize(int rbargc, VALUE argv[], VALUE self)
295306
static VALUE
296307
to_i(VALUE self)
297308
{
298-
fiddle_closure * cl;
299-
void *code;
300-
301-
TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, cl);
309+
fiddle_closure *closure = get_raw(self);
310+
return PTR2NUM(closure->code);
311+
}
302312

303-
code = cl->code;
313+
static VALUE
314+
closure_free(VALUE self)
315+
{
316+
fiddle_closure *closure;
317+
TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure);
318+
if (closure) {
319+
dealloc(closure);
320+
RTYPEDDATA_DATA(self) = NULL;
321+
}
322+
return RUBY_Qnil;
323+
}
304324

305-
return PTR2NUM(code);
325+
static VALUE
326+
closure_freed_p(VALUE self)
327+
{
328+
fiddle_closure *closure;
329+
TypedData_Get_Struct(self, fiddle_closure, &closure_data_type, closure);
330+
return closure ? RUBY_Qfalse : RUBY_Qtrue;
306331
}
307332

308333
void
@@ -355,8 +380,24 @@ Init_fiddle_closure(void)
355380
/*
356381
* Document-method: to_i
357382
*
358-
* Returns the memory address for this closure
383+
* Returns the memory address for this closure.
359384
*/
360385
rb_define_method(cFiddleClosure, "to_i", to_i, 0);
386+
387+
/*
388+
* Document-method: free
389+
*
390+
* Free this closure explicitly. You can't use this closure anymore.
391+
*
392+
* If this closure is already freed, this does nothing.
393+
*/
394+
rb_define_method(cFiddleClosure, "free", closure_free, 0);
395+
396+
/*
397+
* Document-method: freed?
398+
*
399+
* Whether this closure was freed explicitly.
400+
*/
401+
rb_define_method(cFiddleClosure, "freed?", closure_freed_p, 0);
361402
}
362403
/* vim: set noet sw=4 sts=4 */

lib/fiddle/closure.rb

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,31 @@
11
# frozen_string_literal: true
22
module Fiddle
33
class Closure
4+
class << self
5+
# Create a new closure. If a block is given, the created closure
6+
# is automatically freed after the given block is executed.
7+
#
8+
# The all given arguments are passed to Fiddle::Closure.new. So
9+
# using this method without block equals to Fiddle::Closure.new.
10+
#
11+
# == Example
12+
#
13+
# Fiddle::Closure.create(TYPE_INT, [TYPE_INT]) do |closure|
14+
# # closure is freed automatically when this block is finished.
15+
# end
16+
def create(*args)
17+
if block_given?
18+
closure = new(*args)
19+
begin
20+
yield(closure)
21+
ensure
22+
closure.free
23+
end
24+
else
25+
new(*args)
26+
end
27+
end
28+
end
429

530
# the C type of the return of the FFI closure
631
attr_reader :ctype

test/fiddle/test_closure.rb

Lines changed: 58 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -29,37 +29,40 @@ def test_argument_errors
2929
end
3030

3131
def test_type_symbol
32-
closure = Closure.new(:int, [:void])
33-
assert_equal([
34-
TYPE_INT,
35-
[TYPE_VOID],
36-
],
37-
[
38-
closure.instance_variable_get(:@ctype),
39-
closure.instance_variable_get(:@args),
40-
])
32+
Closure.create(:int, [:void]) do |closure|
33+
assert_equal([
34+
TYPE_INT,
35+
[TYPE_VOID],
36+
],
37+
[
38+
closure.instance_variable_get(:@ctype),
39+
closure.instance_variable_get(:@args),
40+
])
41+
end
4142
end
4243

4344
def test_call
44-
closure = Class.new(Closure) {
45+
closure_class = Class.new(Closure) do
4546
def call
4647
10
4748
end
48-
}.new(TYPE_INT, [])
49-
50-
func = Function.new(closure, [], TYPE_INT)
51-
assert_equal 10, func.call
49+
end
50+
closure_class.create(TYPE_INT, []) do |closure|
51+
func = Function.new(closure, [], TYPE_INT)
52+
assert_equal 10, func.call
53+
end
5254
end
5355

5456
def test_returner
55-
closure = Class.new(Closure) {
57+
closure_class = Class.new(Closure) do
5658
def call thing
5759
thing
5860
end
59-
}.new(TYPE_INT, [TYPE_INT])
60-
61-
func = Function.new(closure, [TYPE_INT], TYPE_INT)
62-
assert_equal 10, func.call(10)
61+
end
62+
closure_class.create(TYPE_INT, [TYPE_INT]) do |closure|
63+
func = Function.new(closure, [TYPE_INT], TYPE_INT)
64+
assert_equal 10, func.call(10)
65+
end
6366
end
6467

6568
def test_const_string
@@ -69,18 +72,35 @@ def call(string)
6972
@return_string
7073
end
7174
end
72-
closure = closure_class.new(:const_string, [:const_string])
75+
closure_class.create(:const_string, [:const_string]) do |closure|
76+
func = Function.new(closure, [:const_string], :const_string)
77+
assert_equal("Hello! World!", func.call("World!"))
78+
end
79+
end
7380

74-
func = Function.new(closure, [:const_string], :const_string)
75-
assert_equal("Hello! World!", func.call("World!"))
81+
def test_free
82+
Closure.create(:int, [:void]) do |closure|
83+
assert do
84+
not closure.freed?
85+
end
86+
closure.free
87+
assert do
88+
closure.freed?
89+
end
90+
closure.free
91+
end
7692
end
7793

7894
def test_block_caller
7995
cb = Closure::BlockCaller.new(TYPE_INT, [TYPE_INT]) do |one|
8096
one
8197
end
82-
func = Function.new(cb, [TYPE_INT], TYPE_INT)
83-
assert_equal 11, func.call(11)
98+
begin
99+
func = Function.new(cb, [TYPE_INT], TYPE_INT)
100+
assert_equal 11, func.call(11)
101+
ensure
102+
cb.free
103+
end
84104
end
85105

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

100-
clos = Class.new(Closure) do
120+
closure_class = Class.new(Closure) do
101121
define_method(:call) {|x| arg = x}
102-
end.new(t, [t])
103-
104-
v = ~(~0 << (8*s))
105-
106-
arg = nil
107-
assert_equal(v, clos.call(v))
108-
assert_equal(arg, v, n)
109-
110-
arg = nil
111-
func = Function.new(clos, [t], t)
112-
assert_equal(v, func.call(v))
113-
assert_equal(arg, v, n)
114-
ensure
115-
clos = nil
116-
func = nil
122+
end
123+
closure_class.create(t, [t]) do |closure|
124+
v = ~(~0 << (8*s))
125+
126+
arg = nil
127+
assert_equal(v, closure.call(v))
128+
assert_equal(arg, v, n)
129+
130+
arg = nil
131+
func = Function.new(closure, [t], t)
132+
assert_equal(v, func.call(v))
133+
assert_equal(arg, v, n)
134+
end
117135
end
118136
end
119137
end

0 commit comments

Comments
 (0)