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
24 changes: 15 additions & 9 deletions src/prism.c
Original file line number Diff line number Diff line change
Expand Up @@ -9135,12 +9135,18 @@ escape_read_warn(pm_parser_t *parser, uint8_t flags, uint8_t flag, const char *t
}

/**
* Read the value of an escape into the buffer.
* Read the value of an escape into the buffer. The control and meta forms can
* wrap another escape (as in \C-\M-x). Those loop back to the top with the
* accumulated flags rather than recursing, so that arbitrarily long chains of
* prefixes use constant stack space regardless of whether the compiler
* eliminates tail calls.
*/
static void
escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expression_buffer, uint8_t flags) {
uint8_t peeked = peek(parser);
switch (peeked) {
uint8_t peeked;

escape_read_next:
switch (peeked = peek(parser)) {
case '\\': {
parser->current.end++;
escape_write_byte(parser, buffer, regular_expression_buffer, flags, escape_byte('\\', flags));
Expand Down Expand Up @@ -9397,8 +9403,8 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
return;
}

escape_read(parser, buffer, regular_expression_buffer, flags | PM_ESCAPE_FLAG_CONTROL);
return;
flags |= PM_ESCAPE_FLAG_CONTROL;
goto escape_read_next;
case ' ':
parser->current.end++;
escape_read_warn(parser, flags, PM_ESCAPE_FLAG_CONTROL, "\\s");
Expand Down Expand Up @@ -9455,8 +9461,8 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
return;
}

escape_read(parser, buffer, regular_expression_buffer, flags | PM_ESCAPE_FLAG_CONTROL);
return;
flags |= PM_ESCAPE_FLAG_CONTROL;
goto escape_read_next;
case ' ':
parser->current.end++;
escape_read_warn(parser, flags, PM_ESCAPE_FLAG_CONTROL, "\\s");
Expand Down Expand Up @@ -9509,8 +9515,8 @@ escape_read(pm_parser_t *parser, pm_buffer_t *buffer, pm_buffer_t *regular_expre
return;
}

escape_read(parser, buffer, regular_expression_buffer, flags | PM_ESCAPE_FLAG_META);
return;
flags |= PM_ESCAPE_FLAG_META;
goto escape_read_next;
case ' ':
parser->current.end++;
escape_read_warn(parser, flags, PM_ESCAPE_FLAG_META, "\\s");
Expand Down
13 changes: 13 additions & 0 deletions test/prism/api/parse_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -210,8 +210,21 @@ def test_scopes
assert Prism.parse_success?("foo(...)", scopes: [Prism.scope(forwarding: [:"..."])])
end

def test_repeated_escape_prefixes
count = 100_000

assert_repeated_escape_prefixes("\"#{"\\M-" * count}a\"", "meta cannot be repeated", count - 1)
assert_repeated_escape_prefixes("?#{"\\C-" * count}a", "control cannot be repeated", count - 1)
assert_repeated_escape_prefixes("/#{"\\c" * count}a/", "control cannot be repeated", count - 1)
end

private

def assert_repeated_escape_prefixes(source, message, expected)
messages = Prism.parse(source).errors.map(&:message)
assert_equal expected, messages.count { |m| m.end_with?(message) }
end

def find_source_file_node(program)
queue = [program]
while (node = queue.shift)
Expand Down
Loading