Import JRuby implementation (#147)

Fix GH-104

lib/fiddle/jruby.rb is based on

https://github.com/jruby/jruby/blob/master/lib/ruby/stdlib/fiddle/jruby.rb
.

Here are changes for it:

* Move `Fiddle::TYPE_*` to `Fiddle::Types::*`
* Add `Fiddle::Types::VARIADIC`
* Add `Fiddle::Types::CONST_STRING`
* Add `Fiddle::Types::BOOL`
* Add `Fiddle::Types::INT8_T`
* Add `Fiddle::Types::UINT8_T`
* Add `Fiddle::Types::INT16_T`
* Add `Fiddle::Types::UINT16_T`
* Add `Fiddle::Types::INT32_T`
* Add `Fiddle::Types::UINT32_T`
* Add `Fiddle::Types::INT64_T`
* Add `Fiddle::Types::UINT64_T`
* Add more `Fiddle::ALIGN_*` for the above new `Fiddle::Types::*`
* Add more `Fiddle::SIZEOF_*` for the above new `Fiddle::Types::*`
* Add support for specifying type as not only `Fiddle::Types::*` but
also `Symbol` like `:int`
* Add support for variable size arguments in `Fiddle::Function`
* Add `Fiddle::Closure#free`
* Add `Fiddle::Closure#freed?`
* Add `Fiddle::Error` as base the error class
* Add `Fiddle::Pointer#call_free` and stop using `FFI::AutoPointer` in
`Fiddle::Pointer`
* Add support for `Fiddle::Pointer.malloc {}` `Fiddle::Pointer`
* Add support for `Fiddle::Pointer#free=`
* Add `Fiddle::Pointer#freed?`
* Use binary string not C string for `Fiddle::Pointer#[]`
* Add `Fiddle::Handle.sym_defined?`
* Add `Fiddle::Handle#sym_defined?`
* Add `Fiddle::Handle::DEFAULT`
* Add `Fiddle::ClearedReferenceError`
* Add no-op `Fiddle::Pinned`

Some features are still "not implemented". So there are some "omit"s for
JRuby in tests.
This commit is contained in:
Sutou Kouhei 2024-10-07 11:03:10 +09:00 committed by Hiroshi SHIBATA
parent a392ee1437
commit a47f153d9d
No known key found for this signature in database
GPG Key ID: F9CF13417264FAC2
Notes: git 2024-10-10 01:54:48 +00:00
15 changed files with 835 additions and 70 deletions

View File

@ -1,6 +1,11 @@
# frozen_string_literal: true
require 'mkmf'
if RUBY_ENGINE == "jruby"
File.write('Makefile', dummy_makefile("").join)
return
end
# :stopdoc:
def gcc?

View File

@ -40,7 +40,9 @@ Gem::Specification.new do |spec|
"lib/fiddle/cparser.rb",
"lib/fiddle/function.rb",
"lib/fiddle/import.rb",
"lib/fiddle/jruby.rb",
"lib/fiddle/pack.rb",
"lib/fiddle/ruby.rb",
"lib/fiddle/struct.rb",
"lib/fiddle/types.rb",
"lib/fiddle/value.rb",

View File

@ -1,6 +1,6 @@
# frozen_string_literal: true
require 'fiddle.so'
require "fiddle/#{RUBY_ENGINE}"
require 'fiddle/closure'
require 'fiddle/function'
require 'fiddle/version'
@ -10,36 +10,63 @@ module Fiddle
# Returns the last win32 +Error+ of the current executing +Thread+ or nil
# if none
def self.win32_last_error
Thread.current[:__FIDDLE_WIN32_LAST_ERROR__]
if RUBY_ENGINE == 'jruby'
errno = FFI.errno
errno == 0 ? nil : errno
else
Thread.current[:__FIDDLE_WIN32_LAST_ERROR__]
end
end
# Sets the last win32 +Error+ of the current executing +Thread+ to +error+
def self.win32_last_error= error
Thread.current[:__FIDDLE_WIN32_LAST_ERROR__] = error
if RUBY_ENGINE == 'jruby'
FFI.errno = error || 0
else
Thread.current[:__FIDDLE_WIN32_LAST_ERROR__] = error
end
end
# Returns the last win32 socket +Error+ of the current executing
# +Thread+ or nil if none
def self.win32_last_socket_error
Thread.current[:__FIDDLE_WIN32_LAST_SOCKET_ERROR__]
if RUBY_ENGINE == 'jruby'
errno = FFI.errno
errno == 0 ? nil : errno
else
Thread.current[:__FIDDLE_WIN32_LAST_SOCKET_ERROR__]
end
end
# Sets the last win32 socket +Error+ of the current executing
# +Thread+ to +error+
def self.win32_last_socket_error= error
Thread.current[:__FIDDLE_WIN32_LAST_SOCKET_ERROR__] = error
if RUBY_ENGINE == 'jruby'
FFI.errno = error || 0
else
Thread.current[:__FIDDLE_WIN32_LAST_SOCKET_ERROR__] = error
end
end
end
# Returns the last +Error+ of the current executing +Thread+ or nil if none
def self.last_error
Thread.current[:__FIDDLE_LAST_ERROR__]
if RUBY_ENGINE == 'jruby'
errno = FFI.errno
errno == 0 ? nil : errno
else
Thread.current[:__FIDDLE_LAST_ERROR__]
end
end
# Sets the last +Error+ of the current executing +Thread+ to +error+
def self.last_error= error
Thread.current[:__DL2_LAST_ERROR__] = error
Thread.current[:__FIDDLE_LAST_ERROR__] = error
if RUBY_ENGINE == 'jruby'
FFI.errno = error || 0
else
Thread.current[:__DL2_LAST_ERROR__] = error
Thread.current[:__FIDDLE_LAST_ERROR__] = error
end
end
# call-seq: dlopen(library) => Fiddle::Handle

View File

@ -0,0 +1,600 @@
# This is part of JRuby's FFI-based fiddle implementation.
require 'ffi'
module Fiddle
def self.malloc(size)
Fiddle::Pointer.malloc(size)
end
def self.free(ptr)
Fiddle::Pointer::LibC::FREE.call(ptr)
nil
end
def self.dlwrap(val)
Pointer.to_ptr(val)
end
module Types
VOID = 0
VOIDP = 1
CHAR = 2
UCHAR = -CHAR
SHORT = 3
USHORT = -SHORT
INT = 4
UINT = -INT
LONG = 5
ULONG = -LONG
LONG_LONG = 6
ULONG_LONG = -LONG_LONG
FLOAT = 7
DOUBLE = 8
VARIADIC = 9
CONST_STRING = 10
BOOL = 11
INT8_T = CHAR
UINT8_T = UCHAR
if FFI::Type::Builtin::SHORT.size == 2
INT16_T = SHORT
UINT16_T = USHORT
elsif FFI::Type::Builtin::INT.size == 2
INT16_T = INT
UINT16_T = UINT
end
if FFI::Type::Builtin::SHORT.size == 4
INT32_T = SHORT
UINT32_T = USHORT
elsif FFI::Type::Builtin::INT.size == 4
INT32_T = INT
UINT32_T = UINT
elsif FFI::Type::Builtin::LONG.size == 4
INT32_T = LONG
UINT32_T = ULONG
end
if FFI::Type::Builtin::INT.size == 8
INT64_T = INT
UINT64_T = UINT
elsif FFI::Type::Builtin::LONG.size == 8
INT64_T = LONG
UINT64_T = ULONG
else
INT64_T = LONG_LONG
UINT64_T = ULONG_LONG
end
# FIXME: platform specific values
SSIZE_T = INT64_T
SIZE_T = -SSIZE_T
PTRDIFF_T = SSIZE_T
INTPTR_T = INT64_T
UINTPTR_T = -INTPTR_T
end
WINDOWS = FFI::Platform.windows?
module JRuby
FFITypes = {
'c' => FFI::Type::INT8,
'h' => FFI::Type::INT16,
'i' => FFI::Type::INT32,
'l' => FFI::Type::LONG,
'f' => FFI::Type::FLOAT32,
'd' => FFI::Type::FLOAT64,
'p' => FFI::Type::POINTER,
's' => FFI::Type::STRING,
Types::VOID => FFI::Type::Builtin::VOID,
Types::VOIDP => FFI::Type::Builtin::POINTER,
Types::CHAR => FFI::Type::Builtin::CHAR,
Types::UCHAR => FFI::Type::Builtin::UCHAR,
Types::SHORT => FFI::Type::Builtin::SHORT,
Types::USHORT => FFI::Type::Builtin::USHORT,
Types::INT => FFI::Type::Builtin::INT,
Types::UINT => FFI::Type::Builtin::UINT,
Types::LONG => FFI::Type::Builtin::LONG,
Types::ULONG => FFI::Type::Builtin::ULONG,
Types::LONG_LONG => FFI::Type::Builtin::LONG_LONG,
Types::ULONG_LONG => FFI::Type::Builtin::ULONG_LONG,
Types::FLOAT => FFI::Type::Builtin::FLOAT,
Types::DOUBLE => FFI::Type::Builtin::DOUBLE,
Types::BOOL => FFI::Type::Builtin::BOOL,
Types::CONST_STRING => FFI::Type::Builtin::POINTER,
Types::VARIADIC => FFI::Type::Builtin::VARARGS,
}
def self.__ffi_type__(dl_type)
if dl_type.is_a?(Symbol)
dl_type = Types.const_get(dl_type.to_s.upcase)
end
if !dl_type.is_a?(Integer) && dl_type.respond_to?(:to_int)
dl_type = dl_type.to_int
end
ffi_type = FFITypes[dl_type]
ffi_type = FFITypes[-dl_type] if ffi_type.nil? && dl_type.is_a?(Integer) && dl_type < 0
raise TypeError.new("cannot convert #{dl_type} to ffi") unless ffi_type
ffi_type
end
end
class Function
DEFAULT = "default"
STDCALL = "stdcall"
def initialize(ptr, args, return_type, abi = DEFAULT, kwargs = nil)
if kwargs.nil?
if abi.kind_of? Hash
kwargs = abi
abi = DEFAULT
end
end
@name = kwargs[:name] if kwargs.kind_of? Hash
@ptr, @args, @return_type, @abi = ptr, args, return_type, abi
raise TypeError.new "invalid argument types" unless args.is_a?(Array)
ffi_return_type = Fiddle::JRuby::__ffi_type__(@return_type)
ffi_args = @args.map { |t| Fiddle::JRuby.__ffi_type__(t) }
pointer = FFI::Pointer.new(ptr.to_i)
options = {convention: @abi}
if ffi_args.last == FFI::Type::Builtin::VARARGS
@function = FFI::VariadicInvoker.new(
pointer,
ffi_args,
ffi_return_type,
options
)
else
@function = FFI::Function.new(ffi_return_type, ffi_args, pointer, options)
end
end
def call(*args, &block);
if @function.is_a?(FFI::VariadicInvoker)
n_fixed_args = @args.size - 1
n_fixed_args.step(args.size - 1, 2) do |i|
if args[i] == :const_string || args[i] == Types::CONST_STRING
args[i + 1] = String.try_convert(args[i + 1]) || args[i + 1]
end
args[i] = Fiddle::JRuby.__ffi_type__(args[i])
end
end
result = @function.call(*args, &block)
result = Pointer.new(result) if result.is_a?(FFI::Pointer)
result
end
end
class Closure
def initialize(ret, args, abi = Function::DEFAULT)
raise TypeError.new "invalid argument types" unless args.is_a?(Array)
@ctype, @args = ret, args
ffi_args = @args.map { |t| Fiddle::JRuby.__ffi_type__(t) }
if ffi_args.size == 1 && ffi_args[0] == FFI::Type::Builtin::VOID
ffi_args = []
end
@function = FFI::Function.new(
Fiddle::JRuby.__ffi_type__(@ctype),
ffi_args,
self,
:convention => abi
)
@freed = false
end
def to_i
@function.to_i
end
def free
return if @freed
@function.free
@freed = true
end
def freed?
@freed
end
end
class Error < StandardError; end
class DLError < Error; end
class ClearedReferenceError < Error; end
class Pointer
attr_reader :ffi_ptr
extend FFI::DataConverter
native_type FFI::Type::Builtin::POINTER
def self.to_native(value, ctx)
if value.is_a?(Pointer)
value.ffi_ptr
elsif value.is_a?(Integer)
FFI::Pointer.new(value)
elsif value.is_a?(String)
value
end
end
def self.from_native(value, ctx)
self.new(value)
end
def self.to_ptr(value)
if value.is_a?(String)
cptr = Pointer.malloc(value.bytesize)
cptr.ffi_ptr.put_string(0, value)
cptr
elsif value.is_a?(Array)
raise NotImplementedError, "array ptr"
elsif value.respond_to?(:to_ptr)
ptr = value.to_ptr
case ptr
when Pointer
ptr
when FFI::Pointer
Pointer.new(ptr)
else
raise DLError.new("to_ptr should return a Fiddle::Pointer object, was #{ptr.class}")
end
else
Pointer.new(value)
end
end
class << self
alias [] to_ptr
end
def []=(*args, value)
if args.size == 2
if value.is_a?(Integer)
value = self.class.new(value)
end
if value.is_a?(Fiddle::Pointer)
value = value.to_str(args[1])
end
@ffi_ptr.put_bytes(args[0], value, 0, args[1])
elsif args.size == 1
if value.is_a?(Fiddle::Pointer)
value = value.to_str(args[0] + 1)
else
value = value.chr
end
@ffi_ptr.put_bytes(args[0], value, 0, 1)
end
rescue FFI::NullPointerError
raise DLError.new("NULL pointer access")
end
def initialize(addr, size = nil, free = nil)
ptr = if addr.is_a?(FFI::Pointer)
addr
elsif addr.is_a?(Integer)
FFI::Pointer.new(addr)
elsif addr.respond_to?(:to_ptr)
fiddle_ptr = addr.to_ptr
if fiddle_ptr.is_a?(Pointer)
fiddle_ptr.ffi_ptr
elsif fiddle_ptr.is_a?(FFI::AutoPointer)
addr.ffi_ptr
elsif fiddle_ptr.is_a?(FFI::Pointer)
fiddle_ptr
else
raise DLError.new("to_ptr should return a Fiddle::Pointer object, was #{fiddle_ptr.class}")
end
elsif addr.is_a?(IO)
raise NotImplementedError, "IO ptr isn't supported"
end
@size = size ? size : ptr.size
@free = free
@ffi_ptr = ptr
@freed = false
end
module LibC
extend FFI::Library
ffi_lib FFI::Library::LIBC
MALLOC = attach_function :malloc, [ :size_t ], :pointer
REALLOC = attach_function :realloc, [ :pointer, :size_t ], :pointer
FREE = attach_function :free, [ :pointer ], :void
end
def self.malloc(size, free = nil)
if block_given? and free.nil?
message = "a free function must be supplied to #{self}.malloc " +
"when it is called with a block"
raise ArgumentError, message
end
pointer = new(LibC.malloc(size), size, free)
if block_given?
begin
yield(pointer)
ensure
pointer.call_free
end
else
pointer
end
end
def null?
@ffi_ptr.null?
end
def to_ptr
@ffi_ptr
end
def size
defined?(@layout) ? @layout.size : @size
end
def free
@free
end
def free=(free)
@free = free
end
def call_free
return if @free.nil?
return if @freed
if @free == RUBY_FREE
LibC::FREE.call(@ffi_ptr)
else
@free.call(@ffi_ptr)
end
@freed = true
end
def freed?
@freed
end
def size=(size)
@size = size
end
def [](index, length = nil)
if length
ffi_ptr.get_bytes(index, length)
else
ffi_ptr.get_char(index)
end
rescue FFI::NullPointerError
raise DLError.new("NULL pointer dereference")
end
def to_i
ffi_ptr.to_i
end
alias to_int to_i
# without \0
def to_s(len = nil)
if len
ffi_ptr.get_string(0, len)
else
ffi_ptr.get_string(0)
end
rescue FFI::NullPointerError
raise DLError.new("NULL pointer access")
end
def to_str(len = nil)
if len
ffi_ptr.read_string(len)
else
ffi_ptr.read_string(@size)
end
rescue FFI::NullPointerError
raise DLError.new("NULL pointer access")
end
def to_value
raise NotImplementedError, "to_value isn't supported"
end
def inspect
"#<#{self.class.name} ptr=#{to_i.to_s(16)} size=#{@size} free=#{@free.inspect}>"
end
def +(delta)
self.class.new(to_i + delta, @size - delta)
end
def -(delta)
self.class.new(to_i - delta, @size + delta)
end
def <=>(other)
return unless other.is_a?(Pointer)
diff = self.to_i - other.to_i
return 0 if diff == 0
diff > 0 ? 1 : -1
end
def eql?(other)
return unless other.is_a?(Pointer)
self.to_i == other.to_i
end
def ==(other)
eql?(other)
end
def ptr
Pointer.new(ffi_ptr.get_pointer(0))
end
def +@
ptr
end
def -@
ref
end
def ref
cptr = Pointer.malloc(FFI::Type::POINTER.size, RUBY_FREE)
cptr.ffi_ptr.put_pointer(0, ffi_ptr)
cptr
end
end
class Handle
RTLD_GLOBAL = FFI::DynamicLibrary::RTLD_GLOBAL
RTLD_LAZY = FFI::DynamicLibrary::RTLD_LAZY
RTLD_NOW = FFI::DynamicLibrary::RTLD_NOW
def initialize(libname = nil, flags = RTLD_LAZY | RTLD_GLOBAL)
@lib = FFI::DynamicLibrary.open(libname, flags) rescue LoadError
raise DLError.new("Could not open #{libname}") unless @lib
@open = true
begin
yield(self)
ensure
self.close
end if block_given?
end
def close
raise DLError.new("closed handle") unless @open
@open = false
0
end
def self.sym(func)
DEFAULT.sym(func)
end
def sym(func)
raise TypeError.new("invalid function name") unless func.is_a?(String)
raise DLError.new("closed handle") unless @open
address = @lib.find_function(func)
raise DLError.new("unknown symbol #{func}") if address.nil? || address.null?
address.to_i
end
def self.sym_defined?(func)
DEFAULT.sym_defined?(func)
end
def sym_defined?(func)
raise TypeError.new("invalid function name") unless func.is_a?(String)
raise DLError.new("closed handle") unless @open
address = @lib.find_function(func)
!address.nil? && !address.null?
end
def self.[](func)
self.sym(func)
end
def [](func)
sym(func)
end
def enable_close
@enable_close = true
end
def close_enabled?
@enable_close
end
def disable_close
@enable_close = false
end
DEFAULT = new
end
class Pinned
def initialize(object)
@object = object
end
def ref
if @object.nil?
raise ClearedReferenceError, "`ref` called on a cleared object"
end
@object
end
def clear
@object = nil
end
def cleared?
@object.nil?
end
end
RUBY_FREE = Fiddle::Pointer::LibC::FREE.address
NULL = Fiddle::Pointer.new(0)
ALIGN_VOIDP = Fiddle::JRuby::FFITypes[Types::VOIDP].alignment
ALIGN_CHAR = Fiddle::JRuby::FFITypes[Types::CHAR].alignment
ALIGN_SHORT = Fiddle::JRuby::FFITypes[Types::SHORT].alignment
ALIGN_INT = Fiddle::JRuby::FFITypes[Types::INT].alignment
ALIGN_LONG = Fiddle::JRuby::FFITypes[Types::LONG].alignment
ALIGN_LONG_LONG = Fiddle::JRuby::FFITypes[Types::LONG_LONG].alignment
ALIGN_INT8_T = Fiddle::JRuby::FFITypes[Types::INT8_T].alignment
ALIGN_INT16_T = Fiddle::JRuby::FFITypes[Types::INT16_T].alignment
ALIGN_INT32_T = Fiddle::JRuby::FFITypes[Types::INT32_T].alignment
ALIGN_INT64_T = Fiddle::JRuby::FFITypes[Types::INT64_T].alignment
ALIGN_FLOAT = Fiddle::JRuby::FFITypes[Types::FLOAT].alignment
ALIGN_DOUBLE = Fiddle::JRuby::FFITypes[Types::DOUBLE].alignment
ALIGN_BOOL = Fiddle::JRuby::FFITypes[Types::BOOL].alignment
ALIGN_SIZE_T = Fiddle::JRuby::FFITypes[Types::SIZE_T].alignment
ALIGN_SSIZE_T = ALIGN_SIZE_T
ALIGN_PTRDIFF_T = Fiddle::JRuby::FFITypes[Types::PTRDIFF_T].alignment
ALIGN_INTPTR_T = Fiddle::JRuby::FFITypes[Types::INTPTR_T].alignment
ALIGN_UINTPTR_T = Fiddle::JRuby::FFITypes[Types::UINTPTR_T].alignment
SIZEOF_VOIDP = Fiddle::JRuby::FFITypes[Types::VOIDP].size
SIZEOF_CHAR = Fiddle::JRuby::FFITypes[Types::CHAR].size
SIZEOF_UCHAR = Fiddle::JRuby::FFITypes[Types::UCHAR].size
SIZEOF_SHORT = Fiddle::JRuby::FFITypes[Types::SHORT].size
SIZEOF_USHORT = Fiddle::JRuby::FFITypes[Types::USHORT].size
SIZEOF_INT = Fiddle::JRuby::FFITypes[Types::INT].size
SIZEOF_UINT = Fiddle::JRuby::FFITypes[Types::UINT].size
SIZEOF_LONG = Fiddle::JRuby::FFITypes[Types::LONG].size
SIZEOF_ULONG = Fiddle::JRuby::FFITypes[Types::ULONG].size
SIZEOF_LONG_LONG = Fiddle::JRuby::FFITypes[Types::LONG_LONG].size
SIZEOF_ULONG_LONG = Fiddle::JRuby::FFITypes[Types::ULONG_LONG].size
SIZEOF_INT8_T = Fiddle::JRuby::FFITypes[Types::INT8_T].size
SIZEOF_UINT8_T = Fiddle::JRuby::FFITypes[Types::UINT8_T].size
SIZEOF_INT16_T = Fiddle::JRuby::FFITypes[Types::INT16_T].size
SIZEOF_UINT16_T = Fiddle::JRuby::FFITypes[Types::UINT16_T].size
SIZEOF_INT32_T = Fiddle::JRuby::FFITypes[Types::INT32_T].size
SIZEOF_UINT32_T = Fiddle::JRuby::FFITypes[Types::UINT32_T].size
SIZEOF_INT64_T = Fiddle::JRuby::FFITypes[Types::INT64_T].size
SIZEOF_UINT64_T = Fiddle::JRuby::FFITypes[Types::UINT64_T].size
SIZEOF_FLOAT = Fiddle::JRuby::FFITypes[Types::FLOAT].size
SIZEOF_DOUBLE = Fiddle::JRuby::FFITypes[Types::DOUBLE].size
SIZEOF_BOOL = Fiddle::JRuby::FFITypes[Types::BOOL].size
SIZEOF_SIZE_T = Fiddle::JRuby::FFITypes[Types::SIZE_T].size
SIZEOF_SSIZE_T = SIZEOF_SIZE_T
SIZEOF_PTRDIFF_T = Fiddle::JRuby::FFITypes[Types::PTRDIFF_T].size
SIZEOF_INTPTR_T = Fiddle::JRuby::FFITypes[Types::INTPTR_T].size
SIZEOF_UINTPTR_T = Fiddle::JRuby::FFITypes[Types::UINTPTR_T].size
SIZEOF_CONST_STRING = Fiddle::JRuby::FFITypes[Types::VOIDP].size
end

View File

@ -41,6 +41,12 @@ module Fiddle
when SIZEOF_LONG
PACK_MAP[TYPE_BOOL] = PACK_MAP[TYPE_ULONG]
end
if RUBY_ENGINE == "jruby" and WINDOWS and [0].pack("l!").size == 8
# JRuby's 'l!' pack string doesn't use 32-bit on Windows.
# See https://github.com/jruby/jruby/issues/8357 for details
PACK_MAP[TYPE_LONG] = PACK_MAP[TYPE_INT]
PACK_MAP[TYPE_ULONG] = PACK_MAP[TYPE_UINT]
end
SIZE_MAP = {
TYPE_VOIDP => SIZEOF_VOIDP,

View File

@ -0,0 +1 @@
require "fiddle.so"

View File

@ -290,15 +290,28 @@ module Fiddle
# Allocates a C struct with the +types+ provided.
#
# See Fiddle::Pointer.malloc for memory management issues.
def CStructEntity.malloc(types, func = nil, size = size(types), &block)
def CStructEntity.malloc(types, func = nil, size = size(types))
if block_given? and func.nil?
message = "a free function must be supplied to #{self}.malloc " +
"when it is called with a block"
raise ArgumentError, message
end
pointer = Pointer.malloc(size)
begin
struct = new(pointer, types, func)
rescue
pointer.free = func
pointer.call_free
raise
end
if block_given?
super(size, func) do |struct|
struct.set_ctypes types
yield struct
begin
yield(struct)
ensure
struct.call_free
end
else
struct = super(size, func)
struct.set_ctypes types
struct
end
end
@ -505,6 +518,14 @@ module Fiddle
def to_s() # :nodoc:
super(@size)
end
def +(delta)
Pointer.new(to_i + delta, @size - delta)
end
def -(delta)
Pointer.new(to_i - delta, @size + delta)
end
end
# A pointer to a C union

View File

@ -4,11 +4,19 @@ require 'rbconfig/sizeof'
require 'test/unit'
require 'fiddle'
puts("Fiddle::VERSION: #{Fiddle::VERSION}")
# FIXME: this is stolen from DL and needs to be refactored.
libc_so = libm_so = nil
case RUBY_PLATFORM
if RUBY_ENGINE == "jruby"
# "jruby ... [x86_64-linux]" -> "x86_64-linux"
ruby_platform = RUBY_DESCRIPTION.split(" ").last[1..-2]
else
ruby_platform = RUBY_PLATFORM
end
case ruby_platform
when /cygwin/
libc_so = "cygwin1.dll"
libm_so = "cygwin1.dll"
@ -147,6 +155,7 @@ unless rigid_path
end
if !libc_so || !libm_so
require "envutil"
ruby = EnvUtil.rubybin
# When the ruby binary is 32-bit and the host is 64-bit,
# `ldd ruby` outputs "not a dynamic executable" message.

View File

@ -8,6 +8,8 @@ module Fiddle
class TestClosure < Fiddle::TestCase
def teardown
super
# We can't use ObjectSpace with JRuby.
return if RUBY_ENGINE == "jruby"
# Ensure freeing all closures.
# See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
not_freed_closures = []
@ -31,19 +33,6 @@ module Fiddle
end
end
def test_type_symbol
Closure.create(:int, [:void]) do |closure|
assert_equal([
TYPE_INT,
[TYPE_VOID],
],
[
closure.instance_variable_get(:@ctype),
closure.instance_variable_get(:@args),
])
end
end
def test_call
closure_class = Class.new(Closure) do
def call
@ -69,6 +58,11 @@ module Fiddle
end
def test_const_string
if RUBY_ENGINE == "jruby"
omit("Closure with :const_string works but " +
"Function with :const_string doesn't work with JRuby")
end
closure_class = Class.new(Closure) do
def call(string)
@return_string = "Hello! #{string}"
@ -94,7 +88,12 @@ module Fiddle
end
def test_free
Closure.create(:int, [:void]) do |closure|
closure_class = Class.new(Closure) do
def call
10
end
end
closure_class.create(:int, [:void]) do |closure|
assert(!closure.freed?)
closure.free
assert(closure.freed?)
@ -115,6 +114,10 @@ module Fiddle
end
def test_memsize_ruby_dev_42480
if RUBY_ENGINE == "jruby"
omit("We can't use ObjectSpace with JRuby")
end
require 'objspace'
n = 10000
n.times do

View File

@ -6,6 +6,10 @@ end
class TestFiddle < Fiddle::TestCase
def test_nil_true_etc
if RUBY_ENGINE == "jruby"
omit("Fiddle::Q* aren't supported with JRuby")
end
assert_equal Fiddle::Qtrue, Fiddle.dlwrap(true)
assert_equal Fiddle::Qfalse, Fiddle.dlwrap(false)
assert_equal Fiddle::Qnil, Fiddle.dlwrap(nil)

View File

@ -26,6 +26,10 @@ module Fiddle
end
def test_string
if RUBY_ENGINE == "jruby"
omit("Function that returns string doesn't work with JRuby")
end
under_gc_stress do
f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
buff = +"000"
@ -82,6 +86,8 @@ module Fiddle
assert_equal("1349", buff, bug4929)
end
ensure
# We can't use ObjectSpace with JRuby.
return if RUBY_ENGINE == "jruby"
# Ensure freeing all closures.
# See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
not_freed_closures = []
@ -113,37 +119,36 @@ module Fiddle
:variadic,
],
:int)
output_buffer = " " * 1024
output = Pointer[output_buffer]
Pointer.malloc(1024, Fiddle::RUBY_FREE) do |output|
written = snprintf.call(output,
output.size,
"int: %d, string: %.*s, const string: %s\n",
:int, -29,
:int, 4,
:voidp, "Hello",
:const_string, "World")
assert_equal("int: -29, string: Hell, const string: World\n",
output[0, written])
written = snprintf.call(output,
output.size,
"int: %d, string: %.*s, const string: %s\n",
:int, -29,
:int, 4,
:voidp, "Hello",
:const_string, "World")
assert_equal("int: -29, string: Hell, const string: World\n",
output_buffer[0, written])
string_like_class = Class.new do
def initialize(string)
@string = string
end
string_like_class = Class.new do
def initialize(string)
@string = string
end
def to_str
@string
def to_str
@string
end
end
written = snprintf.call(output,
output.size,
"string: %.*s, const string: %s, uint: %u\n",
:int, 2,
:voidp, "Hello",
:const_string, string_like_class.new("World"),
:int, 29)
assert_equal("string: He, const string: World, uint: 29\n",
output[0, written])
end
written = snprintf.call(output,
output.size,
"string: %.*s, const string: %s, uint: %u\n",
:int, 2,
:voidp, "Hello",
:const_string, string_like_class.new("World"),
:int, 29)
assert_equal("string: He, const string: World, uint: 29\n",
output_buffer[0, written])
end
def test_rb_memory_view_available_p

