diff options
Diffstat (limited to 'ext/fiddle/function.c')
| -rw-r--r-- | ext/fiddle/function.c | 399 |
1 files changed, 313 insertions, 86 deletions
diff --git a/ext/fiddle/function.c b/ext/fiddle/function.c index 89239bbb69..274d181d17 100644 --- a/ext/fiddle/function.c +++ b/ext/fiddle/function.c @@ -1,4 +1,7 @@ #include <fiddle.h> +#include <ruby/thread.h> + +#include <stdbool.h> #ifdef PRIsVALUE # define RB_OBJ_CLASSNAME(obj) rb_obj_class(obj) @@ -11,12 +14,28 @@ VALUE cFiddleFunction; +#define MAX_ARGS (SIZE_MAX / (sizeof(void *) + sizeof(fiddle_generic)) - 1) + +#define Check_Max_Args(name, len) \ + Check_Max_Args_(name, len, "") +#define Check_Max_Args_Long(name, len) \ + Check_Max_Args_(name, len, "l") +#define Check_Max_Args_(name, len, fmt) \ + do { \ + if ((size_t)(len) >= MAX_ARGS) { \ + rb_raise(rb_eTypeError, \ + "%s is so large " \ + "that it can cause integer overflow (%"fmt"d)", \ + (name), (len)); \ + } \ + } while (0) + static void deallocate(void *p) { - ffi_cif *ptr = p; - if (ptr->arg_types) xfree(ptr->arg_types); - xfree(ptr); + ffi_cif *cif = p; + if (cif->arg_types) xfree(cif->arg_types); + xfree(cif); } static size_t @@ -25,12 +44,11 @@ function_memsize(const void *p) /* const */ffi_cif *ptr = (ffi_cif *)p; size_t size = 0; - if (ptr) { - size += sizeof(*ptr); + size += sizeof(*ptr); #if !defined(FFI_NO_RAW_API) || !FFI_NO_RAW_API - size += ffi_raw_size(ptr); + size += ffi_raw_size(ptr); #endif - } + return size; } @@ -59,124 +77,320 @@ rb_fiddle_new_function(VALUE address, VALUE arg_types, VALUE ret_type) return rb_class_new_instance(3, argv, cFiddleFunction); } -static int -parse_keyword_arg_i(VALUE key, VALUE value, VALUE self) +static VALUE +normalize_argument_types(const char *name, + VALUE arg_types, + bool *is_variadic) { - if (key == ID2SYM(rb_intern("name"))) { - rb_iv_set(self, "@name", value); - } else { - rb_raise(rb_eArgError, "unknown keyword: %"PRIsVALUE, - RB_OBJ_STRING(key)); + 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; + arg_type = rb_fiddle_type_ensure(arg_type); + 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)); } - return ST_CONTINUE; + + /* 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; - ffi_status result; - VALUE ptr, args, ret_type, abi, kwds; - int i; - - rb_scan_args(argc, argv, "31:", &ptr, &args, &ret_type, &abi, &kwds); - if(NIL_P(abi)) abi = INT2NUM(FFI_DEFAULT_ABI); - - Check_Type(args, T_ARRAY); + VALUE ptr, arg_types, ret_type, abi, kwargs; + VALUE name = Qnil; + VALUE need_gvl = Qfalse; + int c_ret_type; + bool is_variadic = false; + ffi_abi c_ffi_abi; + void *cfunc; + + rb_scan_args(argc, argv, "31:", &ptr, &arg_types, &ret_type, &abi, &kwargs); + rb_iv_set(self, "@closure", ptr); + + if (!NIL_P(kwargs)) { + enum { + kw_name, + kw_need_gvl, + kw_max_, + }; + static ID kw[kw_max_]; + VALUE args[kw_max_]; + if (!kw[0]) { + kw[kw_name] = rb_intern_const("name"); + kw[kw_need_gvl] = rb_intern_const("need_gvl"); + } + rb_get_kwargs(kwargs, kw, 0, kw_max_, args); + if (args[kw_name] != Qundef) { + name = args[kw_name]; + } + if (args[kw_need_gvl] != Qundef) { + need_gvl = args[kw_need_gvl]; + } + } + rb_iv_set(self, "@name", name); + rb_iv_set(self, "@need_gvl", need_gvl); + + ptr = rb_Integer(ptr); + cfunc = NUM2PTR(ptr); + PTR2NUM(cfunc); + c_ffi_abi = NIL_P(abi) ? FFI_DEFAULT_ABI : NUM2INT(abi); + abi = INT2FIX(c_ffi_abi); + ret_type = rb_fiddle_type_ensure(ret_type); + 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"); + } +#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); - - if (!NIL_P(kwds)) rb_hash_foreach(kwds, parse_keyword_arg_i, self); + rb_iv_set(self, "@is_variadic", is_variadic ? Qtrue : Qfalse); TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif); + cif->arg_types = NULL; - arg_types = xcalloc(RARRAY_LEN(args) + 1, sizeof(ffi_type *)); + return self; +} - for (i = 0; i < RARRAY_LEN(args); i++) { - int type = NUM2INT(RARRAY_PTR(args)[i]); - arg_types[i] = INT2FFI_TYPE(type); - } - arg_types[RARRAY_LEN(args)] = NULL; +struct nogvl_ffi_call_args { + ffi_cif *cif; + void (*fn)(void); + void **values; + fiddle_generic retval; +}; - result = ffi_prep_cif ( - cif, - NUM2INT(abi), - RARRAY_LENINT(args), - INT2FFI_TYPE(NUM2INT(ret_type)), - arg_types); +static void * +nogvl_ffi_call(void *ptr) +{ + struct nogvl_ffi_call_args *args = ptr; - if (result) - rb_raise(rb_eRuntimeError, "error creating CIF %d", result); + ffi_call(args->cif, args->fn, &args->retval, args->values); - return self; + return NULL; } static VALUE function_call(int argc, VALUE argv[], VALUE self) { - ffi_cif * cif; - fiddle_generic retval; + struct nogvl_ffi_call_args args = { 0 }; fiddle_generic *generic_args; - void **values; - VALUE cfunc, types, cPointer; + VALUE cfunc; + VALUE abi; + VALUE arg_types; + VALUE cPointer; + VALUE is_variadic; + VALUE need_gvl; + int n_arg_types; + int n_fixed_args = 0; + int n_call_args = 0; int i; + int i_call; + VALUE converted_args = Qnil; + 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")); - - if(argc != RARRAY_LENINT(types)) { - rb_raise(rb_eArgError, "wrong number of arguments (%d for %d)", - argc, RARRAY_LENINT(types)); + is_variadic = rb_iv_get(self, "@is_variadic"); + need_gvl = rb_iv_get(self, "@need_gvl"); + + 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); } - - TypedData_Get_Struct(self, ffi_cif, &function_data_type, cif); - - if (rb_safe_level() >= 1) { - for (i = 0; i < argc; i++) { - VALUE src = argv[i]; - if (OBJ_TAINTED(src)) { - rb_raise(rb_eSecurityError, "tainted parameter not allowed"); - } - } + 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); - values = xcalloc((size_t)argc + 1, (size_t)sizeof(void *)); - generic_args = xcalloc((size_t)argc, (size_t)sizeof(fiddle_generic)); + TypedData_Get_Struct(self, ffi_cif, &function_data_type, args.cif); - for (i = 0; i < argc; i++) { - VALUE type = RARRAY_PTR(types)[i]; - VALUE src = argv[i]; + if (is_variadic && args.cif->arg_types) { + xfree(args.cif->arg_types); + args.cif->arg_types = NULL; + } - if(NUM2INT(type) == TYPE_VOIDP) { - if(NIL_P(src)) { - src = INT2NUM(0); - } else if(cPointer != CLASS_OF(src)) { - src = rb_funcall(cPointer, rb_intern("[]"), 1, src); - } - src = rb_Integer(src); - } + 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; + arg_type = rb_fiddle_type_ensure(arg_type); + 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); + } + } - VALUE2GENERIC(NUM2INT(type), src, &generic_args[i]); - values[i] = (void *)&generic_args[i]; + 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 original_src; + 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); + if (NIL_P(converted_args)) { + converted_args = rb_ary_new(); + } + rb_ary_push(converted_args, src); + } + src = rb_Integer(src); + } + + original_src = src; + VALUE2GENERIC(c_arg_type, src, &generic_args[i_call]); + if (src != original_src) { + if (NIL_P(converted_args)) { + converted_args = rb_ary_new(); + } + rb_ary_push(converted_args, src); + } + args.values[i_call] = (void *)&generic_args[i_call]; } - values[argc] = NULL; + args.values[i_call] = NULL; + args.fn = (void(*)(void))NUM2PTR(cfunc); - ffi_call(cif, NUM2PTR(rb_Integer(cfunc)), &retval, values); + if (RTEST(need_gvl)) { + ffi_call(args.cif, args.fn, &(args.retval), args.values); + } + else { + (void)rb_thread_call_without_gvl(nogvl_ffi_call, &args, 0, 0); + } - rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno)); + { + int errno_keep = errno; #if defined(_WIN32) - rb_funcall(mFiddle, rb_intern("win32_last_error="), 1, INT2NUM(errno)); + DWORD error = WSAGetLastError(); + int socket_error = WSAGetLastError(); + rb_funcall(mFiddle, rb_intern("win32_last_error="), 1, + ULONG2NUM(error)); + rb_funcall(mFiddle, rb_intern("win32_last_socket_error="), 1, + INT2NUM(socket_error)); #endif + rb_funcall(mFiddle, rb_intern("last_error="), 1, INT2NUM(errno_keep)); + } - xfree(values); - xfree(generic_args); + ALLOCV_END(alloc_buffer); - return GENERIC2VALUE(rb_iv_get(self, "@return_type"), retval); + return GENERIC2VALUE(rb_iv_get(self, "@return_type"), args.retval); } void @@ -209,7 +423,7 @@ Init_fiddle_function(void) * * === ABI check * - * @libc = DL.dlopen "/lib/libc.so.6" + * @libc = Fiddle.dlopen "/lib/libc.so.6" * #=> #<Fiddle::Handle:0x00000001d7a8d8> * f = Fiddle::Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP) * #=> #<Fiddle::Function:0x00000001d8ee00> @@ -241,7 +455,13 @@ Init_fiddle_function(void) /* * Document-method: call * - * Calls the constructed Function, with +args+ + * Calls the constructed Function, with +args+. + * Caller must ensure the underlying function is called in a + * thread-safe manner if running in a multi-threaded process. + * + * Note that it is not thread-safe to use this method to + * directly or indirectly call many Ruby C-extension APIs unless + * you don't pass +need_gvl: true+ to Fiddle::Function#new. * * For an example see Fiddle::Function * @@ -250,13 +470,20 @@ Init_fiddle_function(void) /* * Document-method: new - * call-seq: new(ptr, args, ret_type, abi = DEFAULT) + * call-seq: new(ptr, + * args, + * ret_type, + * abi = DEFAULT, + * name: nil, + * need_gvl: false) * * Constructs a Function object. * * +ptr+ is a referenced function, of a Fiddle::Handle * * +args+ is an Array of arguments, passed to the +ptr+ function * * +ret_type+ is the return type of the function * * +abi+ is the ABI of the function + * * +name+ is the name of the function + * * +need_gvl+ is whether GVL is needed to call the function * */ rb_define_method(cFiddleFunction, "initialize", initialize, -1); |
