fix(es/minifier): Bailout regex optimization on invalid flags (#7020)

This commit is contained in:
Austaras 2023-03-07 10:05:04 +08:00 committed by GitHub
parent 2a8f6aeb37
commit 1318afe2b4
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 56 deletions

View File

@ -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()
},
}))) })))
} }

View File

@ -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"));

View File

@ -2,4 +2,4 @@
/bar/gi; /bar/gi;
RegExp(foo); RegExp(foo);
RegExp("bar", ig); RegExp("bar", ig);
/should/afil; RegExp("should", "fail");