Skip to content

Commit

Permalink
Refuse to parse empty parenthesized expressions. Related to #1296
Browse files Browse the repository at this point in the history
  • Loading branch information
fabiosantoscode committed Dec 6, 2023
1 parent 75137cd commit 14cc5e1
Show file tree
Hide file tree
Showing 8 changed files with 141 additions and 105 deletions.
20 changes: 12 additions & 8 deletions lib/parse.js
Original file line number Diff line number Diff line change
Expand Up @@ -2330,9 +2330,7 @@ function parse($TEXT, options) {
var ex = async ? new AST_Call({
expression: async,
args: exprs
}) : exprs.length == 1 ? exprs[0] : new AST_Sequence({
expressions: exprs
});
}) : to_expr_or_sequence(start, exprs);
if (ex.start) {
const outer_comments_before = start.comments_before.length;
outer_comments_before_counts.set(start, outer_comments_before);
Expand Down Expand Up @@ -3429,6 +3427,16 @@ function parse($TEXT, options) {
return left;
};

var to_expr_or_sequence = function(start, exprs) {
if (exprs.length === 1) {
return exprs[0];
} else if (exprs.length > 1) {
return new AST_Sequence({ start, expressions: exprs, end: peek() });
} else {
croak("Invalid parenthesized expression");
}
};

var expression = function(commas, no_in) {
var start = S.token;
var exprs = [];
Expand All @@ -3438,11 +3446,7 @@ function parse($TEXT, options) {
next();
commas = true;
}
return exprs.length == 1 ? exprs[0] : new AST_Sequence({
start : start,
expressions : exprs,
end : peek()
});
return to_expr_or_sequence(start, exprs);
};

function in_loop(cont) {
Expand Down
119 changes: 72 additions & 47 deletions test/compress.js
Original file line number Diff line number Diff line change
Expand Up @@ -78,16 +78,42 @@ function test_directory(dir) {
return path.resolve(tests_dir, dir);
}

function as_toplevel(input, mangle_options) {
if (!(input instanceof AST.AST_BlockStatement))
throw new Error("Unsupported input syntax");
for (var i = 0; i < input.body.length; i++) {
var stat = input.body[i];
if (stat instanceof AST.AST_SimpleStatement && stat.body instanceof AST.AST_String)
input.body[i] = new AST.AST_Directive(stat.body);
else break;
function as_template_string_code(ast) {
if (
ast instanceof AST.AST_SimpleStatement
&& ast.body instanceof AST.AST_TemplateString
&& ast.body.segments.length === 1
) {
return ast.body.segments[0].value;
}
}

function as_toplevel(test, input, mangle_options) {
var toplevel, tpl_input;
if (input instanceof AST.AST_BlockStatement) {
for (var i = 0; i < input.body.length; i++) {
var stat = input.body[i];
if (stat instanceof AST.AST_SimpleStatement && stat.body instanceof AST.AST_String)
input.body[i] = new AST.AST_Directive(stat.body);
else break;
}
var toplevel = new AST.AST_Toplevel(input);
} else if (
(tpl_input = as_template_string_code(input))
) {
try {
var toplevel = parse(tpl_input);
} catch (error) {
log(test, "!!! Cannot parse input\n---INPUT---\n{input}\n--PARSE ERROR--\n{error}\n\n", {
input: tpl_input,
error,
});
return null;
}
} else {
log(test, "Unsupported input syntax");
return null;
}
var toplevel = new AST.AST_Toplevel(input);
toplevel.figure_out_scope(mangle_options);
return toplevel;
}
Expand All @@ -106,61 +132,57 @@ async function run_compress_tests() {
var output_options = test.beautify || test.format || {};
var expect;
if (test.expect) {
expect = make_code(as_toplevel(test.expect, test.mangle), output_options);
let toplevel = as_toplevel(test, test.expect, test.mangle);
if (!toplevel) return false;
expect = make_code(toplevel, output_options);
} else {
expect = test.expect_exact;
}
if (test.expect_error && (test.expect || test.expect_exact || test.expect_stdout)) {
log(test, "!!! Test cannot have an `expect_error` with other expect clauses\n", {});
return false;
}
if (test.input instanceof AST.AST_SimpleStatement
&& test.input.body instanceof AST.AST_TemplateString) {
if (test.input.body.segments.length == 1) {
try {
var input = parse(test.input.body.segments[0].value);
} catch (ex) {
if (!test.expect_error) {
log(test, "!!! Test is missing an `expect_error` clause\n", {});
return false;
}
if (test.expect_error instanceof AST.AST_SimpleStatement
&& test.expect_error.body instanceof AST.AST_Object) {
var expect_error = eval("(" + test.expect_error.body.print_to_string() + ")");
var ex_normalized = JSON.parse(JSON.stringify(ex));
ex_normalized.name = ex.name;
for (var prop in expect_error) {
if (prop == "name" || HOP(expect_error, prop)) {
if (expect_error[prop] != ex_normalized[prop]) {
log(test, "!!! Failed `expect_error` property `{prop}`:\n\n---expect_error---\n{expect_error}\n\n---ACTUAL exception--\n{actual_ex}\n\n", {
prop: prop,
expect_error: JSON.stringify(expect_error, null, 2),
actual_ex: JSON.stringify(ex_normalized, null, 2),
});
return false;
}
var bad_input = as_template_string_code(test.bad_input);
if (bad_input != null) {
try {
var input = parse(bad_input);
} catch (ex) {
if (!test.expect_error) {
log(test, "!!! Test is missing an `expect_error` clause\n", {});
return false;
}
if (test.expect_error instanceof AST.AST_SimpleStatement
&& test.expect_error.body instanceof AST.AST_Object) {
var expect_error = eval("(" + test.expect_error.body.print_to_string() + ")");
var ex_normalized = JSON.parse(JSON.stringify(ex));
ex_normalized.name = ex.name;
for (var prop in expect_error) {
if (prop == "name" || HOP(expect_error, prop)) {
if (expect_error[prop] != ex_normalized[prop]) {
log(test, "!!! Failed `expect_error` property `{prop}`:\n\n---expect_error---\n{expect_error}\n\n---ACTUAL exception--\n{actual_ex}\n\n", {
prop: prop,
expect_error: JSON.stringify(expect_error, null, 2),
actual_ex: JSON.stringify(ex_normalized, null, 2),
});
return false;
}
}
return true;
}
log(test, "!!! Test `expect_error` clause must be an object literal\n---expect_error---\n{expect_error}\n\n", {
expect_error: test.expect_error.print_to_string(),
});
return false;
return true;
}
var input_code = make_code(input, output_options);
var input_formatted = test.input.body.segments[0].value;
} else {
log(test, "!!! Test input template string cannot use unescaped ${} expressions\n---INPUT---\n{input}\n\n", {
input: test.input.body.print_to_string(),
log(test, "!!! Test `expect_error` clause must be an object literal\n---expect_error---\n{expect_error}\n\n", {
expect_error: test.expect_error.print_to_string(),
});
return false;
}
var input_code = make_code(input, output_options);
var input_formatted = bad_input;
} else if (test.expect_error) {
log(test, "!!! Test cannot have an `expect_error` clause without a template string `input`\n", {});
log(test, "!!! Test cannot have an `expect_error` clause without a template string `bad_input`\n", {});
return false;
} else {
var input = as_toplevel(test.input, test.mangle);
var input = as_toplevel(test, test.input, test.mangle);
if (!input) return false;
var input_code = make_code(input, output_options);
var input_formatted = make_code(test.input, {
ecma: 2015,
Expand Down Expand Up @@ -422,6 +444,7 @@ function parse_test(file) {
assert.ok(
[
"input",
"bad_input",
"prepend_code",
"expect",
"expect_error",
Expand Down Expand Up @@ -476,6 +499,8 @@ function parse_test(file) {
}

function make_code(ast, options) {
var code_verbatim = as_template_string_code(ast);
if (code_verbatim != null) return code_verbatim;
var stream = OutputStream(options);
ast.print(stream);
return stream.get();
Expand Down
10 changes: 10 additions & 0 deletions test/compress/async.js
Original file line number Diff line number Diff line change
Expand Up @@ -467,3 +467,13 @@ async_generator_object_literal_method: {
}
expect_exact: "foo({baz:4,async*bar(){yield await Promise.resolve(3)},qux:qux});"
}

await_empty_seq: {
bad_input: `"use strict"; await()`
expect_error: ({
name: "SyntaxError",
message: "Invalid parenthesized expression",
line: 1,
col: 21,
})
}
4 changes: 2 additions & 2 deletions test/compress/big_int.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,15 +42,15 @@ big_int_octal: {
}

big_int_no_e: {
input: `1e3n`
bad_input: `1e3n`
expect_error: ({
name: "SyntaxError",
message: "Invalid or unexpected token"
})
}

big_int_bad_digits_for_base: {
input: `0o9n`
bad_input: `0o9n`
expect_error: ({
name: "SyntaxError",
message: "Invalid or unexpected token"
Expand Down
4 changes: 2 additions & 2 deletions test/compress/numbers.js
Original file line number Diff line number Diff line change
Expand Up @@ -288,15 +288,15 @@ numeric_separators: {
}

numeric_separator_trailing_underscore: {
input: `const trailing = 1000_`
bad_input: `const trailing = 1000_`
expect_error: ({
name: "SyntaxError",
message: "Numeric separators are not allowed at the end of numeric literals"
})
}

numeric_separator_double_underscore: {
input: `const double = 1__000`
bad_input: `const double = 1__000`
expect_error: ({
name: "SyntaxError",
message: "Only one underscore is allowed as numeric separator"
Expand Down
9 changes: 3 additions & 6 deletions test/compress/parse_errors.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,17 @@
basic_syntax_error: {
input: `
// notice the code is within a template string
// as opposed to a block so that the test can
// survive a parse error
bad_input: `
var x = 5--;
`
expect_error: ({
name: "SyntaxError",
message: "Invalid use of -- operator",
line: 5,
line: 2,
col: 17,
})
}

invalid_template_string_example: {
input: `
bad_input: `
console.log(\`foo \${100 + 23}
`
expect_error: ({
Expand Down

0 comments on commit 14cc5e1

Please sign in to comment.