From 307388ea19f5c9d1c8c417d1979c40d970b420a2 Mon Sep 17 00:00:00 2001 From: Aaron Patterson Date: Sun, 2 Aug 2020 14:26:40 -0700 Subject: [ruby/fiddle] Add a "pinning" reference (#44) * Add a "pinning" reference A `Fiddle::Pinned` objects will prevent the objects they point to from moving. This is useful in the case where you need to pass a reference to a C extension that keeps the address in a global and needs the address to be stable. For example: ```ruby class Foo A = "hi" # this is an embedded string some_c_function A # A might move! end ``` If `A` moves, then the underlying string buffer may also move. `Fiddle::Pinned` will prevent the object from moving: ```ruby class Foo A = "hi" # this is an embedded string A_pinner = Fiddle::Pinned.new(A) # :nodoc: some_c_function A # A can't move because of `Fiddle::Pinned` end ``` This is a similar strategy to what Graal uses: https://www.graalvm.org/sdk/javadoc/org/graalvm/nativeimage/PinnedObject.html#getObject-- * rename global to match exception name * Introduce generic Fiddle::Error and rearrange error classes Fiddle::Error is the generic exception base class for Fiddle exceptions. This commit introduces the class and rearranges Fiddle exceptions to inherit from it. https://github.com/ruby/fiddle/commit/ac52d00223 --- ext/fiddle/fiddle.c | 12 ++++- ext/fiddle/fiddle.gemspec | 1 + ext/fiddle/fiddle.h | 2 +- ext/fiddle/handle.c | 14 +++--- ext/fiddle/pinned.c | 123 ++++++++++++++++++++++++++++++++++++++++++++++ ext/fiddle/pointer.c | 6 +-- 6 files changed, 146 insertions(+), 12 deletions(-) create mode 100644 ext/fiddle/pinned.c (limited to 'ext') diff --git a/ext/fiddle/fiddle.c b/ext/fiddle/fiddle.c index e6435ba3f3..5f2bd5ae20 100644 --- a/ext/fiddle/fiddle.c +++ b/ext/fiddle/fiddle.c @@ -1,9 +1,11 @@ #include VALUE mFiddle; +VALUE rb_eFiddleDLError; VALUE rb_eFiddleError; void Init_fiddle_pointer(void); +void Init_fiddle_pinned(void); /* * call-seq: Fiddle.malloc(size) @@ -132,12 +134,19 @@ Init_fiddle(void) */ mFiddle = rb_define_module("Fiddle"); + /* + * Document-class: Fiddle::Error + * + * Generic error class for Fiddle + */ + rb_eFiddleError = rb_define_class_under(mFiddle, "Error", rb_eStandardError); + /* * Document-class: Fiddle::DLError * * standard dynamic load exception */ - rb_eFiddleError = rb_define_class_under(mFiddle, "DLError", rb_eStandardError); + rb_eFiddleDLError = rb_define_class_under(mFiddle, "DLError", rb_eFiddleError); /* Document-const: TYPE_VOID * @@ -439,5 +448,6 @@ Init_fiddle(void) Init_fiddle_closure(); Init_fiddle_handle(); Init_fiddle_pointer(); + Init_fiddle_pinned(); } /* vim: set noet sws=4 sw=4: */ diff --git a/ext/fiddle/fiddle.gemspec b/ext/fiddle/fiddle.gemspec index a92955ccec..850b12d0b8 100644 --- a/ext/fiddle/fiddle.gemspec +++ b/ext/fiddle/fiddle.gemspec @@ -39,6 +39,7 @@ Gem::Specification.new do |spec| "ext/fiddle/function.c", "ext/fiddle/function.h", "ext/fiddle/handle.c", + "ext/fiddle/pinned.c", "ext/fiddle/pointer.c", "ext/fiddle/win32/fficonfig.h", "ext/fiddle/win32/libffi-3.2.1-mswin.patch", diff --git a/ext/fiddle/fiddle.h b/ext/fiddle/fiddle.h index 16f12cab74..46f5a1d737 100644 --- a/ext/fiddle/fiddle.h +++ b/ext/fiddle/fiddle.h @@ -164,7 +164,7 @@ #define ALIGN_DOUBLE ALIGN_OF(double) extern VALUE mFiddle; -extern VALUE rb_eFiddleError; +extern VALUE rb_eFiddleDLError; VALUE rb_fiddle_new_function(VALUE address, VALUE arg_types, VALUE ret_type); diff --git a/ext/fiddle/handle.c b/ext/fiddle/handle.c index 700924afb5..c1b2db557a 100644 --- a/ext/fiddle/handle.c +++ b/ext/fiddle/handle.c @@ -74,14 +74,14 @@ rb_fiddle_handle_close(VALUE self) /* Check dlclose for successful return value */ if(ret) { #if defined(HAVE_DLERROR) - rb_raise(rb_eFiddleError, "%s", dlerror()); + rb_raise(rb_eFiddleDLError, "%s", dlerror()); #else - rb_raise(rb_eFiddleError, "could not close handle"); + rb_raise(rb_eFiddleDLError, "could not close handle"); #endif } return INT2NUM(ret); } - rb_raise(rb_eFiddleError, "dlclose() called too many times"); + rb_raise(rb_eFiddleDLError, "dlclose() called too many times"); UNREACHABLE; } @@ -177,12 +177,12 @@ rb_fiddle_handle_initialize(int argc, VALUE argv[], VALUE self) ptr = dlopen(clib, cflag); #if defined(HAVE_DLERROR) if( !ptr && (err = dlerror()) ){ - rb_raise(rb_eFiddleError, "%s", err); + rb_raise(rb_eFiddleDLError, "%s", err); } #else if( !ptr ){ err = dlerror(); - rb_raise(rb_eFiddleError, "%s", err); + rb_raise(rb_eFiddleDLError, "%s", err); } #endif TypedData_Get_Struct(self, struct dl_handle, &fiddle_handle_data_type, fiddle_handle); @@ -278,7 +278,7 @@ rb_fiddle_handle_sym(VALUE self, VALUE sym) TypedData_Get_Struct(self, struct dl_handle, &fiddle_handle_data_type, fiddle_handle); if( ! fiddle_handle->open ){ - rb_raise(rb_eFiddleError, "closed handle"); + rb_raise(rb_eFiddleDLError, "closed handle"); } return fiddle_handle_sym(fiddle_handle->ptr, sym); @@ -366,7 +366,7 @@ fiddle_handle_sym(void *handle, VALUE symbol) } #endif if( !func ){ - rb_raise(rb_eFiddleError, "unknown symbol \"%"PRIsVALUE"\"", symbol); + rb_raise(rb_eFiddleDLError, "unknown symbol \"%"PRIsVALUE"\"", symbol); } return PTR2NUM(func); diff --git a/ext/fiddle/pinned.c b/ext/fiddle/pinned.c new file mode 100644 index 0000000000..019a3020e2 --- /dev/null +++ b/ext/fiddle/pinned.c @@ -0,0 +1,123 @@ +#include + +VALUE rb_cPinned; +VALUE rb_eFiddleClearedReferenceError; + +struct pinned_data { + VALUE ptr; +}; + +static void +pinned_mark(void *ptr) +{ + struct pinned_data *data = (struct pinned_data*)ptr; + /* Ensure reference is pinned */ + if (data->ptr) { + rb_gc_mark(data->ptr); + } +} + +static size_t +pinned_memsize(const void *ptr) +{ + return sizeof(struct pinned_data); +} + +static const rb_data_type_t pinned_data_type = { + "fiddle/pinned", + {pinned_mark, xfree, pinned_memsize, }, + 0, 0, RUBY_TYPED_FREE_IMMEDIATELY | RUBY_TYPED_WB_PROTECTED +}; + +static VALUE +allocate(VALUE klass) +{ + struct pinned_data *data; + VALUE obj = TypedData_Make_Struct(klass, struct pinned_data, &pinned_data_type, data); + data->ptr = 0; + return obj; +} + +/* + * call-seq: + * Fiddle::Pinned.new(object) => pinned_object + * + * Create a new pinned object reference. The Fiddle::Pinned instance will + * prevent the GC from moving +object+. + */ +static VALUE +initialize(VALUE self, VALUE ref) +{ + struct pinned_data *data; + TypedData_Get_Struct(self, struct pinned_data, &pinned_data_type, data); + RB_OBJ_WRITE(self, &data->ptr, ref); + return self; +} + +/* + * call-seq: ref + * + * Return the object that this pinned instance references. + */ +static VALUE +ref(VALUE self) +{ + struct pinned_data *data; + TypedData_Get_Struct(self, struct pinned_data, &pinned_data_type, data); + if (data->ptr) { + return data->ptr; + } else { + rb_raise(rb_eFiddleClearedReferenceError, "`ref` called on a cleared object"); + } +} + +/* + * call-seq: clear + * + * Clear the reference to the object this is pinning. + */ +static VALUE +clear(VALUE self) +{ + struct pinned_data *data; + TypedData_Get_Struct(self, struct pinned_data, &pinned_data_type, data); + data->ptr = 0; + return self; +} + +/* + * call-seq: cleared? + * + * Returns true if the reference has been cleared, otherwise returns false. + */ +static VALUE +cleared_p(VALUE self) +{ + struct pinned_data *data; + TypedData_Get_Struct(self, struct pinned_data, &pinned_data_type, data); + if (data->ptr) { + return Qfalse; + } else { + return Qtrue; + } +} + +extern VALUE rb_eFiddleError; + +void +Init_fiddle_pinned(void) +{ + rb_cPinned = rb_define_class_under(mFiddle, "Pinned", rb_cObject); + rb_define_alloc_func(rb_cPinned, allocate); + rb_define_method(rb_cPinned, "initialize", initialize, 1); + rb_define_method(rb_cPinned, "ref", ref, 0); + rb_define_method(rb_cPinned, "clear", clear, 0); + rb_define_method(rb_cPinned, "cleared?", cleared_p, 0); + + /* + * Document-class: Fiddle::ClearedReferenceError + * + * Cleared reference exception + */ + rb_eFiddleClearedReferenceError = rb_define_class_under(mFiddle, "ClearedReferenceError", rb_eFiddleError); +} diff --git a/ext/fiddle/pointer.c b/ext/fiddle/pointer.c index 7c60da477a..b531befd6e 100644 --- a/ext/fiddle/pointer.c +++ b/ext/fiddle/pointer.c @@ -561,7 +561,7 @@ rb_fiddle_ptr_aref(int argc, VALUE argv[], VALUE self) struct ptr_data *data; TypedData_Get_Struct(self, struct ptr_data, &fiddle_ptr_data_type, data); - if (!data->ptr) rb_raise(rb_eFiddleError, "NULL pointer dereference"); + if (!data->ptr) rb_raise(rb_eFiddleDLError, "NULL pointer dereference"); switch( rb_scan_args(argc, argv, "11", &arg0, &arg1) ){ case 1: offset = NUM2ULONG(arg0); @@ -599,7 +599,7 @@ rb_fiddle_ptr_aset(int argc, VALUE argv[], VALUE self) struct ptr_data *data; TypedData_Get_Struct(self, struct ptr_data, &fiddle_ptr_data_type, data); - if (!data->ptr) rb_raise(rb_eFiddleError, "NULL pointer dereference"); + if (!data->ptr) rb_raise(rb_eFiddleDLError, "NULL pointer dereference"); switch( rb_scan_args(argc, argv, "21", &arg0, &arg1, &arg2) ){ case 2: offset = NUM2ULONG(arg0); @@ -680,7 +680,7 @@ rb_fiddle_ptr_s_to_ptr(VALUE self, VALUE val) wrap = 0; } else{ - rb_raise(rb_eFiddleError, "to_ptr should return a Fiddle::Pointer object"); + rb_raise(rb_eFiddleDLError, "to_ptr should return a Fiddle::Pointer object"); } } else{ -- cgit v1.2.3