View File

@ -16,6 +16,8 @@ module Fiddle
end
def teardown
# We can't use ObjectSpace with JRuby.
return if RUBY_ENGINE == "jruby"
# Ensure freeing all closures.
# See https://github.com/ruby/fiddle/issues/102#issuecomment-1241763091 .
not_freed_closures = []
@ -36,6 +38,10 @@ module Fiddle
end
def test_need_gvl?
if RUBY_ENGINE == "jruby"
omit("rb_str_dup() doesn't exit in JRuby")
end
libruby = Fiddle.dlopen(nil)
rb_str_dup = Function.new(libruby['rb_str_dup'],
[:voidp],
@ -103,6 +109,10 @@ module Fiddle
end
def test_last_error
if RUBY_ENGINE == "jruby"
omit("Fiddle.last_error doesn't work with JRuby")
end
func = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
assert_nil Fiddle.last_error
@ -135,6 +145,10 @@ module Fiddle
end
def test_strcpy
if RUBY_ENGINE == "jruby"
omit("Function that returns string doesn't work with JRuby")
end
f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
buff = +"000"
str = f.call(buff, "123")
@ -149,6 +163,10 @@ module Fiddle
end
def test_function_as_proc
if RUBY_ENGINE == "jruby"
omit("Function that returns string doesn't work with JRuby")
end
f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
buff, str = call_proc("123", &f)
assert_equal("123", buff)
@ -156,6 +174,10 @@ module Fiddle
end
def test_function_as_method
if RUBY_ENGINE == "jruby"
omit("Function that returns string doesn't work with JRuby")
end
f = Function.new(@libc['strcpy'], [TYPE_VOIDP, TYPE_VOIDP], TYPE_VOIDP)
klass = Class.new do
define_singleton_method(:strcpy, &f)
@ -194,6 +216,10 @@ module Fiddle
end
def test_no_memory_leak
if RUBY_ENGINE == "jruby"
omit("rb_obj_frozen_p() doesn't exist in JRuby")
end
if respond_to?(:assert_nothing_leaked_memory)
rb_obj_frozen_p_symbol = Fiddle.dlopen(nil)["rb_obj_frozen_p"]
rb_obj_frozen_p = Fiddle::Function.new(rb_obj_frozen_p_symbol,

View File

@ -9,11 +9,19 @@ module Fiddle
include Fiddle
def test_to_i
if RUBY_ENGINE == "jruby"
omit("Fiddle::Handle#to_i is unavailable with JRuby")
end
handle = Fiddle::Handle.new(LIBC_SO)
assert_kind_of Integer, handle.to_i
end
def test_to_ptr
if RUBY_ENGINE == "jruby"
omit("Fiddle::Handle#to_i is unavailable with JRuby")
end
handle = Fiddle::Handle.new(LIBC_SO)
ptr = handle.to_ptr
assert_equal ptr.to_i, handle.to_i
@ -26,6 +34,10 @@ module Fiddle
end
def test_static_sym
if RUBY_ENGINE == "jruby"
omit("We can't assume static symbols with JRuby")
end
begin
# Linux / Darwin / FreeBSD
refute_nil Fiddle::Handle.sym('dlopen')
@ -90,6 +102,10 @@ module Fiddle
end
def test_initialize_noargs
if RUBY_ENGINE == "jruby"
omit("rb_str_new() doesn't exist in JRuby")
end
handle = Handle.new
refute_nil handle['rb_str_new']
end
@ -117,6 +133,10 @@ module Fiddle
end
def test_file_name
if RUBY_ENGINE == "jruby"
omit("Fiddle::Handle::NEXT doesn't exist with JRuby")
end
file_name = Handle.new(LIBC_SO).file_name
if file_name
assert_kind_of String, file_name
@ -135,6 +155,10 @@ module Fiddle
end
def test_NEXT
if RUBY_ENGINE == "jruby"
omit("Fiddle::Handle::NEXT doesn't exist with JRuby")
end
begin
# Linux / Darwin
#
@ -173,9 +197,13 @@ module Fiddle
end unless /mswin|mingw/ =~ RUBY_PLATFORM
def test_DEFAULT
if Fiddle::WINDOWS
omit("Fiddle::Handle::DEFAULT doesn't have malloc() on Windows")
end
handle = Handle::DEFAULT
refute_nil handle['malloc']
end unless /mswin|mingw/ =~ RUBY_PLATFORM
end
def test_dlerror
# FreeBSD (at least 7.2 to 7.2) calls nsdispatch(3) when it calls

View File

@ -149,11 +149,15 @@ module Fiddle
def test_unsigned_result()
d = (2 ** 31) + 1
r = LIBC.strtoul(d.to_s, 0, 0)
r = LIBC.strtoul(d.to_s, nil, 0)
assert_equal(d, r)
end
def test_io()
if RUBY_ENGINE == "jruby"
omit("BUILD_RUBY_PLATFORM doesn't exist in JRuby")
end
if( RUBY_PLATFORM != BUILD_RUBY_PLATFORM ) || !defined?(LIBC.fprintf)
return
end
@ -329,11 +333,12 @@ module Fiddle
def test_struct_nested_struct_replace_array_element_hash()
LIBC::StructNestedStruct.malloc(Fiddle::RUBY_FREE) do |s|
s.vertices[0] = nil
s.vertices[0] = {
position: {
x: 10,
y: 100,
}
},
}
assert_equal({
"position" => {
@ -450,7 +455,7 @@ module Fiddle
s.buff = "012345\377"
assert_equal([0,1,2,3,4], s.num)
assert_equal(?a.ord, s.c)
assert_equal([?0.ord,?1.ord,?2.ord,?3.ord,?4.ord,?5.ord,?\377.ord], s.buff)
assert_equal([?0.ord,?1.ord,?2.ord,?3.ord,?4.ord,?5.ord,"\xFF".ord], s.buff)
end
end
@ -467,6 +472,10 @@ module Fiddle
end
def test_strcpy()
if RUBY_ENGINE == "jruby"
omit("Function that returns string doesn't work with JRuby")
end
buff = +"000"
str = LIBC.strcpy(buff, "123")
assert_equal("123", buff)

View File

@ -11,19 +11,22 @@ module Fiddle
end
def test_can_read_write_memory
if RUBY_ENGINE == "jruby"
omit("Fiddle::Pointer.{read,write} don't exist in JRuby")
end
# Allocate some memory
address = Fiddle.malloc(Fiddle::SIZEOF_VOIDP)
Fiddle::Pointer.malloc(Fiddle::SIZEOF_VOIDP, Fiddle::RUBY_FREE) do |pointer|
address = pointer.to_i
bytes_to_write = Fiddle::SIZEOF_VOIDP.times.to_a.pack("C*")
bytes_to_write = Fiddle::SIZEOF_VOIDP.times.to_a.pack("C*")
# Write to the memory
Fiddle::Pointer.write(address, bytes_to_write)
# Write to the memory
Fiddle::Pointer.write(address, bytes_to_write)
# Read the bytes out again
bytes = Fiddle::Pointer.read(address, Fiddle::SIZEOF_VOIDP)
assert_equal bytes_to_write, bytes
ensure
Fiddle.free address
# Read the bytes out again
bytes = Fiddle::Pointer.read(address, Fiddle::SIZEOF_VOIDP)
assert_equal bytes_to_write, bytes
end
end
def test_cptr_to_int
@ -110,6 +113,10 @@ module Fiddle
end
def test_inspect
if RUBY_ENGINE == "jruby"
omit("Fiddle::Pointer#inspect is incompatible on JRuby")
end
ptr = Pointer.new(0)
inspect = ptr.inspect
assert_match(/size=#{ptr.size}/, inspect)
@ -125,6 +132,10 @@ module Fiddle
end
def test_to_ptr_io
if RUBY_ENGINE == "jruby"
omit("Fiddle::Pointer.to_ptr(IO) isn't supported with JRuby")
end
Pointer.malloc(10, Fiddle::RUBY_FREE) do |buf|
File.open(__FILE__, 'r') do |f|
ptr = Pointer.to_ptr f
@ -172,6 +183,10 @@ module Fiddle
end
def test_ref_ptr
if RUBY_ENGINE == "jruby"
omit("Fiddle.dlwrap([]) isn't supported with JRuby")
end
ary = [0,1,2,4,5]
addr = Pointer.new(dlwrap(ary))
assert_equal addr.to_i, addr.ref.ptr.to_i
@ -180,6 +195,10 @@ module Fiddle
end
def test_to_value
if RUBY_ENGINE == "jruby"
omit("Fiddle.dlwrap([]) isn't supported with JRuby")
end
ary = [0,1,2,4,5]
addr = Pointer.new(dlwrap(ary))
assert_equal ary, addr.to_value