Skip to content

Commit bbe6ba7

Browse files
committed
Add an feof callback to Prism stream parsing
Checking the return value of `fgets` isn't good enough to determine whether the input stream has reached EOF or not. `fgets` could have run in to an error and returned NULL. Additionally, we can't just check the buffer contents for `\n` because `fgets` could have read the maximum buffer size (which may not have ended with a `\n`). I want to use this callback to implement a fix for: https://bugs.ruby-lang.org/issues/21188
1 parent e5884cd commit bbe6ba7

4 files changed

Lines changed: 35 additions & 10 deletions

File tree

ext/prism/extension.c

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -994,6 +994,14 @@ profile_file(int argc, VALUE *argv, VALUE self) {
994994
return Qnil;
995995
}
996996

997+
static int
998+
parse_stream_eof(void *stream) {
999+
if (rb_funcall((VALUE) stream, rb_intern("eof?"), 0)) {
1000+
return 1;
1001+
}
1002+
return 0;
1003+
}
1004+
9971005
/**
9981006
* An implementation of fgets that is suitable for use with Ruby IO objects.
9991007
*/
@@ -1034,7 +1042,7 @@ parse_stream(int argc, VALUE *argv, VALUE self) {
10341042
pm_parser_t parser;
10351043
pm_buffer_t buffer;
10361044

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

10401048
VALUE source = pm_source_new(&parser, encoding, options.freeze);

include/prism.h

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -87,17 +87,25 @@ PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse(pm_parser_t *parser);
8787
*/
8888
typedef char * (pm_parse_stream_fgets_t)(char *string, int size, void *stream);
8989

90+
/**
91+
* This function is used in pm_parse_stream to check whether a stream is EOF.
92+
* It closely mirrors that of feof so that feof can be used as the
93+
* default implementation.
94+
*/
95+
typedef int (pm_parse_stream_feof_t)(void *stream);
96+
9097
/**
9198
* Parse a stream of Ruby source and return the tree.
9299
*
93100
* @param parser The parser to use.
94101
* @param buffer The buffer to use.
95102
* @param stream The stream to parse.
96103
* @param stream_fgets The function to use to read from the stream.
104+
* @param stream_feof The function to use to determine if the stream has hit eof.
97105
* @param options The optional options to use when parsing.
98106
* @return The AST representing the source.
99107
*/
100-
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);
108+
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);
101109

102110
// We optionally support serializing to a binary string. For systems that don't
103111
// want or need this functionality, it can be turned off with the
@@ -113,7 +121,7 @@ PRISM_EXPORTED_FUNCTION pm_node_t * pm_parse_stream(pm_parser_t *parser, pm_buff
113121
* @param stream_fgets The function to use to read from the stream.
114122
* @param data The optional data to pass to the parser.
115123
*/
116-
PRISM_EXPORTED_FUNCTION void pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const char *data);
124+
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);
117125

118126
/**
119127
* Serialize the given list of comments to the given buffer.

lib/prism/ffi.rb

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -281,12 +281,14 @@ def parse_stream(stream, **options)
281281
end
282282
}
283283

284+
eof_callback = -> (_) { stream.eof? }
285+
284286
# In the pm_serialize_parse_stream function it accepts a pointer to the
285287
# IO object as a void* and then passes it through to the callback as the
286288
# third argument, but it never touches it itself. As such, since we have
287289
# access to the IO object already through the closure of the lambda, we
288290
# can pass a null pointer here and not worry.
289-
LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, dump_options(options))
291+
LibRubyParser.pm_serialize_parse_stream(buffer.pointer, nil, callback, eof_callback, dump_options(options))
290292
Prism.load(source, buffer.read, options.fetch(:freeze, false))
291293
end
292294
end

src/prism.c

Lines changed: 13 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -22817,7 +22817,7 @@ pm_parse(pm_parser_t *parser) {
2281722817
* otherwise return true.
2281822818
*/
2281922819
static bool
22820-
pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets) {
22820+
pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, pm_parse_stream_feof_t *stream_feof) {
2282122821
#define LINE_SIZE 4096
2282222822
char line[LINE_SIZE];
2282322823

@@ -22853,6 +22853,12 @@ pm_parse_stream_read(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t
2285322853
if (strncmp(line, "__END__\r\n", 9) == 0) return false;
2285422854
break;
2285522855
}
22856+
22857+
// All data should be read via gets. If the string returned by gets
22858+
// _doesn't_ end with a newline, then we assume we hit EOF condition.
22859+
if (stream_feof(stream)) {
22860+
break;
22861+
}
2285622862
}
2285722863

2285822864
return true;
@@ -22888,16 +22894,17 @@ pm_parse_stream_unterminated_heredoc_p(pm_parser_t *parser) {
2288822894
* can stream stdin in to Ruby so we need to support a streaming API.
2288922895
*/
2289022896
PRISM_EXPORTED_FUNCTION pm_node_t *
22891-
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) {
22897+
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) {
2289222898
pm_buffer_init(buffer);
2289322899

22894-
bool eof = pm_parse_stream_read(buffer, stream, stream_fgets);
22900+
bool eof = pm_parse_stream_read(buffer, stream, stream_fgets, stream_feof);
22901+
2289522902
pm_parser_init(parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options);
2289622903
pm_node_t *node = pm_parse(parser);
2289722904

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

2290222909
pm_parser_free(parser);
2290322910
pm_parser_init(parser, (const uint8_t *) pm_buffer_value(buffer), pm_buffer_length(buffer), options);
@@ -22989,13 +22996,13 @@ pm_serialize_parse(pm_buffer_t *buffer, const uint8_t *source, size_t size, cons
2298922996
* given stream into to the given buffer.
2299022997
*/
2299122998
PRISM_EXPORTED_FUNCTION void
22992-
pm_serialize_parse_stream(pm_buffer_t *buffer, void *stream, pm_parse_stream_fgets_t *stream_fgets, const char *data) {
22999+
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) {
2299323000
pm_parser_t parser;
2299423001
pm_options_t options = { 0 };
2299523002
pm_options_read(&options, data);
2299623003

2299723004
pm_buffer_t parser_buffer;
22998-
pm_node_t *node = pm_parse_stream(&parser, &parser_buffer, stream, stream_fgets, &options);
23005+
pm_node_t *node = pm_parse_stream(&parser, &parser_buffer, stream, stream_fgets, stream_feof, &options);
2299923006
pm_serialize_header(buffer);
2300023007
pm_serialize_content(&parser, node, buffer);
2300123008
pm_buffer_append_byte(buffer, '\0');

0 commit comments

Comments
 (0)