8000 [ruby/fiddle] Add support for variadic arguments · nobu/ruby@ae18220 · GitHub
[go: up one dir, main page]

Skip to content

Commit ae18220

Browse files
kounobu
authored andcommitted
[ruby/fiddle] Add support for variadic arguments
GitHub: fix rubyGH-39 Reported by kojix2. Thanks!!! ruby/fiddle@6c4cb904dc
1 parent 9f740ac commit ae18220

File tree

6 files changed

+229
-64
lines changed

6 files changed

+229
-64
lines changed

ext/fiddle/extconf.rb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,8 @@
127127
have_func('ffi_closure_alloc', ffi_header)
128128
end
129129

130+
have_func('ffi_prep_cif_var', ffi_header)
131+
130132
have_header 'sys/mman.h'
131133

132134
if have_header "dlfcn.h"

ext/fiddle/fiddle.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,12 @@ Init_fiddle(void)
227227
*/
228228
rb_define_const(mFiddle, "TYPE_DOUBLE", INT2NUM(TYPE_DOUBLE));
229229

230+
/* Document-const: TYPE_VARIADIC
231+
*
232+
* C type - ...
233+
*/
234+
rb_define_const(mFiddle, "TYPE_VARIADIC", INT2NUM(TYPE_VARIADIC));
235+
230236
/* Document-const: TYPE_SIZE_T
231237
*
232238
* C type - size_t

ext/fiddle/fiddle.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@
115115
#endif
116116
#define TYPE_FLOAT 7
117117
#define TYPE_DOUBLE 8
118+
#define TYPE_VARIADIC 9
118119

119120
#define ALIGN_OF(type) offsetof(struct {char align_c; type align_x;}, align_x)
120121

ext/fiddle/function.c

Lines changed: 188 additions & 63 deletions
F438
Original file line numberDiff line numberDiff line change
@@ -87,63 +87,88 @@ parse_keyword_arg_i(VALUE key, VALUE value, VALUE self)
8787
return ST_CONTINUE;
8888
}
8989

90+
static VALUE
91+
normalize_argument_types(const char *name,
92+
VALUE arg_types,
93+
bool *is_variadic)
94+
{
95+
VALUE normalized_arg_types;
96+
int i;
97+
int n_arg_types;
98+
*is_variadic = false;
99+
100+
Check_Type(arg_types, T_ARRAY);
101+
n_arg_types = RARRAY_LENINT(arg_types);
102+
Check_Max_Args(name, n_arg_types);
103+
104+
normalized_arg_types = rb_ary_new_capa(n_arg_types);
105+
for (i = 0; i < n_arg_types; i++) {
106+
VALUE arg_type = RARRAY_AREF(arg_types, i);
107+
int c_arg_type = NUM2INT(arg_type);
108+
if (c_arg_type == TYPE_VARIADIC) {
109+
if (i != n_arg_types - 1) {
110+
rb_raise(rb_eArgError,
111+
"Fiddle::TYPE_VARIADIC must be the last argument type: "
112+
"%"PRIsVALUE,
113+
arg_types);
114+
}
115+
*is_variadic = true;
116+
break;
117+
}
118+
else {
119+
(void)INT2FFI_TYPE(c_arg_type); /* raise */
120+
}
121+
rb_ary_push(normalized_arg_types, INT2FIX(c_arg_type));
122+
}
123+
124+
/* freeze to prevent inconsistency at calling #to_int later */
125+
OBJ_FREEZE(normalized_arg_types);
126+
return normalized_arg_types;
127+
}
128+
90129
static VALUE
91130
initialize(int argc, VALUE argv[], VALUE self)
92131
{
93132
ffi_cif * cif;
94-
ffi_type **arg_types, *rtype;
95-
ffi_status result;
96-
VALUE ptr, args, ret_type, abi, kwds;
97-
int i, len;
98-
int nabi;
133+
VALUE ptr, arg_types, ret_type, abi, kwds;
134+
int c_ret_type;
135+
bool is_variadic = false;
136+
ffi_abi c_ffi_abi;
99137
void *cfunc;
100138

101-
rb_scan_args(argc, argv, "31:", &ptr, &args, &ret_type, &abi, &kwds);
139+
rb_scan_args(argc, argv, "31:", &ptr, &arg_types, &ret_type, &abi, &kwds);
102140
rb_iv_set(self, "@closure", ptr);
103141

104142
ptr = rb_Integer(ptr);
105143
cfunc = NUM2PTR(ptr);
106144
PTR2NUM(cfunc);
107-
nabi = NIL_P(abi) ? FFI_DEFAULT_ABI : NUM2INT(abi);
108-
abi = INT2FIX(nabi);
109-
i = NUM2INT(ret_type);
110-
rtype = INT2FFI_TYPE(i);
111-
ret_type = INT2FIX(i);
112-
113-
Check_Type(args, T_ARRAY);
114-
len = RARRAY_LENINT(args);
115-
Check_Max_Args("args", len);
116-
/* freeze to prevent inconsistency at calling #to_int later */
117-
args = rb_ary_subseq(args, 0, len);
118-
for (i = 0; i < RARRAY_LEN(args); i++) {
119-
VALUE a = RARRAY_AREF(args, i);
120-
int type = NUM2INT(a);
121-
(void)INT2FFI_TYPE(type); /* raise */
122-
if (INT2FIX(type) != a) rb_ary_store(args, i, INT2FIX(type));
145+
c_ffi_abi = NIL_P(abi) ? FFI_DEFAULT_ABI : NUM2INT(abi);
146+
abi = INT2FIX(c_ffi_abi);
147+
c_ret_type = NUM2INT(ret_type);
148+
(void)INT2FFI_TYPE(c_ret_type); /* raise */
149+
ret_type = INT2FIX(c_ret_type);
150+
151+
arg_types = normalize_argument_types("argument types",
152+
arg_types,
153+
&is_variadic);
154+
#ifndef HAVE_FFI_PREP_CIF_VAR
155+
if (is_variadic) {
156+
rb_raise(rb_eNotImpError,
157+
"ffi_prep_cif_var() is required in libffi "
158+
"for variadic arguments");
123159
}
124-
OBJ_FREEZE(args);
160+
#endif
125161

126162
rb_iv_set(self, "@ptr", ptr);
127-
rb_iv_set(self, "@args", args);
163+
rb_iv_set(self, "@argument_types", arg_types);
128164
rb_iv_set(self, "@return_type", ret_type);
129165
rb_iv_set(self, "@abi", abi);
166+
rb_iv_set(self, "@is_variadic", is_variadic ? Qtrue : Qfalse);
130167

131168
if (!NIL_P(kwds)) rb_hash_foreach(kwds, parse_keyword_arg_i, self);
132169

133170
TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif);
134-
135-
arg_types = xcalloc(len + 1, sizeof(ffi_type *));
136-
137-
for (i = 0; i < RARRAY_LEN(args); i++) {
138-
int type = NUM2INT(RARRAY_AREF(args, i));
139-
arg_types[i] = INT2FFI_TYPE(type);
140-
}
141-
arg_types[len] = NULL;
142-
143-
result = ffi_prep_cif(cif, nabi, len, rtype, arg_types);
144-
145-
if (result)
146-
rb_raise(rb_eRuntimeError, "error creating CIF %d", result);
171+
cif->arg_types = NULL;
147172

148173
return self;
149174
}
@@ -170,44 +195,144 @@ function_call(int argc, VALUE argv[], VALUE self)
170195
{
171196
struct nogvl_ffi_call_args args = { 0 };
172197
fiddle_generic *generic_args;
173-
VALUE cfunc, types, cPointer;
198+
VALUE cfunc;
199+
VALUE abi;
200+
VALUE arg_types;
201+
VALUE cPointer;
202+
VALUE is_variadic;
203+
int n_arg_types;
204+
int n_fixed_args = 0;
205+
int n_call_args = 0;
174206
int i;
207+
int i_call;
175208
VALUE alloc_buffer = 0;
176209

177210
cfunc = rb_iv_get(self, "@ptr");
178-
types = rb_iv_get(self, "@args");
211+
abi = rb_iv_get(self, "@abi");
212+
arg_types = rb_iv_get(self, "@argument_types");
179213
cPointer = rb_const_get(mFiddle, rb_intern("Pointer"));
180-
181-
Check_Max_Args("number of arguments", argc);
182-
if (argc != (i = RARRAY_LENINT(types))) {
183-
rb_error_arity(argc, i, i);
214+
is_variadic = rb_iv_get(self, "@is_variadic");
215+
216+
n_arg_types = RARRAY_LENINT(arg_types);
217+
n_fixed_args = n_arg_types;
218+
if (RTEST(is_variadic)) {
219+
if (argc < n_arg_types) {
220+
rb_error_arity(argc, n_arg_types, UNLIMITED_ARGUMENTS);
221+
}
222+
if (((argc - n_arg_types) % 2) != 0) {
223+
rb_raise(rb_eArgError,
224+
"variadic arguments must be type and value pairs: "
225+
"%"PRIsVALUE,
226+
rb_ary_new_from_values(argc, argv));
227+
}
228+
n_call_args = n_arg_types + ((argc - n_arg_types) / 2);
229+
}
230+
else {
231+
if (argc != n_arg_types) {
232+
rb_error_arity(argc, n_arg_types, n_arg_types);
233+
}
234+
n_call_args = n_arg_types;
184235
}
236+
Check_Max_Args("the number of arguments", n_call_args);
185237

186238
TypedData_Get_Struct(self, ffi_cif, &function_data_type, args.cif);
187239

240+
if (is_variadic && args.cif->arg_types) {
241+
xfree(args.cif->arg_types);
242+
args.cif->arg_types = NULL;
243+
}
244+
245+
if (!args.cif->arg_types) {
246+
VALUE fixed_arg_types = arg_types;
247+
VALUE return_type;
248+
int c_return_type;
249+
ffi_type *ffi_return_type;
250+
ffi_type **ffi_arg_types;
251+
ffi_status result;
252+
253+
arg_types = rb_ary_dup(fixed_arg_types);
254+
for (i = n_fixed_args; i < argc; i += 2) {
255+
VALUE arg_type = argv[i];
256+
int c_arg_type = NUM2INT(arg_type);
257+
(void)INT2FFI_TYPE(c_arg_type); /* raise */
258+
rb_ary_push(arg_types, INT2FIX(c_arg_type));
259+
}
260+
261+
return_type = rb_iv_get(self, "@return_type");
262+
c_return_type = FIX2INT(return_type);
263+
ffi_return_type = INT2FFI_TYPE(c_return_type);
264+
265+
ffi_arg_types = xcalloc(n_call_args + 1, sizeof(ffi_type *));
266+
for (i_call = 0; i_call < n_call_args; i_call++) {
267+
VALUE arg_type;
268+
int c_arg_type;
269+
arg_type = RARRAY_AREF(arg_types, i_call);
270+
c_arg_type = FIX2INT(arg_type);
271+
ffi_arg_types[i_call] = INT2FFI_TYPE(c_arg_type);
272+
}
273+
ffi_arg_types[i_call] = NULL;
274+
275+
if (is_variadic) {
276+
#ifdef HAVE_FFI_PREP_CIF_VAR
277+
result = ffi_prep_cif_var(args.cif,
278+
FIX2INT(abi),
279+
n_fixed_args,
280+
n_call_args,
281+
ffi_return_type,
282+
ffi_arg_types);
283+
#else
284+
/* This code is never used because ffi_prep_cif_var()
285+
* availability check is done in #initialize. */
286+
result = FFI_BAD_TYPEDEF;
287+
#endif
288+
}
289+
else {
290+
result = ffi_prep_cif(args.cif,
291+
FIX2INT(abi),
292+
n_call_args,
293+
ffi_return_type,
294+
ffi_arg_types);
295+
}
296+
if (result != FFI_OK) {
297+
xfree(ffi_arg_types);
298+
args.cif->arg_types = NULL;
299+
rb_raise(rb_eRuntimeError, "error creating CIF %d", result);
300+
}
301+
}
302+
188303
generic_args = ALLOCV(alloc_buffer,
189-
(size_t)(argc + 1) * sizeof(void *) + (size_t)argc * sizeof(fiddle_generic));
304+
sizeof(fiddle_generic) * n_call_args +
305+
sizeof(void *) * (n_call_args + 1));
190306
args.values = (void **)((char *)generic_args +
191-
(size_t)argc * sizeof(fiddle_generic));
192-
193-
for (i = 0; i < argc; i++) {
194-
VALUE type = RARRAY_AREF(types, i);
195-
VALUE src = argv[i];
196-
int argtype = FIX2INT(type);
197-
198-
if (argtype == TYPE_VOIDP) {
199-
if(NIL_P(src)) {
200-
src = INT2FIX(0);
201-
} else if(cPointer != CLASS_OF(src)) {
202-
src = rb_funcall(cPointer, rb_intern("[]"), 1, src);
203-
}
204-
src = rb_Integer(src);
205-
}
206-
207-
VALUE2GENERIC(argtype, src, &generic_args[i]);
208-
args.values[i] = (void *)&generic_args[i];
307+
sizeof(fiddle_generic) * n_call_args);
308+
309+
for (i = 0, i_call = 0;
310+
i < argc && i_call < n_call_args;
311+
i++, i_call++) {
312+
VALUE arg_type;
313+
int c_arg_type;
314+
VALUE src;
315+
arg_type = RARRAY_AREF(arg_types, i_call);
316+
c_arg_type = FIX2INT(arg_type);
317+
if (i >= n_fixed_args) {
318+
i++;
319+
}
320+
src = argv[i];
321+
322+
if (c_arg_type == TYPE_VOIDP) {
323+
if (NIL_P(src)) {
324+
src = INT2FIX(0);
325+
}
326+
else if (cPointer != CLASS_OF(src)) {
327+
src = rb_funcall(cPointer, rb_intern("[]"), 1, src);
328+
}
329+
src = rb_Integer(src);
330+
}
331+
332+
VALUE2GENERIC(c_arg_type, src, &generic_args[i_call]);
333+
args.values[i_call] = (void *)&generic_args[i_call];
209334
}
210-
args.values[argc] = NULL;
335+
args.values[i_call] = NULL;
211336
args.fn = (void(*)(void))NUM2PTR(cfunc);
212337

213338
(void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0);

test/fiddle/test_func.rb

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,36 @@ def call(x, y)
7979
EnvUtil.under_gc_stress {qsort.call(buff, buff.size, 1, cb)}
8080
assert_equal("1349", buff, bug4929)
8181
end
82+
83+
def test_snprintf
84+
snprintf = Function.new(@libc["snprintf"],
85+
[
86+
TYPE_VOIDP,
87+
TYPE_SIZE_T,
88+
TYPE_VOIDP,
89+
TYPE_VARIADIC,
90+
],
91+
TYPE_INT)
92+
output_buffer = " " * 1024
93+
output = Pointer[output_buffer]
94+
95+
written = snprintf.call(output,
96+
output.size,
97+
"int: %d, string: %.*s\n",
98+
TYPE_INT, -29,
99+
TYPE_INT, 4,
100+
TYPE_VOIDP, "Hello")
101+
assert_equal("int: -29, string: Hell\n",
102+
output_buffer[0, written])
103+
104+
written = snprintf.call(output,
105+
output.size,
106+
"string: %.*s, uint: %u\n",
107+
TYPE_INT, 2,
108+
TYPE_VOIDP, "Hello",
109+
TYPE_INT, 29)
110+
assert_equal("string: He, uint: 29\n",
111+
output_buffer[0, written])
112+
end
82113
end
83114
end if defined?(Fiddle)

test/fiddle/test_import.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -110,7 +110,7 @@ def test_sizeof()
110110
assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG)
111111
end
112112

113-
Fiddle.constants.grep(/\ATYPE_(?!VOID\z)(.*)/) do
113+
Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(.*)/) do
114114
type = $&
115115
size = Fiddle.const_get("SIZEOF_#{$1}")
116116
name = $1.sub(/P\z/,"*").gsub(/_(?!T\z)/, " ").downcase

0 commit comments

Comments
 (0)
0