use crate::{
ecmascript::{
AbstractModule, Agent, BUILTIN_STRING_MEMORY, ECMAScriptCode, Environment, ExceptionType,
ExecutionContext, GlobalEnvironment, JsResult, LexicallyScopedDeclaration, LoadedModules,
ModuleRequest, ParseResult, PropertyLookupCache, Realm, ScriptOrModule, SourceCode,
SourceCodeType, String, Value, VarScopedDeclaration, instantiate_function_object,
script_lexically_declared_names, script_lexically_scoped_declarations,
script_var_declared_names, script_var_scoped_declarations,
},
engine::{Bindable, Executable, GcScope, NoGcScope, Scopable, Vm, bindable_handle},
heap::{
ArenaAccess, ArenaAccessMut, BaseIndex, CompactionLists, CreateHeapData, Heap,
HeapIndexHandle, HeapMarkAndSweep, WorkQueues, arena_vec_access, index_handle,
},
ndt,
};
use ahash::AHashSet;
use core::any::Any;
use oxc_ast::ast;
use oxc_diagnostics::OxcDiagnostic;
use oxc_ecmascript::BoundNames;
use std::{ptr::NonNull, rc::Rc};
pub type HostDefined = Rc<dyn Any>;
#[derive(Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
#[repr(transparent)]
pub struct Script<'a>(BaseIndex<'a, ScriptRecord<'static>>);
index_handle!(Script);
arena_vec_access!(Script, 'a, ScriptRecord, scripts);
impl core::fmt::Debug for Script<'_> {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
write!(f, "Script({:?})", self.get_index_u32())
}
}
impl Script<'_> {
pub(crate) fn get_statements<'a>(
self,
agent: &Agent,
_: NoGcScope<'a, '_>,
) -> &'a [ast::Statement<'a>] {
unsafe {
core::mem::transmute::<&[ast::Statement], &'a [ast::Statement<'a>]>(
self.get(agent).ecmascript_code.as_ref(),
)
}
}
pub(crate) fn get_source_code<'a>(
self,
agent: &Agent,
gc: NoGcScope<'a, '_>,
) -> SourceCode<'a> {
self.get(agent).source_code.bind(gc)
}
pub(crate) fn realm<'a>(self, agent: &Agent, gc: NoGcScope<'a, '_>) -> Realm<'a> {
self.get(agent).realm.bind(gc)
}
pub(crate) fn host_defined(self, agent: &Agent) -> Option<HostDefined> {
self.get(agent).host_defined.clone()
}
pub(crate) fn insert_loaded_module(
self,
agent: &mut Agent,
request: ModuleRequest,
module: AbstractModule,
) {
let requests = &agent.heap.module_request_records;
self.get_mut(&mut agent.heap.scripts)
.loaded_modules
.insert_loaded_module(requests, request, module);
}
}
impl HeapMarkAndSweep for Script<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
queues.scripts.push(*self);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
compactions.scripts.shift_index(&mut self.0);
}
}
impl<'a> CreateHeapData<ScriptRecord<'a>, Script<'a>> for Heap {
fn create(&mut self, data: ScriptRecord<'a>) -> Script<'a> {
self.scripts.push(data.unbind());
self.alloc_counter += core::mem::size_of::<ScriptRecord<'static>>();
Script(BaseIndex::last(&self.scripts))
}
}
#[derive(Debug)]
pub(crate) struct ScriptRecord<'a> {
realm: Realm<'a>,
pub(crate) ecmascript_code: NonNull<[ast::Statement<'a>]>,
pub(crate) is_strict: bool,
loaded_modules: LoadedModules<'a>,
pub(crate) host_defined: Option<HostDefined>,
pub(crate) source_code: SourceCode<'a>,
}
unsafe impl Send for ScriptRecord<'_> {}
bindable_handle!(ScriptRecord);
impl HeapMarkAndSweep for ScriptRecord<'static> {
fn mark_values(&self, queues: &mut WorkQueues) {
let Self {
realm,
ecmascript_code: _,
is_strict: _,
loaded_modules,
host_defined: _,
source_code,
} = self;
realm.mark_values(queues);
loaded_modules.mark_values(queues);
source_code.mark_values(queues);
}
fn sweep_values(&mut self, compactions: &CompactionLists) {
let Self {
realm,
ecmascript_code: _,
is_strict: _,
loaded_modules,
host_defined: _,
source_code,
} = self;
realm.sweep_values(compactions);
loaded_modules.sweep_values(compactions);
source_code.sweep_values(compactions);
}
}
pub fn parse_script<'a>(
agent: &mut Agent,
source_text: String,
realm: Realm,
strict: bool,
host_defined: Option<HostDefined>,
gc: NoGcScope<'a, '_>,
) -> Result<Script<'a>, Vec<OxcDiagnostic>> {
let source_type = SourceCodeType::Script { strict };
let parse_result = unsafe {
SourceCode::parse_source(
agent,
source_text,
source_type,
#[cfg(feature = "typescript")]
true,
gc,
)
};
let ParseResult {
source_code,
body,
directives: _,
is_strict,
} = match parse_result {
Ok(result) => result,
Err(errors) => {
return Err(errors);
}
};
let script_record = ScriptRecord {
realm: realm.unbind(),
ecmascript_code: NonNull::from(body),
is_strict,
loaded_modules: Default::default(),
host_defined,
source_code: source_code.unbind(),
};
Ok(agent.heap.create(script_record).bind(gc))
}
pub fn script_evaluation<'a>(
agent: &mut Agent,
script: Script,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, Value<'a>> {
let script = script.bind(gc.nogc());
let mut id = 0;
ndt::script_evaluation_start!(|| {
id = create_id(agent, script, gc.nogc());
id
});
let script_record = script.get(agent);
let realm_id = script_record.realm;
let is_strict_mode = script_record.is_strict;
let source_code = script_record.source_code;
let realm = agent.get_realm_record_by_id(realm_id);
let global_env = realm.global_env.unwrap().bind(gc.nogc());
let script_context = ExecutionContext {
function: None,
realm: realm_id.unbind(),
script_or_module: Some(ScriptOrModule::Script(script.unbind())),
ecmascript_code: Some(ECMAScriptCode {
variable_environment: Environment::Global(global_env.unbind()),
lexical_environment: Environment::Global(global_env.unbind()),
private_environment: None,
is_strict_mode,
source_code: source_code.unbind(),
}),
};
agent.push_execution_context(script_context);
let result = unsafe {
global_declaration_instantiation(agent, script.unbind(), global_env.unbind(), gc.reborrow())
.unbind()
.bind(gc.nogc())
};
let Some(ScriptOrModule::Script(script)) = agent.running_execution_context().script_or_module
else {
panic!("Expected Script");
};
let script = script.bind(gc.nogc());
let result: JsResult<Value> = match result {
Ok(_) => {
let bytecode =
Executable::compile_script(agent, script, gc.nogc()).scope(agent, gc.nogc());
let result = Vm::execute(agent, bytecode.clone(), None, gc.reborrow())
.into_js_result()
.unbind()
.bind(gc.into_nogc());
unsafe { bytecode.take(agent).try_drop(agent) };
result
}
Err(err) => Err(err.unbind().bind(gc.into_nogc())),
};
_ = agent.pop_execution_context();
ndt::script_evaluation_done!(|| id);
result
}
#[inline(never)]
fn create_id(agent: &Agent, script: Script, gc: NoGcScope) -> u64 {
u64::try_from(script.get_statements(agent, gc).as_ptr().addr()).unwrap()
}
unsafe fn global_declaration_instantiation<'a>(
agent: &mut Agent,
script: Script,
env: GlobalEnvironment,
mut gc: GcScope<'a, '_>,
) -> JsResult<'a, ()> {
let script = script.bind(gc.nogc());
let env = env.bind(gc.nogc());
let scoped_env = env.scope(agent, gc.nogc());
let (lex_names, var_names, var_declarations, lex_declarations) = {
let body = unsafe { script.unbind().get(agent).ecmascript_code.as_ref() };
let lex_names = script_lexically_declared_names(body);
let var_names = script_var_declared_names(body);
let var_declarations = script_var_scoped_declarations(body);
let lex_declarations = script_lexically_scoped_declarations(body);
(lex_names, var_names, var_declarations, lex_declarations)
};
for name in lex_names {
let env = scoped_env.get(agent).bind(gc.nogc());
let name_atom = name;
let name = String::from_str(agent, name.as_str(), gc.nogc());
if
env.has_var_declaration(agent, name)
|| env.has_lexical_declaration(agent, name)
|| env.unbind().has_restricted_global_property(agent, name.unbind(), gc.reborrow()).unbind()?.bind(gc.nogc())
{
let error_message = format!(
"Redeclaration of restricted global property '{}'.",
name_atom.as_str()
);
return Err(agent.throw_exception(
ExceptionType::SyntaxError,
error_message,
gc.into_nogc(),
));
}
}
let env = scoped_env.get(agent).bind(gc.nogc());
for name in &var_names {
let name = String::from_str(agent, name.as_str(), gc.nogc());
if env.has_lexical_declaration(agent, name) {
let error_message = format!(
"Redeclaration of lexical binding '{}'.",
name.to_string_lossy_(agent)
);
return Err(agent.throw_exception(
ExceptionType::SyntaxError,
error_message,
gc.into_nogc(),
));
}
}
let mut functions_to_initialize = vec![];
let mut declared_function_names = AHashSet::default();
for d in var_declarations.iter().rev() {
if let VarScopedDeclaration::Function(d) = *d {
let mut function_name = None;
d.bound_names(&mut |identifier| {
assert!(function_name.is_none());
function_name = Some(identifier.name);
});
let function_name = function_name.unwrap();
if declared_function_names.insert(function_name) {
let fn_name = String::from_str(agent, function_name.as_str(), gc.nogc());
let fn_definable = scoped_env
.get(agent)
.can_declare_global_function(agent, fn_name.unbind(), gc.reborrow())
.unbind()?
.bind(gc.nogc());
if !fn_definable {
let error_message = format!(
"Cannot declare of global function '{}'.",
function_name.as_str()
);
return Err(agent.throw_exception(
ExceptionType::TypeError,
error_message,
gc.into_nogc(),
));
}
functions_to_initialize.push(d);
}
}
}
let mut declared_var_names = AHashSet::default();
for d in var_declarations {
if let VarScopedDeclaration::Variable(d) = d {
let mut bound_names = vec![];
d.id.bound_names(&mut |identifier| {
bound_names.push(identifier.name);
});
for vn in bound_names {
if !declared_function_names.contains(&vn) {
let vn = String::from_str(agent, vn.as_str(), gc.nogc()).unbind();
let vn_definable = scoped_env
.get(agent)
.can_declare_global_var(agent, vn, gc.reborrow())
.unbind()?
.bind(gc.nogc());
if !vn_definable {
let error_message = format!(
"Cannot declare global variable '{}'.",
vn.to_string_lossy_(agent)
);
return Err(agent.throw_exception(
ExceptionType::TypeError,
error_message,
gc.into_nogc(),
));
}
declared_var_names.insert(vn);
}
}
}
}
let private_env = None;
let env = scoped_env.get(agent).bind(gc.nogc());
for d in lex_declarations {
let mut bound_names = vec![];
let mut const_bound_names = vec![];
let mut closure = |identifier: &ast::BindingIdentifier| {
bound_names.push(String::from_str(agent, identifier.name.as_str(), gc.nogc()));
};
match d {
LexicallyScopedDeclaration::Variable(decl) => {
if decl.kind == ast::VariableDeclarationKind::Const {
decl.id.bound_names(&mut |identifier| {
const_bound_names.push(String::from_str(
agent,
identifier.name.as_str(),
gc.nogc(),
))
});
} else {
decl.id.bound_names(&mut closure)
}
}
LexicallyScopedDeclaration::Function(decl) => decl.bound_names(&mut closure),
LexicallyScopedDeclaration::Class(decl) => decl.bound_names(&mut closure),
#[cfg(feature = "typescript")]
LexicallyScopedDeclaration::TSEnum(decl) => decl.id.bound_names(&mut closure),
LexicallyScopedDeclaration::DefaultExport => {
bound_names.push(BUILTIN_STRING_MEMORY._default_)
}
}
for dn in const_bound_names {
env.create_immutable_binding(agent, dn, true, gc.nogc())
.unbind()?
.bind(gc.nogc());
}
for dn in bound_names {
env.create_mutable_binding(agent, dn, false, gc.nogc())
.unbind()?
.bind(gc.nogc());
}
}
for f in functions_to_initialize {
let mut function_name = None;
f.bound_names(&mut |identifier| {
assert!(function_name.is_none());
function_name = Some(identifier.name);
});
let env = scoped_env.get(agent).bind(gc.nogc());
let fo =
instantiate_function_object(agent, f, Environment::Global(env), private_env, gc.nogc());
let function_name = String::from_str(agent, function_name.unwrap().as_str(), gc.nogc());
env.unbind()
.create_global_function_binding(
agent,
function_name.unbind(),
fo.unbind().into(),
false,
gc.reborrow(),
)
.unbind()?
.bind(gc.nogc());
}
for vn in declared_var_names {
let cache = PropertyLookupCache::new(agent, vn.to_property_key());
scoped_env
.get(agent)
.create_global_var_binding(agent, vn, cache, false, gc.reborrow())
.unbind()?
.bind(gc.nogc());
}
Ok(())
}
#[cfg(test)]
mod test {
use std::ops::ControlFlow;
use crate::{
ecmascript::{
Agent, AgentOptions, ArgumentsList, Array, BUILTIN_STRING_MEMORY, Behaviour,
BuiltinFunctionArgs, DefaultHostHooks, ExceptionType, InternalMethods, JsResult,
Number, Object, PropertyKey, SmallInteger, String, TryHasBindingContinue, TryHasResult,
Value, create_builtin_function, create_data_property_or_throw,
initialize_default_realm, parse_script, script_evaluation, unwrap_try,
},
engine::{Bindable, GcScope, Scopable},
heap::ArenaAccess,
};
#[test]
fn empty_script() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Undefined);
}
#[test]
fn basic_constants() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "true", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, true.into());
}
#[test]
fn unary_minus() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "-2", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, (-2).into());
}
#[test]
fn unary_void() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "void (2 + 2 + 6)", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Undefined);
}
#[test]
fn unary_plus() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "+(54)", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, (54).into());
}
#[test]
fn logical_not() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "!true", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, (false).into());
}
#[test]
fn bitwise_not() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "~0b1111", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, (-16).into());
}
#[test]
fn unary_typeof() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "typeof undefined", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "undefined", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "typeof null", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "object", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "typeof \"string\"", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "string", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "typeof Symbol()", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "symbol", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "typeof true", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "boolean", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "typeof 3", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "number", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "typeof 3n", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "bigint", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "typeof {}", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "object", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "typeof (function() {})", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "function", gc.nogc())
);
}
#[test]
fn binary_add() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "2 + 2 + 6", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, (10).into());
}
#[test]
fn var_assign() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "var foo = 3;", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Undefined);
}
#[test]
fn empty_object() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "var foo = {};", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert!(result.is_undefined());
let key = PropertyKey::from_static_str(&mut agent, "foo", gc.nogc());
let foo = unwrap_try(agent.current_global_object(gc.nogc()).try_get_own_property(
&mut agent,
key,
None,
gc.nogc(),
))
.unwrap()
.value
.unwrap();
assert!(foo.is_object());
let result = Object::try_from(foo).unwrap();
assert!(
result
.unbind()
.internal_own_property_keys(&mut agent, gc)
.unwrap()
.is_empty()
);
}
#[test]
fn non_empty_object() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "var foo = { a: 3 };", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert!(result.is_undefined());
let key = PropertyKey::from_static_str(&mut agent, "foo", gc.nogc());
let foo = unwrap_try(agent.current_global_object(gc.nogc()).try_get_own_property(
&mut agent,
key,
None,
gc.nogc(),
))
.unwrap()
.value
.unwrap();
assert!(foo.is_object());
let result = Object::try_from(foo).unwrap();
let key = PropertyKey::from_static_str(&mut agent, "a", gc.nogc());
assert_eq!(
result.try_has_property(&mut agent, key, None, gc.nogc()),
ControlFlow::Continue(TryHasResult::Offset(0, result))
);
assert_eq!(
unwrap_try(result.try_get_own_property(&mut agent, key, None, gc.nogc()))
.unwrap()
.value,
Some(Value::from(3))
);
}
#[test]
fn empty_array() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "var foo = [];", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert!(result.is_undefined());
let foo_key = String::from_static_str(&mut agent, "foo", gc.nogc());
let foo = unwrap_try(agent.current_global_env(gc.nogc()).try_get_binding_value(
&mut agent,
foo_key,
None,
true,
gc.nogc(),
));
assert!(foo.is_object());
let result = Object::try_from(foo).unwrap();
let keys = unwrap_try(result.try_own_property_keys(&mut agent, gc.nogc()));
assert_eq!(keys.len(), 1);
assert_eq!(keys[0], BUILTIN_STRING_MEMORY.length.to_property_key());
}
#[test]
fn non_empty_array() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "var foo = [ 'a', 3 ];", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert!(result.is_undefined());
let foo_key = String::from_static_str(&mut agent, "foo", gc.nogc());
let foo = unwrap_try(agent.current_global_env(gc.nogc()).try_get_binding_value(
&mut agent,
foo_key,
None,
true,
gc.nogc(),
));
assert!(foo.is_object());
let result = Array::try_from(foo).unwrap();
let key = PropertyKey::Integer(0.into());
assert_eq!(
result.try_has_property(&mut agent, key, None, gc.nogc()),
ControlFlow::Continue(TryHasResult::Custom(0, result.into()))
);
assert_eq!(
unwrap_try(result.try_get_own_property(&mut agent, key, None, gc.nogc()))
.unwrap()
.value,
Some(Value::from_static_str(&mut agent, "a", gc.nogc()))
);
let key = PropertyKey::Integer(1.into());
assert_eq!(
result.try_has_property(&mut agent, key, None, gc.nogc()),
ControlFlow::Continue(TryHasResult::Custom(1, result.into()))
);
assert_eq!(
unwrap_try(result.try_get_own_property(&mut agent, key, None, gc.nogc()))
.unwrap()
.value,
Some(Value::from(3))
);
}
#[test]
fn empty_function() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "function foo() {}", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert!(result.is_undefined());
let source_text =
String::from_static_str(&mut agent, "let i = 0; const a = 'foo'; i = 3;", gc.nogc());
agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
let global_env = agent.current_global_env(gc.nogc());
let foo_key = String::from_static_str(&mut agent, "foo", gc.nogc());
assert!(matches!(
global_env.try_has_binding(&mut agent, foo_key, None, gc.nogc()),
ControlFlow::Continue(TryHasBindingContinue::Result(true))
));
assert!(
unwrap_try(global_env.try_get_binding_value(
&mut agent,
foo_key,
None,
true,
gc.nogc()
))
.is_function(),
);
}
#[test]
fn empty_iife_function_call() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "(function() {})()", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert!(result.is_undefined());
}
#[test]
fn empty_named_function_call() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "var f = function() {}; f();", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert!(result.is_undefined());
}
#[test]
fn empty_declared_function_call() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "function f() {}; f();", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert!(result.is_undefined());
}
#[test]
fn non_empty_iife_function_call() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "(function() { return 3 })()", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Number::from(3).into());
}
#[test]
fn builtin_function_call() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let global = agent.current_global_object(gc.nogc());
fn test_builtin_function<'a>(
_: &mut Agent,
_: Value,
arguments: ArgumentsList,
_: GcScope<'a, '_>,
) -> JsResult<'a, Value<'a>> {
let arg_0 = arguments.get(0);
if Value::Boolean(true) == arg_0 {
Ok(Value::from(3))
} else {
Ok(Value::Null)
}
}
let func = create_builtin_function(
&mut agent,
Behaviour::Regular(test_builtin_function),
BuiltinFunctionArgs::new(1, "test"),
gc.nogc(),
);
let key = PropertyKey::from_static_str(&mut agent, "test", gc.nogc());
create_data_property_or_throw(
&mut agent,
global.unbind(),
key.unbind(),
func.unbind().into(),
gc.reborrow(),
)
.unwrap();
let source_text = String::from_static_str(&mut agent, "test(true)", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::from(3));
let source_text = String::from_static_str(&mut agent, "test()", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Null);
let source_text = String::from_static_str(&mut agent, "test({})", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Null);
}
#[test]
fn if_statement() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "if (true) 3", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Number::from(3).into());
let source_text = String::from_static_str(&mut agent, "if (false) 3", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Undefined);
}
#[test]
fn if_else_statement() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"var foo = function() { if (true) { return 3; } else { return 5; } }; foo()",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Number::from(3).into());
let source_text = String::from_static_str(
&mut agent,
"var bar = function() { if (false) { return 3; } else { return 5; } }; bar()",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Number::from(5).into());
}
#[test]
fn static_property_access() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "var foo = { a: 3 }; foo.a", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Number::from(3).into());
}
#[test]
fn deep_static_property_access() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"var fn = function() { return 3; }; var foo = { a: { b: fn } }; foo.a.b()",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Number::from(3).into());
}
#[test]
fn computed_property_access() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"var foo = { a: 3 }; var prop = 'a'; foo[prop]",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Number::from(3).into());
}
#[test]
fn for_loop() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "var i = 0; for (; i < 3; i++) {}", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Undefined);
let key = PropertyKey::from_static_str(&mut agent, "i", gc.nogc());
let i: Value = unwrap_try(agent.current_global_object(gc.nogc()).try_get_own_property(
&mut agent,
key,
None,
gc.nogc(),
))
.unwrap()
.value
.unwrap();
assert_eq!(i, Value::from(3));
}
#[test]
fn lexical_declarations() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "let i = 0; const a = 'foo'; i = 3;", gc.nogc());
agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
let realm = agent.current_realm(gc.nogc());
let global_env = agent
.get_realm_record_by_id(realm)
.global_env
.unwrap()
.bind(gc.nogc());
let a_key = String::from_static_str(&mut agent, "a", gc.nogc());
let i_key = String::from_static_str(&mut agent, "i", gc.nogc());
assert!(matches!(
global_env.try_has_binding(&mut agent, a_key, None, gc.nogc()),
ControlFlow::Continue(TryHasBindingContinue::Result(true))
));
assert!(matches!(
global_env.try_has_binding(&mut agent, i_key, None, gc.nogc()),
ControlFlow::Continue(TryHasBindingContinue::Result(true))
));
assert_eq!(
unwrap_try(global_env.try_get_binding_value(&mut agent, a_key, None, true, gc.nogc())),
String::from_small_string("foo").into()
);
assert_eq!(
unwrap_try(global_env.try_get_binding_value(&mut agent, i_key, None, true, gc.nogc())),
Value::from(3)
);
}
#[test]
fn lexical_declarations_in_block() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"{ let i = 0; const a = 'foo'; i = 3; }",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, 3.into());
let realm = agent.current_realm(gc.nogc());
let a_key = String::from_static_str(&mut agent, "a", gc.nogc());
let i_key = String::from_static_str(&mut agent, "i", gc.nogc());
let global_env = agent
.get_realm_record_by_id(realm)
.global_env
.unwrap()
.bind(gc.nogc());
assert!(!global_env.has_lexical_declaration(&agent, a_key));
assert!(!global_env.has_lexical_declaration(&agent, i_key));
}
#[test]
fn object_property_assignment() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "var foo = {}; foo.a = 42; foo", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
let object = Object::try_from(result).unwrap().unbind().bind(gc.nogc());
let pk = PropertyKey::from_static_str(&mut agent, "a", gc.nogc());
assert_eq!(
object
.unbind()
.internal_get(&mut agent, pk.unbind(), object.unbind().into(), gc)
.unwrap(),
Value::Integer(SmallInteger::from(42))
);
}
#[test]
fn try_catch_not_thrown() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"let a = 0; try { a++; } catch { a = 500; }; a++; a",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Integer(SmallInteger::from(2)));
}
#[test]
fn try_catch_thrown() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"let a = 0; try { throw null; a = 500 } catch { a++; }; a++; a",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Integer(SmallInteger::from(2)));
}
#[test]
fn catch_binding() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"let err; try { throw 'thrown'; } catch(e) { err = e; }; err",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "thrown", gc.nogc())
);
}
#[test]
fn throwing_in_try_restores_lexical_environment() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"let a = 42; try { let a = 62; throw 'thrown'; } catch { }; a",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Integer(SmallInteger::from(42)));
}
#[test]
fn function_argument_bindings() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"const foo = function (a) { return a + 10; }; foo(32)",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Integer(SmallInteger::from(42)));
}
#[test]
fn logical_and() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "true && true", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Boolean(true));
let source_text = String::from_static_str(&mut agent, "true && false && true", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Boolean(false));
}
#[test]
fn logical_or() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "false || false", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Boolean(false));
let source_text = String::from_static_str(&mut agent, "true || false || true", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Boolean(true));
}
#[test]
fn nullish_coalescing() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "null ?? 42", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Integer(SmallInteger::from(42)));
let source_text = String::from_static_str(&mut agent, "'foo' ?? 12", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "foo", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "undefined ?? null", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Null);
}
#[test]
fn string_concat() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "'foo' + '' + 'bar'", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "foobar", gc.nogc())
);
let source_text =
String::from_static_str(&mut agent, "'foo' + ' a heap string'", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "foo a heap string", gc.nogc())
);
let source_text = String::from_static_str(
&mut agent,
"'Concatenating ' + 'two heap strings'",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "Concatenating two heap strings", gc.nogc())
);
}
#[test]
fn property_access_on_functions() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "function foo() {}; foo.bar", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Undefined);
let source_text = String::from_static_str(&mut agent, "foo.bar = 42; foo.bar", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Integer(SmallInteger::from(42)));
let source_text = String::from_static_str(&mut agent, "foo.name", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "foo", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "foo.length", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Integer(SmallInteger::zero()));
let source_text = String::from_static_str(&mut agent, "foo.prototype", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert!(result.is_object())
}
#[test]
fn name_and_length_on_builtin_functions() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "TypeError.name", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "TypeError", gc.nogc())
);
let source_text = String::from_static_str(&mut agent, "TypeError.length", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Integer(SmallInteger::from(1)));
}
#[test]
fn constructor() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "function foo() {}; foo.prototype", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
let foo_prototype = Object::try_from(result)
.unwrap()
.unbind()
.scope(&mut agent, gc.nogc());
let source_text = String::from_static_str(&mut agent, "new foo()", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
let instance = Object::try_from(result).unwrap();
assert_eq!(
unwrap_try(
instance
.unbind()
.try_get_prototype_of(&mut agent, gc.nogc())
)
.unwrap(),
foo_prototype.get(&agent)
);
}
#[test]
fn this_expression() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"function foo() { this.bar = 42; }; new foo().bar",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Integer(SmallInteger::from(42)));
let source_text = String::from_static_str(
&mut agent,
"foo.prototype.baz = function() { return this.bar + 10; }; (new foo()).baz()",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Integer(SmallInteger::from(52)));
}
#[test]
fn symbol_stringification() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "+Symbol()", gc.nogc());
let result = agent.run_script(source_text.unbind(), gc.reborrow());
assert!(result.is_err());
let result = result.unwrap_err().value();
let Value::Error(result) = result else {
unreachable!()
};
assert_eq!(result.get(&agent).kind, ExceptionType::TypeError);
let source_text = String::from_static_str(&mut agent, "+Symbol('foo')", gc.nogc());
let result = agent.run_script(source_text.unbind(), gc.reborrow());
assert!(result.is_err());
let result = result.unwrap_err().value();
let Value::Error(result) = result else {
unreachable!()
};
assert_eq!(result.get(&agent).kind, ExceptionType::TypeError);
let source_text = String::from_static_str(&mut agent, "String(Symbol())", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(
result.unbind(),
Value::from_static_str(&mut agent, "Symbol()", gc.nogc())
);
let realm = agent.current_realm(gc.nogc());
let source_text = String::from_static_str(&mut agent, "String(Symbol('foo'))", gc.nogc());
let script = parse_script(&mut agent, source_text, realm, false, None, gc.nogc()).unwrap();
let value = script_evaluation(&mut agent, script.unbind(), gc.reborrow()).unwrap();
assert_eq!(
value.unbind(),
Value::from_static_str(&mut agent, "Symbol(foo)", gc.nogc())
);
}
#[test]
fn instanceof() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(&mut agent, "3 instanceof Number", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, false.into());
let source_text = String::from_static_str(&mut agent, "'foo' instanceof String", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, false.into());
let source_text = String::from_static_str(&mut agent, "({}) instanceof Object", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, true.into());
let source_text = String::from_static_str(&mut agent, "({}) instanceof Array", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, false.into());
let source_text = String::from_static_str(&mut agent, "([]) instanceof Object", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, true.into());
let source_text = String::from_static_str(&mut agent, "([]) instanceof Array", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, true.into());
}
#[test]
fn array_binding_pattern() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "const [a, b, , c] = [1, 2, 3, 4];", gc.nogc());
agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
let a_key = String::from_static_str(&mut agent, "a", gc.nogc());
let b_key = String::from_static_str(&mut agent, "b", gc.nogc());
let c_key = String::from_static_str(&mut agent, "c", gc.nogc());
let realm = agent.current_realm(gc.nogc());
let global_env = agent
.get_realm_record_by_id(realm)
.global_env
.unwrap()
.bind(gc.nogc());
assert!(global_env.has_lexical_declaration(&agent, a_key));
assert!(global_env.has_lexical_declaration(&agent, b_key));
assert!(global_env.has_lexical_declaration(&agent, c_key));
assert_eq!(
unwrap_try(global_env.try_get_binding_value(&mut agent, a_key, None, true, gc.nogc())),
1.into()
);
assert_eq!(
unwrap_try(global_env.try_get_binding_value(&mut agent, b_key, None, true, gc.nogc())),
2.into()
);
assert_eq!(
unwrap_try(global_env.try_get_binding_value(&mut agent, c_key, None, true, gc.nogc())),
4.into()
);
}
#[test]
fn do_while() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "let i = 0; do { i++ } while(i < 10)", gc.nogc());
agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
let realm = agent.current_realm(gc.nogc());
let i_key = String::from_static_str(&mut agent, "i", gc.nogc());
let global_env = agent
.get_realm_record_by_id(realm)
.global_env
.unwrap()
.bind(gc.nogc());
assert!(global_env.has_lexical_declaration(&agent, i_key));
assert_eq!(
unwrap_try(global_env.try_get_binding_value(
&mut agent,
i_key.unbind(),
None,
true,
gc.nogc()
)),
10.into()
);
}
#[test]
fn no_implicit_return() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text =
String::from_static_str(&mut agent, "function foo() { 42; }; foo()", gc.nogc());
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap();
assert_eq!(result, Value::Undefined);
}
#[test]
fn for_in_loop() {
let (mut gc, mut scope) = unsafe { GcScope::create_root() };
let mut gc = GcScope::new(&mut gc, &mut scope);
let mut agent = Agent::new(AgentOptions::default(), &DefaultHostHooks);
initialize_default_realm(&mut agent, gc.reborrow());
let source_text = String::from_static_str(
&mut agent,
"for (let i in { a: 1, b: 2, c: 3 }) { i; }",
gc.nogc(),
);
let result = agent
.run_script(source_text.unbind(), gc.reborrow())
.unwrap()
.unbind()
.bind(gc.nogc());
assert_eq!(result, Value::from_static_str(&mut agent, "c", gc.nogc()));
}
}