mirror of
https://github.com/swc-project/swc.git
synced 2024-12-22 13:11: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)
|
||||
if: matrix.settings.crate == 'swc_plugin_runner'
|
||||
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
|
||||
|
||||
- name: Run cargo test (swc_ecma_minifier)
|
||||
|
23
Cargo.lock
generated
23
Cargo.lock
generated
@ -2332,6 +2332,15 @@ dependencies = [
|
||||
"unicode-ident",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "psm"
|
||||
version = "0.1.21"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "5787f7cda34e3033a72192c018bc5883100330f362ef279a8cbccfce8bb4e874"
|
||||
dependencies = [
|
||||
"cc",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "ptr_meta"
|
||||
version = "0.1.4"
|
||||
@ -2936,6 +2945,19 @@ version = "1.2.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
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]]
|
||||
name = "standback"
|
||||
version = "0.2.17"
|
||||
@ -3695,6 +3717,7 @@ dependencies = [
|
||||
"serde",
|
||||
"serde_json",
|
||||
"smallvec",
|
||||
"stacker",
|
||||
"swc_atoms",
|
||||
"swc_common",
|
||||
"swc_ecma_ast",
|
||||
|
@ -37,6 +37,9 @@ swc_ecma_visit = { version = "0.82.3", path = "../swc_ecma_visit", optional = tr
|
||||
tracing = "0.1.32"
|
||||
typed-arena = "2.0.1"
|
||||
|
||||
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
|
||||
stacker = "0.1.15"
|
||||
|
||||
[dev-dependencies]
|
||||
criterion = "0.3"
|
||||
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_script, Script, |p| { p.parse_script() });
|
||||
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,15 +486,18 @@ impl<'a, I: Tokens> Parser<I> {
|
||||
}
|
||||
|
||||
let cons = {
|
||||
// Annex B
|
||||
if !self.ctx().strict && is!(self, "function") {
|
||||
// TODO: report error?
|
||||
}
|
||||
let ctx = Context {
|
||||
ignore_else_clause: false,
|
||||
..self.ctx()
|
||||
};
|
||||
self.with_ctx(ctx).parse_stmt(false).map(Box::new)?
|
||||
// Prevent stack overflow
|
||||
crate::maybe_grow(512 * 1024, 2 * 1024 * 1024, || {
|
||||
// Annex B
|
||||
if !self.ctx().strict && is!(self, "function") {
|
||||
// TODO: report error?
|
||||
}
|
||||
let ctx = Context {
|
||||
ignore_else_clause: false,
|
||||
..self.ctx()
|
||||
};
|
||||
self.with_ctx(ctx).parse_stmt(false).map(Box::new)
|
||||
})?
|
||||
};
|
||||
|
||||
// We parse `else` branch iteratively, to avoid stack overflow
|
||||
@ -516,13 +519,15 @@ impl<'a, I: Tokens> Parser<I> {
|
||||
}
|
||||
|
||||
if !is!(self, "if") {
|
||||
let ctx = Context {
|
||||
ignore_else_clause: false,
|
||||
..self.ctx()
|
||||
};
|
||||
|
||||
// As we eat `else` above, we need to parse statement once.
|
||||
let last = self.with_ctx(ctx).parse_stmt(false)?;
|
||||
let last = crate::maybe_grow(512 * 1024, 2 * 1024 * 1024, || {
|
||||
let ctx = Context {
|
||||
ignore_else_clause: false,
|
||||
..self.ctx()
|
||||
};
|
||||
|
||||
self.with_ctx(ctx).parse_stmt(false)
|
||||
})?;
|
||||
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