mirror of
				https://github.com/ruby/ruby.git
				synced 2022-11-09 12:17:21 -05:00 
			
		
		
		
	[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.
ac52d00223
			
			
This commit is contained in:
		
							parent
							
								
									e2dfc0c26b
								
							
						
					
					
						commit
						307388ea19
					
				
				
				Notes:
				
					git
				
				2020-11-18 09:05:46 +09:00 
				
			
			
			
		
		
					 7 changed files with 173 additions and 12 deletions
				
			
		| 
						 | 
				
			
			@ -1,9 +1,11 @@
 | 
			
		|||
#include <fiddle.h>
 | 
			
		||||
 | 
			
		||||
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: */
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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",
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -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);
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										123
									
								
								ext/fiddle/pinned.c
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										123
									
								
								ext/fiddle/pinned.c
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,123 @@
 | 
			
		|||
#include <fiddle.h>
 | 
			
		||||
 | 
			
		||||
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);
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -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{
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										27
									
								
								test/fiddle/test_pinned.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								test/fiddle/test_pinned.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,27 @@
 | 
			
		|||
# frozen_string_literal: true
 | 
			
		||||
begin
 | 
			
		||||
  require_relative 'helper'
 | 
			
		||||
rescue LoadError
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
module Fiddle
 | 
			
		||||
  class TestPinned < Fiddle::TestCase
 | 
			
		||||
    def test_pin_object
 | 
			
		||||
      x = Object.new
 | 
			
		||||
      pinner = Pinned.new x
 | 
			
		||||
      assert_same x, pinner.ref
 | 
			
		||||
    end
 | 
			
		||||
 | 
			
		||||
    def test_clear
 | 
			
		||||
      pinner = Pinned.new Object.new
 | 
			
		||||
      refute pinner.cleared?
 | 
			
		||||
      pinner.clear
 | 
			
		||||
      assert pinner.cleared?
 | 
			
		||||
      ex = assert_raise(Fiddle::ClearedReferenceError) do
 | 
			
		||||
        pinner.ref
 | 
			
		||||
      end
 | 
			
		||||
      assert_match "called on", ex.message
 | 
			
		||||
    end
 | 
			
		||||
  end
 | 
			
		||||
end
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue