Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions lib/prism/ffi.rb
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@ def self.load_exported_functions_from(header, *functions, callbacks)
end

callback :pm_parse_stream_fgets_t, [:pointer, :int, :pointer], :pointer
callback :pm_parse_stream_feof_t, [:pointer], :int
enum :pm_string_init_result_t, %i[PM_STRING_INIT_SUCCESS PM_STRING_INIT_ERROR_GENERIC PM_STRING_INIT_ERROR_DIRECTORY]
enum :pm_string_query_t, [:PM_STRING_QUERY_ERROR, -1, :PM_STRING_QUERY_FALSE, :PM_STRING_QUERY_TRUE]

Expand All @@ -101,7 +102,7 @@ def self.load_exported_functions_from(header, *functions, callbacks)
"pm_string_query_local",
"pm_string_query_constant",
"pm_string_query_method_name",
[:pm_parse_stream_fgets_t]
[:pm_parse_stream_fgets_t, :pm_parse_stream_feof_t]
)

load_exported_functions_from(
Expand Down Expand Up @@ -281,12 +282,14 @@ def parse_stream(stream, **options)
end
}

eof_callback = -> (_) { stream.eof? }

# In the pm_serialize_parse_stream function it accepts a pointer to the
# IO object as a void* and then passes it through to the callback as the
# third argument, but it never touches it itself. As such, since we have
# access to the IO object already through the closure of the lambda, we
# can pass a null pointer here and not worry.
LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, dump_options(options))
LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, eof_callback, dump_options(options))
Prism.load(source, buffer.read, options.fetch(:freeze, false))
end
end
Expand Down
10 changes: 9 additions & 1 deletion prism/extension.c
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,14 @@ profile_file(int argc, VALUE *argv, VALUE self) {
return Qnil;
}

static int
parse_stream_eof(void *stream) {
if (rb_funcall((VALUE) stream, rb_intern("eof?"), 0)) {
return 1;
}
return 0;
}

/**
* An implementation of fgets that is suitable for use with Ruby IO objects.
*/
Expand Down Expand Up @@ -1034,7 +1042,7 @@ parse_stream(int argc, VALUE *argv, VALUE self) {
pm_parser_t parser;
pm_buffer_t buffer;

pm_node_t *node = pm_parse_stream(&parser, &buffer, (void *) stream, parse_stream_fgets, &options);
pm_node_t *node = pm_parse_stream(&parser, &buffer, (void *) stream, parse_stream_fgets, parse_stream_eof, &options);
rb_encoding *encoding = rb_enc_find(parser.encoding->name);

VALUE source = pm_source_new(&parser, encoding, options.freeze);
Expand Down
19 changes: 13 additions & 6 deletions prism/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -22848,7 +22848,7 @@ pm_parse(pm_parser_t *parser) {
* otherwise return true.
*/
static bool
pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets) {
pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof) {
#define LINE_SIZE 4096
char line[LINE_SIZE];

Expand Down Expand Up @@ -22884,6 +22884,12 @@ pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t
if (strncmp(line, "__END__\r\n", 9) == 0) return false;
break;
}

// All data should be read via gets. If the string returned by gets
// _doesn't_ end with a newline, then we assume we hit EOF condition.
if (stream_feof(stream)) {
break;
}
}

return true;
Expand Down Expand Up @@ -22919,16 +22925,17 @@ pm_parse_stream_unterminated_heredoc_p(pm_parser_t *parser) {
* can stream stdin in to Ruby so we need to support a streaming API.
*/
PRISM_EXPORTED_FUNCTION pm_node_t *
pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const pm_options_t *options) {
pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const pm_options_t *options) {
pm_buffer_init(buffer);

bool eof = pm_parse_stream_read(buffer, stream, stream_fgets);
bool eof = pm_parse_stream_read(buffer, stream, stream_fgets, stream_feof);

pm_parser_init(parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options);
pm_node_t *node = pm_parse(parser);

while (!eof && parser->error_list.size > 0 && (parser->lex_modes.index > 0 || pm_parse_stream_unterminated_heredoc_p(parser))) {
pm_node_destroy(parser, node);
eof = pm_parse_stream_read(buffer, stream, stream_fgets);
eof = pm_parse_stream_read(buffer, stream, stream_fgets, stream_feof);

pm_parser_free(parser);
pm_parser_init(parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options);
Expand Down Expand Up @@ -23020,13 +23027,13 @@ pm_serialize_parse(pm_buffer_t *buffer, const uint8_t *source, size_t size, cons
* given stream into to the given buffer.
*/
PRISM_EXPORTED_FUNCTION void
pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const char *data) {
pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const char *data) {
pm_parser_t parser;
pm_options_t options = { 0 };
pm_options_read(&options, data);

pm_buffer_t parser_buffer;
pm_node_t *node = pm_parse_stream(&parser, &parser_buffer, stream, stream_fgets, &options);
pm_node_t *node = pm_parse_stream(&parser, &parser_buffer, stream, stream_fgets, stream_feof, &options);
pm_serialize_header(buffer);
pm_serialize_content(&parser, node, buffer);
pm_buffer_append_byte(buffer, '\0');
Expand Down
13 changes: 11 additions & 2 deletions prism/prism.h
Original file line number Diff line number Diff line change
Expand Up @@ -87,17 +87,25 @@ PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse(pm_parser_t *parser);
*/
typedef char * (pm_parse_stream_fgets_t)(char *string, int size, void *stream);

/**
* This function is used in pm_parse_stream to check whether a stream is EOF.
* It closely mirrors that of feof so that feof can be used as the
* default implementation.
*/
typedef int (pm_parse_stream_feof_t)(void *stream);

/**
* Parse a stream of Ruby source and return the tree.
*
* @param parser The parser to use.
* @param buffer The buffer to use.
* @param stream The stream to parse.
* @param stream_fgets The function to use to read from the stream.
* @param stream_feof The function to use to determine if the stream has hit eof.
* @param options The optional options to use when parsing.
* @return The AST representing the source.
*/
PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const pm_options_t *options);
PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const pm_options_t *options);

// We optionally support serializing to a binary string. For systems that don't
// want or need this functionality, it can be turned off with the
Expand All @@ -111,9 +119,10 @@ PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buff
* @param buffer The buffer to serialize to.
* @param stream The stream to parse.
* @param stream_fgets The function to use to read from the stream.
* @param stream_feof The function to use to tell if the stream has hit eof.
* @param data The optional data to pass to the parser.
*/
PRISM_EXPORTED_FUNCTION void pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const char *data);
PRISM_EXPORTED_FUNCTION void pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof, const char *data);

