summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorSutou Kouhei <kou@clear-code.com>2020-06-27 07:25:47 +0900
committerNobuyoshi Nakada <nobu@ruby-lang.org>2020-06-27 23:54:08 +0900
commitae18220f9904c70304bd1672eecadbb3618fc7e5 (patch)
tree26356cc33a9939c1059047cf411a45f3ec26f85d
parent9f740acaf960c0d8fa4b72050f057bc157db31ce (diff)
[ruby/fiddle] Add support for variadic arguments
GitHub: fix GH-39 Reported by kojix2. Thanks!!! https://github.com/ruby/fiddle/commit/6c4cb904dc
Notes
Notes: Merged: https://github.com/ruby/ruby/pull/3265
-rw-r--r--ext/fiddle/extconf.rb2
-rw-r--r--ext/fiddle/fiddle.c6
-rw-r--r--ext/fiddle/fiddle.h1
-rw-r--r--ext/fiddle/function.c251
-rw-r--r--test/fiddle/test_func.rb31
-rw-r--r--test/fiddle/test_import.rb2
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
@@ -88,62 +88,87 @@ parse_keyword_arg_i(VALUE key, VALUE value, VALUE self)
}
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