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!!!
6c4cb904dc
This commit is contained in:
parent
9f740acaf9
commit
ae18220f99
Notes:
git
2020-06-28 02:03:19 +09:00
6 changed files with 229 additions and 64 deletions
|
@ -127,6 +127,8 @@ else
|
||||||
have_func('ffi_closure_alloc', ffi_header)
|
have_func('ffi_closure_alloc', ffi_header)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
have_func('ffi_prep_cif_var', ffi_header)
|
||||||
|
|
||||||
have_header 'sys/mman.h'
|
have_header 'sys/mman.h'
|
||||||
|
|
||||||
if have_header "dlfcn.h"
|
if have_header "dlfcn.h"
|
||||||
|
|
|
@ -227,6 +227,12 @@ Init_fiddle(void)
|
||||||
*/
|
*/
|
||||||
rb_define_const(mFiddle, "TYPE_DOUBLE", INT2NUM(TYPE_DOUBLE));
|
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
|
/* Document-const: TYPE_SIZE_T
|
||||||
*
|
*
|
||||||
* C type - size_t
|
* C type - size_t
|
||||||
|
|
|
@ -115,6 +115,7 @@
|
||||||
#endif
|
#endif
|
||||||
#define TYPE_FLOAT 7
|
#define TYPE_FLOAT 7
|
||||||
#define TYPE_DOUBLE 8
|
#define TYPE_DOUBLE 8
|
||||||
|
#define TYPE_VARIADIC 9
|
||||||
|
|
||||||
#define ALIGN_OF(type) offsetof(struct {char align_c; type align_x;}, align_x)
|
#define ALIGN_OF(type) offsetof(struct {char align_c; type align_x;}, align_x)
|
||||||
|
|
||||||
|
|
|
@ -87,63 +87,88 @@ parse_keyword_arg_i(VALUE key, VALUE value, VALUE self)
|
||||||
return ST_CONTINUE;
|
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
|
static VALUE
|
||||||
initialize(int argc, VALUE argv[], VALUE self)
|
initialize(int argc, VALUE argv[], VALUE self)
|
||||||
{
|
{
|
||||||
ffi_cif * cif;
|
ffi_cif * cif;
|
||||||
ffi_type **arg_types, *rtype;
|
VALUE ptr, arg_types, ret_type, abi, kwds;
|
||||||
ffi_status result;
|
int c_ret_type;
|
||||||
VALUE ptr, args, ret_type, abi, kwds;
|
bool is_variadic = false;
|
||||||
int i, len;
|
ffi_abi c_ffi_abi;
|
||||||
int nabi;
|
|
||||||
void *cfunc;
|
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);
|
rb_iv_set(self, "@closure", ptr);
|
||||||
|
|
||||||
ptr = rb_Integer(ptr);
|
ptr = rb_Integer(ptr);
|
||||||
cfunc = NUM2PTR(ptr);
|
cfunc = NUM2PTR(ptr);
|
||||||
PTR2NUM(cfunc);
|
PTR2NUM(cfunc);
|
||||||
nabi = NIL_P(abi) ? FFI_DEFAULT_ABI : NUM2INT(abi);
|
c_ffi_abi = NIL_P(abi) ? FFI_DEFAULT_ABI : NUM2INT(abi);
|
||||||
abi = INT2FIX(nabi);
|
abi = INT2FIX(c_ffi_abi);
|
||||||
i = NUM2INT(ret_type);
|
c_ret_type = NUM2INT(ret_type);
|
||||||
rtype = INT2FFI_TYPE(i);
|
(void)INT2FFI_TYPE(c_ret_type); /* raise */
|
||||||
ret_type = INT2FIX(i);
|
ret_type = INT2FIX(c_ret_type);
|
||||||
|
|
||||||
Check_Type(args, T_ARRAY);
|
arg_types = normalize_argument_types("argument types",
|
||||||
len = RARRAY_LENINT(args);
|
arg_types,
|
||||||
Check_Max_Args("args", len);
|
&is_variadic);
|
||||||
/* freeze to prevent inconsistency at calling #to_int later */
|
#ifndef HAVE_FFI_PREP_CIF_VAR
|
||||||
args = rb_ary_subseq(args, 0, len);
|
if (is_variadic) {
|
||||||
for (i = 0; i < RARRAY_LEN(args); i++) {
|
rb_raise(rb_eNotImpError,
|
||||||
VALUE a = RARRAY_AREF(args, i);
|
"ffi_prep_cif_var() is required in libffi "
|
||||||
int type = NUM2INT(a);
|
"for variadic arguments");
|
||||||
(void)INT2FFI_TYPE(type); /* raise */
|
|
||||||
if (INT2FIX(type) != a) rb_ary_store(args, i, INT2FIX(type));
|
|
||||||
}
|
}
|
||||||
OBJ_FREEZE(args);
|
#endif
|
||||||
|
|
||||||
rb_iv_set(self, "@ptr", ptr);
|
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, "@return_type", ret_type);
|
||||||
rb_iv_set(self, "@abi", abi);
|
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);
|
if (!NIL_P(kwds)) rb_hash_foreach(kwds, parse_keyword_arg_i, self);
|
||||||
|
|
||||||
TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif);
|
TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif);
|
||||||
|
cif->arg_types = NULL;
|
||||||
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);
|
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
}
|
}
|
||||||
|
@ -170,44 +195,144 @@ function_call(int argc, VALUE argv[], VALUE self)
|
||||||
{
|
{
|
||||||
struct nogvl_ffi_call_args args = { 0 };
|
struct nogvl_ffi_call_args args = { 0 };
|
||||||
fiddle_generic *generic_args;
|
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;
|
||||||
|
int i_call;
|
||||||
VALUE alloc_buffer = 0;
|
VALUE alloc_buffer = 0;
|
||||||
|
|
||||||
cfunc = rb_iv_get(self, "@ptr");
|
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"));
|
cPointer = rb_const_get(mFiddle, rb_intern("Pointer"));
|
||||||
|
is_variadic = rb_iv_get(self, "@is_variadic");
|
||||||
|
|
||||||
Check_Max_Args("number of arguments", argc);
|
n_arg_types = RARRAY_LENINT(arg_types);
|
||||||
if (argc != (i = RARRAY_LENINT(types))) {
|
n_fixed_args = n_arg_types;
|
||||||
rb_error_arity(argc, i, i);
|
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);
|
TypedData_Get_Struct(self, ffi_cif, &function_data_type, args.cif);
|
||||||
|
|
||||||
|
if (is_variadic && args.cif->arg_types) {
|
||||||
|
xfree(args.cif->arg_types);
|
||||||
|
args.cif->arg_types = 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,
|
generic_args = ALLOCV(alloc_buffer,
|
||||||
(size_t)(argc + 1) * sizeof(void *) + (size_t)argc * sizeof(fiddle_generic));
|
sizeof(fiddle_generic) * n_call_args +
|
||||||
|
sizeof(void *) * (n_call_args + 1));
|
||||||
args.values = (void **)((char *)generic_args +
|
args.values = (void **)((char *)generic_args +
|
||||||
(size_t)argc * sizeof(fiddle_generic));
|
sizeof(fiddle_generic) * n_call_args);
|
||||||
|
|
||||||
for (i = 0; i < argc; i++) {
|
for (i = 0, i_call = 0;
|
||||||
VALUE type = RARRAY_AREF(types, i);
|
i < argc && i_call < n_call_args;
|
||||||
VALUE src = argv[i];
|
i++, i_call++) {
|
||||||
int argtype = FIX2INT(type);
|
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 (argtype == TYPE_VOIDP) {
|
if (c_arg_type == TYPE_VOIDP) {
|
||||||
if(NIL_P(src)) {
|
if (NIL_P(src)) {
|
||||||
src = INT2FIX(0);
|
src = INT2FIX(0);
|
||||||
} else if(cPointer != CLASS_OF(src)) {
|
}
|
||||||
|
else if (cPointer != CLASS_OF(src)) {
|
||||||
src = rb_funcall(cPointer, rb_intern("[]"), 1, src);
|
src = rb_funcall(cPointer, rb_intern("[]"), 1, src);
|
||||||
}
|
}
|
||||||
src = rb_Integer(src);
|
src = rb_Integer(src);
|
||||||
}
|
}
|
||||||
|
|
||||||
VALUE2GENERIC(argtype, src, &generic_args[i]);
|
VALUE2GENERIC(c_arg_type, src, &generic_args[i_call]);
|
||||||
args.values[i] = (void *)&generic_args[i];
|
args.values[i_call] = (void *)&generic_args[i_call];
|
||||||
}
|
}
|
||||||
args.values[argc] = NULL;
|
args.values[i_call] = NULL;
|
||||||
args.fn = (void(*)(void))NUM2PTR(cfunc);
|
args.fn = (void(*)(void))NUM2PTR(cfunc);
|
||||||
|
|
||||||
(void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0);
|
(void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0);
|
||||||
|
|
|
@ -79,5 +79,36 @@ module Fiddle
|
||||||
EnvUtil.under_gc_stress {qsort.call(buff, buff.size, 1, cb)}
|
EnvUtil.under_gc_stress {qsort.call(buff, buff.size, 1, cb)}
|
||||||
assert_equal("1349", buff, bug4929)
|
assert_equal("1349", buff, bug4929)
|
||||||
end
|
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
|
||||||
end if defined?(Fiddle)
|
end if defined?(Fiddle)
|
||||||
|
|
|
@ -110,7 +110,7 @@ module Fiddle
|
||||||
assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG)
|
assert_equal(SIZEOF_LONG_LONG, LIBC.sizeof("long long")) if defined?(SIZEOF_LONG_LONG)
|
||||||
end
|
end
|
||||||
|
|
||||||
Fiddle.constants.grep(/\ATYPE_(?!VOID\z)(.*)/) do
|
Fiddle.constants.grep(/\ATYPE_(?!VOID|VARIADIC\z)(.*)/) do
|
||||||
type = $&
|
type = $&
|
||||||
size = Fiddle.const_get("SIZEOF_#{$1}")
|
size = Fiddle.const_get("SIZEOF_#{$1}")
|
||||||
name = $1.sub(/P\z/,"*").gsub(/_(?!T\z)/, " ").downcase
|
name = $1.sub(/P\z/,"*").gsub(/_(?!T\z)/, " ").downcase
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue