1
0
Fork 0
mirror of https://github.com/ruby/ruby.git synced 2022-11-09 12:17:21 -05:00

[ruby/fiddle] Add support for variadic arguments

GitHub: fix GH-39

Reported by kojix2. Thanks!!!

https://github.com/ruby/fiddle/commit/6c4cb904dc
This commit is contained in:
Sutou Kouhei 2020-06-27 07:25:47 +09:00 committed by Nobuyoshi Nakada
parent 9f740acaf9
commit ae18220f99
No known key found for this signature in database
GPG key ID: 7CD2805BFA3770C6
Notes: git 2020-06-28 02:03:19 +09:00
6 changed files with 229 additions and 64 deletions

View file

@ -127,6 +127,8 @@ else
have_func('ffi_closure_alloc', ffi_header)
end
have_func('ffi_prep_cif_var', ffi_header)
have_header 'sys/mman.h'
if have_header "dlfcn.h"

View file

@ -227,6 +227,12 @@ Init_fiddle(void)
*/
rb_define_const(mFiddle, "TYPE_DOUBLE", INT2NUM(TYPE_DOUBLE));
/* Document-const: TYPE_VARIADIC
*
* C type - ...
*/
rb_define_const(mFiddle, "TYPE_VARIADIC", INT2NUM(TYPE_VARIADIC));
/* Document-const: TYPE_SIZE_T
*
* C type - size_t

View file

@ -115,6 +115,7 @@
#endif
#define TYPE_FLOAT 7
#define TYPE_DOUBLE 8
#define TYPE_VARIADIC 9
#define ALIGN_OF(type) offsetof(struct {char align_c; type align_x;}, align_x)

View file

@ -87,63 +87,88 @@ parse_keyword_arg_i(VALUE key, VALUE value, VALUE self)
return ST_CONTINUE;
}
static VALUE
normalize_argument_types(const char *name,
VALUE arg_types,
bool *is_variadic)
{
VALUE normalized_arg_types;
int i;
int n_arg_types;
*is_variadic = false;
Check_Type(arg_types, T_ARRAY);
n_arg_types = RARRAY_LENINT(arg_types);
Check_Max_Args(name, n_arg_types);
normalized_arg_types = rb_ary_new_capa(n_arg_types);
for (i = 0; i < n_arg_types; i++) {
VALUE arg_type = RARRAY_AREF(arg_types, i);
int c_arg_type = NUM2INT(arg_type);
if (c_arg_type == TYPE_VARIADIC) {
if (i != n_arg_types - 1) {
rb_raise(rb_eArgError,
"Fiddle::TYPE_VARIADIC must be the last argument type: "
"%"PRIsVALUE,
arg_types);
}
*is_variadic = true;
break;
}
else {
(void)INT2FFI_TYPE(c_arg_type); /* raise */
}
rb_ary_push(normalized_arg_types, INT2FIX(c_arg_type));
}
/* freeze to prevent inconsistency at calling #to_int later */
OBJ_FREEZE(normalized_arg_types);
return normalized_arg_types;
}
static VALUE
initialize(int argc, VALUE argv[], VALUE self)
{
ffi_cif * cif;
ffi_type **arg_types, *rtype;
ffi_status result;
VALUE ptr, args, ret_type, abi, kwds;
int i, len;
int nabi;
VALUE ptr, arg_types, ret_type, abi, kwds;
int c_ret_type;
bool is_variadic = false;
ffi_abi c_ffi_abi;
void *cfunc;
rb_scan_args(argc, argv, "31:", &ptr, &args, &ret_type, &abi, &kwds);
rb_scan_args(argc, argv, "31:", &ptr, &arg_types, &ret_type, &abi, &kwds);
rb_iv_set(self, "@closure", ptr);
ptr = rb_Integer(ptr);
cfunc = NUM2PTR(ptr);
PTR2NUM(cfunc);
nabi = NIL_P(abi) ? FFI_DEFAULT_ABI : NUM2INT(abi);
abi = INT2FIX(nabi);
i = NUM2INT(ret_type);
rtype = INT2FFI_TYPE(i);
ret_type = INT2FIX(i);
c_ffi_abi = NIL_P(abi) ? FFI_DEFAULT_ABI : NUM2INT(abi);
abi = INT2FIX(c_ffi_abi);
c_ret_type = NUM2INT(ret_type);
(void)INT2FFI_TYPE(c_ret_type); /* raise */
ret_type = INT2FIX(c_ret_type);
Check_Type(args, T_ARRAY);
len = RARRAY_LENINT(args);
Check_Max_Args("args", len);
/* freeze to prevent inconsistency at calling #to_int later */
args = rb_ary_subseq(args, 0, len);
for (i = 0; i < RARRAY_LEN(args); i++) {
VALUE a = RARRAY_AREF(args, i);
int type = NUM2INT(a);
(void)INT2FFI_TYPE(type); /* raise */
if (INT2FIX(type) != a) rb_ary_store(args, i, INT2FIX(type));
arg_types = normalize_argument_types("argument types",
arg_types,
&is_variadic);
#ifndef HAVE_FFI_PREP_CIF_VAR
if (is_variadic) {
rb_raise(rb_eNotImpError,
"ffi_prep_cif_var() is required in libffi "
"for variadic arguments");
}
OBJ_FREEZE(args);
#endif
rb_iv_set(self, "@ptr", ptr);
rb_iv_set(self, "@args", args);
rb_iv_set(self, "@argument_types", arg_types);
rb_iv_set(self, "@return_type", ret_type);
rb_iv_set(self, "@abi", abi);
rb_iv_set(self, "@is_variadic", is_variadic ? Qtrue : Qfalse);
if (!NIL_P(kwds)) rb_hash_foreach(kwds, parse_keyword_arg_i, self);
TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif);
arg_types = xcalloc(len + 1, sizeof(ffi_type *));
for (i = 0; i < RARRAY_LEN(args); i++) {
int type = NUM2INT(RARRAY_AREF(args, i));
arg_types[i] = INT2FFI_TYPE(type);
}
arg_types[len] = NULL;
result = ffi_prep_cif(cif, nabi, len, rtype, arg_types);
if (result)
rb_raise(rb_eRuntimeError, "error creating CIF %d", result);
cif->arg_types = NULL;
return self;
}
@ -170,44 +195,144 @@ function_call(int argc, VALUE argv[], VALUE self)
{
struct nogvl_ffi_call_args args = { 0 };
fiddle_generic *generic_args;
VALUE cfunc, types, cPointer;
VALUE cfunc;
VALUE abi;
VALUE arg_types;
VALUE cPointer;
VALUE is_variadic;
int n_arg_types;
int n_fixed_args = 0;
int n_call_args = 0;
int i;
int i_call;
VALUE alloc_buffer = 0;
cfunc = rb_iv_get(self, "@ptr");
types = rb_iv_get(self, "@args");
abi = rb_iv_get(self, "@abi");
arg_types = rb_iv_get(self, "@argument_types");
cPointer = rb_const_get(mFiddle, rb_intern("Pointer"));
is_variadic = rb_iv_get(self, "@is_variadic");
Check_Max_Args("number of arguments", argc);
if (argc != (i = RARRAY_LENINT(types))) {
rb_error_arity(argc, i, i);
n_arg_types = RARRAY_LENINT(arg_types);
n_fixed_args = n_arg_types;
if (RTEST(is_variadic)) {
if (argc < n_arg_types) {
rb_error_arity(argc, n_arg_types, UNLIMITED_ARGUMENTS);
}
if (((argc - n_arg_types) % 2) != 0) {
rb_raise(rb_eArgError,
"variadic arguments must be type and value pairs: "
"%"PRIsVALUE,
rb_ary_new_from_values(argc, argv));
}
n_call_args = n_arg_types + ((argc - n_arg_types) / 2);
}
else {
if (argc != n_arg_types) {
rb_error_arity(argc, n_arg_types, n_arg_types);
}
n_call_args = n_arg_types;
}
Check_Max_Args("the number of arguments", n_call_args);
TypedData_Get_Struct(self, ffi_cif, &function_data_type, args.cif);
generic_args = ALLOCV(alloc_buffer,
(size_t)(argc + 1) * sizeof(void *) + (size_t)argc * sizeof(fiddle_generic));
args.values = (void **)((char *)generic_args +
(size_t)argc * sizeof(fiddle_generic));
for (i = 0; i < argc; i++) {
VALUE type = RARRAY_AREF(types, i);
VALUE src = argv[i];
int argtype = FIX2INT(type);
if (argtype == TYPE_VOIDP) {
if(NIL_P(src)) {
src = INT2FIX(0);
} else if(cPointer != CLASS_OF(src)) {
src = rb_funcall(cPointer, rb_intern("[]"), 1, src);
}
src = rb_Integer(src);
}
VALUE2GENERIC(argtype, src, &generic_args[i]);
args.values[i] = (void *)&generic_args[i];
if (is_variadic && args.cif->arg_types) {
xfree(args.cif->arg_types);
args.cif->arg_types = NULL;
}
args.values[argc] = NULL;
if (!args.cif->arg_types) {
VALUE fixed_arg_types = arg_types;
VALUE return_type;
int c_return_type;
ffi_type *ffi_return_type;
ffi_type **ffi_arg_types;
ffi_status result;
arg_types = rb_ary_dup(fixed_arg_types);
for (i = n_fixed_args; i < argc; i += 2) {
VALUE arg_type = argv[i];
int c_arg_type = NUM2INT(arg_type);
(void)INT2FFI_TYPE(c_arg_type); /* raise */
rb_ary_push(arg_types, INT2FIX(c_arg_type));
}
return_type = rb_iv_get(self, "@return_type");
c_return_type = FIX2INT(return_type);
ffi_return_type = INT2FFI_TYPE(c_return_type);
ffi_arg_types = xcalloc(n_call_args + 1, sizeof(ffi_type *));
for (i_call = 0; i_call < n_call_args; i_call++) {
VALUE arg_type;
int c_arg_type;
arg_type = RARRAY_AREF(arg_types, i_call);
c_arg_type = FIX2INT(arg_type);
ffi_arg_types[i_call] = INT2FFI_TYPE(c_arg_type);
}
ffi_arg_types[i_call] = NULL;
if (is_variadic) {
#ifdef HAVE_FFI_PREP_CIF_VAR
result = ffi_prep_cif_var(args.cif,
FIX2INT(abi),
n_fixed_args,
n_call_args,
ffi_return_type,
ffi_arg_types);
#else
/* This code is never used because ffi_prep_cif_var()
* availability check is done in #initialize. */
result = FFI_BAD_TYPEDEF;
#endif
}
else {
result = ffi_prep_cif(args.cif,
FIX2INT(abi),
n_call_args,
ffi_return_type,
ffi_arg_types);
}
if (result != FFI_OK) {
xfree(ffi_arg_types);
args.cif->arg_types = NULL;
rb_raise(rb_eRuntimeError, "error creating CIF %d", result);
}
}
generic_args = ALLOCV(alloc_buffer,
sizeof(fiddle_generic) * n_call_args +
sizeof(void *) * (n_call_args + 1));
args.values = (void **)((char *)generic_args +
sizeof(fiddle_generic) * n_call_args);
for (i = 0, i_call = 0;
i < argc && i_call < n_call_args;
i++, i_call++) {
VALUE arg_type;
int c_arg_type;
VALUE src;
arg_type = RARRAY_AREF(arg_types, i_call);
c_arg_type = FIX2INT(arg_type);
if (i >= n_fixed_args) {
i++;
}
src = argv[i];
if (c_arg_type == TYPE_VOIDP) {
if (NIL_P(src)) {
src = INT2FIX(0);
}
else if (cPointer != CLASS_OF(src)) {
src = rb_funcall(cPointer, rb_intern("[]"), 1, src);
}
src = rb_Integer(src);
}
VALUE2GENERIC(c_arg_type, src, &generic_args[i_call]);
args.values[i_call] = (void *)&generic_args[i_call];
}
args.values[i_call] = NULL;
args.fn = (void(*)(void))NUM2PTR(cfunc);
(void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0);

View file

@ -79,5 +79,36 @@ module Fiddle
EnvUtil.under_gc_stress {qsort.call(buff, buff.size, 1, cb)}
assert_equal("1349", buff, bug4929)
end
def test_snprintf
snprintf = Function.new(@libc["snprintf"],
[
TYPE_VOIDP,
TYPE_SIZE_T,
TYPE_VOIDP,
TYPE_VARIADIC,
],
TYPE_INT)
output_buffer = " " * 1024
output = Pointer[output_buffer]
written = snprintf.call(output,
output.size,
"int: %d, string: %.*s\n",
TYPE_INT, -29,
TYPE_INT, 4,
TYPE_VOIDP, "Hello")
assert_equal("int: -29, string: Hell\n",
output_buffer[0, written])
written = snprintf.call(output,
output.size,
"string: %.*s, uint: %u\n",
TYPE_INT, 2,
TYPE_VOIDP, "Hello",
TYPE_INT, 29)
assert_equal("string: He, uint: 29\n",
output_buffer[0, written])
end
end
end if defined?(Fiddle)

View file

@ -110,7 +110,7 @@ module Fiddle
assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG)
end
Fiddle.constants.grep(/\ATYPE_(?!VOID\z)(.*)/) do
Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(.*)/) do
type = $&
size = Fiddle.const_get("SIZEOF_#{$1}")
name = $1.sub(/P\z/,"*").gsub(/_(?!T\z)/, " ").downcase