mirror of
https://github.com/swc-project/swc.git
synced 2024-12-22 21:21:31 +03:00
fix(es/parser): Fix stack overflow due to deeply nested if (#6910)
**Related issue:** - Closes https://github.com/swc-project/swc/issues/6813.
This commit is contained in:
parent
bd0072511b
commit
f36d945a33
1
.github/workflows/CI.yml
vendored
1
.github/workflows/CI.yml
vendored
@ -660,6 +660,7 @@ jobs:
|
|||||||
- name: Run cargo test (plugin)
|
- name: Run cargo test (plugin)
|
||||||
if: matrix.settings.crate == 'swc_plugin_runner'
|
if: matrix.settings.crate == 'swc_plugin_runner'
|
||||||
run: |
|
run: |
|
||||||
|
# export CARGO_TARGET_DIR=$(pwd)/target
|
||||||
cargo test -p swc_plugin_runner --release --features plugin_transform_schema_v1 --features rkyv-impl --features ecma --features css
|
cargo test -p swc_plugin_runner --release --features plugin_transform_schema_v1 --features rkyv-impl --features ecma --features css
|
||||||
|
|
||||||
- name: Run cargo test (swc_ecma_minifier)
|
- name: Run cargo test (swc_ecma_minifier)
|
||||||
|
23
Cargo.lock
generated
23
Cargo.lock
generated
@ -2332,6 +2332,15 @@ dependencies = [
|
|||||||
"unicode-ident",
|
"unicode-ident",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "psm"
|
||||||
|
version = "0.1.21"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "ptr_meta"
|
name = "ptr_meta"
|
||||||
version = "0.1.4"
|
version = "0.1.4"
|
||||||
@ -2936,6 +2945,19 @@ version = "1.2.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "stacker"
|
||||||
|
version = "0.1.15"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c886bd4480155fd3ef527d45e9ac8dd7118a898a46530b7b94c3e21866259fce"
|
||||||
|
dependencies = [
|
||||||
|
"cc",
|
||||||
|
"cfg-if 1.0.0",
|
||||||
|
"libc",
|
||||||
|
"psm",
|
||||||
|
"winapi",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "standback"
|
name = "standback"
|
||||||
version = "0.2.17"
|
version = "0.2.17"
|
||||||
@ -3695,6 +3717,7 @@ dependencies = [
|
|||||||
"serde",
|
"serde",
|
||||||
"serde_json",
|
"serde_json",
|
||||||
"smallvec",
|
"smallvec",
|
||||||
|
"stacker",
|
||||||
"swc_atoms",
|
"swc_atoms",
|
||||||
"swc_common",
|
"swc_common",
|
||||||
"swc_ecma_ast",
|
"swc_ecma_ast",
|
||||||
|
@ -37,6 +37,9 @@ swc_ecma_visit = { version = "0.82.3", path = "../swc_ecma_visit", optional = tr
|
|||||||
tracing = "0.1.32"
|
tracing = "0.1.32"
|
||||||
typed-arena = "2.0.1"
|
typed-arena = "2.0.1"
|
||||||
|
|
||||||
|
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||||
|
stacker = "0.1.15"
|
||||||
|
|
||||||
[dev-dependencies]
|
[dev-dependencies]
|
||||||
criterion = "0.3"
|
criterion = "0.3"
|
||||||
pretty_assertions = "1.1"
|
pretty_assertions = "1.1"
|
||||||
|
@ -446,3 +446,13 @@ expose!(parse_file_as_expr, Box<Expr>, |p| {
|
|||||||
expose!(parse_file_as_module, Module, |p| { p.parse_module() });
|
expose!(parse_file_as_module, Module, |p| { p.parse_module() });
|
||||||
expose!(parse_file_as_script, Script, |p| { p.parse_script() });
|
expose!(parse_file_as_script, Script, |p| { p.parse_script() });
|
||||||
expose!(parse_file_as_program, Program, |p| { p.parse_program() });
|
expose!(parse_file_as_program, Program, |p| { p.parse_program() });
|
||||||
|
|
||||||
|
#[inline(always)]
|
||||||
|
#[cfg_attr(target_arch = "wasm32", allow(unused))]
|
||||||
|
fn maybe_grow<R, F: FnOnce() -> R>(red_zone: usize, stack_size: usize, callback: F) -> R {
|
||||||
|
#[cfg(target_arch = "wasm32")]
|
||||||
|
return callback();
|
||||||
|
|
||||||
|
#[cfg(not(target_arch = "wasm32"))]
|
||||||
|
return stacker::maybe_grow(red_zone, stack_size, callback);
|
||||||
|
}
|
||||||
|
@ -486,6 +486,8 @@ impl<'a, I: Tokens> Parser<I> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
let cons = {
|
let cons = {
|
||||||
|
// Prevent stack overflow
|
||||||
|
crate::maybe_grow(512 * 1024, 2 * 1024 * 1024, || {
|
||||||
// Annex B
|
// Annex B
|
||||||
if !self.ctx().strict && is!(self, "function") {
|
if !self.ctx().strict && is!(self, "function") {
|
||||||
// TODO: report error?
|
// TODO: report error?
|
||||||
@ -494,7 +496,8 @@ impl<'a, I: Tokens> Parser<I> {
|
|||||||
ignore_else_clause: false,
|
ignore_else_clause: false,
|
||||||
..self.ctx()
|
..self.ctx()
|
||||||
};
|
};
|
||||||
self.with_ctx(ctx).parse_stmt(false).map(Box::new)?
|
self.with_ctx(ctx).parse_stmt(false).map(Box::new)
|
||||||
|
})?
|
||||||
};
|
};
|
||||||
|
|
||||||
// We parse `else` branch iteratively, to avoid stack overflow
|
// We parse `else` branch iteratively, to avoid stack overflow
|
||||||
@ -516,13 +519,15 @@ impl<'a, I: Tokens> Parser<I> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
if !is!(self, "if") {
|
if !is!(self, "if") {
|
||||||
|
// As we eat `else` above, we need to parse statement once.
|
||||||
|
let last = crate::maybe_grow(512 * 1024, 2 * 1024 * 1024, || {
|
||||||
let ctx = Context {
|
let ctx = Context {
|
||||||
ignore_else_clause: false,
|
ignore_else_clause: false,
|
||||||
..self.ctx()
|
..self.ctx()
|
||||||
};
|
};
|
||||||
|
|
||||||
// As we eat `else` above, we need to parse statement once.
|
self.with_ctx(ctx).parse_stmt(false)
|
||||||
let last = self.with_ctx(ctx).parse_stmt(false)?;
|
})?;
|
||||||
break Some(last);
|
break Some(last);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
115
crates/swc_ecma_parser/tests/js.rs
Normal file
115
crates/swc_ecma_parser/tests/js.rs
Normal file
@ -0,0 +1,115 @@
|
|||||||
|
#![allow(clippy::needless_update)]
|
||||||
|
|
||||||
|
use std::{
|
||||||
|
fs::File,
|
||||||
|
io::Read,
|
||||||
|
path::{Path, PathBuf},
|
||||||
|
};
|
||||||
|
|
||||||
|
use swc_common::{comments::SingleThreadedComments, FileName};
|
||||||
|
use swc_ecma_ast::*;
|
||||||
|
use swc_ecma_parser::{lexer::Lexer, EsConfig, PResult, Parser, StringInput, Syntax};
|
||||||
|
use swc_ecma_visit::FoldWith;
|
||||||
|
use testing::StdErr;
|
||||||
|
|
||||||
|
use crate::common::Normalizer;
|
||||||
|
|
||||||
|
#[path = "common/mod.rs"]
|
||||||
|
mod common;
|
||||||
|
|
||||||
|
#[testing::fixture("tests/js/**/*.js")]
|
||||||
|
fn spec(file: PathBuf) {
|
||||||
|
let output = file.parent().unwrap().join(format!(
|
||||||
|
"{}.json",
|
||||||
|
file.file_name().unwrap().to_string_lossy()
|
||||||
|
));
|
||||||
|
run_spec(&file, &output);
|
||||||
|
}
|
||||||
|
|
||||||
|
fn run_spec(file: &Path, output_json: &Path) {
|
||||||
|
let file_name = file
|
||||||
|
.display()
|
||||||
|
.to_string()
|
||||||
|
.replace("\\\\", "/")
|
||||||
|
.replace('\\', "/");
|
||||||
|
|
||||||
|
{
|
||||||
|
// Drop to reduce memory usage.
|
||||||
|
//
|
||||||
|
// Because the test suite contains lots of test cases, it results in oom in
|
||||||
|
// github actions.
|
||||||
|
let input = {
|
||||||
|
let mut buf = String::new();
|
||||||
|
File::open(file).unwrap().read_to_string(&mut buf).unwrap();
|
||||||
|
buf
|
||||||
|
};
|
||||||
|
|
||||||
|
eprintln!(
|
||||||
|
"\n\n========== Running reference test {}\nSource:\n{}\n",
|
||||||
|
file_name, input
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
with_parser(false, file, false, |p, _| {
|
||||||
|
let program = p.parse_program()?.fold_with(&mut Normalizer {
|
||||||
|
drop_span: false,
|
||||||
|
is_test262: false,
|
||||||
|
});
|
||||||
|
|
||||||
|
let json =
|
||||||
|
serde_json::to_string_pretty(&program).expect("failed to serialize module as json");
|
||||||
|
|
||||||
|
if StdErr::from(json).compare_to_file(output_json).is_err() {
|
||||||
|
panic!()
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(())
|
||||||
|
})
|
||||||
|
.map_err(|_| ())
|
||||||
|
.unwrap();
|
||||||
|
}
|
||||||
|
|
||||||
|
fn with_parser<F, Ret>(
|
||||||
|
treat_error_as_bug: bool,
|
||||||
|
file_name: &Path,
|
||||||
|
shift: bool,
|
||||||
|
f: F,
|
||||||
|
) -> Result<Ret, StdErr>
|
||||||
|
where
|
||||||
|
F: FnOnce(&mut Parser<Lexer<StringInput<'_>>>, &SingleThreadedComments) -> PResult<Ret>,
|
||||||
|
{
|
||||||
|
::testing::run_test(treat_error_as_bug, |cm, handler| {
|
||||||
|
if shift {
|
||||||
|
cm.new_source_file(FileName::Anon, "".into());
|
||||||
|
}
|
||||||
|
|
||||||
|
let comments = SingleThreadedComments::default();
|
||||||
|
|
||||||
|
let fm = cm
|
||||||
|
.load_file(file_name)
|
||||||
|
.unwrap_or_else(|e| panic!("failed to load {}: {}", file_name.display(), e));
|
||||||
|
|
||||||
|
let lexer = Lexer::new(
|
||||||
|
Syntax::Es(EsConfig {
|
||||||
|
..Default::default()
|
||||||
|
}),
|
||||||
|
EsVersion::Es2015,
|
||||||
|
(&*fm).into(),
|
||||||
|
Some(&comments),
|
||||||
|
);
|
||||||
|
|
||||||
|
let mut p = Parser::new_from(lexer);
|
||||||
|
|
||||||
|
let res = f(&mut p, &comments).map_err(|e| e.into_diagnostic(handler).emit());
|
||||||
|
|
||||||
|
for err in p.take_errors() {
|
||||||
|
err.into_diagnostic(handler).emit();
|
||||||
|
}
|
||||||
|
|
||||||
|
if handler.has_errors() {
|
||||||
|
return Err(());
|
||||||
|
}
|
||||||
|
|
||||||
|
res
|
||||||
|
})
|
||||||
|
}
|
201
crates/swc_ecma_parser/tests/js/stack-overflow/1.js
Normal file
201
crates/swc_ecma_parser/tests/js/stack-overflow/1.js
Normal file
@ -0,0 +1,201 @@
|
|||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
if (true) {
|
||||||
|
console.log(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
2870
crates/swc_ecma_parser/tests/js/stack-overflow/1.js.json
Normal file
2870
crates/swc_ecma_parser/tests/js/stack-overflow/1.js.json
Normal file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user