From ae18220f9904c70304bd1672eecadbb3618fc7e5 Mon Sep 17 00:00:00 2001 From: Sutou Kouhei Date: Sat, 27 Jun 2020 07:25:47 +0900 Subject: [ruby/fiddle] Add support for variadic arguments GitHub: fix GH-39 Reported by kojix2. Thanks!!! https://github.com/ruby/fiddle/commit/6c4cb904dc --- ext/fiddle/extconf.rb | 2 + ext/fiddle/fiddle.c | 6 ++ ext/fiddle/fiddle.h | 1 + ext/fiddle/function.c | 251 +++++++++++++++++++++++++++++++++------------ test/fiddle/test_func.rb | 31 ++++++ test/fiddle/test_import.rb | 2 +- 6 files changed, 229 insertions(+), 64 deletions(-) diff --git a/ext/fiddle/extconf.rb b/ext/fiddle/extconf.rb index f8a94d41e7..1ec9c6b15a 100644 --- a/ext/fiddle/extconf.rb +++ b/ext/fiddle/extconf.rb @@ -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" diff --git a/ext/fiddle/fiddle.c b/ext/fiddle/fiddle.c index bb6b1070d3..96356311c7 100644 --- a/ext/fiddle/fiddle.c +++ b/ext/fiddle/fiddle.c @@ -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 diff --git a/ext/fiddle/fiddle.h b/ext/fiddle/fiddle.h index d2583c1cbf..2dddc0b18b 100644 --- a/ext/fiddle/fiddle.h +++ b/ext/fiddle/fiddle.h @@ -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) diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c index bd6c9bd60d..ddb49deb1e 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -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); - - 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)); + 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); + + 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")); - - Check_Max_Args("number of arguments", argc); - if (argc != (i = RARRAY_LENINT(types))) { - rb_error_arity(argc, i, i); + is_variadic = rb_iv_get(self, "@is_variadic"); + + 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); + 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, - (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 + - (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]; + 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[argc] = NULL; + args.values[i_call] = NULL; args.fn = (void(*)(void))NUM2PTR(cfunc); (void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0); diff --git a/test/fiddle/test_func.rb b/test/fiddle/test_func.rb index ca89173766..18052fffdd 100644 --- a/test/fiddle/test_func.rb +++ b/test/fiddle/test_func.rb @@ -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) diff --git a/test/fiddle/test_import.rb b/test/fiddle/test_import.rb index fce8b864d9..c56d81c351 100644 --- a/test/fiddle/test_import.rb +++ b/test/fiddle/test_import.rb @@ -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 -- cgit v1.2.3