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 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_ecma_ast::*;
|
||||
use swc_ecma_transforms_optimization::debug_assert_valid;
|
||||
@ -247,76 +248,78 @@ impl Pure<'_> {
|
||||
|
||||
/// `new RegExp("([Sap]+)", "ig")` => `/([Sap]+)/gi`
|
||||
fn optimize_regex(&mut self, args: &mut Vec<ExprOrSpread>, span: &mut Span) -> Option<Expr> {
|
||||
if args.is_empty() || args.len() > 2 {
|
||||
return None;
|
||||
}
|
||||
|
||||
// We aborts the method if arguments are not literals.
|
||||
if args.iter().any(|v| {
|
||||
v.spread.is_some()
|
||||
|| match &*v.expr {
|
||||
Expr::Lit(Lit::Str(s)) => {
|
||||
if s.value.contains(|c: char| {
|
||||
// whitelist
|
||||
!c.is_ascii_alphanumeric()
|
||||
&& !matches!(c, '%' | '[' | ']' | '(' | ')' | '{' | '}' | '-' | '+')
|
||||
}) {
|
||||
return true;
|
||||
}
|
||||
if s.value.contains("\\\0") || s.value.contains('/') {
|
||||
return true;
|
||||
}
|
||||
|
||||
false
|
||||
}
|
||||
_ => true,
|
||||
fn valid_pattern(pattern: &Expr) -> Option<JsWord> {
|
||||
if let Expr::Lit(Lit::Str(s)) = pattern {
|
||||
if s.value.contains(|c: char| {
|
||||
// whitelist
|
||||
!c.is_ascii_alphanumeric()
|
||||
&& !matches!(c, '$' | '[' | ']' | '(' | ')' | '{' | '}' | '-' | '+' | '_')
|
||||
}) {
|
||||
None
|
||||
} else {
|
||||
Some(s.value.clone())
|
||||
}
|
||||
}) {
|
||||
return None;
|
||||
} else {
|
||||
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 = match *pattern {
|
||||
Expr::Lit(Lit::Str(s)) => s.value,
|
||||
_ => {
|
||||
unreachable!()
|
||||
}
|
||||
let (pattern, flag) = match args.as_slice() {
|
||||
[ExprOrSpread { spread: None, expr }] => (valid_pattern(expr)?, "".into()),
|
||||
[ExprOrSpread {
|
||||
spread: None,
|
||||
expr: pattern,
|
||||
}, ExprOrSpread {
|
||||
spread: None,
|
||||
expr: flag,
|
||||
}] => (
|
||||
valid_pattern(pattern)?,
|
||||
valid_flag(flag, self.options.ecma)?,
|
||||
),
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
if pattern.is_empty() {
|
||||
// For some expressions `RegExp()` and `RegExp("")`
|
||||
// Theoretically we can use `/(?:)/` to achieve shorter code
|
||||
// But some browsers released in 2015 don't support them yet.
|
||||
args[0].expr = pattern.into();
|
||||
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");
|
||||
|
||||
Some(Expr::Lit(Lit::Regex(Regex {
|
||||
span: *span,
|
||||
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("", "u"));
|
||||
bar(/a/);
|
||||
bar(/a/u);
|
||||
bar(RegExp("a", "u"));
|
||||
|
@ -2,4 +2,4 @@
|
||||
/bar/gi;
|
||||
RegExp(foo);
|
||||
RegExp("bar", ig);
|
||||
/should/afil;
|
||||
RegExp("should", "fail");
|
||||
|
Loading…
Reference in New Issue
Block a user