Skip to content

[FFI] Struct Arrays behave differently on JRuby vs FFI gem #630

@Burgestrand

Description

@Burgestrand

On Ruby 2.0 (FFI gem), the below code runs without errors. On JRuby it will fail on two accounts:

  • A struct by reference (i.e. it’s given a pointer at initialization, memory is not allocated by FFI) will use a VariableLengthArray as field type, where Ruby uses an InlineArray of size 0. VariableLengthArray does not exist in Ruby FFI (I believe?), and while useful is maybe not what one would mean with [:type, 0] — perhaps nil is a better value than 0 to coerce into a VariableLengthArray instead of an Array, and always use Array for any integer value?
  • When FFI allocates the memory, one cannot access the field at all, and I am assuming this is because FFI does not allocate memory for the VariableLengthArray because it does not know how much memory is needed.

Using nil instead of 0 as a signal for a field being of variable length (and not having an explicit size) might break backwards-compatibility, but only on JRuby. Ruby FFI on MRI and Rubinius does not have the VariableLengthArray at all, and always return an Array field.

require "ffi"

class Zero < FFI::Struct
  layout dummy: :uint,
         pointers: [ :pointer, 0 ]
end

class One < FFI::Struct
  layout dummy: :uint,
         pointers: [ :pointer, 1 ]
end

fzero = Zero.layout.fields[1]
fone = One.layout.fields[1]

p [fzero, fzero.size] # => StructLayout::Array, size 0
p [fone, fone.size] # => StructLayout::Array, size 8

zero = Zero.new(FFI::Pointer::NULL) # existing pointer
one = One.new(FFI::Pointer::NULL) # existing pointer

p zero[:pointers] # => Struct::InlineArray (MRI), StructLayout::VariableLengthArrayProxy (JRuby)
p zero[:pointers].size # => 0
# ^ fails on JRuby, NoMethodError: undefined method `size' for #<FFI::StructLayout::VariableLengthArrayProxy:0x5edea768>
p one[:pointers] # => Struct::InlineArray (MRI), Struct::ArrayProxy (JRuby)
p one[:pointers].size # => 1

zero = Zero.new # allocate memory
one = One.new # allocate memory

p zero[:pointers]
# ^ fails on JRuby, IndexError: Memory access offset=8 size=1 is out of bounds
      # [] at org/jruby/ext/ffi/Struct.java:27
p one[:pointers]

Array fields of size 0, instead of being variable length when length is set as 0, are convenient for fields you map over. I’ve defined structs, where the size of the array is defined at runtime (and could be 0), which allows me to write code like this:

class MyStruct < FFI::Struct
  layout count: :uint, names: [:pointer, 0]

  def initialize()
    # logic for calculating the *true* layout, runtime, when handed a pointer
    super(, *new_layout)
  end
end

MyStruct.new(some_pointer)[:names].map(&:read_string) # => returns [] on MRI and Rubinius, raises an error on JRuby

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions