rubinius/rubinius

View on GitHub
core/pointer.rb

Summary

Maintainability
C
1 day
Test Coverage
module Rubinius
  module FFI
    # We need to define this so we can include it. Since the file is
    # auto-generated, we can't pull it in here but we don't want to break the
    # alphabetized load order for random cases.
    module PointerAccessors; end

    ##
    # Pointer is Rubinius's "fat" pointer class. It represents an actual
    # pointer, in C language terms, to an address in memory. They're called
    # fat pointers because the Pointer object is an wrapper around
    # the actual pointer, the Rubinius runtime doesn't have direct access
    # to the raw address.
    #
    # This class is used extensively in FFI usage to interface with various
    # parts of the underlying system. It provides a number of operations
    # for operating on the memory that is pointed to. These operations effectively
    # give Rubinius the cast/read capabilities available in C, but using
    # high level methods.
    #
    # MemoryPointer objects can be put in autorelease mode. In this mode,
    # when the GC cleans up a MemoryPointer object, the memory it points
    # to is passed to free(3), releasing the memory back to the OS.
    #
    # NOTE: MemoryPointer exposes direct, unmanaged operations on any
    # memory. It therefore MUST be used carefully. Reading or writing to
    # invalid address will cause bus errors and segmentation faults.
    #
    class Pointer
      include PointerAccessors

      def initialize(a1, a2=undefined)
        if undefined.equal? a2
          self.address = a1
        else
          @type = a1
          self.address = a2
        end
      end

      private :initialize

      def inspect
        # Don't have this print the data at the location. It can crash everything.
        addr = address()

        if addr < 0
          sign = "-"
          addr = -addr
        else
          sign = ""
        end

        "#<#{self.class.name} address=#{sign}0x#{addr.to_s(16)}>"
      end

      # Return the address pointed to as an Integer
      def address
        Rubinius.primitive :pointer_address
        raise PrimitiveFailure, "FFI::Pointer#address primitive failed"
      end

      alias_method :to_i, :address

      # Set the address pointed to from an Integer
      def address=(address)
        Rubinius.primitive :pointer_set_address
        raise PrimitiveFailure, "FFI::Pointer#address= primitive failed"
      end

      def null?
        address == 0x0
      end

      # Add +value+ to the address pointed to and return a new Pointer
      def +(value)
        Rubinius.primitive :pointer_add
        raise PrimitiveFailure, "FFI::Pointer#+ primitive failed"
      end

      # Indicates if +self+ and +other+ point to the same address
      def ==(other)
        return false unless other.kind_of? Pointer
        return address == other.address
      end

      def network_order(start, size)
        Rubinius.primitive :pointer_network_order
        raise PrimitiveFailure, "FFI::Pointer#network_order primitive failed"
      end

      # Read +len+ bytes from the memory pointed to and return them as
      # a String
      def read_string_length(len)
        Rubinius.primitive :pointer_read_string
        raise PrimitiveFailure, "FFI::Pointer#read_string_length primitive failed"
      end

      # Read bytes from the memory pointed to until a NULL is seen, return
      # the bytes as a String
      def read_string_to_null
        Rubinius.primitive :pointer_read_string_to_null
        raise PrimitiveFailure, "FFI::Pointer#read_string_to_null primitive failed"
      end

      # Read bytes as a String from the memory pointed to
      def read_string(len=nil)
        if len
          read_string_length(len)
        else
          read_string_to_null
        end
      end

      # FFI compat methods
      def get_bytes(offset, length)
        (self + offset).read_string_length(length)
      end

      # Write String +str+ as bytes into the memory pointed to. Only
      # write up to +len+ bytes.
      def write_string_length(str, len)
        Rubinius.primitive :pointer_write_string
        raise PrimitiveFailure, "FFI::Pointer#write_string_length primitive failed"
      end

      # Write a String +str+ as bytes to the memory pointed to.
      def write_string(str, len=nil)
        len = str.bytesize unless len

        write_string_length(str, len);
      end

      # Read a sequence of types +type+, length +length+, using method +reader+
      def read_array_of_type(type, reader, length, signed=nil)
        # If signed is not nil and is actually a boolean,
        # then use that as an argument to the reader, which
        # is then assumed to support signed reading.
        args = []
        args = [signed] if !signed.nil?

        # Build up the array
        ary = []
        size = FFI.type_size(FFI.find_type type)
        tmp = self
        length.times {
          ary << tmp.send(reader, *args)
          tmp += size
        }
        ary
      end

      # Write a sequence of types +type+  using method +reader+ from +ary+
      def write_array_of_type(type, writer, ary)
        size = FFI.type_size(FFI.find_type type)
        tmp = self
        ary.each do |i|
          tmp.send(writer, i)
          tmp += size
        end
        self
      end

      # Read bytes from +offset+ from the memory pointed to as type +type+
      def get_at_offset(offset, type)
        Rubinius.primitive :pointer_get_at_offset
        raise PrimitiveFailure, "FFI::Pointer#get_at_offset primitive failed"
      end

      # Write +val+ as type +type+ to bytes from +offset+
      def set_at_offset(offset, type, val)
        Rubinius.primitive :pointer_set_at_offset
        raise PrimitiveFailure, "FFI::Pointer#set_at_offset primitive failed"
      end

      # Number of bytes taken up by a pointer.
      def self.size
        Rubinius::WORDSIZE / 8
      end

      # Primitive methods
      def primitive_read_char(signed)
        Rubinius.primitive :pointer_read_char
        raise PrimitiveFailure, "FFI::Pointer#primitive_read_char primitive failed"
      end

      def primitive_write_char(obj)
        Rubinius.primitive :pointer_write_char
        raise PrimitiveFailure, "FFI::Pointer#primitive_write_char primitive failed"
      end

      def primitive_read_short(signed)
        Rubinius.primitive :pointer_read_short
        raise PrimitiveFailure, "FFI::Pointer#primitive_read_short primitive failed"
      end

      def primitive_write_short(obj)
        Rubinius.primitive :pointer_write_short
        raise PrimitiveFailure, "FFI::Pointer#primitive_write_short primitive failed"
      end

      def primitive_read_int(signed)
        Rubinius.primitive :pointer_read_int
        raise PrimitiveFailure, "FFI::Pointer#primitive_read_int primitive failed"
      end

      def primitive_write_int(obj)
        Rubinius.primitive :pointer_write_int
        raise PrimitiveFailure, "FFI::Pointer#primitive_write_int primitive failed"
      end

      def primitive_read_long(signed)
        Rubinius.primitive :pointer_read_long
        raise PrimitiveFailure, "FFI::Pointer#primitive_read_long primitive failed"
      end

      def primitive_write_long(obj)
        Rubinius.primitive :pointer_write_long
        raise PrimitiveFailure, "FFI::Pointer#primitive_write_long primitive failed"
      end

      def primitive_read_long_long(signed)
        Rubinius.primitive :pointer_read_long_long
        raise PrimitiveFailure, "FFI::Pointer#primitive_read_long_long primitive failed"
      end

      def primitive_write_long_long(obj)
        Rubinius.primitive :pointer_write_long_long
        raise PrimitiveFailure, "FFI::Pointer#primitive_write_long_long primitive failed"
      end

      def primitive_read_float
        Rubinius.primitive :pointer_read_float
        raise PrimitiveFailure, "FFI::Pointer#primitive_read_float primitive failed"
      end

      def primitive_write_float(obj)
        Rubinius.primitive :pointer_write_float
        raise PrimitiveFailure, "FFI::Pointer#primitive_write_float primitive failed"
      end

      def primitive_read_double
        Rubinius.primitive :pointer_read_double
        raise PrimitiveFailure, "FFI::Pointer#primitive_read_double primitive failed"
      end

      def primitive_write_double(obj)
        Rubinius.primitive :pointer_write_double
        raise PrimitiveFailure, "FFI::Pointer#primitive_write_double primitive failed"
      end

      def primitive_read_pointer
        Rubinius.primitive :pointer_read_pointer
        raise PrimitiveFailure, "FFI::Pointer#primitive_read_pointer primitive failed"
      end

      def primitive_write_pointer(obj)
        Rubinius.primitive :pointer_write_pointer
        raise PrimitiveFailure, "FFI::Pointer#primitive_write_pointer primitive failed"
      end

      ##
      # If +val+ is true, this Pointer object will call
      # free() on it's address when it is garbage collected.
      def autorelease=(val)
        Rubinius.primitive :pointer_set_autorelease
        raise PrimitiveFailure, "FFI::Pointer#autorelease= primitive failed"
      end

      ##
      # Returns true if autorelease is enabled, otherwise false.
      def autorelease?
        Rubinius.primitive :pointer_autorelease_p
        raise PrimitiveFailure, "FFI::Pointer#pointer_autorelease_p primitive failed"
      end

      NULL = Pointer.new(0x0)
    end

    class MemoryPointer < Pointer

      # call-seq:
      #   MemoryPointer.new(num) => MemoryPointer instance of <i>num</i> bytes
      #   MemoryPointer.new(sym) => MemoryPointer instance with number
      #                             of bytes need by FFI type <i>sym</i>
      #   MemoryPointer.new(obj) => MemoryPointer instance with number
      #                             of <i>obj.size</i> bytes
      #   MemoryPointer.new(sym, count) => MemoryPointer instance with number
      #                             of bytes need by length-<i>count</i> array
      #                             of FFI type <i>sym</i>
      #   MemoryPointer.new(obj, count) => MemoryPointer instance with number
      #                             of bytes need by length-<i>count</i> array
      #                             of <i>obj.size</i> bytes
      #   MemoryPointer.new(arg) { |p| ... }
      #
      # Both forms create a MemoryPointer instance. The number of bytes to
      # allocate is either specified directly or by passing an FFI type, which
      # specifies the number of bytes needed for that type.
      #
      # The form without a block returns the MemoryPointer instance. The form
      # with a block yields the MemoryPointer instance and frees the memory
      # when the block returns. The value returned is the value of the block.
      #
      def self.new(type, count=nil, clear=true)
        if type.kind_of? Fixnum
          size = type
        elsif type.kind_of? Symbol
          type = FFI.find_type type
          size = FFI.type_size(type)
        else
          size = type.size
        end

        if count
          total = size * count
        else
          total = size
        end

        return NULL if total < 0

        ptr = malloc total
        ptr.total = total
        ptr.type_size = size
        FFI::Platform::POSIX.memset ptr, 0, total if clear

        if block_given?
          begin
            value = yield ptr
          ensure
            ptr.free
          end

          return value
        else
          ptr.autorelease = true
          ptr
        end
      end

      def self.malloc(total)
        Rubinius.primitive :pointer_malloc
        raise PrimitiveFailure, "FFI::MemoryPointer.malloc primitive failed"
      end

      def self.from_string(str)
        ptr = new str.bytesize + 1
        ptr.write_string str + "\0"

        ptr
      end

      def copy
        other = malloc total
        other.total = total
        other.type_size = type_size
        FFI::Platform::POSIX.memcpy other, self, total

        Rubinius.privately do
          other.initialize_copy self
        end

        other
      end

      # Indicates how many bytes the chunk of memory that is pointed to takes up.
      attr_accessor :total

      # Indicates how many bytes the type that the pointer is cast as uses.
      attr_accessor :type_size

      # Access the MemoryPointer like a C array, accessing the +which+ number
      # element in memory. The position of the element is calculate from
      # +@type_size+ and +which+. A new MemoryPointer object is returned, which
      # points to the address of the element.
      #
      # Example:
      #   ptr = MemoryPointer.new(:int, 20)
      #   new_ptr = ptr[9]
      #
      # c-equiv:
      #   int *ptr = (int*)malloc(sizeof(int) * 20);
      #   int *new_ptr;
      #   new_ptr = &ptr[9];
      #
      def [](which)
        raise ArgumentError, "unknown type size" unless @type_size
        self + (which * @type_size)
      end

      # Release the memory pointed to back to the OS.
      def free
        Rubinius.primitive :pointer_free
        raise PrimitiveFailure, "FFI::MemoryPointer#free primitive failed"
      end
    end

    class DynamicLibrary
      class Symbol < Pointer
        def initialize(library, ptr, name)
          @library = library
          @name = name
          self.address = ptr.address
        end

        private :initialize

        def inspect
          "#<FFI::Library::Symbol name=#{@name} address=#{address.to_s(16)}>"
        end
      end
    end

    class Function < Pointer
      def initialize(ret_type, arg_types, val=nil, options=nil, &block)
        if block
          if val or options
            raise ArgumentError, "specify a block or a proc/address, not both"
          end

          val = block
        end

        args = arg_types.map { |x| FFI.find_type(x) }
        ret =  FFI.find_type(ret_type)

        if val.kind_of? Pointer
          @function = FFI.generate_function(val, :func, args, ret)
          self.address = val.address
        elsif val.respond_to? :call
          @function, ptr = FFI.generate_trampoline(val, :func, args, ret)
          self.address = ptr.address
        else
          raise ArgumentError, "value wasn't a FFI::Pointer and didn't respond to call"
        end

        # Hook the created function into the method_table so that #call goes
        # straight to it.
        sc = Rubinius::Type.object_singleton_class(self)
        Rubinius::VM.reset_method_cache sc, :call
        sc.method_table.store :call, nil, @function, nil, 0, :public
      end

      private :initialize

      attr_reader :function

      # Hook this Function up to be an instance/class method +name+ on +mod+
      def attach(mod, name)
        unless mod.kind_of?(Module)
          raise TypeError, "mod must be a Module"
        end

        name = name.to_sym

        # Make it available as a method callable directly..
        sc = Rubinius::Type.object_singleton_class(mod)
        Rubinius::VM.reset_method_cache sc, name
        sc.method_table.store name, nil, @function, nil, 0, :public

        # and expose it as a private method for people who
        # want to include this module.
        Rubinius::VM.reset_method_cache mod, name
        mod.method_table.store name, nil, @function, nil, 0, :public
      end
    end
  end
end