Skip to content

Commit dde05e3

Browse files
committed
1 parent 2b89d5a commit dde05e3

9 files changed

Lines changed: 3062 additions & 46 deletions

File tree

crates/oxc_mangler/src/lib.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,11 @@ impl Mangler {
192192

193193
assert!(scope_tree.has_child_ids(), "child_id needs to be generated");
194194

195+
// TODO: implement opt-out of direct-eval in a branch of scopes.
196+
if scope_tree.root_flags().contains_direct_eval() {
197+
return symbol_table;
198+
}
199+
195200
let (exported_names, exported_symbols) = if self.options.top_level {
196201
Mangler::collect_exported_symbols(program)
197202
} else {

crates/oxc_minifier/tests/mangler/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ fn mangle(source_text: &str, top_level: bool) -> String {
1616
CodeGenerator::new().with_symbol_table(Some(symbol_table)).build(&program).code
1717
}
1818

19+
#[test]
20+
fn direct_eval() {
21+
let source_text = "function foo() { let NO_MANGLE; eval('') }";
22+
let mangled = mangle(source_text, false);
23+
assert_eq!(mangled, "function foo() {\n\tlet NO_MANGLE;\n\teval(\"\");\n}\n");
24+
}
25+
1926
#[test]
2027
fn mangler() {
2128
let cases = [

crates/oxc_minifier/tests/peephole/mod.rs

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -82,3 +82,24 @@ fn tagged_template() {
8282
test("foo(true && o.f)", "foo(o.f)");
8383
test("foo(true ? o.f : false)", "foo(o.f)");
8484
}
85+
86+
#[test]
87+
fn eval() {
88+
// Keep indirect-eval syntaxes
89+
test_same("(!0 && eval)(x)");
90+
test_same("(1 ? eval : 2)(x)");
91+
test_same("(1 ? eval : 2)?.(x)");
92+
test_same("(1, eval)(x)");
93+
test_same("(1, eval)?.(x)");
94+
test_same("(3, eval)(x)");
95+
test_same("(4, eval)?.(x)");
96+
test_same("(eval)(x)");
97+
test_same("(eval)?.(x)");
98+
test_same("eval(x)");
99+
test_same("eval(x, y)");
100+
test_same("eval(x,y)");
101+
test_same("eval?.(x)");
102+
test_same("eval?.(x)");
103+
test_same("eval?.(x, y)");
104+
test_same("eval?.(x,y)");
105+
}

crates/oxc_semantic/src/builder.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -564,8 +564,12 @@ impl<'a> Visit<'a> for SemanticBuilder<'a> {
564564
// So we could `.unwrap()` here. But that seems to produce a small perf impact, probably because
565565
// `leave_scope` then doesn't get inlined because of its larger size due to the panic code.
566566
let parent_id = self.scope.get_parent_id(self.current_scope_id);
567+
567568
debug_assert!(parent_id.is_some());
568569
if let Some(parent_id) = parent_id {
570+
if self.scope.get_flags(self.current_scope_id).contains_direct_eval() {
571+
self.scope.get_flags_mut(parent_id).insert(ScopeFlags::DirectEval);
572+
}
569573
self.current_scope_id = parent_id;
570574
}
571575

@@ -2069,6 +2073,11 @@ impl<'a> SemanticBuilder<'a> {
20692073
AstKind::YieldExpression(_) => {
20702074
self.set_function_node_flags(NodeFlags::HasYield);
20712075
}
2076+
AstKind::CallExpression(call_expr) => {
2077+
if !call_expr.optional && call_expr.callee.is_specific_id("eval") {
2078+
self.scope.get_flags_mut(self.current_scope_id).insert(ScopeFlags::DirectEval);
2079+
}
2080+
}
20722081
_ => {}
20732082
}
20742083
}

crates/oxc_semantic/tests/integration/scopes.rs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -261,3 +261,25 @@ fn test_ts_conditional_types() {
261261
.has_number_of_references(0)
262262
.test();
263263
}
264+
265+
#[test]
266+
fn test_eval() {
267+
let direct_evals = ["eval('')", "function foo() { eval('') }"];
268+
for code in direct_evals {
269+
let tester = SemanticTester::js(code);
270+
let semantic = tester.build();
271+
assert!(semantic.scopes().root_flags().contains_direct_eval());
272+
}
273+
274+
let indirect_evals = [
275+
"eval?.('')",
276+
"(0, eval)('')",
277+
"const myEval = eval; myEval('')",
278+
"const obj = { eval }; obj.eval('x + y')",
279+
];
280+
for code in indirect_evals {
281+
let tester = SemanticTester::js(code);
282+
let semantic = tester.build();
283+
assert!(!semantic.scopes().root_flags().contains_direct_eval());
284+
}
285+
}

crates/oxc_syntax/src/scope.rs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ bitflags! {
7979
const GetAccessor = 1 << 7;
8080
const SetAccessor = 1 << 8;
8181
const CatchClause = 1 << 9;
82+
const DirectEval = 1 << 10; // <https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval#direct_and_indirect_eval>
8283
const Var = Self::Top.bits() | Self::Function.bits() | Self::ClassStaticBlock.bits() | Self::TsModuleBlock.bits();
8384
}
8485
}
@@ -153,4 +154,9 @@ impl ScopeFlags {
153154
pub fn is_catch_clause(&self) -> bool {
154155
self.contains(Self::CatchClause)
155156
}
157+
158+
#[inline]
159+
pub fn contains_direct_eval(&self) -> bool {
160+
self.contains(Self::DirectEval)
161+
}
156162
}

tasks/coverage/snapshots/runtime.snap

Lines changed: 2104 additions & 31 deletions
Large diffs are not rendered by default.

tasks/coverage/snapshots/semantic_test262.snap

Lines changed: 886 additions & 1 deletion
Large diffs are not rendered by default.

tasks/coverage/src/runtime/mod.rs

Lines changed: 2 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -65,7 +65,6 @@ static SKIP_INCLUDES: &[&str] = &[
6565

6666
static SKIP_TEST_CASES: &[&str] = &[
6767
// node.js runtime error
68-
"language/eval-code",
6968
"language/expressions/dynamic-import",
7069
"language/global-code/decl-func.js",
7170
"language/module-code",
@@ -77,12 +76,7 @@ static SKIP_TEST_CASES: &[&str] = &[
7776
"language/expressions/prefix-decrement/operator-prefix-decrement-x-calls-putvalue-lhs-newvalue",
7877
];
7978

80-
static SKIP_ESID: &[&str] = &[
81-
// Always fail because they need to perform `eval`
82-
"sec-performeval-rules-in-initializer",
83-
"sec-privatefieldget",
84-
"sec-privatefieldset",
85-
];
79+
static SKIP_ESID: &[&str] = &["sec-privatefieldget", "sec-privatefieldset"];
8680

8781
pub struct Test262RuntimeCase {
8882
base: Test262Case,
@@ -113,13 +107,7 @@ impl Case for Test262RuntimeCase {
113107
let features = &self.base.meta().features;
114108
self.base.should_fail()
115109
|| self.base.skip_test_case()
116-
|| (self
117-
.base
118-
.meta()
119-
.esid
120-
.as_ref()
121-
.is_some_and(|esid| SKIP_ESID.contains(&esid.as_ref()))
122-
&& test262_path.contains("direct-eval"))
110+
|| self.base.meta().esid.as_ref().is_some_and(|esid| SKIP_ESID.contains(&esid.as_ref()))
123111
|| base_path.contains("built-ins")
124112
|| base_path.contains("staging")
125113
|| base_path.contains("intl402")

0 commit comments

Comments
 (0)