Skip to main content

boa_engine/builtins/eval/
mod.rs

1//! Boa's implementation of ECMAScript's global `eval` function.
2//!
3//! The `eval()` function evaluates ECMAScript code represented as a string.
4//!
5//! More information:
6//!  - [ECMAScript reference][spec]
7//!  - [MDN documentation][mdn]
8//!
9//! [spec]: https://tc39.es/ecma262/#sec-eval-x
10//! [mdn]: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/eval
11
12use crate::{
13    Context, JsArgs, JsExpect, JsResult, JsString, JsValue, SpannedSourceText,
14    builtins::{BuiltInObject, function::OrdinaryFunction},
15    bytecompiler::{ByteCompiler, prepare_eval_declaration_instantiation},
16    context::intrinsics::Intrinsics,
17    environments::SavedEnvironments,
18    error::JsNativeError,
19    js_string,
20    object::JsObject,
21    realm::Realm,
22    spanned_source_text::SourceText,
23    string::StaticJsStrings,
24    vm::{CallFrame, CallFrameFlags, source_info::SourcePath},
25};
26use boa_ast::{
27    operations::{ContainsSymbol, contains, contains_arguments},
28    scope::Scope,
29};
30use boa_gc::Gc;
31use boa_parser::{Parser, Source};
32
33use super::{BuiltInBuilder, IntrinsicObject};
34
35#[derive(Debug, Clone, Copy)]
36pub(crate) struct Eval;
37
38impl IntrinsicObject for Eval {
39    fn init(realm: &Realm) {
40        BuiltInBuilder::callable_with_intrinsic::<Self>(realm, Self::eval)
41            .name(Self::NAME)
42            .length(1)
43            .build();
44    }
45
46    fn get(intrinsics: &Intrinsics) -> JsObject {
47        intrinsics.objects().eval().into()
48    }
49}
50
51impl BuiltInObject for Eval {
52    const NAME: JsString = StaticJsStrings::EVAL;
53}
54
55impl Eval {
56    /// `19.2.1 eval ( x )`
57    ///
58    /// More information:
59    ///  - [ECMAScript reference][spec]
60    ///
61    /// [spec]: https://tc39.es/ecma262/#sec-eval-x
62    fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
63        // 1. Return ? PerformEval(x, false, false).
64        Self::perform_eval(args.get_or_undefined(0), false, None, false, context)
65    }
66
67    /// `19.2.1.1 PerformEval ( x, strictCaller, direct )`
68    ///
69    /// More information:
70    ///  - [ECMAScript reference][spec]
71    ///
72    /// [spec]: https://tc39.es/ecma262/#sec-performeval
73    pub(crate) fn perform_eval(
74        x: &JsValue,
75        direct: bool,
76        lexical_scope: Option<Scope>,
77        mut strict: bool,
78        context: &mut Context,
79    ) -> JsResult<JsValue> {
80        bitflags::bitflags! {
81            /// Flags used to throw early errors on invalid `eval` calls.
82            #[derive(Default)]
83            struct Flags: u8 {
84                const IN_FUNCTION = 0b0001;
85                const IN_METHOD = 0b0010;
86                const IN_DERIVED_CONSTRUCTOR = 0b0100;
87                const IN_CLASS_FIELD_INITIALIZER = 0b1000;
88            }
89        }
90
91        /// Possible actions that can be executed after exiting this function to restore the environment to its
92        /// original state.
93        enum EnvStackAction {
94            Truncate(usize),
95            Restore(SavedEnvironments),
96        }
97
98        // 1. Assert: If direct is false, then strictCaller is also false.
99        debug_assert!(direct || !strict);
100
101        // 2. If Type(x) is not String, return x.
102        // TODO: rework parser to take an iterator of `u32` unicode codepoints
103        let Some(x) = x.as_string() else {
104            return Ok(x.clone());
105        };
106
107        // Because of implementation details the following code differs from the spec.
108
109        // 3. Let evalRealm be the current Realm Record.
110        // 4. NOTE: In the case of a direct eval, evalRealm is the realm of both the caller of eval
111        // and of the eval function itself.
112        let eval_realm = context.realm().clone();
113
114        // 5. Perform ? HostEnsureCanCompileStrings(evalRealm, « », x, direct).
115        context
116            .host_hooks()
117            .ensure_can_compile_strings(eval_realm, &[], &x, direct, context)?;
118
119        // 11. Perform the following substeps in an implementation-defined order, possibly interleaving parsing and error detection:
120        //     a. Let script be ParseText(StringToCodePoints(x), Script).
121        //     b. If script is a List of errors, throw a SyntaxError exception.
122        //     c. If script Contains ScriptBody is false, return undefined.
123        //     d. Let body be the ScriptBody of script.
124        let x = x.to_vec();
125        let source = Source::from_utf16(&x);
126
127        let mut parser = Parser::new(source);
128        parser.set_identifier(context.next_parser_identifier());
129        if strict {
130            parser.set_strict();
131        }
132        let (mut body, source) = parser.parse_eval(direct, context.interner_mut())?;
133
134        // 6. Let inFunction be false.
135        // 7. Let inMethod be false.
136        // 8. Let inDerivedConstructor be false.
137        // 9. Let inClassFieldInitializer be false.
138        // a. Let thisEnvRec be GetThisEnvironment().
139        let flags = match {
140            let frame = context.vm.frame();
141            frame
142                .environments
143                .get_this_environment(frame.realm.environment())
144        }
145        .as_function()
146        {
147            // 10. If direct is true, then
148            //     b. If thisEnvRec is a Function Environment Record, then
149            Some(function_env) if direct => {
150                // i. Let F be thisEnvRec.[[FunctionObject]].
151                let function_object = function_env
152                    .slots()
153                    .function_object()
154                    .downcast_ref::<OrdinaryFunction>()
155                    .js_expect("must be function object")?;
156
157                // ii. Set inFunction to true.
158                let mut flags = Flags::IN_FUNCTION;
159
160                // iii. Set inMethod to thisEnvRec.HasSuperBinding().
161                if function_env.has_super_binding() {
162                    flags |= Flags::IN_METHOD;
163                }
164
165                // iv. If F.[[ConstructorKind]] is derived, set inDerivedConstructor to true.
166                if function_object.is_derived_constructor() {
167                    flags |= Flags::IN_DERIVED_CONSTRUCTOR;
168                }
169
170                // v. Let classFieldInitializerName be F.[[ClassFieldInitializerName]].
171                // vi. If classFieldInitializerName is not empty, set inClassFieldInitializer to true.
172                if function_object.in_class_field_initializer() {
173                    flags |= Flags::IN_CLASS_FIELD_INITIALIZER;
174                }
175
176                flags
177            }
178            _ => Flags::default(),
179        };
180
181        if !flags.contains(Flags::IN_FUNCTION) && contains(&body, ContainsSymbol::NewTarget) {
182            return Err(JsNativeError::syntax()
183                .with_message("invalid `new.target` expression inside eval")
184                .into());
185        }
186        if !flags.contains(Flags::IN_METHOD) && contains(&body, ContainsSymbol::SuperProperty) {
187            return Err(JsNativeError::syntax()
188                .with_message("invalid `super` reference inside eval")
189                .into());
190        }
191        if !flags.contains(Flags::IN_DERIVED_CONSTRUCTOR)
192            && contains(&body, ContainsSymbol::SuperCall)
193        {
194            return Err(JsNativeError::syntax()
195                .with_message("invalid `super` call inside eval")
196                .into());
197        }
198        if flags.contains(Flags::IN_CLASS_FIELD_INITIALIZER) && contains_arguments(&body) {
199            return Err(JsNativeError::syntax()
200                .with_message("invalid `arguments` reference inside eval")
201                .into());
202        }
203
204        strict |= body.strict();
205
206        // Because our environment model does not map directly to the spec, this section looks very different.
207        // 12 - 13 are implicit in the call of `Context::compile_with_new_declarative`.
208        // 14 - 33 are in the following section, together with EvalDeclarationInstantiation.
209        let action = if direct {
210            // If the call to eval is direct, the code is executed in the current environment.
211
212            // Poison the last parent function environment, because it may contain new declarations after/during eval.
213            if !strict {
214                {
215                    let frame = context.vm.frame_mut();
216                    let global = frame.realm.environment();
217                    frame.environments.poison_until_last_function(global);
218                }
219            }
220
221            // Set the compile time environment to the current running environment and save the number of current environments.
222            let environments_len = context.vm.frame().environments.len();
223
224            // Pop any added runtime environments that were not removed during the eval execution.
225            EnvStackAction::Truncate(environments_len)
226        } else {
227            // If the call to eval is indirect, the code is executed in the global environment.
228
229            // Pop all environments before the eval execution.
230            let saved = context.vm.frame_mut().environments.pop_to_global();
231
232            // Restore all environments to the state from before the eval execution.
233            EnvStackAction::Restore(saved)
234        };
235
236        let context = &mut context.guard(move |ctx| match action {
237            EnvStackAction::Truncate(len) => ctx.vm.frame_mut().environments.truncate(len),
238            EnvStackAction::Restore(saved) => {
239                ctx.vm.frame_mut().environments.truncate(0);
240                ctx.vm.frame_mut().environments.restore_from_saved(saved);
241            }
242        });
243
244        let (var_environment, mut variable_scope) =
245            if let Some(e) = context.vm.frame().environments.outer_function_environment() {
246                (e.0, e.1)
247            } else {
248                (
249                    context.realm().environment().clone(),
250                    context.realm().scope().clone(),
251                )
252            };
253
254        let lexical_scope = lexical_scope.unwrap_or(context.realm().scope().clone());
255        let lexical_scope = Scope::new(lexical_scope, strict);
256
257        let mut annex_b_function_names = Vec::new();
258
259        prepare_eval_declaration_instantiation(
260            &mut annex_b_function_names,
261            &body,
262            strict,
263            if strict {
264                &lexical_scope
265            } else {
266                &variable_scope
267            },
268            &lexical_scope,
269            context,
270        )?;
271
272        let in_with = context.vm.frame().environments.has_object_environment();
273
274        let source_text = SourceText::new(source);
275        let spanned_source_text = SpannedSourceText::new_source_only(source_text);
276
277        let mut compiler = ByteCompiler::new(
278            js_string!("<eval>"),
279            body.strict(),
280            false,
281            variable_scope.clone(),
282            lexical_scope.clone(),
283            false,
284            false,
285            context.interner_mut(),
286            in_with,
287            spanned_source_text,
288            // TODO: Could give more information from previous shadow stack.
289            SourcePath::Eval,
290        );
291
292        // Increments the number of open environments to
293        // account for the environment scope pushed to
294        // the context at the end of `perform_eval`.
295        compiler.current_open_environments_count += 1;
296
297        if strict {
298            variable_scope = lexical_scope.clone();
299            compiler.variable_scope = lexical_scope.clone();
300        }
301
302        #[cfg(feature = "annex-b")]
303        {
304            compiler
305                .annex_b_function_names
306                .clone_from(&annex_b_function_names);
307        }
308
309        let bindings = body
310            .analyze_scope_eval(
311                strict,
312                &variable_scope,
313                &lexical_scope,
314                &annex_b_function_names,
315                compiler.interner(),
316            )
317            .map_err(|e| JsNativeError::syntax().with_message(e))?;
318
319        compiler.eval_declaration_instantiation(&body, strict, &variable_scope, bindings);
320
321        compiler.compile_statement_list(body.statements(), true, false);
322
323        let code_block = Gc::new(compiler.finish());
324
325        // Strict calls don't need extensions, since all strict eval calls push a new
326        // function environment before evaluating.
327        if !strict {
328            var_environment.extend_from_compile();
329        }
330
331        let env_fp = context.vm.frame().environments.len() as u32;
332        let environments = context.vm.frame().environments.clone();
333        let realm = context.realm().clone();
334        context.vm.push_frame_with_stack(
335            CallFrame::new(code_block.clone(), None, environments, realm)
336                .with_env_fp(env_fp)
337                .with_flags(CallFrameFlags::EXIT_EARLY),
338            JsValue::undefined(),
339            JsValue::null(),
340        );
341
342        context.realm().resize_global_env();
343
344        // Pushing the scope here ensures any function objects created
345        // in `eval_declaration_instantiation` get their proper
346        // environment stack.
347        {
348            let frame = context.vm.frame_mut();
349            let global = frame.realm.environment();
350            frame
351                .environments
352                .push_lexical(lexical_scope.num_bindings_non_local(), global);
353        }
354
355        context
356            .eval_declaration_instantiation(&code_block)
357            .inspect_err(|_| {
358                context.vm.pop_frame();
359            })?;
360
361        let record = context.run();
362        context.vm.pop_frame();
363
364        record.consume()
365    }
366}