[ruby/prism] Reject statements at non-statement posisions

Fix https://github.com/ruby/prism/pull/1547

https://github.com/ruby/prism/commit/cdb643aeab
This commit is contained in:
TSUYUSATO Kitsune 2023-11-20 13:23:48 +09:00 коммит произвёл git
Родитель cc7a5dcd06
Коммит 76f9abced7
4 изменённых файлов: 85 добавлений и 0 удалений

Просмотреть файл

@ -232,6 +232,10 @@ static const char* const diagnostic_messages[PM_DIAGNOSTIC_ID_LEN] = {
[PM_ERR_RESCUE_TERM] = "Expected a closing delimiter for the `rescue` clause",
[PM_ERR_RESCUE_VARIABLE] = "Expected an exception variable after `=>` in a rescue statement",
[PM_ERR_RETURN_INVALID] = "Invalid `return` in a class or module body",
[PM_ERR_STATEMENT_ALIAS] = "Unexpected an `alias` at a non-statement position",
[PM_ERR_STATEMENT_POSTEXE_END] = "Unexpected an `END` at a non-statement position",
[PM_ERR_STATEMENT_PREEXE_BEGIN] = "Unexpected a `BEGIN` at a non-statement position",
[PM_ERR_STATEMENT_UNDEF] = "Unexpected an `undef` at a non-statement position",
[PM_ERR_STRING_CONCATENATION] = "Expected a string for concatenation",
[PM_ERR_STRING_INTERPOLATED_TERM] = "Expected a closing delimiter for the interpolated string",
[PM_ERR_STRING_LITERAL_TERM] = "Expected a closing delimiter for the string literal",

Просмотреть файл

@ -226,6 +226,10 @@ typedef enum {
PM_ERR_RESCUE_TERM,
PM_ERR_RESCUE_VARIABLE,
PM_ERR_RETURN_INVALID,
PM_ERR_STATEMENT_ALIAS,
PM_ERR_STATEMENT_POSTEXE_END,
PM_ERR_STATEMENT_PREEXE_BEGIN,
PM_ERR_STATEMENT_UNDEF,
PM_ERR_STRING_CONCATENATION,
PM_ERR_STRING_INTERPOLATED_TERM,
PM_ERR_STRING_LITERAL_TERM,

Просмотреть файл

@ -14214,6 +14214,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
parser_lex(parser);
return (pm_node_t *) pm_source_line_node_create(parser, &parser->previous);
case PM_TOKEN_KEYWORD_ALIAS: {
if (binding_power != PM_BINDING_POWER_STATEMENT) {
pm_parser_err_current(parser, PM_ERR_STATEMENT_ALIAS);
}
parser_lex(parser);
pm_token_t keyword = parser->previous;
@ -14451,6 +14455,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
return (pm_node_t *) begin_node;
}
case PM_TOKEN_KEYWORD_BEGIN_UPCASE: {
if (binding_power != PM_BINDING_POWER_STATEMENT) {
pm_parser_err_current(parser, PM_ERR_STATEMENT_PREEXE_BEGIN);
}
parser_lex(parser);
pm_token_t keyword = parser->previous;
@ -14901,6 +14909,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
);
}
case PM_TOKEN_KEYWORD_END_UPCASE: {
if (binding_power != PM_BINDING_POWER_STATEMENT) {
pm_parser_err_current(parser, PM_ERR_STATEMENT_POSTEXE_END);
}
parser_lex(parser);
pm_token_t keyword = parser->previous;
@ -14983,6 +14995,10 @@ parse_expression_prefix(pm_parser_t *parser, pm_binding_power_t binding_power) {
parser_lex(parser);
return parse_conditional(parser, PM_CONTEXT_IF);
case PM_TOKEN_KEYWORD_UNDEF: {
if (binding_power != PM_BINDING_POWER_STATEMENT) {
pm_parser_err_current(parser, PM_ERR_STATEMENT_UNDEF);
}
parser_lex(parser);
pm_undef_node_t *undef = pm_undef_node_create(parser, &parser->previous);
pm_node_t *name = parse_undef_argument(parser);
@ -16760,6 +16776,19 @@ parse_expression(pm_parser_t *parser, pm_binding_power_t binding_power, pm_diagn
return node;
}
// The statements BEGIN { ... }, END { ... }, alias ..., and undef ... are statement.
// They cannot follow operators, but they can follow modifiers.
bool is_statement =
PM_NODE_TYPE_P(node, PM_PRE_EXECUTION_NODE) || PM_NODE_TYPE_P(node, PM_POST_EXECUTION_NODE) ||
PM_NODE_TYPE_P(node, PM_ALIAS_GLOBAL_VARIABLE_NODE) || PM_NODE_TYPE_P(node, PM_ALIAS_METHOD_NODE) ||
PM_NODE_TYPE_P(node, PM_UNDEF_NODE);
// TODO: the right condition should `pm_binding_powers[parser->current.type].left > PM_BINDING_POWER_MODIFIER_RESCUE` instead.
// However, it does not work because of the `rescue` modifier's binding power trick.
// After getting to merge #1879, this TODO can be removed.
if (is_statement && pm_binding_powers[parser->current.type].right > PM_BINDING_POWER_MODIFIER_RESCUE + 1) {
return node;
}
// Otherwise we'll look and see if the next token can be parsed as an infix
// operator. If it can, then we'll parse it using parse_expression_infix.
pm_binding_powers_t current_binding_powers;

Просмотреть файл

@ -1722,6 +1722,54 @@ module Prism
]
end
def test_statement_operators
source = <<~RUBY
alias x y + 1
alias x y.z
BEGIN { bar } + 1
BEGIN { bar }.z
END { bar } + 1
END { bar }.z
undef x + 1
undef x.z
RUBY
message1 = 'Expected a newline or semicolon after the statement'
message2 = 'Cannot parse the expression'
assert_errors expression(source), source, [
[message1, 9..9],
[message2, 9..9],
[message1, 23..23],
[message2, 23..23],
[message1, 39..39],
[message2, 39..39],
[message1, 57..57],
[message2, 57..57],
[message1, 71..71],
[message2, 71..71],
[message1, 87..87],
[message2, 87..87],
[message1, 97..97],
[message2, 97..97],
[message1, 109..109],
[message2, 109..109],
]
end
def test_statement_at_non_statement
source = <<~RUBY
foo(alias x y)
foo(BEGIN { bar })
foo(END { bar })
foo(undef x)
RUBY
assert_errors expression(source), source, [
['Unexpected an `alias` at a non-statement position', 4..9],
['Unexpected a `BEGIN` at a non-statement position', 19..24],
['Unexpected an `END` at a non-statement position', 38..41],
['Unexpected an `undef` at a non-statement position', 55..60],
]
end
private
def assert_errors(expected, source, errors, compare_ripper: RUBY_ENGINE == "ruby")