/**
* Serialize the given list of comments to the given buffer.
Expand Down
30 changes: 28 additions & 2 deletions prism_compile.c
Original file line number Diff line number Diff line change
Expand Up @@ -11492,6 +11492,18 @@ pm_parse_string(pm_parse_result_t *result, VALUE source, VALUE filepath, VALUE *
return pm_parse_process(result, node, script_lines);
}

struct rb_stdin_wrapper {
VALUE rb_stdin;
int eof_seen;
};

static int
pm_parse_stdin_eof(void *stream)
{
struct rb_stdin_wrapper * wrapped_stdin = (struct rb_stdin_wrapper *)stream;
return wrapped_stdin->eof_seen;
}

/**
* An implementation of fgets that is suitable for use with Ruby IO objects.
*/
Expand All @@ -11500,7 +11512,9 @@ pm_parse_stdin_fgets(char *string, int size, void *stream)
{
RUBY_ASSERT(size > 0);

VALUE line = rb_funcall((VALUE) stream, rb_intern("gets"), 1, INT2FIX(size - 1));
struct rb_stdin_wrapper * wrapped_stdin = (struct rb_stdin_wrapper *)stream;

VALUE line = rb_funcall(wrapped_stdin->rb_stdin, rb_intern("gets"), 1, INT2FIX(size - 1));
if (NIL_P(line)) {
return NULL;
}
Expand All @@ -11511,6 +11525,13 @@ pm_parse_stdin_fgets(char *string, int size, void *stream)
memcpy(string, cstr, length);
string[length] = '\0';

// We're reading strings from stdin via gets. We'll assume that if the
// string is smaller than the requested length, and doesn't end with a
// newline, that we hit EOF.
if (length < (size - 1) && string[length - 1] != '\n') {
wrapped_stdin->eof_seen = 1;
}

return string;
}

Expand All @@ -11527,8 +11548,13 @@ pm_parse_stdin(pm_parse_result_t *result)
{
pm_options_frozen_string_literal_init(&result->options);

struct rb_stdin_wrapper wrapped_stdin = {
rb_stdin,
0
};

pm_buffer_t buffer;
pm_node_t *node = pm_parse_stream(&result->parser, &buffer, (void *) rb_stdin, pm_parse_stdin_fgets, &result->options);
pm_node_t *node = pm_parse_stream(&result->parser, &buffer, (void *) &wrapped_stdin, pm_parse_stdin_fgets, pm_parse_stdin_eof, &result->options);

// Copy the allocated buffer contents into the input string so that it gets
// freed. At this point we've handed over ownership, so we don't need to
Expand Down
2 changes: 1 addition & 1 deletion test/.excludes-zjit/TestFixnum.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
# Issue: https://github.com/Shopify/ruby/issues/646
exclude(/test_/, 'Tests make ZJIT panic')
exclude(/test_/, 'Tests make ZJIT panic on Ubuntu')
2 changes: 0 additions & 2 deletions test/.excludes-zjit/TestProc.rb
Original file line number Diff line number Diff line change
@@ -1,3 +1 @@
exclude(:test_proc_args_pos_rest_block, 'Test crashes with ZJIT')
exclude(:test_proc_args_rest_post_block, 'Test crashes with ZJIT')
exclude(:test_binding_receiver, 'Test fails with ZJIT')
8 changes: 0 additions & 8 deletions test/.excludes-zjit/TestResolvDNS.rb

This file was deleted.

3 changes: 1 addition & 2 deletions test/.excludes-zjit/TestThread.rb
Original file line number Diff line number Diff line change
@@ -1,2 +1 @@
exclude(:test_switch_while_busy_loop, 'Test hangs with ZJIT')
exclude(:test_handle_interrupted?, 'Test fails with ZJIT')
exclude(:test_switch_while_busy_loop, 'Test sometimes hangs with ZJIT')
Loading