mirror of
https://github.com/swc-project/swc.git
synced 2024-11-28 02:29:04 +03:00
fix(es/minifier): Bailout regex optimization on invalid flags (#7020)
This commit is contained in:
parent
2a8f6aeb37
commit
1318afe2b4
@ -1,6 +1,7 @@
|
|||||||
use std::{fmt::Write, iter::once, num::FpCategory};
|
use std::{fmt::Write, iter::once, num::FpCategory};
|
||||||
|
|
||||||
use swc_atoms::js_word;
|
use rustc_hash::FxHashSet;
|
||||||
|
use swc_atoms::{js_word, JsWord};
|
||||||
use swc_common::{iter::IdentifyLast, util::take::Take, Span, DUMMY_SP};
|
use swc_common::{iter::IdentifyLast, util::take::Take, Span, DUMMY_SP};
|
||||||
use swc_ecma_ast::*;
|
use swc_ecma_ast::*;
|
||||||
use swc_ecma_transforms_optimization::debug_assert_valid;
|
use swc_ecma_transforms_optimization::debug_assert_valid;
|
||||||
@ -247,76 +248,78 @@ impl Pure<'_> {
|
|||||||
|
|
||||||
/// `new RegExp("([Sap]+)", "ig")` => `/([Sap]+)/gi`
|
/// `new RegExp("([Sap]+)", "ig")` => `/([Sap]+)/gi`
|
||||||
fn optimize_regex(&mut self, args: &mut Vec<ExprOrSpread>, span: &mut Span) -> Option<Expr> {
|
fn optimize_regex(&mut self, args: &mut Vec<ExprOrSpread>, span: &mut Span) -> Option<Expr> {
|
||||||
if args.is_empty() || args.len() > 2 {
|
fn valid_pattern(pattern: &Expr) -> Option<JsWord> {
|
||||||
return None;
|
if let Expr::Lit(Lit::Str(s)) = pattern {
|
||||||
}
|
if s.value.contains(|c: char| {
|
||||||
|
// whitelist
|
||||||
// We aborts the method if arguments are not literals.
|
!c.is_ascii_alphanumeric()
|
||||||
if args.iter().any(|v| {
|
&& !matches!(c, '$' | '[' | ']' | '(' | ')' | '{' | '}' | '-' | '+' | '_')
|
||||||
v.spread.is_some()
|
}) {
|
||||||
|| match &*v.expr {
|
None
|
||||||
Expr::Lit(Lit::Str(s)) => {
|
} else {
|
||||||
if s.value.contains(|c: char| {
|
Some(s.value.clone())
|
||||||
// whitelist
|
|
||||||
!c.is_ascii_alphanumeric()
|
|
||||||
&& !matches!(c, '%' | '[' | ']' | '(' | ')' | '{' | '}' | '-' | '+')
|
|
||||||
}) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if s.value.contains("\\\0") || s.value.contains('/') {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
false
|
|
||||||
}
|
|
||||||
_ => true,
|
|
||||||
}
|
}
|
||||||
}) {
|
} else {
|
||||||
return None;
|
None
|
||||||
|
}
|
||||||
|
}
|
||||||
|
fn valid_flag(flag: &Expr, es_version: EsVersion) -> Option<JsWord> {
|
||||||
|
if let Expr::Lit(Lit::Str(s)) = flag {
|
||||||
|
let mut set = FxHashSet::default();
|
||||||
|
for c in s.value.chars() {
|
||||||
|
if !(matches!(c, 'g' | 'i' | 'm')
|
||||||
|
|| (es_version >= EsVersion::Es2015 && matches!(c, 'u' | 'y'))
|
||||||
|
|| (es_version >= EsVersion::Es2018 && matches!(c, 's')))
|
||||||
|
|| (es_version >= EsVersion::Es2022 && matches!(c, 'd'))
|
||||||
|
{
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
|
||||||
|
if !set.insert(c) {
|
||||||
|
return None;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(s.value.clone())
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let pattern = args[0].expr.take();
|
let (pattern, flag) = match args.as_slice() {
|
||||||
|
[ExprOrSpread { spread: None, expr }] => (valid_pattern(expr)?, "".into()),
|
||||||
let pattern = match *pattern {
|
[ExprOrSpread {
|
||||||
Expr::Lit(Lit::Str(s)) => s.value,
|
spread: None,
|
||||||
_ => {
|
expr: pattern,
|
||||||
unreachable!()
|
}, ExprOrSpread {
|
||||||
}
|
spread: None,
|
||||||
|
expr: flag,
|
||||||
|
}] => (
|
||||||
|
valid_pattern(pattern)?,
|
||||||
|
valid_flag(flag, self.options.ecma)?,
|
||||||
|
),
|
||||||
|
_ => return None,
|
||||||
};
|
};
|
||||||
|
|
||||||
if pattern.is_empty() {
|
if pattern.is_empty() {
|
||||||
// For some expressions `RegExp()` and `RegExp("")`
|
// For some expressions `RegExp()` and `RegExp("")`
|
||||||
// Theoretically we can use `/(?:)/` to achieve shorter code
|
// Theoretically we can use `/(?:)/` to achieve shorter code
|
||||||
// But some browsers released in 2015 don't support them yet.
|
// But some browsers released in 2015 don't support them yet.
|
||||||
args[0].expr = pattern.into();
|
|
||||||
return None;
|
return None;
|
||||||
}
|
}
|
||||||
|
|
||||||
let flags = args
|
|
||||||
.get_mut(1)
|
|
||||||
.map(|v| v.expr.take())
|
|
||||||
.map(|v| match *v {
|
|
||||||
Expr::Lit(Lit::Str(s)) => {
|
|
||||||
assert!(s.value.is_ascii());
|
|
||||||
|
|
||||||
let s = s.value.to_string();
|
|
||||||
let mut bytes = s.into_bytes();
|
|
||||||
bytes.sort_unstable();
|
|
||||||
|
|
||||||
String::from_utf8(bytes).unwrap().into()
|
|
||||||
}
|
|
||||||
_ => {
|
|
||||||
unreachable!()
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
|
|
||||||
report_change!("Optimized regex");
|
report_change!("Optimized regex");
|
||||||
|
|
||||||
Some(Expr::Lit(Lit::Regex(Regex {
|
Some(Expr::Lit(Lit::Regex(Regex {
|
||||||
span: *span,
|
span: *span,
|
||||||
exp: pattern.into(),
|
exp: pattern.into(),
|
||||||
flags,
|
flags: {
|
||||||
|
let flag = flag.to_string();
|
||||||
|
let mut bytes = flag.into_bytes();
|
||||||
|
bytes.sort_unstable();
|
||||||
|
|
||||||
|
String::from_utf8(bytes).unwrap().into()
|
||||||
|
},
|
||||||
})))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
bar(RegExp(""));
|
bar(RegExp(""));
|
||||||
bar(RegExp("", "u"));
|
bar(RegExp("", "u"));
|
||||||
bar(/a/);
|
bar(/a/);
|
||||||
bar(/a/u);
|
bar(RegExp("a", "u"));
|
||||||
|
@ -2,4 +2,4 @@
|
|||||||
/bar/gi;
|
/bar/gi;
|
||||||
RegExp(foo);
|
RegExp(foo);
|
||||||
RegExp("bar", ig);
|
RegExp("bar", ig);
|
||||||
/should/afil;
|
RegExp("should", "fail");
|
||||||
|
Loading…
Reference in New Issue
Block a user