use std::rc::Rc;
use crate::{
builtins::{function::OrdinaryFunction, BuiltInObject},
bytecompiler::{eval_declaration_instantiation_context, ByteCompiler},
context::intrinsics::Intrinsics,
environments::{CompileTimeEnvironment, Environment},
error::JsNativeError,
js_string,
object::JsObject,
realm::Realm,
string::StaticJsStrings,
vm::{CallFrame, CallFrameFlags, Opcode},
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_ast::operations::{contains, contains_arguments, ContainsSymbol};
use boa_gc::Gc;
use boa_parser::{Parser, Source};
use boa_profiler::Profiler;
use super::{BuiltInBuilder, IntrinsicObject};
#[derive(Debug, Clone, Copy)]
pub(crate) struct Eval;
impl IntrinsicObject for Eval {
fn init(realm: &Realm) {
let _timer = Profiler::global().start_event(std::any::type_name::<Self>(), "init");
BuiltInBuilder::callable_with_intrinsic::<Self>(realm, Self::eval)
.name(Self::NAME)
.length(1)
.build();
}
fn get(intrinsics: &Intrinsics) -> JsObject {
intrinsics.objects().eval().into()
}
}
impl BuiltInObject for Eval {
const NAME: JsString = StaticJsStrings::EVAL;
}
impl Eval {
fn eval(_: &JsValue, args: &[JsValue], context: &mut Context) -> JsResult<JsValue> {
Self::perform_eval(args.get_or_undefined(0), false, false, context)
}
pub(crate) fn perform_eval(
x: &JsValue,
direct: bool,
mut strict: bool,
context: &mut Context,
) -> JsResult<JsValue> {
bitflags::bitflags! {
#[derive(Default)]
struct Flags: u8 {
const IN_FUNCTION = 0b0001;
const IN_METHOD = 0b0010;
const IN_DERIVED_CONSTRUCTOR = 0b0100;
const IN_CLASS_FIELD_INITIALIZER = 0b1000;
}
}
#[derive(Debug)]
enum EnvStackAction {
Truncate(usize),
Restore(Vec<Environment>),
}
debug_assert!(direct || !strict);
let Some(x) = x.as_string() else {
return Ok(x.clone());
};
let eval_realm = context.realm().clone();
context
.host_hooks()
.ensure_can_compile_strings(eval_realm, &[], x, direct, context)?;
let x = x.to_vec();
let mut parser = Parser::new(Source::from_utf16(&x));
parser.set_identifier(context.next_parser_identifier());
if strict {
parser.set_strict();
}
let body = parser.parse_eval(direct, context.interner_mut())?;
let flags = match context.vm.environments.get_this_environment().as_function() {
Some(function_env) if direct => {
let function_object = function_env.slots().function_object().borrow();
let mut flags = Flags::IN_FUNCTION;
if function_env.has_super_binding() {
flags |= Flags::IN_METHOD;
}
let function_object = function_object
.downcast_ref::<OrdinaryFunction>()
.expect("must be function object");
if function_object.is_derived_constructor() {
flags |= Flags::IN_DERIVED_CONSTRUCTOR;
}
if function_object.in_class_field_initializer() {
flags |= Flags::IN_CLASS_FIELD_INITIALIZER;
}
flags
}
_ => Flags::default(),
};
if !flags.contains(Flags::IN_FUNCTION) && contains(&body, ContainsSymbol::NewTarget) {
return Err(JsNativeError::syntax()
.with_message("invalid `new.target` expression inside eval")
.into());
}
if !flags.contains(Flags::IN_METHOD) && contains(&body, ContainsSymbol::SuperProperty) {
return Err(JsNativeError::syntax()
.with_message("invalid `super` reference inside eval")
.into());
}
if !flags.contains(Flags::IN_DERIVED_CONSTRUCTOR)
&& contains(&body, ContainsSymbol::SuperCall)
{
return Err(JsNativeError::syntax()
.with_message("invalid `super` call inside eval")
.into());
}
if flags.contains(Flags::IN_CLASS_FIELD_INITIALIZER) && contains_arguments(&body) {
return Err(JsNativeError::syntax()
.with_message("invalid `arguments` reference inside eval")
.into());
}
strict |= body.strict();
let action = if direct {
if !strict {
context.vm.environments.poison_until_last_function();
}
let environments_len = context.vm.environments.len();
EnvStackAction::Truncate(environments_len)
} else {
let environments = context.vm.environments.pop_to_global();
EnvStackAction::Restore(environments)
};
let context = &mut context.guard(move |ctx| match action {
EnvStackAction::Truncate(len) => ctx.vm.environments.truncate(len),
EnvStackAction::Restore(envs) => {
ctx.vm.environments.truncate(0);
ctx.vm.environments.extend(envs);
}
});
let var_environment = context.vm.environments.outer_function_environment().clone();
let mut var_env = var_environment.compile_env();
let lex_env = context.vm.environments.current_compile_environment();
let lex_env = Rc::new(CompileTimeEnvironment::new(lex_env, strict));
let mut annex_b_function_names = Vec::new();
eval_declaration_instantiation_context(
&mut annex_b_function_names,
&body,
strict,
if strict { &lex_env } else { &var_env },
&lex_env,
context,
)?;
let in_with = context.vm.environments.has_object_environment();
let mut compiler = ByteCompiler::new(
js_string!("<main>"),
body.strict(),
false,
var_env.clone(),
lex_env.clone(),
context.interner_mut(),
in_with,
);
compiler.current_open_environments_count += 1;
let env_index = compiler.constants.len() as u32;
compiler
.constants
.push(crate::vm::Constant::CompileTimeEnvironment(lex_env.clone()));
compiler.emit_with_varying_operand(Opcode::PushDeclarativeEnvironment, env_index);
if strict {
var_env = lex_env.clone();
compiler.variable_environment = lex_env.clone();
}
#[cfg(feature = "annex-b")]
{
compiler.annex_b_function_names = annex_b_function_names;
}
compiler.eval_declaration_instantiation(&body, strict, &var_env, &lex_env);
compiler.compile_statement_list(body.statements(), true, false);
let code_block = Gc::new(compiler.finish());
if !strict {
var_environment.extend_from_compile();
}
let env_fp = context.vm.environments.len() as u32;
let environments = context.vm.environments.clone();
let realm = context.realm().clone();
context.vm.push_frame_with_stack(
CallFrame::new(code_block, None, environments, realm)
.with_env_fp(env_fp)
.with_flags(CallFrameFlags::EXIT_EARLY),
JsValue::undefined(),
JsValue::null(),
);
context.realm().resize_global_env();
let record = context.run();
context.vm.pop_frame();
record.consume()
}
}