[ruby/yarp] Treat yp_buffer_t as an opaque pointer

Right now, we have to keep the buffer FFI object in sync with the
definition of yp_buffer_t because we access its fields directly. If
we add more fields or change the order, things will get out of sync.

Instead, let's treat yp_buffer_t as an opaque pointer and access
its fields through accessor functions directly. This is more
consistent with how we handle strings anyway.

https://github.com/ruby/yarp/commit/878d845eff
This commit is contained in:
Kevin Newton 2023-08-15 09:18:44 -04:00 committed by Takashi Kokubun
parent 75a4767525
commit 81d715099c
Notes: git 2023-08-17 16:59:16 +00:00
4 changed files with 88 additions and 47 deletions

View File

@ -75,7 +75,10 @@ module YARP
load_exported_functions_from( load_exported_functions_from(
"yarp/util/yp_buffer.h", "yarp/util/yp_buffer.h",
"yp_buffer_sizeof",
"yp_buffer_init", "yp_buffer_init",
"yp_buffer_value",
"yp_buffer_length",
"yp_buffer_free" "yp_buffer_free"
) )
@ -88,34 +91,49 @@ module YARP
"yp_string_sizeof" "yp_string_sizeof"
) )
# This object represents a yp_buffer_t. Its structure must be kept in sync # This object represents a yp_buffer_t. We only use it as an opaque pointer,
# with the C version. # so it doesn't need to know the fields of yp_buffer_t.
class YPBuffer < FFI::Struct class YPBuffer
layout value: :pointer, length: :size_t, capacity: :size_t SIZEOF = LibRubyParser.yp_buffer_sizeof
# Read the contents of the buffer into a String object and return it. attr_reader :pointer
def to_ruby_string
self[:value].read_string(self[:length]) def initialize(pointer)
@pointer = pointer
end end
def value
LibRubyParser.yp_buffer_value(pointer)
end
def length
LibRubyParser.yp_buffer_length(pointer)
end
def read
value.read_string(length)
end end
# Initialize a new buffer and yield it to the block. The buffer will be # Initialize a new buffer and yield it to the block. The buffer will be
# automatically freed when the block returns. # automatically freed when the block returns.
def self.with_buffer(&block) def self.with(&block)
buffer = YPBuffer.new pointer = FFI::MemoryPointer.new(SIZEOF)
begin begin
raise unless yp_buffer_init(buffer) raise unless LibRubyParser.yp_buffer_init(pointer)
yield buffer yield new(pointer)
ensure ensure
yp_buffer_free(buffer) LibRubyParser.yp_buffer_free(pointer)
buffer.pointer.free pointer.free
end
end end
end end
# This object represents a yp_string_t. We only use it as an opaque pointer, # This object represents a yp_string_t. We only use it as an opaque pointer,
# so it doesn't have to be an FFI::Struct. # so it doesn't have to be an FFI::Struct.
class YPString class YPString
SIZEOF = LibRubyParser.yp_string_sizeof
attr_reader :pointer attr_reader :pointer
def initialize(pointer) def initialize(pointer)
@ -133,23 +151,18 @@ module YARP
def read def read
source.read_string(length) source.read_string(length)
end end
end
# This is the size of a yp_string_t. It is returned by the yp_string_sizeof
# function which we call once to ensure we have sufficient space for the
# yp_string_t FFI pointer.
SIZEOF_YP_STRING = yp_string_sizeof
# Yields a yp_string_t pointer to the given block. # Yields a yp_string_t pointer to the given block.
def self.with_string(filepath, &block) def self.with(filepath, &block)
string = FFI::MemoryPointer.new(SIZEOF_YP_STRING) pointer = FFI::MemoryPointer.new(SIZEOF)
begin begin
raise unless yp_string_mapped_init(string, filepath) raise unless LibRubyParser.yp_string_mapped_init(pointer, filepath)
yield YPString.new(string) yield new(pointer)
ensure ensure
yp_string_free(string) LibRubyParser.yp_string_free(pointer)
string.free pointer.free
end
end end
end end
end end
@ -162,10 +175,10 @@ module YARP
VERSION = LibRubyParser.yp_version.read_string VERSION = LibRubyParser.yp_version.read_string
def self.dump_internal(source, source_size, filepath) def self.dump_internal(source, source_size, filepath)
LibRubyParser.with_buffer do |buffer| LibRubyParser::YPBuffer.with do |buffer|
metadata = [filepath.bytesize, filepath.b, 0].pack("LA*L") if filepath metadata = [filepath.bytesize, filepath.b, 0].pack("LA*L") if filepath
LibRubyParser.yp_parse_serialize(source, source_size, buffer, metadata) LibRubyParser.yp_parse_serialize(source, source_size, buffer.pointer, metadata)
buffer.to_ruby_string buffer.read
end end
end end
private_class_method :dump_internal private_class_method :dump_internal
@ -177,24 +190,24 @@ module YARP
# Mirror the YARP.dump_file API by using the serialization API. # Mirror the YARP.dump_file API by using the serialization API.
def self.dump_file(filepath) def self.dump_file(filepath)
LibRubyParser.with_string(filepath) do |string| LibRubyParser::YPString.with(filepath) do |string|
dump_internal(string.source, string.length, filepath) dump_internal(string.source, string.length, filepath)
end end
end end
# Mirror the YARP.lex API by using the serialization API. # Mirror the YARP.lex API by using the serialization API.
def self.lex(code, filepath = nil) def self.lex(code, filepath = nil)
LibRubyParser.with_buffer do |buffer| LibRubyParser::YPBuffer.with do |buffer|
LibRubyParser.yp_lex_serialize(code, code.bytesize, filepath, buffer) LibRubyParser.yp_lex_serialize(code, code.bytesize, filepath, buffer.pointer)
Serialize.load_tokens(Source.new(code), buffer.read)
source = Source.new(code)
Serialize.load_tokens(source, buffer.to_ruby_string)
end end
end end
# Mirror the YARP.lex_file API by using the serialization API. # Mirror the YARP.lex_file API by using the serialization API.
def self.lex_file(filepath) def self.lex_file(filepath)
LibRubyParser.with_string(filepath) { |string| lex(string.read, filepath) } LibRubyParser::YPString.with(filepath) do |string|
lex(string.read, filepath)
end
end end
# Mirror the YARP.parse API by using the serialization API. # Mirror the YARP.parse API by using the serialization API.
@ -206,6 +219,8 @@ module YARP
# native strings instead of Ruby strings because it allows us to use mmap when # native strings instead of Ruby strings because it allows us to use mmap when
# it is available. # it is available.
def self.parse_file(filepath) def self.parse_file(filepath)
LibRubyParser.with_string(filepath) { |string| parse(string.read, filepath) } LibRubyParser::YPString.with(filepath) do |string|
parse(string.read, filepath)
end
end end
end end

