use crate::{
Context, JsArgs, JsResult, JsString, JsValue, SpannedSourceText,
builtins::{BuiltInObject, function::OrdinaryFunction},
bytecompiler::{ByteCompiler, eval_declaration_instantiation_context},
context::intrinsics::Intrinsics,
environments::Environment,
error::JsNativeError,
js_string,
object::JsObject,
realm::Realm,
spanned_source_text::SourceText,
string::StaticJsStrings,
vm::{CallFrame, CallFrameFlags, Constant, source_info::SourcePath},
};
use boa_ast::{
operations::{ContainsSymbol, contains, contains_arguments},
scope::Scope,
};
use boa_gc::Gc;
use boa_parser::{Parser, Source};
use super::{BuiltInBuilder, IntrinsicObject};
#[derive(Debug, Clone, Copy)]
pub(crate) struct Eval;
impl IntrinsicObject for Eval {
fn init(realm: &Realm) {
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, None, false, context)
}
pub(crate) fn perform_eval(
x: &JsValue,
direct: bool,
lexical_scope: Option<Scope>,
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 source = Source::from_utf16(&x);
let mut parser = Parser::new(source);
parser.set_identifier(context.next_parser_identifier());
if strict {
parser.set_strict();
}
let (mut body, source) = 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()
.downcast_ref::<OrdinaryFunction>()
.expect("must be function object");
let mut flags = Flags::IN_FUNCTION;
if function_env.has_super_binding() {
flags |= Flags::IN_METHOD;
}
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, mut variable_scope) =
if let Some(e) = context.vm.environments.outer_function_environment() {
(e.0, e.1)
} else {
(
context.realm().environment().clone(),
context.realm().scope().clone(),
)
};
let lexical_scope = lexical_scope.unwrap_or(context.realm().scope().clone());
let lexical_scope = Scope::new(lexical_scope, strict);
let mut annex_b_function_names = Vec::new();
eval_declaration_instantiation_context(
&mut annex_b_function_names,
&body,
strict,
if strict {
&lexical_scope
} else {
&variable_scope
},
&lexical_scope,
context,
)?;
let in_with = context.vm.environments.has_object_environment();
let source_text = SourceText::new(source);
let spanned_source_text = SpannedSourceText::new_source_only(source_text);
let mut compiler = ByteCompiler::new(
js_string!("<eval>"),
body.strict(),
false,
variable_scope.clone(),
lexical_scope.clone(),
false,
false,
context.interner_mut(),
in_with,
spanned_source_text,
SourcePath::Eval,
);
compiler.current_open_environments_count += 1;
let scope_index = compiler.constants.len() as u32;
compiler
.constants
.push(Constant::Scope(lexical_scope.clone()));
compiler.bytecode.emit_push_scope(scope_index.into());
if strict {
variable_scope = lexical_scope.clone();
compiler.variable_scope = lexical_scope.clone();
}
#[cfg(feature = "annex-b")]
{
compiler
.annex_b_function_names
.clone_from(&annex_b_function_names);
}
let bindings = body
.analyze_scope_eval(
strict,
&variable_scope,
&lexical_scope,
&annex_b_function_names,
compiler.interner(),
)
.map_err(|e| JsNativeError::syntax().with_message(e))?;
compiler.eval_declaration_instantiation(&body, strict, &variable_scope, bindings);
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()
}
}