Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(html/minifier)/(ecma/minifier): bug with mangling #6455

Merged
merged 9 commits into from Nov 17, 2022
8 changes: 8 additions & 0 deletions crates/swc_ecma_minifier/src/analyzer/mod.rs
Expand Up @@ -1150,6 +1150,14 @@ where
n.visit_children_with(&mut *self.with_ctx(ctx))
}

fn visit_script(&mut self, n: &Script) {
let ctx = Ctx {
skip_standalone: true,
..self.ctx
};
n.visit_children_with(&mut *self.with_ctx(ctx))
}

fn visit_named_export(&mut self, n: &NamedExport) {
if n.src.is_some() {
return;
Expand Down
4 changes: 4 additions & 0 deletions crates/swc_ecma_minifier/src/compress/mod.rs
Expand Up @@ -336,6 +336,10 @@ where
{
noop_visit_mut_type!();

fn visit_mut_script(&mut self, n: &mut Script) {
self.optimize_unit_repeatedly(n);
}

fn visit_mut_module(&mut self, n: &mut Module) {
self.optimize_unit_repeatedly(n);
}
Expand Down
93 changes: 92 additions & 1 deletion crates/swc_ecma_minifier/src/debug.rs
Expand Up @@ -66,7 +66,7 @@ where
///
/// If the cargo feature `debug` is disabled or the environment variable
/// `SWC_RUN` is not `1`, this function is noop.
pub(crate) fn invoke(module: &Module) {
pub(crate) fn invoke_module(module: &Module) {
debug_assert_valid(module);

let _noop_sub = tracing::subscriber::set_default(tracing::subscriber::NoSubscriber::default());
Expand Down Expand Up @@ -153,3 +153,94 @@ pub(crate) fn invoke(module: &Module) {
)
}
}

/// Invokes code using node.js.
///
/// If the cargo feature `debug` is disabled or the environment variable
/// `SWC_RUN` is not `1`, this function is noop.
pub(crate) fn invoke_script(script: &Script) {
debug_assert_valid(script);

let _noop_sub = tracing::subscriber::set_default(tracing::subscriber::NoSubscriber::default());

let should_run =
cfg!(debug_assertions) && cfg!(feature = "debug") && option_env!("SWC_RUN") == Some("1");
let should_check = cfg!(debug_assertions) && option_env!("SWC_CHECK") == Some("1");

if !should_run && !should_check {
return;
}

let script = script
.clone()
.fold_with(&mut hygiene())
.fold_with(&mut fixer(None));
let script = drop_span(script);

let mut buf = vec![];
let cm = Lrc::new(SourceMap::default());

{
let mut emitter = Emitter {
cfg: Default::default(),
cm: cm.clone(),
comments: None,
wr: Box::new(JsWriter::new(cm, "\n", &mut buf, None)),
};

emitter.emit_script(&script).unwrap();
}

let code = String::from_utf8(buf).unwrap();

debug!("Validating with node.js:\n{}", code);

if should_check {
let mut child = Command::new("node")
.arg("-")
.arg("--check")
.stdin(Stdio::piped())
.stdout(Stdio::piped())
.stderr(Stdio::piped())
.spawn()
.expect("failed to spawn node");

{
let child_stdin = child.stdin.as_mut().unwrap();
child_stdin
.write_all(code.as_bytes())
.expect("failed to write");
}

let output = child.wait_with_output().expect("failed to check syntax");

if !output.status.success() {
panic!(
"[SWC_CHECK] Failed to validate code:\n{}\n===== ===== ===== ===== =====\n{}\n{}",
code,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}
} else {
let output = Command::new("node")
.arg("-e")
.arg(&code)
.output()
.expect("[SWC_RUN] failed to validate code using `node`");
if !output.status.success() {
panic!(
"[SWC_RUN] Failed to validate code:\n{}\n===== ===== ===== ===== =====\n{}\n{}",
code,
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr),
);
}

tracing::info!(
"[SWC_RUN]\n{}\n{}",
code,
String::from_utf8_lossy(&output.stdout)
)
}
}
42 changes: 21 additions & 21 deletions crates/swc_ecma_minifier/src/lib.rs
Expand Up @@ -87,7 +87,7 @@ pub(crate) static HEAVY_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT *
pub(crate) static LIGHT_TASK_PARALLELS: Lazy<usize> = Lazy::new(|| *CPU_COUNT * 100);

pub fn optimize(
mut m: Program,
mut n: Program,
_cm: Lrc<SourceMap>,
comments: Option<&dyn Comments>,
mut timings: Option<&mut Timings>,
Expand All @@ -99,7 +99,7 @@ pub fn optimize(
let mut marks = Marks::new();
marks.unresolved_mark = extra.unresolved_mark;

debug_assert_valid(&m);
debug_assert_valid(&n);

if let Some(defs) = options.compress.as_ref().map(|c| &c.global_defs) {
let _timer = timer!("inline global defs");
Expand All @@ -111,15 +111,15 @@ pub fn optimize(

if !defs.is_empty() {
let defs = defs.iter().map(|(k, v)| (k.clone(), v.clone())).collect();
m.visit_mut_with(&mut global_defs::globals_defs(
n.visit_mut_with(&mut global_defs::globals_defs(
defs,
extra.unresolved_mark,
extra.top_level_mark,
));
}
}

let module_info = match &m {
let module_info = match &n {
Program::Script(_) => ModuleInfo::default(),
Program::Module(m) => ModuleInfo {
blackbox_imports: m
Expand Down Expand Up @@ -162,20 +162,20 @@ pub fn optimize(
if let Some(_options) = &options.compress {
let _timer = timer!("precompress");

m.visit_mut_with(&mut precompress_optimizer());
debug_assert_valid(&m);
n.visit_mut_with(&mut precompress_optimizer());
debug_assert_valid(&n);
}

if options.compress.is_some() {
m.visit_mut_with(&mut info_marker(
n.visit_mut_with(&mut info_marker(
options.compress.as_ref(),
comments,
marks,
extra.unresolved_mark,
));
debug_assert_valid(&m);
debug_assert_valid(&n);
}
m.visit_mut_with(&mut unique_scope());
n.visit_mut_with(&mut unique_scope());

if options.wrap {
// TODO: wrap_common_js
Expand All @@ -191,8 +191,8 @@ pub fn optimize(
}
if let Some(options) = &options.compress {
if options.unused {
perform_dce(&mut m, options, extra);
debug_assert_valid(&m);
perform_dce(&mut n, options, extra);
debug_assert_valid(&n);
}
}

Expand All @@ -207,7 +207,7 @@ pub fn optimize(
if options.rename && DISABLE_BUGGY_PASSES {
// toplevel.figure_out_scope(options.mangle);
// TODO: Pass `options.mangle` to name expander.
m.visit_mut_with(&mut name_expander());
n.visit_mut_with(&mut name_expander());
}

if let Some(ref mut t) = timings {
Expand All @@ -217,14 +217,14 @@ pub fn optimize(
{
let _timer = timer!("compress ast");

m.visit_mut_with(&mut compressor(&module_info, marks, options, &Minification))
n.visit_mut_with(&mut compressor(&module_info, marks, options, &Minification))
}

// Again, we don't need to validate ast

let _timer = timer!("postcompress");

m.visit_mut_with(&mut postcompress_optimizer(options));
n.visit_mut_with(&mut postcompress_optimizer(options));

let mut pass = 0;
loop {
Expand All @@ -241,7 +241,7 @@ pub fn optimize(
debug_infinite_loop: false,
},
);
m.visit_mut_with(&mut v);
n.visit_mut_with(&mut v);
if !v.changed() || options.passes <= pass {
break;
}
Expand All @@ -263,30 +263,30 @@ pub fn optimize(
let _timer = timer!("mangle names");
// TODO: base54.reset();

let preserved = idents_to_preserve(mangle.clone(), &m);
let preserved = idents_to_preserve(mangle.clone(), &n);

let chars = CharFreq::compute(
&m,
&n,
&preserved,
SyntaxContext::empty().apply_mark(marks.unresolved_mark),
)
.compile();

m.visit_mut_with(&mut name_mangler(mangle.clone(), preserved, chars));
n.visit_mut_with(&mut name_mangler(mangle.clone(), preserved, chars));

if let Some(property_mangle_options) = &mangle.props {
mangle_properties(&mut m, &module_info, property_mangle_options.clone(), chars);
mangle_properties(&mut n, &module_info, property_mangle_options.clone(), chars);
}
}

m.visit_mut_with(&mut merge_exports());
n.visit_mut_with(&mut merge_exports());

if let Some(ref mut t) = timings {
t.section("hygiene");
t.end_section();
}

m
n
}

fn perform_dce(m: &mut Program, options: &CompressOptions, extra: &ExtraOptions) {
Expand Down
17 changes: 14 additions & 3 deletions crates/swc_ecma_minifier/src/metadata/mod.rs
Expand Up @@ -191,12 +191,23 @@ impl VisitMut for InfoMarker<'_> {

fn visit_mut_lit(&mut self, _: &mut Lit) {}

fn visit_mut_module(&mut self, m: &mut Module) {
m.visit_mut_children_with(self);
fn visit_mut_script(&mut self, n: &mut Script) {
n.visit_mut_children_with(self);

if self.state.is_bundle {
tracing::info!("Running minifier in the bundle mode");
n.span = n.span.apply_mark(self.marks.bundle_of_standalone);
} else {
tracing::info!("Running minifier in the normal mode");
}
}

fn visit_mut_module(&mut self, n: &mut Module) {
n.visit_mut_children_with(self);

if self.state.is_bundle {
tracing::info!("Running minifier in the bundle mode");
m.span = m.span.apply_mark(self.marks.bundle_of_standalone);
n.span = n.span.apply_mark(self.marks.bundle_of_standalone);
} else {
tracing::info!("Running minifier in the normal mode");
}
Expand Down
7 changes: 7 additions & 0 deletions crates/swc_ecma_minifier/src/pass/mangle_names/preserver.rs
Expand Up @@ -121,6 +121,13 @@ impl Visit for Preserver {

fn visit_ident(&mut self, _: &Ident) {}

fn visit_script(&mut self, n: &Script) {
for n in n.body.iter() {
self.in_top_level = true;
n.visit_with(self);
}
}

fn visit_module_items(&mut self, n: &[ModuleItem]) {
for n in n {
self.in_top_level = true;
Expand Down
37 changes: 36 additions & 1 deletion crates/swc_ecma_minifier/src/util/unit.rs
Expand Up @@ -69,7 +69,42 @@ impl CompileUnit for Module {
{
self.visit_mut_with(&mut *visitor);

crate::debug::invoke(self);
crate::debug::invoke_module(self);
}

fn remove_mark(&mut self) -> Mark {
Mark::root()
}
}

impl CompileUnit for Script {
fn is_module() -> bool {
false
}

fn force_dump(&self) -> String {
let _noop_sub =
tracing::subscriber::set_default(tracing::subscriber::NoSubscriber::default());

dump(
&self
.clone()
.fold_with(&mut fixer(None))
.fold_with(&mut hygiene())
.fold_with(&mut as_folder(DropSpan {
preserve_ctxt: false,
})),
true,
)
}

fn apply<V>(&mut self, visitor: &mut V)
where
V: VisitMut,
{
self.visit_mut_with(&mut *visitor);

crate::debug::invoke_script(self);
}

fn remove_mark(&mut self) -> Mark {
Expand Down
9 changes: 4 additions & 5 deletions crates/swc_ecma_transforms_base/src/rename/mod.rs
Expand Up @@ -242,12 +242,11 @@ where

if contains_eval(s, true) {
s.visit_mut_children_with(self);
return;
}

let map = self.get_map(s, false, true);
} else {
let map = self.get_map(s, false, true);

s.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
s.visit_mut_with(&mut rename_with_config(&map, self.config.clone()));
}
}
}

Expand Down
Expand Up @@ -8,5 +8,5 @@
<circle onmouseover='alert("test")' cx=50 cy=50 r=30 style=fill:url(#gradient) />
</svg>
<div type=text onmouseover=myFunction()>test</div>
<a href=https://datacadamia.com onclick="console.log(`Navigation to ${this.href} cancelled`);return false">Click me</a>
<a href=https://datacadamia.com onclick="console.log(`Navigation to ${this.href} cancelled`);return false">Click me</a>
<a href=https://datacadamia.com onclick="return console.log(`Navigation to ${this.href} cancelled`),!1">Click me</a>
<a href=https://datacadamia.com onclick="return console.log(`Navigation to ${this.href} cancelled`),!1">Click me</a>
@@ -1,6 +1,6 @@
<!doctype html><script defer>console.log()</script><script>console.log();console.log()</script><script type=module>console.log(),console.log()</script><script>window.jQuery||document.write('<script src="jquery.js"><\/script>')</script><script type=text/html>
<!doctype html><script defer>console.log()</script><script>console.log(),console.log()</script><script type=module>console.log(),console.log()</script><script>window.jQuery||document.write('<script src="jquery.js"><\/script>')</script><script type=text/html>
<div>
test
</div>
<!-- aa -->\n
</script><script type="">alert(1)</script><script type=modules>alert(1)</script><script>alert(1);alert(1)</script><script type=module src=app.mjs></script><script nomodule defer src=classic-app-bundle.js></script><script>alert(1)</script><script type=text/vbscript>MsgBox("foo bar")</script><script type="">MsgBox("foo bar")</script><script type=;;;;;>MsgBox("foo bar")</script><script>alert(1);alert(1);alert(1);alert(1);alert(1);alert(1);alert(1)</script>
</script><script type="">alert(1)</script><script type=modules>alert(1)</script><script>alert(1),alert(1)</script><script type=module src=app.mjs></script><script nomodule defer src=classic-app-bundle.js></script><script>alert(1)</script><script type=text/vbscript>MsgBox("foo bar")</script><script type="">MsgBox("foo bar")</script><script type=;;;;;>MsgBox("foo bar")</script><script>alert(1),alert(1),alert(1),alert(1),alert(1),alert(1),alert(1)</script>