use crate::{
builtins::BuiltInObject,
bytecompiler::ByteCompiler,
context::intrinsics::Intrinsics,
environments::Environment,
error::JsNativeError,
object::JsObject,
realm::Realm,
vm::{CallFrame, Opcode},
Context, JsArgs, JsResult, JsString, JsValue,
};
use boa_ast::operations::{contains, contains_arguments, ContainsSymbol};
use boa_gc::Gc;
use boa_interner::Sym;
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(Self::NAME, "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: &'static str = "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().map(JsString::to_std_string_escaped) else {
return Ok(x.clone());
};
context
.host_hooks()
.ensure_can_compile_strings(context.realm().clone(), context)?;
let mut parser = Parser::new(Source::from_bytes(&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
.as_function()
.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(1);
ctx.vm.environments.extend(envs);
}
});
let mut compiler = ByteCompiler::new(
Sym::MAIN,
body.strict(),
false,
context.vm.environments.current_compile_environment(),
context,
);
compiler.push_compile_environment(strict);
let push_env = compiler.emit_opcode_with_operand(Opcode::PushDeclarativeEnvironment);
compiler.eval_declaration_instantiation(&body, strict)?;
compiler.compile_statement_list(body.statements(), true, false);
let env_index = compiler.pop_compile_environment();
compiler.patch_jump_with_target(push_env, env_index);
compiler.emit_opcode(Opcode::PopEnvironment);
let code_block = Gc::new(compiler.finish());
if direct && !strict {
context.vm.environments.extend_outer_function_environment();
}
context.vm.push_frame(CallFrame::new(code_block));
context.realm().resize_global_env();
let record = context.run();
context.vm.pop_frame();
record.consume()
}
}