View File

@ -66,7 +66,7 @@ dump_input(yp_string_t *input, const char *filepath) {
yp_node_t *node = yp_parse(&parser); yp_node_t *node = yp_parse(&parser);
yp_serialize(&parser, node, &buffer); yp_serialize(&parser, node, &buffer);
VALUE result = rb_str_new(buffer.value, buffer.length); VALUE result = rb_str_new(yp_buffer_value(&buffer), yp_buffer_length(&buffer));
yp_node_destroy(&parser, node); yp_node_destroy(&parser, node);
yp_buffer_free(&buffer); yp_buffer_free(&buffer);
yp_parser_free(&parser); yp_parser_free(&parser);
@ -483,7 +483,7 @@ parse_serialize_file_metadata(VALUE self, VALUE filepath, VALUE metadata) {
if (!yp_string_mapped_init(&input, checked)) return Qnil; if (!yp_string_mapped_init(&input, checked)) return Qnil;
yp_parse_serialize(yp_string_source(&input), yp_string_length(&input), &buffer, check_string(metadata)); yp_parse_serialize(yp_string_source(&input), yp_string_length(&input), &buffer, check_string(metadata));
VALUE result = rb_str_new(buffer.value, buffer.length); VALUE result = rb_str_new(yp_buffer_value(&buffer), yp_buffer_length(&buffer));
yp_buffer_free(&buffer); yp_buffer_free(&buffer);
return result; return result;

View File

@ -2,6 +2,12 @@
#define YP_BUFFER_INITIAL_SIZE 1024 #define YP_BUFFER_INITIAL_SIZE 1024
// Return the size of the yp_buffer_t struct.
size_t
yp_buffer_sizeof(void) {
return sizeof(yp_buffer_t);
}
// Initialize a yp_buffer_t with its default values. // Initialize a yp_buffer_t with its default values.
bool bool
yp_buffer_init(yp_buffer_t *buffer) { yp_buffer_init(yp_buffer_t *buffer) {
@ -12,6 +18,18 @@ yp_buffer_init(yp_buffer_t *buffer) {
return buffer->value != NULL; return buffer->value != NULL;
} }
// Return the value of the buffer.
char *
yp_buffer_value(yp_buffer_t *buffer) {
return buffer->value;
}
// Return the length of the buffer.
size_t
yp_buffer_length(yp_buffer_t *buffer) {
return buffer->length;
}
// Append the given amount of space to the buffer. // Append the given amount of space to the buffer.
static inline void static inline void
yp_buffer_append_length(yp_buffer_t *buffer, size_t length) { yp_buffer_append_length(yp_buffer_t *buffer, size_t length) {

View File

@ -12,16 +12,24 @@
// A yp_buffer_t is a simple memory buffer that stores data in a contiguous // A yp_buffer_t is a simple memory buffer that stores data in a contiguous
// block of memory. It is used to store the serialized representation of a // block of memory. It is used to store the serialized representation of a
// YARP tree. // YARP tree.
// NOTE: keep in sync with YARP::LibRubyParser::Buffer in lib/yarp.rb
typedef struct { typedef struct {
char *value; char *value;
size_t length; size_t length;
size_t capacity; size_t capacity;
} yp_buffer_t; } yp_buffer_t;
// Return the size of the yp_buffer_t struct.
YP_EXPORTED_FUNCTION size_t yp_buffer_sizeof(void);
// Initialize a yp_buffer_t with its default values. // Initialize a yp_buffer_t with its default values.
YP_EXPORTED_FUNCTION bool yp_buffer_init(yp_buffer_t *buffer); YP_EXPORTED_FUNCTION bool yp_buffer_init(yp_buffer_t *buffer);
// Return the value of the buffer.
YP_EXPORTED_FUNCTION char * yp_buffer_value(yp_buffer_t *buffer);
// Return the length of the buffer.
YP_EXPORTED_FUNCTION size_t yp_buffer_length(yp_buffer_t *buffer);
// Append the given amount of space as zeroes to the buffer. // Append the given amount of space as zeroes to the buffer.
void yp_buffer_append_zeroes(yp_buffer_t *buffer, size_t length); void yp_buffer_append_zeroes(yp_buffer_t *buffer, size_t length);